Skip to main content

Payables

15 operations under /v1/payables (GET POST PATCH): parameters, status codes, refusal bodies, and the same request in curl, HTTP, Python and TypeScript.

46 min read
View MarkdownEdit on GitHub

Base URL: https://api.codespar.dev

Every operation below requires a Bearer token. See Authentication.

GET /v1/payables

GEThttps://api.codespar.dev/v1/payables

List payables

This project's payables, each in the same shape GET /v1/payables/{payableId} returns plus due_date_effective (the due date rolled forward to the next bank business day), ordered by PRINTED due date ascending, then by creation, then by id; payables with no due date come last. That order agrees with due_date_effective except between a boleto and a Pix charge around the last business day of the year: a boleto due that day is effectively due on the first business day of January, a Pix charge is not extended, so the boleto can be listed first although it is effectively due later. Order by due_date_effective where that matters. Filter by status (one or several, comma-separated); there is no date filter. Page with before: pass the previous page's next_before as it was returned, and stop when it is null. The cursor is opaque and carries no tenant, so a cursor from another project pages this project's payables and reveals nothing. Payables of other projects and organizations are never listed. Reading never re-runs a check or changes a status.

Scope: payables:read.

Query parameters

NameTypeRequiredDescription
beforestringnoThe next_before of the previous page, as it was returned. Opaque.
limitintegernoPage size, 1..200. Default 50.
statusstringnoOne status or several, comma-separated (NEEDS_REVIEW,READY). Absent: every status.

Responses

StatusBodyDescription
200objectOK
400objectA filter, the limit or the cursor is not valid.

Response 200

FieldTypeRequiredDescription
next_beforestring,nullyesPass as before for the next page; null on the last page.
payablesarray of objectyes—
Example request
curl -X GET https://api.codespar.dev/v1/payables \
  -H "Authorization: Bearer $CODESPAR_API_KEY"
GET /v1/payables HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
import os
import requests

res = requests.get(
    "https://api.codespar.dev/v1/payables",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
)
res.raise_for_status()
data = res.json()
const res = await fetch("https://api.codespar.dev/v1/payables", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
  },
});

const data = await res.json();
const result = await cs.api.get("/v1/payables");
Example response 200
application/json
{
  "payables": [
    {
      "schema_version": "payable.v1",
      "doc_type": "invoice",
      "attribution": {
        "channel": "upload",
        "credential_id": "credential_0000000000000000",
        "connection_id": "conn_0000000000000000",
        "actor": "string"
      },
      "document_hash": "string",
      "supplier": {
        "document": {
          "value": "string",
          "from": "parser_local",
          "evidence": {
            "page": 0,
            "excerpt": "string",
            "verification": "verified"
          },
          "confidence": 0
        },
        "name": {
          "value": "string",
          "from": "parser_local",
          "evidence": {
            "page": 0,
            "excerpt": "string",
            "verification": "verified"
          },
          "confidence": 0
        }
      },
      "recipient": {
        "document": {
          "value": "string",
          "from": "parser_local",
          "evidence": {
            "page": 0,
            "excerpt": "string",
            "verification": "verified"
          },
          "confidence": 0
        },
        "name": {
          "value": "string",
          "from": "parser_local",
          "evidence": {
            "page": 0,
            "excerpt": "string",
            "verification": "verified"
          },
          "confidence": 0
        }
      },
      "invoice_number": {
        "value": "string",
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      },
      "serie": {
        "value": "string",
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      },
      "issue_date": {
        "value": "2026-01-15",
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      },
      "due_date": {
        "value": "2026-01-15",
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      },
      "items": [
        {
          "description": {
            "value": "string",
            "from": "parser_local",
            "evidence": {
              "page": 0,
              "excerpt": "string",
              "verification": "verified"
            },
            "confidence": 0
          },
          "quantity": {
            "value": 1000,
            "from": "parser_local",
            "evidence": {
              "page": 0,
              "excerpt": "string",
              "verification": "verified"
            },
            "confidence": 0
          },
          "unit_amount_minor": {
            "value": 1000,
            "from": "parser_local",
            "evidence": {
              "page": 0,
              "excerpt": "string",
              "verification": "verified"
            },
            "confidence": 0
          },
          "total_amount_minor": {
            "value": 1000,
            "from": "parser_local",
            "evidence": {
              "page": 0,
              "excerpt": "string",
              "verification": "verified"
            },
            "confidence": 0
          }
        }
      ],
      "totals": {
        "subtotal_minor": {
          "value": 1000,
          "from": "parser_local",
          "evidence": {
            "page": 0,
            "excerpt": "string",
            "verification": "verified"
          },
          "confidence": 0
        },
        "discount_minor": {
          "value": 1000,
          "from": "parser_local",
          "evidence": {
            "page": 0,
            "excerpt": "string",
            "verification": "verified"
          },
          "confidence": 0
        },
        "tax_relief_deducted_minor": {
          "value": 1000,
          "from": "parser_local",
          "evidence": {
            "page": 0,
            "excerpt": "string",
            "verification": "verified"
          },
          "confidence": 0
        },
        "freight_minor": {
          "value": 1000,
          "from": "parser_local",
          "evidence": {
            "page": 0,
            "excerpt": "string",
            "verification": "verified"
          },
          "confidence": 0
        },
        "insurance_minor": {
          "value": 1000,
          "from": "parser_local",
          "evidence": {
            "page": 0,
            "excerpt": "string",
            "verification": "verified"
          },
          "confidence": 0
        },
        "other_charges_minor": {
          "value": 1000,
          "from": "parser_local",
          "evidence": {
            "page": 0,
            "excerpt": "string",
            "verification": "verified"
          },
          "confidence": 0
        },
        "taxes_added_minor": {
          "value": 1000,
          "from": "parser_local",
          "evidence": {
            "page": 0,
            "excerpt": "string",
            "verification": "verified"
          },
          "confidence": 0
        },
        "taxes_included_minor": {
          "value": 1000,
          "from": "parser_local",
          "evidence": {
            "page": 0,
            "excerpt": "string",
            "verification": "verified"
          },
          "confidence": 0
        },
        "grand_total_minor": {
          "value": 1000,
          "from": "parser_local",
          "evidence": {
            "page": 0,
            "excerpt": "string",
            "verification": "verified"
          },
          "confidence": 0
        },
        "currency": {
          "value": "string",
          "from": "parser_local",
          "evidence": {
            "page": 0,
            "excerpt": "string",
            "verification": "verified"
          },
          "confidence": 0
        }
      },
      "carriers": {
        "boleto": "string",
        "br_code": "string",
        "nfe": "string"
      },
      "extraction": {
        "model": "string",
        "model_version": "string",
        "input_tokens": 0,
        "output_tokens": 0,
        "reasoning_tokens": 0
      },
      "payable_id": "payable_0000000000000000",
      "status": "RECEIVED",
      "rail_quote": {
        "beneficiary": {
          "value": "string",
          "from": "trilho"
        },
        "beneficiary_document_masked": {
          "value": "string",
          "from": "trilho"
        },
        "final_beneficiary": {
          "value": "string",
          "from": "trilho"
        },
        "final_beneficiary_document_masked": {
          "value": "string",
          "from": "trilho"
        },
        "amount_minor": {
          "value": 1000,
          "from": "trilho"
        },
        "discount_minor": {
          "value": 1000,
          "from": "trilho"
        },
        "interest_minor": {
          "value": 1000,
          "from": "trilho"
        },
        "fine_minor": {
          "value": 1000,
          "from": "trilho"
        },
        "due_date": {
          "value": "2026-01-15",
          "from": "trilho"
        },
        "quoted_at": "2026-01-15T12:00:00.000Z"
      },
      "validation": {
        "carrier_check_digits": {
          "outcome": "ok",
          "detail": "string"
        },
        "document_check_digits": {
          "outcome": "ok",
          "detail": "string"
        },
        "nfe_key_consistency": {
          "outcome": "ok",
          "detail": "string"
        },
        "arithmetic": {
          "outcome": "ok",
          "detail": "string"
        },
        "duplicate_document": {
          "outcome": "ok",
          "detail": "string"
        },
        "amount_matches_rail": {
          "outcome": "ok",
          "detail": "string"
        },
        "beneficiary_document_matches_supplier": {
          "outcome": "ok",
          "detail": "string"
        },
        "due_date_state": {
          "outcome": "ok",
          "detail": "string"
        },
        "pix_key_owner_matches_supplier": {
          "outcome": "ok",
          "detail": "string"
        },
        "supplier_registry": {
          "outcome": "ok",
          "detail": "string"
        },
        "bank_details_changed": {
          "outcome": "ok",
          "detail": "string"
        }
      },
      "review": {
        "validation_status": "REJECTED",
        "review_by": [
          {
            "code": "string",
            "cause": "check_failed"
          }
        ],
        "rejected_by": [
          "string"
        ],
        "confirmed_by": [
          "string"
        ],
        "awaiting": [
          "string"
        ],
        "not_covered": [
          "string"
        ],
        "review_triggers": [
          "string"
        ],
        "resolutions": [
          {
            "code": "string",
            "resolution": "human_correction",
            "at": "string",
            "by": "string",
            "by_source": "verified_user_token",
            "fields": [
              "string"
            ],
            "confirmed_beneficiary_document": "string"
          }
        ]
      },
      "payment": {
        "state": "awaiting_approval",
        "approval_id": "approval_0000000000000000",
        "settled_against": "document_line",
        "document_amount_minor": 1000,
        "original_document_settled": false,
        "settlement_note": "string",
        "mandate_id": "mandate_0000000000000000",
        "attempt_id": "attempt_0000000000000000",
        "tries": 0,
        "amount_minor": 1000,
        "quote_hash": "string",
        "claimed_at": "string",
        "key_id": "key_0000000000000000",
        "via": "string",
        "executed_at": "string",
        "receipt_id": "receipt_0000000000000000",
        "transaction_id": "transaction_0000000000000000",
        "end_to_end_id": "endtoend_0000000000000000",
        "money_moved": true,
        "uncertain_code": "string",
        "hold_entry_id": "holdentry_0000000000000000",
        "last_refusal": {
          "code": "string",
          "attempt_id": "attempt_0000000000000000",
          "at": "string"
        }
      },
      "created_at": "string",
      "updated_at": "string",
      "due_date_effective": "2026-01-15"
    }
  ],
  "next_before": "string"
}

POST /v1/payables

POSThttps://api.codespar.dev/v1/payables

Create a payable from a partner's record or a person's form

A payable is an obligation to pay somebody. This endpoint takes one as structured data: attribution.channel is structured for a partner system's record and manual for a form a person filled in. Both go through the same schema and the same deterministic checks, so the same payable sent through either channel ends in the same status with the same validation. A file (PDF or image) goes to POST /v1/payables/documents, which reads the payable out of it and enters the same pipeline; a multipart/form-data body sent HERE answers 415 payable_document_endpoint, and a JSON body claiming attribution.channel: "upload" is refused.

Every field carries its provenance in from. Amounts are integer centavos (*_minor). rail_quote is refused by name: the amount and the payee of a payment come from the rail, never from a request. attribution.credential_id is set to the API key the request authenticated with; a different value is refused.

The amount and the payee come from the rail, never from what was sent. amount_matches_rail, beneficiary_document_matches_supplier and due_date_state run against a consult of the boleto on the payable, and they block READY whether or not they answered. A consult that could not be made — no connected payment provider, a provider that did not answer — leaves them indisponivel and the payable NEEDS_REVIEW, named under review.review_by. pix_key_owner_matches_supplier is not consulted in this release and blocks a payable carrying a Pix BR Code with cause required_not_implemented. VALIDATING is never returned: nothing re-runs a validation yet, so a check with no answer goes to a person rather than waiting. due_date_state calls a payable overdue only after its EFFECTIVE due date: a due date on a weekend, a national bank holiday (Carnival Monday and Tuesday and Corpus Christi included) or, for a boleto, the last business day of the year is payable without charges on the next business day. State and municipal holidays are not considered.

Status. Two fields answer two questions. review.validation_status is what the checks alone say: REJECTED (a check that rejects failed), NEEDS_REVIEW (a check failed, or a required check has no answer it can have), VALIDATING (a required, implemented check did not answer and will be retried) or VALIDATED (every required check passed). status is what the payable may do: READY only from VALIDATED with no review trigger open, and NEEDS_REVIEW when a trigger is open. The trigger this API evaluates is new_supplier: this project has no executed payable for the supplier's document, or the payable names no supplier document. It is evaluated per project, never across projects.

Why things are in review. review.review_by lists each check that sends the payable to a person and its cause: check_failed, retries_exhausted or required_not_implemented. review.review_triggers lists the open triggers. review.not_covered lists checks that did not run and do not block READY in this release (supplier_registry, bank_details_changed).

What fails a check. A carrier that fails its own check digits (carrier_check_digits) or a duplicate (duplicate_document) is REJECTED and not stored. A document number with wrong check digits, an NF-e key that disagrees with the printed fields, or totals that do not add up (arithmetic, one centavo of tolerance per comparison) is check_failed. The total is composed the way an NF-e composes vNF: subtotal_minor minus discount_minor and tax_relief_deducted_minor, plus taxes_added_minor, freight_minor, insurance_minor and other_charges_minor. taxes_included_minor is informational and in no sum.

Duplicates. A payable is a duplicate when another payable of the SAME project that is open or already paid shares a duplicity axis with it: document_hash, the boleto (a 47- or 48-digit linha digitável and the 44-digit barcode it expands from are the same boleto), the Pix txid, the NF-e access key, or the supplier document together with invoice number and série. The answer is 409 payable_duplicate and nothing is stored; details.existing_payable_id names the payable that holds the axis. A payable in another project never collides and is never named.

Idempotency. Send an Idempotency-Key header to make a retry safe. The same key with the same body answers 200 with idempotent_replay: true and the payable as it is now, never a duplicate. The same key with a different body answers 409 idempotency_key_conflict. Without a key, a retry of a create that succeeded answers payable_duplicate naming the payable it created.

Scope: payables:write.

Request body

FieldTypeRequiredDescription
attributionobjectyes—
carriersobjectno—
doc_type"invoice" | "danfe_nfe" | "boleto" | "unknown"yes—
document_hashstringno—
due_dateobjectno—
extractionobjectno—
invoice_numberobjectno—
issue_dateobjectno—
itemsarray of objectno—
rail_quote—no—
recipientobjectno—
schema_version"payable.v1"yes—
serieobjectno—
supplierobjectyes—
totalsobjectyes—

Responses

StatusBodyDescription
200objectOK
201objectOK
400objectThe body did not match the schema, carried rail_quote, or the Idempotency-Key header is empty or longer than 255 characters. details.issues names paths and codes, never the values sent.
409objectpayable_duplicate: the payable is REJECTED as a duplicate and was not stored. details.axes names the axis, details.existing_payable_id the payable of this project that holds it, and details.validation the verdict. idempotency_key_conflict: the key was used with a different body.
415objectA multipart/form-data body was sent to the JSON endpoint. Send the file to POST /v1/payables/documents (details.endpoint).
422objectpayable_carrier_malformed: a carrier failed its own check digits, so the payable is REJECTED and was not stored; details.carriers names the carrier and the failure. payable_no_duplicate_key: the payable carries no duplicity axis. payable_channel_unsupported: a JSON body claimed upload. payable_attribution_mismatch: attribution.credential_id names another API key.
503objectThe key that protects supplier documents could not be read. Nothing was stored; retry later with the same Idempotency-Key.

Response 200

