Account API
Automate dashboard operations with scoped API keys: products, licenses, access rules, analytics, and billing.
The Account API (also called the management API) lets sellers and organizations automate everything they can do in the dashboard. Create products, issue licenses, manage access rules, review license requests, and read billing data — all with scoped API keys.
The Account API is a paid plan feature gated by the API access permission on your seller plan.
Base URL
https://pingless-license-system.vercel.app/api/v1/management
If you are self-hosting, replace https://pingless-license-system.vercel.app with your own deployment URL.
Authentication
Every request must include an API key in the Authorization header:
Authorization: Bearer altis_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Create, regenerate, and revoke keys from Dashboard → Settings → API Keys. The plaintext token is shown only once; the server stores only its SHA-256 hash. Tokens are scoped to your organization and your user account, so a key can never access another organization's data.
Scopes
Each key can be granted one or more scopes. full_access grants every permission, and any :write scope automatically implies the matching :read scope.
| Scope | Access |
|---|---|
full_access | All Account API operations. |
products:read | List and read products. |
products:write | Create, update, and delete products. |
sub_products:read | List and read sub-products. |
sub_products:write | Create, update, and delete sub-products. |
presets:read | List and read feature-flag presets. |
presets:write | Create, update, and delete presets. |
licenses:read | List and read licenses. |
licenses:write | Create, update, and delete licenses. |
license_requests:read | List license requests for your products. |
license_requests:write | Approve or reject license requests. |
access_rules:read | Read access rules. |
access_rules:write | Create, update, and delete access rules. |
analytics:read | Read product analytics. |
billing:read | Read billing, invoices, and payment methods. |
billing:write | Change plan, cancel plan, and manage payment methods. |
Rate limits
Every key has its own per-minute rate limit. The default is 60 requests per minute and can be raised or lowered per key from the API Keys page. Requests that exceed the limit return 429 Too Many Requests with a Retry-After header.
Conventions
- All bodies and responses are JSON.
- List endpoints support
limit(max 100),offset, and relevant filters via query params. - Successful single-resource responses wrap the resource in
{ data: ... }. - Successful list responses wrap the array in
{ data: [], pagination: { limit, offset, total } }. - Error responses use
{ error: { code, message } }. - All operations are scoped to the organization linked to the API key.
:idpath parameters are UUIDs.
Endpoints
Products
| Method | Endpoint | Required scope | Description |
|---|---|---|---|
GET | /products | products:read | List products in your organization. |
POST | /products | products:write | Create a product. Returns the product with its derived ed25519_public_key. |
GET | /products/:id | products:read | Read a product, including ed25519_public_key. |
PATCH | /products/:id | products:write | Update a product. |
DELETE | /products/:id | products:write | Delete a product (only if it has no licenses). |
POST /products accepts the same fields as the dashboard product form: name, slug, description, product_link, docs_link, support_link, icon_url, is_active, product_type (native or web), protection_profile (none, hostname, or hardware), default_max_activations, default_block_all_until_whitelisted, default_offline_days, and allow_self_regenerate.
Sub-products
| Method | Endpoint | Required scope | Description |
|---|---|---|---|
GET | /products/:id/sub-products | sub_products:read or products:read | List sub-products for a product. |
POST | /products/:id/sub-products | sub_products:write | Create a sub-product. |
GET | /sub-products/:id | sub_products:read | Read a sub-product. |
PATCH | /sub-products/:id | sub_products:write | Update a sub-product. |
DELETE | /sub-products/:id | sub_products:write | Delete a sub-product. |
Sub-product bodies accept name, slug, description, and is_active.
Feature-flag presets
| Method | Endpoint | Required scope | Description |
|---|---|---|---|
GET | /products/:id/presets | presets:read | List presets for a product. |
POST | /products/:id/presets | presets:write | Create a preset. |
GET | /presets/:id | presets:read | Read a preset. |
PATCH | /presets/:id | presets:write | Update a preset. |
DELETE | /presets/:id | presets:write | Delete a preset. |
Preset bodies accept name and flags (an object mapping flag names to string, number, boolean, or null values).
Licenses
| Method | Endpoint | Required scope | Description |
|---|---|---|---|
GET | /licenses | licenses:read | List licenses. Filter with product_id and status. |
POST | /licenses | licenses:write | Create a license. |
GET | /licenses/:id | licenses:read | Read a license. |
PATCH | /licenses/:id | licenses:write | Update a license. |
DELETE | /licenses/:id | licenses:write | Permanently delete a license and its activations/logs. |
POST /licenses accepts:
| Field | Type | Description |
|---|---|---|
product_id | UUID (required) | Product the license belongs to. |
sub_product_id | UUID (optional) | Optional sub-product. |
user_id | UUID (optional) | User to assign the license to. |
max_activations | integer | Default 1. |
expires_at | ISO 8601 datetime (optional) | Expiry date. Requires plan permission. |
is_trial | boolean | Default false. Requires plan permission. |
offline_days | integer (optional) | Offline grace period. Requires plan permission. |
block_all_until_whitelisted | boolean (optional) | Block-all-until-whitelisted mode. |
feature_flags | object (optional) | Feature flags. Requires plan permission. |
notes | string (optional) | Internal notes. |
PATCH /licenses/:id accepts the same fields except product_id. status can be set to active, suspended, or revoked.
License requests
| Method | Endpoint | Required scope | Description |
|---|---|---|---|
GET | /license-requests | license_requests:read | List license requests for your products. Filter with status and product_id. |
POST | /license-requests/:id/approve | license_requests:write | Approve a request and create a license. |
POST | /license-requests/:id/reject | license_requests:write | Reject a request. |
The approve body accepts max_activations, expires_at, offline_days, and feature_flags.
Access rules
| Method | Endpoint | Required scope | Description |
|---|---|---|---|
GET | /access-rules | access_rules:read | List access rules. Filter with scope, rule_type, mode, product_id, license_id. |
POST | /access-rules | access_rules:write | Create an access rule. |
GET | /access-rules/:id | access_rules:read | Read an access rule. |
PATCH | /access-rules/:id | access_rules:write | Update an access rule. |
DELETE | /access-rules/:id | access_rules:write | Delete an access rule. |
Access-rule bodies accept:
| Field | Type | Description |
|---|---|---|
rule_type | ipv4, ipv6, cidr, or fqdn | Type of rule. |
value | string | The IP, CIDR, or FQDN value. |
mode | block or whitelist | Rule mode. |
scope | global, product, or license | Rule scope. |
scope_id | UUID (optional) | Required when scope is product or license. |
note | string (optional) | Internal note. |
Analytics
| Method | Endpoint | Required scope | Description |
|---|---|---|---|
GET | /analytics | analytics:read | Read check/activation analytics. Filter with product_id, start, end, and group_by (day or hour). |
Response shape:
{
"summary": {
"total_checks": 1523,
"valid_checks": 1489,
"denied_checks": 34,
"activation_count": 42
},
"top_denial_reasons": [
{ "reason": "denied_seat_limit", "count": 20 }
],
"timeseries": [
{
"bucket": "2026-07-15",
"total_checks": 120,
"valid_checks": 118,
"denied_checks": 2,
"activations": 5
}
]
}
Billing
| Method | Endpoint | Required scope | Description |
|---|---|---|---|
GET | /billing | billing:read | Read organization plan, usage, recent invoices, and payment methods. |
GET | /billing/plans | billing:read | List available seller plans. |
POST | /billing/plan-change | billing:write | Request a plan change. |
POST | /billing/plan-cancel | billing:write | Request plan cancellation. |
GET | /billing/invoices | billing:read | List invoices. |
GET | /billing/invoices/:id | billing:read | Read an invoice. |
GET | /billing/payment-methods | billing:read | List payment methods. |
POST | /billing/payment-methods | billing:write | Add a payment method. |
GET | /billing/payment-methods/:id | billing:read | Read a payment method. |
DELETE | /billing/payment-methods/:id | billing:write | Remove a payment method. |
POST | /billing/payment-methods/:id/default | billing:write | Set a payment method as default. |
Plan-change and plan-cancel bodies accept an optional reason string. These endpoints write an audit log request; actual billing changes may still require admin review depending on your integration.
Payment-method bodies accept:
| Field | Type | Description |
|---|---|---|
provider | stripe or cashfree | Payment provider. |
provider_payment_method_id | string | Provider's payment-method ID. |
brand | string (optional) | Card brand or provider label. |
last4 | string (optional) | Last four digits. |
Billing write endpoints require the API key user to be an organization owner or admin.
Example: create a license
curl -X POST https://pingless-license-system.vercel.app/api/v1/management/licenses \
-H "Authorization: Bearer altis_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"product_id": "550e8400-e29b-41d4-a716-446655440000",
"user_id": "660e8400-e29b-41d4-a716-446655440001",
"max_activations": 2,
"expires_at": "2027-01-01T00:00:00Z",
"feature_flags": { "tier": "pro", "api_access": true }
}'
Product public keys
Each product has its own Ed25519 key pair. The public key is available in:
- The
ed25519_public_keyfield of any product response from the Account API. - The product details page in the dashboard (Ed25519 public key card).
Use this key — not a platform-wide key — to verify /api/v1/license/check signatures.
Rotating a product key
Admins and product owners can rotate the key from the product details page. Rotation increments an internal counter and derives a fresh deterministic key pair from the same master seed. Rotating invalidates every previously signed offline token — clients must update their embedded public key before they can verify new responses.
Verifying webhook signatures
If you configure product webhooks and set a secret, every delivery is signed with HMAC-SHA256 and sent in the X-Webhook-Signature header as sha256=<hex>.
Verify the signature before trusting the payload:
import { createHmac } from "crypto";
function verifyWebhook(secret: string, body: string, signature: string): boolean {
const expected = "sha256=" + createHmac("sha256", secret).update(body).digest("hex");
try {
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
} catch {
return false;
}
}
Webhook bodies are JSON objects with event, timestamp, and payload keys. Reject any request where the signature does not match.
Error codes
error.code | HTTP | Meaning |
|---|---|---|
unauthorized | 401 | Missing or invalid API key. |
forbidden | 403 | Key does not have access to this organization or resource. |
insufficient_scope | 403 | Key lacks the required scope for this operation. |
plan_forbidden | 403 | Your seller plan does not include this feature. |
plan_limit_exceeded | 403 | You exceeded a plan limit (products, licenses, etc.). |
not_found | 404 | Resource does not exist or does not belong to your organization. |
invalid_json | 400 | Request body could not be parsed as JSON. |
invalid_request | 400 | Request body failed schema validation. |
no_changes | 422 | No fields provided to update. |
invalid_state | 422 | Resource is in a state that prevents the operation. |
duplicate_slug | 409 | A product or sub-product with this slug already exists. |
duplicate_name | 409 | A preset with this name already exists for this product. |
duplicate_payment_method | 409 | This payment method has already been added. |
conflict | 409 | Cannot delete a product that still has licenses. |
rate_limit_exceeded | 429 | Key exceeded its per-minute rate limit. |
For rate_limit_exceeded responses, the Retry-After header indicates how many seconds to wait before retrying.
