> ## Documentation Index
> Fetch the complete documentation index at: https://docs.didit.me/llms.txt
> Use this file to discover all available pages before exploring further.

# Programmatic Registration for AI Agents

> Register and get Didit API credentials in 2 API calls — no browser, no 2FA. The most agent-native identity verification platform for Cursor, Claude, Devin.

## Overview

Didit is the **most agent-friendly identity verification platform**. AI coding agents (Cursor, Claude Code, GitHub Copilot, Devin, OpenHands, Codex) can register, configure workflows, and start verifying identities — all programmatically, without ever opening a browser.

**Why agents love Didit:**

* **2 API calls** from zero to credentials (register + verify-email).
* **No browser required** — fully headless, perfect for CI/CD and agent workflows.
* **No 2FA friction** for API accounts — tokens are returned immediately after email verification.
* **Auto-provisioned** organization and application with `api_key` in the verify response.
* **Full management API** — configure workflows, questionnaires, lists, billing, all via API.
* **MCP server** available — agents can discover and use Didit tools natively.

The full contract is published as [openapi-auth.json](/openapi-auth.json); the endpoints below are audited against it.

## Base URL

All endpoints in this guide live on the **auth host**:

```
https://apx.didit.me/auth/v2
```

This is a different host from the verification API (`https://verification.didit.me/v3`). The auth host issues JWT access tokens and manages applications. The verification host runs sessions, AML, workflows, etc., authenticated with the long-lived `api_key` you get from this flow.

## Quick start

<Warning>
  **Use a real, deliverable email address.** The endpoint sends the verification code synchronously, so reserved test domains (`@example.com`, `@example.org`, `@*.test`, `@*.example`, `@*.invalid`) get rejected by mail delivery and the call returns a `500`. Substitute the `you@yourdomain.com` placeholder below with an inbox you can read.
</Warning>

### Step 1: Register

```bash theme={null}
curl -X POST https://apx.didit.me/auth/v2/programmatic/register/ \
  -H "Content-Type: application/json" \
  -d '{"email": "you@yourdomain.com", "password": "MyStr0ng!Pass"}'
```

**Response** (`201 Created`):

```json theme={null}
{
  "message": "Registration successful. Check your email for the verification code.",
  "email": "you@yourdomain.com"
}
```

A 6-character alphanumeric verification code (e.g. `A3K9F2`) is emailed to the address. **The code expires 10 minutes after it is sent** — if it lapses, call this endpoint again with the same email and password: for a still-unverified account it returns `200` with `"A new verification code has been sent to your email."` and a fresh code. A dedicated `POST /programmatic/resend-otp/` endpoint (body: `{"email", "password"}`) also re-sends the code, with a 60-second cooldown between sends; both routes share the registration rate limit below.

### Step 2: Verify the code and get credentials

```bash theme={null}
curl -X POST https://apx.didit.me/auth/v2/programmatic/verify-email/ \
  -H "Content-Type: application/json" \
  -d '{"email": "you@yourdomain.com", "code": "A3K9F2"}'
```

**Response** (`200 OK`):

```json theme={null}
{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expires_in": 86400,
  "organization": {
    "uuid": "a1b2c3d4-5678-90ab-cdef-111111111111",
    "name": "developer"
  },
  "application": {
    "uuid": "b2c3d4e5-6789-01bc-defg-222222222222",
    "name": "developer",
    "client_id": "S9LIYGSoWNuGMLHsvEt9dQ",
    "api_key": "05mHcOWL8GathLZlz8oIDawYj9qFAcoSHtz-75PAkuo"
  }
}
```

You now have everything you need. Persist `application.api_key` securely and send it as the `x-api-key` header on every verification API call.

<Note>
  Tokens are **RS256-signed JWTs**. `expires_in` defaults to **86400 seconds (24 hours)**. The access token is only needed for the management endpoints on `apx.didit.me/auth/v2` — verification API traffic on `verification.didit.me/v3` is authenticated with the long-lived `api_key`, not the JWT.
</Note>

### Step 3: Use the API

```bash theme={null}
curl https://verification.didit.me/v3/sessions/ \
  -H "x-api-key: 05mHcOWL8GathLZlz8oIDawYj9qFAcoSHtz-75PAkuo"
```

## Subsequent logins

From any future machine, exchange your email and password for a fresh JWT:

```bash theme={null}
curl -X POST https://apx.didit.me/auth/v2/programmatic/login/ \
  -H "Content-Type: application/json" \
  -d '{"email": "you@yourdomain.com", "password": "MyStr0ng!Pass"}'
```