FieldTypeRequiredDescription
attributionobjectyes—
carriersobjectno—
created_atstringyesISO 8601 instant the payable was created.
doc_type"invoice" | "danfe_nfe" | "boleto" | "unknown"yes—
document_hashstringno—
due_dateobjectno—
extractionobjectno—
idempotent_replaybooleanyesTrue when this answer replays an earlier call made with the same Idempotency-Key.
invoice_numberobjectno—
issue_dateobjectno—
itemsarray of objectno—
payable_idstringyes—
paymentobject,nullyesWhat paying it did (ent#1587); null until a payment was attempted.
rail_quoteobjectno—
recipientobjectno—
reviewobjectyes—
schema_version"payable.v1"yes—
serieobjectno—
status"RECEIVED" | "EXTRACTING" | "EXTRACTED" | "VALIDATING" | "VALIDATED" | "NEEDS_REVIEW" | "READY" | "APPROVED" | "EXECUTED" | "REJECTED" | "FAILED_EXTRACTION" | "EXPIRED"yes—
supplierobjectyes—
totalsobjectyes—
updated_atstringyesISO 8601 instant of the last change to the payable.
validationobjectyes—
Example request
curl -X POST https://api.codespar.dev/v1/payables \
  -H "Authorization: Bearer $CODESPAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
       "schema_version": "payable.v1",
       "doc_type": "invoice",
       "attribution": {
         "channel": "upload",
         "credential_id": "credential_0000000000000000",
         "connection_id": "conn_0000000000000000",
         "actor": "string"
       },
       "document_hash": "string",
       "supplier": {
         "document": {
           "value": "string",
           "from": "parser_local",
           "evidence": {
             "page": 0,
             "excerpt": "string",
             "verification": "verified"
           },
           "confidence": 0
         },
         "name": {
           "value": "string",
           "from": "parser_local",
           "evidence": {
             "page": 0,
             "excerpt": "string",
             "verification": "verified"
           },
           "confidence": 0
         }
       },
       "recipient": {
         "document": {
           "value": "string",
           "from": "parser_local",
           "evidence": {
             "page": 0,
             "excerpt": "string",
             "verification": "verified"
           },
           "confidence": 0
         },
         "name": {
           "value": "string",
           "from": "parser_local",
           "evidence": {
             "page": 0,
             "excerpt": "string",
             "verification": "verified"
           },
           "confidence": 0
         }
       },
       "invoice_number": {
         "value": "string",
         "from": "parser_local",
         "evidence": {
           "page": 0,
           "excerpt": "string",
           "verification": "verified"
         },
         "confidence": 0
       },
       "serie": {
         "value": "string",
         "from": "parser_local",
         "evidence": {
           "page": 0,
           "excerpt": "string",
           "verification": "verified"
         },
         "confidence": 0
       },
       "issue_date": {
         "value": "2026-01-15",
         "from": "parser_local",
         "evidence": {
           "page": 0,
           "excerpt": "string",
           "verification": "verified"
         },
         "confidence": 0
       },
       "due_date": {
         "value": "2026-01-15",
         "from": "parser_local",
         "evidence": {
           "page": 0,
           "excerpt": "string",
           "verification": "verified"
         },
         "confidence": 0
       },
       "items": [
         {
           "description": {
             "value": "string",
             "from": "parser_local",
             "evidence": {
               "page": 0,
               "excerpt": "string",
               "verification": "verified"
             },
             "confidence": 0
           },
           "quantity": {
             "value": 1000,
             "from": "parser_local",
             "evidence": {
               "page": 0,
               "excerpt": "string",
               "verification": "verified"
             },
             "confidence": 0
           },
           "unit_amount_minor": {
             "value": 1000,
             "from": "parser_local",
             "evidence": {
               "page": 0,
               "excerpt": "string",
               "verification": "verified"
             },
             "confidence": 0
           },
           "total_amount_minor": {
             "value": 1000,
             "from": "parser_local",
             "evidence": {
               "page": 0,
               "excerpt": "string",
               "verification": "verified"
             },
             "confidence": 0
           }
         }
       ],
       "totals": {
         "subtotal_minor": {
           "value": 1000,
           "from": "parser_local",
           "evidence": {
             "page": 0,
             "excerpt": "string",
             "verification": "verified"
           },
           "confidence": 0
         },
         "discount_minor": {
           "value": 1000,
           "from": "parser_local",
           "evidence": {
             "page": 0,
             "excerpt": "string",
             "verification": "verified"
           },
           "confidence": 0
         },
         "tax_relief_deducted_minor": {
           "value": 1000,
           "from": "parser_local",
           "evidence": {
             "page": 0,
             "excerpt": "string",
             "verification": "verified"
           },
           "confidence": 0
         },
         "freight_minor": {
           "value": 1000,
           "from": "parser_local",
           "evidence": {
             "page": 0,
             "excerpt": "string",
             "verification": "verified"
           },
           "confidence": 0
         },
         "insurance_minor": {
           "value": 1000,
           "from": "parser_local",
           "evidence": {
             "page": 0,
             "excerpt": "string",
             "verification": "verified"
           },
           "confidence": 0
         },
         "other_charges_minor": {
           "value": 1000,
           "from": "parser_local",
           "evidence": {
             "page": 0,
             "excerpt": "string",
             "verification": "verified"
           },
           "confidence": 0
         },
         "taxes_added_minor": {
           "value": 1000,
           "from": "parser_local",
           "evidence": {
             "page": 0,
             "excerpt": "string",
             "verification": "verified"
           },
           "confidence": 0
         },
         "taxes_included_minor": {
           "value": 1000,
           "from": "parser_local",
           "evidence": {
             "page": 0,
             "excerpt": "string",
             "verification": "verified"
           },
           "confidence": 0
         },
         "grand_total_minor": {
           "value": 1000,
           "from": "parser_local",
           "evidence": {
             "page": 0,
             "excerpt": "string",
             "verification": "verified"
           },
           "confidence": 0
         },
         "currency": {
           "value": "string",
           "from": "parser_local",
           "evidence": {
             "page": 0,
             "excerpt": "string",
             "verification": "verified"
           },
           "confidence": 0
         }
       },
       "carriers": {
         "boleto": "string",
         "br_code": "string",
         "nfe": "string"
       },
       "extraction": {
         "model": "string",
         "model_version": "string",
         "input_tokens": 0,
         "output_tokens": 0,
         "reasoning_tokens": 0
       }
     }'
POST /v1/payables HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
Content-Type: application/json

