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

# Submit a document

> Uploads one document for a case, either for the case as a whole (`tenantId` omitted) or for one tenant.

Accepted while the case is `pending-documentation` or `ready-to-issue`. Accepted formats: PDF, JPEG and PNG, up to 10 MB each.
Documents are only stored here; they reach the insurer when the policy is issued. When the last missing requirement is met, the case moves to `ready-to-issue`.




## OpenAPI

````yaml /openapi/arco-partner-api.json post /non-payment-insurance/cases/{caseId}/documents
openapi: 3.1.0
info:
  title: Vivara Partner API
  version: 1.0.0
  description: >
    Vivara's external API for tenant scoring and non-payment insurance.


    ## Authentication


    Every request must include the `X-API-Key` header with the API key issued to
    the integration. Keys are scoped per API client: scorings and cases created
    with one key are not visible to other clients.


    ## Conventions


    - Money is always a number in euros with up to two decimals (`1250.5` =
    1,250.50 €).

    - Dates are `YYYY-MM-DD`; timestamps are ISO 8601 in UTC.

    - Every answer that carries a resource wraps it in `data`.

    - Every error has the same body: `status`, `statusCode`, `code` and
    `message`. Use `code`, which is stable, to decide what to do. The catalog of
    codes is in the guides.


    ## Processing model


    **Scorings.** Creating or updating a scoring evaluates every candidature
    synchronously and returns no body. Read the results with `GET
    /scorings/{scoringId}`.


    In this release, evaluation finishes during create or update and `status` is
    `completed`. Document upload and asynchronous evaluation will arrive in a
    later release (`processing` and candidature status `pending`).


    **Non-payment insurance.** You generate the id of the case (a UUID v4) and
    open it with `POST /non-payment-insurance/cases`, which prices the offer
    synchronously and answers `201` with a `Location` header and no body. A case
    id that was already used answers `409`, whatever the data. Read the case,
    with its offer and the documents it requires, with `GET
    /non-payment-insurance/cases/{caseId}`. Upload the documents, issue the
    policy with `POST /non-payment-insurance/cases/{caseId}/policy`, and poll
    the case with exponential backoff (at least 30 s between calls) until it is
    `policy-issued` or `rejected`.


    ## Edge, routing and rate limiting


    Traffic is served through Azure Front Door, which terminates TLS at
    `https://api.vivara.io/v1/*` and forwards requests to the partner API.

    The Front Door web application firewall enforces rate limits per client IP.
    When a limit is exceeded it returns `429 Too Many Requests`; requests
    blocked by firewall rules return `403 Forbidden`. Those two answers come
    from the firewall and have no `code`. A `403` with `code: forbidden` comes
    from the API, when the API client has no access to it. Rate limit values are
    shared during onboarding.


    ## Data protection


    Requests contain personal data (DNI or NIE, income, and the documents of the
    people on a rental contract). Delete a scoring with `DELETE
    /scorings/{scoringId}` once it is no longer needed. Remove a document
    uploaded by mistake with `DELETE
    /non-payment-insurance/cases/{caseId}/documents/{documentId}` while the case
    is `pending-documentation` or `ready-to-issue`. Cases cannot be deleted.
servers:
  - url: https://api.vivara.io/v1
    description: Production
  - url: https://api-stg.vivara.io/v1
    description: Staging
security:
  - apiKeyAuth: []
tags:
  - name: Scorings
    description: >-
      Score candidates for a property and prioritize who to offer a visit to
      first
  - name: Non-payment insurance
    description: >-
      Quote, document upload and policy issuance for rental non-payment
      insurance cases
paths:
  /non-payment-insurance/cases/{caseId}/documents:
    post:
      tags:
        - Non-payment insurance
      summary: Submit a document
      description: >
        Uploads one document for a case, either for the case as a whole
        (`tenantId` omitted) or for one tenant.


        Accepted while the case is `pending-documentation` or `ready-to-issue`.
        Accepted formats: PDF, JPEG and PNG, up to 10 MB each.

        Documents are only stored here; they reach the insurer when the policy
        is issued. When the last missing requirement is met, the case moves to
        `ready-to-issue`.
      operationId: uploadCaseDocument
      parameters:
        - $ref: '#/components/parameters/CaseId'
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/UploadDocumentRequest'
            encoding:
              file:
                contentType: application/pdf, image/jpeg, image/png
      responses:
        '201':
          description: Document stored
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UploadDocumentResponse'
        '400':
          $ref: '#/components/responses/BadRequestError'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '409':
          $ref: '#/components/responses/ConflictError'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
        '500':
          $ref: '#/components/responses/InternalServerError'
