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

# Read documents

> Required scope: `kyc:extract`.

Read one upload step's document(s) into canonical fields, with checks.

`doc_type` hints the reader which document this is. `step_key` names
the step in a language-independent way; it decides which document types
the step accepts.

With a `reference`, the files and what was read from them are also filed
on that client's case in `environment` ('sandbox' or 'production'), and
the answer carries `case_id` and each document's `document_id`.

`kind` (the client kind, as `/verify` takes it) picks the KYC or KYB
policy whose thresholds the document checks use; omitted, the KYC one.

Guides: [Read documents](/guides/ocr-documents), [Document types and fields](/guides/document-types).



## OpenAPI

````yaml /openapi.json post /v1/kyc/extract
openapi: 3.1.0
info:
  title: Sahl Partner API
  version: 0.2.0
  description: >-
    Server-to-server API for lenders and banks: document reading, verification,
    risk assessment and eID checks. Authenticate with `Authorization: Bearer
    <API key>`. Paths are relative to the server URL. New workspaces start in
    sandbox.
servers:
  - url: https://app.sahlfinancial.com/api
    description: Single API host. Send environment=sandbox (the default) for test data.
security:
  - bearerAuth: []
tags:
  - name: KYC
    description: Read documents, verify a profile, assess risk, start an eID check.
  - name: Bank connections (Growth)
    description: >-
      Growth plan, switched on per workspace by Sahl. Sandbox simulation today;
      production answers 409 until a bank data provider is live.
