AltScore
Borrower Central (BC)

Documents API

This API allows managing documents in the system. It provides endpoints to create, read, update, and delete documents, as well as to handle their attachments.

The Document Object

The Document object represents a document in the system.

{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "borrowerId": "789e4567-e89b-12d3-a456-426614174001",
  "key": "identification",
  "label": "Identification Document",
  "value": "123456789",
  "tags": ["personal", "verified"],
  "createdAt": "2023-01-01T00:00:00Z",
  "updatedAt": "2023-01-01T00:00:00Z",
  "hasAttachments": true,
  "processingStatus": null
}

Attributes

AttributeDescriptionType
idUnique identifier of the documentString
borrowerIdID of the borrower associated with the documentString
keyKey identifying the type of documentString
labelDescriptive label of the documentString
valueValue associated with the documentAny
tagsTags associated with the documentArray of String
createdAtDocument creation date and timeString (ISO 8601)
updatedAtLast update date and timeString (ISO 8601)
hasAttachmentsIndicates if the document has attachmentsBoolean
processingStatusStatus of the last document processing run after manual reviews: valid, invalid or needs_review. null when the document was never processedString or null

Available Operations

Create a New Document

Creates a new document in the system.

POST /v1/documents

Input Parameters

ParameterDescriptionTypeRequired
borrowerIdID of the borrower associated with the documentStringYes
keyKey identifying the type of documentStringYes
valueValue associated with the documentAnyNo
tagsTags associated with the documentArray of StringNo

Request Example

{
  "borrowerId": "789e4567-e89b-12d3-a456-426614174001",
  "key": "identification",
  "value": "123456789",
  "tags": ["personal", "verified"]
}

Successful Response

{
  "id": "123e4567-e89b-12d3-a456-426614174000"
}

Update a Document

Updates an existing document.

PATCH /v1/documents/:document_id

Path Parameters

ParameterDescription
:document_idID of the document to update

Input Parameters

ParameterDescriptionTypeRequired
keyNew document keyStringNo
valueNew value associated with the documentAnyNo
tagsNew tags associated with the documentArray of StringNo

Request Example

{
  "value": "987654321",
  "tags": ["personal", "verified", "updated"]
}

Successful Response

Returns the updated Document object.

Retrieve a Document

Retrieves the information of a specific document.

GET /v1/documents/:document_id

Path Parameters

ParameterDescription
:document_idID of the document to retrieve

Successful Response

Returns the complete Document object.

Delete a Document

Deletes a document from the system.

DELETE /v1/documents/:document_id

Path Parameters

ParameterDescription
:document_idID of the document to delete

Successful Response

Status code 204 (No Content) if the deletion was successful.

List Documents

Retrieves a paginated list of documents with filtering and sorting options.

GET /v1/documents

Query Parameters

ParameterDescriptionType
borrower-idFilter by borrower IDString
searchText to search in documentsString
pagePage number (default: 1)Integer
per-pageItems per page (default: 10)Integer
keyFilter by document keyString
sort-byField to sort byString
sort-directionSorting direction (asc or desc)String

Successful Response

Paginated list of Document objects.

Add an Attachment to a Document

Adds a new attachment to an existing document.

POST /v1/documents/:document_id/attachments

Path Parameters

ParameterDescription
:document_idID of the document to add attachment to

Input Parameters

ParameterDescriptionTypeRequired
urlURL of the attached fileStringYes
labelDescriptive label of the attachmentStringNo
fileExtensionFile extensionStringNo
metadataMetadata associated with the attachmentObjectNo

Request Example

{
  "url": "https://example.com/file.pdf",
  "label": "Identity Document",
  "fileExtension": "pdf",
  "metadata": {
    "size": 1024,
    "pageCount": 2
  }
}

Successful Response

Status code 201 (Created) if the attachment was successfully added.

Upload an Attachment to a Document

Uploads a file as an attachment to an existing document.

POST /v1/documents/:document_id/attachments/upload

Path Parameters

ParameterDescription
:document_idID of the document to upload attachment to

Form Parameters

