Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: KnoxCall Client Management Audit Logs 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: Audit Logs
description: Query the immutable audit trail of all API actions.
paths:
/audit-logs:
get:
operationId: listAuditLogs
summary: List audit logs
description: 'Returns a paginated list of audit log entries recording all actions performed
via the API. Entries are ordered by creation time descending. Supports filtering
by action and resource type.
**`details` visibility.** `details` is always sanitised: values under
credential-shaped keys and any `user:password@` userinfo in a URL are removed.
An API key or OAuth client holding `audit_log:list` receives the sanitised
`details`. A user-bound token (the `knoxcall` CLI, device and
authorization-code grants) receives them only when its holder may read audit
details (`audit_log:read_details`: Admin or Owner). Otherwise each row carries
`details: {}` and `details_redacted: true`, and the rest of the row is unchanged.'
tags:
- Audit Logs
parameters:
- $ref: '#/components/parameters/PageParam'
- $ref: '#/components/parameters/PerPageParam'
- name: action
in: query
schema:
type: string
description: Filter by action (e.g. `create`, `update`, `delete`, `rotate`, `revoke`, or dotted actions like `vault.detokenize`, `transit.encrypt`, `webhook.test`).
example: create
- name: resource_type
in: query
schema:
type: string
description: Filter by resource type (e.g. `route`, `secret`, `client`, `environment`, `api_key`, `webhook`, `oauth_client`, `client_credential`, `transit`, `vault`, `pki`, `dyn_db_cred`, `agent`).
example: route
responses:
'200':
description: A paginated list of audit log entries.
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/AuditLog'
meta:
$ref: '#/components/schemas/PaginationMeta'
'401':
$ref: '#/components/responses/Unauthorized'
'429':
$ref: '#/components/responses/RateLimited'
'500':
$ref: '#/components/responses/InternalError'
/audit-logs/events:
get:
operationId: streamAuditEvents
summary: Audit event feed (cursor)
description: 'A keyset-cursor feed of audit events, for pollers and SIEM shippers.
`GET /audit-logs` is offset-paginated over `created_at DESC`, which is
correct for a console and wrong for a feed: rows inserted while a poller
pages shift the offsets underneath it, so events are skipped or repeated
with no way to tell which. This endpoint is ordered by the monotonic
`seq` and resumes from an opaque cursor.
**Contract**
- **Ordering** — ascending `seq`, stable across calls.
- **Delivery** — AT LEAST ONCE. Dedupe on `id` (a uuid, stable per row).
- **Freshness** — trails real time by a short watermark, reported as
`meta.watermark_seconds`, so a row cannot appear behind a cursor you
have already passed.
- **Termination** — `next_cursor` is `null` when you have drained
everything outside the watermark. That is not the end of the feed:
poll again later with your last non-null cursor.'
tags:
- Audit Logs
parameters:
- name: cursor
in: query
schema:
type: string
description: The opaque `meta.next_cursor` from a previous response. Omit to start at the beginning.
- name: limit
in: query
schema:
type: integer
minimum: 1
description: Rows per page.
- name: action
in: query
schema:
type: string
description: Exact-match on the action name.
example: ai_gateway.request_denied
- name: action_prefix
in: query
schema:
type: string
maxLength: 128
description: Prefix-match on the action name — subscribe to a whole SURFACE rather than to one event. `ai_gateway.` is the entire AI Gateway surface (denials, agent and MCP-server creation, token mints, geo-velocity anomalies, canary leaks, unpriced models), INCLUDING action names added after your integration was built. Composes with `action` and `resource_type`. `_` and `%` are matched literally, not as wildcards.
example: ai_gateway.
- name: resource_type
in: query
schema:
type: string
description: Exact-match on the resource type.
responses:
'200':
description: A page of audit events, oldest first.
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/AuditLog'
meta:
type: object
properties:
next_cursor:
type:
- string
- 'null'
description: Pass as `cursor` on the next call. `null` means you have drained the feed to the watermark.
limit:
type: integer
delivery:
type: string
example: at_least_once
dedupe_on:
type: string
example: id
watermark_seconds:
type: integer
'400':
$ref: '#/components/responses/ValidationError'
'401':
$ref: '#/components/responses/Unauthorized'
'429':
$ref: '#/components/responses/RateLimited'
'500':
$ref: '#/components/responses/InternalError'
components:
responses:
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
RateLimited:
description: 'Too many requests. Retry after the indicated time. The `X-RateLimit-*` headers are
also present on non-429 responses whenever a limit is configured.
'
headers:
X-Request-Id:
$ref: '#/components/headers/X-Request-Id'
KnoxCall-Version:
$ref: '#/components/headers/KnoxCall-Version'
X-RateLimit-Limit:
$ref: '#/components/headers/X-RateLimit-Limit'
X-RateLimit-Remaining:
$ref: '#/components/headers/X-RateLimit-Remaining'
X-RateLimit-Reset:
$ref: '#/components/headers/X-RateLimit-Reset'
Retry-After:
$ref: '#/components/headers/Retry-After'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
error:
type: rate_limit_exceeded
message: API rate limit exceeded. Please retry later.
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 <api_key> 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
headers:
X-RateLimit-Remaining:
description: 'Remaining requests in the current window. Emitted on every response whenever a rate
limit is configured for the authenticated key.
'
schema:
type: integer
example: 999
X-RateLimit-Limit:
description: 'Maximum requests allowed per window. Emitted on every response whenever a rate
limit is configured for the authenticated key.
'
schema:
type: integer
example: 1000
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
X-RateLimit-Reset:
description: 'Epoch seconds at which the current rate-limit window resets. Emitted on every
response whenever a rate limit is configured for the authenticated key.
'
schema:
type: integer
example: 1735689600
Retry-After:
description: Seconds to wait before retrying. Set on `429` responses.
schema:
type: integer
example: 30
schemas:
AuditLog:
type: object
description: An immutable record of an action performed via the API.
properties:
id:
type: string
format: uuid
action:
type: string
description: 'The action that was performed. CRUD actions use bare verbs
(`create`, `update`, `delete`, `rotate`, `revoke`); feature areas
use dotted actions (e.g. `vault.detokenize`, `transit.encrypt`,
`pki.leaf.issue`, `webhook.test`, `client_token.mint.decrypt`).
'
example: create
resource_type:
type: string
description: 'The type of resource the action was performed on (e.g. `route`,
`route_environment_config`, `secret`, `client`, `client_credential`,
`environment`, `api_key`, `webhook`, `oauth_client`, `agent`,
`transit`, `vault`, `pki`, `dyn_db_cred`, `ephemeral_proxy`,
`client_capability_token`).
'
example: route
resource_id:
type:
- string
- 'null'
format: uuid
description: UUID of the affected resource.
details:
type:
- object
- 'null'
description: 'Additional context about the action (e.g. changed field names, resource name).
Always sanitised: values under credential-shaped keys are replaced with
`***REDACTED***` and `user:password@` userinfo is removed from URLs.
Identifiers are kept (a UUID under an `*_id` key, such as the acting
`api_key_id`). When the caller may not read audit details this is an
empty object and `details_redacted` is `true`. It is always an object
(or null), never a string: an entry whose stored details are not a JSON
object (a bare string, array or number from a self-hosted agent) is
returned sanitised and wrapped as `{"value": ...}`.
'
additionalProperties: true
example:
name: stripe-payments
via: client_api
api_key_id: f0a1b2c3-d4e5-6f7a-8b9c-0d1e2f3a4b5c
details_redacted:
type: boolean
description: '`true` when `details` was withheld from this caller (it is then `{}`).
A user-bound token needs its holder''s `audit_log:read_details` permission
(Admin or Owner) to see `details`. API keys and OAuth clients holding
`audit_log:list` always receive sanitised `details`.
'
example: false
ip_address:
type:
- string
- 'null'
description: IP address of the API caller.
example: 203.0.113.42
created_at:
type: string
format: date-time
description: Timestamp of when the action occurred.
example: '2025-03-15T10:30:00.000Z'
PaginationMeta:
type: object
description: Pagination metadata for list endpoints.
properties:
total:
type: integer
description: Total number of records matching the query.
example: 42
page:
type: integer
description: Current page number.
example: 1
per_page:
type: integer
description: Number of records per page.
example: 20
total_pages:
type: integer
description: Total number of pages, capped at the deepest page the 100,000-row offset ceiling allows -- `min(ceil(total / per_page), floor(100000 / per_page) + 1)`. When it is below `ceil(total / per_page)` the result set is larger than pagination can reach; narrow it with a filter.
example: 3
request_id:
type: string
format: uuid
description: Unique identifier for this API request.
example: 550e8400-e29b-41d4-a716-446655440000
required:
- total
- page
- per_page
- total_pages
- 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
parameters:
PageParam:
name: page
in: query
schema:
type: integer
minimum: 1
default: 1
description: Page number for pagination. Clamped server-side so the derived offset never exceeds 100,000 rows -- the effective maximum is `floor(100000 / per_page) + 1`, i.e. 1,001 at `per_page=100`. A larger value returns that last page, and `meta.page` reports it.
PerPageParam:
name: per_page
in: query
schema:
type: integer
minimum: 1
maximum: 100
default: 20
description: Number of items per page (max 100).
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 <access_token>`.
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.
'