api_keys
GET /projects/{project_id}/api-keys
List a project's API keys
Cursor-paginated key list (003 §1). Shows only the lookup prefix — never plaintext or hash. Sessions need org role admin or above; API keys need the admin scope and can list only their own project, and only the keys of their own environment (a key holds exactly one project + environment, 003 §2); sessions list both environments' keys. The default representation omits name even for named keys, preserving strict legacy clients. Explicit include_name=true opts into names; retain this option on subsequent cursor requests.
Operation ID: listApiKeys · Service: gateway-api
Authentication
apiKey(http (bearer)) — Project API key (003 §2):tsk_live_…ortsk_test_…inAuthorization: Bearer. Scoped to exactly one project + environment; scopes are a subset ofingest,read,admin. Keys are stored hashed and shown once at creation.consoleSession(http (bearer)) — Console session (003 §2): Identity Platform JWT. Data-plane calls addX-Tessera-Project+X-Tessera-Environmentheaders, validated against org membership.
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
project_id | path | yes | string | Project id (proj_ ULID). |
cursor | query | no | string | Opaque pagination cursor from a previous response's next_cursor. |
include_name | query | no | boolean | Explicitly opt into stored integration names. Omitted or false preserves the legacy representation without name. Unnamed keys omit name in both representations. |
limit | query | no | integer | Page size (cursor pagination, 003 §1). Maximum 200. |
Pagination
Cursor pagination (003 §1): pass limit (max 200) and cursor; the
response is the pagination envelope — data plus next_cursor
(null on the last page).
Responses
| Status | Schema | Description |
|---|---|---|
200 | ApiKeyPage | Page of API keys. |
default | ErrorEnvelope | Error response (003 §1). Every non-2xx response uses this envelope; the HTTP status matches error.code. Rate-limited requests get 429 resource_exhausted with retry_after_seconds and a Retry-After header; the X-RateLimit-* headers ride on EVERY response, success or error (003 §1; plan-driven per API key + endpoint class — WP-B2). A 503 unavailable carries retry_after_seconds too for a lock-wait timeout (SQLSTATE 55P03), a rule preview at capacity (preview_capacity_exhausted), or a label preview during which the erasure of every entity holding its censor clock began (label_preview_clock_entity_erased): how long to wait before retrying. |
POST /projects/{project_id}/api-keys
Create an API key
Mints a key for one environment (003 §2): secret keys are tsk_\{env\}_…, publishable keys tpk_\{env\}_…. The plaintext appears exactly once, in this response. Secret keys must declare scopes (subset of ingest, read, admin); publishable keys are forced to exactly ["ingest"] and may carry allowed_origins (enforced by browser ingestion in Phase 2, WP-S6). Requires org role admin or above. Console sessions only.
Operation ID: createApiKey · Service: gateway-api
Authentication
consoleSession(http (bearer)) — Console session (003 §2): Identity Platform JWT. Data-plane calls addX-Tessera-Project+X-Tessera-Environmentheaders, validated against org membership.
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
Idempotency-Key | header | no | string | Idempotency key accepted by all POSTs (003 §1): 24 h dedup window, keyed per project. POSTs that are reads (batchSlotScores, scoreSlotEvent, deriveSlotBands, validateSourceRawPreset, sampleSource, watchEventFeedHeads and the preview operations: previewSourceMapping, previewCatalogMapping, previewLabel, previewPatternLabel, previewMarker, previewDatasetEligibility, previewSlotAudience, previewSharedCheckpointImport, previewSlotAtlasInquiry) accept the key but never replay a stored response: a retry with the same key is executed again (and metered again) against current data. A stored response that names an entity is removed when that entity is erased. A key that already answered answers only a retry of the request that stored its response: the same caller, environment, operation, path, query, scoping headers and body (a JSON body by its content, a gzip body by its inflated content). The same key with any other request is 409 already_exists with details[0].reason idempotency_key_reused, and that request is not run. The window holds no in-flight marker: two first requests sent at once can both run. A response stored by a release before this rule cannot be matched to its request and is refused the same way until it expires; send the request again with a new key. A body over 8 MiB (ingest: over its 5 MiB request limit) is run and its response is not stored. The ingest batch (POST /events) is the exception, except for an application's credentials: under a key that answered another batch it runs again, uncached, and its events' ids deduplicate them; so it does when its stored response cannot be checked. The operations that return a one-time secret store their response sealed: the stored copy opens only for the same key and credential, and one that cannot be opened is 409 idempotency_key_reused. While the server cannot check a stored response (it cannot read the key that checks it, for one), the request is 503 unavailable with Retry-After: retry with the same key. An operation whose handler claims the key itself answers the same 503, before it runs, while the key-hash pepper cannot be read; any other request sent with a key runs meanwhile, and its response is not stored. |
project_id | path | yes | string | Project id (proj_ ULID). |
Idempotency
This operation accepts an Idempotency-Key header (003 §1): within
24 hours, keyed per project and caller, a retry of the same request
gets the first response, and the same key with another request is
409 already_exists (idempotency_key_reused). See the
Idempotency-Key parameter for the exceptions.
Request body
Responses
| Status | Schema | Description |
|---|---|---|
201 | ApiKeyWithSecret | The created key, including the one-time plaintext. |
default | ErrorEnvelope | Error response (003 §1). Every non-2xx response uses this envelope; the HTTP status matches error.code. Rate-limited requests get 429 resource_exhausted with retry_after_seconds and a Retry-After header; the X-RateLimit-* headers ride on EVERY response, success or error (003 §1; plan-driven per API key + endpoint class — WP-B2). A 503 unavailable carries retry_after_seconds too for a lock-wait timeout (SQLSTATE 55P03), a rule preview at capacity (preview_capacity_exhausted), or a label preview during which the erasure of every entity holding its censor clock began (label_preview_clock_entity_erased): how long to wait before retrying. |
DELETE /projects/{project_id}/api-keys/{key_id}
Revoke an API key
Revocation is permanent; authenticated calls with the key fail with 401 unauthenticated within 5 seconds (WP-A2 req 7). Requires org role admin or above. Console sessions only.
Operation ID: revokeApiKey · Service: gateway-api
Authentication
consoleSession(http (bearer)) — Console session (003 §2): Identity Platform JWT. Data-plane calls addX-Tessera-Project+X-Tessera-Environmentheaders, validated against org membership.
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
key_id | path | yes | string | API key id (key_ ULID). |
project_id | path | yes | string | Project id (proj_ ULID). |
Responses
| Status | Schema | Description |
|---|---|---|
204 | — | Key revoked (idempotent). |
default | ErrorEnvelope | Error response (003 §1). Every non-2xx response uses this envelope; the HTTP status matches error.code. Rate-limited requests get 429 resource_exhausted with retry_after_seconds and a Retry-After header; the X-RateLimit-* headers ride on EVERY response, success or error (003 §1; plan-driven per API key + endpoint class — WP-B2). A 503 unavailable carries retry_after_seconds too for a lock-wait timeout (SQLSTATE 55P03), a rule preview at capacity (preview_capacity_exhausted), or a label preview during which the erasure of every entity holding its censor clock began (label_preview_clock_entity_erased): how long to wait before retrying. |
GET /projects/{project_id}/api-keys/{key_id}/purposes
Get an API key's purpose allow-list
The key's purpose allow-list (WP-V12 req 8). purposes: null means unrestricted, the default for every key. Additive to the closed ApiKey record, which never carries purposes. A revoked or foreign key is 404 not_found. Requires org role admin or above. Console sessions only.
Operation ID: getApiKeyPurposes · Service: gateway-api
Authentication
consoleSession(http (bearer)) — Console session (003 §2): Identity Platform JWT. Data-plane calls addX-Tessera-Project+X-Tessera-Environmentheaders, validated against org membership.
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
key_id | path | yes | string | API key id (key_ ULID). |
project_id | path | yes | string | Project id (proj_ ULID). |
Responses
| Status | Schema | Description |
|---|---|---|
200 | PrincipalPurposes | The key's purpose allow-list. |
default | ErrorEnvelope | Error response (003 §1). Every non-2xx response uses this envelope; the HTTP status matches error.code. Rate-limited requests get 429 resource_exhausted with retry_after_seconds and a Retry-After header; the X-RateLimit-* headers ride on EVERY response, success or error (003 §1; plan-driven per API key + endpoint class — WP-B2). A 503 unavailable carries retry_after_seconds too for a lock-wait timeout (SQLSTATE 55P03), a rule preview at capacity (preview_capacity_exhausted), or a label preview during which the erasure of every entity holding its censor clock began (label_preview_clock_entity_erased): how long to wait before retrying. |
PUT /projects/{project_id}/api-keys/{key_id}/purposes
Replace an API key's purpose allow-list
Replaces the key's allow-list (WP-V12 req 8): null lifts the restriction; a list names 1–16 unique purposes registered in the key's project (GET /v1/purposes). Unregistered, malformed, reserved or duplicate names and unknown fields are 400 invalid_argument naming the offending purposes. Once restricted, every output path whose effective purpose (destination or export declared purpose, else the slot purpose, else general) is outside the list answers 403 permission_denied with details[0].reason purpose_not_allowed and details[0].purpose. The listed purposes pin the registry (purpose_referenced) while the key is live. The change and its api_key.purposes.update audit record commit in one transaction; an unchanged list writes nothing. Takes effect within 5 seconds. Requires org role admin or above. Console sessions only.
Operation ID: putApiKeyPurposes · Service: gateway-api
Authentication
consoleSession(http (bearer)) — Console session (003 §2): Identity Platform JWT. Data-plane calls addX-Tessera-Project+X-Tessera-Environmentheaders, validated against org membership.
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
key_id | path | yes | string | API key id (key_ ULID). |
project_id | path | yes | string | Project id (proj_ ULID). |
Request body
Responses
| Status | Schema | Description |
|---|---|---|
200 | PrincipalPurposes | The stored allow-list. |
default | ErrorEnvelope | Error response (003 §1). Every non-2xx response uses this envelope; the HTTP status matches error.code. Rate-limited requests get 429 resource_exhausted with retry_after_seconds and a Retry-After header; the X-RateLimit-* headers ride on EVERY response, success or error (003 §1; plan-driven per API key + endpoint class — WP-B2). A 503 unavailable carries retry_after_seconds too for a lock-wait timeout (SQLSTATE 55P03), a rule preview at capacity (preview_capacity_exhausted), or a label preview during which the erasure of every entity holding its censor clock began (label_preview_clock_entity_erased): how long to wait before retrying. |
Errors
Every non-2xx response uses the 003 §1 error envelope; error.request_id
matches the X-Request-Id response header, and the HTTP status always
matches error.code:
{
"error": {
"code": "invalid_argument",
"details": [
{
"type": "field_violation"
}
],
"message": "human-readable",
"request_id": "req_01hxample"
}
}
Closed error-code set (003 §1): invalid_argument, unauthenticated, permission_denied, not_found, already_exists, failed_precondition, resource_exhausted, internal, unavailable.
resource_exhausted responses (HTTP 429) additionally carry
retry_after_seconds. See ErrorEnvelope.