ParameterDescriptionTypeRequired
fileFile to uploadFileYes
labelDescriptive label of the attachmentStringNo
metadataMetadata in JSON formatStringNo

Successful Response

Status code 201 (Created) if the file was successfully uploaded.

Retrieve Attachments of a Document

Retrieves the list of attachments associated with a document.

GET /v1/documents/:document_id/attachments

Path Parameters

ParameterDescription
:document_idID of the document to retrieve attachments from

Successful Response

List of Attachment objects associated with the document.

Delete an Attachment from a Document

Deletes a specific attachment from a document.

DELETE /v1/documents/:document_id/attachments/:attachment_id

Path Parameters

ParameterDescription
:document_idID of the document
:attachment_idID of the attachment to delete

Successful Response

Status code 204 (No Content) if the attachment was successfully deleted. The page images and crops kept by document processing for that attachment are deleted with it.

Process a Document

Reads attachments of a document with the processing definition of its data model, extracting and validating what the definition asks for, and stores the results. It is what the Extract data, Validate or Extract and validate button of a file does in the Hub, which always sends that one file in attachmentIds. The Document Processing guide explains what is computed and how to set it up.

POST /v1/documents/:document_id/process

Requires the borrowers.write permission. The data model whose key is the document's key must have a processing definition with enabled: true.

Path Parameters

ParameterDescription
:document_idID of the document to process

Input Parameters

ParameterDescriptionTypeRequired
attachmentIdsAttachments to process, up to 20. Absent or empty: every attachment of the documentArray of StringNo

Request Example

{
  "attachmentIds": ["a1b2c3d4-0000-4000-8000-000000000001"]
}

The call waits for the run to finish, which can take a few minutes for files with many pages; allow up to five minutes before timing out. Only one run per document at a time: a second one while the first is running gets 409 with DOCUMENT_PROCESSING_IN_PROGRESS.

Attachments that are not PDF, JPG, JPEG, PNG, WEBP, HEIC or HEIF are not read; their result is stored with outcome unsupported and they do not count toward the document's status. Attachments that were not selected keep their earlier results and their reviews, as long as they were produced with the current definition, and rules that compare files take them into account. That includes attachments that were never read (too_large, failed, unreadable, unsupported): reading one attachment again does not drop another one that was ignored. Results of other attachments produced with an earlier definition are not counted until those attachments are read again.

A run replaces the results and the evidence of the attachments it read, the reviews of those attachments, and every review of a document validation.

Successful Response

The same body as Get the Processing Results of a Document, after the run was stored.

Get the Processing Results of a Document

Returns the stored results of a document and of each of its attachments, with signed links to the page images and crops.

GET /v1/documents/:document_id/processing

Requires the bc.attachments.read permission, because the response gives access to images of the files.

Path Parameters

ParameterDescription
:document_idID of the document

Successful Response

Abridged: one attachment, two of its four fields and one of its two validations.

