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

# API login programmatico: token di accesso headless

> POST /programmatic/login/ di Didit: restituisce direttamente i token di accesso, senza 2FA. Per agenti, pronto per MCP, a consumo.



Prompt per il login programmatico
        Copia il prompt negli appunti



      
      

\<pre>

\`Exchange a verified Didit account's email + password for a fresh JWT access token (headless, no browser).

Endpoint:
POST https://apx.didit.me/auth/v2/programmatic/login/

Authentication:
None. Email + password is the credential.

Request body:
\- email (string, required) — Same email used at /programmatic/register/.
\- password (string, required) — Plain password (HTTPS only).

Account requirements:
The account must have a verified email and password. Accounts created through POST /programmatic/register/ work immediately after email verification. Console-created email/password accounts can also use this endpoint. OAuth-only console accounts must set a password first.

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

Response (200 OK):
\- access_token (string) — RS256 JWT. Pass as "Authorization: Bearer <access_token>" on every Account Management endpoint at apx.didit.me/auth/v2. DO NOT use against verification.didit.me — that host uses x-api-key.
\- refresh_token (string) — RS256 JWT for refresh flows.
\- expires_in (integer) — Default 86400 (24 hours).

Token shape:
\- 24-hour default lifetime.
\- RS256-signed.
\- No browser session is required.

Brute-force protection:
\- Progressive account lockout per email: 5 consecutive failures = 15 min lockout, 10 = 1 hour, 20 = 24 hours.
\- Per-IP rate limit: 20 attempts/minute and 100 attempts/hour.

Failure modes:
\- 400 — { "email": ["..."] } / { "password": ["..."] } field-level errors.
\- 401 — { "detail": "Invalid email or password." }
\- 403 — { "detail": "..." } when SSO policy blocks password login for the account.
\- 429 — { "detail": "Too many login attempts.", "wait": <seconds> } on lockout or per-IP limit. Honour the wait value before retrying.

Recovering your api_key:
After a successful login, call GET /organizations/me/ to enumerate organizations and applications, then GET /organizations/me/{org_id}/applications/{app_id}/ to read the api_key — see /auth-api/get-credentials.

Where to use access_token vs api_key:
\- access_token (Bearer) → https://apx.didit.me/auth/v2/... (org / app management).
\- api_key (x-api-key) → https://verification.didit.me/v3/... (sessions, workflows, billing).

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

\</pre>



      
      

Incolla in



## OpenAPI

````yaml it/openapi-auth.json POST /programmatic/login/
openapi: 3.0.0
info:
  version: 1.0.0
  title: Didit Auth API
  description: >-
    API di registrazione e autenticazione programmatica per sviluppatori e
    agenti AI. Registrati in 2 chiamate API -- senza bisogno di browser. La
    piattaforma di verifica dell'identità più adatta agli agenti.


    ## Base URL


    Tutti gli endpoint di questa specifica sono ospitati su
    `https://apx.didit.me/auth/v2`. Si tratta di un host diverso rispetto a
    quello dell'API di verifica (`https://verification.didit.me/v3`).
    Distinguili chiaramente: questo host di autenticazione rilascia i tuoi
    **token di accesso JWT** e ti consente di elencare e gestire le tue
    **organizzazioni** e le tue **applicazioni**. L'host di verifica è il luogo
    in cui chiami sessioni, workflow, AML, ecc., autenticandoti con la
    **`api_key`** restituita da questa API.


    ## Come funziona il flusso di autenticazione


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

    2. Ricevi un codice alfanumerico di 6 caratteri via email.

    3. `POST /programmatic/verify-email/` — invia il codice. Ricevi in risposta
    un `access_token` JWT, un `refresh_token`, un'organizzazione e
    un'applicazione predefinita con `client_id` + `api_key`.

    4. Salva la `api_key`. Usala come `x-api-key` per ogni chiamata a
    `https://verification.didit.me/v3/...`.

    5. Da qualsiasi macchina in futuro, `POST /programmatic/login/` scambia
    email + password con un nuovo JWT, quindi chiama `GET /organizations/me/` e
    gli endpoint dell'applicazione per recuperare o ruotare le credenziali.


    Il JWT serve solo per gli endpoint di **Gestione dell'account** in questa
    specifica; il traffico dell'API di verifica utilizza la `api_key` a lunga
    durata, non il JWT.


    ## Convenzioni


    - Tutti i corpi di richiesta e risposta sono in formato JSON (`Content-Type:
    application/json`).

    - **Usa un indirizzo email reale e recapitabile quando testi `POST
    /programmatic/register/`.** L'endpoint invia il codice di verifica in modo
    sincrono, quindi gli indirizzi su domini di test riservati (`@example.com`,
    `@example.org`, `@*.test`, `@*.example`, `@*.invalid`) vengono rifiutati
    dalla consegna della posta e la richiesta restituisce `500`. Sostituisci il
    segnaposto `you@yourdomain.com` negli esempi con una casella di posta che
    puoi effettivamente consultare.

    - Gli errori di validazione restituiscono HTTP `400` con una delle due
    strutture: `{"field": ["message", ...]}` per problemi a livello di campo,
    oppure `{"detail": "message"}` per errori legati a regole aziendali. Alcuni
    validatori (robustezza della password in fase di registrazione, scadenza del
    codice su verify-email) attualmente rispondono con un array semplice
    `["message"]` anziché con il formato indicizzato per campo: si tratta di
    un'incoerenza nota lato server, pertanto il codice client dovrebbe accettare
    entrambi i formati.

    - Gli errori di autenticazione vengono restituiti come `{"detail": "..."}`
    con HTTP `401` (token bearer non valido) o `403` (token bearer mancante /
    autorizzazioni insufficienti).

    - Le risposte relative al limite di frequenza includono un campo `wait` con
    il tempo di attesa in secondi.
