Core principles
- Clear communication: structured responses make success and failure equally informative
- Practical over purist: pragmatic choices over rigid adherence to a single paradigm
- Predictable patterns: consistent endpoint behavior
Response structure
All responses share a consistent envelope:data and include pagination
metadata in a top-level pagination object:
limit and cursor fields in the JSON
body. Responses require pagination.hasMore. Include pagination.cursor only
when another page exists.
Working with the API
Always use the request ID
Every response includes a uniquerequestId. Include it when debugging or requesting support. You can also search for the request ID in logs.
Handling pagination
- Make your initial request.
- Check
pagination.hasMore. - Use
pagination.cursorfor the next request.
Versioning
APIs use a major version in the URL, for example/v2/. Breaking changes increment the major version.
OpenAPI examples
Every public v2 operation must include realistic OpenAPI examples. Examples are part of the API contract because docs, SDKs, and agents use them to learn the correct request and response shape. Each operation needs:- At least one request example
- At least one successful response example
- At least one example per status code or different outcome
api_1234abcd, key_1234abcd,
perm_1234abcd, and req_1234abcd. Don’t use placeholder names like foo,
bar, or example when a domain-specific value is available.
Schema strictness
OpenAPI schemas are closed by default. Request bodies must useadditionalProperties: false unless the object is intentionally map-like.
Response objects must also be closed when the shape is known.
Use open objects only for explicit key/value maps, such as metadata fields,
analytics query rows, or user-provided attribute bags. Open objects must explain
what keys and values are valid. When possible, document size limits, value type
limits, and performance impact.
Don’t use open objects as an escape hatch for incomplete modeling. If an API
field has known variants, model them explicitly in the schema.
Idempotency and retries
Every operation must declare its idempotency and retry behavior. Retries are a client-visible contract, especially for SDKs and agents that may retry requests after connection errors or5xx responses.
Classify each operation as one of:
idempotent: retrying the same request body produces the same final stateconditionally-idempotent: retrying is safe only with a stable client value, such as an idempotency key or caller-provided resource identifiernot-idempotent: retrying may create additional side effects
not-idempotent until the API provides a stronger contract.
OpenAPI operations must expose this as machine-readable metadata:
Update semantics
Update endpoints use three-state field semantics:- Omitted field: leave the existing value unchanged
- Field with a value: set or replace the value
- Field with
null: clear the value, only when the schema explicitly permitsnull
metadata: {} replaces metadata with an empty object, and
ratelimits: [] replaces the rate limit collection with an empty collection
when the endpoint defines full replacement behavior.
Document nullable clear behavior on each field that supports it. Don’t rely on a
general update endpoint to imply that every field accepts null.
Replacement arrays replace the entire collection unless the endpoint is
explicitly named as an incremental action, such as addPermissions or
removeRoles.
Resource identifiers
Prefer user-meaningful identifiers for public API inputs when they are stable and unambiguous. Slugs, names, and external IDs are easier for humans and agents to use than internal Unkey IDs because callers often know them without making an extra lookup request. Internal Unkey IDs are opaque. Don’t require callers to parse their prefixes or derive meaning from their structure. When an endpoint requires an internal ID, document that the value must be fetched from another API response and include a realistic example. Every identifier field must document:- Whether it accepts a slug, external ID, internal Unkey ID, or multiple forms
- Whether the value is caller-defined or generated by Unkey
- Whether the value is stable over the resource lifetime
- A realistic example value
Delete behavior
Resource delete endpoints must be safe by default. Public API callers don’t get dashboard confirmation flows, and agents can delete resources accidentally. For resources, prefer soft deletion first. Soft deletion immediately removes the resource from normal use, then schedules hard deletion after 48 hours with a delayed workflow. During the 48-hour window, users can restore the resource and cancel the hard deletion. Use one generic restore endpoint instead of one restore endpoint per resource type. The caller passes the resource type and resource identifier in the request body. The restore operation must validate that the resource type supports restoration and that the resource is still inside its restore window. Delete endpoints must document:- Whether deletion is soft, hard, or configurable
- How long the restore window lasts
- How to restore the resource with the generic restore endpoint
- What related resources are affected immediately
- When hard deletion happens
- Whether repeated delete requests are idempotent
- Whether deleted resources remain visible in audit logs

