Reference

Licenzy API reference

Reference the Licenzy /v1 runtime API for checkout, access checks, entitlements, and usage consumption. Configure projects, API keys, Stripe setup, outbound Licenzy webhooks, and billing from the Licenzy portal.

Runtime API reference

Use these /v1 endpoints from server-side code. Try It examples are local previews only.

API key authenticated /v1 reference

Use these endpoints from server-side code. API keys select the account and mode, test mode remains available for setup, and live writes require an active or trial commercial subscription.

  • Global API key auth: Send Authorization: Bearer lz_test_... or lz_live_....
  • Modes: Data is scoped by account_id and API key mode. Runtime request bodies do not switch modes.
  • Common auth errors: missing_bearer_token, invalid_api_key, and plan_mode_not_allowed.

Runtime checkout

POST/v1/checkout/session

Create checkout session

Creates or returns a Stripe Checkout Session for a configured product in the current project.

Auth: Required. Uses a Licenzy runtime API key from server-side code.

Required headers

Authorizationrequired
Bearer <api_key>

Licenzy runtime API key. Each API key belongs to one project, so runtime requests are scoped to that project's products, entitlements, Stripe connection, and outbound webhook activity.

Content-Typerequired
application/json

Required for requests with JSON bodies.

Idempotency-Keyrequired
string

Required for this write. Checkout keys are limited to 200 characters.

Body

subject_refrequired
string

Customer or subject reference, min 1 and max 200 characters.

product_coderequired
string

Configured product code, min 1 and max 100 characters.

Request examples

cURL
curl -X POST 'https://api.licenzy.app/v1/checkout/session' \
  -H 'Authorization: Bearer lz_test_...' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: example-key-001' \
  --data '{
  "subject_ref": "customer_123",
  "product_code": "PACK_1000"
}'

Responses

Checkout created200
{
  "attempt_id": "uuid",
  "status": "checkout_created",
  "checkout_url": "https://checkout.stripe.com/c/pay/...",
  "mode": "test"
}

Errors

Missing idempotency key400
{
  "error": "missing_idempotency_key"
}
Invalid body400
{
  "error": "invalid_body",
  "details": {}
}
Product not found404
{
  "error": "product_not_found"
}
Invalid product kind400
{
  "error": "invalid_product_kind"
}
Idempotency conflict409
{
  "error": "idempotency_conflict",
  "message": "The supplied Idempotency-Key was already used with different request parameters."
}
Stripe connection missing409
{
  "error": "stripe_connection_not_configured",
  "code": "STRIPE_CONNECTION_NOT_CONFIGURED",
  "message": "Stripe connection is not configured for this account and mode.",
  "details": {
    "account_id": "uuid",
    "mode": "test"
  }
}
Missing bearer token401
{
  "error": "missing_bearer_token"
}
Invalid API key401
{
  "error": "invalid_api_key"
}
Plan mode not allowed403
{
  "error": "plan_mode_not_allowed",
  "code": "PLAN_MODE_NOT_ALLOWED",
  "message": "Current plan does not allow this mode.",
  "details": {
    "mode": "live",
    "allowed_modes": ["test"],
    "plan_code": "free"
  }
}

Notes

  • Use the Licenzy portal for API key creation, Stripe credential setup, product mapping, outbound Licenzy webhook configuration, and billing operations.
  • Idempotency is required. An existing matching attempt returns the same Stripe session URL when available.
  • Configure the Stripe inbound webhook in Stripe so Stripe can later send /v1/webhooks/stripe/tenant to finalize the purchase and update entitlements.

Runtime access

POST/v1/access/check

Check access

Minimal boolean access check for a subject.

Auth: Required. Uses a Licenzy runtime API key from server-side code.

Required headers

Authorizationrequired
Bearer <api_key>

Licenzy runtime API key. Each API key belongs to one project, so runtime requests are scoped to that project's products, entitlements, Stripe connection, and outbound webhook activity.

Content-Typerequired
application/json

Required for requests with JSON bodies.

Body

subject_refrequired
string

Customer or subject reference, min 1 and max 200 characters.

Request examples

cURL
curl -X POST 'https://api.licenzy.app/v1/access/check' \
  -H 'Authorization: Bearer lz_test_...' \
  -H 'Content-Type: application/json' \
  --data '{
  "subject_ref": "customer_123"
}'

Responses

