https://permisoft.local/developer/integration

API & licence integration

Permisoft is the system of record for perpetual licence keys. Your software never mints keys — you validate and track keys we issue on each purchase, and customers activate through our public API.

1. Customer purchases on Permisoft

Checkout runs on our platform. We issue a unique one-time licence key and email the buyer.

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)

After approval you receive a ps_live_… API key. Validate keys and pull sales stats—never mint keys yourself.

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 or once by ops on approval.
  • The full key is shown exactly one time. Permisoft stores only a hash — we cannot recover it.
  • 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-uwpbvkfyka-uw.a.run.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-uwpbvkfyka-uw.a.run.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_… (scope stats:read)

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