> ## 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 Programmatik: Token Autentikasi Headless

> POST /programmatic/login/ untuk autentikasi headless. Mengembalikan access token langsung tanpa 2FA. Siap agen, siap MCP, harga pay-per-use di Didit.



Prompt Login Terprogram
        Salin prompt ke papan klip



      
      

\<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>



      
      

Tempel ke



## OpenAPI

````yaml id/openapi-auth.json POST /programmatic/login/
openapi: 3.0.0
info:
  version: 1.0.0
  title: Didit Auth API
  description: >-
    API pendaftaran dan autentikasi terprogram untuk pengembang dan agen AI.
    Daftar dalam 2 panggilan API -- tanpa perlu browser. Platform verifikasi
    identitas yang paling ramah agen.


    ## URL Dasar


    Semua endpoint dalam spesifikasi ini di-hosting di
    `https://apx.didit.me/auth/v2`. Ini adalah host yang berbeda dari API
    verifikasi (`https://verification.didit.me/v3`). Perhatikan perbedaannya:
    host autentikasi ini menerbitkan **token akses JWT** Anda dan memungkinkan
    Anda mencantumkan serta mengelola **organisasi** dan **aplikasi** Anda. Host
    verifikasi adalah tempat Anda memanggil sesi, alur kerja, AML, dll., yang
    diautentikasi dengan **`api_key`** yang dikembalikan oleh API ini.


    ## Cara kerja alur autentikasi


    1. `POST /programmatic/register/` — kirimkan email + kata sandi.

    2. Terima kode alfanumerik 6 karakter melalui email.

    3. `POST /programmatic/verify-email/` — kirimkan kode tersebut. Anda akan
    mendapatkan kembali JWT `access_token`, `refresh_token`, sebuah organisasi,
    dan aplikasi default dengan `client_id` + `api_key`.

    4. Simpan `api_key`. Gunakan sebagai `x-api-key` untuk setiap panggilan ke
    `https://verification.didit.me/v3/...`.

    5. Dari mesin mana pun di masa mendatang, `POST /programmatic/login/`
    menukarkan email + kata sandi Anda dengan JWT baru, lalu panggil `GET
    /organizations/me/` dan endpoint aplikasi untuk memulihkan atau merotasi
    kredensial.


    JWT hanya diperlukan untuk endpoint **Manajemen Akun** dalam spesifikasi
    ini; lalu lintas API verifikasi menggunakan `api_key` yang berjangka
    panjang, bukan JWT.


    ## Konvensi


    - Semua body permintaan dan respons berupa JSON (`Content-Type:
    application/json`).

    - **Gunakan alamat email asli yang dapat menerima pesan saat menguji `POST
    /programmatic/register/`.** Endpoint ini mengirimkan kode verifikasi secara
    sinkron, sehingga alamat pada domain pengujian yang dicadangkan
    (`@example.com`, `@example.org`, `@*.test`, `@*.example`, `@*.invalid`)
    ditolak oleh pengiriman email dan permintaan mengembalikan `500`. Ganti
    placeholder `you@yourdomain.com` dalam contoh dengan kotak masuk yang
    benar-benar dapat Anda baca.

    - Error validasi mengembalikan HTTP `400` dengan salah satu dari dua format:
    `{"field": ["message", ...]}` untuk masalah tingkat kolom, atau `{"detail":
    "message"}` untuk error aturan bisnis. Sejumlah kecil validator (kekuatan
    kata sandi saat mendaftar, kedaluwarsa kode saat verifikasi email) saat ini
    merespons dengan array sederhana `["message"]` alih-alih bentuk dengan kunci
    kolom -- ini adalah inkonsistensi sisi server yang diketahui, sehingga kode
    klien harus menerima kedua bentuk tersebut.

    - Error autentikasi kembali sebagai `{"detail": "..."}` dengan HTTP `401`
    (bearer token tidak valid) atau `403` (bearer token tidak ada / hak istimewa
    tidak memadai).

    - Respons batas laju menyertakan kolom `wait` dengan durasi cooldown dalam
    detik.
servers:
  - url: https://apx.didit.me/auth/v2
    description: Server autentikasi produksi
security: []
tags:
  - name: Programmatic Auth
    description: >-
      Siklus hidup akun dengan email dan kata sandi: daftar, verifikasi, masuk.
      Mengembalikan `access_token` JWT yang Anda gunakan untuk endpoint
      Manajemen Akun, ditambah (saat verifikasi) `client_id` dan `api_key`
      aplikasi pertama Anda.
  - name: Account Management
    description: >-
      Endpoint terautentikasi (JWT bearer) yang mencantumkan organisasi dan
      membuat, mengambil, atau memperbarui aplikasi. Gunakan endpoint ini untuk
      memulihkan atau merotasi `api_key` yang Anda gunakan untuk mengautentikasi
      panggilan API verifikasi.