Allowed200
{
  "subject_ref": "customer_123",
  "allowed": true
}

Errors

Invalid body400
{
  "error": "invalid_body",
  "details": {}
}
Missing bearer token401
{
  "error": "missing_bearer_token"
}
Invalid API key401
{
  "error": "invalid_api_key"
}
Plan mode not allowed403
{
  "error": "plan_mode_not_allowed",
  "code": "PLAN_MODE_NOT_ALLOWED",
  "message": "Current plan does not allow this mode.",
  "details": {
    "mode": "live",
    "allowed_modes": ["test"],
    "plan_code": "free"
  }
}

Notes

  • In live mode, blocked commercial account state returns allowed: false.
  • Checks subscription, then time pass, then usage pack. It does not consume usage.
  • Response intentionally omits reason/details.
GET/v1/customer/access/:subject_ref

Customer access detail

Detailed access status and entitlement availability for a subject.

Auth: Required. Uses a Licenzy runtime API key from server-side code.

Required headers

Authorizationrequired
Bearer <api_key>

Licenzy runtime API key. Each API key belongs to one project, so runtime requests are scoped to that project's products, entitlements, Stripe connection, and outbound webhook activity.

Path params

subject_refrequired
string

Customer or subject reference, min 1 and max 200 characters.

Request examples

cURL
curl -X GET 'https://api.licenzy.app/v1/customer/access/:subject_ref' \
  -H 'Authorization: Bearer lz_test_...'

Responses

Access detail200
{
  "subject_ref": "customer_123",
  "has_access": true,
  "status": "active",
  "entitlements": [
    {
      "kind": "usage_pack",
      "status": "active",
      "plan": null,
      "expires_at": null,
      "usage_remaining": 990,
      "available": true,
      "availability_reason": "usage_pack_available"
    }
  ]
}

Errors

Invalid subject reference400
{
  "error": "invalid_subject_ref",
  "details": {}
}
Missing bearer token401
{
  "error": "missing_bearer_token"
}
Invalid API key401
{
  "error": "invalid_api_key"
}
Plan mode not allowed403
{
  "error": "plan_mode_not_allowed",
  "code": "PLAN_MODE_NOT_ALLOWED",
  "message": "Current plan does not allow this mode.",
  "details": {
    "mode": "live",
    "allowed_modes": ["test"],
    "plan_code": "free"
  }
}

Notes

  • Use this for post-payment polling, customer workspace inspection, or backend investigation.
  • Does not consume usage.
  • Availability reasons include subscription_active, subscription_inactive, time_pass_active, time_pass_expired, usage_pack_available, usage_pack_empty, usage_pack_inactive, and commercial_account_blocked.
  • Human operator workflows such as denial investigation, history review, and manual recovery live in the Licenzy portal.
GET/v1/entitlements/:subject_ref

Entitlements

Raw entitlement rows for a subject plus computed status.

Auth: Required. Uses a Licenzy runtime API key from server-side code.

Required headers

Authorizationrequired
Bearer <api_key>

Licenzy runtime API key. Each API key belongs to one project, so runtime requests are scoped to that project's products, entitlements, Stripe connection, and outbound webhook activity.

Path params

subject_refrequired
string

Customer or subject reference.

Request examples

cURL
curl -X GET 'https://api.licenzy.app/v1/entitlements/:subject_ref' \
  -H 'Authorization: Bearer lz_test_...'

Responses

Entitlements200
{
  "subject_ref": "customer_123",
  "mode": "test",
  "now": "2026-05-07T10:00:00.000Z",
  "entitlements": [
    {
      "id": "ent_123",
      "account_id": "acc_123",
      "mode": "test",
      "subject_ref": "customer_123",
      "kind": "usage_pack",
      "plan": null,
      "expires_at": null,
      "usage_remaining": 990,
      "status": "active",
      "updated_at": "2026-05-07T10:00:00.000Z",
      "effective_status": "active",
      "is_active": true
    }
  ]
}

Errors

Missing bearer token401
{
  "error": "missing_bearer_token"
}
Invalid API key401
{
  "error": "invalid_api_key"
}
Plan mode not allowed403
{
  "error": "plan_mode_not_allowed",
  "code": "PLAN_MODE_NOT_ALLOWED",
  "message": "Current plan does not allow this mode.",
  "details": {
    "mode": "live",
    "allowed_modes": ["test"],
    "plan_code": "free"
  }
}