{
  "schema_version": "payable.v1",
  "doc_type": "invoice",
  "attribution": {
    "channel": "upload",
    "credential_id": "credential_0000000000000000",
    "connection_id": "conn_0000000000000000",
    "actor": "string"
  },
  "document_hash": "string",
  "supplier": {
    "document": {
      "value": "string",
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "name": {
      "value": "string",
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    }
  },
  "recipient": {
    "document": {
      "value": "string",
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "name": {
      "value": "string",
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    }
  },
  "invoice_number": {
    "value": "string",
    "from": "parser_local",
    "evidence": {
      "page": 0,
      "excerpt": "string",
      "verification": "verified"
    },
    "confidence": 0
  },
  "serie": {
    "value": "string",
    "from": "parser_local",
    "evidence": {
      "page": 0,
      "excerpt": "string",
      "verification": "verified"
    },
    "confidence": 0
  },
  "issue_date": {
    "value": "2026-01-15",
    "from": "parser_local",
    "evidence": {
      "page": 0,
      "excerpt": "string",
      "verification": "verified"
    },
    "confidence": 0
  },
  "due_date": {
    "value": "2026-01-15",
    "from": "parser_local",
    "evidence": {
      "page": 0,
      "excerpt": "string",
      "verification": "verified"
    },
    "confidence": 0
  },
  "items": [
    {
      "description": {
        "value": "string",
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      },
      "quantity": {
        "value": 1000,
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      },
      "unit_amount_minor": {
        "value": 1000,
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      },
      "total_amount_minor": {
        "value": 1000,
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      }
    }
  ],
  "totals": {
    "subtotal_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "discount_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "tax_relief_deducted_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "freight_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "insurance_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "other_charges_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "taxes_added_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "taxes_included_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "grand_total_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "currency": {
      "value": "string",
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    }
  },
  "carriers": {
    "boleto": "string",
    "br_code": "string",
    "nfe": "string"
  },
  "extraction": {
    "model": "string",
    "model_version": "string",
    "input_tokens": 0,
    "output_tokens": 0,
    "reasoning_tokens": 0
  }
}
import os
import requests

res = requests.post(
    "https://api.codespar.dev/v1/payables",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
    json={
      "schema_version": "payable.v1",
      "doc_type": "invoice",
      "attribution": {
        "channel": "upload",
        "credential_id": "credential_0000000000000000",
        "connection_id": "conn_0000000000000000",
        "actor": "string"
      },
      "document_hash": "string",
      "supplier": {
        "document": {
          "value": "string",
          "from": "parser_local",
          "evidence": {
            "page": 0,
            "excerpt": "string",
            "verification": "verified"
          },
          "confidence": 0
        },
        "name": {
          "value": "string",
          "from": "parser_local",
          "evidence": {
            "page": 0,
            "excerpt": "string",
            "verification": "verified"
          },
          "confidence": 0
        }
      },
      "recipient": {
        "document": {
          "value": "string",
          "from": "parser_local",
          "evidence": {
            "page": 0,
            "excerpt": "string",
            "verification": "verified"
          },
          "confidence": 0
        },
        "name": {
          "value": "string",
          "from": "parser_local",
          "evidence": {
            "page": 0,
            "excerpt": "string",
            "verification": "verified"
          },
          "confidence": 0
        }
      },
      "invoice_number": {
        "value": "string",
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      },
      "serie": {
        "value": "string",
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      },
      "issue_date": {
        "value": "2026-01-15",
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      },
      "due_date": {
        "value": "2026-01-15",
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      },
      "items": [
        {
          "description": {
            "value": "string",
            "from": "parser_local",
            "evidence": {
              "page": 0,
              "excerpt": "string",
              "verification": "verified"
            },
            "confidence": 0
          },
          "quantity": {
            "value": 1000,
            "from": "parser_local",
            "evidence": {
              "page": 0,
              "excerpt": "string",
              "verification": "verified"
            },
            "confidence": 0
          },
          "unit_amount_minor": {
            "value": 1000,
            "from": "parser_local",
            "evidence": {
              "page": 0,
              "excerpt": "string",
              "verification": "verified"
            },
            "confidence": 0
          },
          "total_amount_minor": {
            "value": 1000,
            "from": "parser_local",
            "evidence": {
              "page": 0,
              "excerpt": "string",
              "verification": "verified"
            },
            "confidence": 0
          }
        }
      ],
      "totals": {
        "subtotal_minor": {
          "value": 1000,
          "from": "parser_local",
          "evidence": {
            "page": 0,
            "excerpt": "string",
            "verification": "verified"
          },
          "confidence": 0
        },
        "discount_minor": {
          "value": 1000,
          "from": "parser_local",
          "evidence": {
            "page": 0,
            "excerpt": "string",
            "verification": "verified"
          },
          "confidence": 0
        },
        "tax_relief_deducted_minor": {
          "value": 1000,
          "from": "parser_local",
          "evidence": {
            "page": 0,
            "excerpt": "string",
            "verification": "verified"
          },
          "confidence": 0
        },
        "freight_minor": {
          "value": 1000,
          "from": "parser_local",
          "evidence": {
            "page": 0,
            "excerpt": "string",
            "verification": "verified"
          },
          "confidence": 0
        },
        "insurance_minor": {
          "value": 1000,
          "from": "parser_local",
          "evidence": {
            "page": 0,
            "excerpt": "string",
            "verification": "verified"
          },
          "confidence": 0
        },
        "other_charges_minor": {
          "value": 1000,
          "from": "parser_local",
          "evidence": {
            "page": 0,
            "excerpt": "string",
            "verification": "verified"
          },
          "confidence": 0
        },
        "taxes_added_minor": {
          "value": 1000,
          "from": "parser_local",
          "evidence": {
            "page": 0,
            "excerpt": "string",
            "verification": "verified"
          },
          "confidence": 0
        },
        "taxes_included_minor": {
          "value": 1000,
          "from": "parser_local",
          "evidence": {
            "page": 0,
            "excerpt": "string",
            "verification": "verified"
          },
          "confidence": 0
        },
        "grand_total_minor": {
          "value": 1000,
          "from": "parser_local",
          "evidence": {
            "page": 0,
            "excerpt": "string",
            "verification": "verified"
          },
          "confidence": 0
        },
        "currency": {
          "value": "string",
          "from": "parser_local",
          "evidence": {
            "page": 0,
            "excerpt": "string",
            "verification": "verified"
          },
          "confidence": 0
        }
      },
      "carriers": {
        "boleto": "string",
        "br_code": "string",
        "nfe": "string"
      },
      "extraction": {
        "model": "string",
        "model_version": "string",
        "input_tokens": 0,
        "output_tokens": 0,
        "reasoning_tokens": 0
      }
    },
)
res.raise_for_status()
data = res.json()
const res = await fetch("https://api.codespar.dev/v1/payables", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "schema_version": "payable.v1",
    "doc_type": "invoice",
    "attribution": {
      "channel": "upload",
      "credential_id": "credential_0000000000000000",
      "connection_id": "conn_0000000000000000",
      "actor": "string"
    },
    "document_hash": "string",
    "supplier": {
      "document": {
        "value": "string",
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      },
      "name": {
        "value": "string",
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      }
    },
    "recipient": {
      "document": {
        "value": "string",
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      },
      "name": {
        "value": "string",
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      }
    },
    "invoice_number": {
      "value": "string",
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "serie": {
      "value": "string",
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "issue_date": {
      "value": "2026-01-15",
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "due_date": {
      "value": "2026-01-15",
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "items": [
      {
        "description": {
          "value": "string",
          "from": "parser_local",
          "evidence": {
            "page": 0,
            "excerpt": "string",
            "verification": "verified"
          },
          "confidence": 0
        },
        "quantity": {
          "value": 1000,
          "from": "parser_local",
          "evidence": {
            "page": 0,
            "excerpt": "string",
            "verification": "verified"
          },
          "confidence": 0
        },
        "unit_amount_minor": {
          "value": 1000,
          "from": "parser_local",
          "evidence": {
            "page": 0,
            "excerpt": "string",
            "verification": "verified"
          },
          "confidence": 0
        },
        "total_amount_minor": {
          "value": 1000,
          "from": "parser_local",
          "evidence": {
            "page": 0,
            "excerpt": "string",
            "verification": "verified"
          },
          "confidence": 0
        }
      }
    ],
    "totals": {
      "subtotal_minor": {
        "value": 1000,
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      },
      "discount_minor": {
        "value": 1000,
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      },
      "tax_relief_deducted_minor": {
        "value": 1000,
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      },
      "freight_minor": {
        "value": 1000,
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      },
      "insurance_minor": {
        "value": 1000,
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      },
      "other_charges_minor": {
        "value": 1000,
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      },
      "taxes_added_minor": {
        "value": 1000,
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      },
      "taxes_included_minor": {
        "value": 1000,
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      },
      "grand_total_minor": {
        "value": 1000,
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      },
      "currency": {
        "value": "string",
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      }
    },
    "carriers": {
      "boleto": "string",
      "br_code": "string",
      "nfe": "string"
    },
    "extraction": {
      "model": "string",
      "model_version": "string",
      "input_tokens": 0,
      "output_tokens": 0,
      "reasoning_tokens": 0
    }
  }),
});

const data = await res.json();
const r = await cs.api.response("post", "/v1/payables", {
  body: {
    schema_version: "payable.v1",
    doc_type: "invoice",
    attribution: {
      channel: "upload",
      credential_id: "credential_0000000000000000",
      connection_id: "conn_0000000000000000",
      actor: "string"
    },
    document_hash: "string",
    supplier: {
      document: {
        value: "string",
        from: "parser_local",
        evidence: {
          page: 0,
          excerpt: "string",
          verification: "verified"
        },
        confidence: 0
      },
      name: {
        value: "string",
        from: "parser_local",
        evidence: {
          page: 0,
          excerpt: "string",
          verification: "verified"
        },
        confidence: 0
      }
    },
    recipient: {
      document: {
        value: "string",
        from: "parser_local",
        evidence: {
          page: 0,
          excerpt: "string",
          verification: "verified"
        },
        confidence: 0
      },
      name: {
        value: "string",
        from: "parser_local",
        evidence: {
          page: 0,
          excerpt: "string",
          verification: "verified"
        },
        confidence: 0
      }
    },
    invoice_number: {
      value: "string",
      from: "parser_local",
      evidence: {
        page: 0,
        excerpt: "string",
        verification: "verified"
      },
      confidence: 0
    },
    serie: {
      value: "string",
      from: "parser_local",
      evidence: {
        page: 0,
        excerpt: "string",
        verification: "verified"
      },
      confidence: 0
    },
    issue_date: {
      value: "2026-01-15",
      from: "parser_local",
      evidence: {
        page: 0,
        excerpt: "string",
        verification: "verified"
      },
      confidence: 0
    },
    due_date: {
      value: "2026-01-15",
      from: "parser_local",
      evidence: {
        page: 0,
        excerpt: "string",
        verification: "verified"
      },
      confidence: 0
    },
    items: [
      {
        description: {
          value: "string",
          from: "parser_local",
          evidence: {
            page: 0,
            excerpt: "string",
            verification: "verified"
          },
          confidence: 0
        },
        quantity: {
          value: 1000,
          from: "parser_local",
          evidence: {
            page: 0,
            excerpt: "string",
            verification: "verified"
          },
          confidence: 0
        },
        unit_amount_minor: {
          value: 1000,
          from: "parser_local",
          evidence: {
            page: 0,
            excerpt: "string",
            verification: "verified"
          },
          confidence: 0
        },
        total_amount_minor: {
          value: 1000,
          from: "parser_local",
          evidence: {
            page: 0,
            excerpt: "string",
            verification: "verified"
          },
          confidence: 0
        }
      }
    ],
    totals: {
      subtotal_minor: {
        value: 1000,
        from: "parser_local",
        evidence: {
          page: 0,
          excerpt: "string",
          verification: "verified"
        },
        confidence: 0
      },
      discount_minor: {
        value: 1000,
        from: "parser_local",
        evidence: {
          page: 0,
          excerpt: "string",
          verification: "verified"
        },
        confidence: 0
      },
      tax_relief_deducted_minor: {
        value: 1000,
        from: "parser_local",
        evidence: {
          page: 0,
          excerpt: "string",
          verification: "verified"
        },
        confidence: 0
      },
      freight_minor: {
        value: 1000,
        from: "parser_local",
        evidence: {
          page: 0,
          excerpt: "string",
          verification: "verified"
        },
        confidence: 0
      },
      insurance_minor: {
        value: 1000,
        from: "parser_local",
        evidence: {
          page: 0,
          excerpt: "string",
          verification: "verified"
        },
        confidence: 0
      },
      other_charges_minor: {
        value: 1000,
        from: "parser_local",
        evidence: {
          page: 0,
          excerpt: "string",
          verification: "verified"
        },
        confidence: 0
      },
      taxes_added_minor: {
        value: 1000,
        from: "parser_local",
        evidence: {
          page: 0,
          excerpt: "string",
          verification: "verified"
        },
        confidence: 0
      },
      taxes_included_minor: {
        value: 1000,
        from: "parser_local",
        evidence: {
          page: 0,
          excerpt: "string",
          verification: "verified"
        },
        confidence: 0
      },
      grand_total_minor: {
        value: 1000,
        from: "parser_local",
        evidence: {
          page: 0,
          excerpt: "string",
          verification: "verified"
        },
        confidence: 0
      },
      currency: {
        value: "string",
        from: "parser_local",
        evidence: {
          page: 0,
          excerpt: "string",
          verification: "verified"
        },
        confidence: 0
      }
    },
    carriers: {
      boleto: "string",
      br_code: "string",
      nfe: "string"
    },
    extraction: {
      model: "string",
      model_version: "string",
      input_tokens: 0,
      output_tokens: 0,
      reasoning_tokens: 0
    }
  }
});
// r.status is one of the documented statuses (200, 422),
// each with its own body shape in r.data; nothing here throws on 422.
if (r.ok) {
  console.log(r.data);
}
Example response 200
application/json
{
  "schema_version": "payable.v1",
  "doc_type": "invoice",
  "attribution": {
    "channel": "upload",
    "credential_id": "credential_0000000000000000",
    "connection_id": "conn_0000000000000000",
    "actor": "string"
  },
  "document_hash": "string",
  "supplier": {
    "document": {
      "value": "string",
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "name": {
      "value": "string",
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    }
  },
  "recipient": {
    "document": {
      "value": "string",
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "name": {
      "value": "string",
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    }
  },
  "invoice_number": {
    "value": "string",
    "from": "parser_local",
    "evidence": {
      "page": 0,
      "excerpt": "string",
      "verification": "verified"
    },
    "confidence": 0
  },
  "serie": {
    "value": "string",
    "from": "parser_local",
    "evidence": {
      "page": 0,
      "excerpt": "string",
      "verification": "verified"
    },
    "confidence": 0
  },
  "issue_date": {
    "value": "2026-01-15",
    "from": "parser_local",
    "evidence": {
      "page": 0,
      "excerpt": "string",
      "verification": "verified"
    },
    "confidence": 0
  },
  "due_date": {
    "value": "2026-01-15",
    "from": "parser_local",
    "evidence": {
      "page": 0,
      "excerpt": "string",
      "verification": "verified"
    },
    "confidence": 0
  },
  "items": [
    {
      "description": {
        "value": "string",
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      },
      "quantity": {
        "value": 1000,
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      },
      "unit_amount_minor": {
        "value": 1000,
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      },
      "total_amount_minor": {
        "value": 1000,
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      }
    }
  ],
  "totals": {
    "subtotal_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "discount_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "tax_relief_deducted_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "freight_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "insurance_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "other_charges_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "taxes_added_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "taxes_included_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "grand_total_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "currency": {
      "value": "string",
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    }
  },
  "carriers": {
    "boleto": "string",
    "br_code": "string",
    "nfe": "string"
  },
  "extraction": {
    "model": "string",
    "model_version": "string",
    "input_tokens": 0,
    "output_tokens": 0,
    "reasoning_tokens": 0
  },
  "payable_id": "payable_0000000000000000",
  "status": "RECEIVED",
  "rail_quote": {
    "beneficiary": {
      "value": "string",
      "from": "trilho"
    },
    "beneficiary_document_masked": {
      "value": "string",
      "from": "trilho"
    },
    "final_beneficiary": {
      "value": "string",
      "from": "trilho"
    },
    "final_beneficiary_document_masked": {
      "value": "string",
      "from": "trilho"
    },
    "amount_minor": {
      "value": 1000,
      "from": "trilho"
    },
    "discount_minor": {
      "value": 1000,
      "from": "trilho"
    },
    "interest_minor": {
      "value": 1000,
      "from": "trilho"
    },
    "fine_minor": {
      "value": 1000,
      "from": "trilho"
    },
    "due_date": {
      "value": "2026-01-15",
      "from": "trilho"
    },
    "quoted_at": "2026-01-15T12:00:00.000Z"
  },
  "validation": {
    "carrier_check_digits": {
      "outcome": "ok",
      "detail": "string"
    },
    "document_check_digits": {
      "outcome": "ok",
      "detail": "string"
    },
    "nfe_key_consistency": {
      "outcome": "ok",
      "detail": "string"
    },
    "arithmetic": {
      "outcome": "ok",
      "detail": "string"
    },
    "duplicate_document": {
      "outcome": "ok",
      "detail": "string"
    },
    "amount_matches_rail": {
      "outcome": "ok",
      "detail": "string"
    },
    "beneficiary_document_matches_supplier": {
      "outcome": "ok",
      "detail": "string"
    },
    "due_date_state": {
      "outcome": "ok",
      "detail": "string"
    },
    "pix_key_owner_matches_supplier": {
      "outcome": "ok",
      "detail": "string"
    },
    "supplier_registry": {
      "outcome": "ok",
      "detail": "string"
    },
    "bank_details_changed": {
      "outcome": "ok",
      "detail": "string"
    }
  },
  "review": {
    "validation_status": "REJECTED",
    "review_by": [
      {
        "code": "string",
        "cause": "check_failed"
      }
    ],
    "rejected_by": [
      "string"
    ],
    "confirmed_by": [
      "string"
    ],
    "awaiting": [
      "string"
    ],
    "not_covered": [
      "string"
    ],
    "review_triggers": [
      "string"
    ],
    "resolutions": [
      {
        "code": "string",
        "resolution": "human_correction",
        "at": "string",
        "by": "string",
        "by_source": "verified_user_token",
        "fields": [
          "string"
        ],
        "confirmed_beneficiary_document": "string"
      }
    ]
  },
  "payment": {
    "state": "awaiting_approval",
    "approval_id": "approval_0000000000000000",
    "settled_against": "document_line",
    "document_amount_minor": 1000,
    "original_document_settled": false,
    "settlement_note": "string",
    "mandate_id": "mandate_0000000000000000",
    "attempt_id": "attempt_0000000000000000",
    "tries": 0,
    "amount_minor": 1000,
    "quote_hash": "string",
    "claimed_at": "string",
    "key_id": "key_0000000000000000",
    "via": "string",
    "executed_at": "string",
    "receipt_id": "receipt_0000000000000000",
    "transaction_id": "transaction_0000000000000000",
    "end_to_end_id": "endtoend_0000000000000000",
    "money_moved": true,
    "uncertain_code": "string",
    "hold_entry_id": "holdentry_0000000000000000",
    "last_refusal": {
      "code": "string",
      "attempt_id": "attempt_0000000000000000",
      "at": "string"
    }
  },
  "created_at": "string",
  "updated_at": "string",
  "idempotent_replay": true
}

POST /v1/payables/batches

POSThttps://api.codespar.dev/v1/payables/batches

Create a batch of payables from files and items

At least one of file_ids (uploaded with POST /v1/payables/files) and items (stated in text). At most 20 files and 500 items. Every item is resolved on the rail before it can be confirmed.

What an item shows comes from the rail, never from the file. A boleto is quoted (amount, beneficiary, masked document, due date); a Pix key is resolved in the DICT (owner name, masked document); a BR Code is decoded with its CRC, and a dynamic one through the payment provider's own decode call (its location URL is never fetched by us). A bare Pix key never sets the amount: the person, the integration or the CSV column states it, and amount_source says which. Nothing a document says about a beneficiary or an amount changes those values. No model reads any file.

Identity. An item's item_id is the payable's id, derived from the batch and the item's content (the file hash and the carrier, or a row's content and reference), never from its position: re-uploading the same file into the same batch, reordering, or resuming never yields another item. A carrier repeated in the batch is flagged duplicate_in_batch; one already open or paid in the project fails duplicate_document.

One bill, two ways. A page that prints one boleto and one Pix code for the same charge is ONE item with two ways (Pix first); rail is the chosen way and rail_chosen_by says who chose it (default until a person or an integration sends PATCH { rail }). A way the rail refuses (an expired dynamic code, a boleto past its final date, a provider that did not answer) carries unavailable and the item switches to the other way (rail_switched); two ways whose own amounts differ wait for a choice (missing: ["rail"], ways_amount_mismatch). document carries what the page says about itself (title from a closed catalog, issuer, due date, the carrier's own amount), shown before the rail answers and never confirmed in its place.

Idempotency-Key is required; a replay with the same key answers the same batch.

Scope: payables:write.

Request body

FieldTypeRequiredDescription
conversation_idstringno—
file_idsarray of stringno—
itemsarray of objectno—
source"api" | "chat" | "dashboard" | "cli" | "mcp"no—

Responses

StatusBodyDescription
200objectOK
201objectOK
400objectThe body did not match, the batch would be empty, or the Idempotency-Key is missing.
409objectThe Idempotency-Key was used with a different body.
422objectA file_id that is not an unexpired file of this project, or more than 20 files / 500 items.

Response 200

FieldTypeRequiredDescription
batch_idstringyespbat_ followed by 20 characters.
confirmationobject,nullyes—
conversation_idstring,nullyes—
created_atstringyes—
currency"BRL"yes—
discardedobject,nullyesSet once the batch was discarded (POST .../discard). With nothing dispatched before, the status is canceled; after a partial execution the status is computed from the items that remain, which kept their payments.
idempotent_replaybooleanyesTrue when this answers an earlier call with the same Idempotency-Key.
itemsarray of objectyes—
list_hashstringyesSHA-256 over the canonical list: for each non-excluded item in item_id order item_id|rail|destination id|amount_minor|currency, then the total. Send it back on pay.
source"api" | "chat" | "dashboard" | "cli" | "mcp"yes—
status"reviewing" | "ready" | "confirmed" | "executing" | "completed" | "partially_completed" | "failed" | "canceled"yes—
totalsobjectyes—
versionintegeryesBumps on every change to the list. A confirmation binds to one version.
Example request
curl -X POST https://api.codespar.dev/v1/payables/batches \
  -H "Authorization: Bearer $CODESPAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
       "file_ids": [
         "string"
       ],
       "items": [
         {
           "pix_key": "string",
           "boleto_line": "string",
           "br_code": "string",
           "amount_minor": 1000,
           "description": "string",
           "reference": "string"
         }
       ],
       "source": "api",
       "conversation_id": "conversation_0000000000000000"
     }'
POST /v1/payables/batches HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
Content-Type: application/json

{
  "file_ids": [
    "string"
  ],
  "items": [
    {
      "pix_key": "string",
      "boleto_line": "string",
      "br_code": "string",
      "amount_minor": 1000,
      "description": "string",
      "reference": "string"
    }
  ],
  "source": "api",
  "conversation_id": "conversation_0000000000000000"
}
import os
import requests

res = requests.post(
    "https://api.codespar.dev/v1/payables/batches",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
    json={
      "file_ids": [
        "string"
      ],
      "items": [
        {
          "pix_key": "string",
          "boleto_line": "string",
          "br_code": "string",
          "amount_minor": 1000,
          "description": "string",
          "reference": "string"
        }
      ],
      "source": "api",
      "conversation_id": "conversation_0000000000000000"
    },
)
res.raise_for_status()
data = res.json()
const res = await fetch("https://api.codespar.dev/v1/payables/batches", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "file_ids": [
      "string"
    ],
    "items": [
      {
        "pix_key": "string",
        "boleto_line": "string",
        "br_code": "string",
        "amount_minor": 1000,
        "description": "string",
        "reference": "string"
      }
    ],
    "source": "api",
    "conversation_id": "conversation_0000000000000000"
  }),
});

const data = await res.json();
const r = await cs.api.response("post", "/v1/payables/batches", {
  header: {
    "Idempotency-Key": "string"
  },
  body: {
    file_ids: [
      "string"
    ],
    items: [
      {
        pix_key: "string",
        boleto_line: "string",
        br_code: "string",
        amount_minor: 1000,
        description: "string",
        reference: "string"
      }
    ],
    source: "api",
    conversation_id: "conversation_0000000000000000"
  }
});
// r.status is one of the documented statuses (200, 422),
// each with its own body shape in r.data; nothing here throws on 422.
if (r.ok) {
  console.log(r.data);
}
Example response 200
application/json
{
  "batch_id": "batch_0000000000000000",
  "status": "reviewing",
  "version": 0,
  "list_hash": "string",
  "currency": "BRL",
  "totals": {
    "items": 0,
    "payable_items": 0,
    "excluded": 0,
    "amount_minor": 1000
  },
  "confirmation": {
    "version": 0,
    "list_hash": "string",
    "confirmed_by": "string",
    "confirmed_at": "string",
    "mandate_id": "mandate_0000000000000000"
  },
  "discarded": {
    "at": "string",
    "items": 0
  },
  "items": [
    {
      "item_id": "item_0000000000000000",
      "position": 0,
      "origin": {
        "file_id": "file_0000000000000000",
        "page": 0,
        "row": 0,
        "kind": "boleto_line"
      },
      "rail": "boleto",
      "rail_chosen_by": "default",
      "ways": [
        {
          "rail": "pix",
          "origin": {
            "page": 0,
            "kind": "boleto_line"
          },
          "destination": {
            "kind": "boleto",
            "display": "string",
            "beneficiary_name": "Example",
            "beneficiary_document_masked": "string",
            "due_date": "2026-01-15",
            "beneficiary_institution": "string",
            "key": "string"
          },
          "unavailable": {
            "code": "br_code_expired",
            "message": "string"
          }
        }
      ],
      "document": {
        "title": "Example",
        "issuer": "string",
        "due_date": "2026-01-15",
        "amount_minor": 1000
      },
      "state": "needs_complement",
      "missing": [
        "amount"
      ],
      "destination": {
        "kind": "boleto",
        "display": "string",
        "beneficiary_name": "Example",
        "beneficiary_document_masked": "string",
        "due_date": "2026-01-15",
        "beneficiary_institution": "string",
        "key": "string"
      },
      "amount_minor": 1000,
      "amount_source": "rail_quote",
      "description": "string",
      "reference": "string",
      "checks": [
        {
          "code": "carrier_check_digits",
          "status": "pass",
          "message": "string",
          "simulated": true
        }
      ],
      "payment": {
        "state": "executed",
        "attempt_id": "attempt_0000000000000000",
        "receipt_id": "receipt_0000000000000000",
        "end_to_end_id": "endtoend_0000000000000000",
        "refusal": {
          "code": "string",
          "message": "string"
        },
        "test_substitution": {
          "settled_against": "sandbox_fixture",
          "original_document_settled": false,
          "document_amount_minor": 1000,
          "note": "string"
        }
      }
    }
  ],
  "created_at": "string",
  "source": "api",
  "conversation_id": "conversation_0000000000000000",
  "idempotent_replay": true
}

GET /v1/payables/batches/{batchId}

GEThttps://api.codespar.dev/v1/payables/batches/{batchId}

Read a batch, its items and their results

The batch as it is now: every item with its resolved destination, amount and source, the missing fields, the checks and, after paying, its own payment result. While a large batch executes, status is executing and this shows progress.

The confirmation binds to the list. version bumps on every change and list_hash is the SHA-256 of the canonical list (each non-excluded item in item_id order as item_id|rail|destination id|amount_minor|currency, then the total). Any change after a confirmation needs a new one.

Scope: payables:read.

Path parameters

NameTypeRequiredDescription
batchIdstringyespbat_ followed by 20 characters.

Responses

StatusBodyDescription
200objectOK
404objectNo batch with this id in this project.

Response 200

FieldTypeRequiredDescription
batch_idstringyespbat_ followed by 20 characters.
confirmationobject,nullyes—
conversation_idstring,nullyes—
created_atstringyes—
currency"BRL"yes—
discardedobject,nullyesSet once the batch was discarded (POST .../discard). With nothing dispatched before, the status is canceled; after a partial execution the status is computed from the items that remain, which kept their payments.
itemsarray of objectyes—
list_hashstringyesSHA-256 over the canonical list: for each non-excluded item in item_id order item_id|rail|destination id|amount_minor|currency, then the total. Send it back on pay.
source"api" | "chat" | "dashboard" | "cli" | "mcp"yes—
status"reviewing" | "ready" | "confirmed" | "executing" | "completed" | "partially_completed" | "failed" | "canceled"yes—
totalsobjectyes—
versionintegeryesBumps on every change to the list. A confirmation binds to one version.
Example request
curl -X GET https://api.codespar.dev/v1/payables/batches/{batchId} \
  -H "Authorization: Bearer $CODESPAR_API_KEY"
