> ## 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.

# Verify Email & Get Credentials

> Step 2 of onboarding. Exchanges the 6-char code for a verified account, a default org+app, and a JWT pair. Persist the returned `api_key`.

export const AgentPromptAccordion = ({prompt, title = "AI Agent Integration Prompt"}) => {
  const [copied, setCopied] = React.useState(false);
  const handleCopy = e => {
    e.stopPropagation();
    if (!prompt) return;
    navigator.clipboard.writeText(prompt.trim()).then(() => {
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    });
  };
  const agents = ["Claude Code", "Codex", "Cursor", "Devin", "Windsurf", "GitHub Copilot"];
  return <div className="didit-agent-card">
      {}
      <div className="didit-agent-titlebar">
        <div className="didit-agent-dots" aria-hidden="true">
          <span className="didit-agent-dot didit-agent-dot-red"></span>
          <span className="didit-agent-dot didit-agent-dot-yellow"></span>
          <span className="didit-agent-dot didit-agent-dot-green"></span>
        </div>
        <span className="didit-agent-filename">{title}</span>
        <button type="button" className={`didit-agent-copy ${copied ? "didit-agent-copy-copied" : ""}`} onClick={handleCopy} title="Copy prompt to clipboard" aria-label={copied ? "Copied!" : "Copy prompt to clipboard"}>
          {copied ? <>
              <svg width="13" height="13" viewBox="0 0 16 16" fill="none">
                <path d="M3 8.5l3.5 3.5L13 4" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
              </svg>
              <span>Copied</span>
            </> : <>
              <svg width="13" height="13" viewBox="0 0 16 16" fill="none">
                <rect x="5" y="5" width="9" height="9" rx="1.5" stroke="currentColor" strokeWidth="1.5" />
                <path d="M11 5V3.5A1.5 1.5 0 0 0 9.5 2h-6A1.5 1.5 0 0 0 2 3.5v6A1.5 1.5 0 0 0 3.5 11H5" stroke="currentColor" strokeWidth="1.5" />
              </svg>
              <span>Copy</span>
            </>}
        </button>
      </div>

      {}
      <pre className="didit-agent-body"><code>{prompt.trim()}</code></pre>

      {}
      <div className="didit-agent-footer">
        <span className="didit-agent-footer-label">Paste into</span>
        <div className="didit-agent-chips">
          {agents.map(name => <span key={name} className="didit-agent-chip">{name}</span>)}
        </div>
      </div>
    </div>;
};

<AgentPromptAccordion
  title="Verify Email & Get Credentials Prompt"
  prompt={`Exchange the email verification code for usable Didit credentials — step 2 of programmatic onboarding.

Endpoint:
POST https://apx.didit.me/auth/v2/programmatic/verify-email/

Authentication:
None. The email + code pair is the credential.

Request body:
- email (string, required) — Same email used in /programmatic/register/.
- code (string, required) — 6-character alphanumeric code from the registration email (e.g. "A3K9F2"). Valid for 15 minutes.

curl example:
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):
{
"access_token": "eyJ...RS256 JWT",
"refresh_token": "eyJ...RS256 JWT",
"expires_in": 86400,
"organization": {
"uuid": "a1b2c3d4-5678-...",
"name": "you"
},
"application": {
"uuid": "b2c3d4e5-6789-...",
"name": "you",
"client_id": "S9LIYGSoWNuGMLHsvEt9dQ",
"api_key": "05mHcOWL8GathLZlz8oIDawYj9qFAcoSHtz-75PAkuo"
}
}

What happens server-side:
1. The pending account is marked email-verified.
2. A default organization is created (named after the local part of the email, e.g. "you" from "you@yourdomain.com").
3. A default application is created inside the organization with a fresh client_id and api_key.
4. JWTs are issued (RS256, default 24h / 86400 seconds lifetime).

How to use the response:
- application.api_key → set as the x-api-key header on EVERY call to https://verification.didit.me/v3/... (sessions, workflows, questionnaires, billing, etc.). PERSIST IT SECURELY — no other endpoint shows the api_key unprefixed.
- access_token → use as "Authorization: Bearer <access_token>" against Account Management endpoints on https://apx.didit.me/auth/v2 (e.g. to create more applications).
- refresh_token → exchange for a new access_token after expiry.

Save the api_key, the organization.uuid (org_id), and the application.uuid (app_id) — you will need all three to manage applications later.

Failure modes (400 unless noted):
- Field-level: { "email": ["Enter a valid email address."] } or { "code": ["Ensure this field has at least 6 characters."] }.
- Wrong or expired code CURRENTLY returns a BARE ARRAY ["Invalid or expired verification code."] — known server-side inconsistency; treat as { "code": [...] } client-side.
- Unknown email: { "detail": "User not found." }

Recovery if you lose the api_key later:
Call POST /programmatic/login/ with email + password to get a fresh access_token, then GET /organizations/me/{org_id}/applications/{app_id}/ to read the api_key back. See /auth-api/login and /auth-api/get-credentials.

For end-to-end Didit integration, paste in the full prompt at /integration/integration-prompt.`}
/>