Notes

  • Read-only. Does not consume usage.
  • Use the portal for product configuration, support/debug operations, or account administration. This endpoint is only for runtime state inspection.

Runtime usage

POST/v1/usage/consume

Consume usage

Atomically consumes units from a subject's usage_pack entitlement.

Auth: Required. Uses a Licenzy runtime API key from server-side code.

Required headers

Authorizationrequired
Bearer <api_key>

Licenzy runtime API key. Each API key belongs to one project, so runtime requests are scoped to that project's products, entitlements, Stripe connection, and outbound webhook activity.

Content-Typerequired
application/json

Required for requests with JSON bodies.

Idempotency-Keyrequired
string

Required for this write. Checkout keys are limited to 200 characters.

Body

subject_refrequired
string

Customer or subject reference, min 1 and max 200 characters.

unitsrequired
integer

Positive unit count, max 1,000,000.

Request examples

cURL
curl -X POST 'https://api.licenzy.app/v1/usage/consume' \
  -H 'Authorization: Bearer lz_test_...' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: example-key-001' \
  --data '{
  "subject_ref": "customer_123",
  "units": 10
}'

Responses

Consumed200
{
  "ok": true,
  "consumed": 10,
  "usage_remaining": 990
}
Idempotent replay200
{
  "ok": true,
  "idempotent": true,
  "consumed": 10,
  "usage_remaining": 990
}

Errors

Missing idempotency key400
{
  "error": "missing_idempotency_key"
}
Invalid body400
{
  "error": "invalid_body",
  "details": {}
}
Insufficient usage409
{
  "error": "insufficient_usage"
}
Idempotency conflict409
{
  "error": "idempotency_conflict"
}
Plan limit exceeded409
{
  "error": "plan_limit_exceeded",
  "code": "PLAN_LIMIT_MONTHLY_USAGE_EXCEEDED",
  "message": "Plan limit exceeded for monthly usage.",
  "details": {
    "limit": 10000,
    "current": 9990,
    "requested": 20,
    "plan_code": "pro",
    "period_start_utc": "2026-05-01T00:00:00.000Z",
    "period_end_utc": "2026-06-01T00:00:00.000Z"
  }
}
Missing bearer token401
{
  "error": "missing_bearer_token"
}
Invalid API key401
{
  "error": "invalid_api_key"
}
Plan mode not allowed403
{
  "error": "plan_mode_not_allowed",
  "code": "PLAN_MODE_NOT_ALLOWED",
  "message": "Current plan does not allow this mode.",
  "details": {
    "mode": "live",
    "allowed_modes": ["test"],
    "plan_code": "free"
  }
}

Notes

  • Idempotency is required and enforced through the usage ledger uniqueness constraint.
  • Scoped by API key mode. Writes require commercial status active or trial.
  • Request body is only subject_ref plus units. The meter is derived from the active usage_pack entitlement.