GET /v1/payables/batches/{batchId} HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
import os
import requests

res = requests.get(
    "https://api.codespar.dev/v1/payables/batches/{batchId}",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
)
res.raise_for_status()
data = res.json()
const res = await fetch("https://api.codespar.dev/v1/payables/batches/{batchId}", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
  },
});

const data = await res.json();
const result = await cs.api.get("/v1/payables/batches/{batchId}", {
  path: {
    batchId: "batch_0000000000000000"
  }
});
Example response 200
application/json
{
  "batch_id": "batch_0000000000000000",
  "status": "reviewing",
  "version": 0,
  "list_hash": "string",
  "currency": "BRL",
  "totals": {
    "items": 0,
    "payable_items": 0,
    "excluded": 0,
    "amount_minor": 1000
  },
  "confirmation": {
    "version": 0,
    "list_hash": "string",
    "confirmed_by": "string",
    "confirmed_at": "string",
    "mandate_id": "mandate_0000000000000000"
  },
  "discarded": {
    "at": "string",
    "items": 0
  },
  "items": [
    {
      "item_id": "item_0000000000000000",
      "position": 0,
      "origin": {
        "file_id": "file_0000000000000000",
        "page": 0,
        "row": 0,
        "kind": "boleto_line"
      },
      "rail": "boleto",
      "rail_chosen_by": "default",
      "ways": [
        {
          "rail": "pix",
          "origin": {
            "page": 0,
            "kind": "boleto_line"
          },
          "destination": {
            "kind": "boleto",
            "display": "string",
            "beneficiary_name": "Example",
            "beneficiary_document_masked": "string",
            "due_date": "2026-01-15",
            "beneficiary_institution": "string",
            "key": "string"
          },
          "unavailable": {
            "code": "br_code_expired",
            "message": "string"
          }
        }
      ],
      "document": {
        "title": "Example",
        "issuer": "string",
        "due_date": "2026-01-15",
        "amount_minor": 1000
      },
      "state": "needs_complement",
      "missing": [
        "amount"
      ],
      "destination": {
        "kind": "boleto",
        "display": "string",
        "beneficiary_name": "Example",
        "beneficiary_document_masked": "string",
        "due_date": "2026-01-15",
        "beneficiary_institution": "string",
        "key": "string"
      },
      "amount_minor": 1000,
      "amount_source": "rail_quote",
      "description": "string",
      "reference": "string",
      "checks": [
        {
          "code": "carrier_check_digits",
          "status": "pass",
          "message": "string",
          "simulated": true
        }
      ],
      "payment": {
        "state": "executed",
        "attempt_id": "attempt_0000000000000000",
        "receipt_id": "receipt_0000000000000000",
        "end_to_end_id": "endtoend_0000000000000000",
        "refusal": {
          "code": "string",
          "message": "string"
        },
        "test_substitution": {
          "settled_against": "sandbox_fixture",
          "original_document_settled": false,
          "document_amount_minor": 1000,
          "note": "string"
        }
      }
    }
  ],
  "created_at": "string",
  "source": "api",
  "conversation_id": "conversation_0000000000000000"
}

POST /v1/payables/batches/{batchId}/discard

POSThttps://api.codespar.dev/v1/payables/batches/{batchId}/discard

Discard what was never dispatched

Takes out of the list every item never dispatched (needs_complement, resolving, ready, rejected, including items a large batch has not reached yet): each becomes excluded, and the charge it held is free for a new batch. Items already dispatched, awaiting an approval, uncertain, executed or refused stay exactly as they are; a discard never stops or reverses money already in motion, and a later pay call with the same confirmation dispatches nothing new.

With nothing dispatched before, the batch becomes canceled: no change, add or pay is taken after (payable_batch_discarded). After a partial execution, status is the one the remaining items give. discarded records when and how many. Discarding again answers the batch as it is.

Scope: payables:write.

Path parameters

NameTypeRequiredDescription
batchIdstringyes—

Request body

Type: object

Responses

StatusBodyDescription
200objectOK
400objectThe body is not an empty object.
404objectNo batch with this id in this project.

Response 200

FieldTypeRequiredDescription
batch_idstringyespbat_ followed by 20 characters.
confirmationobject,nullyes—
conversation_idstring,nullyes—
created_atstringyes—
currency"BRL"yes—
discardedobject,nullyesSet once the batch was discarded (POST .../discard). With nothing dispatched before, the status is canceled; after a partial execution the status is computed from the items that remain, which kept their payments.
itemsarray of objectyes—
list_hashstringyesSHA-256 over the canonical list: for each non-excluded item in item_id order item_id|rail|destination id|amount_minor|currency, then the total. Send it back on pay.
source"api" | "chat" | "dashboard" | "cli" | "mcp"yes—
status"reviewing" | "ready" | "confirmed" | "executing" | "completed" | "partially_completed" | "failed" | "canceled"yes—
totalsobjectyes—
versionintegeryesBumps on every change to the list. A confirmation binds to one version.
Example request
curl -X POST https://api.codespar.dev/v1/payables/batches/{batchId}/discard \
  -H "Authorization: Bearer $CODESPAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
POST /v1/payables/batches/{batchId}/discard HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
Content-Type: application/json

{}
import os
import requests

res = requests.post(
    "https://api.codespar.dev/v1/payables/batches/{batchId}/discard",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
    json={},
)
res.raise_for_status()
data = res.json()
const res = await fetch("https://api.codespar.dev/v1/payables/batches/{batchId}/discard", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({}),
});

const data = await res.json();
const result = await cs.api.post("/v1/payables/batches/{batchId}/discard", {
  path: {
    batchId: "batch_0000000000000000"
  },
  body: {}
});
Example response 200
application/json
{
  "batch_id": "batch_0000000000000000",
  "status": "reviewing",
  "version": 0,
  "list_hash": "string",
  "currency": "BRL",
  "totals": {
    "items": 0,
    "payable_items": 0,
    "excluded": 0,
    "amount_minor": 1000
  },
  "confirmation": {
    "version": 0,
    "list_hash": "string",
    "confirmed_by": "string",
    "confirmed_at": "string",
    "mandate_id": "mandate_0000000000000000"
  },
  "discarded": {
    "at": "string",
    "items": 0
  },
  "items": [
    {
      "item_id": "item_0000000000000000",
      "position": 0,
      "origin": {
        "file_id": "file_0000000000000000",
        "page": 0,
        "row": 0,
        "kind": "boleto_line"
      },
      "rail": "boleto",
      "rail_chosen_by": "default",
      "ways": [
        {
          "rail": "pix",
          "origin": {
            "page": 0,
            "kind": "boleto_line"
          },
          "destination": {
            "kind": "boleto",
            "display": "string",
            "beneficiary_name": "Example",
            "beneficiary_document_masked": "string",
            "due_date": "2026-01-15",
            "beneficiary_institution": "string",
            "key": "string"
          },
          "unavailable": {
            "code": "br_code_expired",
            "message": "string"
          }
        }
      ],
      "document": {
        "title": "Example",
        "issuer": "string",
        "due_date": "2026-01-15",
        "amount_minor": 1000
      },
      "state": "needs_complement",
      "missing": [
        "amount"
      ],
      "destination": {
        "kind": "boleto",
        "display": "string",
        "beneficiary_name": "Example",
        "beneficiary_document_masked": "string",
        "due_date": "2026-01-15",
        "beneficiary_institution": "string",
        "key": "string"
      },
      "amount_minor": 1000,
      "amount_source": "rail_quote",
      "description": "string",
      "reference": "string",
      "checks": [
        {
          "code": "carrier_check_digits",
          "status": "pass",
          "message": "string",
          "simulated": true
        }
      ],
      "payment": {
        "state": "executed",
        "attempt_id": "attempt_0000000000000000",
        "receipt_id": "receipt_0000000000000000",
        "end_to_end_id": "endtoend_0000000000000000",
        "refusal": {
          "code": "string",
          "message": "string"
        },
        "test_substitution": {
          "settled_against": "sandbox_fixture",
          "original_document_settled": false,
          "document_amount_minor": 1000,
          "note": "string"
        }
      }
    }
  ],
  "created_at": "string",
  "source": "api",
  "conversation_id": "conversation_0000000000000000"
}

POST /v1/payables/batches/{batchId}/items

POSThttps://api.codespar.dev/v1/payables/batches/{batchId}/items

Add files or items to an open batch

Same rules as creating: every new item is resolved on the rail; version bumps.

Scope: payables:write.

Path parameters

NameTypeRequiredDescription
batchIdstringyes—

Request body

FieldTypeRequiredDescription
expected_versionintegeryes—
file_idsarray of stringno—
itemsarray of objectno—

Responses

StatusBodyDescription
200objectOK
400objectThe body did not match, or nothing to add.
404objectNo batch with this id in this project.
409objectStale expected_version (current batch in details.batch), or the batch was confirmed or discarded.
422objectA file_id that is not an unexpired file of this project, or the batch would pass 20 files / 500 items.

Response 200

FieldTypeRequiredDescription
batch_idstringyespbat_ followed by 20 characters.
confirmationobject,nullyes—
conversation_idstring,nullyes—
created_atstringyes—
currency"BRL"yes—
discardedobject,nullyesSet once the batch was discarded (POST .../discard). With nothing dispatched before, the status is canceled; after a partial execution the status is computed from the items that remain, which kept their payments.
itemsarray of objectyes—
list_hashstringyesSHA-256 over the canonical list: for each non-excluded item in item_id order item_id|rail|destination id|amount_minor|currency, then the total. Send it back on pay.
source"api" | "chat" | "dashboard" | "cli" | "mcp"yes—
status"reviewing" | "ready" | "confirmed" | "executing" | "completed" | "partially_completed" | "failed" | "canceled"yes—
totalsobjectyes—
versionintegeryesBumps on every change to the list. A confirmation binds to one version.
Example request
curl -X POST https://api.codespar.dev/v1/payables/batches/{batchId}/items \
  -H "Authorization: Bearer $CODESPAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
       "expected_version": 0,
       "file_ids": [
         "string"
       ],
       "items": [
         {
           "pix_key": "string",
           "boleto_line": "string",
           "br_code": "string",
           "amount_minor": 1000,
           "description": "string",
           "reference": "string"
         }
       ]
     }'
POST /v1/payables/batches/{batchId}/items HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
Content-Type: application/json

{
  "expected_version": 0,
  "file_ids": [
    "string"
  ],
  "items": [
    {
      "pix_key": "string",
      "boleto_line": "string",
      "br_code": "string",
      "amount_minor": 1000,
      "description": "string",
      "reference": "string"
    }
  ]
}
import os
import requests

res = requests.post(
    "https://api.codespar.dev/v1/payables/batches/{batchId}/items",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
    json={
      "expected_version": 0,
      "file_ids": [
        "string"
      ],
      "items": [
        {
          "pix_key": "string",
          "boleto_line": "string",
          "br_code": "string",
          "amount_minor": 1000,
          "description": "string",
          "reference": "string"
        }
      ]
    },
)
res.raise_for_status()
data = res.json()
const res = await fetch("https://api.codespar.dev/v1/payables/batches/{batchId}/items", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "expected_version": 0,
    "file_ids": [
      "string"
    ],
    "items": [
      {
        "pix_key": "string",
        "boleto_line": "string",
        "br_code": "string",
        "amount_minor": 1000,
        "description": "string",
        "reference": "string"
      }
    ]
  }),
});

const data = await res.json();
const r = await cs.api.response("post", "/v1/payables/batches/{batchId}/items", {
  path: {
    batchId: "batch_0000000000000000"
  },
  body: {
    expected_version: 0,
    file_ids: [
      "string"
    ],
    items: [
      {
        pix_key: "string",
        boleto_line: "string",
        br_code: "string",
        amount_minor: 1000,
        description: "string",
        reference: "string"
      }
    ]
  }
});
// r.status is one of the documented statuses (200, 422),
// each with its own body shape in r.data; nothing here throws on 422.
if (r.ok) {
  console.log(r.data);
}
Example response 200
application/json
{
  "batch_id": "batch_0000000000000000",
  "status": "reviewing",
  "version": 0,
  "list_hash": "string",
  "currency": "BRL",
  "totals": {
    "items": 0,
    "payable_items": 0,
    "excluded": 0,
    "amount_minor": 1000
  },
  "confirmation": {
    "version": 0,
    "list_hash": "string",
    "confirmed_by": "string",
    "confirmed_at": "string",
    "mandate_id": "mandate_0000000000000000"
  },
  "discarded": {
    "at": "string",
    "items": 0
  },
  "items": [
    {
      "item_id": "item_0000000000000000",
      "position": 0,
      "origin": {
        "file_id": "file_0000000000000000",
        "page": 0,
        "row": 0,
        "kind": "boleto_line"
      },
      "rail": "boleto",
      "rail_chosen_by": "default",
      "ways": [
        {
          "rail": "pix",
          "origin": {
            "page": 0,
            "kind": "boleto_line"
          },
          "destination": {
            "kind": "boleto",
            "display": "string",
            "beneficiary_name": "Example",
            "beneficiary_document_masked": "string",
            "due_date": "2026-01-15",
            "beneficiary_institution": "string",
            "key": "string"
          },
          "unavailable": {
            "code": "br_code_expired",
            "message": "string"
          }
        }
      ],
      "document": {
        "title": "Example",
        "issuer": "string",
        "due_date": "2026-01-15",
        "amount_minor": 1000
      },
      "state": "needs_complement",
      "missing": [
        "amount"
      ],
      "destination": {
        "kind": "boleto",
        "display": "string",
        "beneficiary_name": "Example",
        "beneficiary_document_masked": "string",
        "due_date": "2026-01-15",
        "beneficiary_institution": "string",
        "key": "string"
      },
      "amount_minor": 1000,
      "amount_source": "rail_quote",
      "description": "string",
      "reference": "string",
      "checks": [
        {
          "code": "carrier_check_digits",
          "status": "pass",
          "message": "string",
          "simulated": true
        }
      ],
      "payment": {
        "state": "executed",
        "attempt_id": "attempt_0000000000000000",
        "receipt_id": "receipt_0000000000000000",
        "end_to_end_id": "endtoend_0000000000000000",
        "refusal": {
          "code": "string",
          "message": "string"
        },
        "test_substitution": {
          "settled_against": "sandbox_fixture",
          "original_document_settled": false,
          "document_amount_minor": 1000,
          "note": "string"
        }
      }
    }
  ],
  "created_at": "string",
  "source": "api",
  "conversation_id": "conversation_0000000000000000"
}

PATCH /v1/payables/batches/{batchId}/items/{itemId}

PATCHhttps://api.codespar.dev/v1/payables/batches/{batchId}/items/{itemId}

Complement, correct, choose the way or exclude one item

Re-resolves the item's destination on the rail, re-runs its checks, keeps every other item as it is and bumps version. A correction of the carrier is a new carrier for the same item. rail chooses the way of an item that has two (ways): the item re-resolves on it, rail_chosen_by becomes person (dashboard, chat) or integration (API key), and a switch after a confirmation needs a new one, since list_hash covers rail.

The confirmation binds to the list. version bumps on every change and list_hash is the SHA-256 of the canonical list (each non-excluded item in item_id order as item_id|rail|destination id|amount_minor|currency, then the total). Any change after a confirmation needs a new one.

Scope: payables:write.

Path parameters

NameTypeRequiredDescription
batchIdstringyes—
itemIdstringyespbl_ followed by 16 characters.

Request body

FieldTypeRequiredDescription
amount_minorintegerno—
boleto_linestringno—
br_codestringno—
descriptionstringno—
excludedbooleanno—
expected_versionintegeryes—
pix_keystringno—
rail"pix" | "boleto"noThe way to pay an item that has two (ways): the item re-resolves on it. Refused 409 payable_item_rail_unavailable when the item has no such way or it is unavailable.

Responses

