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

http
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:

http
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.

ScopeAccess
full_accessAll Account API operations.
products:readList and read products.
products:writeCreate, update, and delete products.
sub_products:readList and read sub-products.
sub_products:writeCreate, update, and delete sub-products.
presets:readList and read feature-flag presets.
presets:writeCreate, update, and delete presets.
licenses:readList and read licenses.
licenses:writeCreate, update, and delete licenses.
license_requests:readList license requests for your products.
license_requests:writeApprove or reject license requests.
access_rules:readRead access rules.
access_rules:writeCreate, update, and delete access rules.
analytics:readRead product analytics.
billing:readRead billing, invoices, and payment methods.
billing:writeChange 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.
  • :id path parameters are UUIDs.

Endpoints

Products

MethodEndpointRequired scopeDescription
GET/productsproducts:readList products in your organization.
POST/productsproducts:writeCreate a product. Returns the product with its derived ed25519_public_key.
GET/products/:idproducts:readRead a product, including ed25519_public_key.
PATCH/products/:idproducts:writeUpdate a product.
DELETE/products/:idproducts:writeDelete 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

MethodEndpointRequired scopeDescription
GET/products/:id/sub-productssub_products:read or products:readList sub-products for a product.
POST/products/:id/sub-productssub_products:writeCreate a sub-product.
GET/sub-products/:idsub_products:readRead a sub-product.
PATCH/sub-products/:idsub_products:writeUpdate a sub-product.
DELETE/sub-products/:idsub_products:writeDelete a sub-product.

Sub-product bodies accept name, slug, description, and is_active.

Feature-flag presets

MethodEndpointRequired scopeDescription
GET/products/:id/presetspresets:readList presets for a product.
POST/products/:id/presetspresets:writeCreate a preset.
GET/presets/:idpresets:readRead a preset.
PATCH/presets/:idpresets:writeUpdate a preset.
DELETE/presets/:idpresets:writeDelete a preset.

Preset bodies accept name and flags (an object mapping flag names to string, number, boolean, or null values).

Licenses

MethodEndpointRequired scopeDescription
GET/licenseslicenses:readList licenses. Filter with product_id and status.
POST/licenseslicenses:writeCreate a license.
GET/licenses/:idlicenses:readRead a license.
PATCH/licenses/:idlicenses:writeUpdate a license.
DELETE/licenses/:idlicenses:writePermanently delete a license and its activations/logs.

POST /licenses accepts:

FieldTypeDescription
product_idUUID (required)Product the license belongs to.
sub_product_idUUID (optional)Optional sub-product.
user_idUUID (optional)User to assign the license to.
max_activationsintegerDefault 1.
expires_atISO 8601 datetime (optional)Expiry date. Requires plan permission.
is_trialbooleanDefault false. Requires plan permission.
offline_daysinteger (optional)Offline grace period. Requires plan permission.
block_all_until_whitelistedboolean (optional)Block-all-until-whitelisted mode.
feature_flagsobject (optional)Feature flags. Requires plan permission.
notesstring (optional)Internal notes.

PATCH /licenses/:id accepts the same fields except product_id. status can be set to active, suspended, or revoked.

License requests

MethodEndpointRequired scopeDescription
GET/license-requestslicense_requests:readList license requests for your products. Filter with status and product_id.
POST/license-requests/:id/approvelicense_requests:writeApprove a request and create a license.
POST/license-requests/:id/rejectlicense_requests:writeReject a request.

The approve body accepts max_activations, expires_at, offline_days, and feature_flags.

Access rules

MethodEndpointRequired scopeDescription
GET/access-rulesaccess_rules:readList access rules. Filter with scope, rule_type, mode, product_id, license_id.
POST/access-rulesaccess_rules:writeCreate an access rule.
GET/access-rules/:idaccess_rules:readRead an access rule.
PATCH/access-rules/:idaccess_rules:writeUpdate an access rule.
DELETE/access-rules/:idaccess_rules:writeDelete an access rule.

Access-rule bodies accept:

FieldTypeDescription
rule_typeipv4, ipv6, cidr, or fqdnType of rule.
valuestringThe IP, CIDR, or FQDN value.
modeblock or whitelistRule mode.
scopeglobal, product, or licenseRule scope.
scope_idUUID (optional)Required when scope is product or license.
notestring (optional)Internal note.

Analytics

MethodEndpointRequired scopeDescription
GET/analyticsanalytics:readRead check/activation analytics. Filter with product_id, start, end, and group_by (day or hour).

Response shape:

json
{
  "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

MethodEndpointRequired scopeDescription
GET/billingbilling:readRead organization plan, usage, recent invoices, and payment methods.
GET/billing/plansbilling:readList available seller plans.
POST/billing/plan-changebilling:writeRequest a plan change.
POST/billing/plan-cancelbilling:writeRequest plan cancellation.
GET/billing/invoicesbilling:readList invoices.
GET/billing/invoices/:idbilling:readRead an invoice.
GET/billing/payment-methodsbilling:readList payment methods.
POST/billing/payment-methodsbilling:writeAdd a payment method.
GET/billing/payment-methods/:idbilling:readRead a payment method.
DELETE/billing/payment-methods/:idbilling:writeRemove a payment method.
POST/billing/payment-methods/:id/defaultbilling:writeSet 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:

FieldTypeDescription
providerstripe or cashfreePayment provider.
provider_payment_method_idstringProvider's payment-method ID.
brandstring (optional)Card brand or provider label.
last4string (optional)Last four digits.

Billing write endpoints require the API key user to be an organization owner or admin.

Example: create a license

bash
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_key field 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:

typescript
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.codeHTTPMeaning
unauthorized401Missing or invalid API key.
forbidden403Key does not have access to this organization or resource.
insufficient_scope403Key lacks the required scope for this operation.
plan_forbidden403Your seller plan does not include this feature.
plan_limit_exceeded403You exceeded a plan limit (products, licenses, etc.).
not_found404Resource does not exist or does not belong to your organization.
invalid_json400Request body could not be parsed as JSON.
invalid_request400Request body failed schema validation.
no_changes422No fields provided to update.
invalid_state422Resource is in a state that prevents the operation.
duplicate_slug409A product or sub-product with this slug already exists.
duplicate_name409A preset with this name already exists for this product.
duplicate_payment_method409This payment method has already been added.
conflict409Cannot delete a product that still has licenses.
rate_limit_exceeded429Key exceeded its per-minute rate limit.

For rate_limit_exceeded responses, the Retry-After header indicates how many seconds to wait before retrying.