Billing
The published plan ladder, and what is in force for this workspace.
packages/api/src/resources/billing.controller.ts by packages/docs/tools/generate-reference.mjs. Every example is a real exchange recorded against the control plane by packages/api/src/testing/reference-examples.spec.ts, with ids from that run. Credentials in the formats this product issues (kr_, krsk_, krses_, JWTs) and values under credential-named fields (password, *_secret, *_token, *_key, lookup_hash) are elided at record time; the recorded file and every page of this site are swept for credential shapes (kr_, krsk_, krses_, JWTs, URLs carrying a password, password fields) before either is accepted. Do not edit by hand: src/reference.spec.ts regenerates it and fails on a difference.What a workspace may read about its billing, and nothing else.
Both endpoints are GET and neither writes anything, which is the whole of
this controller: the plan ladder is data somebody else seeds, and the
subscription is written by POST /v1/workspaces and by the Stripe webhook
(billing/stripe-webhook.service.ts), never here. The writes on this
prefix live beside it in billing/checkout.controller.ts and
billing/spend-control.controller.ts -- separate controllers so that
"nothing in this file changes anything" stays a property of this file.
Nothing here enforces a limit -- the one enforcement this product has is
plan-limit.ts, at tenant creation.
The two differ on the test-mode boundary because their subjects do. The
catalog is a published price list with no workspace in it at all, so a
krsk_test_ credential reads it like any other. The subscription is this
workspace's commercial terms and carries the live billing figure
(active_tenants_this_month comes from keyring.billable_tenant_month,
which is defined where env = 'live'), so it is refused outright, exactly
as GET /v1/usage/billing is and for the same written reason.
GET /v1/billing/plans
The ladder, from keyring_meta -- a global catalog with no workspace to
be scoped to, which is why it lives there and why this read is unscoped
rather than inWorkspace. There is nothing workspace-shaped to filter on,
and a workspace scope would suggest there were.
Ordered by rank, never by price: an "upgrade" is the ladder's own order,
and comparing prices to decide direction breaks the first time a founding
price or an annual interval exists. A plan that is not is_public is a
row this endpoint does not serve; a retired one likewise.
Answers 200 on success.
Authentication.
- A
krsk_secret key or akrses_dashboard session, asAuthorization: Bearer.
Example.
curl https://api.keyringdev.com/v1/billing/plans \
-H "Authorization: Bearer $KEYRING_SECRET_KEY"HTTP/1.1 200 OK
Content-Type: application/json
{
"object": "list",
"data": [
{
"object": "plan",
"code": "free",
"name": "Free",
"rank": 1,
"included_tenants": 3,
"fair_use_requests_per_month": 150000,
"is_self_serve": true,
"prices": [
{
"object": "plan_price",
"currency": "EUR",
"interval": "month",
"amount_cents": 0,
"overage_cents_per_tenant": null
}
]
},
{
"object": "plan",
"code": "team",
"name": "Team",
"rank": 2,
"included_tenants": 25,
"fair_use_requests_per_month": 5000000,
"is_self_serve": true,
"prices": [
{
"object": "plan_price",
"currency": "EUR",
"interval": "month",
"amount_cents": 4900,
"overage_cents_per_tenant": null
}
]
},
{
"object": "plan",
"code": "business",
"name": "Business",
"rank": 3,
"included_tenants": 150,
"fair_use_requests_per_month": 30000000,
"is_self_serve": true,
"prices": [
{
"object": "plan_price",
"currency": "EUR",
"interval": "month",
"amount_cents": 19900,
"overage_cents_per_tenant": null
}
]
},
{
"object": "plan",
"code": "scale",
"name": "Scale",
"rank": 4,
"included_tenants": 750,
"fair_use_requests_per_month": 150000000,
"is_self_serve": true,
"prices": [
{
"object": "plan_price",
"currency": "EUR",
"interval": "month",
"amount_cents": 49900,
"overage_cents_per_tenant": null
}
]
},
{
"object": "plan",
"code": "enterprise",
"name": "Enterprise",
"rank": 5,
"included_tenants": null,
"fair_use_requests_per_month": null,
"is_self_serve": false,
"prices": []
}
]
}GET /v1/billing/subscription
What is in force for the calling workspace, read from
keyring.workspace_entitlement -- which joins billable_tenant_month,
the view, so the entitlement and the invoice read the one definition of
the billing number rather than two.
A 404 when there is no row, and that is deliberate rather than a fallback: a workspace and its subscription are written in one transaction and every older workspace was backfilled by migration 0016, so absence is a bug of ours. Answering it with an invented free entitlement would hide that, and would be a default answer to a question that has not been asked.
Answers 200 on success.
Authentication.
- A
krsk_secret key or akrses_dashboard session, asAuthorization: Bearer.
Example.
curl https://api.keyringdev.com/v1/billing/subscription \
-H "Authorization: Bearer $KEYRING_SECRET_KEY"HTTP/1.1 200 OK
Content-Type: application/json
{
"object": "subscription",
"plan_code": "free",
"included_tenants": 3,
"overage_cents_per_tenant": null,
"currency": "EUR",
"amount_cents": 0,
"interval": "month",
"status": "none",
"collection_state": "current",
"grace_until": null,
"active_tenants_this_month": 1,
"budget_cap_cents": 0,
"effective_tenant_ceiling": 3,
"tenants_held": 1,
"payments_live": false
}