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

# Upload a document

> Give us one of the documents the workflow asks for.

Name the step it belongs to and the slot on that step it answers; the workflow's `intake` names both. Where that slot names several kinds of document, name which of them this is. Send a file per slot, and send several to the same slot where it takes several, as a certificate whose pages arrive as separate images does; the files in one slot are all of the same kind.

Nothing is read from a document as it arrives and no check runs on it. Upload everything you hold, then run the checks. Once you have asked for the checks to run the submission is frozen, and an upload is refused until you reset.



## OpenAPI

````yaml /verification/api-reference/openapi.json post /verifications/{verification_id}/documents
openapi: 3.1.0
info:
  title: Verify with Privue
  description: >-
    Start identity-and-business verifications and read the results.


    There are three ways to run one, and a verification reads the same whichever
    it was. Every workflow declares which it is for as its `mode`, and a
    workflow is one of them and never more.


    **Hosted.** You create a verification with the user's mobile number and
    receive a journey URL to hand to them. They work through the steps your
    workflow configures, on our screens, and submit. Use this when the person
    being verified is reachable and holds their own documents.


    **Staged.** You create a verification, upload each document you hold against
    the step it answers, and ask for the checks to run. They take longer than a
    request should be held open, so you read the result from a callback or by
    polling. Use this when documents reach you over time, or when you want them
    kept on the record to download later.


    **Instant.** You send the whole submission in one request. The checks run
    before it answers, so the reply is the finished record. The documents are
    read as they arrive and stored nowhere. Use this when you hold everything
    already and want the answer in the call you made.


    Whichever it was, you read the whole record: every step, its state, what it
    collected, and why anything did not pass. An instant verification hands you
    that record in its reply; for the other two, poll the verification or
    register a callback endpoint and be told when it is submitted.


    Across your whole account you can list the workflows configured for you,
    count your verifications by status, and page through the records behind a
    count.


    Every error response has the shape `{"code": "...", "detail": "..."}`.
    Branch on `code`: several causes share a status, and `detail` is written for
    a person reading a log rather than for code. Treat a code you do not
    recognise as the status it arrived at.
  version: 0.1.0
servers:
  - url: https://api.verify.privue.ai
    description: Production
security: []
tags:
  - name: Workflows
    description: >-
      The workflows configured for your account. Read one to see its mode, the
      documents it asks for, and how each step is answered over the API: the
      slots its documents go in and the values it takes.
  - name: Verifications
    description: >-
      Starting a verification, ending one, clearing one to run the same subject
      again, and purging one. Shared by both modes. A start answers 201 when it
      created the verification and 200 when the subject already had one, so a
      duplicate is visible from the status code alone.
  - name: Hosted workflows
    description: >-
      For a workflow the person being verified walks themselves. Start a
      verification with their mobile number, hand them the journey URL on the
      record, or open it for them from inside your own app with a handoff.
  - name: Staged workflows
    description: >-
      For a workflow you build up from documents you already hold. Upload each
      file against the step and slot it answers, withdraw one you got wrong,
      then ask for the checks to run and read the result when they have. Nobody
      is sent anywhere.
  - name: Instant workflows
    description: >-
      For a workflow answered in the request that asks it. Send the whole
      submission at once and read the finished record in the reply. Nothing is
      kept of the documents and nothing is left open to come back to.
  - name: Results
    description: >-
      Reading what a verification came to: one record, a page of them, counts
      across your account, and links to the files it holds.
