STACK
MENU
DOCS / API REFERENCE / PASSPORTS

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 operator-scoped bearer token.

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, optional opaque authority references, 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"],
      "allow_data_from": ["guestbook"]
    }
  ],
  "identity_claim_ids": ["clm_9xm3kR7wL2"],
  "authority_binding_ids": ["abn_tax_filing"],
  "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, constraints?, intents?, allow_data_from?} objects. If omitted, uses all granted scopes.
  • scopes[].constraints (array, optional) - Parameter limits added to the service grant baseline.
  • scopes[].intents (array, optional) - Intent references expanded into constraints.
  • scopes[].allow_data_from (string[], optional) - The services whose content may flow into this one. It must be a subset of the operator grant. Omit it to inherit the grant; pass [] to allow no cross-service flow. A missing or empty grant allows none, so issuance can never introduce a source.
  • identity_claim_ids (string[], optional) - Specific identity claim IDs to attach. If omitted, STACK uses the agent assignment and the account inheritance policy.
  • authority_binding_ids (string[], optional) - Up to eight active authority bindings for this exact agent and current agent key. STACK stores only immutable opaque references in the Passport.
  • 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.
  • intent.refs (array, optional) - Named intent references and parameters. Max 64.
  • checkpoint_interval_seconds (number, optional) - How often the agent should submit checkpoints. Min 60, max 3600.
  • mission_ids (string[], optional) - Up to eight active missions to bind to this passport.

A grant may use ["*"] as standing operator policy. Before signing, STACK replaces it with the exact active service names currently granted to the agent. The Passport never contains a wildcard, and a service connected later does not change an existing Passport.

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,
    "intent": {
      "summary": "Process support messages",
      "services": ["slack"]
    }
  }'

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",
        "connection_id": "scon_a1b2c3d4",
        "allow_data_from": ["guestbook"]
      }
    ],
    "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
      }
    ],
    "authority_refs": [
      {
        "binding_id": "abn_tax_filing",
        "binding_version": 1,
        "binding_claim_id": "pc_...",
        "binding_digest": "sha256:...",
        "receiver_id": "rcv_tax_agency",
        "receiver_profile_version_id": "apv_...",
        "expires_at": 1713175100
      }
    ],
    "delegation_depth": 0,
    "session_id": "sess_abc123",
    "accountability": "enforced",
    "intent_summary": "Process customer support emails",
    "intent_services": ["slack", "gmail"],
    "checkpoint_interval": 300
  }
}

Authority-bearing Passports

Each authority_refs entry pins the active binding version, signed claim, digest, receiver profile, and expiry. It contains no principal name, proof material, mandate text, disclosed fact, or native evidence. Runtime routes resolve the reference against live binding, proof, receiver, key, approval, and revocation state before a protected action. See Authority API.

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. The next STACK-verified call rejects it. Offline signature-only verifiers must call the online verify endpoint or honor expiry.

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",
  "cascaded_count": 0,
  "revoked_claims_count": 0
}

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"],
      "allow_data_from": ["guestbook"]
    }
  ],
  "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, constraints?, intents?, allow_data_from?}. Must be a subset of the parent passport scopes.
  • scopes[].constraints (array, optional) - Additional limits. A child cannot loosen the parent limits.
  • scopes[].intents (array, optional) - Intent references expanded into constraints.
  • scopes[].allow_data_from (string[], optional) - Data-flow declaration for the child. Must be a SUBSET of the parent scope's allow_data_from for the same service - a child can inherit or narrow the parent's flows but never gain one the parent lacked. Widening is rejected (SCOPE_ESCALATION). Omit to inherit the parent's declaration unchanged.
  • 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.

Standard-mode agents cannot submit checkpoints. Logged and enforced agents can.

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.

Standard-mode agents cannot submit a checkout.

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", blocks future issuance and revokes the agent's active Passport chain.

Approving is a governance action: with decision: "approved" and a Bearer credential (API key or OAuth token, including agentic MCP sessions) the call returns 403 GOVERNANCE_APPROVAL_REQUIRED with an approval id. A human approves at /governance/approvals in the dashboard, then the same call is retried with theX-Governance-Approval header.decision: "blocked" is tightening and never steps up; dashboard sessions decide directly. Full protocol: /docs/api/security-events.

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"],
    "authority_refs": [{
      "binding_id": "abn_tax_filing",
      "binding_version": 1,
      "binding_claim_id": "pc_...",
      "binding_digest": "sha256:...",
      "receiver_id": "rcv_tax_agency",
      "receiver_profile_version_id": "apv_...",
      "expires_at": 1713175100
    }],
    "intent_summary": "Process support messages",
    "intent_services": ["slack"],
    "intent_refs": [],
    "mission_ids": [],
    "estimated_duration_seconds": 1800,
    "checkpoint_interval_seconds": 300,
    "checkpoint_count": 1,
    "last_checkpoint_at": "2026-04-15T10:35:00.000Z",
    "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. It marks every active Passport in the account as revoked, so the next STACK-verified call rejects it. Offline signature-only verification cannot see the change and may accept the token until its signed expiry.

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: the next STACK-verified call fails after revoke. Offline signature-only checks cannot see live revocation.
  • Verification: local via JWKS or POST /v1/passports/verify
  • Custom claims namespace: stk (all STACK claims nested under this key)
stack | Docs