openapi: 3.2.0 info: title: KnoxCall Client Management Client Tokens API version: '1.0' description: 'The KnoxCall Client Management API provides programmatic access to manage your API gateway resources including routes, secrets, clients, environments, webhooks, and API keys. ## Authentication All requests must include an OAuth 2.1 access token **or** a legacy API key. The recommended path for new integrations is the first-party SDKs, which mint, cache, and refresh OAuth tokens for you (start with `knoxcall login` on a developer machine, or `KNOXCALL_CLIENT_ID` / `KNOXCALL_CLIENT_SECRET` in CI). You can authenticate using any of: - **Authorization header (OAuth 2.1 bearer token — recommended)**: `Authorization: Bearer ` - **Authorization header (legacy API key)**: `Authorization: Bearer tk_xxx_yyy` - **x-api-key header (legacy API key)**: `x-api-key: tk_xxx_yyy` OAuth 2.1 access tokens (`kc_` prefix) are minted at the root-host token endpoint `https://api.knoxcall.com/oauth/token` (not under `/v1`) using the `client_credentials` or `authorization_code` grant. See the `OAuth2` security scheme and the `/oauth/*` endpoints. Some resources that are managed exclusively through OAuth reject API-key auth with a `403 use_oauth` error. Long-lived API keys remain fully supported. They come in three types: - **Standard keys** (`tk_` prefix) are used on the production API (`api.knoxcall.com`). - **Test keys** (`tk_` prefix, type=test) are used on the sandbox API (`sandbox.knoxcall.com`). - **Enterprise Access Keys** (`AKE` prefix) follow the format `AKE[16chars]:[40chars]`. Using a test key on the production API (or vice versa) will return a `403 wrong_key_type` error. ## Request IDs Every response — success or error — includes an `X-Request-Id` response header (a UUID). Its value is identical to the `request_id` field in the response body (`meta.request_id` on success, `error.request_id` on error). Quote it when contacting support. ## API Versioning The API is versioned on two axes: - **Major version — the `/v1` path prefix.** Breaking changes ship under a new major (`/v2`). `/v1` never receives a breaking change. - **Minor version — the `KnoxCall-Version` header (dated, `YYYY-MM-DD`).** Send `KnoxCall-Version: 2026-08-05` to pin the behaviour of a specific dated release. Omit the header to get the newest minor version. Every response echoes the resolved version back in a `KnoxCall-Version` response header. Within a major version, **additive changes are not breaking** and may appear without a new dated version: new endpoints, new optional request fields, new response fields, new `error.type` values, and new enum members. Clients must tolerate unknown response fields and unknown `error.type` values (see the `ErrorResponse` schema). An **unknown or malformed** `KnoxCall-Version` value returns `400` with `error.type: invalid_api_version`. ## Rate Limiting API keys can have optional rate limits configured. When a limit is configured, the following headers are emitted on **every** response (not only `429`s): - `X-RateLimit-Limit` -- maximum requests allowed per window - `X-RateLimit-Remaining` -- remaining requests in the current window - `X-RateLimit-Reset` -- epoch seconds at which the window resets When the limit is exceeded the API returns `429` (`rate_limit_exceeded` / `rate_limited`) and additionally sets `Retry-After` (seconds until the window resets). ## DPoP sender-constrained tokens, and the server nonce A token minted with a `cnf.jkt` binding (DPoP, RFC 9449) requires a `DPoP` proof header on every request, signed over the request''s method and canonical public URL. **Every response to a request that carried a `DPoP` header also carries a `DPoP-Nonce` response header** -- mints, refusals and successful calls alike, on `POST /oauth/token`, `/v1`, the tenant data plane, the self-hosted proxy and the AI gateway. The value is stateless and changes every minute; the server accepts the current and the previous minute''s, so a nonce is good for 60-120 seconds.' contact: name: KnoxCall Support url: https://knoxcall.com email: support@knoxcall.com license: name: Proprietary url: https://knoxcall.com/terms servers: - url: https://api.knoxcall.com/v1 description: Production - url: https://sandbox.knoxcall.com/v1 description: Sandbox (test keys only) security: - OAuth2: [] - BearerAuth: [] - ApiKeyAuth: [] tags: - name: Client Tokens description: Single-use client-side capability tokens (`kct_` prefix) that let a browser exchange one bound ciphertext or vault token for its plaintext. paths: /client-tokens: post: operationId: mintClientToken summary: Mint a capability token description: 'Mints a single-use capability token (`kct_` prefix). Your backend (API-key authenticated) mints the token and hands it to the browser; the browser exchanges it exactly once. The minting key must itself hold the permission being delegated. There are TWO kinds of binding, chosen by `action`: * `decrypt` / `detokenize` are **payload-pinned** — bound to the exact `kc:` ciphertext or vault token in `data`, and consumed at `POST /client/decrypt` / `POST /client/detokenize`. * `tokenize` is **vault + origin bound** — the value does not exist yet, so there is no payload to pin. Supply `vault` and `origins` instead (and no `data`); the token is consumed at `POST /client/tokenize`, which requires an `Origin` header matching one of them.' tags: - Client Tokens requestBody: required: true content: application/json: schema: type: object required: - action properties: action: type: string enum: - decrypt - detokenize - tokenize description: Which consume endpoint the token authorizes. data: type: string description: The exact `kc:` ciphertext (decrypt) or vault token (detokenize) the token is bound to. Required for those two actions and REFUSED for `tokenize`. vault: type: string description: '`tokenize` only, and required for it: the vault (name or id) the browser may write into. Resolved against your tenant and the current Live/Test space; the token stores the resolved id, so renaming the vault does not re-point an outstanding capability.' origins: type: array minItems: 1 maxItems: 10 items: type: string description: '`tokenize` only, and required for it: the exact page origins the capability may be presented from, each `https://host[:port]` with no path, query, fragment, credentials or trailing slash. Wildcards are NOT supported in any form — list each origin, or mint per page.' role: type: string description: Data-role tag (decrypt action only). ttl_seconds: type: integer minimum: 1 maximum: 600 description: Token lifetime in seconds. Defaults to 300 and is clamped to a 600-second ceiling. example: action: tokenize vault: cardholder_pii origins: - https://shop.example.com ttl_seconds: 300 responses: '200': description: The capability token. Single-use — it is consumed by its first successful exchange. content: application/json: schema: type: object properties: data: type: object properties: token: type: string description: The single-use `kct_` token. example: kct_9f8e7d6c... expires_at: type: string format: date-time action: type: string enum: - decrypt - detokenize - tokenize vault_id: type: string format: uuid description: '`tokenize` only — the resolved vault the capability is bound to.' origins: type: array items: type: string description: '`tokenize` only — the normalized, deduped, sorted origin binding.' meta: $ref: '#/components/schemas/RequestMeta' '400': $ref: '#/components/responses/ValidationError' '401': $ref: '#/components/responses/Unauthorized' '403': description: The vault is disabled, or — `card_program_unavailable` — it is a `pan`-format vault and KnoxCall does not currently hold a PCI DSS Attestation of Compliance covering browser tokenization of card data. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '404': $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalError' /client/decrypt: post: operationId: consumeClientTokenDecrypt summary: Decrypt with a capability token description: 'Exchanges a single-use `kct_` capability token for the plaintext of the bound `kc:` ciphertext. **Authenticated by the capability token itself** (`Authorization: Bearer kct_...`), not an API key. The token is invalidated on first use and must match the presented payload exactly.' tags: - Client Tokens security: - CapabilityTokenAuth: [] requestBody: required: true content: application/json: schema: type: object required: - data properties: data: type: string description: The `kc:` ciphertext the token was minted for. responses: '200': description: The decrypted value. content: application/json: schema: type: object properties: data: type: object properties: plaintext: description: The decrypted plaintext value. meta: $ref: '#/components/schemas/RequestMeta' '400': $ref: '#/components/responses/ValidationError' '401': description: Capability token invalid, expired, already used, or bound to a different payload. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': $ref: '#/components/responses/InternalError' /client/detokenize: post: operationId: consumeClientTokenDetokenize summary: Detokenize with a capability token description: 'Exchanges a single-use `kct_` capability token for the original value of the bound vault token. **Authenticated by the capability token itself** (`Authorization: Bearer kct_...`), not an API key.' tags: - Client Tokens security: - CapabilityTokenAuth: [] requestBody: required: true content: application/json: schema: type: object required: - data properties: data: type: string description: The vault token the capability token was minted for. responses: '200': description: The original value. content: application/json: schema: type: object properties: data: type: object properties: value: type: string meta: $ref: '#/components/schemas/RequestMeta' '400': $ref: '#/components/responses/ValidationError' '401': description: Capability token invalid, expired, already used, or bound to a different payload. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '404': $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalError' /client/tokenize: post: operationId: consumeClientTokenTokenize summary: Tokenize from the browser with a capability token description: 'Exchanges a single-use `kct_` capability token minted with `action: tokenize` for a vault token, putting a sensitive value INTO the bound vault without it ever reaching your own servers. **Authenticated by the capability token itself** (`Authorization: Bearer kct_...`), not an API key. **Browser-only.** This is the one `/v1` endpoint that answers CORS, and the one that REQUIRES an `Origin` header: the request must come from an origin bound into the token at mint time, and a request with no `Origin` is refused with `origin_required` (`403`). Server-side callers should use `POST /vaults/{vault}/tokens` with an API key instead. **Two browser callers, and their `Origin` headers mean different things.** A browser stamps `Origin` with the origin of the DOCUMENT that issued the request. `KnoxClient.tokenize()` runs on the merchant''s own page, so its `Origin` IS the bound page origin. A KnoxCall hosted field (`` / `mountSecureField`) runs inside a document served on `elements.knoxcall.com`, so its `Origin` is the hosted-fields origin — and it declares the page that framed it in `X-Knox-Page-Origin`, which is what the binding is then checked against. That header is read ONLY when `Origin` is this deployment''s own hosted-fields origin; from any other caller it is ignored entirely, so it cannot be used to nominate someone else''s origin. **CORS.** The `OPTIONS` preflight is answered for any origin with `Access-Control-Allow-Headers: content-type, authorization, x-knox-page-origin` and `Vary: Origin`, and grants nothing (credentials are never allowed). The actual `POST` reflects `Access-Control-Allow-Origin` on a successful exchange from a bound origin, and on **every** response — including refusals — when the requester is the hosted-fields origin itself, so the field can read the error code rather than seeing an opaque network failure. A refusal to a merchant origin omits the header entirely, so a page on an unbound origin cannot read the response. **Refusals are uniform.** An unknown, expired, already-spent, wrong-action, wrong-mode or unbound-origin token all return the same `401 invalid_token`. A capability token is a bearer secret whose holder may be an attacker, so the refusal deliberately does not say which check failed.' tags: - Client Tokens security: - CapabilityTokenAuth: [] parameters: - name: Origin in: header required: true schema: type: string description: The origin of the document issuing the request. From a merchant page it must exactly match one of the origins bound into the capability token; from a KnoxCall hosted field it is the hosted-fields origin and the binding is checked against `X-Knox-Page-Origin` instead. - name: X-Knox-Page-Origin in: header required: false schema: type: string description: Set by a KnoxCall hosted field to declare the origin of the page that framed it, as the browser reported it. Read ONLY when `Origin` is this deployment's hosted-fields origin; ignored from every other caller. Never set this yourself. requestBody: required: true content: application/json: schema: type: object required: - value properties: value: type: string description: The sensitive value to tokenize. Never echoed back in any response. metadata: type: object description: Optional per-token metadata, stored encrypted alongside the value. ttl_seconds: type: - integer - 'null' minimum: 1 description: Per-token expiry. Omit to use the vault's default; `null` means no expiry. example: value: '4111111111111111' metadata: order_ref: ord_1042 responses: '200': description: The vault token, plus display-safe metadata for card vaults. content: application/json: schema: type: object properties: data: type: object properties: token: type: string description: The vault token. Store this; the value itself is not returned. id: type: string format: uuid description: The vault-token row id. last4: type: string description: '`pan` vaults only — the last four digits, safe to display.' brand: type: string description: '`pan` vaults only, and only when the BIN is recognised: visa, mastercard, amex, discover, jcb, diners or unionpay.' expires_at: type: - string - 'null' format: date-time meta: $ref: '#/components/schemas/RequestMeta' '400': description: '`validation_error` — the request failed validation; or `wrong_format` — the value (or a string inside `metadata`) is a payment card number and this vault''s `token_format` is not `pan`. Card numbers are only ever stored in a `pan` vault, which carries the Luhn precondition, the format-preserving BIN/last4 token and the required destination allowlist. The refusal never echoes the value.' headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' KnoxCall-Version: $ref: '#/components/headers/KnoxCall-Version' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': description: Capability token invalid, expired, already used, minted for a different action or Live/Test space, or not bound to the presented origin. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '403': description: '`origin_required` — the request carried no usable `Origin` header; or `card_program_unavailable` — the bound vault is `pan`-format and KnoxCall does not currently hold a PCI DSS Attestation of Compliance covering it.' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '404': $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalError' components: responses: NotFound: description: The requested resource was not found. headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' KnoxCall-Version: $ref: '#/components/headers/KnoxCall-Version' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: type: not_found message: Route not found. request_id: 550e8400-e29b-41d4-a716-446655440000 ValidationError: description: The request failed validation (always `400 validation_error`). A malformed idempotency key returns `400 invalid_idempotency_key`. headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' KnoxCall-Version: $ref: '#/components/headers/KnoxCall-Version' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' examples: validation_error: summary: Request body failed validation value: error: type: validation_error message: name and target_base_url are required. request_id: 550e8400-e29b-41d4-a716-446655440000 invalid_idempotency_key: summary: Malformed idempotency key value: error: type: invalid_idempotency_key message: X-Idempotency-Key must be a non-empty string of at most 255 characters. request_id: 550e8400-e29b-41d4-a716-446655440000 InternalError: description: An unexpected server error occurred. headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' KnoxCall-Version: $ref: '#/components/headers/KnoxCall-Version' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: type: internal_error message: An unexpected error occurred. request_id: 550e8400-e29b-41d4-a716-446655440000 Unauthorized: description: Authentication is required or the provided API key is invalid. headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' KnoxCall-Version: $ref: '#/components/headers/KnoxCall-Version' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' examples: missing_key: summary: No API key provided value: error: type: authentication_required message: 'API key is required. Use Authorization: Bearer or x-api-key header.' request_id: 550e8400-e29b-41d4-a716-446655440000 invalid_key: summary: Invalid API key value: error: type: invalid_api_key message: Invalid API key. request_id: 550e8400-e29b-41d4-a716-446655440000 schemas: RequestMeta: type: object description: Metadata included with every API response. properties: request_id: type: string format: uuid description: Unique identifier for this API request, useful for support and debugging. example: 550e8400-e29b-41d4-a716-446655440000 required: - request_id ErrorResponse: type: object description: Standard error response envelope. properties: error: type: object properties: type: type: string description: 'Machine-readable error type. The set is OPEN — endpoints define specific types beyond the common ones below, so clients should branch on the types they know and fall back to the HTTP status class for anything unrecognized. Common values: authentication_required, invalid_api_key, wrong_key_type, subscription_inactive, rate_limit_exceeded, validation_error, forbidden, not_found, method_not_allowed (405, carries Allow), conflict, in_use, plan_limit, plan_feature, internal_error, dependency_unavailable (503, carries Retry-After), invalid_request, invalid_definition, not_cancellable, invalid_idempotency_key, idempotency_key_reuse, request_in_progress, system_client_protected, use_oauth, use_dpop_nonce (401 on /v1 and the data plane, 400 on POST /oauth/token; see "DPoP sender-constrained tokens").' example: validation_error message: type: string description: Human-readable error message. example: name and target_base_url are required. request_id: type: string format: uuid description: Unique identifier for this request. example: 550e8400-e29b-41d4-a716-446655440000 required: - type - message - request_id required: - error headers: KnoxCall-Version: description: 'The dated API version (`YYYY-MM-DD`) that served this response. Equals the value of the `KnoxCall-Version` request header when supplied, otherwise the newest version. ' schema: type: string pattern: ^\d{4}-\d{2}-\d{2}$ example: '2026-08-05' X-Request-Id: description: 'Unique identifier for this request (UUID). Present on **every** response and equal to the `request_id` field in the response body. Quote it when contacting support. ' schema: type: string format: uuid example: 550e8400-e29b-41d4-a716-446655440000 securitySchemes: OAuth2: type: oauth2 description: 'OAuth 2.1 authentication — recommended for new integrations. Access tokens (`kc_` prefix) are minted at the root-host token endpoint `https://api.knoxcall.com/oauth/token` and passed as `Authorization: Bearer `. Public clients must use PKCE with the `authorization_code` grant; confidential clients may use `client_credentials`. The first-party SDKs and the `knoxcall login` CLI handle token minting, caching, refresh, and DPoP for you. ' flows: clientCredentials: tokenUrl: https://api.knoxcall.com/oauth/token refreshUrl: https://api.knoxcall.com/oauth/token scopes: routes:read: Read routes. routes:write: Create, update, and delete routes. secrets:read: Read secret metadata. Does NOT include retrieving a live OAuth2 access token. secrets:oauth_token: Retrieve a live upstream OAuth2 access token (GET /v1/secrets/{id}/oauth2/token). Distinct from secrets:read because the call refreshes the credential at the provider. secrets:write: Create, update, and delete secrets. vaults:detokenize: Turn a vault token back into its original value. Required by GET /v1/vaults/{vault}/tokens/{token}, by a token template reference in a /v1/proxy body, and by creating a `detokenize` route action. Distinct from vaults:read, which lists vaults and token metadata and never returns a value. transit:decrypt: Open a portable `kc:` ciphertext with a tenant transit key. Required by creating a `decrypt` route action, which stores a standing instruction to open every matching field on every later request through that route. It is the same capability name the policy engine uses for POST /v1/decrypt. No routes scope satisfies it — managing a route is not permission to open the data flowing through it. proxy:invoke: Call the one-shot ephemeral proxy (any method on /v1/proxy). Required for EVERY verb — the proxy is one capability behind one handler, and a bodyless GET spends an escrowed credential exactly as a POST does. Neither proxy:read nor proxy:write satisfies it. clients:read: Read clients. clients:write: Manage clients. webhooks:write: Manage webhooks. workflows:write: Manage and execute workflows. '*:*': Full access (all scopes). authorizationCode: authorizationUrl: https://api.knoxcall.com/oauth/authorize tokenUrl: https://api.knoxcall.com/oauth/token refreshUrl: https://api.knoxcall.com/oauth/token scopes: routes:read: Read routes. routes:write: Create, update, and delete routes. secrets:read: Read secret metadata. Does NOT include retrieving a live OAuth2 access token. secrets:oauth_token: Retrieve a live upstream OAuth2 access token (GET /v1/secrets/{id}/oauth2/token). Distinct from secrets:read because the call refreshes the credential at the provider. secrets:write: Create, update, and delete secrets. vaults:detokenize: Turn a vault token back into its original value. Required by GET /v1/vaults/{vault}/tokens/{token}, by a token template reference in a /v1/proxy body, and by creating a `detokenize` route action. Distinct from vaults:read, which lists vaults and token metadata and never returns a value. transit:decrypt: Open a portable `kc:` ciphertext with a tenant transit key. Required by creating a `decrypt` route action, which stores a standing instruction to open every matching field on every later request through that route. It is the same capability name the policy engine uses for POST /v1/decrypt. No routes scope satisfies it — managing a route is not permission to open the data flowing through it. proxy:invoke: Call the one-shot ephemeral proxy (any method on /v1/proxy). Required for EVERY verb — the proxy is one capability behind one handler, and a bodyless GET spends an escrowed credential exactly as a POST does. Neither proxy:read nor proxy:write satisfies it. clients:read: Read clients. clients:write: Manage clients. webhooks:write: Manage webhooks. workflows:write: Manage and execute workflows. '*:*': Full access (all scopes). PhantomToken: type: http scheme: bearer description: "The AI **data plane** credential — a `kp_`-prefixed capability token, not a\nManagement API credential. It is minted per agent (or per MCP resource) at\n`POST /v1/ai-gateway/agents/{agentId}/tokens`, or exchanged from a CI workload's\nOIDC token at `POST /v1/oauth/token`, and it is the ONLY credential `/v1/ai/*`\naccepts: an OAuth `kc_` access token or a `tk_` API key is refused there, and a\n`kp_` token is refused on `api.knoxcall.com`.\n\nThree header spellings are accepted, so an existing provider SDK can be pointed at\nthe agent URL without rewriting its auth:\n\n Authorization: Bearer kp_live_a_...\n x-api-key: kp_live_a_...\n x-knox-ai-key: kp_live_a_...\n\n`kp_live_` is refused on the sandbox host and `kp_test_` on the Live host — the env\nsegment is enforced, not cosmetic. DPoP-bound tokens additionally require a valid\n`DPoP` proof header on every request, signed over the canonical public URL rather\nthan a rewritten `Host`.\n\nA rejected credential answers `401`/`403` in the data-plane error envelope with the\ncause in `code` and in the `X-Auth-Failure` response header.\n" BearerAuth: type: http scheme: bearer description: 'Legacy long-lived API key authentication via the Authorization header. Pass your API key as: `Authorization: Bearer tk_xxx_yyy`. Standard keys (`tk_` prefix) are accepted on `api.knoxcall.com`. Test keys (type=test) are accepted on `sandbox.knoxcall.com`. Enterprise Access Keys (`AKE` prefix) are accepted on both. Fully supported, but prefer the `OAuth2` scheme (or the SDKs, which handle OAuth for you) for new integrations. ' ApiKeyAuth: type: apiKey in: header name: x-api-key description: 'Legacy API key authentication via the `x-api-key` header. This is an alternative to Bearer token authentication for clients that cannot set the `Authorization` header. Prefer the `OAuth2` scheme for new integrations. ' CapabilityTokenAuth: type: http scheme: bearer description: 'Single-use client capability token (`kct_` prefix), passed as `Authorization: Bearer kct_...`. Minted server-side via `POST /client-tokens` and bound either to one exact payload + action (`decrypt`, `detokenize`) or to one vault + a closed set of page origins (`tokenize`). This is NOT an API key — it authenticates only the `POST /client/decrypt`, `POST /client/detokenize` and `POST /client/tokenize` consume endpoints and is invalidated after its first use. '