StatusBodyDescription
200objectOK
400objectThe body did not match the schema.
404objectNo such batch or item in this project.
409objectpayable_batch_version_conflict: expected_version is stale; details.batch is the current batch. payable_batch_locked: the batch was confirmed. payable_item_rail_unavailable: rail names a way the item does not have, or one that is unavailable (details.unavailable says why, details.ways lists the item's ways). payable_batch_discarded: the batch was discarded.
422objectThe correction does not fit this item.

Response 200

FieldTypeRequiredDescription
batch_idstringyespbat_ followed by 20 characters.
confirmationobject,nullyes—
conversation_idstring,nullyes—
created_atstringyes—
currency"BRL"yes—
discardedobject,nullyesSet once the batch was discarded (POST .../discard). With nothing dispatched before, the status is canceled; after a partial execution the status is computed from the items that remain, which kept their payments.
itemsarray of objectyes—
list_hashstringyesSHA-256 over the canonical list: for each non-excluded item in item_id order item_id|rail|destination id|amount_minor|currency, then the total. Send it back on pay.
source"api" | "chat" | "dashboard" | "cli" | "mcp"yes—
status"reviewing" | "ready" | "confirmed" | "executing" | "completed" | "partially_completed" | "failed" | "canceled"yes—
totalsobjectyes—
versionintegeryesBumps on every change to the list. A confirmation binds to one version.
Example request
curl -X PATCH https://api.codespar.dev/v1/payables/batches/{batchId}/items/{itemId} \
  -H "Authorization: Bearer $CODESPAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
       "expected_version": 0,
       "amount_minor": 1000,
       "pix_key": "string",
       "boleto_line": "string",
       "br_code": "string",
       "description": "string",
       "excluded": true,
       "rail": "pix"
     }'
PATCH /v1/payables/batches/{batchId}/items/{itemId} HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
Content-Type: application/json

{
  "expected_version": 0,
  "amount_minor": 1000,
  "pix_key": "string",
  "boleto_line": "string",
  "br_code": "string",
  "description": "string",
  "excluded": true,
  "rail": "pix"
}
import os
import requests

res = requests.patch(
    "https://api.codespar.dev/v1/payables/batches/{batchId}/items/{itemId}",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
    json={
      "expected_version": 0,
      "amount_minor": 1000,
      "pix_key": "string",
      "boleto_line": "string",
      "br_code": "string",
      "description": "string",
      "excluded": True,
      "rail": "pix"
    },
)
res.raise_for_status()
data = res.json()
const res = await fetch("https://api.codespar.dev/v1/payables/batches/{batchId}/items/{itemId}", {
  method: "PATCH",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "expected_version": 0,
    "amount_minor": 1000,
    "pix_key": "string",
    "boleto_line": "string",
    "br_code": "string",
    "description": "string",
    "excluded": true,
    "rail": "pix"
  }),
});

const data = await res.json();
const r = await cs.api.response("patch", "/v1/payables/batches/{batchId}/items/{itemId}", {
  path: {
    batchId: "batch_0000000000000000",
    itemId: "item_0000000000000000"
  },
  body: {
    expected_version: 0,
    amount_minor: 1000,
    pix_key: "string",
    boleto_line: "string",
    br_code: "string",
    description: "string",
    excluded: true,
    rail: "pix"
  }
});
// r.status is one of the documented statuses (200, 422),
// each with its own body shape in r.data; nothing here throws on 422.
if (r.ok) {
  console.log(r.data);
}
Example response 200
application/json
{
  "batch_id": "batch_0000000000000000",
  "status": "reviewing",
  "version": 0,
  "list_hash": "string",
  "currency": "BRL",
  "totals": {
    "items": 0,
    "payable_items": 0,
    "excluded": 0,
    "amount_minor": 1000
  },
  "confirmation": {
    "version": 0,
    "list_hash": "string",
    "confirmed_by": "string",
    "confirmed_at": "string",
    "mandate_id": "mandate_0000000000000000"
  },
  "discarded": {
    "at": "string",
    "items": 0
  },
  "items": [
    {
      "item_id": "item_0000000000000000",
      "position": 0,
      "origin": {
        "file_id": "file_0000000000000000",
        "page": 0,
        "row": 0,
        "kind": "boleto_line"
      },
      "rail": "boleto",
      "rail_chosen_by": "default",
      "ways": [
        {
          "rail": "pix",
          "origin": {
            "page": 0,
            "kind": "boleto_line"
          },
          "destination": {
            "kind": "boleto",
            "display": "string",
            "beneficiary_name": "Example",
            "beneficiary_document_masked": "string",
            "due_date": "2026-01-15",
            "beneficiary_institution": "string",
            "key": "string"
          },
          "unavailable": {
            "code": "br_code_expired",
            "message": "string"
          }
        }
      ],
      "document": {
        "title": "Example",
        "issuer": "string",
        "due_date": "2026-01-15",
        "amount_minor": 1000
      },
      "state": "needs_complement",
      "missing": [
        "amount"
      ],
      "destination": {
        "kind": "boleto",
        "display": "string",
        "beneficiary_name": "Example",
        "beneficiary_document_masked": "string",
        "due_date": "2026-01-15",
        "beneficiary_institution": "string",
        "key": "string"
      },
      "amount_minor": 1000,
      "amount_source": "rail_quote",
      "description": "string",
      "reference": "string",
      "checks": [
        {
          "code": "carrier_check_digits",
          "status": "pass",
          "message": "string",
          "simulated": true
        }
      ],
      "payment": {
        "state": "executed",
        "attempt_id": "attempt_0000000000000000",
        "receipt_id": "receipt_0000000000000000",
        "end_to_end_id": "endtoend_0000000000000000",
        "refusal": {
          "code": "string",
          "message": "string"
        },
        "test_substitution": {
          "settled_against": "sandbox_fixture",
          "original_document_settled": false,
          "document_amount_minor": 1000,
          "note": "string"
        }
      }
    }
  ],
  "created_at": "string",
  "source": "api",
  "conversation_id": "conversation_0000000000000000"
}

POST /v1/payables/batches/{batchId}/pay

POSThttps://api.codespar.dev/v1/payables/batches/{batchId}/pay
Moves money

pays every payable item through the governed consumer-payment cycle

Confirm the exact list and pay it

Records the confirmation (version, hash, who, mandate) and pays every payable item through the governed consumer-payment cycle: mandate verification, policy, caps under lock, hold, dispatch with uncertainty classification and a sealed receipt. Each item gets its own payment: an item refused by the mandate, a policy or funds is refused with the code and does not stop the others.

Who confirms. A verified person through the dashboard (the decide gate, enforcement on), or an API key with payables:pay and a mandate_id of this organization.

Resume, never pay twice. A later call with the same version and list_hash resumes: executed items are returned as they are, an uncertain item is looked up on the payment's own record first and never resent, and only items never dispatched are dispatched. Two concurrent calls claim items with a conditional update, so an item is dispatched at most once. The attempt id is derived from the item and its quote, so the same item at the same quote is the same attempt, hold and receipt.

The rail is consulted again before each dispatch; an answer that is not the confirmed one is refused with payable_quote_diverged and nothing is sent. A large batch may answer with status: executing after about 20 seconds: repeat the same call (same version and list_hash) to continue with the items never dispatched; GET shows progress.

Idempotency-Key is required.

Scope: payables:pay.

Path parameters

NameTypeRequiredDescription
batchIdstringyes—

Request body

FieldTypeRequiredDescription
list_hashstringyes—
mandate_idstringnoRequired for an API key. A chat batch paid by its person uses the conversation's Shop mandate when absent.
versionintegeryes—

Responses

StatusBodyDescription
200objectOK
400objectThe body did not match, or the Idempotency-Key is missing.
403objectThe dashboard path confirms only as a verified person: the person's session token is missing, invalid or names someone else. An API key never takes this path; it pays with payables:pay and a mandate_id. mandate_paused, mandate_revoked, mandate_expired: the mandate_id a first confirmation names cannot pay; nothing is recorded, and the batch can be confirmed under an active mandate.
404objectNo batch with this id in this project, or no such mandate in this organization.
409objectpayable_batch_version_mismatch: version/list_hash are not the current list's; details.batch is the current batch. payable_batch_discarded: the batch was discarded.
422objectpayable_batch_not_ready: a non-excluded item is not ready (details.items). payable_batch_mandate_required: an API key named no mandate_id. payable_batch_mandate_unavailable: the conversation has no Shop mandate to pay under.

Response 200

FieldTypeRequiredDescription
batch_idstringyespbat_ followed by 20 characters.
confirmationobject,nullyes—
conversation_idstring,nullyes—
created_atstringyes—
currency"BRL"yes—
discardedobject,nullyesSet once the batch was discarded (POST .../discard). With nothing dispatched before, the status is canceled; after a partial execution the status is computed from the items that remain, which kept their payments.
itemsarray of objectyes—
list_hashstringyesSHA-256 over the canonical list: for each non-excluded item in item_id order item_id|rail|destination id|amount_minor|currency, then the total. Send it back on pay.
source"api" | "chat" | "dashboard" | "cli" | "mcp"yes—
status"reviewing" | "ready" | "confirmed" | "executing" | "completed" | "partially_completed" | "failed" | "canceled"yes—
totalsobjectyes—
versionintegeryesBumps on every change to the list. A confirmation binds to one version.
Example request
curl -X POST https://api.codespar.dev/v1/payables/batches/{batchId}/pay \
  -H "Authorization: Bearer $CODESPAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
       "version": 0,
       "list_hash": "string",
       "mandate_id": "mandate_0000000000000000"
     }'
POST /v1/payables/batches/{batchId}/pay HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
Content-Type: application/json

{
  "version": 0,
  "list_hash": "string",
  "mandate_id": "mandate_0000000000000000"
}
import os
import requests

res = requests.post(
    "https://api.codespar.dev/v1/payables/batches/{batchId}/pay",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
    json={
      "version": 0,
      "list_hash": "string",
      "mandate_id": "mandate_0000000000000000"
    },
)
res.raise_for_status()
data = res.json()
const res = await fetch("https://api.codespar.dev/v1/payables/batches/{batchId}/pay", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "version": 0,
    "list_hash": "string",
    "mandate_id": "mandate_0000000000000000"
  }),
});

const data = await res.json();
const r = await cs.api.response("post", "/v1/payables/batches/{batchId}/pay", {
  path: {
    batchId: "batch_0000000000000000"
  },
  header: {
    "Idempotency-Key": "string"
  },
  body: {
    version: 0,
    list_hash: "string",
    mandate_id: "mandate_0000000000000000"
  }
});
// r.status is one of the documented statuses (200, 403, 422),
// each with its own body shape in r.data; nothing here throws on 403.
if (r.ok) {
  console.log(r.data);
}
Example response 200
application/json
{
  "batch_id": "batch_0000000000000000",
  "status": "reviewing",
  "version": 0,
  "list_hash": "string",
  "currency": "BRL",
  "totals": {
    "items": 0,
    "payable_items": 0,
    "excluded": 0,
    "amount_minor": 1000
  },
  "confirmation": {
    "version": 0,
    "list_hash": "string",
    "confirmed_by": "string",
    "confirmed_at": "string",
    "mandate_id": "mandate_0000000000000000"
  },
  "discarded": {
    "at": "string",
    "items": 0
  },
  "items": [
    {
      "item_id": "item_0000000000000000",
      "position": 0,
      "origin": {
        "file_id": "file_0000000000000000",
        "page": 0,
        "row": 0,
        "kind": "boleto_line"
      },
      "rail": "boleto",
      "rail_chosen_by": "default",
      "ways": [
        {
          "rail": "pix",
          "origin": {
            "page": 0,
            "kind": "boleto_line"
          },
          "destination": {
            "kind": "boleto",
            "display": "string",
            "beneficiary_name": "Example",
            "beneficiary_document_masked": "string",
            "due_date": "2026-01-15",
            "beneficiary_institution": "string",
            "key": "string"
          },
          "unavailable": {
            "code": "br_code_expired",
            "message": "string"
          }
        }
      ],
      "document": {
        "title": "Example",
        "issuer": "string",
        "due_date": "2026-01-15",
        "amount_minor": 1000
      },
      "state": "needs_complement",
      "missing": [
        "amount"
      ],
      "destination": {
        "kind": "boleto",
        "display": "string",
        "beneficiary_name": "Example",
        "beneficiary_document_masked": "string",
        "due_date": "2026-01-15",
        "beneficiary_institution": "string",
        "key": "string"
      },
      "amount_minor": 1000,
      "amount_source": "rail_quote",
      "description": "string",
      "reference": "string",
      "checks": [
        {
          "code": "carrier_check_digits",
          "status": "pass",
          "message": "string",
          "simulated": true
        }
      ],
      "payment": {
        "state": "executed",
        "attempt_id": "attempt_0000000000000000",
        "receipt_id": "receipt_0000000000000000",
        "end_to_end_id": "endtoend_0000000000000000",
        "refusal": {
          "code": "string",
          "message": "string"
        },
        "test_substitution": {
          "settled_against": "sandbox_fixture",
          "original_document_settled": false,
          "document_amount_minor": 1000,
          "note": "string"
        }
      }
    }
  ],
  "created_at": "string",
  "source": "api",
  "conversation_id": "conversation_0000000000000000"
}

GET /v1/payables/counts

GEThttps://api.codespar.dev/v1/payables/counts

Count the payables waiting for a person

How many of this project's payables are NEEDS_REVIEW and how many are READY, and how many unpaid open ones fall due in the rest of this week. due_this_week counts READY and APPROVED payables (an APPROVED payment is claimed or awaiting an approval and has not paid yet) whose EFFECTIVE due date is from today to Sunday of the current São Paulo week, inclusive (the day taken in America/Sao_Paulo; weeks run Monday to Sunday). The effective due date is the printed one rolled forward to the next bank business day, the day until which the bill is payable without charges: a boleto due last Saturday is due this Monday. Due today is in it; an effective due date before today is overdue and is not. NEEDS_REVIEW is not in due_this_week, and a payable with no due date never is.

overdue counts the same READY and APPROVED set whose effective due date is before today.

Scope: payables:read.

Responses

StatusBodyDescription
200objectOK

Response 200

FieldTypeRequiredDescription
due_this_weekintegeryesUnpaid open payables (READY and APPROVED) whose EFFECTIVE due date (due_date_effective) is from today to Sunday of the current São Paulo week, inclusive. Due today is in it; an effective due date before today is overdue and is not; NEEDS_REVIEW is not in it.
needs_reviewintegeryesPayables in NEEDS_REVIEW.
overdueintegeryesThe same unpaid open payables (READY and APPROVED) whose EFFECTIVE due date is before today in São Paulo. A printed date before today that rolls forward to today is not overdue; NEEDS_REVIEW is not in it.
readyintegeryesPayables in READY.
Example request
curl -X GET https://api.codespar.dev/v1/payables/counts \
  -H "Authorization: Bearer $CODESPAR_API_KEY"
GET /v1/payables/counts HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
import os
import requests

res = requests.get(
    "https://api.codespar.dev/v1/payables/counts",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
)
res.raise_for_status()
data = res.json()
const res = await fetch("https://api.codespar.dev/v1/payables/counts", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
  },
});

const data = await res.json();
const result = await cs.api.get("/v1/payables/counts");
Example response 200
application/json
{
  "needs_review": 0,
  "ready": 0,
  "due_this_week": 0,
  "overdue": 0
}

POST /v1/payables/documents

POSThttps://api.codespar.dev/v1/payables/documents

Create a payable from an uploaded document (PDF or image)

The file goes in the multipart field document (PDF, PNG, JPEG or WEBP by its bytes; the declared type is not consulted), an optional text field actor names the person who uploaded it, and the payable is READ from the document: attribution.channel is upload, every field carries from: "modelo", a page and a literal excerpt, and extraction names the model and its token counts. From there it enters the same pipeline as POST /v1/payables — the same schema, the same deterministic checks, the same duplicate axes, the same statuses — and answers the same 200/201 shape. A JSON body sent here answers 415 payable_document_required.

Upload (multipart). The door is behind a deployment switch: while it is off every upload answers 503 payable_extraction_disabled and nothing is read or stored. The file is checked before any reader sees it — real type by magic bytes (415 payable_document_type_unsupported; HEIC is detected and named but not accepted), at most 10 MB (413 payable_document_too_large), at most 10 pages (422 payable_document_too_many_pages), no JavaScript, embedded files, forms or launch actions (422 payable_document_active_content; refused, not stripped, so the hash on the payable is the hash of the bytes sent), and it must open (422 payable_document_unreadable, details.reason encrypted or malformed). The SHA-256 of the file is document_hash, computed before anything else: a file an open or paid payable of this project already holds answers 409 payable_duplicate on the document_hash axis without being read again. The original file is NOT kept; re-extraction is re-upload.

The reader's citations are checked against the PDF's text layer (§7): each field's evidence.verification is verified, moved (the excerpt is on another page) or unverifiable (an image, or a PDF with no text layer — a scan). moved and unverifiable on a critical field open the critical_field_without_evidence review trigger; an excerpt that is in NO page of a critical field refuses the whole document, 422 payable_evidence_fabricated, and nothing is stored. A reader that did not find enough for the schema answers 422 payable_extraction_incomplete naming the missing paths. A reader that did not answer after a bounded retry answers 502 payable_extraction_unavailable (retriable, nothing stored; the original is not kept, so there is no failed row to resume); a reader that declined the document answers 422 payable_extraction_refused. Nothing of the document's content reaches a log, an audit event or an error body: the trail carries the hash, the model, the token counts and the verdict counts.

