VitrinaAPI

Charge and reconcile payments

Record what somebody owes, what they paid and how the two are matched.

GET /obligations answers what somebody owes and GET /payments what arrived. An allocation says which payment covers which obligation, and the balance, the state and what a person owes today all follow from that. No balance is ever edited by hand.

curl "https://api.vitrinadev.com/api/v1/obligations?contact_id=$CONTACT" \
  -H "Authorization: Bearer $VITRINA_KEY"

Reads need payments:read and writes payments:write. Reversing a payment has a scope of its own, payments:reverse, because undoing money received isn't the same act as receiving it.

The obligation

{
  "data": {
    "id": "b44936d0-0000-4000-8000-000000000004",
    "contact_id": "1a73af9e-0000-4000-8000-000000000001",
    "kind": "service",
    "label": "Ortodoncia — primera cuota",
    "expected_clp": 112000,
    "allocated_clp": 0,
    "outstanding_clp": 112000,
    "state": "pending",
    "due_on": "2026-10-10"
  }
}

kind says what it was born from, and that's where the vertical shows:

vehicle_reservation_deposit is the deposit on a reservation and sale_note what is left of a sale note. The commercial flow opens them: taking a reservation already leaves its obligation.

Several obligations that are one thing to the person, such as the instalments of a plan, share a group_id. GET /obligations/groups/{groupId} reads them together with their balance.

An obligation ends three ways. Paid: the allocations cover it. Waived (/waive, with a reason): the organisation decides not to charge it. Cancelled (/cancel, with a reason and with what happened to anything already paid: aplicado, devuelto or perdido). Editing the expected amount demands a reason in the body.

The payment

curl -X POST https://api.vitrinadev.com/api/v1/payments \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: pago-transferencia-884213" \
  -d '{
    "contact_id": "1a73af9e-0000-4000-8000-000000000001",
    "instrument": "transferencia",
    "amount_clp": 50000,
    "paid_on": "2026-09-24",
    "bank": "Banco de Chile",
    "document_number": "884213",
    "allocations": [
      { "obligation_id": "b44936d0-0000-4000-8000-000000000004", "amount_clp": 50000 }
    ]
  }'

instrument is how it arrived: cash, transfer, card, cheque, vale vista. paid_on is the day the person paid, which isn't always today. allocations applies it there and then. If you don't know yet what it covers, the payment stays unapplied and shows up in GET /payments?unapplied=true, which is a legitimate state.

A payment that arrived without anyone knowing whose it is gets recorded anyway, with no contact_id, and POST /payments/{id}/assign-payer gives it an owner later. POST /payments/{id}/allocations allocates afterwards and DELETE /payments/{id}/allocations/{allocationId} releases a wrong allocation, always with a reason.

A payment is never deleted. POST /payments/{id}/reverse reverses it with its reason and leaves the reversal as one more row.

One person's statement

curl https://api.vitrinadev.com/api/v1/contacts/$CONTACT/ledger \
  -H "Authorization: Bearer $VITRINA_KEY"

It answers in one call what they owe, what they paid and what is still unapplied. It's the read worth putting on a screen, rather than adding obligations and payments yourself and arriving at a different number.

A charge names a person, and what a connected app sees of them is in Personal data.

On this page