## OpenAPI

````yaml POST /programmatic/verify-email/
openapi: 3.0.0
info:
  version: 1.0.0
  title: Didit Auth API
  description: >-
    Programmatic registration and authentication API for developers and AI
    agents. Register in 2 API calls -- no browser needed. The most
    agent-friendly identity verification platform.


    ## Base URL


    All endpoints in this spec are hosted at `https://apx.didit.me/auth/v2`.
    This is a different host than the verification API
    (`https://verification.didit.me/v3`). Keep them straight: this auth host
    issues your **JWT access tokens** and lets you list and manage your
    **organizations** and **applications**. The verification host is where you
    call sessions, workflows, AML, etc., authenticated with the **`api_key`**
    returned by this API.


    ## How the auth flow works


    1. `POST /programmatic/register/` — submit email + password.

    2. Receive a 6-character alphanumeric code by email.

    3. `POST /programmatic/verify-email/` — submit the code. You get back a JWT
    `access_token`, a `refresh_token`, an organization, and a default
    application with `client_id` + `api_key`.

    4. Save the `api_key`. Use it as `x-api-key` for every call to
    `https://verification.didit.me/v3/...`.

    5. From any future machine, `POST /programmatic/login/` exchanges your email
    + password for a fresh JWT, then call `GET /organizations/me/` and the
    application endpoints to recover or rotate credentials.


    The JWT is only needed for the **Account Management** endpoints in this
    spec; verification API traffic uses the long-lived `api_key`, not the JWT.


    ## Conventions


    - All request and response bodies are JSON (`Content-Type:
    application/json`).

    - **Use a real, deliverable email address when testing `POST
    /programmatic/register/`.** The endpoint sends the verification code
    synchronously, so addresses on reserved test domains (`@example.com`,
    `@example.org`, `@*.test`, `@*.example`, `@*.invalid`) are refused by mail
    delivery and the request returns a `500`. Substitute the
    `you@yourdomain.com` placeholder in the samples with an inbox you can
    actually read.

    - Validation errors return HTTP `400` with one of two envelopes: `{"field":
    ["message", ...]}` for field-level problems, or `{"detail": "message"}` for
    business-rule errors. A handful of validators (password strength on
    register, code expiry on verify-email) currently respond with a bare array
    `["message"]` instead of the field-keyed form -- this is a known server-side
    inconsistency, so client code should accept both shapes.

    - Authentication errors come back as `{"detail": "..."}` with HTTP `401`
    (invalid bearer token) or `403` (missing bearer token / insufficient
    privileges).

    - Rate-limit responses include a `wait` field with the cooldown in seconds.
servers:
  - url: https://apx.didit.me/auth/v2
    description: Production auth server
security: []
tags:
  - name: Programmatic Auth
    description: >-
      Email-and-password account lifecycle: register, verify, login. Returns the
      JWT `access_token` you use against the Account Management endpoints, plus
      (on verify) your first application's `client_id` and `api_key`.
  - name: Account Management
    description: >-
      Authenticated endpoints (JWT bearer) that list organizations and create,
      fetch, or update applications. Use these to recover or rotate the
      `api_key` you authenticate verification API calls with.