paths:
  /programmatic/login/:
    post:
      tags:
        - Programmatic Auth
      summary: Login Terprogram
      description: >-
        Autentikasi akun Didit dengan email/kata sandi terverifikasi apa pun dan
        terima pasangan JWT RS256 (masa berlaku default 86400 detik). Akun
        email/kata sandi yang dibuat di konsol dapat menggunakan endpoint ini;
        akun konsol khusus OAuth harus menetapkan kata sandi terlebih dahulu.
      operationId: programmatic_login
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LoginRequest'
            examples:
              Default:
                summary: Login terprogram
                value:
                  email: you@yourdomain.com
                  password: MyStr0ng!Pass
      responses:
        '200':
          description: >-
            Login berhasil. Mengembalikan pasangan JWT baru. Gunakan
            `access_token` sebagai `Authorization: Bearer <token>` ke
            `/organizations/me/...` untuk melihat `org_id`, `app_id`, dan
            `api_key`. Jika akun memiliki aplikasi autentikator atau passkey,
            respons akan memuat `2fa_required: true` dan `temp_token` alih-alih
            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: Kesalahan validasi. Masalah tingkat kolom pada isi permintaan.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                MissingFields:
                  summary: Isi kosong
                  value:
                    email:
                      - This field is required.
                    password:
                      - This field is required.
                InvalidEmail:
                  summary: Email salah format
                  value:
                    email:
                      - Enter a valid email address.
        '401':
          description: Autentikasi ditolak.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthError'
              examples:
                InvalidCredentials:
                  summary: >-
                    Email + kata sandi tidak cocok (juga dikembalikan saat akun
                    tidak ada, untuk menghindari enumerasi pengguna)
                  value:
                    detail: Invalid email or password.
                NotVerified:
                  summary: Akun ada tetapi email belum diverifikasi
                  value:
                    detail: Please verify your email address before logging in.
                SSORequired:
                  summary: Akun tersebut milik organisasi yang mewajibkan 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: >-
            Akun berhasil dipulihkan dan harus mengatur metode masuk baru
            sebelum dapat masuk. Tautan untuk melakukannya telah dikirim melalui
            email ke alamat akun. Akun yang organisasinya menerapkan SSO
            mendapatkan `403` dengan `error: sso_required`, `organization_name`,
            `organization_slug`, dan `sso_url`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecoverySetupRequiredError'
              examples:
                RecoverySetupRequired:
                  summary: Akun yang dipulihkan dan belum memiliki metode masuk
                  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: >-
            Akun terkunci (terlalu banyak kegagalan baru-baru ini pada email
            ini) atau IP terkena pembatasan laju (terlalu banyak upaya dari
            alamat ini). Juga dikembalikan, dengan header `Retry-After: 5`,
            ketika layanan sedang memeriksa sebanyak mungkin kata sandi atau
            kode sekaligus untuk sesaat; coba lagi setelah jumlah detik yang
            ditentukan.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RateLimitError'
              examples:
                AccountLocked:
                  summary: Penguncian bertahap terpicu
                  value:
                    detail: >-
                      Account temporarily locked due to too many failed login
                      attempts. Please try again later.
                IPRateLimited:
                  summary: Lebih dari 20/menit atau 100/jam dari alamat IP ini
                  value:
                    detail: >-
                      Too many login attempts from this IP address. Please wait
                      before trying again.
                PasswordChecksBusy:
                  summary: >-
                    Terlalu banyak pemeriksaan kata sandi yang sedang
                    berlangsung
                  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 bertanda tangan RS256. Gunakan sebagai `Authorization: Bearer
            <token>` untuk endpoint Manajemen Akun.
        refresh_token:
          type: string
        expires_in:
          type: integer
          description: Masa berlaku token akses dalam detik (default 86400).
          example: 86400
        message:
          type: string
          example: Login successful
    ValidationError:
      description: >-
        Isi kesalahan validasi. Envelope yang diharapkan adalah pemetaan
        `{field: ["message", ...]}` untuk kesalahan tingkat kolom atau envelope
        kunci tunggal `{detail: "..."}` untuk kesalahan aturan bisnis. Sejumlah
        kecil validator (kekuatan kata sandi saat mendaftar, validitas kode pada
        verifikasi email) saat ini merespons dengan array polos `["message"]` --
        inkonsistensi sisi server yang sudah diketahui. Skema di bawah ini
        mempertahankan cabang array polos tersebut sehingga kode klien yang
        dihasilkan dari spesifikasi ini mendeserialisasi respons saat ini, namun
        kode klien baru harus memperlakukannya sebagai bentuk dengan kunci kolom
        yang relevan (`{password: [...]}` atau `{code: [...]}`) dan kontrak
        server akan disesuaikan ke bentuk tersebut.
      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: Struktur respons error autentikasi / otorisasi / tidak ditemukan.
      properties:
        detail:
          type: string
          example: Invalid access token
    RecoverySetupRequiredError:
      type: object
      description: >-
        Akun berhasil dipulihkan dan belum memiliki metode masuk yang diatur.
        Tidak ada token yang diterbitkan; tautan sekali pakai untuk mengatur
        metode baru telah dikirim melalui email ke alamat akun. Buka tautan
        tersebut, atur metodenya, lalu masuk kembali.
      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: >-
        Respons batas laju atau penguncian progresif. `wait` adalah waktu tunggu
        dalam hitungan detik.
      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.