Identity & Trust Model
STACK uses a three-layer trust model that progressively increases confidence in an agent's authorization. Each layer builds on the previous one, and claims from different layers can be composed on a single passport to express rich trust semantics.
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 Three Layers
Layer 1: Humanity Verification
L1 answers the question: is there a real person behind this agent? This is the baseline trust requirement. An L1-verified passport carries a verified_human claim, which proves the agent acts on behalf of an actual human being -- not a bot loop, not an unattended script.
L1 verification does not reveal who the person is. It only attests that a verified identity provider has confirmed a real human is in the chain of delegation. This is sufficient for many use cases: posting to a forum, browsing a catalog, or invoking low-trust skills.
- 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
Layer 2: Identity Verification
L2 answers the question: who exactly is this person? An L2-verified passport carries a verified_identity claim that includes a reference to the verified identity data (name, date of birth, national ID number, etc.) without embedding that data directly in the JWT.
The actual PII is encrypted at rest using AWS KMS envelope encryption and stored separately. The passport only carries a claim_id reference (e.g., clm_abc123) that can be resolved by authorized parties. This design ensures that identity data never leaks through JWT inspection, logging, or accidental exposure.
L2 verification involves PII. Every L2 service requirement must declare requires_pii: true and a lawful_basis under GDPR (e.g., "consent", "legitimate_interest", "legal_obligation"). STACK enforces this at the API level -- you cannot create an L2 service requirement without these fields.
- 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
Layer 3: Delegation
L3 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 in the chain can only narrow scopes -- never widen them. The maximum delegation depth is 4 hops (human → operator → agent → sub-agent → sub-sub-agent).
Delegation claims carry delegated_scopes (e.g., "tax:file", "tax:read") and a reference to the delegating human. The full chain is recorded in the audit log. If any link in the chain is revoked, all downstream passports become invalid within 60 seconds (propagated via Redis cache invalidation).
- 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 -- any provider that can attest to humanity (L1) or identity (L2) can be integrated. The following providers are currently active:
BankID Sweden
Swedish national eID used by 8.5 million residents. Provides the highest assurance level available in Scandinavia. BankID verification confirms both humanity and full legal identity (name, personal number, date of birth).
- Provider key: bankid_se
- Layers: L1 (humanity) + L2 (identity)
- Assurance: high
- Verification method: BankID app signing (redirect flow)
Stripe Identity
Document-based identity verification powered by Stripe. Users upload a government-issued ID (passport, driver's license) and a selfie. Stripe performs liveness detection and document authenticity checks.
- Provider key: stripe_identity
- Layers: L1 (humanity) + L2 (identity)
- Assurance: high
- 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).
- Provider key: login_gov
- Layers: L1 (humanity) + L2 (identity)
- Assurance: IAL1 = substantial, IAL2 = high
- Verification method: OAuth 2.0 + OIDC with government-grade proofing
ID.me
Identity verification network with 120+ million users. Widely used by U.S. government agencies and private sector. Supports both self-asserted (IAL1) and document-verified (IAL2) levels.
- Provider key: id_me
- Layers: L1 (humanity) + L2 (identity)
- Assurance: IAL1 = substantial, IAL2 = high
- Verification method: OAuth 2.0 + multi-factor + optional document verification
Plaid
Financial identity verification through bank account ownership. Plaid connects to the user's bank, verifies account ownership, and can extract KYC data (name, address, date of birth) from the financial institution's records.
- Provider key: plaid
- Layers: L1 (humanity) + L2 (identity)
- Assurance: high
- Verification method: bank account linking + KYC data extraction
Verification Flow
All identity providers follow the same abstract flow, regardless of whether the underlying mechanism is a redirect, an API call, or a document upload:
1. Initiate Verification
The operator (or agent on behalf of the operator) requests verification with a specific provider. STACK returns a session identifier and, if applicable, a redirect URL or instructions.
curl -X POST https://api.getstack.run/v1/identity/verify/initiate \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"provider_key": "bankid_se",
"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. This might involve signing with a BankID app, uploading documents to Stripe, or authenticating through Login.gov. STACK does not see or handle the raw verification data -- the provider attests to the result.
3. Complete Verification
The provider calls back to STACK (or STACK polls the provider) with the verification result. If successful, STACK creates a verified claim on the operator's account.
curl -X POST https://api.getstack.run/v1/identity/verify/complete \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"provider_key": "bankid_se",
"session_ref": "vs_abc123"
}'{
"claim_id": "clm_a1b2c3d4",
"claim_type": "verified_identity",
"provider": "bankid_se",
"assurance_level": "high",
"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 automatically included as references. The passport JWT contains the claim metadata but never the underlying PII.
{
"stk": {
"identity_claims": [
{
"claim_id": "clm_a1b2c3d4",
"provider": "bankid_se",
"layer": "humanity",
"claim_type": "verified_human",
"assurance_level": "high",
"verified_at": 1712340000,
"expires_at": 1743876000
},
{
"claim_id": "clm_a1b2c3d4",
"provider": "bankid_se",
"layer": "identity",
"claim_type": "verified_identity",
"assurance_level": "high",
"verified_at": 1712340000,
"expires_at": 1743876000
}
]
}
}PII Handling & Encryption
STACK treats PII as toxic material -- it must be encrypted at rest, never logged, never included in JWTs, and only decrypted when explicitly requested by an authorized party with a valid reason.
- 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 Layer 2, the requirement must declare its GDPR properties. This is enforced at the API level -- the server will reject any L2 service requirement that lacks these fields:
// Required fields for L2 service requirements
{
layer: "identity",
requires_claim: "verified_identity",
minimum_assurance: "high",
requires_pii: true, // must be true for Layer 2
lawful_basis: "consent" // GDPR lawful basis
}Valid lawful basis values include "consent", "legitimate_interest", "legal_obligation", "vital_interest", "public_task", and "contract".
Claim Composability
Claims from different providers and layers can be stacked on a single passport. This allows fine-grained trust decisions: a skill might require both humanity verification (L1 from any provider) and financial identity (L2 from Plaid specifically).
{
"stk": {
"identity_claims": [
{
"claim_id": "clm_001",
"provider": "login_gov",
"layer": "humanity",
"claim_type": "verified_human",
"assurance_level": "substantial",
"verified_at": 1712340000,
"expires_at": 1743876000
},
{
"claim_id": "clm_002",
"provider": "plaid",
"layer": "identity",
"claim_type": "verified_identity",
"assurance_level": "high",
"verified_at": 1712340000,
"expires_at": 1743876000
},
{
"claim_id": "clm_003",
"provider": "stripe_identity",
"layer": "identity",
"claim_type": "verified_identity",
"assurance_level": "high",
"verified_at": 1712340000,
"expires_at": 1743876000
}
]
}
}Skills and services can declare trust requirements using a flexible matching syntax. For example, a skill can require "any L1 claim with assurance >= substantial" or "L2 from bankid_se or stripe_identity with assurance = high". The passport verification logic evaluates these requirements against the claims present in 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.