Identity

STACK uses a four-level trust model. Each level builds on the previous one, and claims from different levels can be composed on a single passport. "Trust Level" and "Identity Claim" are two names for the same underlying concept - the claims are the mechanism, the levels are the names we give to common combinations.

Identity verification is separate from authentication. Authentication (API keys, session JWTs) proves you are a registered operator. Identity verification proves who the human behind the operator actually is, and that an agent is acting on their behalf.

The four levels

L0 - Base

The default for any valid passport. No identity claim required. Every agent that can issue a passport qualifies. Suitable for internal automation, marketplace browsing, and anything that does not leave the operator's account.

L1 - Humanity

Answers the question: is there a real person behind this agent? An L1-verified passport carries a verified_human claim that proves a verified identity provider has confirmed a real human is in the chain of delegation. L1 does not reveal who the person is - no PII is stored.

  • Claim type: verified_human
  • Assurance levels: substantial or high (depends on provider)
  • PII required: No - L1 never stores personal information
  • Use cases: skill invocation at L1 trust, basic agent interactions, humanity gates

L2 - Identity

Answers the question: who exactly is this person? An L2-verified passport carries a verified_identity claim that references verified identity data (name, date of birth, national ID number). The PII is encrypted at rest with AWS KMS; the passport only carries aclaim_id pointer.

L2 verification involves PII. Every L2 service requirement must declarerequires_pii: true and alawful_basis under GDPR. STACK enforces this at the API level - L2 service requirements without these fields are rejected.

  • Claim type: verified_identity
  • Assurance levels: substantial or high (depends on provider and verification depth)
  • PII required: Yes - encrypted at rest with KMS, never in JWT
  • Use cases: financial transactions, regulated workflows, high-trust skill invocation

L3 - Delegation

Answers the question: who authorized this agent to act? Delegation creates a verifiable chain from a human principal through one or more intermediaries to the acting agent. Each hop can only narrow scope - never widen. Maximum chain depth is 4 hops.

  • Claim type: delegated_authority
  • Max depth: 4 hops
  • Scope narrowing: each delegation can only restrict, never expand permissions
  • Revocation: cascading - revoking any link invalidates all downstream passports

Identity providers

STACK's identity verification is provider-agnostic - the claims framework is pluggable. One production L2 provider and two L1 paths ship today; additional providers are on the roadmap.

Stripe Identity

Document-based identity verification powered by Stripe. The user uploads a government ID (passport, driver's license) and a selfie; Stripe performs liveness detection and document authenticity checks.

  • Provider key: stripe_identity
  • Levels: L1 (humanity) + L2 (identity)
  • Assurance: substantial
  • Verification method: document upload + selfie + liveness check

Login.gov

U.S. government identity platform used by federal agencies. Supports two Identity Assurance Levels: IAL1 (self-asserted, email-verified) and IAL2 (document-verified with in-person or remote proofing option). Free-tier for federally-aligned workflows.

  • Provider key: login_gov
  • Levels: L1 + L2
  • Assurance: IAL1 = substantial, IAL2 = high
  • Verification method: OAuth 2.0 + OIDC with government-grade proofing

Turnstile

Humanity-only check via an inline Cloudflare Turnstile token. Verifies that a request originates from a real browser without collecting PII. Suitable for L1 gates.

  • Provider key: turnstile
  • Levels: L1 (humanity only)
  • Assurance: substantial
  • Verification method: inline token, no redirect

Verification flow

All providers follow the same abstract flow regardless of underlying mechanism:

1. Initiate verification

bash
curl -X POST https://api.getstack.run/v1/identity/verify/initiate \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "provider_key": "stripe_identity",
    "requested_claims": ["verified_human", "verified_identity"],
    "return_url": "https://your-app.com/verify/callback"
  }'

2. Provider interaction

The human completes the verification with the external provider. STACK does not see or handle the raw verification data - the provider attests to the result.

3. Complete verification

bash
curl -X POST https://api.getstack.run/v1/identity/verify/complete \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "provider_key": "stripe_identity",
    "session_ref": "vs_abc123"
  }'
json
{
  "claim_id": "clm_a1b2c3d4",
  "claim_type": "verified_identity",
  "provider": "stripe_identity",
  "assurance_level": "substantial",
  "layer": "identity",
  "verified_at": "2026-04-15T10:30:00Z",
  "expires_at": "2027-04-15T10:30:00Z"
}

4. Claim attached to passport

When a passport is issued, verified claims are included as references. The JWT contains the claim metadata but never the underlying PII.

json
{
  "stk": {
    "identity_claims": [
      {
        "claim_id": "clm_a1b2c3d4",
        "provider": "stripe_identity",
        "layer": "humanity",
        "claim_type": "verified_human",
        "assurance_level": "substantial",
        "verified_at": 1712340000,
        "expires_at": 1743876000
      },
      {
        "claim_id": "clm_a1b2c3d4",
        "provider": "stripe_identity",
        "layer": "identity",
        "claim_type": "verified_identity",
        "assurance_level": "substantial",
        "verified_at": 1712340000,
        "expires_at": 1743876000
      }
    ]
  }
}

PII handling

STACK treats PII as toxic material - encrypted at rest, never logged, never included in JWTs, decrypted only when explicitly requested by an authorized party.

  • Encryption: AWS KMS envelope encryption (AES-256-GCM data key, wrapped by KMS master key)
  • Storage: encrypted PII stored in dedicated columns, separate from claim metadata
  • Access: only the operator who owns the claim can request decryption
  • JWT: only claim_id travels in the passport - never names, IDs, or dates of birth
  • Logging: PII fields are redacted in all audit logs and error messages
  • Deletion: PII can be purged on request (GDPR right to erasure) without affecting claim validity

GDPR compliance

When setting service identity requirements for L2, the requirement must declare its GDPR properties. The server rejects any L2 service requirement that lacks these fields:

typescript
{
  layer: "identity",
  requires_claim: "verified_identity",
  minimum_assurance: "high",
  requires_pii: true,           // must be true for L2
  lawful_basis: "consent"       // GDPR lawful basis
}

Valid lawful_basis values: consent, legitimate_interest, legal_obligation, vital_interest, public_task, contract.

Claim composability

Claims from different providers and levels can stack on a single passport. A skill might require both humanity (L1 from any provider) and specific identity (L2 from login_gov with IAL2 assurance).

json
{
  "stk": {
    "identity_claims": [
      { "claim_id": "clm_001", "provider": "turnstile",      "layer": "humanity", "claim_type": "verified_human",    "assurance_level": "substantial" },
      { "claim_id": "clm_002", "provider": "stripe_identity","layer": "identity", "claim_type": "verified_identity", "assurance_level": "substantial" },
      { "claim_id": "clm_003", "provider": "login_gov",      "layer": "identity", "claim_type": "verified_identity", "assurance_level": "high" }
    ]
  }
}

Skills and services declare their requirements using a flexible matching syntax. Examples: "any L1 claim with assurance ≥ substantial," or "L2 from stripe_identity or login_gov with assurance = high." The passport verification logic evaluates these requirements against the claims on the JWT.

Claims are independent and non-overlapping. Adding a new claim never invalidates existing ones. Revoking a claim only removes that specific attestation - other claims on the same passport remain valid.

Related

  • /docs/api/identity - verification endpoints, provider list, session lifecycle
  • /docs/concepts/passports - how identity claims attach at issue time
  • /docs/concepts/revocation - claim revocation and cascading passport invalidation
stack | docs