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

# Proof of Address API

> Extract and validate a Proof of Address document — utility bills, bank statements, and government-issued documents — in one call. Returns the issuer, issue/expiry dates, the holder name, the raw and parsed/geocoded address, an `Approved`/`Declined` `status`, and a `warnings` list explaining every issue found.

**Latency.** Extraction is LLM-based: typical calls take **5–15 seconds**, multi-page PDFs up to **~30 seconds**. Configure a client timeout of **at least 45 seconds** and do not retry before the call completes.

**How it works.** The document (image or PDF, up to 15 MB; encrypted PDFs require `document_password`) is classified into a document type and subtype (e.g. `UTILITY_BILL` / `WATER_BILL`), and the issuer, dates, holder name, and address are extracted with LLM-based extraction. The address is parsed and geocoded into `poa_parsed_address`. PDF/EXIF forensics screen for tampering — modification after digital signing, known PDF editors, overlay-text manipulation, suspicious re-exports — surfacing `SUSPECTED_DOCUMENT_MANIPULATION` or `DOCUMENT_METADATA_MISMATCH`. Document age is validated against `poa_document_age_months`, and the language against `poa_languages_allowed`. If you pass `expected_address`, `expected_country`, or `expected_first_name`/`expected_last_name`, the extracted values are cross-checked and mismatches are flagged.

**Decision logic.** `status` is `Approved` unless at least one warning resolves to a decline. Hard failures always decline: `MISSING_ADDRESS_INFORMATION`, `POA_DOCUMENT_EXPIRED`, `INVALID_DOCUMENT_TYPE`, `UNABLE_TO_VALIDATE_DOCUMENT_AGE`, plus `UNABLE_TO_EXTRACT_ISSUE_DATE` and `POA_NAME_NOT_DETECTED` (not configurable on this endpoint). Five risk groups are configurable per request via the `poa_*_action` options (`DECLINE` or `NO_ACTION`, all defaulting to `DECLINE`). `POA_DOCUMENT_NOT_SUPPORTED_FOR_APPLICATION` and `UNPARSABLE_OR_INVALID_ADDRESS` are informational on this endpoint and never decline. A document that cannot be processed at all returns `400`; a readable but problematic document returns `200` with `status: "Declined"` — always inspect `poa.status` and `poa.warnings`, not just the HTTP code.

**Billing.** Each `200` response consumes one Proof of Address API credit (standalone APIs have no free tier). When the organization's balance cannot cover the call, the endpoint returns `403` with the not-enough-credits error before any document processing.

**Session persistence (`save_api_request`, default `true`).** When `true`, the call is persisted as an API-type session: it appears in the Business Console, the uploaded document is stored, the returned `request_id` is a real session id you can pass to `GET /v3/session/{sessionId}/decision/` (the decision additionally exposes `document_file` and the `document_metadata` forensics, which this response omits), and a `status.updated` webhook is emitted. When `false`, nothing is stored and `request_id` is a one-off correlation UUID that cannot be looked up later.

**Sandbox.** Sandbox API keys skip all processing and billing: after request validation (malformed input still returns `400`), the endpoint returns a static `Approved` mock payload (a fictional "Sandbox Power Co." electricity bill), no session is persisted, and no credits are consumed.

**Authentication.** Send your application's API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{"detail": "You do not have permission to perform this action."}`) — this API never returns `401`.

**Rate limit.** Shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.

Note: `FUTURE_ISSUE_DATE` and `POOR_DOCUMENT_QUALITY` exist in workflow sessions but are never produced by this standalone endpoint — a future-dated document is not auto-declined here.



## OpenAPI

````yaml POST /v3/poa/
openapi: 3.0.0
info:
  version: 3.0.0
  title: Didit Verification API
  description: Identity verification API. Authenticate with x-api-key header.
servers:
  - url: https://verification.didit.me
