Passports API

Passports are cryptographically signed JWTs that prove an agent's identity and authorization. They are the core trust primitive in STACK — any service can verify a passport without calling STACK's API by using the public JWKS endpoint. Passports are signed with EdDSA (Ed25519), have a default TTL of 15 minutes (hard max: 1 hour), and support delegation chains up to 4 hops deep.

All endpoints except POST /v1/passports/verify and the GET /v1/.well-known/jwks.json JWKS endpoint require an Authorization: Bearer sk_live_... header.

Issue Passport

POST /v1/passports/issue

Issue a new passport JWT for a registered agent. The passport embeds the agent's identity, service scopes, identity claims, and accountability settings in a stk namespace within the JWT payload.

Request Body

json
{
  "agent_id": "agt_7kx9m2nq4p",
  "ttl_seconds": 900,
  "scopes": [
    {
      "service_connection_id": "svc_conn_abc123",
      "scopes": ["read:messages", "write:messages"]
    }
  ],
  "identity_claim_ids": ["clm_9xm3kR7wL2"],
  "intent": {
    "summary": "Process and respond to customer support emails",
    "services": ["slack", "gmail"],
    "will_delegate": false,
    "estimated_duration_seconds": 1800
  },
  "checkpoint_interval_seconds": 300
}
  • agent_id (string, required) — The agent ID to issue the passport for. Must be registered under your account.
  • ttl_seconds (number, optional) — Time-to-live in seconds. Min: 60, max: 3600. Default: 900 (15 minutes).
  • scopes (array, optional) — Array of {service_connection_id, scopes} objects. If omitted, uses all granted scopes.
  • identity_claim_ids (string[], optional) — Specific identity claim IDs to embed. If omitted, all active claims are included.
  • intent (object, optional) — Declaration of what the agent intends to do with this passport.
  • intent.summary (string, required within intent) — Plain-text description of intended activity. Max 500 characters.
  • intent.services (string[], required within intent) — Services the agent plans to use.
  • intent.will_delegate (boolean, optional) — Whether the agent plans to delegate this passport.
  • intent.estimated_duration_seconds (number, optional) — How long the agent expects to use the passport. Min 60, max 86400.
  • checkpoint_interval_seconds (number, optional) — How often the agent should submit checkpoints. Min 60, max 3600.

Request Example

bash
curl -X POST https://api.getstack.run/v1/passports/issue \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer sk_live_op_abc123" \
  -d '{
    "agent_id": "agt_7kx9m2nq4p",
    "ttl_seconds": 600
  }'

Response — 201 Created

json
{
  "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9...",
  "jti": "ppt_9xm3kR7wL2",
  "expires_at": "2026-04-15T10:40:00.000Z"
}

JWT Payload Structure (stk namespace)

The decoded JWT uses a stk namespace for all STACK-specific claims. The sub field is the agent ID.

json
{
  "iss": "https://api.getstack.run",
  "sub": "agt_7kx9m2nq4p",
  "iat": 1713174600,
  "exp": 1713175200,
  "jti": "ppt_9xm3kR7wL2",
  "stk": {
    "operator_id": "op_abc123",
    "agent_id": "agt_7kx9m2nq4p",
    "agent_name": "invoice-processor",
    "services": [
      {
        "service_id": "svc_123",
        "service_name": "slack",
        "scopes": ["read:messages"],
        "credential_ref": "cred_ref_abc"
      }
    ],
    "identity_claims": [
      {
        "claim_id": "clm_9xm3kR7wL2",
        "provider": "bankid_se",
        "layer": "humanity",
        "claim_type": "verified_human",
        "assurance_level": "high",
        "verified_at": 1713174000,
        "expires_at": 1744710000,
        "humanity_verified": true
      }
    ],
    "delegation_depth": 0,
    "session_id": "sess_abc123",
    "accountability": "enforced",
    "intent_summary": "Process customer support emails",
    "intent_services": ["slack", "gmail"],
    "checkpoint_interval": 300
  }
}

Verify Passport

POST /v1/passports/verify

Verify a passport token and return its decoded claims. This endpoint is unauthenticated — no API key is required. It checks the EdDSA signature, expiration, and revocation status. Optionally pass a service_id to verify the passport grants access to a specific service.

Request Body

json
{
  "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9...",
  "service_id": "svc_123"
}
  • token (string, required) — The passport JWT to verify.
  • service_id (string, optional) — If provided, verification also checks that the passport includes scopes for this service.

Request Example

