Exchange external IDs for a platform JWT.
const url = 'https://shiftagent.example.com/auth/token-exchange';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"tenant_external_id":"acme:tenant:128231","user_external_id":"acme:user:9f27c1","ttl_seconds":900}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://shiftagent.example.com/auth/token-exchange \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "tenant_external_id": "acme:tenant:128231", "user_external_id": "acme:user:9f27c1", "ttl_seconds": 900 }'Trades a (tenant, user) external-ID pair for a short-lived platform JWT scoped to that user — the per-user credential for conversation operations. Requires the integration service key.
- Both parties must already exist (this endpoint does not
provision — upsert first): unknown IDs →
404not-found. - Suspended tenant or deactivated user →
403(tenant-suspended/insufficient-scope). This is the lazy enforcement leg of lifecycle reconciliation: deprovisioned identities fail here even before a sweep runs. - Default TTL 15 minutes, max 60 (
ttl_seconds). Adapters may cache the JWT untilexpires_atbut must never persist it.
Authorizations
Section titled “Authorizations ”Request Body required
Section titled “Request Body required ”Body for tokenExchange.
object
The tenant’s namespaced external ID.
The user’s namespaced external ID (within that tenant).
Requested token lifetime.
Examples
Per-request user context
{ "tenant_external_id": "acme:tenant:128231", "user_external_id": "acme:user:9f27c1", "ttl_seconds": 900}Responses
Section titled “ Responses ”Short-lived platform JWT for the user.
A short-lived platform JWT scoped to one user.
object
The JWT. Cache until expires_at; never persist.
RFC 3339 / ISO 8601 timestamp, UTC.
Resolved internal tenant ID.
Resolved internal user ID.
Examples
Platform JWT (illustrative)
{ "object": "token", "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c3JfMDFoeng4amFuZTAwMSJ9.example", "token_type": "Bearer", "expires_at": "2026-07-02T10:15:00Z", "tenant_id": "tnt_01hzx8acme001", "user_id": "usr_01hzx8jane001"}Missing or invalid credentials — no bearer token, an unknown/revoked sk_int_ key, or an expired platform JWT.
RFC 9457 problem+json error envelope. type is a URI under https://shiftagent.example.com/problems/{slug} (deployment host substituted); see the API-level problem registry for every slug.
object
Problem type URI (registry slug).
Short, human-readable summary of the problem type.
HTTP status code.
Human-readable explanation specific to this occurrence.
URI reference identifying this occurrence.
Correlation ID for support and log lookup.
On name-conflict, external-id-conflict, and resource-in-use: the ID of the existing/depended-on resource — fetch it and continue (replay recovery).
On validation-error, field-level details.
object
JSON pointer to the offending field.
What failed.
Examples
Missing or invalid bearer token
{ "type": "https://shiftagent.example.com/problems/insufficient-scope", "title": "Unauthorized", "status": 401, "detail": "Provide a valid sk_int_ service key or platform JWT.", "request_id": "req_01hzx8auth001"}Forbidden — tenant-suspended (writes to a suspended tenant), insufficient-scope (key/token lacks the scope or a platform JWT reaches beyond its user), or approval-signature-invalid (approval assertion failed verification).
RFC 9457 problem+json error envelope. type is a URI under https://shiftagent.example.com/problems/{slug} (deployment host substituted); see the API-level problem registry for every slug.
object
Problem type URI (registry slug).
Short, human-readable summary of the problem type.
HTTP status code.
Human-readable explanation specific to this occurrence.
URI reference identifying this occurrence.
Correlation ID for support and log lookup.
On name-conflict, external-id-conflict, and resource-in-use: the ID of the existing/depended-on resource — fetch it and continue (replay recovery).
On validation-error, field-level details.
object
JSON pointer to the offending field.
What failed.
Examples
Suspended tenant rejects conversation writes
{ "type": "https://shiftagent.example.com/problems/tenant-suspended", "title": "Tenant suspended", "status": 403, "detail": "Tenant tnt_01hzx8acme001 is suspended; conversation writes are rejected.", "request_id": "req_01hzx8sus001"}Approval assertion failed verification
{ "type": "https://shiftagent.example.com/problems/approval-signature-invalid", "title": "Approval signature invalid", "status": 403, "detail": "Signature did not verify against approver key apk_01hzx8host001.", "request_id": "req_01hzx8sig001"}Not found — the resource does not exist, was deprovisioned, or lies outside the integration key’s subtree (indistinguishable by design).
RFC 9457 problem+json error envelope. type is a URI under https://shiftagent.example.com/problems/{slug} (deployment host substituted); see the API-level problem registry for every slug.
object
Problem type URI (registry slug).
Short, human-readable summary of the problem type.
HTTP status code.
Human-readable explanation specific to this occurrence.
URI reference identifying this occurrence.
Correlation ID for support and log lookup.
On name-conflict, external-id-conflict, and resource-in-use: the ID of the existing/depended-on resource — fetch it and continue (replay recovery).
On validation-error, field-level details.
object
JSON pointer to the offending field.
What failed.
Examples
Unknown resource
{ "type": "https://shiftagent.example.com/problems/not-found", "title": "Not found", "status": 404, "detail": "No tenant with external_id acme:tenant:999999.", "request_id": "req_01hzx8nf001"}Unprocessable — validation-error (schema/semantic validation failed; errors[] lists JSON-pointer details) or role-required (user has multiple roles and no role_id was given).
RFC 9457 problem+json error envelope. type is a URI under https://shiftagent.example.com/problems/{slug} (deployment host substituted); see the API-level problem registry for every slug.
object
Problem type URI (registry slug).
Short, human-readable summary of the problem type.
HTTP status code.
Human-readable explanation specific to this occurrence.
URI reference identifying this occurrence.
Correlation ID for support and log lookup.
On name-conflict, external-id-conflict, and resource-in-use: the ID of the existing/depended-on resource — fetch it and continue (replay recovery).
On validation-error, field-level details.
object
JSON pointer to the offending field.
What failed.
Examples
Field-level validation failure
{ "type": "https://shiftagent.example.com/problems/validation-error", "title": "Validation error", "status": 422, "detail": "One or more fields failed validation.", "errors": [ { "pointer": "/skill_access/skill_ids/0", "message": "skl_01hzx8unknown does not belong to the effective repository." } ], "request_id": "req_01hzx8val001"}Ambiguous role at conversation creation
{ "type": "https://shiftagent.example.com/problems/role-required", "title": "Role required", "status": 422, "detail": "User usr_01hzx8jane001 holds 2 roles; pass role_id explicitly.", "request_id": "req_01hzx8role01"}