Quote a plan and close the till
Propose a plan, quote it under a folio and balance the shift's till.
ClinicsOnly in clinic workspaces.
POST /clinic/treatment-plans proposes the treatment, POST /clinic/budgets quotes it under a folio and POST /clinic/cash-sessions opens the shift that takes the money. The patient accepts the presupuesto, and that opens what they owe.
Reads need clinic_money:read and writes clinic_money:write. Opening and closing the till also needs a person: a personal token will do, an API key won't (Personal tokens).
The treatment plan
A plan proposes which services, how many times and in what order.
curl -X POST https://api.vitrinadev.com/api/v1/clinic/treatment-plans \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
"name": "Ortodoncia fija superior e inferior",
"diagnosis": "Apiñamiento anterosuperior moderado",
"items": [
{ "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001", "phase": 1, "quantity": 4 }
]
}'POST /clinic/treatment-plan-items/{id}/status moves each phase to scheduled, performed or cancelled. Moving to scheduled demands naming the appointment it belongs to. GET /clinic/patients/{id}/treatment-progress counts appointments held and pack sessions used: it reports what was done and never what was promised.
The presupuesto and its folio
curl -X POST https://api.vitrinadev.com/api/v1/clinic/budgets \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: presupuesto-mjfuentes-2026-09" \
-d '{
"clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
"treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
"valid_until": "2026-12-31",
"installments": 3,
"items": [
{ "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001", "quantity": 4, "phase": 1 }
]
}'A folio is a legal sequence
A presupuesto takes its folio (display_id, "E-47") when it is created,
and the sequence has no gaps: a draft thrown away leaves its number spent.
Always send an Idempotency-Key; a retry without one spends another folio.
Every line freezes its price as it's added: the price quoted is the price charged, even if the price list moves tomorrow. A draft can be edited, gain and lose lines, or be thrown away. A sent presupuesto is voided, with its reason, and never deleted.
Sending it
POST /clinic/budgets/{id}/send sends it to the patient over their channel, with a link of their own to accept or decline. The answer says how it left (delivered), or that the clinic had no way to reach them.
There are three endings, each with its event: accepted, rejected or voided. Acceptance opens the money: it creates the patient's obligations, instalments included, grouped under an obligation_group_id.
The shift at the till
curl -X POST https://api.vitrinadev.com/api/v1/clinic/cash-sessions \
-H "Authorization: Bearer $VITRINA_PERSONAL_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "location_id": "b1b1b1b1-0000-4000-8000-000000000002", "opening_float_clp": 30000 }'A cash session is one shift at one branch. It opens with a float, takes the day's payments and closes against a count:
{
"data": {
"id": "d1d832c4-0000-4000-8000-000000000001",
"location_id": "b1b1b1b1-0000-4000-8000-000000000002",
"opening_float_clp": 30000,
"expected_clp": 30000,
"counted_clp": 50000,
"difference_clp": 20000,
"difference_reason": "Vuelto de un pago en efectivo",
"status": "closed"
}
}difference_clp is what the drawer held minus what the ledger expected. When it isn't zero the close demands a reason, and POST /clinic/cash-sessions/{id}/reconcile signs off the count.
Where the charges land
The money lives in the platform's charges ledger, the same one every vertical uses. Obligations, payments and allocations are in Charges and payments.
An appointment with a deposit policy opens its own obligation, and an accepted presupuesto opens its own. GET /contacts/{id}/ledger answers whether this patient owes anything.
Events
| Event | When |
|---|---|
clinic_budget.created | The presupuesto was created, and a folio was spent |
clinic_budget.sent | It went to the patient, with their link |
clinic_budget.accepted | Accepted; carries the obligation_group_id it opened |
clinic_budget.rejected · .voided | The patient said no, or the clinic voided it with a reason |
clinic_cash_session.opened · .closed | The shift opened, or closed against a count (with the difference) |
Budget events carry the folio, the state and the totals, never the lines. Whoever may read it reads it with their own credential, at the url the notice carries.