This reference intentionally excludes /portal/* cookie/session routes and Stripe inbound plus outbound operational webhook surfaces that are not part of the public tenant-facing runtime API. Documented endpoints: 5.

Outbound payload glossary

Outbound Licenzy payloads are source-aware and reason-aware. Nullable fields are expected when a field is not relevant to the current event.

  • Common fields may include subject_ref, project_id, mode, product_code, kind, status, reason, source, entitlement_id, attempt_id, units, usage_remaining, amount_total, currency, stripe_checkout_session_id, stripe_payment_intent_id, stripe_subscription_id, current_period_end, expires_at, canceled_at, actor_user_id, metadata, created_at, and updated_at.
  • status describes the runtime entity state relevant to the current event, not the HTTP delivery status in the portal.
  • source describes where the runtime state change originated: Stripe lifecycle processing, a portal operator action, or a runtime API operation.
  • reason is operational context for why the event exists. Treat it as descriptive context, not a closed enum or the only source of truth.
  • attempt_id identifies the checkout attempt when the event still has current checkout-attempt context.
  • entitlement_id identifies the runtime entitlement row involved in the change when one exists.
  • amount_total is the billing amount for the current event snapshot when billing context is relevant.
  • metadata carries the current event-specific metadata snapshot. It can differ across Stripe, portal, and runtime sources.
  • current_period_end, expires_at, and canceled_at are lifecycle timestamps that appear only when the event needs them.
  • created_at and updated_at belong to the payload snapshot emitted by Licenzy. Treat portal delivery history timestamps separately from payload entity timestamps.
  • source=stripe includes Stripe and checkout-attempt context only when it is current and relevant.
  • source=portal includes actor_user_id for manual operator events and does not backfill stale Stripe fields.
  • source=runtime includes runtime usage context and does not backfill stale Stripe or portal fields.
  • Nullable fields are expected whenever the current event does not need billing, operator, or usage-specific context.
Stripe-sourced outbound payload example
JSON
{
  "subject_ref": "user_123",
  "project_id": "proj_123",
  "mode": "live",
  "product_code": "PRO_MONTHLY",
  "kind": "subscription",
  "status": "active",
  "reason": "<stripe-derived reason>",
  "source": "stripe",
  "entitlement_id": "ent_123",
  "attempt_id": "att_123",
  "amount_total": 7900,
  "currency": "usd",
  "stripe_checkout_session_id": "cs_test_123",
  "stripe_payment_intent_id": "pi_123",
  "stripe_subscription_id": "sub_123",
  "current_period_end": "2026-06-01T00:00:00.000Z",
  "expires_at": null,
  "canceled_at": null,
  "metadata": {},
  "created_at": "2026-05-18T09:00:00.000Z",
  "updated_at": "2026-05-18T09:00:00.000Z"
}
Portal-sourced outbound payload example
JSON
{
  "subject_ref": "user_123",
  "project_id": "proj_123",
  "mode": "live",
  "product_code": "PACK_1000",
  "kind": "usage_pack",
  "status": "active",
  "reason": "<portal operation reason>",
  "source": "portal",
  "entitlement_id": "ent_123",
  "usage_remaining": 1000,
  "actor_user_id": "usr_123",
  "metadata": {},
  "created_at": "2026-05-18T09:15:00.000Z",
  "updated_at": "2026-05-18T09:15:00.000Z"
}
Runtime-sourced outbound payload example
JSON
{
  "subject_ref": "user_123",
  "project_id": "proj_123",
  "mode": "live",
  "product_code": "PACK_1000",
  "kind": "usage_pack",
  "status": "active",
  "reason": "<runtime usage reason>",
  "source": "runtime",
  "entitlement_id": "ent_123",
  "units": 10,
  "usage_remaining": 990,
  "metadata": {},
  "created_at": "2026-05-18T09:20:00.000Z",
  "updated_at": "2026-05-18T09:20:00.000Z"
}

Error glossary

These are the most common runtime error codes you should handle explicitly in backend integrations:

  • missing_idempotency_key: checkout or usage write was sent without the required Idempotency-Key header.
  • idempotency_conflict: the same idempotency key was reused with different request parameters.
  • product_not_found: the supplied product_code does not exist in the selected project and mode.
  • invalid_product_kind: the target product cannot be used for the requested runtime flow.
  • stripe_connection_not_configured: Stripe is not configured for the selected project and mode.
  • plan_mode_not_allowed: the current Licenzy plan does not allow that mode, usually live.
  • insufficient_usage: the subject does not have enough remaining usage units to fulfill the request.
  • plan_limit_exceeded: the account hit a commercial or plan-level usage limit.
Operational note
Runtime errors belong in backend retry, purchase, or upgrade logic. Support/debug investigation and manual recovery belong in the Licenzy portal.

Common integration sequence

Most product integrations use the APIs in this order:

  1. 01Configure Stripe credentials, products, API keys, and any outbound Licenzy webhooks in the Licenzy portal.
  2. 02Make sure the API keys, products, Stripe setup, and outbound Licenzy webhooks you use all belong to the same project context.
  3. 03Start checkout from your backend with POST /v1/checkout/session.
  4. 04Redirect the user to the returned Stripe Checkout URL.
  5. 05Register the Stripe inbound webhook in Stripe so Stripe can call /v1/webhooks/stripe/tenant. Your app should not call that inbound Stripe route directly.
  6. 06Read access with POST /v1/access/check or inspect entitlements after webhook processing.
  7. 07For usage packs, consume units with POST /v1/usage/consume from your backend.
  8. 08Use the Licenzy portal for support/debug operations, outbound webhook delivery inspection, and manual entitlement recovery.

The full integration example shows the same sequence as an end-to-end implementation.

Key guides