Tenants
One row per customer organisation; the unit the invoice counts.
packages/api/src/resources/tenants.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.POST /v1/tenants
Answers 201 on success.
Authentication.
- A
krsk_secret key or akrses_dashboard session, asAuthorization: Bearer.
Body. Validated by this schema, from the control plane's own source:
const CreateTenant = z.object({
external_id: z.string().min(1).max(200),
name: Name.optional(),
meta: Meta.optional(),
});Example.
curl https://keyring-api.belghalem.fr/v1/tenants \
-X POST \
-H "Authorization: Bearer $KEYRING_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"external_id": "cus_8f3k2",
"name": "Globex Corporation",
"meta": {
"plan": "growth"
}
}'HTTP/1.1 201 Created
Content-Type: application/json
{
"object": "tenant",
"id": "01a0ade7-b909-7ae2-9477-aa287f446aad",
"external_id": "cus_8f3k2",
"name": "Globex Corporation",
"meta": {
"plan": "growth"
},
"embed_epoch": 1,
"created_at": "2026-09-17T05:47:19.688Z",
"updated_at": "2026-09-17T05:47:19.688Z"
}Example: A body that fails validation.
curl https://keyring-api.belghalem.fr/v1/tenants \
-X POST \
-H "Authorization: Bearer $KEYRING_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Missing its external id"
}'HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"error": {
"code": "invalid_request",
"message": "The request body is not valid.",
"details": [
{
"path": "external_id",
"message": "Invalid input: expected string, received undefined"
}
]
}
}GET /v1/tenants
Answers 200 on success.
Authentication.
- A
krsk_secret key or akrses_dashboard session, asAuthorization: Bearer.
Example.
curl https://keyring-api.belghalem.fr/v1/tenants \
-H "Authorization: Bearer $KEYRING_SECRET_KEY"HTTP/1.1 200 OK
Content-Type: application/json
{
"object": "list",
"data": [
{
"object": "tenant",
"id": "01a0ade7-b909-7ae2-9477-aa287f446aad",
"external_id": "cus_8f3k2",
"name": "Globex Corporation",
"meta": {
"plan": "growth"
},
"embed_epoch": 1,
"created_at": "2026-09-17T05:47:19.688Z",
"updated_at": "2026-09-17T05:47:19.688Z"
}
]
}GET /v1/tenants/:id
Answers 200 on success.
Authentication.
- A
krsk_secret key or akrses_dashboard session, asAuthorization: Bearer.
Path parameters. id.
Example.
curl https://keyring-api.belghalem.fr/v1/tenants/01a0ade7-b909-7ae2-9477-aa287f446aad \
-H "Authorization: Bearer $KEYRING_SECRET_KEY"HTTP/1.1 200 OK
Content-Type: application/json
{
"object": "tenant",
"id": "01a0ade7-b909-7ae2-9477-aa287f446aad",
"external_id": "cus_8f3k2",
"name": "Globex Corporation",
"meta": {
"plan": "growth"
},
"embed_epoch": 1,
"created_at": "2026-09-17T05:47:19.688Z",
"updated_at": "2026-09-17T05:47:19.688Z"
}Example: A tenant that was deleted.
curl https://keyring-api.belghalem.fr/v1/tenants/01a0ade7-bbcc-7bd3-895b-367eea450fb3 \
-H "Authorization: Bearer $KEYRING_SECRET_KEY"HTTP/1.1 404 Not Found
Content-Type: application/json
{
"error": {
"code": "not_found",
"message": "No such tenant."
}
}PATCH /v1/tenants/:id
Answers 200 on success.
Authentication.
- A
krsk_secret key or akrses_dashboard session, asAuthorization: Bearer.
Path parameters. id.
Body. Validated by this schema, from the control plane's own source:
const UpdateTenant = z.object({
name: Name.nullable().optional(),
meta: Meta.optional(),
});Example.
curl https://keyring-api.belghalem.fr/v1/tenants/01a0ade7-b909-7ae2-9477-aa287f446aad \
-X PATCH \
-H "Authorization: Bearer $KEYRING_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"meta": {
"plan": "scale"
}
}'HTTP/1.1 200 OK
Content-Type: application/json
{
"object": "tenant",
"id": "01a0ade7-b909-7ae2-9477-aa287f446aad",
"external_id": "cus_8f3k2",
"name": "Globex Corporation",
"meta": {
"plan": "scale"
},
"embed_epoch": 1,
"created_at": "2026-09-17T05:47:19.688Z",
"updated_at": "2026-09-17T05:47:19.694Z"
}POST /v1/tenants/:id/embed_tokens/revoke
Report section 7.2's real revocation lever, and the reason a five-minute TTL is defensible: bumping the tenant's epoch kills every outstanding embed token for that tenant on its next request, however long it had left.
The bump commits first and the cache is written after. The other order would let a reader see the new epoch before the transaction that produced it committed, and a rollback would then leave the cache ahead of the database -- refusing tokens that are still valid, with nothing to correct it but the entry's expiry.
This one is audited, unlike the mints (see embed-tokens.controller.ts):
it is a revocation, it is human-paced, and it is the event an auditor asks
about.
Answers 200 on success.
Authentication.
- A
krsk_secret key or akrses_dashboard session, asAuthorization: Bearer.
Path parameters. id.
Example.
curl https://keyring-api.belghalem.fr/v1/tenants/01a0ade7-b909-7ae2-9477-aa287f446aad/embed_tokens/revoke \
-X POST \
-H "Authorization: Bearer $KEYRING_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{}'HTTP/1.1 200 OK
Content-Type: application/json
{
"object": "tenant",
"id": "01a0ade7-b909-7ae2-9477-aa287f446aad",
"embed_epoch": 2,
"embed_tokens_revoked": true
}DELETE /v1/tenants/:id
Deleting a customer organisation revokes the keys it holds. This is the
call a vendor makes when a customer churns or is cut off for abuse, so a
version of it that leaves those keys verifying is the wrong one;
cascadeRevoke carries the reasoning and the policy-version bump.
Answers 200 on success.
Authentication.
- A
krsk_secret key or akrses_dashboard session, asAuthorization: Bearer.
Path parameters. id.
Example.
curl https://keyring-api.belghalem.fr/v1/tenants/01a0ade7-bbcc-7bd3-895b-367eea450fb3 \
-X DELETE \
-H "Authorization: Bearer $KEYRING_SECRET_KEY"HTTP/1.1 200 OK
Content-Type: application/json
{
"object": "tenant",
"id": "01a0ade7-bbcc-7bd3-895b-367eea450fb3",
"deleted": true,
"revoked_keys": 0
}Shared validators
Defined once in packages/api/src/validation.ts and used by the schemas above.
const Meta = z.record(z.string(), z.unknown());const Name = z.string().min(1).max(200);