paths:
  /v1/kyc/extract:
    post:
      tags:
        - KYC
      summary: Read documents
      description: >-
        Required scope: `kyc:extract`.


        Read one upload step's document(s) into canonical fields, with checks.


        `doc_type` hints the reader which document this is. `step_key` names

        the step in a language-independent way; it decides which document types

        the step accepts.


        With a `reference`, the files and what was read from them are also filed

        on that client's case in `environment` ('sandbox' or 'production'), and

        the answer carries `case_id` and each document's `document_id`.


        `kind` (the client kind, as `/verify` takes it) picks the KYC or KYB

        policy whose thresholds the document checks use; omitted, the KYC one.


        Guides: [Read documents](/guides/ocr-documents), [Document types and
        fields](/guides/document-types).
      operationId: extract
      requestBody:
        content:
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/Body_extract_api_v1_kyc_extract_post'
        required: true
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExtractResponse'
              example:
                fields:
                  first_name: Test
                  last_name: Client
                  document_holder_name: Test Client
                  employer_name: Test Employer SARL
                  occupation: Analyst
                  document_date: '2026-09-30'
                documents:
                  - filename: payslip-test.pdf
                    doc_type: payslip
                    step_hint: payslip
                    step_key: null
                    fields:
                      first_name: Test
                      last_name: Client
                      document_holder_name: Test Client
                      employer_name: Test Employer SARL
                      occupation: Analyst
                      document_date: '2026-09-30'
                    meta_created: '2026-10-01'
                    meta_provenance:
                      producer: Example Payroll 4.2
                      revisions: 1
                    mapped: 6
                    notes: []
                    document_id: 22222222-2222-4222-8222-222222222222
                field_count: 6
                checks: []
                reader_unavailable: false
                policy:
                  id: null
                  version: 0
                  source: legacy
                  regime: none
                  regulator: null
                  purpose: onboarding
                  overrides_refused: []
                case_id: 00000000-0000-4000-8000-000000000001
                document_ids:
                  - 22222222-2222-4222-8222-222222222222
        '400':
          description: >-
            Not 1 to 5 files, unsupported file type, or content that does not
            match the declared type.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: Send between 1 and 5 files.
        '401':
          description: Missing, invalid, revoked or expired API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: Invalid or revoked API key
        '403':
          description: >-
            Key lacks the scope, or the workspace is not enabled for the partner
            KYC API.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail:
                  code: kyc_scope_not_allowed
                  message: This workspace is not enabled for the partner KYC API.
        '413':
          description: >-
            File over the size limit (30 MB by default) or image over 50
            megapixels.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail: File too large. Maximum allowed size is 30 MB.
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
        '429':
          description: >-
            Monthly document-read limit reached (`kyc_extract_cap_reached`), or
            the per-IP rate limit (`rate_limit_exceeded`, top-level `code`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                detail:
                  code: kyc_extract_cap_reached
                  message: Monthly document-read limit of 2000 reached.
                  used: 2000
                  limit: 2000
        '500':
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorFlat'
              example:
                code: internal_error
                message: An unexpected error occurred
                details: null
      security:
        - bearerAuth: []
components:
  schemas:
    Body_extract_api_v1_kyc_extract_post:
      properties:
        files:
          items:
            type: string
            contentMediaType: application/octet-stream
            format: binary
          type: array
          title: Files
          description: >-
            1 to 5 files. JPEG, PNG, WebP, TIFF or PDF, up to 30 MB each. The
            content must match the declared type.
        doc_type:
          anyOf:
            - type: string
            - type: 'null'
          title: Doc Type
          description: >-
            Hint for the reader about which document this is, for example
            `payslip` or `national_id`. Applies to every file in the call, so
            send one document type per call.
          examples:
            - payslip
        step_key:
          anyOf:
            - type: string
            - type: 'null'
          title: Step Key
          description: >-
            Stable name of your upload step, for example `photo_id` or
            `proof_of_address`. It decides which document types the step
            accepts. Optional.
          examples:
            - proof_of_address
        reference:
          anyOf:
            - type: string
            - type: 'null'
          title: Reference
          description: >-
            Your own id for the client: 1 to 64 characters of `A-Z a-z 0-9 _ . :
            -`. With one, the files, the fields and the verdict are filed on a
            case in your workspace. Without one, nothing is filed.
          examples:
            - client-0001
        environment:
          type: string
          title: Environment
          default: sandbox
          enum:
            - sandbox
            - production
          description: >-
            `sandbox` (default) or `production`. Cases are separate per
            environment.
          examples:
            - sandbox
        subject:
          anyOf:
            - type: string
            - type: 'null'
          title: Subject
          description: Client name for the case, up to 255 characters.
          examples:
            - Test Client
        kind:
          anyOf:
            - type: string
            - type: 'null'
          title: Kind
          description: >-
            Client kind: `individual`, `corporation`, `partnership`,
            `charitable_org`, `trust`, `estate`. Picks the individual or entity
            policy thresholds. Omitted, the individual policy applies.
          examples:
            - individual
      type: object
      required:
        - files
      title: Extract request
      description: 1 to 5 files per call. JPEG, PNG, WebP, TIFF or PDF, up to 30 MB each.
    ExtractResponse:
      type: object
      title: ExtractResponse
      properties:
        fields:
          type: object
          additionalProperties:
            type: string
          description: >-
            Fields merged across the files of the call. The first non-empty
            value wins, in file order, so send the strongest identity document
            first.
        documents:
          type: array
          items:
            $ref: '#/components/schemas/ExtractedDocument'
        field_count:
          type: integer
          description: Number of keys in `fields`.
        checks:
          type: array
          items:
            $ref: '#/components/schemas/Check'
          description: The per-document checks of every file, in one list.
        reader_unavailable:
          type: boolean
          description: >-
            True when at least one file was never read (a problem on the Sahl
            side). It does not mean the document was blank.
        policy:
          $ref: '#/components/schemas/PolicyBlock'
        case_id:
          type: string
          format: uuid
          description: Only with a `reference`.
        document_ids:
          type: array
          items:
            type: string
            format: uuid
          description: Only with a `reference`. Same order as `documents`.
      required:
        - fields
        - documents
        - field_count
        - checks
        - reader_unavailable
        - policy
    Error:
      type: object
      title: Error
      description: >-
        FastAPI error body. `detail` is a string, or an object with `code` and
        `message` (extra keys possible).
      properties:
        detail:
          oneOf:
            - type: string
            - type: object
              additionalProperties: true
              properties:
                code:
                  type: string
                message:
                  type: string
      required:
        - detail
    HTTPValidationError:
      properties:
        detail:
          items:
            $ref: '#/components/schemas/ValidationError'
          type: array
          title: Detail
      type: object
      title: HTTPValidationError
    ErrorFlat:
      type: object
      title: ErrorFlat
      description: >-
        Body of a rate-limit 429, an unknown route 404 and a 500: `code` and
        `message` at the top level, with no `detail`.
      properties:
        code:
          type: string
        message:
          type: string
        details:
          type:
            - string
            - 'null'
          description: Present on a 500 only; null unless the server runs in debug mode.
      required:
        - code
        - message
    ExtractedDocument:
      type: object
      title: ExtractedDocument
      description: >-
        One read file. Send these entries back to `/verify` and `/assess`
        unchanged, in `documents`.
      properties:
        filename:
          type: string
        doc_type:
          type:
            - string
            - 'null'
          description: >-
            What the reader says the document is: passport, national_id,
            drivers_license, pr_card, residence_permit, utility_bill,
            proof_of_address, bank_statement, void_cheque, bank_letter, invoice,
            payslip, articles_of_incorporation, business_registration, bylaws,
            beneficial_ownership, directors_register, board_resolution,
            financial_statements, trust_deed, beneficiary_list, or other. Other
            strings are lowercased with `_`; null when it could not be named.
        step_hint:
          type:
            - string
            - 'null'
          description: The `doc_type` you sent.
        step_key:
          type:
            - string
            - 'null'
          description: The `step_key` you sent.
        fields:
          type: object
          additionalProperties:
            type: string
          description: >-
            Fields read from this file. Values are strings. A field the reader
            could not read is absent.
        meta_created:
          type:
            - string
            - 'null'
          description: >-
            Creation date from the file metadata (PDF CreationDate, or image
            EXIF), `YYYY-MM-DD`.
        meta_provenance:
          type: object
          additionalProperties: true
          description: >-
            What the file says about how it was made: `producer`, `creator`,
            `modified`, `revisions` for a PDF; `creator`, `camera` for an image.
            Can be empty.
        mapped:
          type: integer
          description: Number of fields read from this file.
        notes:
          type: array
          items:
            type: string
          description: Changes the server made to the reader's answer, with the reason.
        document_id:
          type: string
          format: uuid
          description: Only with a `reference`.
      required:
        - filename
        - doc_type
        - step_hint
        - fields
        - mapped
        - notes
    Check:
      type: object
      title: Check
      description: >-
        One verification result. `passed: false` with severity `critical` blocks
        the file; `warning` is a flag for a person; `info` is kept for the audit
        trail.
      properties:
        id:
          type: string
          description: >-
            Stable id, for example `expiry:national_id`. Some ids end in a
            document label or a party name.
        label:
          type: string
          description: Human sentence.
        severity:
          type: string
          enum:
            - critical
            - warning
            - info
        passed:
          type: boolean
        detail:
          type: string
          description: Why it failed. Empty when it passed.
      required:
        - id
        - label
        - severity
        - passed
        - detail
    PolicyBlock:
      type: object
      title: PolicyBlock
      description: >-
        Which workspace policy the call ran under, and which request switches it
        refused.
      properties:
        id:
          type:
            - string
            - 'null'
          format: uuid
          description: Saved policy id, or null when none is saved.
        version:
          type: integer
        source:
          type: string
          enum:
            - tenant
            - preset
            - legacy
            - fallback
          description: >-
            `tenant` a saved policy, `preset` a regime preset, `legacy` the
            default, `fallback` a saved policy that no longer validates.
        regime:
          type: string
        regulator:
          type:
            - string
            - 'null'
        purpose:
          type: string
          enum:
            - onboarding
            - periodic_review
        overrides_refused:
          type: array
          items:
            type: object
            properties:
              field:
                type: string
              requested:
                type: boolean
              enforced:
                type: boolean
              locked_item:
                type: string
      required:
        - id
        - version
        - source
        - regime
        - purpose
        - overrides_refused
    ValidationError:
      properties:
        loc:
          items:
            anyOf:
              - type: string
              - type: integer
          type: array
          title: Location
        msg:
          type: string
          title: Message
        type:
          type: string
          title: Error Type
        input:
          title: Input
        ctx:
          type: object
          title: Context
      type: object
      required:
        - loc
        - msg
        - type
      title: ValidationError
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >-
        API key created in the console. Scopes: kyc:extract, kyc:verify,
        kyc:eid; bank:read, bank:write (Growth plan, enabled per workspace by
        Sahl).

````

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