bash
curl -X POST https://api.getstack.run/v1/passports/verify \
  -H "Content-Type: application/json" \
  -d '{
    "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9..."
  }'

Response — 200 OK

json
{
  "valid": true,
  "jti": "ppt_9xm3kR7wL2",
  "agent_id": "agt_7kx9m2nq4p",
  "expires_at": "2026-04-15T10:40:00.000Z",
  "claims": {
    "operator_id": "op_abc123",
    "agent_id": "agt_7kx9m2nq4p",
    "agent_name": "invoice-processor",
    "services": [],
    "identity_claims": [],
    "delegation_depth": 0,
    "session_id": "sess_abc123"
  }
}

The verify endpoint does not require authentication. Any service can verify a passport by posting the token. For fully offline verification, use the JWKS endpoint to fetch STACK's public keys and verify the EdDSA signature locally.

Revoke Passport

POST /v1/passports/revoke

Immediately revoke an active passport. Revocation propagates via Redis within 60 seconds.

Request Body

json
{
  "jti": "ppt_9xm3kR7wL2",
  "reason": "Agent compromised"
}
  • jti (string, required) — The passport JTI (unique identifier) to revoke.
  • reason (string, optional) — Human-readable reason for revocation. Default: "Revoked by operator".

Response — 200 OK

json
{
  "success": true,
  "jti": "ppt_9xm3kR7wL2"
}

Delegate Passport

POST /v1/passports/delegate

Create a delegated passport from an existing one. The child passport inherits the parent's identity chain but can only have equal or narrower scopes. Maximum delegation depth is 4 hops.

Request Body

json
{
  "parent_passport_token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9...",
  "child_agent_id": "agt_3fh8j1kw6r",
  "scopes": [
    {
      "service_connection_id": "svc_conn_abc123",
      "scopes": ["read:messages"]
    }
  ],
  "ttl_seconds": 300
}
  • parent_passport_token (string, required) — A valid, non-expired passport JWT to delegate from.
  • child_agent_id (string, required) — The target agent receiving the delegated passport.
  • scopes (array, required) — Array of {service_connection_id, scopes}. Must be a subset of the parent passport scopes.
  • ttl_seconds (number, optional) — TTL for the child passport. Min 60, max 3600. Cannot exceed parent expiry.

Response — 201 Created

json
{
  "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9...",
  "jti": "ppt_5nK2xW8mR4",
  "expires_at": "2026-04-15T10:35:00.000Z"
}

Refresh Passport

POST /v1/passports/refresh

Refresh an existing passport token. Returns a new JWT with a fresh TTL while preserving the same scopes, claims, and delegation chain.

Request Body

json
{
  "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9...",
  "ttl_seconds": 900
}
  • token (string, required) — The passport JWT to refresh.
  • ttl_seconds (number, optional) — TTL for the new passport. Min 60, max 3600. Default: 900.

Response — 201 Created

json
{
  "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9...",
  "jti": "ppt_8rW4mK9xN1",
  "expires_at": "2026-04-15T10:55:00.000Z"
}

Submit Checkpoint

POST /v1/passports/:jti/checkpoint

Submit a checkpoint for an active passport. Checkpoints report what the agent has been doing — which services were used, how many actions taken, and whether any delegation occurred. Required at regular intervals for agents in enforced accountability mode.

Request Body

json
{
  "services_used": ["slack", "gmail"],
  "tool_calls": [
    { "service": "slack", "method": "postMessage", "target": "#support" },
    { "service": "gmail", "method": "send" }
  ],
  "actions_count": 5,
  "delegated_to": [],
  "summary": "Responded to 3 support tickets, sent 2 follow-up emails"
}
  • services_used (string[], required) — List of service identifiers accessed since last checkpoint.
  • tool_calls (array, optional) — Detailed tool call log. Each entry has service, method, and optional target.
  • actions_count (number, required) — Total number of actions performed. Min 0.
  • delegated_to (string[], optional) — Agent IDs that this passport was delegated to.
  • summary (string, optional) — Human-readable summary of activity. Max 1000 characters.

Response — 201 Created

Returns the checkpoint record with any flags raised by the accountability engine.

Submit Checkout

POST /v1/passports/:jti/checkout

Submit a final checkout report when the agent is done using the passport. This is the end-of-session report that triggers the review process for agents in enforced mode. The checkout compares the agent's declared intent against its actual activity.

Request Body