The amount and the payee come from the rail, never from what was sent. amount_matches_rail, beneficiary_document_matches_supplier and due_date_state run against a consult of the boleto on the payable, and they block READY whether or not they answered. A consult that could not be made — no connected payment provider, a provider that did not answer — leaves them indisponivel and the payable NEEDS_REVIEW, named under review.review_by. pix_key_owner_matches_supplier is not consulted in this release and blocks a payable carrying a Pix BR Code with cause required_not_implemented. VALIDATING is never returned: nothing re-runs a validation yet, so a check with no answer goes to a person rather than waiting. due_date_state calls a payable overdue only after its EFFECTIVE due date: a due date on a weekend, a national bank holiday (Carnival Monday and Tuesday and Corpus Christi included) or, for a boleto, the last business day of the year is payable without charges on the next business day. State and municipal holidays are not considered.

Status. Two fields answer two questions. review.validation_status is what the checks alone say: REJECTED (a check that rejects failed), NEEDS_REVIEW (a check failed, or a required check has no answer it can have), VALIDATING (a required, implemented check did not answer and will be retried) or VALIDATED (every required check passed). status is what the payable may do: READY only from VALIDATED with no review trigger open, and NEEDS_REVIEW when a trigger is open. The trigger this API evaluates is new_supplier: this project has no executed payable for the supplier's document, or the payable names no supplier document. It is evaluated per project, never across projects.

Why things are in review. review.review_by lists each check that sends the payable to a person and its cause: check_failed, retries_exhausted or required_not_implemented. review.review_triggers lists the open triggers. review.not_covered lists checks that did not run and do not block READY in this release (supplier_registry, bank_details_changed).

Idempotency. Send an Idempotency-Key header to make a retry safe. The same key with the same file answers 200 with idempotent_replay: true; the same key with a different file answers 409 idempotency_key_conflict. Both are checked before the reader runs, so a retry never spends a model call.

Scope: payables:write.

Responses

StatusBodyDescription
200objectOK
201objectOK
400objectThe multipart field document is absent or empty (payable_document_required), the actor field is out of shape or the multipart body could not be read (invalid_body), or the Idempotency-Key header is empty or longer than 255 characters.
409objectpayable_duplicate: an open or paid payable of this project already holds this file's document_hash (refused before the reader runs; no details.validation) or shares a carrier with what was read. idempotency_key_conflict: the key was used with a different file.
413objectThe uploaded file exceeds 10 MB. Nothing was read or stored.
415objectpayable_document_type_unsupported: the file is not a PDF, PNG, JPEG or WEBP by its bytes; details.detected names what it is when known (image/heic is detected and not accepted). payable_document_required: the body was not multipart/form-data at all.
422objectThe PDF has more than 10 pages (payable_document_too_many_pages), carries active content (payable_document_active_content, details.found), or cannot be opened (payable_document_unreadable, details.reason); the reader cited, for a critical field, a passage in no page (payable_evidence_fabricated, details.fields); the reader found too little (payable_extraction_incomplete, details.issues); the reader declined the document (payable_extraction_refused); or a carrier it read fails its check digits (payable_carrier_malformed). None of these stores anything.
502objectpayable_extraction_unavailable: the reader did not answer usably after a bounded retry (details.attempts); nothing was stored; retry later with the same Idempotency-Key. payable_extraction_failed: the extraction request was refused for a reason on our side; not fixable by retrying.
503objectpayable_extraction_disabled: the document door is switched off on this deployment; send the payable as JSON (structured or manual). payable_extractor_unconfigured: the door is on with no reader behind it; a deployment fault, not retriable. vault_unavailable: the key that protects supplier documents could not be read; retry later with the same Idempotency-Key.

Response 200

FieldTypeRequiredDescription
attributionobjectyes—
carriersobjectno—
created_atstringyesISO 8601 instant the payable was created.
doc_type"invoice" | "danfe_nfe" | "boleto" | "unknown"yes—
document_hashstringno—
due_dateobjectno—
extractionobjectno—
idempotent_replaybooleanyesTrue when this answer replays an earlier call made with the same Idempotency-Key.
invoice_numberobjectno—
issue_dateobjectno—
itemsarray of objectno—
payable_idstringyes—
paymentobject,nullyesWhat paying it did (ent#1587); null until a payment was attempted.
rail_quoteobjectno—
recipientobjectno—
reviewobjectyes—
schema_version"payable.v1"yes—
serieobjectno—
status"RECEIVED" | "EXTRACTING" | "EXTRACTED" | "VALIDATING" | "VALIDATED" | "NEEDS_REVIEW" | "READY" | "APPROVED" | "EXECUTED" | "REJECTED" | "FAILED_EXTRACTION" | "EXPIRED"yes—
supplierobjectyes—
totalsobjectyes—
updated_atstringyesISO 8601 instant of the last change to the payable.
validationobjectyes—
Example request
curl -X POST https://api.codespar.dev/v1/payables/documents \
  -H "Authorization: Bearer $CODESPAR_API_KEY"
POST /v1/payables/documents HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
import os
import requests

res = requests.post(
    "https://api.codespar.dev/v1/payables/documents",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
)
res.raise_for_status()
data = res.json()
const res = await fetch("https://api.codespar.dev/v1/payables/documents", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
  },
});

const data = await res.json();
const r = await cs.api.response("post", "/v1/payables/documents", {
  body: {
    document: "string",
    actor: "string"
  }
});
// r.status is one of the documented statuses (200, 422),
// each with its own body shape in r.data; nothing here throws on 422.
if (r.ok) {
  console.log(r.data);
}
Example response 200
application/json
{
  "schema_version": "payable.v1",
  "doc_type": "invoice",
  "attribution": {
    "channel": "upload",
    "credential_id": "credential_0000000000000000",
    "connection_id": "conn_0000000000000000",
    "actor": "string"
  },
  "document_hash": "string",
  "supplier": {
    "document": {
      "value": "string",
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "name": {
      "value": "string",
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    }
  },
  "recipient": {
    "document": {
      "value": "string",
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "name": {
      "value": "string",
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    }
  },
  "invoice_number": {
    "value": "string",
    "from": "parser_local",
    "evidence": {
      "page": 0,
      "excerpt": "string",
      "verification": "verified"
    },
    "confidence": 0
  },
  "serie": {
    "value": "string",
    "from": "parser_local",
    "evidence": {
      "page": 0,
      "excerpt": "string",
      "verification": "verified"
    },
    "confidence": 0
  },
  "issue_date": {
    "value": "2026-01-15",
    "from": "parser_local",
    "evidence": {
      "page": 0,
      "excerpt": "string",
      "verification": "verified"
    },
    "confidence": 0
  },
  "due_date": {
    "value": "2026-01-15",
    "from": "parser_local",
    "evidence": {
      "page": 0,
      "excerpt": "string",
      "verification": "verified"
    },
    "confidence": 0
  },
  "items": [
    {
      "description": {
        "value": "string",
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      },
      "quantity": {
        "value": 1000,
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      },
      "unit_amount_minor": {
        "value": 1000,
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      },
      "total_amount_minor": {
        "value": 1000,
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      }
    }
  ],
  "totals": {
    "subtotal_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "discount_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "tax_relief_deducted_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "freight_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "insurance_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "other_charges_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "taxes_added_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "taxes_included_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "grand_total_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "currency": {
      "value": "string",
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    }
  },
  "carriers": {
    "boleto": "string",
    "br_code": "string",
    "nfe": "string"
  },
  "extraction": {
    "model": "string",
    "model_version": "string",
    "input_tokens": 0,
    "output_tokens": 0,
    "reasoning_tokens": 0
  },
  "payable_id": "payable_0000000000000000",
  "status": "RECEIVED",
  "rail_quote": {
    "beneficiary": {
      "value": "string",
      "from": "trilho"
    },
    "beneficiary_document_masked": {
      "value": "string",
      "from": "trilho"
    },
    "final_beneficiary": {
      "value": "string",
      "from": "trilho"
    },
    "final_beneficiary_document_masked": {
      "value": "string",
      "from": "trilho"
    },
    "amount_minor": {
      "value": 1000,
      "from": "trilho"
    },
    "discount_minor": {
      "value": 1000,
      "from": "trilho"
    },
    "interest_minor": {
      "value": 1000,
      "from": "trilho"
    },
    "fine_minor": {
      "value": 1000,
      "from": "trilho"
    },
    "due_date": {
      "value": "2026-01-15",
      "from": "trilho"
    },
    "quoted_at": "2026-01-15T12:00:00.000Z"
  },
  "validation": {
    "carrier_check_digits": {
      "outcome": "ok",
      "detail": "string"
    },
    "document_check_digits": {
      "outcome": "ok",
      "detail": "string"
    },
    "nfe_key_consistency": {
      "outcome": "ok",
      "detail": "string"
    },
    "arithmetic": {
      "outcome": "ok",
      "detail": "string"
    },
    "duplicate_document": {
      "outcome": "ok",
      "detail": "string"
    },
    "amount_matches_rail": {
      "outcome": "ok",
      "detail": "string"
    },
    "beneficiary_document_matches_supplier": {
      "outcome": "ok",
      "detail": "string"
    },
    "due_date_state": {
      "outcome": "ok",
      "detail": "string"
    },
    "pix_key_owner_matches_supplier": {
      "outcome": "ok",
      "detail": "string"
    },
    "supplier_registry": {
      "outcome": "ok",
      "detail": "string"
    },
    "bank_details_changed": {
      "outcome": "ok",
      "detail": "string"
    }
  },
  "review": {
    "validation_status": "REJECTED",
    "review_by": [
      {
        "code": "string",
        "cause": "check_failed"
      }
    ],
    "rejected_by": [
      "string"
    ],
    "confirmed_by": [
      "string"
    ],
    "awaiting": [
      "string"
    ],
    "not_covered": [
      "string"
    ],
    "review_triggers": [
      "string"
    ],
    "resolutions": [
      {
        "code": "string",
        "resolution": "human_correction",
        "at": "string",
        "by": "string",
        "by_source": "verified_user_token",
        "fields": [
          "string"
        ],
        "confirmed_beneficiary_document": "string"
      }
    ]
  },
  "payment": {
    "state": "awaiting_approval",
    "approval_id": "approval_0000000000000000",
    "settled_against": "document_line",
    "document_amount_minor": 1000,
    "original_document_settled": false,
    "settlement_note": "string",
    "mandate_id": "mandate_0000000000000000",
    "attempt_id": "attempt_0000000000000000",
    "tries": 0,
    "amount_minor": 1000,
    "quote_hash": "string",
    "claimed_at": "string",
    "key_id": "key_0000000000000000",
    "via": "string",
    "executed_at": "string",
    "receipt_id": "receipt_0000000000000000",
    "transaction_id": "transaction_0000000000000000",
    "end_to_end_id": "endtoend_0000000000000000",
    "money_moved": true,
    "uncertain_code": "string",
    "hold_entry_id": "holdentry_0000000000000000",
    "last_refusal": {
      "code": "string",
      "attempt_id": "attempt_0000000000000000",
      "at": "string"
    }
  },
  "created_at": "string",
  "updated_at": "string",
  "idempotent_replay": true
}

POST /v1/payables/files

POSThttps://api.codespar.dev/v1/payables/files

Upload a document or a spreadsheet of payables

One file per request, in the multipart field file: a PDF, a PNG, JPEG or WEBP image, a CSV or an XLSX, accepted by its bytes (the declared type is not consulted). At most 10 MB, 10 pages and 500 rows. A local reader runs in the request — PDF text layer (boleto lines and barcodes, BR Codes, labelled Pix keys), QR codes and boleto barcodes in images, rows of a sheet (pix_key | boleto_line | br_code, amount, description, reference; Portuguese or English headers) — and what it cannot resolve still becomes an item that asks for the missing field. The original file is not kept: only its SHA-256, kind, size and the items found. The file_id is project-scoped and expires in 24 hours if no batch uses it.

Scope: payables:write.

Responses

StatusBodyDescription
201objectOK
400objectNo multipart part named file, or an empty one; or the body could not be read.
413objectOver 10 MB. Nothing was read or kept.
415objectNot a PDF, PNG, JPEG, WEBP, CSV or XLSX by its bytes.
422objectpayable_file_unreadable: details.reason is too_many_pages, active_content, encrypted, malformed, resource_limit, webp_unsupported or sheet_unreadable. payable_file_rows_exceeded: more than 500 data rows.
503objectEvery document inspector is busy; retry in a few seconds.

Response 201

FieldTypeRequiredDescription
expires_atstringyesISO 8601. A file no batch used by then is forgotten.
file_idstringyespfile_ followed by 20 characters. Project-scoped; used by a batch, by the chat and by the remote MCP.
items_foundintegeryesCharges found by the local reader (boleto lines, BR Codes, Pix keys, QR codes, rows).
kind"pdf" | "image" | "csv" | "xlsx"yes—
sha256stringyesSHA-256 of the bytes as sent. The file itself is not kept.
size_bytesintegeryes—
unresolvedintegeryesOf those, how many still miss something the person has to complete (an amount, a destination).
Example request
curl -X POST https://api.codespar.dev/v1/payables/files \
  -H "Authorization: Bearer $CODESPAR_API_KEY"
POST /v1/payables/files HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
import os
import requests

res = requests.post(
    "https://api.codespar.dev/v1/payables/files",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
)
res.raise_for_status()
data = res.json()
const res = await fetch("https://api.codespar.dev/v1/payables/files", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
  },
});

const data = await res.json();
const r = await cs.api.response("post", "/v1/payables/files", {
  body: {
    file: "string"
  }
});
// r.status is one of the documented statuses (200, 422),
// each with its own body shape in r.data; nothing here throws on 422.
if (r.ok) {
  console.log(r.data);
}
Example response 201
application/json
{
  "file_id": "file_0000000000000000",
  "kind": "pdf",
  "sha256": "string",
  "size_bytes": 0,
  "items_found": 0,
  "unresolved": 0,
  "expires_at": "string"
}

POST /v1/payables/files/authorizations

POSThttps://api.codespar.dev/v1/payables/files/authorizations

Authorize a browser to upload files straight to the API

For a browser that cannot send the file through its own server (a proxy that refuses large bodies). Returns a short-lived token and the URL that accepts it: POST upload_url with Authorization: Bearer <token> and the multipart field file, exactly as POST /v1/payables/files. The token uploads at most max_files files of at most max_bytes each into this caller's org, project and environment until expires_at (10 minutes), attributed to this caller; it reads nothing and is accepted by no other route. Only its SHA-256 is kept. Use upload_url as given: it is authenticated by the token alone (send it in the header, never in the URL), answers the dashboard's browser origins only and never with credentials, spends one use per request whatever the outcome, and refuses an absent, unknown, expired or used-up token with 401 payable_upload_token_invalid. Its other answers are those of POST /v1/payables/files.

Scope: payables:write.

Request body

Type: object

Responses

StatusBodyDescription
201objectOK
400objectThe body is not an empty JSON object.

Response 201

FieldTypeRequiredDescription
expires_atstringyesISO 8601, ten minutes from now.
max_bytesintegeryesThe size limit of each file: 10485760.
max_filesintegeryesHow many uploads the token allows: 20.
tokenstringyespfup_ followed by 32 characters. Shown once; it uploads files into this project and authorizes nothing else.
upload_urlstringyesWhere the browser posts each file (multipart field file) with Authorization: Bearer <token>. Use it as given.
Example request
curl -X POST https://api.codespar.dev/v1/payables/files/authorizations \
  -H "Authorization: Bearer $CODESPAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
POST /v1/payables/files/authorizations HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
Content-Type: application/json

{}
import os
import requests

res = requests.post(
    "https://api.codespar.dev/v1/payables/files/authorizations",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
    json={},
)
res.raise_for_status()
data = res.json()
const res = await fetch("https://api.codespar.dev/v1/payables/files/authorizations", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({}),
});

const data = await res.json();
const result = await cs.api.post("/v1/payables/files/authorizations", {
  body: {}
});
Example response 201
application/json
{
  "upload_url": "https://example.com/hook",
  "token": "string",
  "expires_at": "string",
  "max_bytes": 1,
  "max_files": 1
}

GET /v1/payables/{payableId}

GEThttps://api.codespar.dev/v1/payables/{payableId}

Read a payable

The payable, its status, the outcome of every check in validation, and why it has that status in review. Reading never re-runs a check or changes the status. The amount and the payee come from the rail, never from what was sent. amount_matches_rail, beneficiary_document_matches_supplier and due_date_state run against a consult of the boleto on the payable, and they block READY whether or not they answered. A consult that could not be made — no connected payment provider, a provider that did not answer — leaves them indisponivel and the payable NEEDS_REVIEW, named under review.review_by. pix_key_owner_matches_supplier is not consulted in this release and blocks a payable carrying a Pix BR Code with cause required_not_implemented. VALIDATING is never returned: nothing re-runs a validation yet, so a check with no answer goes to a person rather than waiting. due_date_state calls a payable overdue only after its EFFECTIVE due date: a due date on a weekend, a national bank holiday (Carnival Monday and Tuesday and Corpus Christi included) or, for a boleto, the last business day of the year is payable without charges on the next business day. State and municipal holidays are not considered.

