# AIdenID > The Clearance Layer for Agentic Access AIdenID is the site-owned decision layer for AI-agent traffic on the open web. It answers one question per request: should this agent-attributable traffic be **allowed**, **throttled**, **queued**, **sandboxed**, **denied**, or **price-required**? The product runs in three paths: a **cryptographic hard path** (HTTP Message Signatures RFC 9421 + DPoP RFC 9449 + audience-bound session tokens + Token Exchange RFC 8693) for verified delegated agents; a **confidence-labeled soft path** for `verified_agent | signed_agent | likely_human | suspicious_automation | unknown` traffic; and an **append-only audit path** with Tessera-backed transparency-log commitments for revocations, policy versions, issuer key rotations, and evidence roots. Category positioning: vendor-neutral, site-owned, cross-edge, per-route, delegated-intent-aware, with revocation and audit transparency. Cloudflare's stack is edge-owned; Okta's is enterprise-IAM-owned; Auth0/Aembit/Browserbase/Fingerprint each own a slice. AIdenID owns the **site-side decision** primitive. Around the core decision layer we layer supporting APIs for **identity registry** (scoped agent identities with intent-bound delegation, TTL, and lineage), **verified target registries**, signed webhooks, demo evidence, and authorized resilience simulations. These support clearance adoption; they are not the primary buyer story by themselves. Launch posture: receive-first, policy-first, audit-first. AIdenID does not automate third-party account signup or fabricate sessions outside declared authority. Classification is label-based, never "human vs bot" certainty. ## Core Capabilities - **Six-Outcome Decision Ladder**: Every request resolves to one of `allow | throttle | queue | sandbox | deny | price_required`. Policy is per-route, per-actor-class, per-method. - **Cryptographic Hard Path**: HTTP Message Signatures (RFC 9421) + DPoP (RFC 9449) + Token Exchange (RFC 8693) + audience-bound session tokens with `cnf.jkt` proof-of-possession binding. - **Confidence-Labeled Soft Path**: Actor classes `verified_agent | signed_agent | likely_human | suspicious_automation | unknown`. Never claims "human vs bot" certainty — labels, not hallucinated scores. - **Transparency-Log Audit Path**: Tessera-backed Merkle checkpoints for the minimum auditable set (issuer key rotations, policy bundle versions, revocation epoch bumps, grant/session issuance hashes, evidence bundle Merkle roots, sampled decision batch roots). Commitments, not every row. - **Instant Revocation**: Chain-scoped revocation epochs with Redis pub/sub fanout. The next request on a revoked chain, anywhere in the world, denies. - **Route Clearance Policy**: Configure observe, shadow, and enforce behavior per host, route, method, actor class, quota, and risk tier. - **Edge and Origin Verifiers**: Cloudflare, Fastly, Node, Python, and origin middleware share the same decision model and strip raw upstream credentials before forwarding. - **Scoped Identity Visas**: Supporting issuer primitive with declared intent, allowed targets (RFC 9396 Rich Authorization Requests), TTL, and revocation lease. - **Delegated Subidentities**: Supporting swarm primitive for scoped child identities with inherited budgets, lineage tracking, and independent revocation. - **Issuer-Key Ledger**: TOFU + admin-approved rotation by default; transparency-log-backed issuer key history for Enterprise. Silent JWKS rotation is rejected. - **Pairwise Pseudonymous Subject Handles**: HMAC-derived, per-site — GDPR-defensible by default. Org-wide correlation handle is opt-in via explicit delegated consent only. - **Verified Target Registry**: Customers pre-authorize which domains / endpoints their identities may interact with. - **Supporting Verification Interception**: Redacted OTP, magic-link, and confirmation extraction for authorized inbox workflows. - **Authorized Resilience Simulation**: Bounded failure-injection against customer-controlled assets — staging-only, allowlist-gated. - **Signed Webhooks**: HMAC-SHA256 signed payloads with replay support and secret rotation. - **Decision Event Stream**: Server-Sent Events for live traffic stream in the dashboard; OCSF-shaped events for SIEM export (Splunk HEC, Datadog Intake, Chronicle). - **MCP Server**: Model Context Protocol integration (RFC 9728 Protected Resource Metadata + RFC 8707 Resource Indicators + RFC 8693 Token Exchange — no token passthrough). - **Policy Copilot** (offline only): AI proposes policy diffs; humans approve; Omar Gate verifies before deploy; rollback is one-click. Never runs on the request hot path. - **Enterprise Governance**: BYOD domains, RBAC, audit logs, tenant quotas, DB-backed runtime feature flags, retention policies. - **Budget Envelopes**: Per-tenant and per-swarm quotas; every identity inherits a hard ceiling. - **Consumer Inbox**: Web-based inbox with SSE real-time updates for human users. ## Approved Use Cases - **Website agent clearance** — Decide which AI agents can access checkout, support, account, health, benefits, admin, or internal-tool routes. - **Observe-to-enforce rollout** — Measure actor-class mix, model counterfactual blocks, then move route policies from observe to shadow to enforce. - **Authorized demo and staging tests** — Generate reproducible evidence against customer-controlled staging targets with explicit allowlists. - **Agent issuer interoperability** — Bind signed agent identities and delegated sessions to route-level site decisions. ## Buyer and Support Audiences ### For Site Owners and Security Teams Mount AIdenID at the edge or origin, register route policies, classify traffic, enforce six-outcome decisions, stream OCSF-shaped evidence, and revoke sessions or issuers instantly. ### For Agent Builders and Swarms Use scoped identity visas, delegated subidentities, target registries, DPoP/session binding, and budget envelopes so websites can safely recognize useful automation. ### For QA, Demo, and GTM Teams Run authorized browser swarms against customer-controlled staging portals and produce dashboard logs, videos, evidence ledgers, and report emails that show the verified/signed/suspicious/unknown decomposition. ### For Supporting Human Inbox Users Human inbox and magic-link flows remain a supporting surface for consumer identities, demos, and legacy inbox workflows; they are no longer the core clearance-layer pitch. ### Zero-Friction Supporting Identity Free Tier No signup, no API key, no credit card. IP-keyed, 1 identity, 24h TTL, polling-only. ## Architecture Overview AIdenID is a multi-service clearance platform built contract-first on Next.js 15 + FastAPI + PostgreSQL 16 + Redis 7 with edge adapters, verifier SDKs, event streams, and supporting identity/inbox infrastructure. The major surfaces: - **Verifier hot path** — deterministic cryptographic and classification checks that return a route decision without calling an LLM on the request path. - **Control plane API** — targets, grants, sessions, policies, revocation, billing, quotas, webhook endpoints, and audit controls. - **Decision event layer** — live dashboard streams, OCSF-shaped exports, signed outbound callbacks, and transparency-log commitments. - **Demo and proof harness** — authorized browser swarms, evidence scoring, videos, reports, and email-ready proof packs for staging targets. - **Supporting identity layer** — scoped identities, delegated subidentities, inboxes, lifecycle controls, and extraction APIs used by issuer and test workflows. - **Operational governance** — rollout modes, rate limits, quota guardrails, DSAR workflows, signed webhooks, and evidence retention. Sites adopt AIdenID incrementally: observe first, shadow when policy is ready, enforce route by route. --- # Quickstart Plan your first clearance gate in under 5 minutes. Supporting identity and inbox APIs remain available when your issuer, QA, or demo workflow needs email evidence. ## Prerequisites - An AIdenID account (sign up at aidenid.com) - Your API key, organization ID, and project ID from the dashboard - `curl` or any HTTP client ## Step 1: Create an identity Provision a new disposable email identity. This creates a unique inbox that can receive email and extract verification codes. ``` curl -X POST https://api.aidenid.com/v1/identities \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "X-Org-Id: YOUR_ORG_ID" \ -H "X-Project-Id: YOUR_PROJECT_ID" \ -H "Idempotency-Key: " \ -H "Content-Type: application/json" \ -d '{ "label": "my-first-identity", "ttl_hours": 24 }' ``` Response: ```json { "id": "ident_a1b2c3d4e5", "email": "a1b2c3d4e5@inbox.aidenid.com", "label": "my-first-identity", "status": "provisioned", "created_at": "2026-04-04T10:00:00Z", "expires_at": "2026-04-05T10:00:00Z" } ``` **Important:** Save the `id` and `email` from the response. You will use the email address in your signup or verification flow, and the ID to poll for extractions. ## Step 2: Use the email in a signup flow Use the generated email address (`a1b2c3d4e5@inbox.aidenid.com`) to sign up for a service or trigger a verification flow. AIdenID will intercept the incoming email automatically. ## Step 3: Poll for the extraction Once the verification email arrives, AIdenID extracts the authentication code automatically. Poll the extraction endpoint: ``` curl https://api.aidenid.com/v1/identities/ident_a1b2c3d4e5/extractions/latest \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "X-Org-Id: YOUR_ORG_ID" \ -H "X-Project-Id: YOUR_PROJECT_ID" ``` When the extraction is ready: ```json { "id": "ext_m1n2o3", "identity_id": "ident_a1b2c3d4e5", "type": "otp", "has_secret": true, "redacted_value": "***", "extracted_at": "2026-04-04T10:05:02Z", "confidence": 0.99 } ``` **Tip:** If the extraction is not ready yet, you will receive a 404 response. Wait 2 seconds and try again. For production use, consider using webhooks instead of polling. ## Step 4: Use the extracted code Use the extracted secret from your secure runtime flow to complete the verification step. Do not log raw OTP or link values in application logs, traces, or analytics events. ## Step 5: Clean up When you are done with the identity, squash it to revoke the inbox and prevent further email delivery: ``` curl -X POST https://api.aidenid.com/v1/identities/ident_a1b2c3d4e5/squash \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "X-Org-Id: YOUR_ORG_ID" \ -H "X-Project-Id: YOUR_PROJECT_ID" \ -H "Idempotency-Key: " ``` ## Python example ```python import requests import time API = "https://api.aidenid.com" HEADERS = { "Authorization": "Bearer YOUR_API_KEY", "X-Org-Id": "YOUR_ORG_ID", "X-Project-Id": "YOUR_PROJECT_ID", "Content-Type": "application/json", } # Create identity identity = requests.post( f"{API}/v1/identities", headers={**HEADERS, "Idempotency-Key": "qs-create-001"}, json={"label": "quickstart", "ttl_hours": 24}, ).json() print(f"Email: {identity['email']}") # ... use the email in your signup flow ... # Poll for extraction for _ in range(30): resp = requests.get( f"{API}/v1/identities/{identity['id']}/extractions/latest", headers=HEADERS, ) if resp.status_code == 200: extraction = resp.json() print(f"Extraction ready: {extraction['type']} (confidence: {extraction['confidence']})") break time.sleep(2) # Clean up requests.post( f"{API}/v1/identities/{identity['id']}/squash", headers={**HEADERS, "Idempotency-Key": "qs-squash-001"}, ) ``` ## TypeScript example ```typescript const API = "https://api.aidenid.com"; const headers = { "Authorization": "Bearer YOUR_API_KEY", "X-Org-Id": "YOUR_ORG_ID", "X-Project-Id": "YOUR_PROJECT_ID", "Content-Type": "application/json", }; // Create identity const identity = await fetch(`${API}/v1/identities`, { method: "POST", headers: { ...headers, "Idempotency-Key": "qs-create-001" }, body: JSON.stringify({ label: "quickstart", ttl_hours: 24 }), }).then((r) => r.json()); console.log("Email:", identity.email); // ... use the email in your signup flow ... // Poll for extraction for (let i = 0; i < 30; i++) { const resp = await fetch( `${API}/v1/identities/${identity.id}/extractions/latest`, { headers } ); if (resp.ok) { const extraction = await resp.json(); console.log("Extraction ready:", extraction.type, extraction.confidence); break; } await new Promise((r) => setTimeout(r, 2000)); } // Clean up await fetch(`${API}/v1/identities/${identity.id}/squash`, { method: "POST", headers: { ...headers, "Idempotency-Key": "qs-squash-001" }, }); ``` ## Quickstart for humans If you are a human consumer looking for disposable email addresses (not building an agent), the process is simpler: 1. Visit aidenid.com/consumer/signup and enter your personal email 2. Click the magic link in your inbox to verify your account 3. Create a disposable email from the web inbox — it appears instantly 4. Use the disposable address for signups. OTPs and magic links appear in your inbox in real time via SSE 5. OTPs are also forwarded to your personal email so you never miss one **Free tier for humans:** The free trial gives you 3 disposable emails with a 7-day TTL and 3 total OTP deliveries. Upgrade to Personal ($29/mo) for 10 emails, 30-day TTL, and 100 OTP deliveries per month. ## Quickstart for the agent free tier No signup or API key needed. Create a disposable identity with a single request: ``` # Create a free identity (IP-keyed, 1 per IP, 24h TTL) curl -X POST https://api.aidenid.com/v1/free/identity \ -H "Content-Type: application/json" \ -H "Idempotency-Key: " # Poll for the extraction curl "https://api.aidenid.com/v1/free/identity/{id}/extraction?token={token}" ``` --- # API Reference The AIdenID REST API lets you programmatically manage disposable identities, webhooks, auth flow tests, and more. All endpoints are available at `https://api.aidenid.com`. ## Base URL ``` https://api.aidenid.com ``` ## Authentication AIdenID supports three authentication models: - **API key auth** — For agent integrations. See the authentication guide. - **Magic-link sessions** — For human consumer endpoints (`/v1/consumer/*`). - **IP-keyed tokens** — For free tier (`/v1/free/*`). No signup required. ``` # Agent API auth Authorization: Bearer X-Org-Id: X-Project-Id: ``` ## Request format All request bodies use JSON. Set `Content-Type: application/json` for POST, PUT, and PATCH requests. ## Idempotency All mutation endpoints (POST, PUT, PATCH, DELETE) require an `Idempotency-Key` header. This ensures that retried requests do not create duplicate resources. Use a UUID or any unique string per logical operation. ``` Idempotency-Key: ``` ## Response format All responses return JSON. Successful responses use HTTP 200 or 201 status codes. List endpoints return paginated results with `items`, `total`, `limit`, and `offset` fields. ## Error handling Errors follow a consistent structure: ```json { "code": "identity_not_found", "message": "The requested identity does not exist.", "retryable": false, "request_id": "req_x1y2z3" } ``` ## Response headers | Header | Description | |--------|-------------| | `X-Request-Id` | Unique identifier for the request, useful for debugging | | `X-Trace-Id` | Distributed trace identifier | | `X-RateLimit-Limit` | Maximum requests per rate limit window | | `X-RateLimit-Remaining` | Remaining requests in the current window | | `X-RateLimit-Reset` | Unix timestamp when the rate limit window resets | ## API groups | Group | Prefix | Description | |-------|--------|-------------| | Authentication | `/v1/auth` | API keys, token exchange, and scopes | | Identities | `/v1/identities` | Create, list, extend, and squash identities | | Free Tier | `/v1/free` | Zero-auth identity creation for AI agents | | Consumer | `/v1/consumer` | Human consumer signup, inbox, and notifications | | Trials | `/v1/trials` | Start, manage, and convert free trials | | Webhooks | `/v1/webhooks` | Register endpoints, rotate secrets, replay events | | Auth Flows | `/v1/reliability` | Reliability testing for password reset and invite flows | | Domains | `/v1/domains` | Register and verify custom email domains | | Billing | `/v1/billing` | Subscriptions, checkout, invoices, and usage | | Realtime | `/v1/realtime` | Server-Sent Events for live project updates | | Exports | `/v1/exports` | Bulk data export in JSON or CSV | | Marketplace | `/v1/marketplace` | Agent templates and pre-built identity flows | --- # Authentication Every request to the AIdenID API must be authenticated with an API key and scoped to a specific organization and project. **Important:** All authentication examples are backend integration examples. Do not copy API-key or bearer-token flows into browser JavaScript, extensions, or mobile client bundles. ## API keys API keys are the primary authentication mechanism. Each key is bound to an organization and grants access to one or more projects. API keys follow the format `aid_*`. You can generate and manage API keys from the Dashboard > Settings page. ## Required headers Every API request must include these three headers: | Header | Description | Example | |--------|-------------|---------| | `Authorization` | Bearer token with your API key | `Bearer aid_your_api_key` | | `X-Org-Id` | Your organization identifier | `org_abc123` | | `X-Project-Id` | The project to scope the request to | `proj_def456` | **Scope note:** The `Authorization: Bearer ...` header applies only to server-side API key requests. Consumer endpoints use cookie-backed sessions and do not accept consumer bearer session headers. **Tenant header anti-spoofing:** Treat `X-Org-Id` and `X-Project-Id` as requested scope hints, not trusted identity claims. The backend must derive authorized org/project scope from the credential, reject mismatches with a `scope_mismatch` error, and include a request ID in the error envelope. ``` curl https://api.aidenid.com/v1/identities \ -H "Authorization: Bearer aid_your_api_key" \ -H "X-Org-Id: org_abc123" \ -H "X-Project-Id: proj_def456" ``` ## API key scopes | Scope | Permissions | |-------|-------------| | `identities:read` | List and retrieve identities, events, and extractions | | `identities:write` | Create, extend, squash identities and manage children | | `webhooks:read` | List webhook endpoints | | `webhooks:write` | Create, update, delete webhook endpoints and rotate secrets | | `reliability:write` | Create and manage auth flow test runs | | `domains:read` | List registered domains | | `domains:write` | Register and verify custom domains | ## Token exchange For short-lived access, you can exchange your API key for a temporary access token. **Mutation safety:** include a unique `Idempotency-Key` header on every token-exchange request. ### POST /v1/auth/tokens ``` curl -X POST https://api.aidenid.com/v1/auth/tokens \ -H "Authorization: Bearer aid_your_api_key" \ -H "X-Org-Id: org_abc123" \ -H "X-Project-Id: proj_def456" \ -H "Idempotency-Key: " \ -H "Content-Type: application/json" \ -d '{ "scopes": ["identities:read", "identities:write"], "ttl_seconds": 3600 }' ``` Response: ``` HTTP/1.1 200 OK Cache-Control: no-store, max-age=0 Pragma: no-cache Content-Type: application/json { "access_token": "", "expires_at": "2026-04-06T16:10:00Z", "scopes": ["identities:read", "identities:write"] } ``` **Token exchange response handling:** Treat exchanged bearer tokens as highly sensitive credentials. Return them only from server-side handlers with `Cache-Control: no-store, max-age=0` and `Pragma: no-cache`, never persist them in browser storage, and redact them from logs and traces. ## Get current profile ### GET /v1/me Returns the organization, project, and scopes associated with the current API key or access token. ```json { "organization_id": "org_abc123", "project_id": "proj_def456", "scopes": ["identities:read", "identities:write", "webhooks:read"] } ``` ## Consumer authentication (magic link) Human consumers authenticate using passwordless magic links. This flow does not use API keys — instead, a magic link is sent to the consumer's personal email, and clicking it creates a session. ### Signup flow ``` # 1. Request signup curl -X POST https://api.aidenid.com/v1/consumer/signup \ -H "Content-Type: application/json" \ -H "Idempotency-Key: " \ -d '{ "email": "user@example.com" }' # 2. User clicks magic link in email # 3. Verify the token from the magic link curl -X POST https://api.aidenid.com/v1/consumer/verify \ -H "Content-Type: application/json" \ -H "Idempotency-Key: " \ -d '{ "token": "" }' # Response sets an httpOnly session cookie # { "accountId": "cac_...", "verified": true, ... } ``` ### Login flow ``` # Request a magic link curl -X POST https://api.aidenid.com/v1/consumer/login \ -H "Content-Type: application/json" \ -H "Idempotency-Key: " \ -d '{ "email": "user@example.com" }' # User clicks the link, then verify as above ``` Once authenticated, consumer API requests use the httpOnly `aidenid_consumer_session` cookie. ## Free tier authentication (anonymous grant token) The agent free tier uses a server-issued anonymous grant token tied to IP reputation and abuse controls. ``` # No API key required; X-Anon-Grant is required curl -X POST https://api.aidenid.com/v1/free/identity \ -H "Content-Type: application/json" \ -H "Idempotency-Key: " \ -H "X-Anon-Grant: " ``` **X-Anon-Grant hardening:** Accept `X-Anon-Grant` only over HTTPS, enforce short TTL and audience/org/project binding, require one-time/replay-protected `jti` semantics, and never log raw grant tokens. Grant token error responses: | Status | Code | Description | |--------|------|-------------| | 401 | `unauthorized` | Missing X-Anon-Grant header | | 403 | `grant_expired` | Anonymous grant expired | | 409 | `grant_replayed` | Anonymous grant already consumed | | 403 | `forbidden` | Anonymous grant is invalid | ## Security best practices - **Never expose API keys in client-side code.** API keys should only be used in server-side applications or secure agent runtimes. - **Use minimal scopes.** Grant only the scopes each agent or service actually needs. - **Rotate keys regularly.** Generate new API keys periodically and revoke old ones. - **Use idempotency keys.** All mutation requests require an `Idempotency-Key` header to prevent duplicate operations on retry. ## Authentication error responses | Code | Status | Description | |------|--------|-------------| | `unauthorized` | 401 | Missing or invalid API key | | `forbidden` | 403 | API key lacks the required scope for this operation | --- # Identities API Identities are the core resource in AIdenID. Each identity represents a disposable email inbox that can receive mail and extract authentication codes. ## Create identity ### POST /v1/identities Provision a new disposable email identity with a unique inbox address. **Request body:** | Field | Type | Required | Description | |-------|------|----------|-------------| | `label` | string | Yes | Human-readable label for the identity | | `domain` | string | No | Custom domain (defaults to `inbox.aidenid.com`) | | `ttl_hours` | integer | No | Time-to-live in hours (default: 24) | | `metadata` | object | No | Arbitrary key-value pairs for your own tracking | ``` curl -X POST https://api.aidenid.com/v1/identities \ -H "Authorization: Bearer aid_your_api_key" \ -H "X-Org-Id: org_abc123" \ -H "X-Project-Id: proj_def456" \ -H "Idempotency-Key: " \ -H "Content-Type: application/json" \ -d '{ "label": "signup-test", "ttl_hours": 48, "metadata": { "agent": "onboarding-bot" } }' ``` **Response:** ```json { "id": "ident_a1b2c3d4e5", "email": "a1b2c3d4e5@inbox.aidenid.com", "label": "signup-test", "status": "provisioned", "created_at": "2026-04-04T10:00:00Z", "expires_at": "2026-04-06T10:00:00Z", "metadata": { "agent": "onboarding-bot" } } ``` ## List identities ### GET /v1/identities List all identities in the current project with optional filtering. **Query parameters:** | Parameter | Type | Description | |-----------|------|-------------| | `status` | string | Filter by status: `provisioned`, `active`, `extended`, `squashed`, `expired` | | `limit` | integer | Max results per page (default: 50, max: 200) | | `offset` | integer | Pagination offset | ``` curl "https://api.aidenid.com/v1/identities?status=active&limit=10" \ -H "Authorization: Bearer aid_your_api_key" \ -H "X-Org-Id: org_abc123" \ -H "X-Project-Id: proj_def456" ``` ## Get identity ### GET /v1/identities/{identity_id} Retrieve a specific identity by its ID. ## Extend identity ### POST /v1/identities/{identity_id}/extend Extend the TTL of an active or provisioned identity. The identity must not be squashed or expired. **Request body:** | Field | Type | Required | Description | |-------|------|----------|-------------| | `additional_hours` | integer | Yes | Number of hours to add to the current TTL | ``` curl -X POST https://api.aidenid.com/v1/identities/ident_a1b2c3d4e5/extend \ -H "Authorization: Bearer aid_your_api_key" \ -H "X-Org-Id: org_abc123" \ -H "X-Project-Id: proj_def456" \ -H "Idempotency-Key: " \ -H "Content-Type: application/json" \ -d '{ "additional_hours": 48 }' ``` ## Squash identity ### POST /v1/identities/{identity_id}/squash Immediately revoke an identity. This disables the inbox, halts extraction, and transitions the identity to `squashed` status. This action is irreversible. ``` curl -X POST https://api.aidenid.com/v1/identities/ident_a1b2c3d4e5/squash \ -H "Authorization: Bearer aid_your_api_key" \ -H "X-Org-Id: org_abc123" \ -H "X-Project-Id: proj_def456" \ -H "Idempotency-Key: " ``` ## List identity events ### GET /v1/identities/{identity_id}/events List inbound events for an identity. Events include email arrivals, extraction results, and lifecycle transitions. ## Get latest event ### GET /v1/identities/{identity_id}/events/latest Get the most recent event for an identity. ## Get latest extraction ### GET /v1/identities/{identity_id}/extractions/latest Get the most recent extraction result. Returns the extracted value (OTP code, magic link URL, etc.) along with extraction type and confidence score. **Response:** ```json { "id": "ext_m1n2o3", "identity_id": "ident_a1b2c3d4e5", "type": "otp", "has_secret": true, "redacted_value": "***", "source_event_id": "evt_x1y2z3", "extracted_at": "2026-04-04T10:05:02Z", "confidence": 0.99 } ``` **Polling vs. webhooks:** While you can poll this endpoint, we recommend setting up webhooks to receive `extraction.completed` events in real time. ## Create child identity ### POST /v1/identities/{identity_id}/children Create a subordinate identity linked to a parent. Useful for multi-step flows where a secondary email is needed (e.g., recovery email during signup). **Request body:** | Field | Type | Required | Description | |-------|------|----------|-------------| | `label` | string | Yes | Label for the child identity | | `ttl_hours` | integer | No | TTL for the child (default: parent TTL) | ## Revoke children ### DELETE /v1/identities/{identity_id}/children Squash all child identities linked to a parent. ## Get identity lineage ### GET /v1/identities/{identity_id}/lineage Retrieve the full lineage tree for an identity, including parent and all children. ## Bulk create ### POST /v1/identities/bulk Create multiple identities in a single request. The batch size limit depends on your plan (5 for Starter, up to 200 for Enterprise). ``` curl -X POST https://api.aidenid.com/v1/identities/bulk \ -H "Authorization: Bearer aid_your_api_key" \ -H "X-Org-Id: org_abc123" \ -H "X-Project-Id: proj_def456" \ -H "Idempotency-Key: " \ -H "Content-Type: application/json" \ -d '{ "identities": [ { "label": "test-1", "ttl_hours": 24 }, { "label": "test-2", "ttl_hours": 24 }, { "label": "test-3", "ttl_hours": 24 } ] }' ``` ## Bulk squash ### POST /v1/identities/bulk-squash Squash multiple identities in a single request. ``` curl -X POST https://api.aidenid.com/v1/identities/bulk-squash \ -H "Authorization: Bearer aid_your_api_key" \ -H "X-Org-Id: org_abc123" \ -H "X-Project-Id: proj_def456" \ -H "Idempotency-Key: " \ -H "Content-Type: application/json" \ -d '{ "identity_ids": ["ident_a1b2c3d4e5", "ident_f6g7h8i9j0"] }' ``` ## Python example ```python import requests API = "https://api.aidenid.com" HEADERS = { "Authorization": "Bearer aid_your_api_key", "X-Org-Id": "org_abc123", "X-Project-Id": "proj_def456", "Content-Type": "application/json", } # Create identity identity = requests.post( f"{API}/v1/identities", headers={**HEADERS, "Idempotency-Key": "py-create-001"}, json={"label": "signup-test", "ttl_hours": 48}, ).json() print(f"Created: {identity['email']}") # List active identities active = requests.get( f"{API}/v1/identities", headers=HEADERS, params={"status": "active", "limit": 10}, ).json() # Extend identity requests.post( f"{API}/v1/identities/{identity['id']}/extend", headers={**HEADERS, "Idempotency-Key": "py-extend-001"}, json={"additional_hours": 24}, ) # Get latest extraction extraction = requests.get( f"{API}/v1/identities/{identity['id']}/extractions/latest", headers=HEADERS, ) if extraction.status_code == 200: data = extraction.json() print(f"Extraction ready: {data['type']} (confidence: {data['confidence']})") # Squash when done requests.post( f"{API}/v1/identities/{identity['id']}/squash", headers={**HEADERS, "Idempotency-Key": "py-squash-001"}, ) ``` ## TypeScript example ```typescript const API = "https://api.aidenid.com"; const headers = { Authorization: "Bearer aid_your_api_key", "X-Org-Id": "org_abc123", "X-Project-Id": "proj_def456", "Content-Type": "application/json", }; // Create identity const identity = await fetch(`${API}/v1/identities`, { method: "POST", headers: { ...headers, "Idempotency-Key": "ts-create-001" }, body: JSON.stringify({ label: "signup-test", ttl_hours: 48 }), }).then((r) => r.json()); console.log("Created:", identity.email); // List active identities const active = await fetch( `${API}/v1/identities?status=active&limit=10`, { headers } ).then((r) => r.json()); // Extend identity await fetch(`${API}/v1/identities/${identity.id}/extend`, { method: "POST", headers: { ...headers, "Idempotency-Key": "ts-extend-001" }, body: JSON.stringify({ additional_hours: 24 }), }); // Get latest extraction const extResp = await fetch( `${API}/v1/identities/${identity.id}/extractions/latest`, { headers } ); if (extResp.ok) { const extraction = await extResp.json(); console.log("Extraction ready:", extraction.type, extraction.confidence); } // Squash when done await fetch(`${API}/v1/identities/${identity.id}/squash`, { method: "POST", headers: { ...headers, "Idempotency-Key": "ts-squash-001" }, }); ``` ## Identities error codes | Code | Status | Description | |------|--------|-------------| | `identity_not_found` | 404 | Identity does not exist in this project | | `identity_already_squashed` | 409 | Cannot modify a squashed identity | | `identity_expired` | 409 | Cannot extend or use an expired identity | | `quota_exceeded` | 403 | Active identity limit reached for your plan | | `missing_idempotency_key` | 400 | Mutation request missing Idempotency-Key header | --- # Free Tier API Zero-auth endpoints designed for AI agents. Create a disposable email identity, use it to sign up for a service, then poll for the extracted OTP or verification code — no API key required. **Rate limits:** Free tier endpoints are rate-limited to 10 requests per minute per IP. Each IP address may hold at most 1 active identity at a time, with a 24-hour TTL. ## Create a disposable identity ### POST /v1/free/identity Provision a single disposable email identity keyed to the caller's IP address. No authentication headers are required. The identity expires automatically after 24 hours. **Request body:** | Field | Type | Required | Description | |-------|------|----------|-------------| | `label` | string | No | Optional human-readable label for the identity | **curl:** ``` curl -X POST https://api.aidenid.com/v1/free/identity \ -H "Content-Type: application/json" \ -d '{ "label": "github-signup" }' ``` **Python:** ```python import requests resp = requests.post( "https://api.aidenid.com/v1/free/identity", json={"label": "github-signup"}, ) data = resp.json() print(data["email"], data["token"]) ``` **TypeScript:** ```typescript const res = await fetch("https://api.aidenid.com/v1/free/identity", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ label: "github-signup" }), }); const { id, email, token } = await res.json(); ``` **Response 201 Created:** ```json { "id": "free_a1b2c3d4e5", "email": "a1b2c3d4e5@inbox.aidenid.com", "label": "github-signup", "token": "ftok_x9y8z7w6v5", "status": "provisioned", "expires_at": "2026-04-07T10:00:00Z" } ``` **Ephemeral token:** The `token` returned at creation is the only credential for this identity. Store it — it cannot be retrieved again. All subsequent requests for this identity require it as a query parameter. ## Check identity status ### GET /v1/free/identity/{id} Retrieve the current status of a free-tier identity. Requires the ephemeral token from creation. **Query parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `token` | string | Yes | Ephemeral token returned at identity creation | ``` curl "https://api.aidenid.com/v1/free/identity/free_a1b2c3d4e5?token=ftok_x9y8z7w6v5" ``` **Response 200 OK:** ```json { "id": "free_a1b2c3d4e5", "email": "a1b2c3d4e5@inbox.aidenid.com", "label": "github-signup", "status": "active", "expires_at": "2026-04-07T10:00:00Z" } ``` ## Poll for extracted code ### GET /v1/free/identity/{id}/extraction Poll for an extracted OTP, verification code, or magic link. Returns `202 Accepted` while the extraction is still pending and `200 OK` when a value has been extracted. **Query parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `token` | string | Yes | Ephemeral token returned at identity creation | ``` curl "https://api.aidenid.com/v1/free/identity/free_a1b2c3d4e5/extraction?token=ftok_x9y8z7w6v5" ``` **Response -- pending 202 Accepted:** ```json { "status": "pending", "retryAfterSeconds": 3 } ``` **Response -- ready 200 OK:** ```json { "status": "ready", "type": "otp", "has_secret": true, "redacted_value": "***", "extracted_at": "2026-04-06T10:05:02Z", "confidence": 0.99 } ``` ## Complete agent flow example ### Python ```python import requests, time BASE = "https://api.aidenid.com/v1/free" # Step 1: Create a disposable identity identity = requests.post(f"{BASE}/identity", json={"label": "demo-signup"}).json() email = identity["email"] token = identity["token"] identity_id = identity["id"] print(f"Using email: {email}") # Step 2: Use the email in an authorized customer-controlled flow # ... your signup automation here ... # Step 3: Poll for redacted extraction evidence for attempt in range(20): resp = requests.get(f"{BASE}/identity/{identity_id}/extraction", params={"token": token}) if resp.status_code == 200: data = resp.json() print(f"Extraction ready: {data['type']}") break retry_after = resp.json().get("retryAfterSeconds", 3) print(f"Pending... retrying in {retry_after}s") time.sleep(retry_after) else: print("Timed out waiting for extraction") ``` ### TypeScript ```typescript const BASE = "https://api.aidenid.com/v1/free"; // Step 1: Create a disposable identity const identity = await fetch(`${BASE}/identity`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ label: "demo-signup" }), }).then((r) => r.json()); const { id, email, token } = identity; console.log("Using email:", email); // Step 2: Use the email in an authorized customer-controlled flow // ... your signup automation here ... // Step 3: Poll for redacted extraction evidence for (let i = 0; i < 20; i++) { const res = await fetch(`${BASE}/identity/${id}/extraction?token=${token}`); const data = await res.json(); if (res.status === 200) { console.log("Extraction ready:", data.type); break; } const wait = data.retryAfterSeconds ?? 3; console.log(`Pending... retrying in ${wait}s`); await new Promise((r) => setTimeout(r, wait * 1000)); } ``` ## Free tier error codes | Code | Status | Description | |------|--------|-------------| | `ip_limit_reached` | 429 | This IP already has an active free-tier identity | | `rate_limited` | 429 | Too many requests — retry after the indicated interval | | `identity_not_found` | 404 | Identity does not exist or has expired | | `invalid_token` | 401 | The ephemeral token is missing or incorrect | | `identity_expired` | 410 | The identity has passed its 24-hour TTL | **Moving to production?** The free tier is designed for experimentation and single-use agent flows. For production workloads with concurrent identities, custom domains, webhooks, and higher rate limits, upgrade to a paid plan. --- # Consumer API Human-facing endpoints for individual consumers. Unlike the agent API which uses API keys, the consumer API uses passwordless magic-link authentication to create browser sessions. **Consumer auth vs. agent auth:** The agent API authenticates with `Authorization: Bearer aid_*` API keys scoped to an organization and project. The consumer API uses magic-link email verification to establish a session cookie. Consumer sessions are bound to a single user and automatically provision a personal org and project on signup. ## Sign up ### POST /v1/consumer/signup Create a new consumer account. A magic-link verification email is sent to the provided address. **Request body:** | Field | Type | Required | Description | |-------|------|----------|-------------| | `email` | string | Yes | Personal email address for the consumer account | ``` curl -X POST https://api.aidenid.com/v1/consumer/signup \ -H "Content-Type: application/json" \ -d '{ "email": "alice@example.com" }' ``` **Response 200 OK:** ```json { "message": "Verification email sent", "email": "alice@example.com" } ``` ## Verify magic link ### POST /v1/consumer/verify Exchange a magic-link token for a consumer session. **Request body:** | Field | Type | Required | Description | |-------|------|----------|-------------| | `token` | string | Yes | Magic-link token from the verification email | ``` curl -X POST https://api.aidenid.com/v1/consumer/verify \ -H "Content-Type: application/json" \ -d '{ "token": "ml_abc123def456" }' ``` **Response 200 OK:** ```json { "consumer_id": "csr_m1n2o3p4", "org_id": "org_personal_m1n2o3p4", "project_id": "proj_default_m1n2o3p4", "session_token": "sess_q5r6s7t8", "expires_at": "2026-04-13T10:00:00Z" } ``` ## Request login ### POST /v1/consumer/login Send a new magic-link login email to an existing consumer. **Request body:** | Field | Type | Required | Description | |-------|------|----------|-------------| | `email` | string | Yes | Email address of the existing consumer account | ``` curl -X POST https://api.aidenid.com/v1/consumer/login \ -H "Content-Type: application/json" \ -d '{ "email": "alice@example.com" }' ``` **Response 200 OK:** ```json { "message": "Login email sent", "email": "alice@example.com" } ``` ## Inbox stream (SSE) ### GET /v1/consumer/inbox Server-Sent Events stream of inbound email events and OTP extractions for the authenticated consumer. Requires a valid consumer session. ``` curl -N "https://api.aidenid.com/v1/consumer/inbox" \ -H "Authorization: Bearer sess_q5r6s7t8" ``` **TypeScript:** ```typescript const evtSource = new EventSource( "https://api.aidenid.com/v1/consumer/inbox" ); evtSource.addEventListener("inbound.received", (e) => { const data = JSON.parse(e.data); console.log("New email from:", data.from); }); evtSource.addEventListener("extraction.completed", (e) => { const data = JSON.parse(e.data); console.log("Extraction ready:", data.type, data.confidence); }); ``` **Event types:** | Event | Description | |-------|-------------| | `inbound.received` | A new email arrived at one of the consumer's identity inboxes | | `extraction.completed` | An OTP or verification code was extracted from an email | ## List identities ### GET /v1/consumer/identities List the authenticated consumer's temporary email identities. Supports pagination. **Query parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `limit` | integer | No | Max results per page (default: 20, max: 100) | | `offset` | integer | No | Pagination offset | ``` curl "https://api.aidenid.com/v1/consumer/identities?limit=10&offset=0" \ -H "Authorization: Bearer sess_q5r6s7t8" ``` **Response 200 OK:** ```json { "items": [ { "id": "ident_c1d2e3f4", "email": "c1d2e3f4@inbox.aidenid.com", "label": "twitter-signup", "status": "active", "created_at": "2026-04-06T08:00:00Z", "expires_at": "2026-04-07T08:00:00Z" } ], "total": 1, "limit": 10, "offset": 0 } ``` ## Create identity ### POST /v1/consumer/identities Create a new temporary email identity for the consumer. **Request body:** | Field | Type | Required | Description | |-------|------|----------|-------------| | `label` | string | Yes | Human-readable label for the identity | ``` curl -X POST https://api.aidenid.com/v1/consumer/identities \ -H "Authorization: Bearer sess_q5r6s7t8" \ -H "Content-Type: application/json" \ -d '{ "label": "twitter-signup" }' ``` **Tier limits:** Trial consumers can create up to 3 active identities. Paid consumers (`human_paid` plan) can create up to 10. ## List notifications ### GET /v1/consumer/notifications Retrieve notification history for the authenticated consumer, including extraction alerts and identity expiration warnings. ``` curl "https://api.aidenid.com/v1/consumer/notifications" \ -H "Authorization: Bearer sess_q5r6s7t8" ``` **Response 200 OK:** ```json { "items": [ { "id": "notif_u1v2w3", "type": "extraction.completed", "title": "OTP received for twitter-signup", "body": "A redacted code was extracted with 99% confidence", "read": false, "created_at": "2026-04-06T10:05:02Z" } ], "total": 1, "limit": 20, "offset": 0 } ``` ## Upgrade to paid plan ### POST /v1/consumer/upgrade Start a Stripe checkout session to upgrade the consumer to the `human_paid` plan. ``` curl -X POST https://api.aidenid.com/v1/consumer/upgrade \ -H "Authorization: Bearer sess_q5r6s7t8" ``` **Response 200 OK:** ```json { "checkout_url": "https://checkout.stripe.com/c/pay/cs_live_..." } ``` ## Consumer error codes | Code | Status | Description | |------|--------|-------------| | `email_already_registered` | 409 | A consumer account with this email already exists | | `invalid_magic_link` | 401 | The magic-link token is invalid or expired | | `session_expired` | 401 | The consumer session has expired — request a new login | | `identity_limit_reached` | 403 | Active identity limit reached for the consumer's tier | | `consumer_not_found` | 404 | No consumer account found for this email | | `already_upgraded` | 409 | Consumer is already on the paid plan | --- # Trial API Start a 7-day free trial of any paid agent plan — no credit card required. One trial is allowed per organization. ## Trial lifecycle Each trial follows a state machine: ``` ACTIVE --> EXPIRED (automatic after 7 days) ACTIVE --> CONVERTED (Stripe checkout completed) ACTIVE --> CANCELED (user cancels trial) EXPIRED -> CONVERTED (3-day grace period allows late conversion) ``` **Grace period:** After a trial expires, there is a 3-day grace period during which the organization can still convert to a paid plan without data loss. After the grace period, trial resources are permanently deleted. ## Start a trial ### POST /v1/trials/start Start a 7-day free trial of a paid agent plan. No credit card is required. Each organization may only start one trial. **Request body:** | Field | Type | Required | Description | |-------|------|----------|-------------| | `plan` | string | Yes | Plan to trial: `starter`, `growth`, or `pro` | ``` curl -X POST https://api.aidenid.com/v1/trials/start \ -H "Authorization: Bearer aid_your_api_key" \ -H "X-Org-Id: org_abc123" \ -H "X-Project-Id: proj_def456" \ -H "Idempotency-Key: " \ -H "Content-Type: application/json" \ -d '{ "plan": "growth" }' ``` **Python:** ```python import requests resp = requests.post( "https://api.aidenid.com/v1/trials/start", headers={ "Authorization": "Bearer aid_your_api_key", "X-Org-Id": "org_abc123", "X-Project-Id": "proj_def456", "Idempotency-Key": "trial-start-001", }, json={"plan": "growth"}, ) trial = resp.json() print(f"Trial {trial['id']} active until {trial['expires_at']}") ``` **Response 201 Created:** ```json { "id": "trial_g1h2i3j4", "plan": "growth", "status": "ACTIVE", "started_at": "2026-04-06T10:00:00Z", "expires_at": "2026-04-13T10:00:00Z", "org_id": "org_abc123" } ``` ## Check trial status ### GET /v1/trials/{id} Retrieve the current state of a trial. Possible statuses are `ACTIVE`, `EXPIRED`, `CONVERTED`, and `CANCELED`. ``` curl "https://api.aidenid.com/v1/trials/trial_g1h2i3j4" \ -H "Authorization: Bearer aid_your_api_key" \ -H "X-Org-Id: org_abc123" \ -H "X-Project-Id: proj_def456" ``` **Response 200 OK:** ```json { "id": "trial_g1h2i3j4", "plan": "growth", "status": "ACTIVE", "started_at": "2026-04-06T10:00:00Z", "expires_at": "2026-04-13T10:00:00Z", "days_remaining": 7, "org_id": "org_abc123" } ``` ## Convert trial to paid ### POST /v1/trials/{id}/convert Convert an active (or recently expired) trial to a paid subscription. Returns a Stripe checkout URL. ``` curl -X POST https://api.aidenid.com/v1/trials/trial_g1h2i3j4/convert \ -H "Authorization: Bearer aid_your_api_key" \ -H "X-Org-Id: org_abc123" \ -H "X-Project-Id: proj_def456" \ -H "Idempotency-Key: " ``` **Response 200 OK:** ```json { "checkout_url": "https://checkout.stripe.com/c/pay/cs_live_...", "trial_id": "trial_g1h2i3j4", "plan": "growth" } ``` ## Trial error codes | Code | Status | Description | |------|--------|-------------| | `trial_already_exists` | 409 | This organization has already used its trial | | `trial_not_found` | 404 | Trial does not exist | | `trial_expired` | 410 | Trial has expired beyond the grace period | | `trial_already_converted` | 409 | Trial has already been converted to a paid plan | | `trial_canceled` | 409 | Trial was canceled and cannot be converted | | `invalid_plan` | 400 | Plan must be one of: starter, growth, pro | | `missing_idempotency_key` | 400 | Mutation request missing Idempotency-Key header | --- # Webhooks API Register HTTPS endpoints to receive real-time events from AIdenID. Webhook payloads are signed with HMAC-SHA256 for verification. ## Create webhook endpoint ### POST /v1/webhooks Register a new webhook endpoint. **Request body:** | Field | Type | Required | Description | |-------|------|----------|-------------| | `url` | string | Yes | HTTPS URL to receive webhook events | | `events` | string[] | Yes | Event types to subscribe to | | `description` | string | No | Human-readable description | ``` curl -X POST https://api.aidenid.com/v1/webhooks \ -H "Authorization: Bearer aid_your_api_key" \ -H "X-Org-Id: org_abc123" \ -H "X-Project-Id: proj_def456" \ -H "Idempotency-Key: " \ -H "Content-Type: application/json" \ -d '{ "url": "https://your-app.com/webhooks/aidenid", "events": ["email.received", "extraction.completed"], "description": "Production webhook" }' ``` **Response:** ```json { "id": "wh_p1q2r3s4", "url": "https://your-app.com/webhooks/aidenid", "events": ["email.received", "extraction.completed"], "secret_issued": true, "secretPreview": "whsec_............", "description": "Production webhook", "created_at": "2026-04-04T10:00:00Z" } ``` **Important:** Treat create/rotate responses as non-cacheable secret envelopes. Store full secret material in backend secret management and only persist `secretPreview` in normal app records. ## List webhook endpoints ### GET /v1/webhooks List all registered webhook endpoints for the current project. ## Rotate webhook secret ### POST /v1/webhooks/{webhook_id}/rotate-secret Generate a new signing secret for a webhook endpoint. Deploy verifier code that accepts both active and previous secrets first, then switch traffic, then retire the previous secret. ``` curl -X POST https://api.aidenid.com/v1/webhooks/wh_p1q2r3s4/rotate-secret \ -H "Authorization: Bearer aid_your_api_key" \ -H "X-Org-Id: org_abc123" \ -H "X-Project-Id: proj_def456" \ -H "Idempotency-Key: " ``` ## Replay event ### POST /v1/webhooks/{webhook_id}/replay Re-deliver a specific event to a webhook endpoint. **Request body:** | Field | Type | Required | Description | |-------|------|----------|-------------| | `event_id` | string | Yes | The event ID to replay | ``` curl -X POST https://api.aidenid.com/v1/webhooks/wh_p1q2r3s4/replay \ -H "Authorization: Bearer aid_your_api_key" \ -H "X-Org-Id: org_abc123" \ -H "X-Project-Id: proj_def456" \ -H "Idempotency-Key: " \ -H "Content-Type: application/json" \ -d '{ "event_id": "evt_x1y2z3" }' ``` ## Event types | Event type | Description | |------------|-------------| | `email.received` | New email arrived at an identity inbox | | `extraction.completed` | Authentication code or link successfully extracted | | `identity.provisioned` | New identity created | | `identity.extended` | Identity TTL extended | | `identity.squashed` | Identity revoked | | `identity.expired` | Identity TTL elapsed | ### Consumer notification events | Event type | Description | |------------|-------------| | `consumer.notification.sent` | OTP or magic link forwarded to consumer's personal email | | `consumer.notification.failed` | Notification delivery to personal email failed | ## Webhook payload format All webhook deliveries include two signature headers: | Header | Description | |--------|-------------| | `X-Signature` | HMAC-SHA256 signature of the payload | | `X-Timestamp` | Unix timestamp of the delivery | **Payload example:** ```json { "id": "evt_x1y2z3", "type": "extraction.completed", "timestamp": "2026-04-04T10:05:02Z", "data": { "identity_id": "ident_a1b2c3d4e5", "extraction": { "type": "otp", "has_secret": true, "redacted_value": "***", "confidence": 0.99 } } } ``` ## Verifying signatures Compute the HMAC-SHA256 of `{timestamp}.{payload}` using your webhook secret, and compare it to the `X-Signature` header using a timing-safe comparison. Also enforce timestamp freshness and event-id replay protection. ```typescript import { createHmac, timingSafeEqual } from "crypto"; function verifyDelivery( payload: string, eventId: string, signature: string | undefined, timestamp: string | undefined, secret: string ): boolean { if (!signature || !timestamp) return false; if (!/^[a-f0-9]{64}$/i.test(signature)) return false; if (!/^[0-9]{10}$/.test(timestamp)) return false; const ts = Number(timestamp); const now = Math.floor(Date.now() / 1000); if (!Number.isSafeInteger(ts) || Math.abs(now - ts) > 300) { return false; // stale or malformed timestamp } const signed = `${timestamp}.${payload}`; const expected = createHmac("sha256", secret).update(signed).digest("hex"); const provided = Buffer.from(signature, "hex"); const computed = Buffer.from(expected, "hex"); if (provided.length !== computed.length) return false; return timingSafeEqual(provided, computed); } ``` ## Webhooks error codes | Code | Status | Description | |------|--------|-------------| | `webhook_not_found` | 404 | Webhook endpoint does not exist | | `event_not_found` | 404 | Event ID not found for replay | | `quota_exceeded` | 403 | Webhook endpoint limit reached for your plan | | `missing_idempotency_key` | 400 | Mutation request missing Idempotency-Key header | --- # Auth Flows API Test the reliability of authentication flows against any target URL. AIdenID provisions an identity, triggers the flow, intercepts the verification email, and reports the results. **Use case:** Auth flow testing is ideal for monitoring the reliability of your own signup, password reset, and invite flows — or for validating that third-party services correctly deliver verification emails. ## Create password reset run ### POST /v1/reliability/password-reset Initiate a password reset flow test against a target URL. **Request body:** | Field | Type | Required | Description | |-------|------|----------|-------------| | `target_url` | string | Yes | URL of the password reset page to test | | `identity_id` | string | Yes | Identity to use for the test (must have an active inbox) | | `timeout_seconds` | integer | No | Max time to wait for the email (default: 120) | ``` curl -X POST https://api.aidenid.com/v1/reliability/password-reset \ -H "Authorization: Bearer aid_your_api_key" \ -H "X-Org-Id: org_abc123" \ -H "X-Project-Id: proj_def456" \ -H "Idempotency-Key: " \ -H "Content-Type: application/json" \ -d '{ "target_url": "https://example.com/forgot-password", "identity_id": "ident_a1b2c3d4e5", "timeout_seconds": 120 }' ``` **Response:** ```json { "id": "run_t1u2v3w4", "flow_type": "password_reset", "status": "pending", "identity_id": "ident_a1b2c3d4e5", "target_url": "https://example.com/forgot-password", "created_at": "2026-04-04T10:10:00Z" } ``` ## Create invite flow run ### POST /v1/reliability/invite Test an invite flow. Works the same as password reset testing but targets invite or signup URLs. **Request body:** | Field | Type | Required | Description | |-------|------|----------|-------------| | `target_url` | string | Yes | URL of the invite page to test | | `identity_id` | string | Yes | Identity to use for the test | | `timeout_seconds` | integer | No | Max time to wait for the email (default: 120) | ## Get run status ### GET /v1/reliability/{run_id} Check the status of a reliability test run. **Run statuses:** | Status | Description | |--------|-------------| | `pending` | Run created, waiting for email delivery | | `email_received` | Email arrived, extraction in progress | | `completed` | Flow completed successfully, code extracted | | `timeout` | Email was not received within the timeout period | | `failed` | Flow failed (target URL error, extraction failure, etc.) | **Completed run response:** ```json { "id": "run_t1u2v3w4", "flow_type": "password_reset", "status": "completed", "identity_id": "ident_a1b2c3d4e5", "target_url": "https://example.com/forgot-password", "created_at": "2026-04-04T10:10:00Z", "completed_at": "2026-04-04T10:10:45Z", "result": { "email_received_at": "2026-04-04T10:10:38Z", "extraction_type": "otp", "has_secret": true, "redacted_value": "***921", "secret_retrieval_available": true, "delivery_time_ms": 38200, "confidence": 0.98 } } ``` ## Typical workflow 1. Create an identity with a short TTL (e.g., 1 hour) 2. Start a reliability run with the identity and target URL 3. Poll the run status until it reaches `completed`, `timeout`, or `failed` 4. Inspect the result for delivery timing and extraction confidence 5. Squash the identity when done ## Auth error envelope contract All auth-related failures return a stable envelope: ```json { "error": { "code": "scope_mismatch", "message": "Consumer session does not match provided org/project scope headers.", "details": { "requestId": "req_abc123" } }, "requestId": "req_abc123", "timestamp": "2026-04-06T21:33:45.225313+00:00", "path": "/v1/reliability/password-reset", "retryable": false } ``` | Code | Status | When returned | Retryable | |------|--------|---------------|-----------| | `unauthorized` | 401 | Missing/invalid bearer token or session credential | No | | `forbidden` | 403 | Authenticated caller lacks required access scope | No | | `scope_mismatch` | 403 | Provided X-Org-Id/X-Project-Id do not match resolved session scope | No | | `dependency_timeout` | 503 | Transient backing dependency timeout during auth/session lookup | Yes | ## Auth flows error codes | Code | Status | Description | |------|--------|-------------| | `run_not_found` | 404 | Reliability run does not exist | | `identity_not_found` | 404 | Referenced identity does not exist | | `identity_already_squashed` | 409 | Cannot use a squashed identity for testing | | `missing_idempotency_key` | 400 | Mutation request missing Idempotency-Key header | --- # Domains API Register custom email domains so identities receive mail at addresses like `agent@yourdomain.com` instead of the default `@inbox.aidenid.com`. **Default domain:** All identities use `@inbox.aidenid.com` by default. You only need to register a custom domain if you want branded email addresses or need to pass domain-specific verification checks on third-party services. ## DNS setup To use a custom domain, add two DNS records at your domain registrar: | Record type | Host | Value | Purpose | |-------------|------|-------|---------| | `MX` | `@` (or subdomain) | `10 mx.aidenid.com` | Route inbound email to AIdenID | | `TXT` | `_aidenid-verify` | `aidenid-verify=` | Prove domain ownership | The exact TXT verification value is returned when you register the domain. DNS propagation typically takes 5 to 30 minutes. ## Register a domain ### POST /v1/domains Register a custom domain for use with identity email addresses. **Request body:** | Field | Type | Required | Description | |-------|------|----------|-------------| | `domain` | string | Yes | The domain to register (e.g., `mail.yourdomain.com`) | ``` curl -X POST https://api.aidenid.com/v1/domains \ -H "Authorization: Bearer aid_your_api_key" \ -H "X-Org-Id: org_abc123" \ -H "X-Project-Id: proj_def456" \ -H "Idempotency-Key: " \ -H "Content-Type: application/json" \ -d '{ "domain": "mail.yourdomain.com" }' ``` **Response 201 Created:** ```json { "id": "dom_k1l2m3n4", "domain": "mail.yourdomain.com", "status": "pending_verification", "dns_records": [ { "type": "MX", "host": "mail.yourdomain.com", "value": "10 mx.aidenid.com", "status": "pending" }, { "type": "TXT", "host": "_aidenid-verify.mail.yourdomain.com", "value": "aidenid-verify=dom_k1l2m3n4_v9w8x7", "status": "pending" } ], "created_at": "2026-04-06T10:00:00Z" } ``` ## List domains ### GET /v1/domains List all registered domains for the current project, including their verification status. ``` curl "https://api.aidenid.com/v1/domains" \ -H "Authorization: Bearer aid_your_api_key" \ -H "X-Org-Id: org_abc123" \ -H "X-Project-Id: proj_def456" ``` **Response 200 OK:** ```json { "items": [ { "id": "dom_k1l2m3n4", "domain": "mail.yourdomain.com", "status": "verified", "created_at": "2026-04-06T10:00:00Z", "verified_at": "2026-04-06T10:15:00Z" } ], "total": 1, "limit": 50, "offset": 0 } ``` ## Get domain details ### GET /v1/domains/{id} Retrieve a specific domain, including the full DNS records required for verification. ## Verify domain ### POST /v1/domains/{id}/verify Trigger a DNS verification check for the domain. ``` curl -X POST https://api.aidenid.com/v1/domains/dom_k1l2m3n4/verify \ -H "Authorization: Bearer aid_your_api_key" \ -H "X-Org-Id: org_abc123" \ -H "X-Project-Id: proj_def456" \ -H "Idempotency-Key: " ``` **Response 200 OK:** ```json { "id": "dom_k1l2m3n4", "domain": "mail.yourdomain.com", "status": "verified", "dns_records": [ { "type": "MX", "host": "mail.yourdomain.com", "value": "10 mx.aidenid.com", "status": "verified" }, { "type": "TXT", "host": "_aidenid-verify.mail.yourdomain.com", "value": "aidenid-verify=dom_k1l2m3n4_v9w8x7", "status": "verified" } ], "verified_at": "2026-04-06T10:15:00Z" } ``` ## Remove domain ### DELETE /v1/domains/{id} Remove a registered domain. Active identities using this domain will continue to receive mail until they expire — no new identities can be created on the domain after removal. ``` curl -X DELETE https://api.aidenid.com/v1/domains/dom_k1l2m3n4 \ -H "Authorization: Bearer aid_your_api_key" \ -H "X-Org-Id: org_abc123" \ -H "X-Project-Id: proj_def456" \ -H "Idempotency-Key: " ``` Response: 204 No Content ## Domains error codes | Code | Status | Description | |------|--------|-------------| | `domain_not_found` | 404 | Domain does not exist in this project | | `domain_already_registered` | 409 | This domain is already registered in your project or another | | `dns_verification_failed` | 422 | Required DNS records not found — check your DNS configuration | | `domain_quota_exceeded` | 403 | Domain limit reached for your plan | | `missing_idempotency_key` | 400 | Mutation request missing Idempotency-Key header | --- # Billing API Manage subscriptions, view invoices, and track usage programmatically. All billing is powered by Stripe — checkout and portal sessions redirect to Stripe-hosted pages. ## Plans | Plan | Price | Identities | API calls/mo | Domains | |------|-------|------------|-------------|---------| | `starter` | See pricing | Plan-defined | Plan-defined | Plan-defined | | `growth` | See pricing | Plan-defined | Plan-defined | Plan-defined | | `pro` | See pricing | Plan-defined | Plan-defined | Plan-defined | | `enterprise` | Contract | Plan-defined | Plan-defined | Plan-defined | ## Get subscription ### GET /v1/billing/subscription Retrieve the current subscription details for the organization. ``` curl "https://api.aidenid.com/v1/billing/subscription" \ -H "Authorization: Bearer aid_your_api_key" \ -H "X-Org-Id: org_abc123" \ -H "X-Project-Id: proj_def456" ``` **Response 200 OK:** ```json { "plan": "growth", "status": "active", "current_period_start": "2026-04-01T00:00:00Z", "current_period_end": "2026-05-01T00:00:00Z", "trial": { "id": "trial_g1h2i3j4", "status": "CONVERTED", "started_at": "2026-03-25T10:00:00Z" }, "billing_provider": "stripe", "customer_reference": "provider-managed" } ``` ## Create checkout session ### POST /v1/billing/checkout Create a Stripe checkout session for a new subscription or plan change. **Request body:** | Field | Type | Required | Description | |-------|------|----------|-------------| | `plan` | string | Yes | Target plan: `starter`, `growth`, `pro`, or `enterprise` | ``` curl -X POST https://api.aidenid.com/v1/billing/checkout \ -H "Authorization: Bearer aid_your_api_key" \ -H "X-Org-Id: org_abc123" \ -H "X-Project-Id: proj_def456" \ -H "Idempotency-Key: " \ -H "Content-Type: application/json" \ -d '{ "plan": "growth" }' ``` **Response 200 OK:** ```json { "checkout_redirect_token": "brt_chk_abc123...", "expires_at": "2026-04-04T10:12:00Z" } ``` **Redirect token handling:** Treat `checkout_redirect_token` as single-use and short-lived. Redeem it server-side only, reject expired tokens, and deny replayed token IDs. ## List invoices ### GET /v1/billing/invoices Retrieve billing history and invoices for the organization. ``` curl "https://api.aidenid.com/v1/billing/invoices" \ -H "Authorization: Bearer aid_your_api_key" \ -H "X-Org-Id: org_abc123" \ -H "X-Project-Id: proj_def456" ``` **Response 200 OK:** ```json { "items": [ { "id": "inv_a1b2c3", "amount_cents": 9900, "currency": "usd", "status": "paid", "period_start": "2026-03-01T00:00:00Z", "period_end": "2026-04-01T00:00:00Z", "pdf_download": { "exchange_required": true, "token_preview": "inv_dl_........", "expires_at": "2026-04-01T00:10:00Z" }, "created_at": "2026-04-01T00:00:00Z" } ], "total": 1, "limit": 20, "offset": 0 } ``` ## Get usage ### GET /v1/billing/usage Get current billing period usage metrics. ``` curl "https://api.aidenid.com/v1/billing/usage" \ -H "Authorization: Bearer aid_your_api_key" \ -H "X-Org-Id: org_abc123" \ -H "X-Project-Id: proj_def456" ``` **Response 200 OK:** ```json { "period_start": "2026-04-01T00:00:00Z", "period_end": "2026-05-01T00:00:00Z", "identities_created": 247, "identities_limit": 1000, "active_identities": 42, "api_calls": 15832, "api_calls_limit": 100000, "domains_registered": 2, "domains_limit": 5 } ``` ## Customer portal ### POST /v1/billing/portal Create a Stripe customer portal session for self-service billing management. ``` curl -X POST https://api.aidenid.com/v1/billing/portal \ -H "Authorization: Bearer aid_your_api_key" \ -H "X-Org-Id: org_abc123" \ -H "X-Project-Id: proj_def456" \ -H "Idempotency-Key: " ``` **Response 200 OK:** ```json { "portal_redirect_token": "brt_portal_abc123...", "expires_at": "2026-04-04T10:12:00Z" } ``` ## Billing mutation replay and error envelope Billing mutations (`/v1/billing/checkout` and `/v1/billing/portal`) require `Idempotency-Key`. Replayed keys return the original response. | Code | Status | When returned | Retryable | |------|--------|---------------|-----------| | `missing_idempotency_key` | 400 | Mutation call omitted required idempotency header | No | | `unauthorized` | 401 | Missing or invalid bearer credential | No | | `forbidden` | 403 | Credential lacks required billing scope | No | | `token_replayed` | 409 | Redirect token already redeemed by trusted backend | No | | `token_expired` | 410 | Redirect token expired before redemption | No | | `dependency_timeout` | 503 | Transient Stripe/upstream timeout while creating session | Yes | ## Billing error codes | Code | Status | Description | |------|--------|-------------| | `no_active_subscription` | 404 | Organization does not have an active subscription | | `invalid_plan` | 400 | Plan must be one of: starter, growth, pro, enterprise | | `checkout_failed` | 502 | Failed to create Stripe checkout session | | `portal_failed` | 502 | Failed to create Stripe portal session | | `missing_idempotency_key` | 400 | Mutation request missing Idempotency-Key header | --- # Realtime API Subscribe to a Server-Sent Events (SSE) stream to receive project events in real time. This is the recommended alternative to polling for extraction results. ## Event stream ### GET /v1/realtime/stream Opens a persistent SSE connection that emits events for the authenticated project. **Headers:** | Header | Required | Description | |--------|----------|-------------| | `Authorization` | Yes | Bearer token with your API key | | `X-Org-Id` | Yes | Organization identifier | | `X-Project-Id` | Yes | Project identifier | | `Last-Event-ID` | No | Resume from a specific event ID after reconnection | ``` curl -N "https://api.aidenid.com/v1/realtime/stream" \ -H "Authorization: Bearer aid_your_api_key" \ -H "X-Org-Id: org_abc123" \ -H "X-Project-Id: proj_def456" \ -H "Accept: text/event-stream" ``` ## Event types | Event | Description | |-------|-------------| | `identity.created` | A new identity was provisioned | | `identity.expired` | An identity's TTL elapsed | | `extraction.completed` | An OTP or verification code was extracted from an inbound email | | `inbound.received` | A new email arrived at an identity inbox | ## Event format Each SSE event contains four fields: | Field | Description | |-------|-------------| | `event` | The event type (e.g., `extraction.completed`) | | `data` | JSON payload with event details | | `id` | Unique event ID — use with `Last-Event-ID` for reconnection | | `retry` | Suggested reconnection interval in milliseconds | **Raw SSE example:** ``` event: extraction.completed id: evt_x1y2z3 retry: 3000 data: {"identity_id":"ident_a1b2c3d4e5","type":"otp","has_secret":true,"redacted_value":"***","confidence":0.99,"extracted_at":"2026-04-06T10:05:02Z"} event: identity.expired id: evt_a4b5c6 retry: 3000 data: {"identity_id":"ident_f6g7h8i9j0","expired_at":"2026-04-06T12:00:00Z"} event: inbound.received id: evt_d7e8f9 retry: 3000 data: {"identity_id":"ident_a1b2c3d4e5","from":"noreply@github.com","subject":"Your verification code","received_at":"2026-04-06T10:04:58Z"} ``` ## TypeScript (Node.js) ```typescript import EventSource from "eventsource"; const es = new EventSource( "https://api.aidenid.com/v1/realtime/stream", { headers: { "Authorization": "Bearer aid_your_api_key", "X-Org-Id": "org_abc123", "X-Project-Id": "proj_def456", }, } ); es.addEventListener("extraction.completed", (event) => { const data = JSON.parse(event.data); console.log(`Extraction ready for ${data.identity_id}: ${data.type}`); }); es.addEventListener("identity.created", (event) => { const data = JSON.parse(event.data); console.log(`New identity: ${data.identity_id}`); }); es.addEventListener("inbound.received", (event) => { const data = JSON.parse(event.data); console.log(`Email from ${data.from}: ${data.subject}`); }); es.onerror = (err) => { console.error("SSE connection error:", err); // EventSource will auto-reconnect with Last-Event-ID }; ``` ## Python (sseclient) ```python import requests import sseclient import json url = "https://api.aidenid.com/v1/realtime/stream" headers = { "Authorization": "Bearer aid_your_api_key", "X-Org-Id": "org_abc123", "X-Project-Id": "proj_def456", "Accept": "text/event-stream", } response = requests.get(url, headers=headers, stream=True) client = sseclient.SSEClient(response) for event in client.events(): data = json.loads(event.data) if event.event == "extraction.completed": print(f"Extraction ready for {data['identity_id']}: {data['type']}") elif event.event == "inbound.received": print(f"Email from {data['from']}: {data['subject']}") elif event.event == "identity.expired": print(f"Identity expired: {data['identity_id']}") ``` ## Reconnection If the SSE connection drops, reconnect and include the `Last-Event-ID` header set to the `id` of the last event you received. The server will replay any events that occurred while you were disconnected. ``` curl -N "https://api.aidenid.com/v1/realtime/stream" \ -H "Authorization: Bearer aid_your_api_key" \ -H "X-Org-Id: org_abc123" \ -H "X-Project-Id: proj_def456" \ -H "Last-Event-ID: evt_x1y2z3" \ -H "Accept: text/event-stream" ``` **Automatic reconnection:** The `EventSource` API and the `eventsource` npm package handle reconnection automatically, including sending `Last-Event-ID`. The `retry` field in each event tells the client how long to wait before reconnecting (default: 3000ms). ## Realtime error codes | Code | Status | Description | |------|--------|-------------| | `unauthorized` | 401 | Missing or invalid API key | | `forbidden` | 403 | API key lacks the required scope | | `stream_limit_reached` | 429 | Too many concurrent SSE connections for your plan | --- # Exports API Create bulk data export jobs to download identities, events, or extraction records in JSON or CSV format. Exports run asynchronously and provide a download URL when complete. ## Create an export ### POST /v1/exports Start a new export job. **Request body:** | Field | Type | Required | Description | |-------|------|----------|-------------| | `type` | string | Yes | Data type to export: `identities`, `events`, or `extractions` | | `format` | string | Yes | Output format: `json` or `csv` | | `date_range` | object | No | Filter by date range with `start` and `end` (ISO 8601) | ``` curl -X POST https://api.aidenid.com/v1/exports \ -H "Authorization: Bearer aid_your_api_key" \ -H "X-Org-Id: org_abc123" \ -H "X-Project-Id: proj_def456" \ -H "Idempotency-Key: " \ -H "Content-Type: application/json" \ -d '{ "type": "identities", "format": "csv", "date_range": { "start": "2026-03-01T00:00:00Z", "end": "2026-04-01T00:00:00Z" } }' ``` **Response 201 Created:** ```json { "id": "exp_r1s2t3u4", "type": "identities", "format": "csv", "status": "pending", "date_range": { "start": "2026-03-01T00:00:00Z", "end": "2026-04-01T00:00:00Z" }, "created_at": "2026-04-06T10:00:00Z" } ``` ## Get export status ### GET /v1/exports/{id} Check the status of an export job. When status is `ready`, the response includes a `download_url`. **Response -- pending:** ```json { "id": "exp_r1s2t3u4", "type": "identities", "format": "csv", "status": "processing", "progress_percent": 45, "created_at": "2026-04-06T10:00:00Z" } ``` **Response -- ready:** ```json { "id": "exp_r1s2t3u4", "type": "identities", "format": "csv", "status": "ready", "progress_percent": 100, "download_url": "https://exports.aidenid.com/exp_r1s2t3u4.csv?token=...", "download_expires_at": "2026-04-06T11:00:00Z", "row_count": 1247, "file_size_bytes": 89432, "created_at": "2026-04-06T10:00:00Z", "completed_at": "2026-04-06T10:02:15Z" } ``` **Response -- failed:** ```json { "id": "exp_r1s2t3u4", "type": "identities", "format": "csv", "status": "failed", "error": "Date range too large. Maximum range is 90 days.", "created_at": "2026-04-06T10:00:00Z" } ``` ## Complete export flow (TypeScript) ```typescript const headers = { "Authorization": "Bearer aid_your_api_key", "X-Org-Id": "org_abc123", "X-Project-Id": "proj_def456", }; // Create the export const createRes = await fetch("https://api.aidenid.com/v1/exports", { method: "POST", headers: { ...headers, "Content-Type": "application/json", "Idempotency-Key": "export-002" }, body: JSON.stringify({ type: "extractions", format: "json" }), }); const { id } = await createRes.json(); // Poll until ready let downloadUrl: string | undefined; for (let i = 0; i < 30; i++) { const statusRes = await fetch(`https://api.aidenid.com/v1/exports/${id}`, { headers }); const data = await statusRes.json(); if (data.status === "ready") { downloadUrl = data.download_url; break; } if (data.status === "failed") { throw new Error(`Export failed: ${data.error}`); } await new Promise((r) => setTimeout(r, 5000)); } if (downloadUrl) { console.log("Download:", downloadUrl); } ``` ## Export statuses | Status | Description | |--------|-------------| | `pending` | Export job is queued and has not started processing | | `processing` | Export is actively being generated | | `ready` | Export is complete and the download URL is available | | `failed` | Export failed — check the `error` field for details | **Download expiration:** Download URLs are signed and expire after 1 hour. If the URL expires, query the export status endpoint again to get a fresh URL. ## Exports error codes | Code | Status | Description | |------|--------|-------------| | `export_not_found` | 404 | Export job does not exist | | `invalid_export_type` | 400 | Type must be one of: identities, events, extractions | | `invalid_format` | 400 | Format must be one of: json, csv | | `date_range_too_large` | 400 | Date range exceeds the maximum of 90 days | | `export_quota_exceeded` | 429 | Too many concurrent export jobs | | `missing_idempotency_key` | 400 | Mutation request missing Idempotency-Key header | --- # Marketplace API Browse and deploy pre-built agent templates. Templates are pre-configured identity provisioning and extraction patterns for common services — deploy one to get a working identity pipeline in seconds. **What are agent templates?** An agent template packages a complete identity workflow for a specific service or use case. For example, the "GitHub Signup" template knows which email patterns GitHub uses for verification, the expected OTP format, and the optimal polling interval. When you deploy a template, AIdenID creates a configured identity with extraction rules tuned for that service. ## List templates ### GET /v1/marketplace/templates List available agent templates. Filter by category or use case. **Query parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `category` | string | No | Filter by category: `social`, `developer`, `ecommerce`, `finance`, `productivity` | | `use_case` | string | No | Filter by use case: `signup`, `password_reset`, `two_factor`, `invite` | | `limit` | integer | No | Max results per page (default: 20, max: 100) | | `offset` | integer | No | Pagination offset | ``` curl "https://api.aidenid.com/v1/marketplace/templates?category=developer&use_case=signup" \ -H "Authorization: Bearer aid_your_api_key" \ -H "X-Org-Id: org_abc123" \ -H "X-Project-Id: proj_def456" ``` **Response 200 OK:** ```json { "items": [ { "id": "tmpl_github_signup", "name": "GitHub Signup", "description": "Automated GitHub account signup with email verification", "category": "developer", "use_case": "signup", "version": "2.1.0", "parameters": [ { "name": "username", "type": "string", "required": true, "description": "Desired GitHub username" } ], "estimated_duration_seconds": 30, "success_rate": 0.97 }, { "id": "tmpl_gitlab_signup", "name": "GitLab Signup", "description": "Automated GitLab account signup with OTP verification", "category": "developer", "use_case": "signup", "version": "1.3.0", "parameters": [ { "name": "username", "type": "string", "required": true, "description": "Desired GitLab username" } ], "estimated_duration_seconds": 25, "success_rate": 0.95 } ], "total": 2, "limit": 20, "offset": 0 } ``` ## Get template details ### GET /v1/marketplace/templates/{id} Retrieve full details for a template, including all configuration parameters and extraction rules. ``` curl "https://api.aidenid.com/v1/marketplace/templates/tmpl_github_signup" \ -H "Authorization: Bearer aid_your_api_key" \ -H "X-Org-Id: org_abc123" \ -H "X-Project-Id: proj_def456" ``` **Response 200 OK:** ```json { "id": "tmpl_github_signup", "name": "GitHub Signup", "description": "Automated GitHub account signup with email verification", "category": "developer", "use_case": "signup", "version": "2.1.0", "parameters": [ { "name": "username", "type": "string", "required": true, "description": "Desired GitHub username" }, { "name": "ttl_hours", "type": "integer", "required": false, "default": 1, "description": "Identity TTL in hours" } ], "extraction_config": { "sender_pattern": "noreply@github.com", "subject_pattern": ".*verification.*", "extraction_type": "otp", "code_pattern": "\\d{6}", "poll_interval_seconds": 3, "max_wait_seconds": 120 }, "estimated_duration_seconds": 30, "success_rate": 0.97, "deploy_count": 12840 } ``` ## Deploy a template ### POST /v1/marketplace/templates/{id}/deploy Deploy a template as a configured identity pipeline. **Request body:** | Field | Type | Required | Description | |-------|------|----------|-------------| | `parameters` | object | Yes | Configuration parameters as defined by the template | ``` curl -X POST https://api.aidenid.com/v1/marketplace/templates/tmpl_github_signup/deploy \ -H "Authorization: Bearer aid_your_api_key" \ -H "X-Org-Id: org_abc123" \ -H "X-Project-Id: proj_def456" \ -H "Idempotency-Key: " \ -H "Content-Type: application/json" \ -d '{ "parameters": { "username": "my-agent-bot" } }' ``` **Response 201 Created:** ```json { "deployment_id": "dep_v1w2x3y4", "template_id": "tmpl_github_signup", "identity": { "id": "ident_z5a6b7c8", "email": "z5a6b7c8@inbox.aidenid.com", "status": "provisioned", "expires_at": "2026-04-06T11:00:00Z" }, "extraction_config": { "sender_pattern": "noreply@github.com", "extraction_type": "otp", "poll_interval_seconds": 3, "max_wait_seconds": 120 }, "status": "active", "estimated_duration_seconds": 30, "created_at": "2026-04-06T10:00:00Z" } ``` **After deployment:** Use the returned `identity.email` for your signup flow. The extraction rules from the template are automatically applied — poll the identity extraction endpoint or subscribe to the realtime stream. ## Marketplace error codes | Code | Status | Description | |------|--------|-------------| | `template_not_found` | 404 | Template does not exist | | `invalid_parameters` | 400 | Missing or invalid template configuration parameters | | `quota_exceeded` | 403 | Active identity limit reached for your plan | | `template_deprecated` | 410 | This template version has been deprecated — use a newer version | | `missing_idempotency_key` | 400 | Mutation request missing Idempotency-Key header | --- # Core Concepts Before diving into the API, it helps to understand the key abstractions that AIdenID is built on. ## Identities An **identity** is the fundamental resource in AIdenID. It represents a disposable email inbox provisioned on demand. Each identity has: - A unique ID (e.g., `ident_a1b2c3d4e5`) - A unique email address (e.g., `a1b2c3d4e5@inbox.aidenid.com`) - A human-readable label - A lifecycle status - A time-to-live (TTL) after which it expires - Optional metadata for your own tracking Identities are always scoped to a **project**, which is itself scoped to an **organization**. This hierarchy provides isolation between different teams, environments, or use cases. ## Inboxes Every identity gets a dedicated inbox capable of receiving email. When an email arrives at the identity's address, AIdenID processes it through the extraction pipeline to detect and extract authentication codes. Inboxes are active for the duration of the identity's lifecycle. Once squashed or expired, the inbox is deactivated and no longer accepts email. ## Extraction **Extraction** is the process of detecting and pulling authentication codes or links from inbound emails. AIdenID supports: - **OTP codes** — Numeric or alphanumeric one-time passwords - **Magic links** — Unique URLs for passwordless authentication - **Confirmation links** — Email verification URLs Each extraction includes a **confidence score** (0.0 to 1.0) indicating how certain the system is about the extracted value. Production extractions typically have confidence scores above 0.95. ## Events Every meaningful action in AIdenID produces an **event**. Events are the basis for webhook delivery and audit trails. Key event types include: - `email.received` — An email arrived at an identity inbox - `extraction.completed` — A code or link was extracted - `identity.provisioned` — A new identity was created - `identity.extended` — An identity TTL was extended - `identity.squashed` — An identity was revoked - `identity.expired` — An identity TTL elapsed ## Lifecycle Identities follow a defined lifecycle with clear state transitions. The five lifecycle states are: 1. **Provisioned** — Created, inbox active 2. **Active** — Receiving and processing email 3. **Extended** — TTL extended beyond original duration 4. **Squashed** — Manually revoked 5. **Expired** — TTL elapsed naturally ## Projects and organizations AIdenID uses a two-level hierarchy for resource isolation: - **Organization** — The top-level entity. Represents your company or team. Contains one or more projects. - **Project** — A namespace within an organization. Identities, webhooks, and other resources are scoped to a project. Use separate projects for staging vs. production, or for different product lines. ## API keys API keys authenticate requests and determine access scope. Each key is bound to an organization and can be restricted to specific scopes (e.g., `identities:read`, `webhooks:write`). ## Idempotency All mutation endpoints require an `Idempotency-Key` header. This ensures that retrying a failed request does not create duplicate resources. AIdenID stores the result of the original request and returns it on subsequent calls with the same idempotency key. ## Webhooks Webhooks allow you to receive real-time notifications when events occur. Instead of polling the API, register an HTTPS endpoint and AIdenID will push events to you. Payloads are signed with HMAC-SHA256 so you can verify authenticity. ## Two audiences: agents and humans | Aspect | AI Agents | Human Consumers | |--------|-----------|-----------------| | **Auth** | API key + X-Org-Id + X-Project-Id | Magic link (passwordless email) | | **Identity creation** | REST API, bulk operations | Web UI, up to tier limit | | **OTP delivery** | API polling or webhooks | Web inbox (SSE) + email forwarding | | **Lifecycle** | Full CRUD: extend, squash, children | Create and delete only | | **Free tier** | IP-keyed, 1 identity, 24h, no signup | 3 emails, 7-day TTL, 3 OTP deliveries | ## Trials All paid agent plans include a 7-day free trial with full access to plan features. Trials are scoped per organization — each org gets one trial per plan. During the trial, identities and webhooks work exactly as they do on a paid subscription. ## Child identities Some flows require multiple email addresses. AIdenID supports **child identities** — subordinate identities linked to a parent. Child identities share the parent's project and can be squashed individually or in bulk through the parent. --- # Identity Lifecycle Every identity in AIdenID follows a defined lifecycle. Understanding these states and transitions is essential for building reliable agent workflows. ## Lifecycle states | State | Inbox active | Description | |-------|-------------|-------------| | `provisioned` | Yes | Identity created. Inbox is active and ready to receive email. Initial state after calling `POST /v1/identities`. | | `active` | Yes | Identity has received at least one email. Extraction is actively processing inbound messages. | | `extended` | Yes | Identity TTL has been extended beyond the original duration. The identity continues to function normally with a new expiration time. | | `squashed` | No | Identity has been manually revoked via `POST /v1/identities/:id/squash`. The inbox is deactivated and extraction is halted. This is irreversible. | | `expired` | No | The TTL has elapsed without extension. The inbox is deactivated. The identity can no longer be extended. | ## State transitions ``` provisioned --> active (first email received) provisioned --> extended (TTL extended before first email) provisioned --> squashed (manually revoked) provisioned --> expired (TTL elapsed) active --> extended (TTL extended) active --> squashed (manually revoked) active --> expired (TTL elapsed) extended --> squashed (manually revoked) extended --> expired (TTL elapsed) ``` **Terminal states:** `squashed` and `expired` are terminal states. Once an identity reaches either state, it cannot be reactivated or extended. Create a new identity if you need a fresh inbox. ## TTL policies Every identity has a time-to-live (TTL) that determines when it expires. The maximum TTL depends on your plan: | Plan | Max TTL | |------|---------| | Starter | 30 days (720 hours) | | Growth | 90 days (2,160 hours) | | Pro | 180 days (4,320 hours) | | Enterprise | 365 days (8,760 hours) | If you do not specify a `ttl_hours` value, the default is 24 hours. ## Extending identities You can extend an identity before it expires. This pushes the expiration time forward by the specified number of hours. The total lifetime (original TTL plus extensions) cannot exceed your plan's maximum TTL. ``` curl -X POST https://api.aidenid.com/v1/identities/ident_a1b2c3d4e5/extend \ -H "Authorization: Bearer aid_your_api_key" \ -H "X-Org-Id: org_abc123" \ -H "X-Project-Id: proj_def456" \ -H "Idempotency-Key: " \ -H "Content-Type: application/json" \ -d '{ "additional_hours": 48 }' ``` ## Squashing identities Squashing is the act of immediately revoking an identity. When you squash an identity: - The inbox is deactivated — no new email will be accepted - Any in-flight extraction is halted - The identity transitions to `squashed` status - Event history is retained for audit purposes - This action is **irreversible** Always squash identities when you are done with them. This is a security best practice that minimizes the window during which an inbox is active. ## Child identity lifecycle Child identities have their own independent lifecycle, but they are linked to a parent. When you squash a parent identity, you can optionally squash all children by calling `DELETE /v1/identities/:id/children`. Child identities default to the same TTL as the parent but can be created with a shorter TTL. They cannot outlive the parent. ## Expiration behavior When an identity's TTL elapses, the lifecycle engine automatically transitions it to `expired` status. This happens asynchronously — there may be a brief delay (typically under 60 seconds) between the TTL elapsed time and the actual state transition. If you need deterministic cleanup, prefer squashing identities explicitly rather than relying on TTL expiration. ## Trial identity lifecycle During a free trial, identities follow the same lifecycle states as paid identities but are subject to trial-specific constraints: - Identities created during a trial are bound to the trial period. When the trial expires, all trial identities transition to `expired` status. - Converting to a paid plan preserves active trial identities — they continue functioning under the paid plan's quota. - Trial quotas match the target plan (e.g., a Growth trial allows up to 50 active identities). ## Consumer identity lifecycle Human consumer identities have a simplified lifecycle: - **Creation:** Consumers create disposable emails from the web inbox. Each email is an identity with a tier-limited TTL. - **Active period:** Inbound emails trigger OTP extraction. Extracted codes appear in the web inbox via SSE and are forwarded to the consumer's personal email. - **Expiration:** Consumer identities expire at the end of their TTL (7 days free, 30 days Personal). There is no extension mechanism for consumer identities. - **Deletion:** Consumers can delete (squash) identities early from the web inbox. ## Best practices - **Use short TTLs.** Set TTLs to the minimum needed for your flow. A signup verification typically completes within minutes — a 1-hour TTL is usually sufficient. - **Squash when done.** Do not leave identities active longer than necessary. Squash them as soon as the flow completes. - **Use metadata for tracking.** Attach agent names, flow types, or correlation IDs to identity metadata for easier debugging and audit. - **Monitor with webhooks.** Subscribe to `identity.expired` and `identity.squashed` events to track lifecycle transitions in real time. --- # Human vs Agent AIdenID serves both AI agents building automated workflows and human consumers who need disposable email. The underlying infrastructure is the same, but the experience differs. ## Two audiences, one platform AIdenID was built API-first for AI agents, but the same identity provisioning, inbox, and extraction infrastructure powers a consumer web experience for humans. Whether you are an agent orchestrating signup flows programmatically or a person who needs a throwaway email address for a one-time signup, AIdenID handles both through the same core platform. ## Authentication Agents and humans authenticate differently based on their interaction model: - **Agents (paid)** — Use API keys via the `Authorization: Bearer` header, combined with `X-Org-Id` and `X-Project-Id` headers to scope requests to the correct project. - **Agents (free tier)** — No authentication required. Access is IP-keyed with ephemeral tokens. One active identity per IP. - **Humans** — Use magic-link email authentication (passwordless). Sessions are cookie-based with automatic refresh. No passwords to manage. ## Identity management Agents create and manage identities via the REST API programmatically, often provisioning identities in bulk for automated workflows. Humans use the web inbox UI to create and manage a smaller number of identities — typically 3 to 10, depending on their plan. ## OTP delivery Both audiences receive OTPs through the same extraction pipeline, but the delivery surface differs: - **Agents** — Receive extracted OTPs via API polling, webhooks, or SSE streams. Fully programmatic, no human in the loop. - **Humans** — See OTPs in their web inbox with real-time SSE updates. Optionally receive email notifications forwarded to their personal email address. ## Lifecycle - **Agent identities** — Ephemeral tools with TTLs ranging from 24 hours to 365 days. Created and destroyed automatically as part of agent workflows. Squashed programmatically when the flow completes. - **Human identities** — Tend to live longer (7 to 30 days) and are managed manually through the inbox UI. Users extend or delete identities as needed. ## Rate limits | Tier | Requests/min | |------|-------------| | Free agent (no auth) | 10 | | Free human trial | 30 | | Starter | 60 | | Growth | 300 | | Pro | 1,000 | ## Use cases | Agent use cases | Human use cases | |----------------|-----------------| | Signup testing and QA automation | Disposable email for signups | | Automated account creation | Privacy protection | | Auth flow automation and monitoring | One-time verifications | | Competitive monitoring | Spam avoidance | **Shared infrastructure:** Under the hood, both agents and humans use the same identity provisioning, email ingress, extraction pipeline, and lifecycle engine. The difference is in the interface — API vs web UI — and the usage patterns that each audience brings. --- # Abuse Prevention AIdenID is designed to be developer-friendly while preventing abuse. Multiple layers of protection ensure fair usage across every tier. ## Rate limiting Per-plan rate limits are enforced at the API gateway level. Every response includes rate-limit headers: | Header | Description | |--------|-------------| | `X-RateLimit-Limit` | Maximum requests allowed per window | | `X-RateLimit-Remaining` | Requests remaining in the current window | | `X-RateLimit-Reset` | Unix timestamp when the window resets | When you exceed the limit, the API returns a `429 Too Many Requests` response with `retryable: true`. Implement exponential backoff and respect the `X-RateLimit-Reset` header. | Plan | Requests/min | Burst | |------|-------------|-------| | Free agent | 10 | 3 | | Free human trial | 30 | 5 | | Starter | 60 | 10 | | Growth | 300 | 50 | | Pro | 1,000 | 100 | | Enterprise | Custom | Custom | ## IP-based controls The free tier is keyed to the caller's IP address. Each IP is limited to one active identity at a time. This prevents bulk identity creation without authentication. ## Identity quotas Each plan enforces a maximum number of active identities. When you reach the limit, the API returns a `403` with the `quota_exceeded` error code. You must squash or wait for existing identities to expire before creating new ones. Bulk creation operations also have plan-specific batch size limits. For example, Starter plans can create up to 5 identities per batch call, while Pro plans support up to 50. ## OTP budget Human trial accounts are limited to 3 total OTP deliveries across all identities. This prevents trial accounts from being used for large-scale verification abuse. Once the OTP budget is exhausted, the account must upgrade to a paid plan. ## Disposable email detection When a consumer signs up for an AIdenID account, the signup flow blocks known disposable email providers. This prevents recursive abuse — using a disposable email address to sign up for AIdenID to generate more disposable emails. Only real, non-disposable email addresses are accepted. ## Trial protections - **One trial per organization email.** Each unique email address can only start one trial. - **Auto-downgrade on expiry.** When a trial expires, the organization is automatically downgraded. - **Excess identity squashing.** On downgrade, if the account has more active identities than the free tier allows, excess identities are automatically squashed (newest first). ## Domain freezing Platform administrators can freeze domains flagged for abuse or deliverability issues. When a domain is frozen: - New identity creation on that domain is blocked - Existing identities on the domain stop accepting inbound email - Downstream traffic (webhook deliveries, SSE streams) for identities on the frozen domain is paused Frozen domains can be unfrozen once the underlying issue is resolved. ## Legal holds AIdenID implements a fail-closed legal hold system. When a legal hold is placed on an identity: - Identity data is preserved and cannot be squashed or deleted, even if the TTL expires - The lifecycle engine skips held identities during automated expiration sweeps - Held identities are excluded from bulk squash operations - Only platform administrators with legal-hold permissions can place or remove holds **Fail-closed design:** Legal holds are fail-closed: if there is any ambiguity about whether a hold applies, the system errs on the side of preservation. Data is never destroyed while a hold is in effect. --- # Integrations AIdenID provides multiple integration patterns to fit your architecture. ## REST API The primary integration method. Full programmatic access to all platform features. - Base URL: `https://api.aidenid.com` - Format: JSON request and response bodies - Auth: Bearer token via `Authorization` header - Idempotency: Required for all mutations via `Idempotency-Key` header ## Webhooks Register HTTPS endpoints to receive real-time event notifications. Webhooks eliminate the need for polling and enable event-driven architectures. All payloads are signed with HMAC-SHA256. ## MCP Server The AIdenID MCP (Model Context Protocol) server allows LLMs to use AIdenID tools directly through tool calling. This is the fastest way to give an AI agent access to identity provisioning and extraction. ## Server-Sent Events (SSE) Connect to a real-time event stream for live updates. SSE is used by the agent realtime API and the consumer web inbox. Events include identity changes, extraction completions, and inbound email arrivals. ## Consumer web inbox Human consumers interact with AIdenID through a web-based inbox that uses SSE for real-time updates. No API integration is needed. ## Integration patterns ### Polling pattern The simplest integration. Create an identity, use the email in a flow, then poll the extraction endpoint until a result is available. ``` # Pseudocode identity = create_identity(label="signup") use_email_in_signup_flow(identity.email) MAX_ATTEMPTS = 30 # ~60s total wait for attempt in range(MAX_ATTEMPTS): extraction = get_latest_extraction(identity.id) if extraction: send_secret_to_secure_runtime(extraction) break sleep(2) else: raise TimeoutError("Extraction not received within timeout") squash_identity(identity.id) ``` ### Webhook-driven pattern Register a webhook for `extraction.completed` events. No polling required. ``` # 1. Register webhook (one-time setup) POST /v1/webhooks { "url": "https://your-server.com/hooks/aidenid", "events": ["extraction.completed"] } # 2. Create identity and use it POST /v1/identities use_email_in_signup_flow(identity.email) # 3. Your webhook handler receives the extraction # POST https://your-server.com/hooks/aidenid # { "type": "extraction.completed", "data": { ... } } ``` ### MCP tool-calling pattern Configure the AIdenID MCP server in your LLM environment. The LLM can then directly call AIdenID tools. ```json // LLM tool call { "tool": "create_identity", "arguments": { "label": "signup-agent", "ttl_hours": 1 } } // LLM receives identity with email address // LLM uses email in signup flow // LLM calls get_latest_extraction to retrieve OTP ``` ## Language support | Language | HTTP client | |----------|-------------| | Python | `requests`, `httpx`, `aiohttp` | | TypeScript/JavaScript | `fetch`, `axios`, `undici` | | Go | `net/http` | | Rust | `reqwest` | | curl | Built-in CLI | --- # MCP Server Integration The AIdenID MCP (Model Context Protocol) server allows LLMs to directly provision identities, check extractions, and manage lifecycle through tool calling. ## What is MCP? The **Model Context Protocol** is a standard for giving LLMs access to external tools. When you configure an MCP server, the LLM can call AIdenID functions as part of its reasoning loop — no custom integration code required. ## Setup ### Claude Desktop / Claude Code ```json { "mcpServers": { "aidenid": { "command": "npx", "args": ["-y", "@aidenid/mcp-server"], "env": { "AIDENID_API_KEY": "aid_your_api_key", "AIDENID_ORG_ID": "org_abc123", "AIDENID_PROJECT_ID": "proj_def456" } } } } ``` ### Environment variables | Variable | Required | Description | |----------|----------|-------------| | `AIDENID_API_KEY` | Yes | Your AIdenID API key | | `AIDENID_ORG_ID` | Yes | Your organization identifier | | `AIDENID_PROJECT_ID` | Yes | The project to scope operations to | | `AIDENID_API_BASE` | No | API base URL (defaults to `https://api.aidenid.com`) | ## Available tools ### create_identity Provision a new disposable email identity. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `label` | string | Yes | Human-readable label | | `ttl_hours` | integer | No | Time-to-live in hours (default: 24) | | `domain` | string | No | Custom domain for the inbox | ### get_identity Retrieve identity details including status, email, and metadata. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `identity_id` | string | Yes | The identity ID to retrieve | ### list_identities List identities in the current project with optional filtering. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `status` | string | No | Filter by status | | `limit` | integer | No | Max results (default: 20) | ### get_latest_extraction Get the most recent extraction result for an identity. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `identity_id` | string | Yes | The identity to check for extractions | ### extend_identity Extend the TTL of an active identity. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `identity_id` | string | Yes | The identity to extend | | `additional_hours` | integer | Yes | Hours to add | ### squash_identity Revoke an identity and deactivate its inbox. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `identity_id` | string | Yes | The identity to squash | ### create_webhook Register a webhook endpoint for event delivery. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `url` | string | Yes | HTTPS endpoint URL | | `events` | string[] | Yes | Event types to subscribe to | ### test_auth_flow Run an auth flow reliability test against a target URL. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `flow_type` | string | Yes | `password_reset` or `invite` | | `target_url` | string | Yes | URL to test | | `identity_id` | string | Yes | Identity to use for the test | ## Example conversation ``` User: Sign up for a new account on example.com using a disposable email. LLM: I'll create a disposable identity and use it to sign up. [Tool call: create_identity] label: "example-signup" ttl_hours: 1 [Tool result] id: "ident_a1b2c3d4e5" email: "a1b2c3d4e5@inbox.aidenid.com" LLM: I've created the identity. Let me use the email to sign up... Now I'll check whether extraction evidence is ready. [Tool call: get_latest_extraction] identity_id: "ident_a1b2c3d4e5" [Tool result] type: "otp" has_secret: true redacted_value: "***" confidence: 0.99 LLM: Extraction evidence is ready. The raw secret must stay in the secure server-side runtime path. Let me clean up the identity. [Tool call: squash_identity] identity_id: "ident_a1b2c3d4e5" ``` ## Security considerations - **API key access.** The MCP server has full access to your API key scopes. Use a key with minimal permissions. - **Environment isolation.** Use a separate project for MCP-driven operations so they do not interfere with production identities. - **TTL hygiene.** Set short TTLs when using MCP tools to minimize active identity window. --- # Webhook Integration Guide Receive real-time event notifications from AIdenID. This guide covers setup, signature verification, event handling, and best practices. ## Overview Webhooks push events to your HTTPS endpoints as they happen. Instead of polling the API, your server receives a POST request with the event payload whenever something occurs — an email arrives, a code is extracted, or an identity changes state. ## Step 1: Create an endpoint Set up an HTTPS endpoint on your server that accepts POST requests. The endpoint must return a `2xx` status code to acknowledge receipt. ```javascript // Express.js example const crypto = require("crypto"); const express = require("express"); const app = express(); app.post("/webhooks/aidenid", express.raw({ type: "application/json", limit: "256kb" }), (req, res) => { const rawBody = req.body.toString("utf8"); try { verifyWebhook( rawBody, req.headers["x-signature"], req.headers["x-timestamp"], process.env.AIDENID_WEBHOOK_SECRET ); } catch (err) { return res.status(403).json({ error: { code: "invalid_webhook_signature", message: "Webhook signature verification failed.", }, }); } const event = JSON.parse(rawBody); // Deduplicate using event.id // Enqueue for async processing enqueueWebhookEvent(event); res.status(200).json({ received: true }); }); app.listen(3000); ``` ## Step 2: Register the webhook ``` curl -X POST https://api.aidenid.com/v1/webhooks \ -H "Authorization: Bearer aid_your_api_key" \ -H "X-Org-Id: org_abc123" \ -H "X-Project-Id: proj_def456" \ -H "Idempotency-Key: " \ -H "Content-Type: application/json" \ -d '{ "url": "https://your-server.com/webhooks/aidenid", "events": [ "email.received", "extraction.completed", "identity.squashed" ] }' ``` **Save the secret:** Store secret material only in backend secret management, rotate on a fixed cadence (e.g., every 30 days), and support short overlap windows. ## Step 3: Verify signatures Every webhook delivery includes two headers for verification: - `X-Signature` — HMAC-SHA256 of `{timestamp}.{payload}` - `X-Timestamp` — Unix timestamp of the delivery ### Node.js verification ```javascript const crypto = require("crypto"); function verifyWebhook(rawBody, signature, timestamp, secret) { if (!secret || typeof secret !== "string") { throw new Error("webhook_secret_not_configured"); } if (!signature || !timestamp) { throw new Error("Missing webhook signature headers"); } if (!/^[a-f0-9]{64}$/i.test(signature)) { throw new Error("Malformed webhook signature"); } if (!/^[0-9]{10}$/.test(timestamp)) { throw new Error("Malformed webhook timestamp"); } // 1. Check timestamp is recent (prevent replay attacks) const tsSec = Number(timestamp); const nowSec = Math.floor(Date.now() / 1000); const age = Math.abs(nowSec - tsSec); if (age > 300) { throw new Error("Webhook timestamp too old"); } // 2. Verify HMAC signature const signed = `${timestamp}.${rawBody}`; const expected = crypto .createHmac("sha256", secret) .update(signed) .digest("hex"); const provided = Buffer.from(signature, "hex"); const computed = Buffer.from(expected, "hex"); if (provided.length !== computed.length) { throw new Error("Invalid webhook signature"); } const valid = crypto.timingSafeEqual(provided, computed); if (!valid) { throw new Error("Invalid webhook signature"); } return true; } ``` ### Python verification ```python import hashlib import hmac import re import time def verify_webhook(payload: str, signature: str, timestamp: str, secret: str): if not secret or not isinstance(secret, str): raise ValueError("webhook_secret_not_configured") if not timestamp or not re.fullmatch(r"[0-9]{10}", timestamp): raise ValueError("Malformed webhook timestamp") if not signature or not re.fullmatch(r"[a-fA-F0-9]{64}", signature): raise ValueError("Malformed webhook signature") # Check timestamp freshness ts_sec = int(timestamp) age = abs(time.time() - ts_sec) if age > 300: raise ValueError("Webhook timestamp too old") # Verify HMAC signature signed = f"{timestamp}.{payload}" expected = hmac.new( secret.encode(), signed.encode(), hashlib.sha256 ).hexdigest() if not hmac.compare_digest(signature, expected): raise ValueError("Invalid webhook signature") ``` ## Delivery behavior - **Timeout:** AIdenID waits up to 10 seconds for your endpoint to respond. - **Retries:** Failed deliveries are retried with exponential backoff (maxAttempts=5, backoffMs=1000, maxBackoffMs=1800000). Terminal failures should move to DLQ. - **Ordering:** Events are delivered in chronological order but are not guaranteed to arrive in perfect sequence. Use the `timestamp` field for ordering if needed. - **Replay:** You can manually replay any event via the Webhooks API. ## Best practices - **Respond quickly.** Return a `200` response immediately and process the event asynchronously. - **Handle duplicates.** Use the event `id` field for deduplication. - **Verify signatures.** Always verify the HMAC signature before processing. - **Check timestamp freshness.** Reject events with timestamps older than 5 minutes. - **Rotate secrets periodically.** Use the `/rotate-secret` endpoint to generate new signing secrets. ## Testing locally ``` # Using ngrok ngrok http 3000 # Register the ngrok URL as your webhook endpoint # https://abc123.ngrok.io/webhooks/aidenid ``` --- # Guides Step-by-step guides for getting the most out of AIdenID. Each guide walks through a specific workflow or feature in detail. ## Available guides - **Dashboard Guide** — Learn how to use the AIdenID dashboard to manage identities, monitor events, and configure your project. --- # Dashboard Guide The dashboard is your control center for managing identities, monitoring events, and configuring your project. This guide walks through every panel and its capabilities. ## Overview The AIdenID dashboard is organized into panels, each focused on a specific capability. You can access the dashboard after logging in at aidenid.com/login. The sidebar navigation gives you quick access to every panel. ## Identity Provisioning The provisioning panel lets you create new disposable email identities directly from the dashboard. Fill in: - **Label** — A human-readable name for the identity (e.g., "github-signup-test") - **Custom domain** (optional) — Select a verified domain to use instead of the default `inbox.aidenid.com` - **TTL in hours** — How long the identity should remain active before expiring - **Metadata** (optional) — Key-value pairs for your own tracking (e.g., agent name, flow type, correlation ID) The identity gets a unique inbox address immediately after creation. ## Identity Lifecycle The lifecycle panel shows all your identities with their current status: - **Provisioned** — Created, not yet received mail - **Active** — Has received inbound email - **Extended** — TTL was extended beyond original duration - **Squashed** — Manually revoked - **Expired** — TTL has elapsed Filter by status to find specific identities. Click any identity to see its full event history, extraction results, and metadata. From the detail view you can extend the TTL, squash the identity, or copy the inbox address. ## Inbound Inbox The inbound inbox is a real-time feed of email events arriving at your identities. Each event shows: - Sender address and subject line - Which identity received the email - Extraction results (OTP, magic link, or confirmation link) - Extraction confidence score Events appear via SSE (Server-Sent Events) — no page refresh needed. ## Auth Flow Testing The auth flow testing panel lets you test signup, password-reset, and invite flows against target URLs. The system: 1. Provisions a temporary identity 2. Submits the identity email to the target service 3. Monitors the inbox for verification emails 4. Extracts and reports the result Results include: - **Success/failure rate** — Did the target service send a verification email? - **Time to receive OTP** — How long between form submission and email arrival - **Extraction confidence** — How certain the system is about the extracted value ## Domain Management Register custom domains so your identities use branded email addresses. The domain management panel shows: - **DNS records** — The MX and TXT records you need to configure - **Verification status** — Whether AIdenID has confirmed your DNS configuration ## Webhook Configuration Set up HTTP endpoints to receive real-time event notifications. Supported event types: - `extraction.completed` — A code or link was extracted from an email - `identity.created` — A new identity was provisioned - `identity.expired` — An identity TTL elapsed - `inbound.received` — An email arrived at an identity inbox The configuration panel also lets you: - View delivery history for each endpoint - Replay failed deliveries - Test endpoints with sample payloads ## Lineage & Budget The lineage and budget panel gives you visibility into: - **Identity budget** — Your active identity count vs your plan limit. A visual bar shows how much capacity remains. - **Lineage trees** — Visual representation of parent-child identity relationships. Useful when using child identities for multi-step verification flows. ## Settings Project configuration is managed from the settings panel: - **API keys** — Create, rotate, and revoke API keys. Each key shows its creation date, last used timestamp, and scopes. - **Webhook secrets** — Manage HMAC signing secrets for webhook payload verification. - **Default TTL** — Set a project-wide default TTL for new identities. - **Metadata templates** — Define reusable metadata key-value templates for consistent identity tagging. ## Admin The admin panel is restricted to platform owners and provides abuse and reliability operations: - **Runtime error stream** — Live feed of 5xx and 429 errors with request IDs and trace IDs - **Abuse circuit breakers** — Bulk squash operations and domain freeze/unfreeze controls - **Legal hold controls** — Place and remove legal holds on identities to prevent data destruction - **Support access grants** — Grant temporary access to support staff with ticket-bound TTLs - **Audit timeline** — Complete audit log of all admin actions - **Incident recommendations** — AI-generated recommendations based on error patterns - **Incident timeline export** — Export incident data for post-mortems and compliance **Owner only:** The admin panel is only visible to platform owners. Regular project members and API key holders do not have access to admin controls. All admin actions are logged in the audit timeline. --- # Pricing & Limits Simple, transparent pricing. Start with a free trial on any plan. Scale as your agents grow. ## Agent free tier No signup, no API key, no credit card. IP-keyed single disposable identity with 24-hour TTL. | Feature | Agent Free | |---------|-----------| | Price | Free | | Active identities | 1 | | Max TTL | 24 hours | | Authentication | None (IP-keyed) | | Delivery | Polling only | | Rate limit | 10 req/min | ## Agent paid plans | Feature | Starter | Growth | Pro | Enterprise | |---------|---------|--------|-----|------------| | **Price** | $49/mo | $199/mo | $599/mo | Custom | | **Free trial** | 7 days | 7 days | 7 days | Custom | | Active identities | 10 | 50 | 200 | Unlimited | | Custom domains | -- | 3 | 10 | Unlimited | | Max TTL | 30 days | 90 days | 180 days | 365 days | | Webhook endpoints | 2 | 10 | 50 | Unlimited | | Bulk create batch size | 5 | 20 | 50 | 200 | | Auth flow testing | -- | Yes | Yes | Yes | | Agent marketplace | -- | -- | Yes | Yes | | MCP server access | -- | -- | Yes | Yes | | Real-time subscriptions | -- | -- | Yes | Yes | | SSO / SCIM | -- | -- | -- | Yes | | Audit log exports | -- | -- | -- | Yes | | Kill switch controls | -- | -- | -- | Yes | | SLA guarantee | -- | -- | -- | Yes | | **Support** | Community | Email | Priority | Dedicated | ## Human consumer plans For individuals who want disposable email addresses for privacy, one-time signups, or spam avoidance. | Feature | Free Trial | Personal | |---------|-----------|----------| | **Price** | Free | $29/mo | | Disposable emails | 3 | 10 | | Email TTL | 7 days | 30 days | | OTP deliveries | 3 total | 100/mo | | Authentication | Magic link (passwordless) | Magic link (passwordless) | | Web inbox | Yes | Yes | | SSE real-time updates | Yes | Yes | | Email forwarding | Yes | Yes | ## Rate limits API requests are rate-limited per project with a burst allowance for short spikes. | Plan | Requests/min | Burst | |------|-------------|-------| | Agent Free | 10 | 3 | | Starter | 60 | 10 | | Growth | 300 | 50 | | Pro | 1,000 | 100 | | Enterprise | Custom | Custom | Rate limit information is included in every API response: | Header | Description | |--------|-------------| | `X-RateLimit-Limit` | Maximum requests per window | | `X-RateLimit-Remaining` | Requests remaining in the current window | | `X-RateLimit-Reset` | Unix timestamp when the window resets | ## Handling rate limits When you exceed the rate limit, the API returns a `429` status code with `retryable: true`. Implement exponential backoff: ```python import time import requests def api_request(method, url, max_retries=3, **kwargs): for attempt in range(max_retries): resp = requests.request(method, url, **kwargs) if resp.status_code == 429: wait = 2 ** attempt # 1s, 2s, 4s time.sleep(wait) continue return resp raise Exception("Rate limit exceeded after retries") ``` ## Quota enforcement When you reach a resource quota, the API returns a `403` with the `quota_exceeded` error code. You must either: - Squash existing identities to free up capacity - Wait for identities to expire naturally - Upgrade to a higher plan ## Free trial All plans include a free trial period. During the trial, you have full access to all features of the selected plan. No credit card required to start. ## Enterprise Enterprise plans include custom rate limits, unlimited resources, SSO, audit log exports, kill switch controls, SLA guarantees, and dedicated support. Contact sales for pricing. --- ## Links - Website: https://aidenid.com - Free Tier: https://aidenid.com/free - Consumer Signup: https://aidenid.com/consumer/signup - Documentation: https://aidenid.com/docs - API Reference: https://aidenid.com/docs/api-reference - Free Tier Docs: https://aidenid.com/docs/api-reference/free-tier - Consumer Docs: https://aidenid.com/docs/api-reference/consumer - Trials Docs: https://aidenid.com/docs/api-reference/trials - Quickstart: https://aidenid.com/docs/quickstart - Human vs Agent: https://aidenid.com/docs/concepts/human-vs-agent - Dashboard Guide: https://aidenid.com/docs/guides/dashboard - Sitemap: https://aidenid.com/sitemap.xml - Robots: https://aidenid.com/robots.txt ## Agent Retrieval Order For LLMs and AI agents seeking to understand or integrate with AIdenID: 1. Read this file (`llms.txt`) for a complete reference — all documentation is inline above 2. Visit `/docs/quickstart` for a step-by-step integration guide 3. Visit `/docs/api-reference` for endpoint-level documentation in web format 4. Visit `/docs/concepts/human-vs-agent` for audience-specific guidance 5. Visit `/docs/concepts` for architectural understanding