components:
  parameters:
    CaseId:
      name: caseId
      in: path
      required: true
      description: The case id, a UUID v4 you generate before quoting.
      schema:
        type: string
        format: uuid
        example: 3f9c2a7e-58b1-4d0a-9a51-0c6e2f1b7d42
  schemas:
    UploadDocumentRequest:
      type: object
      description: A document to store in the case, sent as multipart form data.
      required:
        - type
        - file
      properties:
        type:
          $ref: '#/components/schemas/DocumentType'
        tenantId:
          type: string
          format: uuid
          description: >-
            The tenant the document belongs to (`tenants[].id`). Omit it for
            case documents (`data-protection-consent`).
          example: 6b1e0d44-2c3f-4b8e-a0f2-91d7e5c3a810
        file:
          type: string
          format: binary
          description: The document. PDF, JPEG or PNG, up to 10 MB.
    UploadDocumentResponse:
      type: object
      description: >-
        The stored document and the updated documentation of the case, wrapped
        in `data`.
      required:
        - data
      properties:
        data:
          type: object
          description: The stored document and the updated documentation of the case.
          required:
            - document
            - documentation
          properties:
            document:
              $ref: '#/components/schemas/UploadedDocument'
            documentation:
              $ref: '#/components/schemas/Documentation'
    DocumentType:
      type: string
      enum:
        - data-protection-consent
        - identity-document
        - employment-contract
        - work-history-report
        - payslip
        - income-tax-return
        - quarterly-tax-return
        - pension-certificate
        - company-tax-id-card
        - corporate-income-tax-return
        - power-of-attorney
        - guarantor-identity-document
        - guarantor-income-proof
        - military-commitment
      description: >
        `data-protection-consent` is the signed data protection annex (Anexo
        LOPD), a case document signed by the landlord and every tenant. Every
        other type is a tenant document.
      example: payslip
    UploadedDocument:
      type: object
      required:
        - id
        - type
        - fileName
        - uploadedAt
      properties:
        id:
          type: string
          description: The document id. Use it to remove the document.
          format: uuid
          example: c41a8e93-7d2b-4f6a-b5e1-2a9f0c8d3e17
        type:
          $ref: '#/components/schemas/DocumentType'
        tenantId:
          type: string
          description: The tenant the document belongs to. Absent for case documents.
          format: uuid
          example: 6b1e0d44-2c3f-4b8e-a0f2-91d7e5c3a810
        fileName:
          type: string
          description: Name of the uploaded file.
          example: nomina-septiembre.pdf
        uploadedAt:
          type: string
          description: When the document was stored.
          format: date-time
          example: '2026-10-26T10:02:41Z'
    Documentation:
      type: object
      description: >-
        The documentation of the case: what is required, what is missing and
        what was uploaded.
      required:
        - complete
        - missing
        - documents
      properties:
        complete:
          type: boolean
          description: Every requirement is met, so the policy can be issued.
          example: false
        sentToInsurerAt:
          type:
            - string
            - 'null'
          format: date-time
          description: >-
            When the documents were merged and sent to the insurer, as part of
            the issuance. `null` until then.
          example: null
        missing:
          type: array
          description: Requirements still unmet. Empty once everything is uploaded.
          items:
            $ref: '#/components/schemas/DocumentRequirement'
        documents:
          type: array
          description: Documents uploaded so far.
          items:
            $ref: '#/components/schemas/UploadedDocument'
    Error:
      type: object
      required:
        - status
        - statusCode
        - code
        - message
      properties:
        status:
          type: string
          description: HTTP reason phrase.
        statusCode:
          type: integer
          description: HTTP status code.
        code:
          type: string
          description: >-
            Stable, machine-readable error code in kebab-case. Use it, not the
            message, to decide what to do. The full list is in the error catalog
            of the guides (Key concepts, Errors).
        message:
          type: string
          description: >-
            Human-readable explanation, in English. It names the fields or the
            documents behind the error.
    DocumentRequirement:
      type: object
      description: A requirement the uploaded documents must still meet.
      required:
        - anyOf
        - minFiles
        - uploadedFiles
      properties:
        tenantId:
          type: string
          format: uuid
          description: The tenant the requirement is for. Absent for case requirements.
          example: 6b1e0d44-2c3f-4b8e-a0f2-91d7e5c3a810
        anyOf:
          type: array
          description: Any of these document types satisfies the requirement.
          items:
            $ref: '#/components/schemas/DocumentType'
          example:
            - employment-contract
            - work-history-report
        minFiles:
          type: integer
          description: How many files the requirement needs.
          example: 1
        uploadedFiles:
          type: integer
          description: How many files were uploaded for it so far.
          example: 0
        templateUrl:
          type: string
          format: uri
          description: >-
            A template to fill in and sign, when the document is one Vivara
            provides.
          example: https://files.vivara.es/npi/templates/anexo-lopd.pdf
  responses:
    BadRequestError:
      description: >-
        Bad request. The body, path or headers do not match the expected shape
        or types.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            invalidScoring:
              summary: Invalid scoring
              value:
                status: Bad Request
                statusCode: 400
                code: validation-error
                message: >-
                  "candidatures[0].members[0].netAnnualIncomeAmountInEuros" must
                  be a number
            invalidQuote:
              summary: Invalid quote
              value:
                status: Bad Request
                statusCode: 400
                code: validation-error
                message: '"insuredProperty" is required'
    UnauthorizedError:
      description: >-
        Unauthorized. The X-API-Key header is missing or the API key is not
        valid.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            status: Unauthorized
            statusCode: 401
            code: invalid-api-key
            message: Invalid API key
    ForbiddenError:
      description: >-
        Forbidden. The API client has no access to this API, or the request was
        blocked by the edge firewall.
      content:
        application/json:
          schema:
            oneOf:
              - $ref: '#/components/schemas/Error'
              - 179b90ba-7311-450d-aaec-7de48cb6a4fb
          examples:
            forbidden:
              summary: The API client has no access to this API
              value:
                status: Forbidden
                statusCode: 403
                code: forbidden
                message: The API client has no access to this API
            firewall:
              summary: Blocked by the edge firewall
              value:
                status: Forbidden
                statusCode: 403
                message: Request blocked by the firewall
    NotFoundError:
      description: >-
        Not found. The scoring, the case or the document does not exist for the
        authenticated API client.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            scoringNotFound:
              summary: Scoring not found
              value:
                status: Not Found
                statusCode: 404
                code: scoring-not-found
                message: Scoring 6d1f0c3e-5b8a-4c1e-9f2a-3b7e8d4a1c90 not found
            caseNotFound:
              summary: Case not found
              value:
                status: Not Found
                statusCode: 404
                code: case-not-found
                message: No case exists with id 3f9c2a7e-58b1-4d0a-9a51-0c6e2f1b7d42.
            documentNotFound:
              summary: Document not found in the case
              value:
                status: Not Found
                statusCode: 404
                code: document-not-found
                message: >-
                  No document exists with id
                  c41a8e93-7d2b-4f6a-b5e1-2a9f0c8d3e17 in case
                  3f9c2a7e-58b1-4d0a-9a51-0c6e2f1b7d42.
            pathNotFound:
              summary: The path does not exist
              value:
                status: Not Found
                statusCode: 404
                code: not-found
                message: Cannot GET /non-payment-insurance/unknown
    ConflictError:
      description: >-
        Conflict. The id is already in use with other data, or the status of the
        resource does not allow the operation.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            scoringAlreadyExists:
              summary: Scoring id already in use
              value:
                status: Conflict
                statusCode: 409
                code: scoring-already-exists
                message: Scoring 6d1f0c3e-5b8a-4c1e-9f2a-3b7e8d4a1c90 already exists
            caseAlreadyExists:
              summary: Case id already used with different data
              value:
                status: Conflict
                statusCode: 409
                code: case-already-exists
                message: Case 3f9c2a7e-58b1-4d0a-9a51-0c6e2f1b7d42 already exists.
            invalidCaseStatus:
              summary: The case status does not allow the action
              value:
                status: Conflict
                statusCode: 409
                code: invalid-case-status
                message: >-
                  The policy can only be issued for a ready-to-issue case; this
                  case is rejected.
    PayloadTooLarge:
      description: The file exceeds 10 MB
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            status: Payload Too Large
            statusCode: 413
            code: file-too-large
            message: Files can be up to 10 MB.
    TooManyRequestsError:
      description: >-
        Too many requests. The rate limit enforced by Azure Front Door was
        exceeded. Retry with exponential backoff.
      content:
        application/json:
          schema:
            type: object
            description: >-
              Error answered by the edge firewall (Azure Front Door), before the
              request reaches the API. It has no `code`.
            required:
              - status
              - statusCode
              - message
            properties:
              status:
                type: string
                description: HTTP reason phrase.
              statusCode:
                type: integer
                description: HTTP status code.
              message:
                type: string
                description: Human-readable explanation.
          example:
            status: Too Many Requests
            statusCode: 429
            message: Rate limit exceeded
    InternalServerError:
      description: Internal server error.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            status: Internal Server Error
            statusCode: 500
            code: internal-error
            message: Something went wrong
  securitySchemes:
    apiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: API key issued per external API client.

````

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