servers:
  - url: https://apx.didit.me/auth/v2
    description: Server di autenticazione di produzione
security: []
tags:
  - name: Programmatic Auth
    description: >-
      Ciclo di vita dell'account basato su email e password: registrazione,
      verifica, accesso. Restituisce il JWT `access_token` utilizzato per gli
      endpoint di gestione dell'account, oltre a (in fase di verifica)
      `client_id` e `api_key` della tua prima applicazione.
  - name: Account Management
    description: >-
      Endpoint autenticati (bearer JWT) che elencano le organizzazioni e creano,
      recuperano o aggiornano le applicazioni. Usali per recuperare o ruotare la
      `api_key` con cui autentichi le chiamate API di verifica.
paths:
  /programmatic/login/:
    post:
      tags:
        - Programmatic Auth
      summary: Accesso programmatico
      description: >-
        Autentica qualsiasi account Didit con email/password verificata e ricevi
        una coppia JWT RS256 (durata predefinita 86400s). Gli account
        email/password creati dalla console possono utilizzare questo endpoint;
        gli account della console solo OAuth devono prima impostare una
        password.
      operationId: programmatic_login
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LoginRequest'
            examples:
              Default:
                summary: Accesso programmatico
                value:
                  email: you@yourdomain.com
                  password: MyStr0ng!Pass
      responses:
        '200':
          description: >-
            Accesso riuscito. Restituisce una nuova coppia JWT. Usa
            l'`access_token` come `Authorization: Bearer <token>` verso
            `/organizations/me/...` per recuperare `org_id`, `app_id` e
            `api_key`. Quando l'account dispone di un'app di autenticazione o di
            una passkey, la risposta contiene `2fa_required: true` e un
            `temp_token` invece dei token.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LoginResponse'
              examples:
                Success:
                  value:
                    access_token: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
                    refresh_token: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
                    expires_in: 86400
                    message: Login successful
        '400':
          description: >-
            Errore di validazione. Problemi a livello di campo relativi al corpo
            della richiesta.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                MissingFields:
                  summary: Corpo vuoto
                  value:
                    email:
                      - This field is required.
                    password:
                      - This field is required.
                InvalidEmail:
                  summary: Email non valida
                  value:
                    email:
                      - Enter a valid email address.
        '401':
          description: Autenticazione rifiutata.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthError'
              examples:
                InvalidCredentials:
                  summary: >-
                    Email + password non corrispondono (restituito anche quando
                    l'account non esiste, per evitare l'enumerazione degli
                    utenti)
                  value:
                    detail: Invalid email or password.
                NotVerified:
                  summary: L'account esiste ma l'email non è ancora verificata
                  value:
                    detail: Please verify your email address before logging in.
                SSORequired:
                  summary: L'account appartiene a un'organizzazione che richiede l'SSO
                  value:
                    detail:
                      error: sso_required
                      message: Your organization 'Acme Corp' requires SSO login.
                      organization_name: Acme Corp
                      organization_slug: acme-corp
                      sso_url: /sso/acme-corp
        '403':
          description: >-
            L'account è stato recuperato e deve configurare un nuovo metodo di
            accesso prima di poter accedere. Un link per farlo è stato inviato
            via email all'indirizzo dell'account. Un account la cui
            organizzazione impone l'SSO riceve un `403` con `error:
            sso_required`, `organization_name`, `organization_slug` e `sso_url`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecoverySetupRequiredError'
              examples:
                RecoverySetupRequired:
                  summary: Account recuperato senza ancora alcun metodo di accesso
                  value:
                    recovery_setup_required: true
                    setup_link_sent: true
                    message: >-
                      You recovered your account. Open the link we emailed you
                      to set up a new sign-in method.
                    code: recovery_setup_required
        '429':
          description: >-
            Account bloccato (troppi tentativi falliti recenti su questa email)
            o limite di frequenza IP raggiunto (troppi tentativi da questo
            indirizzo). Viene restituito anche, con un'intestazione
            `Retry-After: 5`, quando il servizio sta verificando momentaneamente
            quante più password o codici possibili contemporaneamente; riprova
            dopo il numero di secondi indicato.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RateLimitError'
              examples:
                AccountLocked:
                  summary: Blocco progressivo attivato
                  value:
                    detail: >-
                      Account temporarily locked due to too many failed login
                      attempts. Please try again later.
                IPRateLimited:
                  summary: Più di 20/min o 100/ora da questo indirizzo IP
                  value:
                    detail: >-
                      Too many login attempts from this IP address. Please wait
                      before trying again.
                PasswordChecksBusy:
                  summary: Troppe verifiche della password in corso
                  value:
                    detail: >-
                      Too many sign-in attempts are being processed right now.
                      Please try again in a few seconds.
      security:
        - {}
      x-codeSamples:
        - lang: curl
          label: cURL
          source: |-
            curl -X POST https://apx.didit.me/auth/v2/programmatic/login/ \
              -H "Content-Type: application/json" \
              -d '{
                "email": "you@yourdomain.com",
                "password": "MyStr0ng!Pass"
              }'
        - lang: python
          label: Python
          source: |-
            from didit import Didit

            # This call needs no API key.
            client = Didit()

            result = client.account.login(
                email="you@yourdomain.com",
                password="MyStr0ng!Pass",
            )
            print(result)
        - lang: javascript
          label: Node.js
          source: |-
            import Didit from "@didit-protocol/sdk-node";

            // This call needs no API key.
            const didit = new Didit();

            const result = await didit.account.login({
              email: "you@yourdomain.com",
              password: "MyStr0ng!Pass",
            });
            console.log(result);
        - lang: go
          label: Go
          source: "ctx := context.Background()\n// This call needs no API key.\nclient := didit.NewClient()\n\nresult, err := client.Account.Login(ctx, didit.LoginRequest{\n\tEmail:    \"you@yourdomain.com\",\n\tPassword: \"MyStr0ng!Pass\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)"
        - lang: java
          label: Java
          source: |-
            import me.didit.Didit;
            import me.didit.types.LoginRequest;

            // This call needs no API key.
            Didit client = new Didit();

            var result = client.account().login(LoginRequest.builder()
                    .email("you@yourdomain.com")
                    .password("MyStr0ng!Pass")
                    .build());
            System.out.println(result);
        - lang: php
          label: PHP
          source: |-
            <?php

            require 'vendor/autoload.php';

            use Didit\DiditClient;

            // This call needs no API key.
            $didit = new DiditClient();

            $result = $didit->account->login([
                'email' => 'you@yourdomain.com',
                'password' => 'MyStr0ng!Pass',
            ]);
            print_r($result->toArray());
        - lang: ruby
          label: Ruby
          source: |-
            require "didit"

            # This call needs no API key.
            didit = Didit::Client.new

            result = didit.account.login(
              email: "you@yourdomain.com",
              password: "MyStr0ng!Pass"
            )
            puts result.to_h
        - lang: csharp
          label: .NET
          source: |-
            using System;
            using System.Text.Json;
            using Didit;

            // This call needs no API key.
            using var client = new DiditClient();

            var result = await client.Account.LoginAsync(new LoginRequest
            {
                Email = "you@yourdomain.com",
                Password = "MyStr0ng!Pass",
            });
            Console.WriteLine(JsonSerializer.Serialize(result));
components:
  schemas:
    LoginRequest:
      type: object
      required:
        - email
        - password
      properties:
        email:
          type: string
          format: email
          example: you@yourdomain.com
        password:
          type: string
          example: MyStr0ng!Pass
    LoginResponse:
      type: object
      properties:
        access_token:
          type: string
          description: >-
            JWT firmato con RS256. Usa come `Authorization: Bearer <token>` per
            gli endpoint di Account Management.
        refresh_token:
          type: string
        expires_in:
          type: integer
          description: 'Durata del token di accesso in secondi (valore predefinito: 86400).'
          example: 86400
        message:
          type: string
          example: Login successful
    ValidationError:
      description: >-
        Corpo dell'errore di validazione. La busta prevista è una mappa `{field:
        ["message", ...]}` per errori a livello di campo o una busta a chiave
        singola `{detail: "..."}` per errori sulle regole di business. Alcuni
        validatori (complessità della password alla registrazione, validità del
        codice in verify-email) attualmente rispondono con un array semplice
        `["message"]` -- incoerenza nota lato server. Lo schema seguente
        mantiene quel ramo con array semplice in modo che il codice client
        generato da questa specifica deserializzi le risposte correnti, ma il
        nuovo codice client dovrebbe trattarlo come la forma con chiave di campo
        pertinente (`{password: [...]}` o `{code: [...]}`) e il contratto del
        server convergerà su tale formato.
      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.
    AuthError:
      type: object
      description: Contenitore di errori di autenticazione / autorizzazione / non trovato.
      properties:
        detail:
          type: string
          example: Invalid access token
    RecoverySetupRequiredError:
      type: object
      description: >-
        L'account è stato recuperato e non ha ancora un metodo di accesso
        configurato. Non viene emesso alcun token; un link monouso per
        configurare un nuovo metodo è stato inviato via email all'indirizzo
        dell'account. Aprilo, configura il metodo e quindi effettua nuovamente
        l'accesso.
      required:
        - recovery_setup_required
        - setup_link_sent
        - message
        - code
      properties:
        recovery_setup_required:
          type: boolean
          example: true
        setup_link_sent:
          type: boolean
          example: true
        message:
          type: string
          example: >-
            You recovered your account. Open the link we emailed you to set up a
            new sign-in method.
        code:
          type: string
          enum:
            - recovery_setup_required
          example: recovery_setup_required
    RateLimitError:
      type: object
      description: >-
        Risposta di limite di frequenza o blocco progressivo. `wait` è il tempo
        di attesa in secondi.
      properties:
        detail:
          type: string
          example: >-
            Too many login attempts from this IP address. Please wait before
            trying again.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.