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.
| Header | Value |
|---|---|
Content-Type | application/wasm |
Cache-Control | public, 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"
}| Header | Value |
|---|---|
Cache-Control | public, 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
| Code | Condition |
|---|---|
200 | Method allowed; upstream response forwarded |
403 | Method not in allowlist |
405 | Non-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
| Parameter | Type | Required | Description |
|---|---|---|---|
pubKeyX | string | Yes | P-256 X coordinate as uint256 hex string |
pubKeyY | string | Yes | P-256 Y coordinate as uint256 hex string |
salt | string | No | uint256 decimal or hex string (default: 0) |
Response (200)
{ "address": "0x..." }Error responses
| Code | error | Condition |
|---|---|---|
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
| Parameter | Type | Required | Description |
|---|---|---|---|
account | string | Yes | 20-byte EVM address |
Response (200)
{
"nonce": "0",
"did": "0x...",
"isActive": true,
"pubKeyX": "0x...",
"pubKeyY": "0x..."
}Error responses
| Code | error | Condition |
|---|---|---|
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
| Parameter | Type | Required | Description |
|---|---|---|---|
account | string | Yes | 20-byte EVM address |
token | string | Yes | "ETH" or "USDC" (case-insensitive) |
chainId | string | Yes | Integer chain ID |
Supported chains
| Chain ID | Network | USDC address |
|---|---|---|
84532 | Base Sepolia | 0x036CbD53842c5426634e7929541eC2318f3dCF7e |
8453 | Base Mainnet | 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 |
Response (200)
{
"balance": "1000000",
"decimals": 6,
"symbol": "USDC",
"formatted": "1.000000"
}For ETH, decimals is 18 and symbol is "ETH".
Error responses
| Code | error | Condition |
|---|---|---|
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
| Parameter | Type | Required | Description |
|---|---|---|---|
credentialId | string | Yes | WebAuthn 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
| Code | error | Condition |
|---|---|---|
400 | "Missing query parameter: credentialId" | Parameter absent |
GET /api/aliases/check
Checks whether a handle alias is available for registration.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
alias | string | Yes | Handle to check (lowercased before lookup) |
Response — available
{ "available": true }Response — taken
{ "available": false, "did": "did:new:0x..." }Error responses
| Code | error | Condition |
|---|---|---|
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
| Field | Type | Required | Description |
|---|---|---|---|
identifier | string | Yes | Registration identifier (also accepts handle) |
channel | string | Yes | Channel type (also inferred as "handle" if only handle field is present) |
did | string | Yes | DID to associate |
Response (200)
{ "ok": true, "identifier": "alice", "channel": "handle", "did": "did:new:0x..." }Error responses
| Code | error | Condition |
|---|---|---|
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
| Parameter | Type | Required | Description |
|---|---|---|---|
identifier | string | Yes | Identifier to check (also accepts handle) |
channel | string | No | Channel (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
| Field | Type | Required | Description |
|---|---|---|---|
identifier | string | Yes | Email address or phone number |
channel | string | Yes | "email" or "sms" |
Response (200)
{ "ok": true, "channel": "email" }Error responses
| Code | error | Condition |
|---|---|---|
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
| Field | Type | Required | Description |
|---|---|---|---|
identifier | string | Yes | Email address or phone number |
channel | string | Yes | "email" or "sms" |
code | string | Yes | OTP 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
| Code | error / condition | Condition |
|---|---|---|
400 | "Missing required fields: identifier, channel, code" | Field absent |
400 | "Invalid channel. Must be \"sms\" or \"email\"" | Unknown channel |
502 | ok: false | Upstream 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
| Parameter | Type | Required | Description |
|---|---|---|---|
identifier | string | Yes | Email address or phone number |
channel | string | Yes | "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
| Field | Type | Required | Description |
|---|---|---|---|
credentialId | string | Yes | WebAuthn credential ID |
identifier | string | Yes | Registration identifier (also accepts handle or name) |
channel | string | Yes | Channel type |
did | string | Yes | DID to register |
txHash | string | Yes | Transaction hash of the on-chain registration |
blockNumber | number | No | Block number of the transaction |
smartAccount | string | No | Smart account address |
attestation | VerificationAttestation | No | Required for non-handle channels |
Response (200)
{ "ok": true, "identifier": "alice", "channel": "handle", "txHash": "0x..." }Error responses
| Code | error | Condition |
|---|---|---|
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
| Code | Condition |
|---|---|
404 | Path not in allowlist |
503 | Neither 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
| Constant | Value | Applies to |
|---|---|---|
MAX_REGISTRATIONS_PER_IP | 3 | /api/sessions (POST) |
MAX_VERIFY_ATTEMPTS | 5 | /api/verify/check per code (email) |
EMAIL_CODE_EXPIRY_SECONDS | 90 | Email OTP TTL |
RATE_LIMIT_CODES_PER_IDENTIFIER | 5 | /api/verify/send per identifier per hour (email) |
MAX_VERIFY_SENDS_PER_IP | 3 | /api/verify/send per IP per day |
ATTESTATION_TTL_SECONDS | 300 | Verification attestation TTL |