paths:
  /verifications/{verification_id}/documents:
    post:
      tags:
        - Staged workflows
      summary: Upload a document
      description: >-
        Give us one of the documents the workflow asks for.


        Name the step it belongs to and the slot on that step it answers; the
        workflow's `intake` names both. Where that slot names several kinds of
        document, name which of them this is. Send a file per slot, and send
        several to the same slot where it takes several, as a certificate whose
        pages arrive as separate images does; the files in one slot are all of
        the same kind.


        Nothing is read from a document as it arrives and no check runs on it.
        Upload everything you hold, then run the checks. Once you have asked for
        the checks to run the submission is frozen, and an upload is refused
        until you reset.
      operationId: upload_document_verifications__verification_id__documents_post
      parameters:
        - name: verification_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
            title: Verification Id
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/UploadForm'
      responses:
        '200':
          description: The stored file.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DocumentRecord'
              example:
                id: 3b9c6e1f-8a24-4d57-b0e3-7f1a5c8d2e69
                slot: pan-card
                kind: pan-card
                label: entity_pan
                filename: avesco-pan.jpg
                content_type: image/jpeg
                size_bytes: 87422
                sha256: >-
                  7a2d9c4e1f8b3a6d0e5c2b9f4a7d1e8c3b6a9d2f5e8c1b4a7d0e3f6c9b2a5d8e
                received_at: '2026-08-27T11:05:17+05:30'
        '400':
          description: >-
            The body is not a multipart form that could be read: the parts are
            malformed, or they do not match the boundary the `Content-Type`
            declares. Nothing was read, so nothing was stored. This is the one
            route that takes a form, so it is the one that answers this.


            The code is `body-unreadable`.
          content:
            application/json:
              example:
                code: body-unreadable
                detail: There was an error parsing the body
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: |-
            The API key is missing, malformed, or revoked.

            The code is `unauthenticated`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                code: unauthenticated
                detail: Missing or malformed Authorization header
        '402':
          description: >-
            The workflow has no prepaid credits left. Either none were ever
            granted for it, or its verifications have used them, their term has
            passed, or we have taken them back. Nothing on the workflow can be
            created or moved on until it is topped up; everything on it stays
            readable. Read the workflow to see where its balance stands.


            The code is `credits-exhausted`.
          content:
            application/json:
              example:
                code: credits-exhausted
                detail: >-
                  the credits for workflow 'merchant-onboarding' are gone; of
                  500 granted, 500 used
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: >-
            The workflow belongs to the other environment. An API key is for
            `uat` or for `production` and reaches only that environment's
            workflows. Or the key is valid but not enrolled for verifications,
            or not set up for exactly one of `uat` and `production`, in which
            case ask us to reissue it.


            The code is one of `environment-mismatch` or `client-not-enrolled`.
          content:
            application/json:
              example:
                code: environment-mismatch
                detail: >-
                  workflow 'merchant-onboarding' is production and this request
                  is for uat
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: >-
            No verification with that id exists on your account, or the version
            of the workflow it runs declares no step by that key. A verification
            runs the version it names in `version`, which can lack a step a
            later version of its workflow declares.


            The code is one of `step-not-found` or `verification-not-found`.
          content:
            application/json:
              example:
                code: step-not-found
                detail: >-
                  version 1 of workflow 'merchant-onboarding' declares no step
                  'pan_card'
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: >-
            The verification is no longer open; its workflow is not staged, so
            its documents are not supplied a call at a time; or the checks have
            already been asked to run, which freezes the submission until a
            reset.


            The code is one of `run-in-progress`, `verification-not-open` or
            `workflow-not-walkable`.
          content:
            application/json:
              example:
                code: run-in-progress
                detail: >-
                  the checks are running on this submission; reset the
                  verification to change it
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '413':
          description: |-
            The file is larger than the step accepts.

            The code is `document-too-large`.
          content:
            application/json:
              example:
                code: document-too-large
                detail: the file exceeds the 20 MB limit
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '415':
          description: |-
            The step does not accept a file of this type.

            The code is `document-type-not-allowed`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                code: document-type-not-allowed
                detail: '''Storefront'' does not take pdf files'
        '422':
          description: >-
            The step takes no documents; it has no slot by that name; the slot
            already holds as many files as it takes; or the kind is missing,
            unknown to the slot, or not the kind the slot already holds.


            The code is one of `step-invalid` or `request-invalid`.
          content:
            application/json:
              example:
                code: step-invalid
                detail: step 'pan' has no slot 'pan-card'
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          description: |-
            The API key is rate limited. Retry after a pause.

            The code is `rate-limited`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                code: rate-limited
                detail: 'Unauthorized: RATE_LIMITED'
        '503':
          description: |-
            API keys could not be checked right now. Retry.

            The code is `auth-unavailable`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                code: auth-unavailable
                detail: Auth provider unavailable
      security:
        - HTTPBearer: []
components:
  schemas:
    UploadForm:
      properties:
        step_key:
          type: string
          minLength: 1
          title: Step Key
          description: The step this document answers.
        slot:
          type: string
          minLength: 1
          title: Slot
          description: The named slot on that step it answers.
        kind:
          anyOf:
            - type: string
              minLength: 1
            - type: 'null'
          title: Kind
          description: >-
            Which of the slot's kinds this document is. A slot naming several
            needs one of them; a slot naming one takes the document as that one.
        file:
          type: string
          format: binary
          title: File
          description: The document itself.
      additionalProperties: false
      type: object
      required:
        - step_key
        - slot
        - file
      title: UploadForm
      description: One document, and where in the workflow it belongs.
    DocumentRecord:
      properties:
        id:
          type: string
          format: uuid
          title: Id
          description: The document's id, used to download it.
        slot:
          type: string
          title: Slot
          description: The named slot the file sits in.
        kind:
          type: string
          title: Kind
          description: >-
            Which of its slot's kinds the file was provided as. Every slot names
            at least one, and `intake.slots` names them for every step answered
            over this API. A file a check fetched carries the kind of document
            the source returned, and a photo taken on the person's own screens
            carries the kind of photo it was asked for.
        label:
          anyOf:
            - type: string
            - type: 'null'
          title: Label
          description: >-
            That kind's name in your own system, where your workflow gives it
            one. The same label on the same document across branches is how you
            find it without knowing which branch ran.
        filename:
          type: string
          title: Filename
          description: The original filename.
        content_type:
          type: string
          title: Content Type
          description: The file's MIME type.
        size_bytes:
          type: integer
          title: Size Bytes
          description: The file's size in bytes.
        sha256:
          type: string
          title: Sha256
          description: The SHA-256 hash of the file's contents.
        received_at:
          type: string
          format: date-time
          title: Received At
          description: When the file was received.
      type: object
      required:
        - id
        - slot
        - kind
        - label
        - filename
        - content_type
        - size_bytes
        - sha256
        - received_at
      title: DocumentRecord
      description: >-
        One file on the record, whether the user uploaded it or the step fetched
        it on their behalf.
    ErrorResponse:
      properties:
        code:
          $ref: '#/components/schemas/ErrorCode'
          description: >-
            What went wrong. Branch on this rather than on the status code,
            which several causes share, and never on `detail`.


            Treat a code you do not recognise as the status it arrived at: codes
            are added as new causes become reachable, and an existing one never
            changes meaning.
        detail:
          type: string
          title: Detail
          description: >-
            What went wrong, in words, naming the workflow, verification, step
            or field at fault where one is. Written for a person reading a log
            rather than for code to read, so it is not stable and nothing should
            be matched against it.
      type: object
      required:
        - code
        - detail
      title: ErrorResponse
      description: The envelope every failure is answered in.
    ErrorCode:
      type: string
      enum:
        - unauthenticated
        - client-not-enrolled
        - rate-limited
        - auth-unavailable
        - environment-mismatch
        - request-invalid
        - body-unreadable
        - page-cursor-invalid
        - window-invalid
        - request-too-large
        - not-found
        - method-not-allowed
        - internal-error
        - workflow-not-found
        - workflow-version-not-found
        - workflow-not-runnable
        - workflow-not-walkable
        - workflow-misconfigured
        - context-incomplete
        - context-value-invalid
        - mobile-required
        - journey-fields-refused
        - return-url-not-allowed
        - reference-taken
        - verification-not-found
        - verification-not-open
        - verification-expired
        - run-in-progress
        - credits-exhausted
        - resets-exhausted
        - live-limit-reached
        - step-not-found
        - step-not-ready
        - step-invalid
        - document-not-found
        - document-not-stored
        - document-too-large
        - document-type-not-allowed
      title: ErrorCode
      description: >-
        What went wrong, as a value to branch on.


        Published vocabulary, stated here rather than shared with any other
        product of ours: these are read by integrations we do not control, so a
        name here means what it says for as long as the endpoint answering it
        exists.


        One code names one cause, so a status carrying several causes is told
        apart by the code and never by the message. Codes are added as new
        causes become reachable; an integration therefore treats an unrecognised
        code as the status it arrived at.
  securitySchemes:
    HTTPBearer:
      type: http
      scheme: bearer

````

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