{
  "documentId": "123e4567-e89b-12d3-a456-426614174000",
  "dataModelKey": "planilla_luz",
  "definition": { "configured": true, "enabled": true, "definitionVersion": 3, "definitionHash": "sha256:5d1c...", "mode": "both" },
  "summary": {
    "lastRunId": "7f3a...",
    "revision": 1,
    "ranAt": "2026-10-21T15:00:00Z",
    "updatedAt": "2026-10-21T15:00:00Z",
    "definitionVersion": 3,
    "definitionHash": "sha256:5d1c...",
    "modelId": "model-id",
    "modelCalls": 2,
    "evaluatedAttachmentIds": ["a1b2c3d4-0000-4000-8000-000000000001"],
    "status": "needs_review",
    "statusReasons": [{ "code": "accepted_type_pending_review", "key": "planilla_luz" }],
    "effectiveStatus": "needs_review",
    "effectiveStatusReasons": [{ "code": "accepted_type_pending_review", "key": "planilla_luz" }],
    "acceptedTypes": {
      "mode": "anyOf",
      "types": ["planilla_luz"],
      "satisfied": false,
      "satisfiable": true,
      "satisfiedBy": { "planilla_luz": [] },
      "pendingBy": { "planilla_luz": ["a1b2c3d4-0000-4000-8000-000000000001"] },
      "missingTypes": [],
      "unreadFiles": [],
      "invalidBy": { "planilla_luz": [] }
    },
    "validations": [],
    "resultsCurrent": true,
    "attachmentsChanged": false,
    "extractionStatus": "needs_review",
    "validation": { "status": "needs_review", "valid": null }
  },
  "attachments": [
    {
      "attachmentId": "a1b2c3d4-0000-4000-8000-000000000001",
      "label": "Planilla septiembre",
      "fileExtension": "jpg",
      "createdAt": "2026-10-21T14:58:00Z",
      "processing": {
        "runId": "7f3a...",
        "ranAt": "2026-10-21T15:00:00Z",
        "definitionVersion": 3,
        "definitionHash": "sha256:5d1c...",
        "modelId": "model-id",
        "context": { "now": "2026-10-21T15:00:00Z" },
        "outcome": "processed",
        "partial": false,
        "error": null,
        "warnings": [],
        "classificationCalls": 1,
        "extractionAttempts": 1,
        "documentType": { "key": "planilla_luz", "label": "Planilla de luz", "confidence": 0.875, "minConfidence": 0.6 },
        "fields": [
          {
            "key": "fecha_emision",
            "label": "Fecha de emisión",
            "type": "date",
            "required": true,
            "minConfidence": 0.85,
            "value": "02/10/2026",
            "typedValue": "2026-10-02",
            "formatFlags": [],
            "confidence": 0.812,
            "confidenceBreakdown": {
              "grounding": 1.0,
              "ocrMatch": 0.625,
              "format": 1.0,
              "ocrSource": "tesseract",
              "ocrText": "FECHA DE EMISION O2/1O/2O26"
            },
            "page": 1,
            "boundingBox": { "yMin": 96, "xMin": 610, "yMax": 118, "xMax": 930 },
            "sourceText": "Fecha de emisión: 02/10/2026",
            "status": "needs_review",
            "needsReview": true,
            "effectiveStatus": "needs_review",
            "evidence": { "pageUrl": "https://storage.example.com/...", "cropUrl": "https://storage.example.com/..." }
          },
          {
            "key": "monto_total",
            "label": "Total a pagar",
            "type": "number",
            "required": true,
            "minConfidence": 0.7,
            "value": "$ 42.75",
            "typedValue": 42.75,
            "formatFlags": [],
            "confidence": 1.0,
            "confidenceBreakdown": {
              "grounding": 1.0,
              "ocrMatch": 1.0,
              "format": 1.0,
              "ocrSource": "tesseract",
              "ocrText": "TOTAL A PAGAR $ 42.75"
            },
            "page": 1,
            "boundingBox": { "yMin": 402, "xMin": 640, "yMax": 430, "xMax": 930 },
            "sourceText": "TOTAL A PAGAR $ 42.75",
            "status": "ok",
            "needsReview": false,
            "effectiveStatus": "ok",
            "evidence": { "pageUrl": "https://storage.example.com/...", "cropUrl": "https://storage.example.com/..." }
          }
        ],
        "validations": [
          {
            "key": "vigente",
            "label": "Emitida en los últimos 60 días",
            "severity": "blocking",
            "status": "inconclusive",
            "reason": "field_low_confidence",
            "failedRules": [],
            "inconclusiveRules": [{ "path": "", "field": "fecha_emision", "reason": "field_low_confidence" }],
            "effectiveStatus": "inconclusive"
          }
        ],
        "logic": [
          {
            "key": "aplica_beneficio",
            "label": "Total menor a 50: aplica el beneficio",
            "result": true,
            "status": "evaluated",
            "reason": null,
            "inconclusiveRules": [],
            "effectiveResult": true
          }
        ],
        "status": "needs_review",
        "statusReasons": [
          { "code": "required_field_needs_review", "key": "fecha_emision" },
          { "code": "blocking_validation_inconclusive", "key": "vigente" }
        ],
        "effectiveStatus": "needs_review",
        "effectiveStatusReasons": [
          { "code": "required_field_needs_review", "key": "fecha_emision" },
          { "code": "blocking_validation_inconclusive", "key": "vigente" }
        ],
        "pages": [{ "page": 1, "width": 1200, "height": 1600, "textLayer": false, "url": "https://storage.example.com/..." }],
        "pageCount": 1,
        "evidenceTruncated": false,
        "resultsCurrent": true
      },
      "extraction": { "...": "see below" },
      "validation": { "...": "see below" }
    }
  ],
  "reviews": [],
  "running": null
}

