Vault API Reference

All endpoints are served with CORS headers.

Vault API Reference

The Vault is a Cloudflare Worker deployed at the root of the did.run domain. It routes per-identifier requests to a Durable Object (the Vault DO, one instance per registration identifier) and handles stateless and global operations directly. All responses are JSON unless noted otherwise.


Static / CDN

GET /enclave.wasm

Returns the enclave WASM binary.

The worker first checks R2 (CDN_ASSETS) for enclave/latest/enclave.wasm. If absent, falls back to the ASSETS static binding.

HeaderValue
Content-Typeapplication/wasm
Cache-Controlpublic, max-age=3600, s-maxage=86400

Stateless API

GET /api/contracts

Returns the default smart contract addresses for Base Sepolia (chain ID 84532).

Response

{
  "chainId": 84532,
  "entryPoint": "0x0000000071727De22E5E9d8BAf0edAc6f37da032",
  "didRegistry": "0xd38972ffea26b66f09e2109e650887acd447e7b7",
  "accountHelper": "0xd4d57cc363dd419cd95712eb5cddf1797ceb9dde",
  "sessionSBT": "0x5a2822bd69aa3799232ac57decf2b07e3fed1881",
  "hyperAuthFactory": "0xb797f4799d8aa218e9207f918fdea3afc76b1e18"
}
HeaderValue
Cache-Controlpublic, max-age=86400

POST /api/bundler

Proxy for the Pimlico ERC-4337 bundler. Forwards the JSON-RPC request body to https://api.pimlico.io/v2/84532/rpc after validating the method name against an allowlist.

Allowed methods

Method
eth_sendUserOperation
eth_getUserOperationReceipt
eth_estimateUserOperationGas
pm_sponsorUserOperation
eth_supportedEntryPoints

Request body

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "eth_sendUserOperation",
  "params": [...]
}

Status codes

CodeCondition
200Method allowed; upstream response forwarded
403Method not in allowlist
405Non-POST request

GET /api/status

Returns the current operational status of the chain and database. Served with CORS headers (used by widget embeds cross-origin).

Response

{
  "chain": "Base Sepolia",
  "chainId": 84532,
  "rpc": "https://sepolia.base.org",
  "database": "connected"
}

database is "connected" when a SELECT 1 against SESSION_DB succeeds, otherwise "unavailable".


GET /api/accounts/predict

Calls getAddress on the hyperAuthFactory contract to predict the counterfactual smart account address for a given P-256 public key and salt.

Query parameters

ParameterTypeRequiredDescription
pubKeyXstringYesP-256 X coordinate as uint256 hex string
pubKeyYstringYesP-256 Y coordinate as uint256 hex string
saltstringNouint256 decimal or hex string (default: 0)

Response (200)

{ "address": "0x..." }

Error responses

CodeerrorCondition
400"Missing query parameters: pubKeyX, pubKeyY"Parameters absent
400"Invalid pubKeyX or pubKeyY: expected uint256 hex string"Decode failure
400"Invalid salt: expected uint256 decimal or hex string"Salt parse failure
502"rpc_error"Upstream eth_call failure

GET /api/accounts/state

Calls getAccountState on the accountHelper contract and decodes the ABI-encoded response (5 × 32-byte slots: nonce, did, isActive, pubKeyX, pubKeyY).

Query parameters

ParameterTypeRequiredDescription
accountstringYes20-byte EVM address

Response (200)

{
  "nonce": "0",
  "did": "0x...",
  "isActive": true,
  "pubKeyX": "0x...",
  "pubKeyY": "0x..."
}

Error responses

CodeerrorCondition
400"Missing query parameter: account"Parameter absent
400"Invalid account: expected 20-byte hex address"Address invalid
502"rpc_error"Upstream eth_call failure

GET /api/balance

Returns the native ETH or USDC balance for an address on a given chain.

Query parameters

ParameterTypeRequiredDescription
accountstringYes20-byte EVM address
tokenstringYes"ETH" or "USDC" (case-insensitive)
chainIdstringYesInteger chain ID

Supported chains

Chain IDNetworkUSDC address
84532Base Sepolia0x036CbD53842c5426634e7929541eC2318f3dCF7e
8453Base Mainnet0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913

Response (200)

{
  "balance": "1000000",
  "decimals": 6,
  "symbol": "USDC",
  "formatted": "1.000000"
}