Returns `access_token` + `refresh_token` directly — no browser. From there, hit `GET /organizations/me/` to discover your `org_id` (the organization's `uuid`), then `GET /organizations/me/{org_id}/applications/` to list the organization's applications (each entry's `uuid` is an `app_id`), and finally `GET /organizations/me/{org_id}/applications/{app_id}/` to recover the `api_key`.

<Note>
  Console-created email/password accounts can also use `POST /programmatic/login/` after the email is verified. OAuth-only console accounts need to set a password before using this headless login endpoint.
</Note>

## Password requirements

Enforced server-side, **one rule at a time** — the first failure short-circuits with a bare-array error like `["Password must contain at least one uppercase letter."]`. (Other validation errors on this endpoint use the field-keyed `{"password": [...]}` envelope. The bare-array shape on password-strength rules is a known server-side inconsistency; treat it as `{"password": [...]}` in client code.)

| Rule              | Detail                                        |
| ----------------- | --------------------------------------------- |
| Minimum length    | 8 characters                                  |
| Uppercase letter  | At least one `A–Z`                            |
| Lowercase letter  | At least one `a–z`                            |
| Digit             | At least one `0–9`                            |
| Special character | At least one of `!@#$%^&*()_+-=[]{}\|;:,.<>?` |

## Rate limits and lockouts

Three independent controls protect the auth host. Any of them can trigger a `429` response; the `Retry-After` response header tells you how long to back off.

| Surface       | Limit                                                                                                                                      |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| Registration  | **5** attempts per **IP** per **hour** (shared with `POST /programmatic/resend-otp/`).                                                     |
| Verify-email  | **10** code attempts per **email** per **hour**, plus exponential backoff between attempts (30 s, doubling per failure, capped at 1 hour). |
| Login (IP)    | **20** attempts per **IP** per **minute**, and **100** per **IP** per **hour**.                                                            |
| Login (email) | Progressive lockout on the account: **5** consecutive failures → 15 min lockout; **10** → 1 hour; **20** → 24 hours.                       |

A `429` body looks like this:

```json theme={null}
{ "detail": "Too many login attempts from this IP address. Please wait before trying again." }
```

Verification API rate limits (e.g. 600 sessions/min) are independent and documented in [Rate limiting](/integration/rate-limiting).

## Managing applications

Once authenticated, you can retrieve, create, or update applications inside your organization.

This is a perfect fit for **resellers** that want one application per customer. Each application has its own `client_id`, `api_key`, metadata, and settings. It is also useful when your organization has different products, brands, regions, environments, or use cases you want to keep separate.

```bash theme={null}
# 1. Discover your org_id
curl https://apx.didit.me/auth/v2/organizations/me/ \
  -H "Authorization: Bearer $ACCESS_TOKEN"

# 2. List the organization's applications (each entry's uuid is an app_id)
curl "https://apx.didit.me/auth/v2/organizations/me/$ORG_ID/applications/" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

# 3. Create an application for a customer, product, or environment
curl -X POST "https://apx.didit.me/auth/v2/organizations/me/$ORG_ID/applications/" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Customer App",
    "website_url": "https://acme.example",
    "redirect_uris": ["https://acme.example/callback"],
    "terms_url": "https://acme.example/terms",
    "privacy_url": "https://acme.example/privacy"
  }'

# 4. Recover credentials (client_id + api_key) for an existing application
curl "https://apx.didit.me/auth/v2/organizations/me/$ORG_ID/applications/$APP_ID/" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

# 5. Update application metadata (PATCH — uuid, client_id, api_key never change)
curl -X PATCH "https://apx.didit.me/auth/v2/organizations/me/$ORG_ID/applications/$APP_ID/" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Production App",
    "website_url": "https://app.acme.example"
  }'
```

Listing, creating, or updating applications requires the user to be an **owner** or **admin** of the target organization; otherwise the response is `403`.

## What can agents do after registration?

With the `api_key`, agents have full access to the verification API. Audited paths on `https://verification.didit.me/v3`:

| API                              | What it does                                                   |
| -------------------------------- | -------------------------------------------------------------- |
| `POST /v3/session/`              | Create a verification session.                                 |
| `GET /v3/sessions/`              | List sessions.                                                 |
| `GET /v3/session/{id}/decision/` | Get the V3 decision (plural arrays — see Data models).         |
| `GET /v3/workflows/`             | List verification workflows.                                   |
| `GET /v3/questionnaires/`        | List custom questionnaires.                                    |
| `GET /v3/users/`                 | List end users (indexed by `vendor_data`).                     |
| `GET /v3/billing/balance/`       | Check credit balance.                                          |
| `POST /v3/billing/top-up/`       | Top up credits.                                                |
| `GET /v3/webhook/destinations/`  | Manage webhook destinations (list / create / update / delete). |

See the [Management API reference](/sessions-api/management-api) for the full surface.

## MCP server integration

For the best agent experience, use the Didit MCP server. Most coding agents (Cursor, Claude Code, Windsurf, etc.) accept this MCP server entry:

```json theme={null}
{
  "mcpServers": {
    "didit": {
      "command": "npx",
      "args": ["@didit-protocol/mcp-server"],
      "env": { "DIDIT_API_KEY": "your_api_key" }
    }
  }
}
```

See the [AI agent integration guide](/integration/ai-agent-integration) for details.

## Error envelope reference

All `apx.didit.me/auth/v2` endpoints share the same three response shapes (see `openapi-auth.json`):

* **Field-level validation** — `{"field": ["message", ...]}` with HTTP `400`.
* **Form-level validation** (notably password strength and verification-code errors) — bare array `["Invalid or expired verification code."]` with HTTP `400`.
* **Authentication / business rule** — `{"detail": "..."}` with `400`, `401`, `403`, `404`, or `429`. `429` responses carry a `Retry-After` response header telling you how long to back off (the body has only `detail`).

The verification host returns errors as `{"detail": "..."}` and 429s include `X-RateLimit-*` and `Retry-After` — see [Rate limiting](/integration/rate-limiting).
