Permisoft Licensing — API & integration

Permisoft Licensing is the system of record for perpetual licence keys on the marketplace — like a store entitlement layer, without FlexNet, SafeNet, or monthly license-server fees. Your software never mints keys; Permisoft issues them on every purchase and customers activate through our public API.

Why publishers use Permisoft Licensing

  • ✓ Zero cost for third-party DRM or license vendors
  • ✓ Checkout, key delivery, activation, and revocation handled by Permisoft
  • ✓ One key per purchase, single-machine activation (transfers via support)
  • ✓ Builds scanned before go-live to ensure integration — see Permisoft Licensing in your publisher portal

1. Customer purchases on Permisoft

Checkout runs on our platform via POST /api/checkout (not a publisher API). We issue a unique licence key and email the buyer when the order is PAID.

2. User activates in your app

Integrate @permisoft/client or call POST /api/keys/activate with the key and a stable machine ID. The key binds to one machine.

3. Publisher API (server-side)

Use ps_live_… for production and ps_test_… for sandbox. Validate keys (POST /api/v1/publisher/keys/validate), pull stats (GET /api/v1/publisher/stats), and create test purchases (POST /api/v1/publisher/sandbox/purchase).

Vendor onboarding checklist

  1. Approved as a publisher — you received portal access
  2. Set product price (USD cents) in Pricing tab
  3. Upload release to Permisoft GCS (Releases tab) → mark uploaded → submit for review
  4. Ops publishes listing (IN_REVIEW → PUBLISHED)
  5. Complete Stripe Connect (Payouts tab) before live checkout if required
  6. Generate API key and integrate validate + stats
  7. Integrate @permisoft/client activation in your app

Internal runbook for testing your own product: see docs/VENDOR_ONBOARDING.md in the repo.

Licence key format (Permisoft issues keys — you do not)

Every paid order creates exactly one key in Permisoft. Keys are uppercase, segmented, and include a checksum. Your publisher prefix is assigned at approval (from your studio slug).

Example: MYAPP-A3B7-K9D2-M4F8-42

  • Never generate or guess keys locally — always validate via the Publisher API or rely on activation.
  • Prefix is stored on your Publisher record as licenseKeyPrefix (e.g. MYAPP).
  • Keys start as UNUSED, become ACTIVATED when the customer binds one machine, or REVOKED on refund/transfer.
  • Demo keys may include DEMO in the body for marketplace demos only.

Publisher API keys (ps_live_ / ps_test_)

  • Generated in the publisher portal under API & integration.
  • The full key is shown exactly one time. Permisoft stores only a hash — we cannot recover it.
  • ps_live_ keys access production sales, keys, and payouts.
  • ps_test_ keys access an isolated sandbox — use POST /api/v1/publisher/sandbox/purchase to create test orders.
  • If lost or leaked: revoke the key in the portal and create a new one.
  • Scopes: stats:read, keys:validate (default on new keys).
  • Never embed ps_live_ keys in desktop apps — server-side only.

POST /api/keys/activate — end-user app (public)

Audience: Desktop app / installer on the customer machine

Auth: None (rate limited by IP)

POST https://permisoft.app/api/keys/activate
Content-Type: application/json

{
  "key": "MYAPP-A3B7-K9D2-M4F8-42",
  "machineId": "stable-hardware-id-from-your-app",
  "machineLabel": "Paul's MacBook (optional)",
  "productSlug": "myapp-myproduct (optional, for extra validation)"
}

Success

{
  "valid": true,
  "message": "Activation successful",
  "scope": "PRODUCT",
  "productSlug": "myapp-myproduct",
  "activationToken": "eyJ...",
  "tokenExpiresAt": "2026-06-01T00:00:00.000Z",
  "publisherSlug": "myapp"
}

Error codes

  • NOT_FOUND — key unknown or wrong product
  • ALREADY_ACTIVATED — key used on another machine
  • REVOKED — refunded or administratively revoked
  • RATE_LIMITED — slow down retries

Use @permisoft/client activateOnline(apiBaseUrl, { key, machineId }) — caches JWT for offline grace.

POST /api/v1/publisher/keys/validate — your server

Audience: Your backend, license server, or support tooling

Auth: Authorization: Bearer ps_live_… or ps_test_…

POST https://permisoft.app/api/v1/publisher/keys/validate
Authorization: Bearer ps_live_xxxxxxxx
Content-Type: application/json

{ "key": "MYAPP-A3B7-K9D2-M4F8-42" }

Responses

{ "valid": false, "reason": "invalid_format" }
{ "valid": false, "reason": "not_found" }
{
  "valid": true,
  "status": "UNUSED" | "ACTIVATED" | "REVOKED",
  "product": { "id": "...", "slug": "...", "name": "..." },
  "activated": true,
  "activatedAt": "2026-05-20T12:00:00.000Z",
  "keyMasked": "MYAPP-****-****-****-42",
  "sandbox": false
}
  • Does not activate — only checks ownership and state.
  • Use before granting support entitlements or online features tied to a purchase.
  • Rate limit: 120 requests / minute / IP.

GET /api/v1/publisher/stats — sales & keys reconciliation

Auth: Authorization: Bearer ps_live_… or ps_test_… (scope stats:read)

GET https://permisoft.app/api/v1/publisher/stats?from=2026-05-01&to=2026-05-31&productId=optional-cuid
{
  "publisherSlug": "myapp",
  "sandbox": false,
  "platformFeePercent": 5,
  "totals": {
    "unitsSold": 12,
    "grossCents": 48000,
    "platformFeeCents": 2400,
    "netPublisherCents": 45600,
    "pendingPayoutCents": 45600
  },
  "keysIssuedByProduct": [{ "productId": "...", "count": 12 }],
  "sales": [{ "orderId": "...", "grossCents": 4000, "platformFeeCents": 200, ... }]
}
  • ps_test_ keys return sandbox: true and only sandbox orders/keys.
  • Compare unitsSold and grossCents to Stripe Connect payouts for the same period.
  • keysIssuedByProduct counts licence rows created (should match paid units minus refunds).
  • pendingPayoutCents is always 0 for sandbox responses.

POST /api/v1/publisher/sandbox/purchase — test environment

Auth: Authorization: Bearer ps_test_… (scope stats:read)

POST https://permisoft.app/api/v1/publisher/sandbox/purchase
Authorization: Bearer ps_test_xxxxxxxx
Content-Type: application/json

{ "productId": "cuid-of-your-product", "holderName": "QA tester" }
{
  "sandbox": true,
  "orderId": "...",
  "licenseKey": "MYAPP-....",
  "message": "Sandbox purchase created..."
}
  • Creates a PAID sandbox order and licence key without charging Stripe.
  • Visible only to ps_test_ stats/validate calls — never mixed with live data.
  • Use during CI or local integration before going live.