Skip to content

API Contract

Use this page when you need the compatibility-sensitive behaviors that SDKs, automation, and external integrations should rely on.

Who This Is For

  • SDK authors and maintainers
  • developers building long-lived integrations
  • AI agents that need stable behavioral rules beyond route discovery

When To Use This

Read this page when envelope shape, renewal rules, scope semantics, or runtime signature behavior matters more than feature walkthroughs.

Treat api/openapi.yaml as the schema and transport source of truth. Treat this page as the public explanation of the behaviors that should remain stable enough for generated clients and external integrations.

How It Works

Envelope rules

These response shapes are foundational:

  • management success responses use {data, meta}
  • runtime success responses use {data, signature, meta}
  • 204 No Content routes intentionally return no body
  • errors use {error, meta}

Consumers should not flatten away the difference between management and runtime envelopes.

Scope disclosure

Management operations publish least-privilege requirements via x-required-scopes in api/openapi.yaml.

Important rules:

  • x-required-scopes is the minimum non-admin scope set for that operation
  • admin satisfies every management route
  • 403 TOKEN_SCOPE_DENIED means the token did not satisfy that operation’s scope requirement

License origin linkage

License creation stores validated order_id and subscription_id links on the license record.

Behavior that matters:

  • if order_id is supplied and already linked to a subscription, subscription_id is inferred when omitted
  • order and subscription records must belong to the same product as the license
  • if both are supplied, they must match each other
  • GET /api/v1/licenses supports order_id, subscription_id, expires_before, and expires_after filters

Renewal semantics

Renewal rules are intentionally explicit:

  • only subscription, time_limited, and trial licenses are renewable
  • renewals are idempotent per license plus Idempotency-Key
  • provide exactly one of expires_at or extend_by_days
  • renewal basis is the current stored expiry when still active, otherwise current server time
  • renewals keep suspended licenses suspended
  • renewal-time order_id and subscription_id are validated and written to audit metadata, but do not rewrite the license’s stored origin linkage

Order and subscription semantics

Orders and subscriptions are linked external records, not a billing engine.

Stable expectations:

  • subscriptions require current_period_start and current_period_end
  • current_period_end must be after current_period_start
  • canceled_at is valid only when subscription status is canceled
  • orders may omit period_start and period_end, but if one is present the other must be too
  • the service stores periods as provided and does not apply provider-specific normalization or proration

Version eligibility

Runtime version checks depend on app_version.

Stable behavior:

  • if app_version is omitted, no version gate is evaluated
  • if a product has no version catalog, version gating is not enforced
  • once a product has any catalog entries, unknown app versions are rejected
  • eligibility cutoff uses maintenance_expires_at for perpetual licenses and expires_at for non-perpetual licenses

Signing and public-key verification

Runtime activation, validation, check, consume, deactivate, and floating responses are signed.

Stable rules:

  • signature covers the JSON bytes of data
  • public verification keys are published at GET /api/v1/system/public-keys
  • retired public keys remain published after rotation so older payloads can still be verified
  • offline issuance returns an encrypted envelope whose decrypted inner payload must still be signature-verified

Example

This is the minimum contract shape an integration should preserve:

json
{
  "data": {
    "license_id": "lic_123",
    "status": "active"
  },
  "signature": {
    "kid": "sig_123",
    "algorithm": "Ed25519",
    "value": "base64..."
  },
  "meta": {
    "request_id": "req_123"
  }
}

And this is the management-side scope rule an automation should follow:

text
operationId: createReportExport
x-required-scopes: ["report:export"]

Common Mistakes

  • treating admin as the normal scope for all automation instead of a bootstrap escape hatch
  • assuming orders or subscriptions imply billing-engine behavior
  • assuming renewals rewrite stored order/subscription linkage
  • trusting runtime data before checking the signature
  • treating additive fields as breaking contract changes

Prototype docs shell for the rewrite workspace.