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

# Create a scoring

> Creates a scoring for one property and evaluates every candidature in the request.

For each candidature, Vivara calculates:

- **Solvency** — whether the group's income can cover the rent (effort rate).
- **Delinquency** — whether any member appears in a delinquency register, when every member has an ID number.
- **Rank** — visit-prioritization order among candidatures in this scoring.

Solvency and delinquency are independent results.

**Optional fields.** Omit `personalId` and `workingStatus` when they are unknown. Explicit `null` is also accepted and has the same meaning as omission. Both forms are normalized to `null`. When employment is unknown, send `netAnnualIncomeAmountInEuros` for solvency. Self-employed members must set `workingStatus` to `self-employed` and send `form100` and `form130`.

**Confidence tier.** `basic` when any member omits `personalId` or `workingStatus`; `declared` when every member provides both.

**Response.** `201` with no body and a `Location` header. Call `GET` on that URL to read the results.

**Partial failures.** If a delinquency check cannot be completed, the candidature is `failed` with reason `service-unavailable`. The scoring is still created.

**Errors.**
- `400` — invalid types, unknown fields, unsupported working statuses, or missing or incompatible income fields.
- `422` — invalid domain values, duplicated references or personal ids, or more than 20 candidatures or 6 members per candidature.
- `409` — the scoring id was already used by this API client (other clients may reuse the same id).




## OpenAPI

````yaml /openapi/arco-partner-api.json post /scorings
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:
  /scorings:
    post:
      tags:
        - Scorings
      summary: Create a scoring
      description: >
        Creates a scoring for one property and evaluates every candidature in
        the request.


        For each candidature, Vivara calculates:


        - **Solvency** — whether the group's income can cover the rent (effort
        rate).

        - **Delinquency** — whether any member appears in a delinquency
        register, when every member has an ID number.

        - **Rank** — visit-prioritization order among candidatures in this
        scoring.


        Solvency and delinquency are independent results.


        **Optional fields.** Omit `personalId` and `workingStatus` when they are
        unknown. Explicit `null` is also accepted and has the same meaning as
        omission. Both forms are normalized to `null`. When employment is
        unknown, send `netAnnualIncomeAmountInEuros` for solvency. Self-employed
        members must set `workingStatus` to `self-employed` and send `form100`
        and `form130`.


        **Confidence tier.** `basic` when any member omits `personalId` or
        `workingStatus`; `declared` when every member provides both.


        **Response.** `201` with no body and a `Location` header. Call `GET` on
        that URL to read the results.


        **Partial failures.** If a delinquency check cannot be completed, the
        candidature is `failed` with reason `service-unavailable`. The scoring
        is still created.


        **Errors.**

        - `400` — invalid types, unknown fields, unsupported working statuses,
        or missing or incompatible income fields.

        - `422` — invalid domain values, duplicated references or personal ids,
        or more than 20 candidatures or 6 members per candidature.

        - `409` — the scoring id was already used by this API client (other
        clients may reuse the same id).
      operationId: createScoring
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateScoringRequest'
            examples:
              createScoring:
                $ref: '#/components/examples/CreateScoringRequest'
      responses:
        '201':
          description: Scoring created and evaluated.
          headers:
            Location:
              $ref: '#/components/headers/Location'
        '400':
          $ref: '#/components/responses/BadRequestError'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '409':
          $ref: '#/components/responses/ConflictError'
        '422':
          $ref: '#/components/responses/UnprocessableEntityError'
        '429':
          $ref: '#/components/responses/TooManyRequestsError'
        '500':
          $ref: '#/components/responses/InternalServerError'
