Keyring

Tenants

One row per customer organisation; the unit the invoice counts.

Generated from 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 a krses_ dashboard session, as Authorization: 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.

Request
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"
    }
  }'
Response
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.

Request
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"
  }'
Response
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 a krses_ dashboard session, as Authorization: Bearer.

Example.

Request
curl https://keyring-api.belghalem.fr/v1/tenants \
  -H "Authorization: Bearer $KEYRING_SECRET_KEY"
Response
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 a krses_ dashboard session, as Authorization: Bearer.

Path parameters. id.

Example.

Request
curl https://keyring-api.belghalem.fr/v1/tenants/01a0ade7-b909-7ae2-9477-aa287f446aad \
  -H "Authorization: Bearer $KEYRING_SECRET_KEY"
Response
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.

Request
curl https://keyring-api.belghalem.fr/v1/tenants/01a0ade7-bbcc-7bd3-895b-367eea450fb3 \
  -H "Authorization: Bearer $KEYRING_SECRET_KEY"
Response
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 a krses_ dashboard session, as Authorization: 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.

Request
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"
    }
  }'
Response
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 a krses_ dashboard session, as Authorization: Bearer.

Path parameters. id.

Example.

Request
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 '{}'
Response
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 a krses_ dashboard session, as Authorization: Bearer.

Path parameters. id.

Example.

Request
curl https://keyring-api.belghalem.fr/v1/tenants/01a0ade7-bbcc-7bd3-895b-367eea450fb3 \
  -X DELETE \
  -H "Authorization: Bearer $KEYRING_SECRET_KEY"
Response
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);

On this page