For ETH, decimals is 18 and symbol is "ETH".

Error responses

CodeerrorCondition
400"Missing query parameters: account, token, chainId"Parameter absent
400"Unsupported token. Supported: ETH, USDC"Unknown token
400"Unsupported chainId"Chain not in supported list
400"USDC not available on this chain"USDC address not known
502"rpc_error"Upstream eth_call failure

GET /api/sessions/status

Returns registration counts for the requesting IP address and globally. IP is extracted from the CF-Connecting-IP header.

Response (200)

{
  "ip_registrations": 1,
  "ip_limit": 3,
  "total_registrations": 42
}

ip_limit is always 3 (the MAX_REGISTRATIONS_PER_IP constant).


Global Lookups

These endpoints query the global SESSION_DB D1 database by credential ID or alias, without routing to a Durable Object. Both are served with CORS headers.

GET /api/dids/lookup

Resolves a WebAuthn credential ID to a registered on-chain DID.

Query parameters

ParameterTypeRequiredDescription
credentialIdstringYesWebAuthn credential ID

Response — found

{
  "found": true,
  "identifier": "alice",
  "channel": "handle",
  "did": "did:new:0x...",
  "txHash": "0x...",
  "blockNumber": 12345678,
  "smartAccount": "0x...",
  "createdAt": "2024-01-01T00:00:00Z"
}

Response — not found

{ "found": false }

Error responses

CodeerrorCondition
400"Missing query parameter: credentialId"Parameter absent

GET /api/aliases/check

Checks whether a handle alias is available for registration.

Query parameters

ParameterTypeRequiredDescription
aliasstringYesHandle to check (lowercased before lookup)

Response — available

{ "available": true }

Response — taken

{ "available": false, "did": "did:new:0x..." }

Error responses

CodeerrorCondition
400"Missing query parameter: alias"Parameter absent

Per-Identifier (Durable Object)

These routes extract an identifier from the request, resolve the Vault Durable Object by name (env.VAULT.getByName(identifier)), and call the corresponding RPC method on the DO instance. Each DO instance manages the full registration lifecycle for a single identifier.

POST /api/sessions

Creates a registration session for an identifier. Checks reserved identifiers, duplicate registrations, and per-IP registration limits before writing to D1.

Request body

FieldTypeRequiredDescription
identifierstringYesRegistration identifier (also accepts handle)
channelstringYesChannel type (also inferred as "handle" if only handle field is present)
didstringYesDID to associate

Response (200)

{ "ok": true, "identifier": "alice", "channel": "handle", "did": "did:new:0x..." }

Error responses

CodeerrorCondition
400"Missing required fields: identifier, channel, did"Field absent
409"identifier_reserved"Identifier is in reserved list
409"identifier_taken"Identifier already registered
429"ip_limit_reached"IP has reached MAX_REGISTRATIONS_PER_IP (3)

GET /api/sessions/check

Checks whether an identifier is available for registration.

Query parameters

ParameterTypeRequiredDescription
identifierstringYesIdentifier to check (also accepts handle)
channelstringNoChannel (default: "handle")

Response (200)

{ "available": true, "reason": null }
{ "available": false, "reason": "taken" }
{ "available": false, "reason": "reserved", "message": "This identifier is reserved" }

POST /api/verify/send

Sends a verification code to the identifier via the specified channel. Email is delivered via Resend; SMS via Twilio Verify.

Request body

FieldTypeRequiredDescription
identifierstringYesEmail address or phone number
channelstringYes"email" or "sms"

Response (200)

{ "ok": true, "channel": "email" }

Error responses

CodeerrorCondition
400"Missing required fields: identifier, channel"Field absent
400"Invalid channel. Must be \"sms\" or \"email\""Unknown channel
429"rate_limited"IP exceeded MAX_VERIFY_SENDS_PER_IP (3/day) or identifier exceeded RATE_LIMIT_CODES_PER_IDENTIFIER (5/hour, email only)
502"sms_send_failed"Twilio API error
502"email_send_failed"Resend API error

POST /api/verify/check

Verifies an OTP code for the identifier. For email, code is checked against DO-local SQLite; for SMS, checked against Twilio Verify.

Request body

FieldTypeRequiredDescription
identifierstringYesEmail address or phone number
channelstringYes"email" or "sms"
codestringYesOTP code submitted by user