How to read it:

  • summary is null when the document was never processed. status is what the run produced and effectiveStatus is the status after manual reviews; use the effective one. resultsCurrent: false means the definition changed after the run, and attachmentsChanged: true means attachments were added or removed since. Either way, run the document again. revision goes up with every run and every review, and modelCalls counts the calls to the AI model in the last run.
  • attachments[].processing is null for an attachment that was never processed.
  • outcome is processed, unclassified (matched no type), unreadable, failed (the AI provider did not answer), too_large (more than 30 pages) or unsupported (a file type that is not read). When it is not processed or unclassified, error.code says why: CONTENT_NOT_RESOLVED, UNREADABLE_FILE, TOO_MANY_PAGES, AI_PROVIDER_UNAVAILABLE, AI_PROVIDER_RATE_LIMITED, AI_PROVIDER_TIMEOUT, AI_PROVIDER_REJECTED, BUDGET_EXHAUSTED or UNEXPECTED. A too_large or failed attachment is left needs_review until someone reviews its type: approved, it is excluded (the Hub's Ignore this file); rejected, it is invalid with type_rejected (Mark as not valid).
  • pageCount is the number of pages in the file. For too_large it is the real count, so you can tell how far over the 30-page limit it is. It is null when nothing was rendered (an unreadable file, a download that failed) or for results stored before this field existed.
  • acceptedTypes says which needed types are covered: satisfiedBy and pendingBy list, per type, the valid attachments and those still to review. unreadFiles lists the attachments that were not read and have no type (too_large, failed, and unclassified without a review): they could be any type, so they keep the document needs_review with accepted_type_unconfirmed instead of making it invalid. invalidBy lists, per type, the attachments of that type that are invalid; a type that only has those is reported as accepted_type_invalid and not as accepted_types_missing. missingTypes lists the types with no valid or pending attachment.
  • partial: true means extraction stopped before trying every field, so some fields may be missing only because they were not asked for again. warnings then includes EXTRACTION_INCOMPLETE. Other warnings are EVIDENCE_SKIPPED (no page image could be made) and OCR_CHECK_SKIPPED (the run was out of time for the cross-check).
  • Fields: value is the text as the model copied it and typedValue the parsed value (null when missing or unparseable). formatFlags can hold ambiguous_separator, fraction_in_integer, ambiguous_day_month, two_digit_year, partial_date, invalid_date, out_of_range or unparseable. confidenceBreakdown.ocrSource says what the value was compared with: text_layer (the PDF's own text), tesseract (OCR of the box), document_text, skipped or null (not computable). boundingBox is on a 0 to 1000 scale of the page image.
  • Links in pages[].url and evidence expire after 60 minutes. Request the results again to get new ones. evidence is always an object: pageUrl or cropUrl is null when that image was not stored.
  • running is {"startedAt": "..."} while a run is in progress.

The extraction and validation blocks

Each attachment also carries two flat blocks. They are derived at read time from the stored result and the current definition; nothing extra is stored. definition.mode says which ones apply: extraction (only fields to extract), validation (only conditions) or both, and null when the data model has no definition. A block is null when the mode has no part of that kind, or when the attachment has no result.

An electricity bill read in mode both, with one condition that failed:

{
  "attachmentId": "a1b2c3d4-0000-4000-8000-000000000003",
  "label": "Planilla agosto",
  "processing": { "...": "..." },
  "extraction": {
    "current": true,
    "status": "extracted",
    "values": { "valor_pagar": 8.9, "fecha_emision": "2026-08-20", "valor_mayor_50": false },
    "evidence": {
      "valor_mayor_50": {
        "label": null,
        "type": "boolean",
        "value": false,
        "status": "ok",
        "confidence": 1.0,
        "basis": "quote",
        "page": 2,
        "boundingBox": { "yMin": 540, "xMin": 120, "yMax": 560, "xMax": 880 },
        "sourceText": "TOTAL A PAGAR: $ 8,90",
        "pageUrl": "https://storage.example.com/...",
        "cropUrl": "https://storage.example.com/..."
      }
    }
  },
  "validation": {
    "current": true,
    "status": "invalid",
    "valid": false,
    "reasons": [{ "code": "blocking_validation_failed", "key": "__expected_fecha_emision" }],
    "rows": [
      {
        "key": "fecha_emision",
        "label": null,
        "type": "date",
        "expected": { "op": "after", "value": "2026-09-01" },
        "read": "2026-08-20",
        "readText": "20/08/2026",
        "fieldStatus": "ok",
        "status": "fail",
        "passed": false,
        "reason": null,
        "page": 2,
        "boundingBox": { "yMin": 210, "xMin": 610, "yMax": 232, "xMax": 905 },
        "sourceText": "Fecha de emisión: 20/08/2026",
        "basis": "value",
        "pageUrl": "https://storage.example.com/...",
        "cropUrl": "https://storage.example.com/..."
      }
    ],
    "otherRules": []
  }
}

extraction:

  • values has one key per field to extract (rows without purpose, or with extraction or both), in the order of the definition. The value is the parsed value (typedValue) when the field is ok or needs_review after reviews, and null when it is missing, waived or was rejected. A file that was not read or not recognized has empty values and evidence.
  • evidence has, for each field that was read, the value as printed (value), its status after reviews, its confidence and basis (value when the value was checked against the page, quote when the sentence the model quoted was checked instead; see the guide), the page, the box, the text it relies on (sourceText) and the signed links to the page and the crop.
  • status is excluded (someone ignored the file), not_read (too_large, failed, unreadable or unsupported), not_recognized (it matched no type, or a reviewer rejected its type), needs_review (the reading was partial, a field needs review, or the type is doubtful) or extracted. A missing optional value is just null.

validation:

  • status is the attachment's status after reviews (valid, invalid, needs_review, excluded), and valid is true or false for the first two and null for the other two. reasons are its reasons after reviews.
  • rows has one entry per condition (rows with purpose validation or both), in the order of the definition: what was expected, what was read (read parsed, readText as printed, both null when nothing usable was read), the field's status, the condition's status (pass, fail, inconclusive) and passed (true, false, null), the reason when it is inconclusive, and the same location and evidence keys as extraction.
  • otherRules lists the type's own validations from Advanced options, with their status and passed.
  • A file that was not read or not recognized has empty rows; its status and reasons say why.

When the result was produced with an earlier definition (resultsCurrent: false), both blocks say "current": false, keep only the fields the result still has, and every expected is null.

In summary, extractionStatus is extracted when every evaluated attachment that is not excluded is extracted, needs_review otherwise, and null when the mode has no fields to extract. validation is {"status": ..., "valid": ...} for the document, or null when the mode has no conditions.

In mode extraction the attachment and document statuses are still computed, but they are not a verdict: nothing was validated. Show extraction.status instead.

Review a Processing Result

Approves or rejects one result that the run could not decide. The reviewer's email and the time are stored with the reason, and the statuses of the attachment and the document are recomputed.

POST /v1/documents/:document_id/processing/review

Requires the borrowers.write permission.

Path Parameters

ParameterDescription
:document_idID of the document

Input Parameters

ParameterDescriptionTypeRequired
target.attachmentIdThe attachment. Must be null for documentValidationStringYes
target.kindfield, validation, logic, documentType or documentValidationStringYes
target.keyKey of the field, validation or logic rule. Ignored for documentTypeStringYes, except for documentType
decisionapproved or rejectedStringYes
reasonWhy, 1 to 500 charactersStringYes

What can be reviewed: a field that needs review, or a required field that is missing; an inconclusive validation, document validation or logic rule; the type of an attachment that is unclassified or whose type confidence is low; and the type of an attachment that was never read (too_large, failed). For those two outcomes, approved makes the attachment excluded and rejected makes it invalid with type_rejected. unreadable and unsupported attachments cannot be reviewed (PROCESSING_REVIEW_TARGET_NOT_REVIEWABLE).

A condition that is inconclusive is reviewed as kind: "validation" with key: "__expected_<field key>"; a doubtful value of that row, as kind: "field" with the field key. The guide's "Manual review" section describes what each decision changes.

Request Example

{
  "target": { "attachmentId": "a1b2c3d4-0000-4000-8000-000000000001", "kind": "field", "key": "fecha_emision" },
  "decision": "approved",
  "reason": "La fecha se lee con claridad en la imagen"
}

Successful Response

The same body as Get the Processing Results of a Document. Reviewing a field or a type re-evaluates the rules that depend on it with the date of the original run; in the example, vigente becomes pass and the attachment and the document become valid. The new review appears first in reviews:

{
  "reviewId": "9a0e...",
  "target": { "attachmentId": "a1b2c3d4-0000-4000-8000-000000000001", "kind": "field", "key": "fecha_emision" },
  "decision": "approved",
  "reason": "La fecha se lee con claridad en la imagen",
  "reviewer": { "email": "reviewer@example.com", "userId": "..." },
  "reviewedAt": "2026-10-21T15:07:00Z",
  "supersededAt": null,
  "supersededBy": null
}

reviewer.email is null when the review was made with a machine-to-machine token. supersededAt and supersededBy are filled when a newer review of the same item or a new run replaced this one.

Processing errors

Errors of the three endpoints above carry an errorSubCode in details. When a run fails, nothing is stored.

StatuserrorSubCodeWhenRetry?
404The document does not existNo
422DOCUMENT_PROCESSING_NOT_CONFIGUREDThe document's data model has no processing definition, or it is disabledNo
422DOCUMENT_HAS_NO_ATTACHMENTSThe document has no attachmentsNo
422PROCESSING_ATTACHMENT_NOT_FOUNDAn id in attachmentIds is not an attachment of the document (details.attachmentIds)No
422DOCUMENT_PROCESSING_TOO_MANY_ATTACHMENTSMore than 20 attachments selectedNo
422DOCUMENT_PROCESSING_NO_SUPPORTED_ATTACHMENTSNone of the selected attachments is a file type that can be readNo
422DOCUMENT_PROCESSING_TOO_MANY_RESULTSThe document has more than 200 processed attachments to evaluate together (details.maxResults). Applies to runs and to reviews that re-evaluate rulesNo
409DOCUMENT_PROCESSING_IN_PROGRESSAnother run of the same document is in progressYes, when it finishes
422DOCUMENT_NOT_PROCESSEDReview: the document has no resultsNo
409DOCUMENT_PROCESSING_STALEReview: the result was produced with an older definitionRun the document again first
422PROCESSING_REVIEW_TARGET_NOT_FOUNDReview: the item does not existNo
422PROCESSING_REVIEW_TARGET_NOT_REVIEWABLEReview: the item is not undecidedNo
503DOCUMENT_PROCESSING_PROVIDER_UNAVAILABLEThe AI provider is not answering (details.providerErrorSubCode)Yes
503DOCUMENT_PROCESSING_TIMEOUTThe processing service did not answer in timeYes
503DOCUMENT_PROCESSING_SERVICE_UNREACHABLEThe processing service could not be reachedLater
503DOCUMENT_PROCESSING_FAILEDThe processing service failedYes
503DOCUMENT_PROCESSING_REJECTEDThe processing service rejected the request. This is a problem on our side, not with your requestNo; contact support

A review of a field or a type also gets 503 when its rules cannot be re-evaluated; the review is not stored. A review of the type of an attachment that was never read (too_large, failed) re-evaluates nothing, so it does not get this error.

Error Handling

The API may return the following error codes:

CodeDescription
400Bad Request
401Unauthorized
403Forbidden
404Not Found
409Conflict (see Processing errors)
422Unprocessable Entity (see Processing errors)
500Internal Server Error
503Service Unavailable (see Processing errors)

Errors will include a descriptive message in the response body.