components:
  schemas:
    CreateScoringRequest:
      type: object
      required:
        - id
        - postalCode
        - monthlyPaymentAmountInEuros
        - candidatures
      properties:
        id:
          type: string
          format: uuid
          description: >-
            Scoring identifier generated by the API client. Must be unique
            within the authenticated API client. Different clients can use the
            same id. Used for subsequent GET, PUT and DELETE calls.
        postalCode:
          type: string
          pattern: ^\d{5}$
          description: >-
            Spanish postal code of the property (five digits). Fixed at
            creation.
        monthlyPaymentAmountInEuros:
          type: number
          exclusiveMinimum: 0
          description: >-
            Monthly property payment (rent plus monthly charges) in euros (EUR),
            up to two decimals. Fixed at creation.
        candidatures:
          type: array
          minItems: 1
          maxItems: 20
          description: Candidate groups interested in the property.
          items:
            type: object
            required:
              - externalReference
              - members
            properties:
              externalReference:
                type: string
                minLength: 1
                description: >-
                  Candidature identifier generated by the API client. Must be
                  unique within the scoring and can be reused across scorings.
                  Returned in the scoring results.
              members:
                type: array
                minItems: 1
                maxItems: 6
                description: People who would rent together.
                items:
                  type: object
                  additionalProperties: false
                  description: >
                    Declared data for one person in a candidature.


                    Delinquency runs only when every member has a `personalId`.
                    Prefer omitting `personalId` or `workingStatus` when you do
                    not have that information yet. Explicit `null` is also
                    accepted and has the same meaning as omission; both are
                    normalized to `null`. For PUT, omitted fields do not
                    preserve previously declared values because the members are
                    replaced in full.


                    Invalid types, unknown fields or incompatible income fields
                    for the working status return `400`. Invalid amounts return
                    `422`.
                  properties:
                    personalId:
                      type:
                        - string
                        - 'null'
                      description: >-
                        Spanish DNI or NIE. Unique within the scoring when
                        provided.
                    workingStatus:
                      $ref: 113cc748-63c0-4b70-8590-dfc1afbc46a6
                      description: >-
                        Optional employment situation. Omit when unknown;
                        explicit null is equivalent. Responses include
                        workingStatus as null in either case.
                    netAnnualIncomeAmountInEuros:
                      type: number
                      exclusiveMinimum: 0
                      description: >-
                        Annual net income in euros, up to two decimals. Required
                        for employed, retired, or when `workingStatus` is
                        omitted or null. Not used for self-employed (use
                        `form100` and `form130`).
                    form100:
                      type: object
                      additionalProperties: false
                      description: >-
                        Required for self-employed members. Annual Modelo 100
                        net income, retained for reference and excluded from the
                        scoring calculation.
                      required:
                        - netIncomeAmountInEuros
                      properties:
                        netIncomeAmountInEuros:
                          type: number
                          exclusiveMinimum: 0
                          description: Annual net income in euros, up to two decimals.
                    form130:
                      type: object
                      additionalProperties: false
                      description: >-
                        Required for self-employed members alongside form100,
                        replacing generic annual income. Scoring uses only
                        cumulative form130 divided by the months covered by the
                        previous calendar quarter at scoring time
                        (Europe/Madrid). Scoring in Q1 divides by 12, Q2 by 3,
                        Q3 by 6 and Q4 by 9.
                      required:
                        - netIncomeAmountInEuros
                      properties:
                        netIncomeAmountInEuros:
                          type: number
                          exclusiveMinimum: 0
                          description: >-
                            Cumulative net income in euros (EUR), up to two
                            decimals, not the tax payment or a monthly amount.
                          example: 1411
                  oneOf:
                    - properties:
                        workingStatus:
                          enum:
                            - employed
                            - retired
                            - null
                      required:
                        - netAnnualIncomeAmountInEuros
                      not:
                        anyOf:
                          - required:
                              - form100
                          - required:
                              - form130
                    - properties:
                        workingStatus:
                          enum:
                            - self-employed
                      required:
                        - workingStatus
                        - form100
                        - form130
                      not:
                        required:
                          - netAnnualIncomeAmountInEuros
    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.
  examples:
    CreateScoringRequest:
      summary: Declared, income-only and self-employed candidatures
      value:
        id: 6d1f0c3e-5b8a-4c1e-9f2a-3b7e8d4a1c90
        postalCode: '08001'
        monthlyPaymentAmountInEuros: 1200
        candidatures:
          - externalReference: cand-001
            members:
              - personalId: 12345678Z
                workingStatus: employed
                netAnnualIncomeAmountInEuros: 25200
              - personalId: X1234567L
                workingStatus: retired
                netAnnualIncomeAmountInEuros: 21000
          - externalReference: cand-002
            members:
              - netAnnualIncomeAmountInEuros: 30000
          - externalReference: cand-003
            members:
              - personalId: 87654321X
                workingStatus: self-employed
                form100:
                  netIncomeAmountInEuros: 44000
                form130:
                  netIncomeAmountInEuros: 28800
  headers:
    Location:
      description: URL of the created scoring.
      schema:
        type: string
      example: /v1/scorings/6d1f0c3e-5b8a-4c1e-9f2a-3b7e8d4a1c90
  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
    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.
    UnprocessableEntityError:
      description: >
        Unprocessable entity. The body is well formed but breaks a business rule
        (such as a duplicated candidature externalReference, a duplicated
        personal id within the scoring, an invalid DNI or NIE, or a change of
        fields that are fixed at creation), or the case lacks documents the
        insurer requires.

        Product validation codes are `scoring-has-invalid-candidature-count`,
        `scoring-has-invalid-member-count`,
        `scoring-has-duplicated-candidature-reference` and
        `scoring-has-duplicated-member`. Other field validation errors may omit
        `code`.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            duplicatedPersonalId:
              summary: Personal id repeated within the scoring
              value:
                status: Unprocessable Entity
                statusCode: 422
                code: scoring-has-duplicated-member
                message: personal id 12345678Z is duplicated in the scoring
            immutableFieldChanged:
              summary: Postal code changed on update
              value:
                status: Unprocessable Entity
                statusCode: 422
                code: immutable-field-changed
                message: Postal code cannot be changed after creation
            documentationIncomplete:
              summary: >-
                Documents missing when issuing the policy. Nothing was sent to
                the insurer
              value:
                status: Unprocessable Entity
                statusCode: 422
                code: documentation-incomplete
                message: >-
                  2 required documents are missing: tenant
                  6b1e0d44-2c3f-4b8e-a0f2-91d7e5c3a810 needs 2 of payslip (1
                  uploaded); case needs 1 of data-protection-consent (0
                  uploaded).
            ownerDataIncomplete:
              summary: The landlord data the insurer needs to issue is missing
              value:
                status: Unprocessable Entity
                statusCode: 422
                code: owner-data-incomplete
                message: >-
                  The insurer needs more data about the landlord: missing
                  owner.email, owner.address
            unreadableDocument:
              summary: A stored document cannot be read
              value:
                status: Unprocessable Entity
                statusCode: 422
                code: unreadable-document
                message: The document payslip.pdf cannot be read.
            documentationTooLarge:
              summary: The merged documentation is too large for the insurer
              value:
                status: Unprocessable Entity
                statusCode: 422
                code: documentation-too-large
                message: >-
                  The documents of the case are too large to send to the
                  insurer.
    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.