Response (200)

{
  "ok": true,
  "verified": true,
  "attestation": {
    "identifier": "alice@example.com",
    "channel": "email",
    "verified_at": "2024-01-01T00:00:00Z",
    "expires_at": "2024-01-01T00:05:00Z",
    "registrar": "did:new:0x...",
    "signature": "0x..."
  }
}

When verified is false, attestation is absent and a message field describes the failure reason.

Error responses

Codeerror / conditionCondition
400"Missing required fields: identifier, channel, code"Field absent
400"Invalid channel. Must be \"sms\" or \"email\""Unknown channel
502ok: falseUpstream Twilio error

The attestation is a signed JWT-like payload (HMAC-SHA256 keyed by ATTESTATION_SIGNING_KEY) that expires in 300 seconds (ATTESTATION_TTL_SECONDS). It is passed as attestation in the subsequent /api/dids registration call to prove ownership.


GET /api/verify/status

Returns the verification state for an identifier and channel.

Query parameters

ParameterTypeRequiredDescription
identifierstringYesEmail address or phone number
channelstringYes"email" or "sms"

Response (200)

{
  "identifier": "alice@example.com",
  "channel": "email",
  "verified": true,
  "verifiedAt": "2024-01-01T00:00:00Z"
}

verifiedAt is null when verified is false.


POST /api/dids

Registers a DID after the identifier has been verified. For non-handle channels, an attestation object must be present and is validated against the ATTESTATION_SIGNING_KEY before the DO call is made.

Request body

FieldTypeRequiredDescription
credentialIdstringYesWebAuthn credential ID
identifierstringYesRegistration identifier (also accepts handle or name)
channelstringYesChannel type
didstringYesDID to register
txHashstringYesTransaction hash of the on-chain registration
blockNumbernumberNoBlock number of the transaction
smartAccountstringNoSmart account address
attestationVerificationAttestationNoRequired for non-handle channels

Response (200)

{ "ok": true, "identifier": "alice", "channel": "handle", "txHash": "0x..." }

Error responses

CodeerrorCondition
400"Missing required fields: credentialId, identifier, channel, did, txHash"Field absent
403"attestation_invalid"Attestation signature invalid
403"attestation_mismatch"Attestation identifier/channel does not match request
409"credential_already_registered"Credential ID already in registered_dids

Indexer Proxy

GET /api/indexer/{path}

Proxies requests to the upstream indexer service. The /api/indexer prefix is stripped and the remainder is forwarded.

Allowed downstream paths

Path
/api/dids
/api/aliases
/api/accounts
/api/stats
/api/events
/health

Requests to any other path return 404. The upstream is resolved via the INDEXER service binding (if configured) or the INDEXER_URL environment variable.

Status codes

CodeCondition
404Path not in allowlist
503Neither INDEXER binding nor INDEXER_URL is configured
*Upstream status forwarded as-is

Payment

These endpoints support the Web Payment Handler API.

GET /pay/sw.js

Returns the payment service worker JavaScript file. Sets Service-Worker-Allowed: /pay/ to authorize the service worker scope.

GET /pay/confirm

Returns the payment confirmation HTML page (/pay/confirm.html from the static ASSETS binding).

GET /pay/manifest.json

Returns the Web App Manifest for the payment handler.

GET /pay/payment-method.json

Returns the payment method manifest. Declares a single webapp application pointing to /pay/manifest.json.

{
  "default_applications": [
    { "platform": "webapp", "manifest_url": "/pay/manifest.json" }
  ]
}

GET /.well-known/payment-method-manifest

Redirects (302) to /pay/payment-method.json.


Inngest

* /api/inngest

Serves the Inngest webhook handler. All methods are forwarded to the Inngest SDK's serve() handler, which manages event registration and function execution callbacks.


Rate Limits and Constants

ConstantValueApplies to
MAX_REGISTRATIONS_PER_IP3/api/sessions (POST)
MAX_VERIFY_ATTEMPTS5/api/verify/check per code (email)
EMAIL_CODE_EXPIRY_SECONDS90Email OTP TTL
RATE_LIMIT_CODES_PER_IDENTIFIER5/api/verify/send per identifier per hour (email)
MAX_VERIFY_SENDS_PER_IP3/api/verify/send per IP per day
ATTESTATION_TTL_SECONDS300Verification attestation TTL

On this page