Skip to content

Resolve License

Resolve a license key to a single, authoritative "entitled right now" answer — the valid flag, subscription status, the currently-granted allowed_features, and any grace window — in one call. This folds together entitlement checks and status/expiry validation, and is the recommended endpoint for a runtime capability gate.

Reading a custom entitlement value (e.g. an updates_until date)? Use Validate License instead — it returns the full entitlements map. resolve returns only granted feature codes (entitlements whose value is boolean true), not arbitrary fields.

Endpoint

POST /api/v1/integration/licensing/resolve

Headers

HeaderRequiredValue
X-API-KeyYesYour API key
Content-TypeYesapplication/json
X-Request-IDRecommendedUUID for tracing (echoed in meta.request_id)

Request Body

json
{
  "license_key": "VX-ACME-abc123def456",
  "features": ["analytics", "sso"]
}
FieldTypeRequiredDescription
license_keystringYesFull license key. Must belong to the tenant resolved from your API key.
featuresstring[]NoIf supplied, allowed_features is the intersection with what the license actually grants. Omit (or send empty) to return all granted features.

Response (200 OK)

json
{
  "data": {
    "valid": true,
    "status": "active",
    "allowed_features": ["analytics", "sso"],
    "grace_period_ends_at": null,
    "expires_at": null,
    "license": {
      "id": "01HXYZABCDEFGHJKMNPQRSTVWX",
      "key": "VX-ACME-abc123def456",
      "type": "perpetual"
    }
  },
  "meta": {
    "request_id": "uuid",
    "api_version": "1"
  }
}
FieldTypeDescription
data.validbooleantrue iff the license is currently usable (active, trialing, or canceled-inside-grace). Authoritative — do not re-derive on the client.
data.statusstringOne of active, trialing, past_due, paused, canceled, expired, suspended, revoked (US spelling).
data.allowed_featuresstring[]Currently-granted feature codes (entitlements whose value is boolean true). [] when nothing is granted. Filtered by request features[] if supplied.
data.grace_period_ends_atstring | nullISO-8601 UTC. Set only in a grace state (canceled/past_due with grace remaining) — "when access drops to Free". Otherwise null.
data.expires_atstring | nullISO-8601 UTC. The license/period expiry; null for perpetual keys. Distinct from grace_period_ends_at.
data.license.idstringInternal ID (ULID). Stable — use for support/webhook cross-references.
data.license.keystringEcho of the input key.
data.license.typestringperpetual, subscription, or trial.

Adapter rule (single line): if (!data.valid) drop_to_free(). Status drives optional UX (a grace banner, a "trial ended" prompt) but never gates capability.

Status state machine

Statevalidstatusgrace_period_ends_atallowed_features
Subscription active, not expiredtrueactivenullfull set
Subscription trialing, trial livetruetrialingnullfull set
Past due, inside dunning gracetruepast_duegrace endfull set
Past due, past gracefalsepast_duenull[]
Pausedfalsepausednull[]
Canceled (cancel_at_period_end), inside periodtruecanceledperiod endfull set
Canceled, period endedfalsecancelednull[]
License expires_at past (e.g. a lapsed trial)falseexpirednull[]
License revoked404 (see errors)
Tenant suspended403 (see errors)

Grace policy: canceled → remaining time to current_period_end; past_due → Stripe-default dunning (~21 days), grace_period_ends_at = last-retry deadline; paused → no grace. valid + grace_period_ends_at are authoritative — you do not model dunning yourself.

Error Responses

CodeHTTPDescription
AUTH.INVALID_API_KEY401Invalid or missing API key
TENANT.STATUS.SUSPENDED403Tenant is suspended
LICENSE.NOT_FOUND404Key does not exist, belongs to another tenant, or is revoked (deliberately indistinguishable)
(validation)422e.g. license_key missing, features[] contains a non-string
RATE_LIMIT.EXCEEDED429Rate limit exceeded — honour Retry-After
INTERNAL.UNEXPECTED5xxServer fault — retry with backoff

Notes

  • Writes a LICENSE.RESOLVED audit event and updates the license's last_used_at.
  • Caching: cache the response per license_key for ~5 minutes. On any valid: false, evict and drop to Free. On LICENSE.NOT_FOUND / TENANT.STATUS.SUSPENDED, evict and do not retry. Keep any offline cache as a "ValidonX-unreachable" fallback only — grace_period_ends_at is the authoritative grace signal.
  • resolve is additive. The path-1 endpoints — Validate License and Check Entitlements — remain available; reach for Validate specifically when you need the full entitlements map (e.g. a custom updates_until field).

Built by Veltara Works