Keyring

Idempotency

The claim, complete, release and heartbeat calls behind `Idempotency-Key`.

Generated from packages/api/src/idempotency/idempotency.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.

Report section 4.3's protocol, as four calls.

claim is the one that matters and it is one atomic Redis script: exactly one of N simultaneous duplicates is told to execute, and the other N-1 are told the request is already in progress. Returning that rather than blocking is Stripe's behaviour and the right one -- blocking would hold a connection in the customer's app for the duration of someone else's slow handler, which is the resource exhaustion the feature is supposed to prevent.

Like the rate-limit endpoint: no audit row, no advisory lock, and a 200 for every verdict. The 409, the 422 and the replayed status belong to the customer's caller and are issued by the SDK in the customer's process.

POST /v1/idempotency/claim

Answers 200 on success.

Authentication.

  • A krsk_ secret key only, resolved once and reused for KEYRING_HOT_AUTH_CACHE_MS (5 s by default).

Body. Validated by this schema, from the control plane's own source:

const IdempotencyClaim = z.object({
  scope_hash: Sha256Hex,
  /**
   * `SHA-256(canonical(method ‖ path ‖ sorted query ‖ body))`. Headers are
   * deliberately not in it: a retry legitimately carries a different
   * `User-Agent`, `Date` or trace header.
   */
  fingerprint: Sha256Hex,
  env: Env,
  ttl_ms: z.number().int().min(60_000).max(604_800_000).optional(),
});

Example.

Request
curl https://keyring-api.belghalem.fr/v1/idempotency/claim \
  -X POST \
  -H "Authorization: Bearer $KEYRING_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "scope_hash": "1d6ba93342007b6bfe1a8cb4eb3d2b8281c08f8a92aa1b3d8c2e71cd91ff4cec",
    "fingerprint": "c8014307dc7247716e7a83dbd326f5a331709cdb849dbb2ea2d209be379e3773",
    "env": "test"
  }'
Response
HTTP/1.1 200 OK
Content-Type: application/json

{
  "object": "idempotency_claim",
  "state": "execute",
  "lock_token": "…",
  "lock_ms": 30000,
  "stolen": false,
  "retry_after_ms": null,
  "response": null,
  "max_body_bytes": 262144
}

Example: The retry finds the stored response.

Request
curl https://keyring-api.belghalem.fr/v1/idempotency/claim \
  -X POST \
  -H "Authorization: Bearer $KEYRING_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "scope_hash": "1d6ba93342007b6bfe1a8cb4eb3d2b8281c08f8a92aa1b3d8c2e71cd91ff4cec",
    "fingerprint": "c8014307dc7247716e7a83dbd326f5a331709cdb849dbb2ea2d209be379e3773",
    "env": "test"
  }'
Response
HTTP/1.1 200 OK
Content-Type: application/json

{
  "object": "idempotency_claim",
  "state": "replay",
  "lock_token": null,
  "lock_ms": 30000,
  "stolen": false,
  "retry_after_ms": null,
  "response": {
    "status": 201,
    "headers": {
      "content-type": "application/json"
    },
    "body": "eyJpZCI6Im9yZF8xIiwiYW1vdW50Ijo0MjAwfQ==",
    "body_stored": true
  },
  "max_body_bytes": 262144
}

POST /v1/idempotency/complete

Answers 200 on success.

Authentication.

  • A krsk_ secret key only, resolved once and reused for KEYRING_HOT_AUTH_CACHE_MS (5 s by default).

Body. Validated by this schema, from the control plane's own source:

const IdempotencyComplete = z.object({
  scope_hash: Sha256Hex,
  env: Env,
  lock_token: z.string().min(8).max(64),
  status: z.number().int().min(100).max(599),
  headers: z.record(z.string().max(128), z.string().max(4096)).default({}),
  /** base64. Absent means the response was over the cap and is not replayable. */
  body: z
    .string()
    .max(6 * 1024 * 1024)
    .nullable()
    .default(null),
  ttl_ms: z.number().int().min(60_000).max(604_800_000).optional(),
});

Example.

Request
curl https://keyring-api.belghalem.fr/v1/idempotency/complete \
  -X POST \
  -H "Authorization: Bearer $KEYRING_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "scope_hash": "1d6ba93342007b6bfe1a8cb4eb3d2b8281c08f8a92aa1b3d8c2e71cd91ff4cec",
    "env": "test",
    "lock_token": "…",
    "status": 201,
    "headers": {
      "content-type": "application/json"
    },
    "body": "eyJpZCI6Im9yZF8xIiwiYW1vdW50Ijo0MjAwfQ=="
  }'
Response
HTTP/1.1 200 OK
Content-Type: application/json

{
  "object": "idempotency_record",
  "outcome": "ok",
  "body_stored": true
}

POST /v1/idempotency/release

Report section 4.4's first case, and its one deviation from Stripe: a 5xx the customer's predicate calls transient releases the record so the retry executes, instead of being stored and replayed forever. Their 503 is usually "my database was failing over", not "this operation was attempted".

Answers 200 on success.

Authentication.

  • A krsk_ secret key only, resolved once and reused for KEYRING_HOT_AUTH_CACHE_MS (5 s by default).

Body. Validated by this schema, from the control plane's own source:

const IdempotencyLock = z.object({
  scope_hash: Sha256Hex,
  env: Env,
  lock_token: z.string().min(8).max(64),
});

Example.

Request
curl https://keyring-api.belghalem.fr/v1/idempotency/release \
  -X POST \
  -H "Authorization: Bearer $KEYRING_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "scope_hash": "28942b778194cf9c5d47b4d1518a4ae449b8f7c283301bedb26ed038d62fadfd",
    "env": "test",
    "lock_token": "…"
  }'
Response
HTTP/1.1 200 OK
Content-Type: application/json

{
  "object": "idempotency_record",
  "outcome": "ok"
}

POST /v1/idempotency/heartbeat

Keeps a legitimately slow handler from being double-executed at 30 s.

Answers 200 on success.

Authentication.

  • A krsk_ secret key only, resolved once and reused for KEYRING_HOT_AUTH_CACHE_MS (5 s by default).

Body. Validated by this schema, from the control plane's own source:

const IdempotencyLock = z.object({
  scope_hash: Sha256Hex,
  env: Env,
  lock_token: z.string().min(8).max(64),
});

Example.

Request
curl https://keyring-api.belghalem.fr/v1/idempotency/heartbeat \
  -X POST \
  -H "Authorization: Bearer $KEYRING_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "scope_hash": "1d6ba93342007b6bfe1a8cb4eb3d2b8281c08f8a92aa1b3d8c2e71cd91ff4cec",
    "env": "test",
    "lock_token": "…"
  }'
Response
HTTP/1.1 200 OK
Content-Type: application/json

{
  "object": "idempotency_record",
  "outcome": "ok"
}

Shared validators

Defined once in packages/api/src/validation.ts and used by the schemas above.

const Env = z.enum(['live', 'test']);

On this page