security: []
tags: []
paths:
  /v3/poa/:
    post:
      tags:
        - Standalone APIs
      summary: Proof of Address (document OCR + address checks)
      description: >-
        Extract and validate a Proof of Address document — utility bills, bank
        statements, and government-issued documents — in one call. Returns the
        issuer, issue/expiry dates, the holder name, the raw and parsed/geocoded
        address, an `Approved`/`Declined` `status`, and a `warnings` list
        explaining every issue found.


        **Latency.** Extraction is LLM-based: typical calls take **5–15
        seconds**, multi-page PDFs up to **~30 seconds**. Configure a client
        timeout of **at least 45 seconds** and do not retry before the call
        completes.


        **How it works.** The document (image or PDF, up to 15 MB; encrypted
        PDFs require `document_password`) is classified into a document type and
        subtype (e.g. `UTILITY_BILL` / `WATER_BILL`), and the issuer, dates,
        holder name, and address are extracted with LLM-based extraction. The
        address is parsed and geocoded into `poa_parsed_address`. PDF/EXIF
        forensics screen for tampering — modification after digital signing,
        known PDF editors, overlay-text manipulation, suspicious re-exports —
        surfacing `SUSPECTED_DOCUMENT_MANIPULATION` or
        `DOCUMENT_METADATA_MISMATCH`. Document age is validated against
        `poa_document_age_months`, and the language against
        `poa_languages_allowed`. If you pass `expected_address`,
        `expected_country`, or `expected_first_name`/`expected_last_name`, the
        extracted values are cross-checked and mismatches are flagged.


        **Decision logic.** `status` is `Approved` unless at least one warning
        resolves to a decline. Hard failures always decline:
        `MISSING_ADDRESS_INFORMATION`, `POA_DOCUMENT_EXPIRED`,
        `INVALID_DOCUMENT_TYPE`, `UNABLE_TO_VALIDATE_DOCUMENT_AGE`, plus
        `UNABLE_TO_EXTRACT_ISSUE_DATE` and `POA_NAME_NOT_DETECTED` (not
        configurable on this endpoint). Five risk groups are configurable per
        request via the `poa_*_action` options (`DECLINE` or `NO_ACTION`, all
        defaulting to `DECLINE`). `POA_DOCUMENT_NOT_SUPPORTED_FOR_APPLICATION`
        and `UNPARSABLE_OR_INVALID_ADDRESS` are informational on this endpoint
        and never decline. A document that cannot be processed at all returns
        `400`; a readable but problematic document returns `200` with `status:
        "Declined"` — always inspect `poa.status` and `poa.warnings`, not just
        the HTTP code.


        **Billing.** Each `200` response consumes one Proof of Address API
        credit (standalone APIs have no free tier). When the organization's
        balance cannot cover the call, the endpoint returns `403` with the
        not-enough-credits error before any document processing.


        **Session persistence (`save_api_request`, default `true`).** When
        `true`, the call is persisted as an API-type session: it appears in the
        Business Console, the uploaded document is stored, the returned
        `request_id` is a real session id you can pass to `GET
        /v3/session/{sessionId}/decision/` (the decision additionally exposes
        `document_file` and the `document_metadata` forensics, which this
        response omits), and a `status.updated` webhook is emitted. When
        `false`, nothing is stored and `request_id` is a one-off correlation
        UUID that cannot be looked up later.


        **Sandbox.** Sandbox API keys skip all processing and billing: after
        request validation (malformed input still returns `400`), the endpoint
        returns a static `Approved` mock payload (a fictional "Sandbox Power
        Co." electricity bill), no session is persisted, and no credits are
        consumed.


        **Authentication.** Send your application's API key in the `x-api-key`
        header. Missing or invalid credentials return `403` (`{"detail": "You do
        not have permission to perform this action."}`) — this API never returns
        `401`.


        **Rate limit.** Shared write budget of 300 requests/min per API key
        across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.


        Note: `FUTURE_ISSUE_DATE` and `POOR_DOCUMENT_QUALITY` exist in workflow
        sessions but are never produced by this standalone endpoint — a
        future-dated document is not auto-declined here.
      operationId: post_v3poa
      parameters: []
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - document
              properties:
                document:
                  type: string
                  format: binary
                  description: >-
                    The Proof of Address document. Allowed extensions: `tiff`,
                    `jpg`, `jpeg`, `png`, `pdf`, `webp`. Maximum upload size:
                    **15 MB** (larger files are rejected with `400`). Multi-page
                    PDFs are supported (expect higher latency); encrypted PDFs
                    require `document_password`. Images are automatically
                    compressed before processing.
                document_password:
                  type: string
                  description: >-
                    Password to decrypt `document` when it is an encrypted PDF.
                    Sending an encrypted PDF without (or with a wrong) password
                    returns `400`.
                  writeOnly: true
                expected_address:
                  type: string
                  description: >-
                    Address to verify against the document. It is geocoded and
                    compared with the extracted address; a mismatch (or an
                    expected address that cannot be geocoded/verified) adds
                    `ADDRESS_MISMATCH_WITH_PROVIDED`, governed by
                    `poa_address_mismatch_action`. The geocoded form is echoed
                    back in `expected_details_formatted_address` /
                    `expected_details_parsed_address`.
                  example: Av. Monseñor Tavella 1291, Salta, Argentina
                expected_country:
                  type: string
                  description: >-
                    Country to verify against the document (ISO 3166-1 code,
                    e.g. `ARG` or `AR`). A mismatch with the document's country
                    adds `POA_COUNTRY_MISMATCH_WITH_PROVIDED`, governed by
                    `poa_address_mismatch_action`.
                  example: ARG
                expected_first_name:
                  type: string
                  description: >-
                    First name to verify against `name_on_document` (fuzzy
                    match, transliteration-aware). A score below the match
                    threshold adds `NAME_MISMATCH_WITH_PROVIDED`, governed by
                    `poa_address_mismatch_action`.
                  example: Sophia
                expected_last_name:
                  type: string
                  description: >-
                    Last name to verify against `name_on_document`, combined
                    with `expected_first_name` for the match score.
                  example: Martinez
                poa_languages_allowed:
                  type: string
                  description: >-
                    Comma-separated list of accepted document languages as ISO
                    639-1 codes (e.g. `en,es,fr`). When omitted or blank, **all
                    51 supported languages** are accepted. A document in a
                    language outside the list adds
                    `UNSUPPORTED_DOCUMENT_LANGUAGE` (governed by
                    `poa_unsupported_language_action`); an unknown code in the
                    list returns `400` with the full set of supported codes.
                  example: en,es
                poa_document_age_months:
                  type: string
                  description: >-
                    Comma-separated `key:value` pairs setting the maximum
                    document age in months per document type — keys:
                    `utility_bill`, `bank_statement`,
                    `government_issued_document`, `other_poa_document`; values:
                    `1`–`120`, or `-1` for unlimited (no age check). Defaults
                    when omitted:
                    `utility_bill:3,bank_statement:3,government_issued_document:12,other_poa_document:12`.
                    A document older than its limit declines with
                    `POA_DOCUMENT_EXPIRED`. **Caution:** when you provide this
                    field, list *every* type you accept — omitted types are
                    treated as not accepted (informational
                    `POA_DOCUMENT_NOT_SUPPORTED_FOR_APPLICATION`) and their age
                    check is skipped.
                  example: utility_bill:3,bank_statement:6
                poa_name_mismatch_action:
                  type: string
                  enum:
                    - DECLINE
                    - NO_ACTION
                  default: DECLINE
                  description: >-
                    Accepted but **currently not applied** — name-mismatch
                    warnings (`NAME_MISMATCH_WITH_PROVIDED`) follow
                    `poa_address_mismatch_action` instead. Set
                    `poa_address_mismatch_action` to control both name and
                    address mismatch behavior.
                poa_document_issues_action:
                  type: string
                  enum:
                    - DECLINE
                    - NO_ACTION
                  default: DECLINE
                  description: >-
                    What to do on file-integrity problems — in practice only
                    `DOCUMENT_METADATA_MISMATCH` is produced by this endpoint.
                poa_document_authenticity_action:
                  type: string
                  enum:
                    - DECLINE
                    - NO_ACTION
                  default: DECLINE
                  description: >-
                    What to do when document manipulation is suspected
                    (`SUSPECTED_DOCUMENT_MANIPULATION`) — e.g. the PDF was
                    modified after digital signing, processed by a known PDF
                    editor, shows overlay-text manipulation, or has inconsistent
                    EXIF dates.
                poa_unsupported_language_action:
                  type: string
                  enum:
                    - DECLINE
                    - NO_ACTION
                  default: DECLINE
                  description: >-
                    What to do when the document language is outside
                    `poa_languages_allowed` (`UNSUPPORTED_DOCUMENT_LANGUAGE`).
                poa_address_mismatch_action:
                  type: string
                  enum:
                    - DECLINE
                    - NO_ACTION
                  default: DECLINE
                  description: >-
                    What to do when the extracted details disagree with the
                    `expected_*` fields: `ADDRESS_MISMATCH_WITH_PROVIDED`,
                    `NAME_MISMATCH_WITH_PROVIDED`, and
                    `POA_COUNTRY_MISMATCH_WITH_PROVIDED` all follow this action.
                poa_issuer_not_identified_action:
                  type: string
                  enum:
                    - DECLINE
                    - NO_ACTION
                  default: DECLINE
                  description: >-
                    What to do when no issuing company/organization can be
                    identified on the document (`ISSUER_NOT_IDENTIFIED`).
                save_api_request:
                  type: boolean
                  default: true
                  description: >-
                    When `true` (default), persists the call as an API-type
                    session — visible in the Business Console, retrievable via
                    `GET /v3/session/{sessionId}/decision/` using the returned
                    `request_id`, announced through a `status.updated` webhook,
                    and with the uploaded document stored. When `false`, nothing
                    is stored and `request_id` is a transient UUID for response
                    correlation only.
                  example: true
                vendor_data:
                  type: string
                  description: >-
                    Optional opaque string (your internal user id, email, UUID…)
                    stored on the persisted session and echoed back in the
                    response. Use it to correlate API calls with your own
                    records and to filter sessions later.
                  example: user-123
                metadata:
                  type: object
                  additionalProperties: true
                  description: >-
                    Optional JSON object stored with the session (when
                    `save_api_request=true`) and echoed back in the response. In
                    multipart requests, send it as a JSON-encoded string field
                    (e.g. `metadata={"flow":"onboarding"}`) — it is parsed into
                    an object.
                  example:
                    flow: onboarding
            example:
              document: (binary JPEG/PNG/PDF of the utility bill or bank statement)
              expected_address: Av. Monseñor Tavella 1291, Salta, Argentina
              expected_country: ARG
              expected_first_name: Sophia
              expected_last_name: Martinez
              poa_languages_allowed: en,es
              save_api_request: true
              vendor_data: user-123
      responses:
        '200':
          description: >-
            Document processed. `poa.status` is `Approved` or `Declined`; every
            detected issue is itemized in `poa.warnings`. An expired,
            mismatched, or suspicious document still returns `200` with `status:
            "Declined"` — inspect the body, not just the HTTP code. When
            `save_api_request=true`, `request_id` is the persisted session id.
          content:
            application/json:
              examples:
                Approved:
                  summary: Utility bill read cleanly, no expected-details checks
                  value:
                    request_id: 4b523a8d-ed32-45e4-8aa8-a89eda459d88
                    poa:
                      issuing_state: ARG
                      document_type: UTILITY_BILL
                      document_subtype: WATER_BILL
                      document_language: es
                      issuer: Aguas del Norte
                      issue_date: '2026-04-10'
                      expiration_date: null
                      poa_address: >-
                        AVDA. MONSEÑOR TAVELLA N° 1291, B° VILLA LAVALLE CP:
                        4400, CAPITAL - SALTA
                      poa_formatted_address: Av. Monseñor Tavella 1291 b, A4400 Salta, Argentina
                      poa_parsed_address:
                        street_1: Avenida Monseñor Tavella 1291
                        street_2: b
                        city: Salta
                        region: Salta
                        country: AR
                        postal_code: A4400
                        document_location:
                          latitude: -24.8208682
                          longitude: -65.4130954
                      expected_details_address: null
                      expected_details_formatted_address: null
                      expected_details_parsed_address: null
                      name_on_document: OESTE EMBOTELLADORA S.A.
                      extra_fields:
                        bank_account_number: null
                        bank_iban: null
                        bank_sort_code: null
                        bank_routing_number: null
                        bank_swift_bic: null
                        bank_branch_name: null
                        bank_branch_address: null
                        document_phone_number: null
                        additional_names:
                          - LA EMBOTELLADORA DEL NORTE SA
                      status: Approved
                      warnings: []
                    vendor_data: user-123
                    metadata: null
                    created_at: '2026-06-12T02:29:02.439655+00:00'
                Declined - document too old:
                  summary: Issue date older than the allowed `poa_document_age_months`
                  value:
                    request_id: f61681b8-9422-4378-ac3e-04c6a247fb45
                    poa:
                      issuing_state: ARG
                      document_type: UTILITY_BILL
                      document_subtype: WATER_BILL
                      document_language: es
                      issuer: Aguas del Norte
                      issue_date: '2021-02-10'
                      expiration_date: null
                      poa_address: >-
                        AVDA. MONSEÑOR TAVELLA N° 1291, B° VILLA LAVALLE CP:
                        4400, CAPITAL - SALTA
                      poa_formatted_address: Av. Monseñor Tavella 1291 b, A4400 Salta, Argentina
                      poa_parsed_address:
                        street_1: Avenida Monseñor Tavella 1291
                        street_2: b
                        city: Salta
                        region: Salta
                        country: AR
                        postal_code: A4400
                        document_location:
                          latitude: -24.8208682
                          longitude: -65.4130954
                      expected_details_address: null
                      expected_details_formatted_address: null
                      expected_details_parsed_address: null
                      name_on_document: OESTE EMBOTELLADORA S.A.
                      extra_fields:
                        bank_account_number: null
                        bank_iban: null
                        bank_sort_code: null
                        bank_routing_number: null
                        bank_swift_bic: null
                        bank_branch_name: null
                        bank_branch_address: null
                        document_phone_number: null
                        additional_names:
                          - LA EMBOTELLADORA DEL NORTE SA
                      status: Declined
                      warnings:
                        - risk: POA_DOCUMENT_EXPIRED
                          feature: PROOF_OF_ADDRESS
                          additional_data:
                            max_age_months: 3
                            document_subtype: WATER_BILL
                            document_type: UTILITY_BILL
                            issue_date: '2021-02-10'
                          log_type: error
                          short_description: Document expired
                          long_description: >-
                            The submitted document is older than 90 days from
                            its issue date, which exceeds the acceptable time
                            period for validity.
                    vendor_data: user-123
                    metadata: null
                    created_at: '2026-06-12T02:23:02.165493+00:00'
                Declined - name mismatch:
                  summary: >-
                    `expected_first_name`/`expected_last_name` do not match
                    `name_on_document`
                  value:
                    request_id: 0a7f2f9c-58f1-4b8e-b1e3-2f6f9d4f7a21
                    poa:
                      issuing_state: ARG
                      document_type: UTILITY_BILL
                      document_subtype: WATER_BILL
                      document_language: es
                      issuer: Aguas del Norte
                      issue_date: '2021-02-10'
                      expiration_date: null
                      poa_address: >-
                        AVDA. MONSEÑOR TAVELLA N° 1291, B° VILLA LAVALLE CP:
                        4400, CAPITAL - SALTA
                      poa_formatted_address: Av. Monseñor Tavella 1291 b, A4400 Salta, Argentina
                      poa_parsed_address:
                        street_1: Avenida Monseñor Tavella 1291
                        street_2: b
                        city: Salta
                        region: Salta
                        country: AR
                        postal_code: A4400
                        document_location:
                          latitude: -24.8208682
                          longitude: -65.4130954
                      expected_details_address: null
                      expected_details_formatted_address: null
                      expected_details_parsed_address: null
                      name_on_document: OESTE EMBOTELLADORA S.A.
                      extra_fields:
                        bank_account_number: null
                        bank_iban: null
                        bank_sort_code: null
                        bank_routing_number: null
                        bank_swift_bic: null
                        bank_branch_name: null
                        bank_branch_address: null
                        document_phone_number: null
                        additional_names:
                          - LA EMBOTELLADORA DEL NORTE SA
                      status: Declined
                      warnings:
                        - risk: NAME_MISMATCH_WITH_PROVIDED
                          feature: PROOF_OF_ADDRESS
                          additional_data: null
                          log_type: error
                          short_description: Name mismatch with provided information
                          long_description: >-
                            The full name on the document does not match the
                            name from the user's verified identity documents, or
                            the full name sent by API.
                    vendor_data: user-123
                    metadata: null
                    created_at: '2026-06-12T02:27:21.330172+00:00'
              schema:
                type: object
                properties:
                  request_id:
                    type: string
                    format: uuid
                    description: >-
                      Persisted session id when `save_api_request=true` (usable
                      with `GET /v3/session/{sessionId}/decision/`); otherwise a
                      transient correlation UUID.
                  poa:
                    type: object
                    properties:
                      status:
                        type: string
                        enum:
                          - Approved
                          - Declined
                        description: >-
                          `Approved` when no warning resolves to a decline;
                          `Declined` otherwise — the `warnings` list explains
                          why.
                      issuing_state:
                        type: string
                        nullable: true
                        description: >-
                          Country the document was issued in, as extracted from
                          the document — usually ISO 3166-1 alpha-3 (e.g.
                          `ARG`).
                        example: ARG
                      document_type:
                        type: string
                        enum:
                          - UTILITY_BILL
                          - BANK_STATEMENT
                          - GOVERNMENT_ISSUED_DOCUMENT
                          - OTHER_POA_DOCUMENT
                          - UNKNOWN
                        description: >-
                          Detected document category. `UNKNOWN` means the
                          document could not be classified as an acceptable
                          Proof of Address and auto-declines with
                          `INVALID_DOCUMENT_TYPE`. Never null — classification
                          failures fall back to `UNKNOWN`.
                      document_subtype:
                        type: string
                        description: >-
                          Finer-grained classification within `document_type` —
                          e.g. `ELECTRICITY_BILL`, `WATER_BILL`, `GAS_BILL`,
                          `INTERNET_BILL`, `PHONE_BILL` (utility bills);
                          `ACCOUNT_STATEMENT`, `CREDIT_CARD_STATEMENT` (bank
                          statements); `TAX_ASSESSMENT`, `RESIDENCY_CERTIFICATE`
                          (government documents). `UNKNOWN` when no subtype
                          could be determined. Never null — classification
                          failures fall back to `UNKNOWN`.
                        example: WATER_BILL
                      document_language:
                        type: string
                        nullable: true
                        description: Detected document language (ISO 639-1).
                        example: es
                      issuer:
                        type: string
                        nullable: true
                        description: >-
                          Company or organization that issued the document.
                          `null` adds `ISSUER_NOT_IDENTIFIED`.
                        example: Aguas del Norte
                      issue_date:
                        type: string
                        format: date
                        nullable: true
                        description: >-
                          Document issue date (`YYYY-MM-DD`). `null` when it
                          could not be extracted — that declines with
                          `UNABLE_TO_EXTRACT_ISSUE_DATE` on this endpoint.
                        example: '2021-02-10'
                      expiration_date:
                        type: string
                        format: date
                        nullable: true
                        description: >-
                          Explicit expiry/validity date printed on the document,
                          when present. Independent of the
                          `poa_document_age_months` age check.
                      poa_address:
                        type: string
                        nullable: true
                        description: >-
                          Address exactly as printed on the document. `null`
                          auto-declines with `MISSING_ADDRESS_INFORMATION`.
                      poa_formatted_address:
                        type: string
                        nullable: true
                        description: Geocoded, normalized version of `poa_address`.
                        example: Av. Monseñor Tavella 1291 b, A4400 Salta, Argentina
                      poa_parsed_address:
                        type: object
                        nullable: true
                        description: >-
                          Structured, geocoded breakdown of the document
                          address. `null` when the address could not be parsed.
                        properties:
                          street_1:
                            type: string
                            nullable: true
                            example: Avenida Monseñor Tavella 1291
                          street_2:
                            type: string
                            nullable: true
                          city:
                            type: string
                            nullable: true
                            example: Salta
                          region:
                            type: string
                            nullable: true
                            example: Salta
                          country:
                            type: string
                            nullable: true
                            description: ISO 3166-1 alpha-2 country code.
                            example: AR
                          postal_code:
                            type: string
                            nullable: true
                            example: A4400
                          document_location:
                            type: object
                            nullable: true
                            properties:
                              latitude:
                                type: number
                              longitude:
                                type: number
                            description: Geocoded coordinates of the document address.
                      expected_details_address:
                        type: string
                        nullable: true
                        description: Echo of the `expected_address` you sent, or `null`.
                      expected_details_formatted_address:
                        type: string
                        nullable: true
                        description: >-
                          Geocoded, normalized version of `expected_address`;
                          `null` when not provided or not geocodable.
                      expected_details_parsed_address:
                        type: object
                        nullable: true
                        additionalProperties: true
                        description: >-
                          Full geocoding result for `expected_address`
                          (street/city/region/country/postal code plus raw
                          geocoder output and an `is_verified` flag). `null`
                          when `expected_address` was not provided.
                      name_on_document:
                        type: string
                        nullable: true
                        description: >-
                          Primary holder name extracted from the document;
                          compared against
                          `expected_first_name`/`expected_last_name` when
                          provided. `null` declines with `POA_NAME_NOT_DETECTED`
                          on this endpoint.
                        example: OESTE EMBOTELLADORA S.A.
                      extra_fields:
                        type: object
                        description: >-
                          Document-specific extras. Bank fields are populated
                          for bank statements when printed on the document;
                          `null` otherwise.
                        properties:
                          bank_account_number:
                            type: string
                            nullable: true
                          bank_iban:
                            type: string
                            nullable: true
                          bank_sort_code:
                            type: string
                            nullable: true
                          bank_routing_number:
                            type: string
                            nullable: true
                          bank_swift_bic:
                            type: string
                            nullable: true
                          bank_branch_name:
                            type: string
                            nullable: true
                          bank_branch_address:
                            type: string
                            nullable: true
                          document_phone_number:
                            type: string
                            nullable: true
                            description: Contact phone number printed on the document.
                          additional_names:
                            type: array
                            items:
                              type: string
                            description: >-
                              Other holder names found on the document (e.g.
                              co-account holders, previous account names).
                      warnings:
                        type: array
                        description: >-
                          Empty on a clean approval. Every entry explains one
                          detected risk; entries with `log_type: "error"` set
                          `status` to `Declined`.
                        items:
                          type: object
                          properties:
                            risk:
                              type: string
                              description: >-
                                Machine-readable risk code. Always-decline:
                                `MISSING_ADDRESS_INFORMATION`,
                                `POA_DOCUMENT_EXPIRED`, `INVALID_DOCUMENT_TYPE`,
                                `UNABLE_TO_VALIDATE_DOCUMENT_AGE`, plus
                                `UNABLE_TO_EXTRACT_ISSUE_DATE` and
                                `POA_NAME_NOT_DETECTED` (fixed to decline on
                                this endpoint). Governed by request options:
                                `NAME_MISMATCH_WITH_PROVIDED` /
                                `ADDRESS_MISMATCH_WITH_PROVIDED` /
                                `POA_COUNTRY_MISMATCH_WITH_PROVIDED`
                                (`poa_address_mismatch_action`),
                                `POOR_DOCUMENT_QUALITY` (never produced by this
                                endpoint) / `DOCUMENT_METADATA_MISMATCH`
                                (`poa_document_issues_action`),
                                `SUSPECTED_DOCUMENT_MANIPULATION`
                                (`poa_document_authenticity_action`),
                                `UNSUPPORTED_DOCUMENT_LANGUAGE`
                                (`poa_unsupported_language_action`),
                                `ISSUER_NOT_IDENTIFIED`
                                (`poa_issuer_not_identified_action`).
                                Informational on this endpoint:
                                `POA_DOCUMENT_NOT_SUPPORTED_FOR_APPLICATION`,
                                `UNPARSABLE_OR_INVALID_ADDRESS`.
                              example: POA_DOCUMENT_EXPIRED
                            feature:
                              type: string
                              enum:
                                - PROOF_OF_ADDRESS
                              description: >-
                                Feature that raised the warning. Always
                                `PROOF_OF_ADDRESS` on this endpoint.
                            additional_data:
                              type: object
                              nullable: true
                              additionalProperties: true
                              description: >-
                                Extra context for some risks — e.g.
                                `POA_DOCUMENT_EXPIRED` includes
                                `max_age_months`, `document_type`,
                                `document_subtype`, and `issue_date`;
                                `SUSPECTED_DOCUMENT_MANIPULATION` includes
                                `reason` and `detection_method`. `null` for most
                                risks.
                            log_type:
                              type: string
                              enum:
                                - error
                                - information
                              description: >-
                                Severity. `error` warnings drive `status` to
                                `Declined`; `information` entries are advisory
                                and never decline on their own. (`warning` is
                                not produced by this endpoint — its options only
                                accept `DECLINE` or `NO_ACTION`.)
                            short_description:
                              type: string
                              description: Human-readable one-line summary of the risk.
                            long_description:
                              type: string
                              description: Human-readable explanation of the risk.
                  vendor_data:
                    type: string
                    nullable: true
                    description: Echo of the `vendor_data` you sent, or `null`.
                  metadata:
                    type: object
                    nullable: true
                    additionalProperties: true
                    description: Echo of the `metadata` object you sent, or `null`.
                  created_at:
                    type: string
                    format: date-time
                    description: >-
                      ISO 8601 timestamp (UTC) of when the response was
                      generated.
        '400':
          description: >-
            Validation or processing error. Field-level problems return DRF's
            standard envelope (one array of messages per offending field).
            Encrypted-PDF problems return `{"detail": ...}`. A document the
            extraction pipeline cannot process returns `{"error": ["Error
            extracting POA information"]}`.
          content:
            application/json:
              examples:
                Missing document:
                  summary: '`document` not included in the form data'
                  value:
                    document:
                      - No file was submitted.
                Unsupported file extension:
                  summary: File extension outside tiff/jpg/jpeg/png/pdf/webp
                  value:
                    document:
                      - >-
                        File extension “txt” is not allowed. Allowed extensions
                        are: tiff, jpg, jpeg, png, pdf, webp.
                File too large:
                  summary: Upload exceeds the 15 MB limit
                  value:
                    document:
                      - File size should not exceed 15 MB
                Encrypted PDF:
                  summary: PDF uploaded without the required password
                  value:
                    detail: >-
                      The PDF is encrypted. Please upload a decrypted PDF or a
                      photo instead.
                Wrong PDF password:
                  summary: '`document_password` does not decrypt the PDF'
                  value:
                    detail: >-
                      The PDF password is incorrect. Please provide the correct
                      password.
                Invalid language code:
                  summary: '`poa_languages_allowed` contains an unsupported code'
                  value:
                    poa_languages_allowed:
                      - >-
                        Invalid language code: 'xx'. Must be one of the
                        supported languages: ['ar', 'bn', 'hy', 'bg', 'bs',
                        'ca', 'cnr', 'sq', 'zh', 'hr', 'cs', 'da', 'nl', 'en',
                        'et', 'fi', 'fr', 'ka', 'kk', 'de', 'el', 'he', 'hi',
                        'hu', 'id', 'it', 'ja', 'ko', 'ky', 'lv', 'lt', 'mk',
                        'mn', 'ms', 'no', 'fa', 'pl', 'pt', 'ro', 'ru', 'sr',
                        'sk', 'sl', 'so', 'es', 'sv', 'th', 'tr', 'uk', 'uz',
                        'vi'].
                Invalid document age value:
                  summary: '`poa_document_age_months` value outside 1–120 / -1'
                  value:
                    poa_document_age_months:
                      - >-
                        Invalid integer value for 'utility_bill': '999'. Must be
                        -1 (unlimited) or a positive integer between 1 and 120.
                Extraction failure:
                  summary: >-
                    The document could not be processed by the extraction
                    pipeline
                  value:
                    error:
                      - Error extracting POA information
        '403':
          description: >-
            Permission denied. Returned when the `x-api-key` header is missing,
            malformed, revoked, or belongs to another environment — and also
            when the calling organization's balance cannot cover the call.
            Authentication failures return `403` with `{"detail": ...}`; this
            API never returns `401`. Credit shortfalls return `403` with
            `{"error": ...}` before any document processing happens.
          content:
            application/json:
              examples:
                Missing or invalid API key:
                  summary: No `x-api-key` header, or the key is invalid/revoked
                  value:
                    detail: You do not have permission to perform this action.
                Not enough credits:
                  summary: Organization balance cannot cover the call
                  value:
                    error: >-
                      You don't have enough credits to perform this request.
                      Please top up at https://business.didit.me
        '429':
          description: >-
            Rate limit exceeded. All POST/PATCH/DELETE endpoints share a budget
            of 300 write requests per minute per API key. The response carries
            `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`,
            and `Retry-After` headers.
          content:
            application/json:
              examples:
                Write rate limit exceeded:
                  summary: More than 300 write requests in the current minute
                  value:
                    detail: >-
                      Write request rate limit exceeded. You can make up to 300
                      requests per minute.
              schema:
                type: object
                properties:
                  detail:
                    type: string
      security:
        - ApiKeyAuth: []
      x-codeSamples:
        - lang: curl
          label: cURL
          source: |-
            curl -X POST 'https://verification.didit.me/v3/poa/' \
              -H 'x-api-key: YOUR_API_KEY' \
              -F 'document=@./utility-bill.pdf' \
              -F 'expected_address=Av. Monseñor Tavella 1291, Salta, Argentina' \
              -F 'expected_country=ARG' \
              -F 'expected_first_name=Sophia' \
              -F 'expected_last_name=Martinez' \
              -F 'poa_languages_allowed=en,es' \
              -F 'vendor_data=user-123' \
              --max-time 45
        - lang: python
          label: Python
          source: |-
            import requests

            url = 'https://verification.didit.me/v3/poa/'
            headers = {'x-api-key': 'YOUR_API_KEY'}

            with open('utility-bill.pdf', 'rb') as f:
                files = {'document': ('utility-bill.pdf', f, 'application/pdf')}
                data = {
                    'expected_address': 'Av. Monseñor Tavella 1291, Salta, Argentina',
                    'expected_country': 'ARG',
                    'expected_first_name': 'Sophia',
                    'expected_last_name': 'Martinez',
                    'poa_languages_allowed': 'en,es',
                    'vendor_data': 'user-123',
                }
                # LLM-based extraction takes 5-15s (multi-page PDFs up to ~30s) - allow headroom
                resp = requests.post(url, headers=headers, files=files, data=data, timeout=45)

            resp.raise_for_status()
            poa = resp.json()['poa']
            print('status:', poa['status'])
            print('issuer:', poa['issuer'], '| issued:', poa['issue_date'])
            print('address:', poa['poa_formatted_address'])
            for w in poa['warnings']:
                print('warning:', w['risk'])
        - lang: javascript
          label: JavaScript
          source: >-
            import fs from 'node:fs';


            const form = new FormData();

            form.append('document', new
            Blob([fs.readFileSync('./utility-bill.pdf')]), 'utility-bill.pdf');

            form.append('expected_address', 'Av. Monseñor Tavella 1291, Salta,
            Argentina');

            form.append('expected_country', 'ARG');

            form.append('expected_first_name', 'Sophia');

            form.append('expected_last_name', 'Martinez');

            form.append('poa_languages_allowed', 'en,es');

            form.append('vendor_data', 'user-123');


            // LLM-based extraction takes 5-15s (multi-page PDFs up to ~30s) -
            allow >=45s

            const response = await
            fetch('https://verification.didit.me/v3/poa/', {
              method: 'POST',
              headers: { 'x-api-key': 'YOUR_API_KEY' },
              body: form,
              signal: AbortSignal.timeout(45_000),
            });


            if (!response.ok) throw new Error(`POA verification failed:
            ${response.status}`);

            const { poa } = await response.json();

            console.log('status:', poa.status);

            console.log('warnings:', poa.warnings.map((w) => w.risk));
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key

````