json
{
  "services_used": ["slack", "gmail"],
  "tool_calls": [
    { "service": "slack", "method": "postMessage" }
  ],
  "actions_count": 12,
  "delegated_to": [],
  "summary": "Completed support ticket processing session"
}
  • services_used (string[], required) — All services accessed during the passport session.
  • tool_calls (array, optional) — Complete tool call log for the session.
  • actions_count (number, required) — Total actions performed. Min 0.
  • delegated_to (string[], optional) — Agent IDs this passport was delegated to.
  • summary (string, optional) — Session summary. Max 2000 characters.

For agents in enforced mode, the checkout triggers an automated review. If critical flags are raised (e.g., undeclared services, missed checkpoints), the agent may be blocked from receiving new passports until an operator approves the review.

Get Passport Report

GET /v1/passports/:jti/report

Retrieve the full accountability report for a passport, including all checkpoints, the checkout summary, any raised flags, and the review status.

Request Example

bash
curl https://api.getstack.run/v1/passports/ppt_9xm3kR7wL2/report \
  -H "Authorization: Bearer sk_live_op_abc123"

Review Queue

GET /v1/passports/reviews

List passport checkout reviews that need operator attention. Supports filtering by status and pagination.

Query Parameters

  • status (string, optional) — Filter by review status.
  • page (number, optional) — Page number, starting at 1.
  • limit (number, optional) — Results per page, max 100.

Request Example

bash
curl "https://api.getstack.run/v1/passports/reviews?status=pending&limit=10" \
  -H "Authorization: Bearer sk_live_op_abc123"

Decide Review

POST /v1/passports/reviews/:checkoutId/decide

Submit an approval or block decision for a passport checkout review.

Request Body

json
{
  "decision": "approved",
  "notes": "Reviewed activity log, all actions within expected scope",
  "block_future": false
}
  • decision (string, required) — Either "approved" or "blocked".
  • notes (string, optional) — Operator notes about the decision. Max 1000 characters.
  • block_future (boolean, optional) — If true and decision is "blocked", prevents the agent from getting new passports.

List Active Passports

GET /v1/passports/active

List all currently active (non-expired, non-revoked) passports for your operator account. Supports filtering by agent or session.

Query Parameters

  • agent_id (string, optional) — Filter to passports for a specific agent.
  • session_id (string, optional) — Filter to passports in a specific session.

Response — 200 OK

json
[
  {
    "jti": "ppt_9xm3kR7wL2",
    "agent_id": "agt_7kx9m2nq4p",
    "session_id": "sess_abc123",
    "delegation_depth": 0,
    "parent_passport_id": null,
    "identity_claim_ids": ["clm_9xm3kR7wL2"],
    "issued_at": "2026-04-15T10:30:00.000Z",
    "expires_at": "2026-04-15T10:45:00.000Z"
  }
]

Bulk Revocation

Revoke All for Agent

POST /v1/passports/revoke-agent/:agentId — Revoke all active passports for a specific agent.

bash
curl -X POST https://api.getstack.run/v1/passports/revoke-agent/agt_7kx9m2nq4p \
  -H "Authorization: Bearer sk_live_op_abc123" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "Agent compromised" }'

Revoke by Session

POST /v1/passports/revoke-session/:sessionId — Revoke all active passports within a specific session.

bash
curl -X POST https://api.getstack.run/v1/passports/revoke-session/sess_abc123 \
  -H "Authorization: Bearer sk_live_op_abc123" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "Session terminated" }'

Revoke All (Emergency)

POST /v1/passports/revoke-all — Revoke every active passport for your entire operator account. Requires explicit confirmation.

json
{
  "confirm": true,
  "reason": "Emergency: suspected key compromise"
}

Bulk Revocation Response

json
{
  "success": true,
  "revoked_count": 7
}

The revoke-all endpoint is an emergency action that immediately invalidates every active passport in your organization. Use with extreme caution.

JWKS Endpoint

GET /v1/.well-known/jwks.json

Public endpoint (no authentication required) that returns STACK's Ed25519 public keys in JWKS format. Use this to verify passport signatures offline without calling the verify endpoint.

Request Example

bash
curl https://api.getstack.run/v1/.well-known/jwks.json

Signing and Verification

  • Algorithm: EdDSA with Ed25519 curve
  • Default TTL: 900 seconds (15 minutes)
  • Maximum TTL: 3600 seconds (1 hour)
  • Minimum TTL: 60 seconds
  • Revocation propagation: under 60 seconds via Redis
  • Verification: local via JWKS or POST /v1/passports/verify
  • Custom claims namespace: stk (all STACK claims nested under this key)
STACK — Infrastructure for AI Agents