paths:
  /programmatic/verify-email/:
    post:
      tags:
        - Programmatic Auth
      summary: Verify Email and Issue Credentials
      description: >-
        Step 2 of onboarding. Exchanges the 6-char code for a verified account,
        a default org+app, and a JWT pair. Persist the returned `api_key`.
      operationId: programmatic_verify_email
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VerifyEmailRequest'
            examples:
              Default:
                summary: Submit the code from the registration email
                value:
                  email: you@yourdomain.com
                  code: A3K9F2
      responses:
        '200':
          description: >-
            Email verified. The response contains tokens for this auth API plus
            the brand-new application's `client_id` and `api_key`. Persist the
            `api_key` securely — it is the long-lived credential used by every
            verification API call.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VerifyEmailResponse'
              examples:
                Success:
                  value:
                    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
        '400':
          description: >-
            Validation error. The intended envelope is `{"field": ["..."]}` for
            field-level problems and `{"detail": "..."}` for business-rule
            errors (e.g. user not found). Wrong-or-expired-code errors currently
            respond with a bare array `["Invalid or expired verification
            code."]` -- known server-side inconsistency; treat it as `{"code":
            ["..."]}` until fixed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                MissingFields:
                  summary: Empty body
                  value:
                    email:
                      - This field is required.
                    code:
                      - This field is required.
                CodeTooShort:
                  summary: Code under 6 characters
                  value:
                    code:
                      - Ensure this field has at least 6 characters.
                InvalidEmail:
                  summary: Malformed email
                  value:
                    email:
                      - Enter a valid email address.
                InvalidCode:
                  summary: Wrong or expired code
                  value:
                    - Invalid or expired verification code.
                UserNotFound:
                  summary: No pending account for that email
                  value:
                    detail: User not found.
      x-codeSamples:
        - lang: curl
          label: cURL
          source: >-
            curl -X POST https://apx.didit.me/auth/v2/programmatic/verify-email/
            \
              -H "Content-Type: application/json" \
              -d '{
                "email": "you@yourdomain.com",
                "code": "A3K9F2"
              }'
        - lang: python
          label: Python
          source: |-
            import requests

            resp = requests.post(
                "https://apx.didit.me/auth/v2/programmatic/verify-email/",
                json={
                    "email": "you@yourdomain.com",
                    "code": "A3K9F2",
                },
                timeout=10,
            )
            resp.raise_for_status()
            data = resp.json()

            api_key = data["application"]["api_key"]
            print(f"Save this for x-api-key: {api_key}")
        - lang: javascript
          label: JavaScript
          source: |-
            const resp = await fetch(
              "https://apx.didit.me/auth/v2/programmatic/verify-email/",
              {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify({
                  email: "you@yourdomain.com",
                  code: "A3K9F2",
                }),
              },
            );
            if (!resp.ok) throw new Error(`Verify failed: ${resp.status}`);
            const { application, access_token } = await resp.json();
            // application.api_key -> x-api-key for verification.didit.me/v3
            // access_token -> Authorization: Bearer ... for this auth API
components:
  schemas:
    VerifyEmailRequest:
      type: object
      required:
        - email
        - code
      properties:
        email:
          type: string
          format: email
          description: Email used during `POST /programmatic/register/`.
          example: you@yourdomain.com
        code:
          type: string
          minLength: 6
          maxLength: 6
          description: >-
            6-character alphanumeric code from the verification email. Codes
            expire 10 minutes after the most recent code was sent.
          example: A3K9F2
    VerifyEmailResponse:
      type: object
      properties:
        access_token:
          type: string
          description: >-
            RS256-signed JWT. Use as `Authorization: Bearer <token>` for Account
            Management endpoints on `apx.didit.me/auth/v2`.
        refresh_token:
          type: string
          description: Refresh token paired with the access token.
        expires_in:
          type: integer
          description: Lifetime of the access token in seconds (default 86400).
          example: 86400
        organization:
          $ref: '#/components/schemas/OrganizationSummary'
        application:
          $ref: '#/components/schemas/ApplicationCredentialsSummary'
    ValidationError:
      description: >-
        Validation error body. The intended envelope is a `{field: ["message",
        ...]}` map for field-level errors or a `{detail: "..."}` single-key
        envelope for business-rule errors. A handful of validators (password
        strength on register, code validity on verify-email) currently respond
        with a bare array `["message"]` -- known server-side inconsistency. The
        schema below keeps that bare-array branch so client code generated from
        this spec deserializes today's responses, but new client code should
        treat it as the relevant field-keyed form (`{password: [...]}` or
        `{code: [...]}`) and the server contract will converge on that shape.
      oneOf:
        - type: object
          additionalProperties:
            type: array
            items:
              type: string
          example:
            email:
              - Enter a valid email address.
        - type: object
          properties:
            detail:
              type: string
          example:
            detail: An account with this email already exists.
        - type: array
          items:
            type: string
          example:
            - Invalid or expired verification code.
    OrganizationSummary:
      type: object
      description: >-
        Minimal organization shape returned inline by `POST
        /programmatic/verify-email/`.
      properties:
        uuid:
          type: string
          format: uuid
          example: a1b2c3d4-5678-90ab-cdef-111111111111
        name:
          type: string
          example: developer
    ApplicationCredentialsSummary:
      type: object
      description: >-
        Minimal application shape returned inline by `POST
        /programmatic/verify-email/`. Persist the `api_key` immediately.
      properties:
        uuid:
          type: string
          format: uuid
          example: b2c3d4e5-6789-01bc-defg-222222222222
        name:
          type: string
          example: developer
        client_id:
          type: string
          example: S9LIYGSoWNuGMLHsvEt9dQ
        api_key:
          type: string
          description: Use this as `x-api-key` on every verification API call.
          example: 05mHcOWL8GathLZlz8oIDawYj9qFAcoSHtz-75PAkuo

````