Status. Two fields answer two questions. review.validation_status is what the checks alone say: REJECTED (a check that rejects failed), NEEDS_REVIEW (a check failed, or a required check has no answer it can have), VALIDATING (a required, implemented check did not answer and will be retried) or VALIDATED (every required check passed). status is what the payable may do: READY only from VALIDATED with no review trigger open, and NEEDS_REVIEW when a trigger is open. The trigger this API evaluates is new_supplier: this project has no executed payable for the supplier's document, or the payable names no supplier document. It is evaluated per project, never across projects.

Why things are in review. review.review_by lists each check that sends the payable to a person and its cause: check_failed, retries_exhausted or required_not_implemented. review.review_triggers lists the open triggers. review.not_covered lists checks that did not run and do not block READY in this release (supplier_registry, bank_details_changed).

A payable of another project, of another organization and an id that is not a payable id all answer the same 404.

Scope: payables:read.

Path parameters

NameTypeRequiredDescription
payableIdstringyespbl_ followed by 16 characters.

Responses

StatusBodyDescription
200objectOK
404objectNo payable with this id in this project.

Response 200

FieldTypeRequiredDescription
attributionobjectyes—
carriersobjectno—
created_atstringyesISO 8601 instant the payable was created.
doc_type"invoice" | "danfe_nfe" | "boleto" | "unknown"yes—
document_hashstringno—
due_dateobjectno—
extractionobjectno—
invoice_numberobjectno—
issue_dateobjectno—
itemsarray of objectno—
payable_idstringyes—
paymentobject,nullyesWhat paying it did (ent#1587); null until a payment was attempted.
rail_quoteobjectno—
recipientobjectno—
reviewobjectyes—
schema_version"payable.v1"yes—
serieobjectno—
status"RECEIVED" | "EXTRACTING" | "EXTRACTED" | "VALIDATING" | "VALIDATED" | "NEEDS_REVIEW" | "READY" | "APPROVED" | "EXECUTED" | "REJECTED" | "FAILED_EXTRACTION" | "EXPIRED"yes—
supplierobjectyes—
totalsobjectyes—
updated_atstringyesISO 8601 instant of the last change to the payable.
validationobjectyes—
Example request
curl -X GET https://api.codespar.dev/v1/payables/{payableId} \
  -H "Authorization: Bearer $CODESPAR_API_KEY"
GET /v1/payables/{payableId} HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
import os
import requests

res = requests.get(
    "https://api.codespar.dev/v1/payables/{payableId}",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
)
res.raise_for_status()
data = res.json()
const res = await fetch("https://api.codespar.dev/v1/payables/{payableId}", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
  },
});

const data = await res.json();
const result = await cs.api.get("/v1/payables/{payableId}", {
  path: {
    payableId: "payable_0000000000000000"
  }
});
Example response 200
application/json
{
  "schema_version": "payable.v1",
  "doc_type": "invoice",
  "attribution": {
    "channel": "upload",
    "credential_id": "credential_0000000000000000",
    "connection_id": "conn_0000000000000000",
    "actor": "string"
  },
  "document_hash": "string",
  "supplier": {
    "document": {
      "value": "string",
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "name": {
      "value": "string",
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    }
  },
  "recipient": {
    "document": {
      "value": "string",
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "name": {
      "value": "string",
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    }
  },
  "invoice_number": {
    "value": "string",
    "from": "parser_local",
    "evidence": {
      "page": 0,
      "excerpt": "string",
      "verification": "verified"
    },
    "confidence": 0
  },
  "serie": {
    "value": "string",
    "from": "parser_local",
    "evidence": {
      "page": 0,
      "excerpt": "string",
      "verification": "verified"
    },
    "confidence": 0
  },
  "issue_date": {
    "value": "2026-01-15",
    "from": "parser_local",
    "evidence": {
      "page": 0,
      "excerpt": "string",
      "verification": "verified"
    },
    "confidence": 0
  },
  "due_date": {
    "value": "2026-01-15",
    "from": "parser_local",
    "evidence": {
      "page": 0,
      "excerpt": "string",
      "verification": "verified"
    },
    "confidence": 0
  },
  "items": [
    {
      "description": {
        "value": "string",
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      },
      "quantity": {
        "value": 1000,
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      },
      "unit_amount_minor": {
        "value": 1000,
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      },
      "total_amount_minor": {
        "value": 1000,
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      }
    }
  ],
  "totals": {
    "subtotal_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "discount_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "tax_relief_deducted_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "freight_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "insurance_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "other_charges_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "taxes_added_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "taxes_included_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "grand_total_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "currency": {
      "value": "string",
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    }
  },
  "carriers": {
    "boleto": "string",
    "br_code": "string",
    "nfe": "string"
  },
  "extraction": {
    "model": "string",
    "model_version": "string",
    "input_tokens": 0,
    "output_tokens": 0,
    "reasoning_tokens": 0
  },
  "payable_id": "payable_0000000000000000",
  "status": "RECEIVED",
  "rail_quote": {
    "beneficiary": {
      "value": "string",
      "from": "trilho"
    },
    "beneficiary_document_masked": {
      "value": "string",
      "from": "trilho"
    },
    "final_beneficiary": {
      "value": "string",
      "from": "trilho"
    },
    "final_beneficiary_document_masked": {
      "value": "string",
      "from": "trilho"
    },
    "amount_minor": {
      "value": 1000,
      "from": "trilho"
    },
    "discount_minor": {
      "value": 1000,
      "from": "trilho"
    },
    "interest_minor": {
      "value": 1000,
      "from": "trilho"
    },
    "fine_minor": {
      "value": 1000,
      "from": "trilho"
    },
    "due_date": {
      "value": "2026-01-15",
      "from": "trilho"
    },
    "quoted_at": "2026-01-15T12:00:00.000Z"
  },
  "validation": {
    "carrier_check_digits": {
      "outcome": "ok",
      "detail": "string"
    },
    "document_check_digits": {
      "outcome": "ok",
      "detail": "string"
    },
    "nfe_key_consistency": {
      "outcome": "ok",
      "detail": "string"
    },
    "arithmetic": {
      "outcome": "ok",
      "detail": "string"
    },
    "duplicate_document": {
      "outcome": "ok",
      "detail": "string"
    },
    "amount_matches_rail": {
      "outcome": "ok",
      "detail": "string"
    },
    "beneficiary_document_matches_supplier": {
      "outcome": "ok",
      "detail": "string"
    },
    "due_date_state": {
      "outcome": "ok",
      "detail": "string"
    },
    "pix_key_owner_matches_supplier": {
      "outcome": "ok",
      "detail": "string"
    },
    "supplier_registry": {
      "outcome": "ok",
      "detail": "string"
    },
    "bank_details_changed": {
      "outcome": "ok",
      "detail": "string"
    }
  },
  "review": {
    "validation_status": "REJECTED",
    "review_by": [
      {
        "code": "string",
        "cause": "check_failed"
      }
    ],
    "rejected_by": [
      "string"
    ],
    "confirmed_by": [
      "string"
    ],
    "awaiting": [
      "string"
    ],
    "not_covered": [
      "string"
    ],
    "review_triggers": [
      "string"
    ],
    "resolutions": [
      {
        "code": "string",
        "resolution": "human_correction",
        "at": "string",
        "by": "string",
        "by_source": "verified_user_token",
        "fields": [
          "string"
        ],
        "confirmed_beneficiary_document": "string"
      }
    ]
  },
  "payment": {
    "state": "awaiting_approval",
    "approval_id": "approval_0000000000000000",
    "settled_against": "document_line",
    "document_amount_minor": 1000,
    "original_document_settled": false,
    "settlement_note": "string",
    "mandate_id": "mandate_0000000000000000",
    "attempt_id": "attempt_0000000000000000",
    "tries": 0,
    "amount_minor": 1000,
    "quote_hash": "string",
    "claimed_at": "string",
    "key_id": "key_0000000000000000",
    "via": "string",
    "executed_at": "string",
    "receipt_id": "receipt_0000000000000000",
    "transaction_id": "transaction_0000000000000000",
    "end_to_end_id": "endtoend_0000000000000000",
    "money_moved": true,
    "uncertain_code": "string",
    "hold_entry_id": "holdentry_0000000000000000",
    "last_refusal": {
      "code": "string",
      "attempt_id": "attempt_0000000000000000",
      "at": "string"
    }
  },
  "created_at": "string",
  "updated_at": "string"
}

POST /v1/payables/{payableId}/pay

POSThttps://api.codespar.dev/v1/payables/{payableId}/pay
Moves money

Pays a `READY` payable through the governed consumer-payment cycle

Pay a payable under a consumer mandate

Pays a READY payable through the governed consumer-payment cycle — mandate HMAC verification, policy, cap under lock, hold, dispatch with uncertainty classification and a sealed receipt — and moves it to EXECUTED. Only a boleto is paid in this wave.

The rail is consulted again first. The stored quote may have aged, so the eleven checks re-run over a fresh consult, with the review's confirmations standing. If that verdict no longer allows paying, nothing is dispatched: the payable goes back to NEEDS_REVIEW with the new verdict and the call answers payable_quote_diverged. READY is derived from a verdict, never assumed from the row.

The attempt id is derived, from the payable id and the fresh quote (payment.attempt_id). The same quote is the same attempt — a retry lands on the same hold, the same provider anchor and the same receipt — and a different quote is a different attempt, which is why the payable leaves READY before money moves.

What the outcome does to the payable. Success: EXECUTED, payment.state = executed, with receipt_id, transaction_id and end_to_end_id; the response also carries receipt. A refusal that provably moved no money (policy, cap, allowlist, funds, boleto_amount_mismatch): the payable is READY again with payment.last_refusal. A dispatch whose outcome is unknown (psp_dispatch_uncertain, psp_attempt_uncertain, payable_dispatch_unknown): the payable stays APPROVED with payment.state = uncertain, pinned for reconciliation — do not resubmit it, under this payable or another; a later call answers payable_payment_uncertain.

The receipt carries the document hash. Its delivery proof is a merchant_document_ref of kind payable whose reference is payable:<id>;sha256:<document_hash> (the hash part only when the payable has one), covered by the receipt's chain: document → mandate → payment → receipt verifies offline.

A policy approval holds the payable. When the durable policy engine routes the spend to a person (403 policy_denied with approval_id), the payable leaves READY under that approval (payment.state = awaiting_approval, payment.approval_id); /pay answers payable_payment_pending_approval until it is decided. The approved execution pays the attempt and records the outcome on the payable; a denied or expired approval is released by the next /pay, which asks the gate again.

Test projects: the document's own line first, a substituted test payment otherwise. A boleto in a test project is settled as its own line when the sandbox knows it. When it does not, a test line (the sandbox fixture) is paid in its place, and only at the confirmed amount: a fixture of another amount is refused sandbox_fixture_amount_mismatch (409) before anything is held. A substituted payment says so: payment.settled_against = sandbox_fixture, payment.original_document_settled = false, payment.settlement_note in the person's words, payment.document_amount_minor keeps what the document quoted, and the receipt's delivery reference ends in ;settled_against:sandbox_fixture. It is never the settlement of the original document, even at an equal amount. Live projects are untouched.

Scope: payables:pay. The mandate must be one of this organization's consumers' and its merchant_allowlist must name the boleto's digitable line.

Path parameters

NameTypeRequiredDescription
payableIdstringyespbl_ followed by 16 characters.

Request body

FieldTypeRequiredDescription
mandate_idstringyes—

Responses

StatusBodyDescription
200objectOK
400objectThe body did not match the pay schema.
403objectpolicy_denied: the durable policy engine refused (reason, ruleId; approval_id when it routed the spend to a person — once approved, the same attempt is paid and a later call to this route records it). payee_not_allowed: the mandate's allowlist does not name the boleto.
404objectnot_found: no payable with this id in this project. mandate_not_found: no consumer mandate with this id in this organization.
409objectpayable_not_payable: not READY; details.status says what it is. payable_already_paid: EXECUTED; details.receipt_id. payable_payment_in_flight: a dispatch is running. payable_payment_uncertain: a dispatch outcome is unknown and the payable is pinned — do not resubmit. payable_payment_pending_approval: a person is deciding the policy approval (details.approval_id). payable_quote_diverged: the fresh verdict no longer allows paying; the payable is back in NEEDS_REVIEW and details.review says why. payable_payment_conflict: the row moved while this payment was planned; read it again. boleto_amount_mismatch: the provider's true amount moved between the fresh quote and the confirm; nothing paid, the payable is READY.
422objectpayable_rail_unsupported: no boleto carrier to pay by. insufficient_funds and the mandate cap codes carry their usual consumer-payment meanings; the payable is READY again. per_tx_cap_exceeded: the payable's amount is above the mandate's per-transaction cap; a boleto is refused, never routed to approval. details carries amount_minor and per_tx_cap_minor (integer strings in the mandate's own unit: minor for a v1 mandate, currency-native for unit_version: 2) and currency, beside payable_status and the message "<amount> > <cap>".
502objectThe dispatch did not answer cleanly. psp_dispatch_uncertain / payable_dispatch_unknown: the outcome is unknown, the payable is APPROVED with payment.state = uncertain and held for reconciliation — do not resubmit. psp_dispatch_failed: the provider refused and nothing moved; the payable is READY. payable_cycle_threw: the cycle failed before any provider debit; nothing moved, the payable is READY, retry.
503objectvault_unavailable: the key that protects supplier documents could not be read. policy_engine_error: the policy engine could not evaluate; refused, nothing dispatched. payable_sandbox_fixture_unavailable: test project, the sandbox settlement consult did not answer; nothing dispatched, retry.

Response 200

FieldTypeRequiredDescription
attributionobjectyes—
carriersobjectno—
created_atstringyesISO 8601 instant the payable was created.
doc_type"invoice" | "danfe_nfe" | "boleto" | "unknown"yes—
document_hashstringno—
due_dateobjectno—
extractionobjectno—
invoice_numberobjectno—
issue_dateobjectno—
itemsarray of objectno—
payable_idstringyes—
paymentobject,nullyesWhat paying it did (ent#1587); null until a payment was attempted.
rail_quoteobjectno—
receiptobject,nullyesThe sealed Control Record receipt; null when no consumer secret could sign one.
recipientobjectno—
reviewobjectyes—
schema_version"payable.v1"yes—
serieobjectno—
status"RECEIVED" | "EXTRACTING" | "EXTRACTED" | "VALIDATING" | "VALIDATED" | "NEEDS_REVIEW" | "READY" | "APPROVED" | "EXECUTED" | "REJECTED" | "FAILED_EXTRACTION" | "EXPIRED"yes—
supplierobjectyes—
totalsobjectyes—
updated_atstringyesISO 8601 instant of the last change to the payable.
validationobjectyes—
Example request
curl -X POST https://api.codespar.dev/v1/payables/{payableId}/pay \
  -H "Authorization: Bearer $CODESPAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
       "mandate_id": "mandate_0000000000000000"
     }'
POST /v1/payables/{payableId}/pay HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
Content-Type: application/json

{
  "mandate_id": "mandate_0000000000000000"
}
import os
import requests

res = requests.post(
    "https://api.codespar.dev/v1/payables/{payableId}/pay",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
    json={
      "mandate_id": "mandate_0000000000000000"
    },
)
res.raise_for_status()
data = res.json()
const res = await fetch("https://api.codespar.dev/v1/payables/{payableId}/pay", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "mandate_id": "mandate_0000000000000000"
  }),
});

const data = await res.json();
const r = await cs.api.response("post", "/v1/payables/{payableId}/pay", {
  path: {
    payableId: "payable_0000000000000000"
  },
  body: {
    mandate_id: "mandate_0000000000000000"
  }
});
// r.status is one of the documented statuses (200, 403, 422),
// each with its own body shape in r.data; nothing here throws on 403.
if (r.ok) {
  console.log(r.data);
}
Example response 200
application/json
{
  "schema_version": "payable.v1",
  "doc_type": "invoice",
  "attribution": {
    "channel": "upload",
    "credential_id": "credential_0000000000000000",
    "connection_id": "conn_0000000000000000",
    "actor": "string"
  },
  "document_hash": "string",
  "supplier": {
    "document": {
      "value": "string",
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "name": {
      "value": "string",
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    }
  },
  "recipient": {
    "document": {
      "value": "string",
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "name": {
      "value": "string",
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    }
  },
  "invoice_number": {
    "value": "string",
    "from": "parser_local",
    "evidence": {
      "page": 0,
      "excerpt": "string",
      "verification": "verified"
    },
    "confidence": 0
  },
  "serie": {
    "value": "string",
    "from": "parser_local",
    "evidence": {
      "page": 0,
      "excerpt": "string",
      "verification": "verified"
    },
    "confidence": 0
  },
  "issue_date": {
    "value": "2026-01-15",
    "from": "parser_local",
    "evidence": {
      "page": 0,
      "excerpt": "string",
      "verification": "verified"
    },
    "confidence": 0
  },
  "due_date": {
    "value": "2026-01-15",
    "from": "parser_local",
    "evidence": {
      "page": 0,
      "excerpt": "string",
      "verification": "verified"
    },
    "confidence": 0
  },
  "items": [
    {
      "description": {
        "value": "string",
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      },
      "quantity": {
        "value": 1000,
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      },
      "unit_amount_minor": {
        "value": 1000,
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      },
      "total_amount_minor": {
        "value": 1000,
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      }
    }
  ],
  "totals": {
    "subtotal_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "discount_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "tax_relief_deducted_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "freight_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "insurance_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "other_charges_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "taxes_added_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "taxes_included_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "grand_total_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "currency": {
      "value": "string",
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    }
  },
  "carriers": {
    "boleto": "string",
    "br_code": "string",
    "nfe": "string"
  },
  "extraction": {
    "model": "string",
    "model_version": "string",
    "input_tokens": 0,
    "output_tokens": 0,
    "reasoning_tokens": 0
  },
  "payable_id": "payable_0000000000000000",
  "status": "RECEIVED",
  "rail_quote": {
    "beneficiary": {
      "value": "string",
      "from": "trilho"
    },
    "beneficiary_document_masked": {
      "value": "string",
      "from": "trilho"
    },
    "final_beneficiary": {
      "value": "string",
      "from": "trilho"
    },
    "final_beneficiary_document_masked": {
      "value": "string",
      "from": "trilho"
    },
    "amount_minor": {
      "value": 1000,
      "from": "trilho"
    },
    "discount_minor": {
      "value": 1000,
      "from": "trilho"
    },
    "interest_minor": {
      "value": 1000,
      "from": "trilho"
    },
    "fine_minor": {
      "value": 1000,
      "from": "trilho"
    },
    "due_date": {
      "value": "2026-01-15",
      "from": "trilho"
    },
    "quoted_at": "2026-01-15T12:00:00.000Z"
  },
  "validation": {
    "carrier_check_digits": {
      "outcome": "ok",
      "detail": "string"
    },
    "document_check_digits": {
      "outcome": "ok",
      "detail": "string"
    },
    "nfe_key_consistency": {
      "outcome": "ok",
      "detail": "string"
    },
    "arithmetic": {
      "outcome": "ok",
      "detail": "string"
    },
    "duplicate_document": {
      "outcome": "ok",
      "detail": "string"
    },
    "amount_matches_rail": {
      "outcome": "ok",
      "detail": "string"
    },
    "beneficiary_document_matches_supplier": {
      "outcome": "ok",
      "detail": "string"
    },
    "due_date_state": {
      "outcome": "ok",
      "detail": "string"
    },
    "pix_key_owner_matches_supplier": {
      "outcome": "ok",
      "detail": "string"
    },
    "supplier_registry": {
      "outcome": "ok",
      "detail": "string"
    },
    "bank_details_changed": {
      "outcome": "ok",
      "detail": "string"
    }
  },
  "review": {
    "validation_status": "REJECTED",
    "review_by": [
      {
        "code": "string",
        "cause": "check_failed"
      }
    ],
    "rejected_by": [
      "string"
    ],
    "confirmed_by": [
      "string"
    ],
    "awaiting": [
      "string"
    ],
    "not_covered": [
      "string"
    ],
    "review_triggers": [
      "string"
    ],
    "resolutions": [
      {
        "code": "string",
        "resolution": "human_correction",
        "at": "string",
        "by": "string",
        "by_source": "verified_user_token",
        "fields": [
          "string"
        ],
        "confirmed_beneficiary_document": "string"
      }
    ]
  },
  "payment": {
    "state": "awaiting_approval",
    "approval_id": "approval_0000000000000000",
    "settled_against": "document_line",
    "document_amount_minor": 1000,
    "original_document_settled": false,
    "settlement_note": "string",
    "mandate_id": "mandate_0000000000000000",
    "attempt_id": "attempt_0000000000000000",
    "tries": 0,
    "amount_minor": 1000,
    "quote_hash": "string",
    "claimed_at": "string",
    "key_id": "key_0000000000000000",
    "via": "string",
    "executed_at": "string",
    "receipt_id": "receipt_0000000000000000",
    "transaction_id": "transaction_0000000000000000",
    "end_to_end_id": "endtoend_0000000000000000",
    "money_moved": true,
    "uncertain_code": "string",
    "hold_entry_id": "holdentry_0000000000000000",
    "last_refusal": {
      "code": "string",
      "attempt_id": "attempt_0000000000000000",
      "at": "string"
    }
  },
  "created_at": "string",
  "updated_at": "string",
  "receipt": {
    "id": "obj_0000000000000000",
    "state": "paid",
    "chain": "string",
    "exceptions": [
      {
        "code": "string",
        "detail": "string",
        "at": "string"
      }
    ]
  }
}

POST /v1/payables/{payableId}/review

POSThttps://api.codespar.dev/v1/payables/{payableId}/review

Complete the review of a payable

A person records what they decided about a payable that is NEEDS_REVIEW, and the payable comes out READY or REJECTED. There is no third outcome and no partial review: a call that leaves anything blocking is refused and writes nothing, so resolve every item of review in one call.

Who may review. A verified human. Forward the reviewer's Clerk session token in x-codespar-user-token on a service-auth request; that user must be an admin of the organization. Any other credential, a project API key or an OAuth token, is refused (bearer_token_cannot_decide) even with a user token: a key or token proves which service is calling, never which person confirmed, and the audit event names the person.

The ordinary review is an empty one. resolutions: [] releases a payable that no check failed and that only a review trigger holds — typically new_supplier, a supplier this project has never paid. The person is still recorded as the author.

One resolution per failed check. Each names a code that is currently needs_review and one of the resolutions that code admits: the two rejecting codes (carrier_check_digits, duplicate_document) admit none, the three payee codes (beneficiary_document_matches_supplier, pix_key_owner_matches_supplier, bank_details_changed) admit reject or confirm_rail_beneficiary, and every other code admits human_correction or reject. A refused resolution lists what its code admits in details.allowed. A check that produced no answer (indisponivel) has no finding to resolve and is refused (payable_check_did_not_run) — a check that never ran is not a check that passed.

  • human_correction carries corrections, each naming a field and its corrected value. The value is stored with provenance pessoa; evidence and confidence of the previous value are dropped. The correction is then RE-CHECKED: the payable is validated again, the rail is consulted again, and the resolution holds only if the check now passes. It is refused otherwise (payable_correction_did_not_resolve).
  • confirm_rail_beneficiary is the only resolution for a payee that differs from the rail's, because editing the extracted field would turn a forged beneficiary into corrected data. It carries confirmed_beneficiary_document: the rail's beneficiary, masked. If it is absent or is not who the rail names now, the call is refused with payable_rail_beneficiary_unconfirmed and details.rail_beneficiary_document_masked tells you who that is — send it back verbatim to confirm.
  • reject ends the payable, and is sent alone. It is a decision about the payable, not the resolution of a check, so it is accepted whatever state the checks are in — a check that produced no answer included; the code it names only has to be one that admits reject.

What a correction may touch. issue_date, due_date, recipient.document, recipient.name and the totals.* fields, and only when the payable already carries them. Everything that is a duplicity axis (document_hash, the carriers, supplier.document, invoice_number, serie) is outside, because it is a canonical column the duplicate check runs on, and supplier.* is outside for the payee reason above. A misread carrier or supplier document is re-entered as a new payable, not patched.

Scope: payables:write.

Path parameters

NameTypeRequiredDescription
payableIdstringyespbl_ followed by 16 characters.

Request body

FieldTypeRequiredDescription
resolutionsarray of objectno—

Responses

StatusBodyDescription
200objectOK
400objectThe body did not match the review schema.
403objectThe reviewer was not established. bearer_token_cannot_decide: the credential is not service auth (a project API key or an OAuth token), which cannot review. user_token_required: no token was forwarded. user_token_invalid: the token did not verify — mint a fresh one. user_token_identity_mismatch: x-codespar-user names somebody else. insufficient_role: that user is not an admin of the organization.
404objectNo payable with this id in this project.
409objectNothing was written. payable_not_reviewable: the payable is not NEEDS_REVIEW. payable_correction_did_not_resolve: the corrections were applied and details.code still does not pass. payable_rail_beneficiary_unconfirmed: the confirmation named nobody, or not who the rail names now; details.rail_beneficiary_document_masked names who. payable_review_incomplete: something still blocks the payable; details.review says what. payable_review_conflict: the payable changed while this review was being decided.
422objectThe review itself is malformed. payable_check_not_in_review: that check did not fail. payable_check_did_not_run: that check produced no answer. payable_resolution_not_allowed: that check does not admit that resolution; details.allowed lists what it does. payable_resolution_repeated: one code, one resolution. payable_resolution_conflict: a rejection resolves nothing else. payable_field_not_correctable: that field is not corrected in a review, or the payable does not carry it. payable_correction_invalid: the corrected payable no longer matches the schema.
503objectvault_unavailable: the key that protects supplier documents could not be read. user_token_verification_unconfigured: this deployment cannot verify a reviewer's token. Nothing was written either way.

Response 200

FieldTypeRequiredDescription
attributionobjectyes—
carriersobjectno—
created_atstringyesISO 8601 instant the payable was created.
doc_type"invoice" | "danfe_nfe" | "boleto" | "unknown"yes—
document_hashstringno—
due_dateobjectno—
extractionobjectno—
invoice_numberobjectno—
issue_dateobjectno—
itemsarray of objectno—
payable_idstringyes—
paymentobject,nullyesWhat paying it did (ent#1587); null until a payment was attempted.
rail_quoteobjectno—
recipientobjectno—
reviewobjectyes—
schema_version"payable.v1"yes—
serieobjectno—
status"RECEIVED" | "EXTRACTING" | "EXTRACTED" | "VALIDATING" | "VALIDATED" | "NEEDS_REVIEW" | "READY" | "APPROVED" | "EXECUTED" | "REJECTED" | "FAILED_EXTRACTION" | "EXPIRED"yes—
supplierobjectyes—
totalsobjectyes—
updated_atstringyesISO 8601 instant of the last change to the payable.
validationobjectyes—
Example request
curl -X POST https://api.codespar.dev/v1/payables/{payableId}/review \
  -H "Authorization: Bearer $CODESPAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
       "resolutions": []
     }'
POST /v1/payables/{payableId}/review HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
Content-Type: application/json

{
  "resolutions": []
}
import os
import requests

res = requests.post(
    "https://api.codespar.dev/v1/payables/{payableId}/review",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
    json={
      "resolutions": []
    },
)
res.raise_for_status()
data = res.json()
const res = await fetch("https://api.codespar.dev/v1/payables/{payableId}/review", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "resolutions": []
  }),
});

const data = await res.json();
const r = await cs.api.response("post", "/v1/payables/{payableId}/review", {
  path: {
    payableId: "payable_0000000000000000"
  },
  body: {
    resolutions: []
  }
});
// r.status is one of the documented statuses (200, 403, 422),
// each with its own body shape in r.data; nothing here throws on 403.
if (r.ok) {
  console.log(r.data);
}
Example response 200
application/json
{
  "schema_version": "payable.v1",
  "doc_type": "invoice",
  "attribution": {
    "channel": "upload",
    "credential_id": "credential_0000000000000000",
    "connection_id": "conn_0000000000000000",
    "actor": "string"
  },
  "document_hash": "string",
  "supplier": {
    "document": {
      "value": "string",
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "name": {
      "value": "string",
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    }
  },
  "recipient": {
    "document": {
      "value": "string",
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "name": {
      "value": "string",
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    }
  },
  "invoice_number": {
    "value": "string",
    "from": "parser_local",
    "evidence": {
      "page": 0,
      "excerpt": "string",
      "verification": "verified"
    },
    "confidence": 0
  },
  "serie": {
    "value": "string",
    "from": "parser_local",
    "evidence": {
      "page": 0,
      "excerpt": "string",
      "verification": "verified"
    },
    "confidence": 0
  },
  "issue_date": {
    "value": "2026-01-15",
    "from": "parser_local",
    "evidence": {
      "page": 0,
      "excerpt": "string",
      "verification": "verified"
    },
    "confidence": 0
  },
  "due_date": {
    "value": "2026-01-15",
    "from": "parser_local",
    "evidence": {
      "page": 0,
      "excerpt": "string",
      "verification": "verified"
    },
    "confidence": 0
  },
  "items": [
    {
      "description": {
        "value": "string",
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      },
      "quantity": {
        "value": 1000,
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      },
      "unit_amount_minor": {
        "value": 1000,
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      },
      "total_amount_minor": {
        "value": 1000,
        "from": "parser_local",
        "evidence": {
          "page": 0,
          "excerpt": "string",
          "verification": "verified"
        },
        "confidence": 0
      }
    }
  ],
  "totals": {
    "subtotal_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "discount_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "tax_relief_deducted_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "freight_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "insurance_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "other_charges_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "taxes_added_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "taxes_included_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "grand_total_minor": {
      "value": 1000,
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    },
    "currency": {
      "value": "string",
      "from": "parser_local",
      "evidence": {
        "page": 0,
        "excerpt": "string",
        "verification": "verified"
      },
      "confidence": 0
    }
  },
  "carriers": {
    "boleto": "string",
    "br_code": "string",
    "nfe": "string"
  },
  "extraction": {
    "model": "string",
    "model_version": "string",
    "input_tokens": 0,
    "output_tokens": 0,
    "reasoning_tokens": 0
  },
  "payable_id": "payable_0000000000000000",
  "status": "RECEIVED",
  "rail_quote": {
    "beneficiary": {
      "value": "string",
      "from": "trilho"
    },
    "beneficiary_document_masked": {
      "value": "string",
      "from": "trilho"
    },
    "final_beneficiary": {
      "value": "string",
      "from": "trilho"
    },
    "final_beneficiary_document_masked": {
      "value": "string",
      "from": "trilho"
    },
    "amount_minor": {
      "value": 1000,
      "from": "trilho"
    },
    "discount_minor": {
      "value": 1000,
      "from": "trilho"
    },
    "interest_minor": {
      "value": 1000,
      "from": "trilho"
    },
    "fine_minor": {
      "value": 1000,
      "from": "trilho"
    },
    "due_date": {
      "value": "2026-01-15",
      "from": "trilho"
    },
    "quoted_at": "2026-01-15T12:00:00.000Z"
  },
  "validation": {
    "carrier_check_digits": {
      "outcome": "ok",
      "detail": "string"
    },
    "document_check_digits": {
      "outcome": "ok",
      "detail": "string"
    },
    "nfe_key_consistency": {
      "outcome": "ok",
      "detail": "string"
    },
    "arithmetic": {
      "outcome": "ok",
      "detail": "string"
    },
    "duplicate_document": {
      "outcome": "ok",
      "detail": "string"
    },
    "amount_matches_rail": {
      "outcome": "ok",
      "detail": "string"
    },
    "beneficiary_document_matches_supplier": {
      "outcome": "ok",
      "detail": "string"
    },
    "due_date_state": {
      "outcome": "ok",
      "detail": "string"
    },
    "pix_key_owner_matches_supplier": {
      "outcome": "ok",
      "detail": "string"
    },
    "supplier_registry": {
      "outcome": "ok",
      "detail": "string"
    },
    "bank_details_changed": {
      "outcome": "ok",
      "detail": "string"
    }
  },
  "review": {
    "validation_status": "REJECTED",
    "review_by": [
      {
        "code": "string",
        "cause": "check_failed"
      }
    ],
    "rejected_by": [
      "string"
    ],
    "confirmed_by": [
      "string"
    ],
    "awaiting": [
      "string"
    ],
    "not_covered": [
      "string"
    ],
    "review_triggers": [
      "string"
    ],
    "resolutions": [
      {
        "code": "string",
        "resolution": "human_correction",
        "at": "string",
        "by": "string",
        "by_source": "verified_user_token",
        "fields": [
          "string"
        ],
        "confirmed_beneficiary_document": "string"
      }
    ]
  },
  "payment": {
    "state": "awaiting_approval",
    "approval_id": "approval_0000000000000000",
    "settled_against": "document_line",
    "document_amount_minor": 1000,
    "original_document_settled": false,
    "settlement_note": "string",
    "mandate_id": "mandate_0000000000000000",
    "attempt_id": "attempt_0000000000000000",
    "tries": 0,
    "amount_minor": 1000,
    "quote_hash": "string",
    "claimed_at": "string",
    "key_id": "key_0000000000000000",
    "via": "string",
    "executed_at": "string",
    "receipt_id": "receipt_0000000000000000",
    "transaction_id": "transaction_0000000000000000",
    "end_to_end_id": "endtoend_0000000000000000",
    "money_moved": true,
    "uncertain_code": "string",
    "hold_entry_id": "holdentry_0000000000000000",
    "last_refusal": {
      "code": "string",
      "attempt_id": "attempt_0000000000000000",
      "at": "string"
    }
  },
  "created_at": "string",
  "updated_at": "string"
}
Payables | CodeSpar