VitrinaAPI

Clinic budgets

Budgets with a folio, their lines with the price frozen, and the treatment plans that order what is to be done.

Beta
This may change at any time, with a changelog entry and a notice to recent callers — see versioning.

Download the full API pública projection: openapi.json.

Quote a plan and close the till explains this resource in prose, with runnable examples.

MethodPathWhat it does
DELETE/clinic/budget-items/{id}Remove a line from a DRAFT
PATCH/clinic/budget-items/{id}Edit a line of a DRAFT
GET/clinic/budgetsList presupuestos (the conversion funnel)
POST/clinic/budgetsCreate a presupuesto (draft)
DELETE/clinic/budgets/{id}Throw away a DRAFT
GET/clinic/budgets/{id}One presupuesto, by UUID or folio
PATCH/clinic/budgets/{id}Edit a DRAFT presupuesto
POST/clinic/budgets/{id}/acceptAccept it (STAFF path)
POST/clinic/budgets/{id}/accept-and-collectCollect a payment and accept the lines it pays (the counter)
POST/clinic/budgets/{id}/itemsAdd a line, freezing its price
GET/clinic/budgets/{id}/lineageEvery version of one proposal
POST/clinic/budgets/{id}/rejectRecord the patient declining
GET/clinic/budgets/{id}/renderThe presupuesto as a PDF
POST/clinic/budgets/{id}/sendSend it to the patient
POST/clinic/budgets/{id}/supersedeWrite a new version (Nueva versión)
POST/clinic/budgets/{id}/voidVoid it — never DELETE
POST/clinic/budgets/from-mirror-planCreate a presupuesto (draft) from a mirrored Dentalink plan
GET/clinic/budgets/mirror-plansA patient's mirrored Dentalink plans, for «Crear desde Dentalink»
GET/clinic/budgets/summaryThe COMERCIAL badge and funnel, counted by lineage
GET/clinic/imported-balances«Saldos pendientes de Dentalink»: what patients still owe on imported presupuestos
GET/clinic/imported-balances/{budget_id}One imported presupuesto’s Dentalink balance and default payer
POST/clinic/imported-balances/{budget_id}/collectCollect a payment (or apply credit) against a Dentalink balance
GET/clinic/patients/{id}/treatment-progressA patient's treatment progress
POST/clinic/treatment-plan-items/{id}/statusMove one phase item along
GET/clinic/treatment-plansList treatment plans, with phase progress
POST/clinic/treatment-plansCreate a treatment plan by hand
DELETE/clinic/treatment-plans/{id}Delete a plan
GET/clinic/treatment-plans/{id}One plan with its phases and progress
PATCH/clinic/treatment-plans/{id}Edit a plan
POST/clinic/treatment-plans/{id}/itemsAdd prestaciones to an open plan

DELETE /clinic/budget-items/{id}

Remove a line from a DRAFT

Remove a line from a DRAFT

Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.

ParameterInTypeRequiredConstraints
idpathuuidyes
curl -X DELETE https://api.vitrinadev.com/api/v1/clinic/budget-items/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

Answers: 204 · 400 · 401 · 403 · 404 · 409 · 429

PATCH /clinic/budget-items/{id}

Edit a line of a DRAFT

Edit a line of a DRAFT

Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.

ParameterInTypeRequiredConstraints
idpathuuidyes

Body

FieldTypeRequiredConstraints
labelstring—mín. 1, máx. 200
quantityinteger—≥ 1, ≤ 999
unit_price_clpinteger—≥ 0, ≤ 9999999999
discount_clpinteger—≥ 0, ≤ 9999999999
tax_treatmentafecto \exento \no_gravado
phaseinteger—≥ 1, ≤ 99
notestring | null—máx. 500
sort_orderinteger—≥ 0, ≤ 999
curl -X PATCH https://api.vitrinadev.com/api/v1/clinic/budget-items/<id> \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "quantity": 2
  }'

Example response (200)

{
  "data": {
    "id": "3a7d9767-0000-4000-8000-000000000006",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "budget_id": "81e4a552-0000-4000-8000-000000000001",
    "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
    "pack_id": null,
    "label": "Limpieza dental",
    "quantity": 2,
    "unit_price_clp": 35000,
    "discount_clp": 0,
    "total_clp": 70000,
    "price_explain": [
      {
        "kind": null,
        "label": "Arancel particular 2026",
        "order": 1,
        "value": null,
        "source": "price_list",
        "rule_id": null,
        "modifier": null,
        "delta_clp": 0,
        "amount_after_clp": 35000,
        "amount_before_clp": null
      }
    ],
    "tax_treatment": "afecto",
    "phase": 3,
    "note": "Control final",
    "sort_order": 2,
    "created_at": "2026-09-22T22:21:15.470Z",
    "updated_at": "2026-09-22T22:21:15.481Z"
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

GET /clinic/budgets

List presupuestos (the conversion funnel)

Quotes newest first, with their lines, the patient and the professional. ?live=true is the working set (borrador + enviado); ?status= picks one column. A voided presupuesto stays listable — a correlative series with a hole in it cannot be audited. Every row carries its LINEAGE (lineage_id, version, lineage_size, reissued_from_id, reissued_by_id) and the patient's «visto» (first_viewed_at, last_viewed_at). ?group_by=lineage returns ONE row per lineage — its latest version that is neither a draft nor voided (a draft only when the lineage has nothing else) — and the other filters then read that version; ?lineage_id= lists one lineage's versions.

Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.

ParameterInTypeRequiredConstraints
statusquerydraft \sent \accepted \
livequerytrue \falseno
patient_idqueryuuidno
contact_idqueryuuidno
professional_idqueryuuidno
location_idqueryuuidno
treatment_plan_idqueryuuidno
lineage_idqueryuuidno
group_byquerylineageno
pagequeryintegerno≥ 1, por defecto 1
limitqueryintegerno≥ 1, ≤ 200, por defecto 50
curl https://api.vitrinadev.com/api/v1/clinic/budgets \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "items": [
      {
        "id": "81e4a552-0000-4000-8000-000000000001",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "source": "native",
        "external_id": null,
        "display_seq": 1,
        "display_id": "E-1",
        "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
        "contact_id": "1a73af9e-0000-4000-8000-000000000001",
        "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
        "lead_id": null,
        "lineage_id": "81e4a552-0000-4000-8000-000000000001",
        "reissued_from_id": null,
        "conversation_id": null,
        "professional_id": "18342d1b-0000-4000-8000-000000000001",
        "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
        "price_list_id": null,
        "agreement_id": null,
        "legal_entity_id": null,
        "status": "draft",
        "subtotal_clp": 147000,
        "discount_clp": 5000,
        "total_clp": 142000,
        "tax_treatment": "afecto",
        "valid_until": "2026-12-31",
        "installments": 3,
        "notes": "Incluye los cuatro controles del primer semestre",
        "obligation_group_id": null,
        "rendered_document_id": null,
        "created_by_user_id": null,
        "sent_at": null,
        "accepted_at": null,
        "accepted_via": null,
        "rejected_at": null,
        "rejected_reason": null,
        "expired_at": null,
        "voided_at": null,
        "voided_by": null,
        "voided_reason": null,
        "created_at": "2026-09-22T22:21:15.369Z",
        "updated_at": "2026-09-22T22:21:15.412Z",
        "patient_name": "María José Fuentes Lagos",
        "professional_name": "Ana Rojas Vidal",
        "items": [
          {
            "id": "3a7d9767-0000-4000-8000-000000000004",
            "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
            "budget_id": "81e4a552-0000-4000-8000-000000000001",
            "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
            "pack_id": null,
            "label": "Control de ortodoncia",
            "quantity": 4,
            "unit_price_clp": 28000,
            "discount_clp": 0,
            "total_clp": 112000,
            "price_explain": [
              {
                "kind": null,
                "label": "Arancel particular 2026",
                "order": 1,
                "value": null,
                "source": "price_list",
                "rule_id": null,
                "modifier": null,
                "delta_clp": 0,
                "amount_after_clp": 28000,
                "amount_before_clp": null
              }
            ],
            "tax_treatment": "afecto",
            "phase": 1,
            "note": null,
            "sort_order": 0,
            "created_at": "2026-09-22T22:21:15.392Z",
            "updated_at": "2026-09-22T22:21:15.392Z",
            "service_name": "Control de ortodoncia"
          },
          {
            "id": "3a7d9767-0000-4000-8000-000000000005",
            "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
            "budget_id": "81e4a552-0000-4000-8000-000000000001",
            "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
            "pack_id": null,
            "label": "Limpieza dental",
            "quantity": 1,
            "unit_price_clp": 35000,
            "discount_clp": 5000,
            "total_clp": 30000,
            "price_explain": [
              {
                "kind": null,
                "label": "Arancel particular 2026",
                "order": 1,
                "value": null,
                "source": "price_list",
                "rule_id": null,
                "modifier": null,
                "delta_clp": 0,
                "amount_after_clp": 35000,
                "amount_before_clp": null
              }
            ],
            "tax_treatment": "afecto",
            "phase": 2,
            "note": null,
            "sort_order": 1,
            "created_at": "2026-09-22T22:21:15.410Z",
            "updated_at": "2026-09-22T22:21:15.410Z",
            "service_name": "Limpieza dental"
          }
        ],
        "is_live": true,
        "days_left": 100
      }
    ],
    "total": 1,
    "page": 1,
    "limit": 50
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/budgets

Create a presupuesto (draft)

Allocates the E- folio and freezes each line's price through the quote resolver — the arancel, the convenio and the professional surcharge are read ONCE and stored with the explanation that produced them. unit_price_clp on a line is the human override; omit it and the resolver decides. Always lands in draft. With reissued_from_id it is a NEW VERSION of that presupuesto and follows exactly the rules of POST /clinic/budgets/\{id\}/supersede: the draft copies the predecessor's header and lines (accepted lines carried), the header fields given override the copied ones, and items are added after the copied lines.

Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.

Body

FieldTypeRequiredConstraints
clinic_patient_iduuidyes
contact_idstring | null—
professional_idstring | null—
location_idstring | null—
price_list_idstring | null—
agreement_idstring | null—
conversation_idstring | null—
lead_idstring | null—
treatment_plan_idstring | null—
reissued_from_idstring | null—
tax_treatmentafecto \exento \no_gravado
valid_untilstring | null—patrón ^\d{4}-\d{2}-\d{2}$
installmentsinteger | null—≥ 1, ≤ 48
installments_first_duestring | null—patrón ^\d{4}-\d{2}-\d{2}$
notesstring | null—máx. 2000
itemsobject[]—
curl -X POST https://api.vitrinadev.com/api/v1/clinic/budgets \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
    "contact_id": "1a73af9e-0000-4000-8000-000000000001",
    "professional_id": "18342d1b-0000-4000-8000-000000000001",
    "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
    "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
    "valid_until": "2026-12-31",
    "installments": 3,
    "notes": "Incluye los cuatro controles del primer semestre",
    "items": [
      {
        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
        "quantity": 4,
        "phase": 1
      },
      {
        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
        "quantity": 1,
        "phase": 2,
        "discount_clp": 5000
      }
    ]
  }'

Example response (201)

{
  "data": {
    "id": "81e4a552-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "source": "native",
    "external_id": null,
    "display_seq": 1,
    "display_id": "E-1",
    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
    "contact_id": "1a73af9e-0000-4000-8000-000000000001",
    "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
    "lead_id": null,
    "lineage_id": "81e4a552-0000-4000-8000-000000000001",
    "reissued_from_id": null,
    "conversation_id": null,
    "professional_id": "18342d1b-0000-4000-8000-000000000001",
    "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
    "price_list_id": null,
    "agreement_id": null,
    "legal_entity_id": null,
    "status": "draft",
    "subtotal_clp": 147000,
    "discount_clp": 5000,
    "total_clp": 142000,
    "tax_treatment": "afecto",
    "valid_until": "2026-12-31",
    "installments": 3,
    "notes": "Incluye los cuatro controles del primer semestre",
    "obligation_group_id": null,
    "rendered_document_id": null,
    "created_by_user_id": null,
    "sent_at": null,
    "accepted_at": null,
    "accepted_via": null,
    "rejected_at": null,
    "rejected_reason": null,
    "expired_at": null,
    "voided_at": null,
    "voided_by": null,
    "voided_reason": null,
    "created_at": "2026-09-22T22:21:15.369Z",
    "updated_at": "2026-09-22T22:21:15.412Z",
    "patient_name": "María José Fuentes Lagos",
    "professional_name": "Ana Rojas Vidal",
    "items": [
      {
        "id": "3a7d9767-0000-4000-8000-000000000004",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "budget_id": "81e4a552-0000-4000-8000-000000000001",
        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
        "pack_id": null,
        "label": "Control de ortodoncia",
        "quantity": 4,
        "unit_price_clp": 28000,
        "discount_clp": 0,
        "total_clp": 112000,
        "price_explain": [
          {
            "kind": null,
            "label": "Arancel particular 2026",
            "order": 1,
            "value": null,
            "source": "price_list",
            "rule_id": null,
            "modifier": null,
            "delta_clp": 0,
            "amount_after_clp": 28000,
            "amount_before_clp": null
          }
        ],
        "tax_treatment": "afecto",
        "phase": 1,
        "note": null,
        "sort_order": 0,
        "created_at": "2026-09-22T22:21:15.392Z",
        "updated_at": "2026-09-22T22:21:15.392Z",
        "service_name": "Control de ortodoncia"
      },
      {
        "id": "3a7d9767-0000-4000-8000-000000000005",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "budget_id": "81e4a552-0000-4000-8000-000000000001",
        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
        "pack_id": null,
        "label": "Limpieza dental",
        "quantity": 1,
        "unit_price_clp": 35000,
        "discount_clp": 5000,
        "total_clp": 30000,
        "price_explain": [
          {
            "kind": null,
            "label": "Arancel particular 2026",
            "order": 1,
            "value": null,
            "source": "price_list",
            "rule_id": null,
            "modifier": null,
            "delta_clp": 0,
            "amount_after_clp": 35000,
            "amount_before_clp": null
          }
        ],
        "tax_treatment": "afecto",
        "phase": 2,
        "note": null,
        "sort_order": 1,
        "created_at": "2026-09-22T22:21:15.410Z",
        "updated_at": "2026-09-22T22:21:15.410Z",
        "service_name": "Limpieza dental"
      }
    ],
    "is_live": true,
    "days_left": 100
  }
}

Answers: 201 · 400 · 401 · 403 · 404 · 409 · 429

DELETE /clinic/budgets/{id}

Throw away a DRAFT

Only a draft. Anything that has been sent is voided (POST /clinic/budgets/\{id\}/void) and keeps its folio — ADR 0058 §10.

Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.

ParameterInTypeRequiredConstraints
idpathuuidyes
curl -X DELETE https://api.vitrinadev.com/api/v1/clinic/budgets/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

Answers: 204 · 400 · 401 · 403 · 404 · 409 · 429

GET /clinic/budgets/{id}

One presupuesto, by UUID or folio

\{id\} accepts a UUID or the folio (E-47).

Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.

ParameterInTypeRequiredConstraints
idpathanyyes
curl https://api.vitrinadev.com/api/v1/clinic/budgets/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "id": "81e4a552-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "source": "native",
    "external_id": null,
    "display_seq": 1,
    "display_id": "E-1",
    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
    "contact_id": "1a73af9e-0000-4000-8000-000000000001",
    "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
    "lead_id": null,
    "lineage_id": "81e4a552-0000-4000-8000-000000000001",
    "reissued_from_id": null,
    "conversation_id": null,
    "professional_id": "18342d1b-0000-4000-8000-000000000001",
    "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
    "price_list_id": null,
    "agreement_id": null,
    "legal_entity_id": null,
    "status": "draft",
    "subtotal_clp": 147000,
    "discount_clp": 5000,
    "total_clp": 142000,
    "tax_treatment": "afecto",
    "valid_until": "2026-12-31",
    "installments": 3,
    "notes": "Incluye los cuatro controles del primer semestre",
    "obligation_group_id": null,
    "rendered_document_id": null,
    "created_by_user_id": null,
    "sent_at": null,
    "accepted_at": null,
    "accepted_via": null,
    "rejected_at": null,
    "rejected_reason": null,
    "expired_at": null,
    "voided_at": null,
    "voided_by": null,
    "voided_reason": null,
    "created_at": "2026-09-22T22:21:15.369Z",
    "updated_at": "2026-09-22T22:21:15.412Z",
    "patient_name": "María José Fuentes Lagos",
    "professional_name": "Ana Rojas Vidal",
    "items": [
      {
        "id": "3a7d9767-0000-4000-8000-000000000004",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "budget_id": "81e4a552-0000-4000-8000-000000000001",
        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
        "pack_id": null,
        "label": "Control de ortodoncia",
        "quantity": 4,
        "unit_price_clp": 28000,
        "discount_clp": 0,
        "total_clp": 112000,
        "price_explain": [
          {
            "kind": null,
            "label": "Arancel particular 2026",
            "order": 1,
            "value": null,
            "source": "price_list",
            "rule_id": null,
            "modifier": null,
            "delta_clp": 0,
            "amount_after_clp": 28000,
            "amount_before_clp": null
          }
        ],
        "tax_treatment": "afecto",
        "phase": 1,
        "note": null,
        "sort_order": 0,
        "created_at": "2026-09-22T22:21:15.392Z",
        "updated_at": "2026-09-22T22:21:15.392Z",
        "service_name": "Control de ortodoncia"
      },
      {
        "id": "3a7d9767-0000-4000-8000-000000000005",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "budget_id": "81e4a552-0000-4000-8000-000000000001",
        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
        "pack_id": null,
        "label": "Limpieza dental",
        "quantity": 1,
        "unit_price_clp": 35000,
        "discount_clp": 5000,
        "total_clp": 30000,
        "price_explain": [
          {
            "kind": null,
            "label": "Arancel particular 2026",
            "order": 1,
            "value": null,
            "source": "price_list",
            "rule_id": null,
            "modifier": null,
            "delta_clp": 0,
            "amount_after_clp": 35000,
            "amount_before_clp": null
          }
        ],
        "tax_treatment": "afecto",
        "phase": 2,
        "note": null,
        "sort_order": 1,
        "created_at": "2026-09-22T22:21:15.410Z",
        "updated_at": "2026-09-22T22:21:15.410Z",
        "service_name": "Limpieza dental"
      }
    ],
    "is_live": true,
    "days_left": 100
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

PATCH /clinic/budgets/{id}

Edit a DRAFT presupuesto

Header fields only, and only while the quote is a draft — a sent one answers 409 rather than silently ignoring the patch. Moving the sucursal moves the razón social with it.

Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.

ParameterInTypeRequiredConstraints
idpathuuidyes

Body

FieldTypeRequiredConstraints
contact_idstring | null—
professional_idstring | null—
location_idstring | null—
price_list_idstring | null—
agreement_idstring | null—
conversation_idstring | null—
lead_idstring | null—
treatment_plan_idstring | null—
tax_treatmentafecto \exento \no_gravado
valid_untilstring | null—patrón ^\d{4}-\d{2}-\d{2}$
installmentsinteger | null—≥ 1, ≤ 48
installments_first_duestring | null—patrón ^\d{4}-\d{2}-\d{2}$
notesstring | null—máx. 2000
curl -X PATCH https://api.vitrinadev.com/api/v1/clinic/budgets/<id> \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "notes": "Incluye los cuatro controles y la limpieza inicial"
  }'

Example response (200)

{
  "data": {
    "id": "81e4a552-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "source": "native",
    "external_id": null,
    "display_seq": 1,
    "display_id": "E-1",
    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
    "contact_id": "1a73af9e-0000-4000-8000-000000000001",
    "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
    "lead_id": null,
    "lineage_id": "81e4a552-0000-4000-8000-000000000001",
    "reissued_from_id": null,
    "conversation_id": null,
    "professional_id": "18342d1b-0000-4000-8000-000000000001",
    "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
    "price_list_id": null,
    "agreement_id": null,
    "legal_entity_id": null,
    "status": "draft",
    "subtotal_clp": 147000,
    "discount_clp": 5000,
    "total_clp": 142000,
    "tax_treatment": "afecto",
    "valid_until": "2026-12-31",
    "installments": 3,
    "notes": "Incluye los cuatro controles y la limpieza inicial",
    "obligation_group_id": null,
    "rendered_document_id": null,
    "created_by_user_id": null,
    "sent_at": null,
    "accepted_at": null,
    "accepted_via": null,
    "rejected_at": null,
    "rejected_reason": null,
    "expired_at": null,
    "voided_at": null,
    "voided_by": null,
    "voided_reason": null,
    "created_at": "2026-09-22T22:21:15.369Z",
    "updated_at": "2026-09-22T22:21:15.445Z",
    "patient_name": "María José Fuentes Lagos",
    "professional_name": "Ana Rojas Vidal",
    "items": [
      {
        "id": "3a7d9767-0000-4000-8000-000000000004",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "budget_id": "81e4a552-0000-4000-8000-000000000001",
        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
        "pack_id": null,
        "label": "Control de ortodoncia",
        "quantity": 4,
        "unit_price_clp": 28000,
        "discount_clp": 0,
        "total_clp": 112000,
        "price_explain": [
          {
            "kind": null,
            "label": "Arancel particular 2026",
            "order": 1,
            "value": null,
            "source": "price_list",
            "rule_id": null,
            "modifier": null,
            "delta_clp": 0,
            "amount_after_clp": 28000,
            "amount_before_clp": null
          }
        ],
        "tax_treatment": "afecto",
        "phase": 1,
        "note": null,
        "sort_order": 0,
        "created_at": "2026-09-22T22:21:15.392Z",
        "updated_at": "2026-09-22T22:21:15.392Z",
        "service_name": "Control de ortodoncia"
      },
      {
        "id": "3a7d9767-0000-4000-8000-000000000005",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "budget_id": "81e4a552-0000-4000-8000-000000000001",
        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
        "pack_id": null,
        "label": "Limpieza dental",
        "quantity": 1,
        "unit_price_clp": 35000,
        "discount_clp": 5000,
        "total_clp": 30000,
        "price_explain": [
          {
            "kind": null,
            "label": "Arancel particular 2026",
            "order": 1,
            "value": null,
            "source": "price_list",
            "rule_id": null,
            "modifier": null,
            "delta_clp": 0,
            "amount_after_clp": 35000,
            "amount_before_clp": null
          }
        ],
        "tax_treatment": "afecto",
        "phase": 2,
        "note": null,
        "sort_order": 1,
        "created_at": "2026-09-22T22:21:15.410Z",
        "updated_at": "2026-09-22T22:21:15.410Z",
        "service_name": "Limpieza dental"
      }
    ],
    "is_live": true,
    "days_left": 100
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/budgets/{id}/accept

Accept it (STAFF path)

Stamps accepted_via: "staff" — the route decides, never the body. Accepts every line still presented, or (#2448) only item_ids: status: "accepted" means AT LEAST ONE line accepted, and the lines not yet accepted stay presented — the patient link keeps working for them, staff can accept more later, and valid_until still applies to them. Opens an obligation_group of kind presupuesto (one per lineage) plus one obligation per NEWLY accepted line in the payments ledger, creates or grows the treatment plan, and moves the lead to «Aceptado». Idempotent: a call with nothing left to accept returns the same objects with accepted_now: false; accepted_item_ids names the lines this call accepted. A presupuesto imported from Dentalink is never accepted: 409 imported_balance_pending while it carries a balance (collected via /clinic/imported-balances), else imported_budget_readonly (write a new native presupuesto to continue the treatment). An AGENT can reach neither this route nor the public one.

Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.

ParameterInTypeRequiredConstraints
idpathuuidyes

Body

FieldTypeRequiredConstraints
item_idsuuid[]—
curl -X POST https://api.vitrinadev.com/api/v1/clinic/budgets/<id>/accept \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'

Example response (200)

{
  "data": {
    "budget": {
      "id": "81e4a552-0000-4000-8000-000000000002",
      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
      "source": "native",
      "external_id": null,
      "display_seq": 2,
      "display_id": "E-2",
      "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
      "contact_id": "1a73af9e-0000-4000-8000-000000000001",
      "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
      "lead_id": "8cb33b27-0000-4000-8000-000000000001",
      "lineage_id": "81e4a552-0000-4000-8000-000000000002",
      "reissued_from_id": null,
      "conversation_id": null,
      "professional_id": "18342d1b-0000-4000-8000-000000000001",
      "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
      "price_list_id": null,
      "agreement_id": null,
      "legal_entity_id": null,
      "status": "accepted",
      "subtotal_clp": 147000,
      "discount_clp": 5000,
      "total_clp": 142000,
      "tax_treatment": "afecto",
      "valid_until": "2026-12-31",
      "installments": 3,
      "notes": "Incluye los cuatro controles del primer semestre",
      "obligation_group_id": "a067dce6-0000-4000-8000-000000000002",
      "rendered_document_id": null,
      "created_by_user_id": null,
      "sent_at": "2026-09-22T22:21:15.544Z",
      "accepted_at": "2026-09-22T22:21:16.040Z",
      "accepted_via": "staff",
      "rejected_at": null,
      "rejected_reason": null,
      "expired_at": null,
      "voided_at": null,
      "voided_by": null,
      "voided_reason": null,
      "created_at": "2026-09-22T22:21:15.502Z",
      "updated_at": "2026-09-22T22:21:16.048Z",
      "patient_name": "María José Fuentes Lagos",
      "professional_name": "Ana Rojas Vidal",
      "items": [
        {
          "id": "3a7d9767-0000-4000-8000-000000000007",
          "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
          "budget_id": "81e4a552-0000-4000-8000-000000000002",
          "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
          "pack_id": null,
          "label": "Control de ortodoncia",
          "quantity": 4,
          "unit_price_clp": 28000,
          "discount_clp": 0,
          "total_clp": 112000,
          "price_explain": [
            {
              "kind": null,
              "label": "Arancel particular 2026",
              "order": 1,
              "value": null,
              "source": "price_list",
              "rule_id": null,
              "modifier": null,
              "delta_clp": 0,
              "amount_after_clp": 28000,
              "amount_before_clp": null
            }
          ],
          "tax_treatment": "afecto",
          "phase": 1,
          "note": null,
          "sort_order": 0,
          "created_at": "2026-09-22T22:21:15.515Z",
          "updated_at": "2026-09-22T22:21:15.515Z",
          "service_name": "Control de ortodoncia"
        },
        {
          "id": "3a7d9767-0000-4000-8000-000000000008",
          "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
          "budget_id": "81e4a552-0000-4000-8000-000000000002",
          "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
          "pack_id": null,
          "label": "Limpieza dental",
          "quantity": 1,
          "unit_price_clp": 35000,
          "discount_clp": 5000,
          "total_clp": 30000,
          "price_explain": [
            {
              "kind": null,
              "label": "Arancel particular 2026",
              "order": 1,
              "value": null,
              "source": "price_list",
              "rule_id": null,
              "modifier": null,
              "delta_clp": 0,
              "amount_after_clp": 35000,
              "amount_before_clp": null
            }
          ],
          "tax_treatment": "afecto",
          "phase": 2,
          "note": null,
          "sort_order": 1,
          "created_at": "2026-09-22T22:21:15.530Z",
          "updated_at": "2026-09-22T22:21:15.530Z",
          "service_name": "Limpieza dental"
        }
      ],
      "is_live": false,
      "days_left": 100
    },
    "plan": {
      "id": "1ccffee2-0000-4000-8000-000000000001",
      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
      "source": "native",
      "external_id": null,
      "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
      "name": "Ortodoncia fija superior e inferior",
      "diagnosis": "Apiñamiento anterosuperior moderado",
      "professional_id": "18342d1b-0000-4000-8000-000000000001",
      "status": "proposed",
      "accepted_at": null,
      "completed_at": null,
      "abandoned_reason": null,
      "created_at": "2026-09-22T22:21:15.213Z",
      "updated_at": "2026-09-22T22:21:15.285Z",
      "patient_name": "María José Fuentes Lagos",
      "professional_name": "Ana Rojas Vidal",
      "items": [
        {
          "id": "3a7d9767-0000-4000-8000-000000000002",
          "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
          "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
          "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
          "phase": 1,
          "quantity": 4,
          "tooth_or_zone": null,
          "status": "scheduled",
          "appointment_id": "d93e8eec-0000-4000-8000-000000000001",
          "budget_item_id": null,
          "sort_order": 0,
          "created_at": "2026-09-22T22:21:15.215Z",
          "updated_at": "2026-09-22T22:21:15.337Z",
          "service_name": "Control de ortodoncia"
        },
        {
          "id": "3a7d9767-0000-4000-8000-000000000003",
          "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
          "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
          "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
          "phase": 2,
          "quantity": 1,
          "tooth_or_zone": null,
          "status": "pending",
          "appointment_id": null,
          "budget_item_id": null,
          "sort_order": 1,
          "created_at": "2026-09-22T22:21:15.215Z",
          "updated_at": "2026-09-22T22:21:15.215Z",
          "service_name": "Limpieza dental"
        }
      ],
      "progress": {
        "pending": 1,
        "scheduled": 4,
        "performed": 0,
        "cancelled": 0,
        "active": 5,
        "percent": 0,
        "phases": [
          {
            "phase": 1,
            "pending": 0,
            "scheduled": 4,
            "performed": 0,
            "cancelled": 0,
            "active": 4,
            "percent": 0
          },
          {
            "phase": 2,
            "pending": 1,
            "scheduled": 0,
            "performed": 0,
            "cancelled": 0,
            "active": 1,
            "percent": 0
          }
        ],
        "current_phase": 1
      }
    },
    "obligation_group_id": "a067dce6-0000-4000-8000-000000000002",
    "obligation_ids": [
      "b44936d0-0000-4000-8000-000000000002",
      "b44936d0-0000-4000-8000-000000000003"
    ],
    "accepted_now": true,
    "accepted_item_ids": [
      "3a7d9767-0000-4000-8000-000000000007",
      "3a7d9767-0000-4000-8000-000000000008"
    ]
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/budgets/{id}/accept-and-collect

Collect a payment and accept the lines it pays (the counter)

Reception's one gesture (#2448): the lines in item_ids become accepted (accepted_via: "staff", stamped with the cashier), their obligations open in the presupuesto group, and the payment is recorded and allocated to exactly those obligations — in ONE transaction, so a refused payment or allocation leaves the lines presented. The payer is the budget's contact_id (409 payer_unknown without one) and the caja its location_id. Money beyond the lines' total stays as the patient's credit; less leaves the last line partly paid. A POS swipe is a tarjeta* instrument with source: "card_terminal". Requires BOTH clinic_money:write and payments:write. 409 nothing_to_accept when the lines are already accepted, expired / not_sent like accept, and imported_balance_pending / imported_budget_readonly for a presupuesto imported from Dentalink (never accepted here). Idempotency-Key is stored on the payment. An AGENT cannot reach it.

Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.

ParameterInTypeRequiredConstraints
idpathuuidyes

Body

FieldTypeRequiredConstraints
item_idsuuid[]yes
paymentobjectyes
curl -X POST https://api.vitrinadev.com/api/v1/clinic/budgets/<id>/accept-and-collect \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "item_ids": [
      "3a7d9767-0000-4000-8000-000000000007"
    ],
    "payment": {
      "instrument": "tarjeta_debito",
      "source": "card_terminal",
      "amount_clp": 112000
    }
  }'

Example response (201)

{
  "data": {
    "budget": {
      "id": "81e4a552-0000-4000-8000-000000000002",
      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
      "source": "native",
      "external_id": null,
      "display_seq": 2,
      "display_id": "E-2",
      "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
      "contact_id": "1a73af9e-0000-4000-8000-000000000001",
      "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
      "lead_id": "8cb33b27-0000-4000-8000-000000000001",
      "lineage_id": "81e4a552-0000-4000-8000-000000000002",
      "reissued_from_id": null,
      "conversation_id": null,
      "professional_id": "18342d1b-0000-4000-8000-000000000001",
      "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
      "price_list_id": null,
      "agreement_id": null,
      "legal_entity_id": null,
      "status": "accepted",
      "subtotal_clp": 147000,
      "discount_clp": 5000,
      "total_clp": 142000,
      "tax_treatment": "afecto",
      "valid_until": "2026-12-31",
      "installments": 3,
      "notes": "Incluye los cuatro controles del primer semestre",
      "obligation_group_id": "a067dce6-0000-4000-8000-000000000002",
      "rendered_document_id": null,
      "created_by_user_id": null,
      "sent_at": "2026-09-22T22:21:15.544Z",
      "accepted_at": "2026-09-22T22:21:16.040Z",
      "accepted_via": "staff",
      "rejected_at": null,
      "rejected_reason": null,
      "expired_at": null,
      "voided_at": null,
      "voided_by": null,
      "voided_reason": null,
      "created_at": "2026-09-22T22:21:15.502Z",
      "updated_at": "2026-09-22T22:21:16.048Z",
      "patient_name": "María José Fuentes Lagos",
      "professional_name": "Ana Rojas Vidal",
      "items": [
        {
          "id": "3a7d9767-0000-4000-8000-000000000007",
          "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
          "budget_id": "81e4a552-0000-4000-8000-000000000002",
          "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
          "pack_id": null,
          "label": "Control de ortodoncia",
          "quantity": 4,
          "unit_price_clp": 28000,
          "discount_clp": 0,
          "total_clp": 112000,
          "price_explain": [
            {
              "kind": null,
              "label": "Arancel particular 2026",
              "order": 1,
              "value": null,
              "source": "price_list",
              "rule_id": null,
              "modifier": null,
              "delta_clp": 0,
              "amount_after_clp": 28000,
              "amount_before_clp": null
            }
          ],
          "tax_treatment": "afecto",
          "phase": 1,
          "note": null,
          "sort_order": 0,
          "created_at": "2026-09-22T22:21:15.515Z",
          "updated_at": "2026-09-22T22:21:15.515Z",
          "service_name": "Control de ortodoncia"
        },
        {
          "id": "3a7d9767-0000-4000-8000-000000000008",
          "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
          "budget_id": "81e4a552-0000-4000-8000-000000000002",
          "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
          "pack_id": null,
          "label": "Limpieza dental",
          "quantity": 1,
          "unit_price_clp": 35000,
          "discount_clp": 5000,
          "total_clp": 30000,
          "price_explain": [
            {
              "kind": null,
              "label": "Arancel particular 2026",
              "order": 1,
              "value": null,
              "source": "price_list",
              "rule_id": null,
              "modifier": null,
              "delta_clp": 0,
              "amount_after_clp": 35000,
              "amount_before_clp": null
            }
          ],
          "tax_treatment": "afecto",
          "phase": 2,
          "note": null,
          "sort_order": 1,
          "created_at": "2026-09-22T22:21:15.530Z",
          "updated_at": "2026-09-22T22:21:15.530Z",
          "service_name": "Limpieza dental"
        }
      ],
      "is_live": false,
      "days_left": 100
    },
    "plan": {
      "id": "1ccffee2-0000-4000-8000-000000000001",
      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
      "source": "native",
      "external_id": null,
      "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
      "name": "Ortodoncia fija superior e inferior",
      "diagnosis": "Apiñamiento anterosuperior moderado",
      "professional_id": "18342d1b-0000-4000-8000-000000000001",
      "status": "proposed",
      "accepted_at": null,
      "completed_at": null,
      "abandoned_reason": null,
      "created_at": "2026-09-22T22:21:15.213Z",
      "updated_at": "2026-09-22T22:21:15.285Z",
      "patient_name": "María José Fuentes Lagos",
      "professional_name": "Ana Rojas Vidal",
      "items": [
        {
          "id": "3a7d9767-0000-4000-8000-000000000002",
          "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
          "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
          "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
          "phase": 1,
          "quantity": 4,
          "tooth_or_zone": null,
          "status": "scheduled",
          "appointment_id": "d93e8eec-0000-4000-8000-000000000001",
          "budget_item_id": null,
          "sort_order": 0,
          "created_at": "2026-09-22T22:21:15.215Z",
          "updated_at": "2026-09-22T22:21:15.337Z",
          "service_name": "Control de ortodoncia"
        },
        {
          "id": "3a7d9767-0000-4000-8000-000000000003",
          "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
          "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
          "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
          "phase": 2,
          "quantity": 1,
          "tooth_or_zone": null,
          "status": "pending",
          "appointment_id": null,
          "budget_item_id": null,
          "sort_order": 1,
          "created_at": "2026-09-22T22:21:15.215Z",
          "updated_at": "2026-09-22T22:21:15.215Z",
          "service_name": "Limpieza dental"
        }
      ],
      "progress": {
        "pending": 1,
        "scheduled": 4,
        "performed": 0,
        "cancelled": 0,
        "active": 5,
        "percent": 0,
        "phases": [
          {
            "phase": 1,
            "pending": 0,
            "scheduled": 4,
            "performed": 0,
            "cancelled": 0,
            "active": 4,
            "percent": 0
          },
          {
            "phase": 2,
            "pending": 1,
            "scheduled": 0,
            "performed": 0,
            "cancelled": 0,
            "active": 1,
            "percent": 0
          }
        ],
        "current_phase": 1
      }
    },
    "obligation_group_id": "a067dce6-0000-4000-8000-000000000002",
    "obligation_ids": [
      "b44936d0-0000-4000-8000-000000000002"
    ],
    "accepted_now": true,
    "accepted_item_ids": [
      "3a7d9767-0000-4000-8000-000000000007"
    ],
    "payment": {
      "payment": {
        "id": "9a7e0000-0000-4000-8000-000000000001",
        "contact_id": "1a73af9e-0000-4000-8000-000000000001",
        "source": "card_terminal",
        "provenance": "manual",
        "instrument": "tarjeta_debito",
        "amount_clp": 112000,
        "paid_on": "2026-09-22",
        "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
        "allocated_clp": 112000,
        "unallocated_clp": 0
      },
      "applied": [
        {
          "obligationId": "b44936d0-0000-4000-8000-000000000002",
          "amountClp": 112000,
          "allocatedClp": 112000,
          "outstandingClp": 0,
          "expectedClp": 112000,
          "state": "paid",
          "label": "Control de ortodoncia"
        }
      ],
      "surplus_clp": 0,
      "reasons": [
        "payer_recorded_by_staff",
        "allocation_staff_manual"
      ],
      "replayed": false
    }
  }
}

Answers: 201 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/budgets/{id}/items

Add a line, freezing its price

Resolves the price through the quote explainer and stores the ordered steps beside the number. An explicit unit_price_clp is recorded AS an override step, so "why does it say $38.000" stays answerable.

Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.

ParameterInTypeRequiredConstraints
idpathuuidyes

Body

FieldTypeRequiredConstraints
clinic_service_idstring | null—
pack_idstring | null—
unit_price_clpinteger | null—≥ 0, ≤ 9999999999
quantityinteger—≥ 1, ≤ 999, por defecto 1
discount_clpinteger—≥ 0, ≤ 9999999999, por defecto 0
tax_treatmentafecto \exento \no_gravado
phaseinteger—≥ 1, ≤ 99, por defecto 1
notestring | null—máx. 500
labelstring—mín. 1, máx. 200
curl -X POST https://api.vitrinadev.com/api/v1/clinic/budgets/<id>/items \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
    "quantity": 1,
    "phase": 3,
    "note": "Control final"
  }'

Example response (201)

{
  "data": {
    "id": "3a7d9767-0000-4000-8000-000000000006",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "budget_id": "81e4a552-0000-4000-8000-000000000001",
    "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
    "pack_id": null,
    "label": "Limpieza dental",
    "quantity": 1,
    "unit_price_clp": 35000,
    "discount_clp": 0,
    "total_clp": 35000,
    "price_explain": [
      {
        "kind": null,
        "label": "Arancel particular 2026",
        "order": 1,
        "value": null,
        "source": "price_list",
        "rule_id": null,
        "modifier": null,
        "delta_clp": 0,
        "amount_after_clp": 35000,
        "amount_before_clp": null
      }
    ],
    "tax_treatment": "afecto",
    "phase": 3,
    "note": "Control final",
    "sort_order": 2,
    "created_at": "2026-09-22T22:21:15.470Z",
    "updated_at": "2026-09-22T22:21:15.470Z"
  }
}

Answers: 201 · 400 · 401 · 403 · 404 · 409 · 429

GET /clinic/budgets/{id}/lineage

Every version of one proposal

The budget's whole lineage (#2446), version ascending — the first presupuesto handed over plus each one that superseded it. \{id\} may name any member, by UUID or folio.

Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.

ParameterInTypeRequiredConstraints
idpathanyyes
curl https://api.vitrinadev.com/api/v1/clinic/budgets/<id>/lineage \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "items": [
      {
        "id": "81e4a552-0000-4000-8000-000000000002",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "source": "native",
        "external_id": null,
        "display_seq": 2,
        "display_id": "E-2",
        "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
        "contact_id": "1a73af9e-0000-4000-8000-000000000001",
        "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
        "lead_id": "8cb33b27-0000-4000-8000-000000000001",
        "lineage_id": "81e4a552-0000-4000-8000-000000000002",
        "reissued_from_id": null,
        "conversation_id": null,
        "professional_id": "18342d1b-0000-4000-8000-000000000001",
        "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
        "price_list_id": null,
        "agreement_id": null,
        "legal_entity_id": null,
        "status": "accepted",
        "subtotal_clp": 147000,
        "discount_clp": 5000,
        "total_clp": 142000,
        "tax_treatment": "afecto",
        "valid_until": "2026-12-31",
        "installments": 3,
        "notes": "Incluye los cuatro controles del primer semestre",
        "obligation_group_id": "a067dce6-0000-4000-8000-000000000002",
        "rendered_document_id": null,
        "created_by_user_id": null,
        "sent_at": "2026-09-23T01:07:07.750Z",
        "accepted_at": "2026-09-23T01:07:07.912Z",
        "accepted_via": "staff",
        "rejected_at": null,
        "rejected_reason": null,
        "expired_at": null,
        "voided_at": null,
        "voided_by": null,
        "voided_reason": null,
        "first_viewed_at": null,
        "last_viewed_at": null,
        "created_at": "2026-09-23T01:07:07.683Z",
        "updated_at": "2026-09-23T01:07:07.941Z",
        "patient_name": "María José Fuentes Lagos",
        "professional_name": "Ana Rojas Vidal",
        "items": [
          {
            "id": "3a7d9767-0000-4000-8000-000000000007",
            "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
            "budget_id": "81e4a552-0000-4000-8000-000000000002",
            "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
            "pack_id": null,
            "label": "Control de ortodoncia",
            "quantity": 4,
            "unit_price_clp": 28000,
            "discount_clp": 0,
            "total_clp": 112000,
            "price_explain": [
              {
                "kind": null,
                "label": "Arancel particular 2026",
                "order": 1,
                "value": null,
                "source": "price_list",
                "rule_id": null,
                "modifier": null,
                "delta_clp": 0,
                "amount_after_clp": 28000,
                "amount_before_clp": null
              }
            ],
            "tax_treatment": "afecto",
            "phase": 1,
            "note": null,
            "sort_order": 0,
            "accepted_at": "2026-09-23T01:07:07.912Z",
            "carried_from_item_id": null,
            "created_at": "2026-09-23T01:07:07.701Z",
            "updated_at": "2026-09-23T01:07:07.911Z",
            "service_name": "Control de ortodoncia"
          },
          {
            "id": "3a7d9767-0000-4000-8000-000000000008",
            "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
            "budget_id": "81e4a552-0000-4000-8000-000000000002",
            "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
            "pack_id": null,
            "label": "Limpieza dental",
            "quantity": 1,
            "unit_price_clp": 35000,
            "discount_clp": 5000,
            "total_clp": 30000,
            "price_explain": [
              {
                "kind": null,
                "label": "Arancel particular 2026",
                "order": 1,
                "value": null,
                "source": "price_list",
                "rule_id": null,
                "modifier": null,
                "delta_clp": 0,
                "amount_after_clp": 35000,
                "amount_before_clp": null
              }
            ],
            "tax_treatment": "afecto",
            "phase": 2,
            "note": null,
            "sort_order": 1,
            "accepted_at": "2026-09-23T01:07:07.912Z",
            "carried_from_item_id": null,
            "created_at": "2026-09-23T01:07:07.718Z",
            "updated_at": "2026-09-23T01:07:07.911Z",
            "service_name": "Limpieza dental"
          }
        ],
        "is_live": false,
        "days_left": 100,
        "version": 1,
        "lineage_size": 2,
        "reissued_by_id": "458363ad-0000-4000-8000-000000000001"
      },
      {
        "id": "458363ad-0000-4000-8000-000000000001",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "source": "native",
        "external_id": null,
        "display_seq": 3,
        "display_id": "E-3",
        "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
        "contact_id": "1a73af9e-0000-4000-8000-000000000001",
        "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
        "lead_id": "8cb33b27-0000-4000-8000-000000000001",
        "lineage_id": "81e4a552-0000-4000-8000-000000000002",
        "reissued_from_id": "81e4a552-0000-4000-8000-000000000002",
        "conversation_id": null,
        "professional_id": "18342d1b-0000-4000-8000-000000000001",
        "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
        "price_list_id": null,
        "agreement_id": null,
        "legal_entity_id": null,
        "status": "draft",
        "subtotal_clp": 147000,
        "discount_clp": 5000,
        "total_clp": 142000,
        "tax_treatment": "afecto",
        "valid_until": "2026-12-31",
        "installments": 3,
        "notes": "Incluye los cuatro controles del primer semestre",
        "obligation_group_id": null,
        "rendered_document_id": null,
        "created_by_user_id": null,
        "sent_at": null,
        "accepted_at": null,
        "accepted_via": null,
        "rejected_at": null,
        "rejected_reason": null,
        "expired_at": null,
        "voided_at": null,
        "voided_by": null,
        "voided_reason": null,
        "first_viewed_at": null,
        "last_viewed_at": null,
        "created_at": "2026-09-23T01:07:07.990Z",
        "updated_at": "2026-09-23T01:07:07.990Z",
        "patient_name": "María José Fuentes Lagos",
        "professional_name": "Ana Rojas Vidal",
        "items": [
          {
            "id": "3a7d9767-0000-4000-8000-000000000009",
            "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
            "budget_id": "458363ad-0000-4000-8000-000000000001",
            "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
            "pack_id": null,
            "label": "Control de ortodoncia",
            "quantity": 4,
            "unit_price_clp": 28000,
            "discount_clp": 0,
            "total_clp": 112000,
            "price_explain": [
              {
                "kind": null,
                "label": "Arancel particular 2026",
                "order": 1,
                "value": null,
                "source": "price_list",
                "rule_id": null,
                "modifier": null,
                "delta_clp": 0,
                "amount_after_clp": 28000,
                "amount_before_clp": null
              }
            ],
            "tax_treatment": "afecto",
            "phase": 1,
            "note": null,
            "sort_order": 0,
            "accepted_at": "2026-09-23T01:07:07.912Z",
            "carried_from_item_id": "3a7d9767-0000-4000-8000-000000000007",
            "created_at": "2026-09-23T01:07:07.990Z",
            "updated_at": "2026-09-23T01:07:07.990Z",
            "service_name": "Control de ortodoncia"
          },
          {
            "id": "3a7d9767-0000-4000-8000-00000000000a",
            "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
            "budget_id": "458363ad-0000-4000-8000-000000000001",
            "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
            "pack_id": null,
            "label": "Limpieza dental",
            "quantity": 1,
            "unit_price_clp": 35000,
            "discount_clp": 5000,
            "total_clp": 30000,
            "price_explain": [
              {
                "kind": null,
                "label": "Arancel particular 2026",
                "order": 1,
                "value": null,
                "source": "price_list",
                "rule_id": null,
                "modifier": null,
                "delta_clp": 0,
                "amount_after_clp": 35000,
                "amount_before_clp": null
              }
            ],
            "tax_treatment": "afecto",
            "phase": 2,
            "note": null,
            "sort_order": 1,
            "accepted_at": "2026-09-23T01:07:07.912Z",
            "carried_from_item_id": "3a7d9767-0000-4000-8000-000000000008",
            "created_at": "2026-09-23T01:07:07.990Z",
            "updated_at": "2026-09-23T01:07:07.990Z",
            "service_name": "Limpieza dental"
          }
        ],
        "is_live": true,
        "days_left": 100,
        "version": 2,
        "lineage_size": 2,
        "reissued_by_id": null
      }
    ],
    "total": 2
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/budgets/{id}/reject

Record the patient declining

Record the patient declining

Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.

ParameterInTypeRequiredConstraints
idpathuuidyes

Body

FieldTypeRequiredConstraints
reasonstring | null—máx. 500
curl -X POST https://api.vitrinadev.com/api/v1/clinic/budgets/<id>/reject \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "reason": "El paciente prefiere esperar al próximo semestre"
  }'

Example response (200)

{
  "data": {
    "id": "90afb8c1-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "source": "native",
    "external_id": null,
    "display_seq": 3,
    "display_id": "E-3",
    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
    "contact_id": "1a73af9e-0000-4000-8000-000000000001",
    "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
    "lead_id": "8cb33b27-0000-4000-8000-000000000001",
    "lineage_id": "90afb8c1-0000-4000-8000-000000000001",
    "reissued_from_id": null,
    "conversation_id": null,
    "professional_id": "18342d1b-0000-4000-8000-000000000001",
    "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
    "price_list_id": null,
    "agreement_id": null,
    "legal_entity_id": null,
    "status": "rejected",
    "subtotal_clp": 147000,
    "discount_clp": 5000,
    "total_clp": 142000,
    "tax_treatment": "afecto",
    "valid_until": "2026-12-31",
    "installments": 3,
    "notes": "Incluye los cuatro controles del primer semestre",
    "obligation_group_id": null,
    "rendered_document_id": null,
    "created_by_user_id": null,
    "sent_at": "2026-09-22T22:21:16.137Z",
    "accepted_at": null,
    "accepted_via": null,
    "rejected_at": "2026-09-22T22:21:16.460Z",
    "rejected_reason": "El paciente prefiere esperar al próximo semestre",
    "expired_at": null,
    "voided_at": null,
    "voided_by": null,
    "voided_reason": null,
    "created_at": "2026-09-22T22:21:16.091Z",
    "updated_at": "2026-09-22T22:21:16.459Z",
    "patient_name": "María José Fuentes Lagos",
    "professional_name": "Ana Rojas Vidal",
    "items": [
      {
        "id": "3a7d9767-0000-4000-8000-000000000009",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "budget_id": "90afb8c1-0000-4000-8000-000000000001",
        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
        "pack_id": null,
        "label": "Control de ortodoncia",
        "quantity": 4,
        "unit_price_clp": 28000,
        "discount_clp": 0,
        "total_clp": 112000,
        "price_explain": [
          {
            "kind": null,
            "label": "Arancel particular 2026",
            "order": 1,
            "value": null,
            "source": "price_list",
            "rule_id": null,
            "modifier": null,
            "delta_clp": 0,
            "amount_after_clp": 28000,
            "amount_before_clp": null
          }
        ],
        "tax_treatment": "afecto",
        "phase": 1,
        "note": null,
        "sort_order": 0,
        "created_at": "2026-09-22T22:21:16.106Z",
        "updated_at": "2026-09-22T22:21:16.106Z",
        "service_name": "Control de ortodoncia"
      },
      {
        "id": "3a7d9767-0000-4000-8000-00000000000a",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "budget_id": "90afb8c1-0000-4000-8000-000000000001",
        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
        "pack_id": null,
        "label": "Limpieza dental",
        "quantity": 1,
        "unit_price_clp": 35000,
        "discount_clp": 5000,
        "total_clp": 30000,
        "price_explain": [
          {
            "kind": null,
            "label": "Arancel particular 2026",
            "order": 1,
            "value": null,
            "source": "price_list",
            "rule_id": null,
            "modifier": null,
            "delta_clp": 0,
            "amount_after_clp": 35000,
            "amount_before_clp": null
          }
        ],
        "tax_treatment": "afecto",
        "phase": 2,
        "note": null,
        "sort_order": 1,
        "created_at": "2026-09-22T22:21:16.121Z",
        "updated_at": "2026-09-22T22:21:16.121Z",
        "service_name": "Limpieza dental"
      }
    ],
    "is_live": false,
    "days_left": 100
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

GET /clinic/budgets/{id}/render

The presupuesto as a PDF

Renders on demand and returns application/pdf. It is immutable BECAUSE every figure on it is frozen in the budget and its lines (ADR 0057), not because the bytes were stored.

Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.

ParameterInTypeRequiredConstraints
idpathanyyes
curl https://api.vitrinadev.com/api/v1/clinic/budgets/<id>/render \
  -H "Authorization: Bearer $VITRINA_KEY"

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/budgets/{id}/send

Send it to the patient

Moves draft → sent FIRST (the freeze must be in force before the patient can read the figures), then delivers through the conversation with a tokenised accept link: WhatsApp while the window is open, email otherwise. A patient with neither is reported honestly (delivered: "none") and the quote still counts as sent — the clinic handed it over. Also puts the lead on «Presupuesto enviado» with the total as its value.

Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.

ParameterInTypeRequiredConstraints
idpathuuidyes

Body

FieldTypeRequiredConstraints
conversation_idstring | null—
resendboolean—por defecto false
curl -X POST https://api.vitrinadev.com/api/v1/clinic/budgets/<id>/send \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'

Example response (200)

{
  "data": {
    "budget": {
      "id": "81e4a552-0000-4000-8000-000000000002",
      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
      "source": "native",
      "external_id": null,
      "display_seq": 2,
      "display_id": "E-2",
      "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
      "contact_id": "1a73af9e-0000-4000-8000-000000000001",
      "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
      "lead_id": "8cb33b27-0000-4000-8000-000000000001",
      "lineage_id": "81e4a552-0000-4000-8000-000000000002",
      "reissued_from_id": null,
      "conversation_id": null,
      "professional_id": "18342d1b-0000-4000-8000-000000000001",
      "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
      "price_list_id": null,
      "agreement_id": null,
      "legal_entity_id": null,
      "status": "sent",
      "subtotal_clp": 147000,
      "discount_clp": 5000,
      "total_clp": 142000,
      "tax_treatment": "afecto",
      "valid_until": "2026-12-31",
      "installments": 3,
      "notes": "Incluye los cuatro controles del primer semestre",
      "obligation_group_id": null,
      "rendered_document_id": null,
      "created_by_user_id": null,
      "sent_at": "2026-09-22T22:21:15.544Z",
      "accepted_at": null,
      "accepted_via": null,
      "rejected_at": null,
      "rejected_reason": null,
      "expired_at": null,
      "voided_at": null,
      "voided_by": null,
      "voided_reason": null,
      "created_at": "2026-09-22T22:21:15.502Z",
      "updated_at": "2026-09-22T22:21:16.026Z",
      "patient_name": "María José Fuentes Lagos",
      "professional_name": "Ana Rojas Vidal",
      "items": [
        {
          "id": "3a7d9767-0000-4000-8000-000000000007",
          "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
          "budget_id": "81e4a552-0000-4000-8000-000000000002",
          "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
          "pack_id": null,
          "label": "Control de ortodoncia",
          "quantity": 4,
          "unit_price_clp": 28000,
          "discount_clp": 0,
          "total_clp": 112000,
          "price_explain": [
            {
              "kind": null,
              "label": "Arancel particular 2026",
              "order": 1,
              "value": null,
              "source": "price_list",
              "rule_id": null,
              "modifier": null,
              "delta_clp": 0,
              "amount_after_clp": 28000,
              "amount_before_clp": null
            }
          ],
          "tax_treatment": "afecto",
          "phase": 1,
          "note": null,
          "sort_order": 0,
          "created_at": "2026-09-22T22:21:15.515Z",
          "updated_at": "2026-09-22T22:21:15.515Z",
          "service_name": "Control de ortodoncia"
        },
        {
          "id": "3a7d9767-0000-4000-8000-000000000008",
          "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
          "budget_id": "81e4a552-0000-4000-8000-000000000002",
          "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
          "pack_id": null,
          "label": "Limpieza dental",
          "quantity": 1,
          "unit_price_clp": 35000,
          "discount_clp": 5000,
          "total_clp": 30000,
          "price_explain": [
            {
              "kind": null,
              "label": "Arancel particular 2026",
              "order": 1,
              "value": null,
              "source": "price_list",
              "rule_id": null,
              "modifier": null,
              "delta_clp": 0,
              "amount_after_clp": 35000,
              "amount_before_clp": null
            }
          ],
          "tax_treatment": "afecto",
          "phase": 2,
          "note": null,
          "sort_order": 1,
          "created_at": "2026-09-22T22:21:15.530Z",
          "updated_at": "2026-09-22T22:21:15.530Z",
          "service_name": "Limpieza dental"
        }
      ],
      "is_live": true,
      "days_left": 100
    },
    "delivered": "email",
    "accept_url": "https://api.example.com/api/v1/public/clinic/budget/<token>",
    "reason": null
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/budgets/{id}/supersede

Write a new version (Nueva versión)

From sent, accepted, rejected or expired (409 otherwise, and 409 when a version already supersedes it). Creates a DRAFT copying the header and every line verbatim, in the same lineage. Lines already ACCEPTED are carried forward (carried_from_item_id, their original accepted_at), are immutable on the draft (409 carried_item), and open no second obligation when the new version is accepted — which reuses the predecessor's obligation group. Nothing is withdrawn until the draft is SENT: that voids a sent, rejected or expired predecessor with reason superseded in the same transaction. An ACCEPTED predecessor stays accepted until the new version is itself accepted, so its debt always has an accepted document. Discarding the draft leaves the predecessor untouched. Same rules as POST /clinic/budgets with reissued_from_id. A presupuesto photographed from a mirrored Dentalink plan (mirror set) is RE-PHOTOGRAPHED instead: accepted lines are still carried, every other line comes from the plan's live state, and the new version records that state — born without mirror.changed_at (409 MIRROR_LINES_UNMAPPED if the live plan has a prestación missing from the catalogue).

Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.

ParameterInTypeRequiredConstraints
idpathuuidyes

Body

FieldTypeRequiredConstraints
curl -X POST https://api.vitrinadev.com/api/v1/clinic/budgets/<id>/supersede \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'

Example response (201)

{
  "data": {
    "id": "458363ad-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "source": "native",
    "external_id": null,
    "display_seq": 3,
    "display_id": "E-3",
    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
    "contact_id": "1a73af9e-0000-4000-8000-000000000001",
    "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
    "lead_id": "8cb33b27-0000-4000-8000-000000000001",
    "lineage_id": "81e4a552-0000-4000-8000-000000000002",
    "reissued_from_id": "81e4a552-0000-4000-8000-000000000002",
    "conversation_id": null,
    "professional_id": "18342d1b-0000-4000-8000-000000000001",
    "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
    "price_list_id": null,
    "agreement_id": null,
    "legal_entity_id": null,
    "status": "draft",
    "subtotal_clp": 147000,
    "discount_clp": 5000,
    "total_clp": 142000,
    "tax_treatment": "afecto",
    "valid_until": "2026-12-31",
    "installments": 3,
    "notes": "Incluye los cuatro controles del primer semestre",
    "obligation_group_id": null,
    "rendered_document_id": null,
    "created_by_user_id": null,
    "sent_at": null,
    "accepted_at": null,
    "accepted_via": null,
    "rejected_at": null,
    "rejected_reason": null,
    "expired_at": null,
    "voided_at": null,
    "voided_by": null,
    "voided_reason": null,
    "first_viewed_at": null,
    "last_viewed_at": null,
    "created_at": "2026-09-23T01:07:07.990Z",
    "updated_at": "2026-09-23T01:07:07.990Z",
    "patient_name": "María José Fuentes Lagos",
    "professional_name": "Ana Rojas Vidal",
    "items": [
      {
        "id": "3a7d9767-0000-4000-8000-000000000009",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "budget_id": "458363ad-0000-4000-8000-000000000001",
        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
        "pack_id": null,
        "label": "Control de ortodoncia",
        "quantity": 4,
        "unit_price_clp": 28000,
        "discount_clp": 0,
        "total_clp": 112000,
        "price_explain": [
          {
            "kind": null,
            "label": "Arancel particular 2026",
            "order": 1,
            "value": null,
            "source": "price_list",
            "rule_id": null,
            "modifier": null,
            "delta_clp": 0,
            "amount_after_clp": 28000,
            "amount_before_clp": null
          }
        ],
        "tax_treatment": "afecto",
        "phase": 1,
        "note": null,
        "sort_order": 0,
        "accepted_at": "2026-09-23T01:07:07.912Z",
        "carried_from_item_id": "3a7d9767-0000-4000-8000-000000000007",
        "created_at": "2026-09-23T01:07:07.990Z",
        "updated_at": "2026-09-23T01:07:07.990Z",
        "service_name": "Control de ortodoncia"
      },
      {
        "id": "3a7d9767-0000-4000-8000-00000000000a",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "budget_id": "458363ad-0000-4000-8000-000000000001",
        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
        "pack_id": null,
        "label": "Limpieza dental",
        "quantity": 1,
        "unit_price_clp": 35000,
        "discount_clp": 5000,
        "total_clp": 30000,
        "price_explain": [
          {
            "kind": null,
            "label": "Arancel particular 2026",
            "order": 1,
            "value": null,
            "source": "price_list",
            "rule_id": null,
            "modifier": null,
            "delta_clp": 0,
            "amount_after_clp": 35000,
            "amount_before_clp": null
          }
        ],
        "tax_treatment": "afecto",
        "phase": 2,
        "note": null,
        "sort_order": 1,
        "accepted_at": "2026-09-23T01:07:07.912Z",
        "carried_from_item_id": "3a7d9767-0000-4000-8000-000000000008",
        "created_at": "2026-09-23T01:07:07.990Z",
        "updated_at": "2026-09-23T01:07:07.990Z",
        "service_name": "Limpieza dental"
      }
    ],
    "is_live": true,
    "days_left": 100,
    "version": 2,
    "lineage_size": 2,
    "reissued_by_id": null
  }
}

Answers: 201 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/budgets/{id}/void

Void it — never DELETE

Keeps the folio and stays listable (ADR 0058 §10). reason is required by the service AND by a database CHECK: "why did we withdraw 40 quotes this month" is only answerable if it was written down. An ACCEPTED presupuesto cannot be voided here, and the reason superseded is reserved — write a new version instead (POST /clinic/budgets/\{id\}/supersede).

Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.

ParameterInTypeRequiredConstraints
idpathuuidyes

Body

FieldTypeRequiredConstraints
reasonstringyesmín. 1, máx. 500
curl -X POST https://api.vitrinadev.com/api/v1/clinic/budgets/<id>/void \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "reason": "Se emitió con el arancel equivocado"
  }'

Example response (200)

{
  "data": {
    "id": "e9cede9b-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "source": "native",
    "external_id": null,
    "display_seq": 4,
    "display_id": "E-4",
    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
    "contact_id": "1a73af9e-0000-4000-8000-000000000001",
    "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
    "lead_id": "8cb33b27-0000-4000-8000-000000000001",
    "lineage_id": "e9cede9b-0000-4000-8000-000000000001",
    "reissued_from_id": null,
    "conversation_id": null,
    "professional_id": "18342d1b-0000-4000-8000-000000000001",
    "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
    "price_list_id": null,
    "agreement_id": null,
    "legal_entity_id": null,
    "status": "voided",
    "subtotal_clp": 147000,
    "discount_clp": 5000,
    "total_clp": 142000,
    "tax_treatment": "afecto",
    "valid_until": "2026-12-31",
    "installments": 3,
    "notes": "Incluye los cuatro controles del primer semestre",
    "obligation_group_id": null,
    "rendered_document_id": null,
    "created_by_user_id": null,
    "sent_at": "2026-09-22T22:21:16.514Z",
    "accepted_at": null,
    "accepted_via": null,
    "rejected_at": null,
    "rejected_reason": null,
    "expired_at": null,
    "voided_at": "2026-09-22T22:21:16.806Z",
    "voided_by": null,
    "voided_reason": "Se emitió con el arancel equivocado",
    "created_at": "2026-09-22T22:21:16.473Z",
    "updated_at": "2026-09-22T22:21:16.805Z",
    "patient_name": "María José Fuentes Lagos",
    "professional_name": "Ana Rojas Vidal",
    "items": [
      {
        "id": "3a7d9767-0000-4000-8000-00000000000b",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "budget_id": "e9cede9b-0000-4000-8000-000000000001",
        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
        "pack_id": null,
        "label": "Control de ortodoncia",
        "quantity": 4,
        "unit_price_clp": 28000,
        "discount_clp": 0,
        "total_clp": 112000,
        "price_explain": [
          {
            "kind": null,
            "label": "Arancel particular 2026",
            "order": 1,
            "value": null,
            "source": "price_list",
            "rule_id": null,
            "modifier": null,
            "delta_clp": 0,
            "amount_after_clp": 28000,
            "amount_before_clp": null
          }
        ],
        "tax_treatment": "afecto",
        "phase": 1,
        "note": null,
        "sort_order": 0,
        "created_at": "2026-09-22T22:21:16.485Z",
        "updated_at": "2026-09-22T22:21:16.485Z",
        "service_name": "Control de ortodoncia"
      },
      {
        "id": "3a7d9767-0000-4000-8000-00000000000c",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "budget_id": "e9cede9b-0000-4000-8000-000000000001",
        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
        "pack_id": null,
        "label": "Limpieza dental",
        "quantity": 1,
        "unit_price_clp": 35000,
        "discount_clp": 5000,
        "total_clp": 30000,
        "price_explain": [
          {
            "kind": null,
            "label": "Arancel particular 2026",
            "order": 1,
            "value": null,
            "source": "price_list",
            "rule_id": null,
            "modifier": null,
            "delta_clp": 0,
            "amount_after_clp": 35000,
            "amount_before_clp": null
          }
        ],
        "tax_treatment": "afecto",
        "phase": 2,
        "note": null,
        "sort_order": 1,
        "created_at": "2026-09-22T22:21:16.500Z",
        "updated_at": "2026-09-22T22:21:16.500Z",
        "service_name": "Limpieza dental"
      }
    ],
    "is_live": false,
    "days_left": 100
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/budgets/from-mirror-plan

Create a presupuesto (draft) from a mirrored Dentalink plan

On a Dentalink clinic the plan is born in Dentalink; this composes the presupuesto FROM its mirrored copy in one action. Every line of the plan becomes a line at Dentalink's net price (after its descuentos), frozen with an explanation that names the source; the plan's professional becomes the presupuesto's (per-line performers are not kept). The response carries mirror: \{plan_id, snapshot_at, changed_at\}. When the plan later changes in Dentalink — a line added, removed or repriced, even at the same total — mirror.changed_at is set and clinic_budget.mirror_changed fires; POST /clinic/budgets/\{id\}/supersede then writes a new version from the live plan. 409 MIRROR_LINES_UNMAPPED lists the prestaciones missing from the local catalogue (details.fix names the importer); 409 with details.reason for a plan whose patient is not linked, a plan with no lines yet, or one another presupuesto already photographs. Vitrina never writes to Dentalink.

Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.

Body

FieldTypeRequiredConstraints
mirror_plan_iduuidyes
clinic_patient_iduuid—
contact_idstring | null—
location_idstring | null—
conversation_idstring | null—
lead_idstring | null—
tax_treatmentafecto \exento \no_gravado
valid_untilstring | null—patrón ^\d{4}-\d{2}-\d{2}$
installmentsinteger | null—≥ 1, ≤ 48
notesstring | null—máx. 2000
curl -X POST https://api.vitrinadev.com/api/v1/clinic/budgets/from-mirror-plan \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "mirror_plan_id": "6d2c1f0a-0000-4000-8000-000000000001"
  }'

Example response (201)

{
  "data": {
    "id": "458363ad-0000-4000-8000-000000000009",
    "display_id": "E-52",
    "status": "draft",
    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
    "contact_id": "1a73af9e-0000-4000-8000-000000000001",
    "professional_id": "18342d1b-0000-4000-8000-000000000001",
    "lineage_id": "458363ad-0000-4000-8000-000000000009",
    "reissued_from_id": null,
    "subtotal_clp": 150000,
    "discount_clp": 0,
    "total_clp": 150000,
    "tax_treatment": "afecto",
    "valid_until": "2026-10-23",
    "mirror": {
      "plan_id": "6d2c1f0a-0000-4000-8000-000000000001",
      "snapshot_at": "2026-09-23T14:02:11.000Z",
      "changed_at": null
    },
    "items": [
      {
        "id": "a1c0ffee-0000-4000-8000-000000000001",
        "clinic_service_id": "5e5e5e5e-0000-4000-8000-000000000001",
        "label": "Resina compuesta",
        "quantity": 1,
        "unit_price_clp": 100000,
        "discount_clp": 0,
        "total_clp": 100000,
        "price_explain": [
          {
            "order": 1,
            "source": "mirror_snapshot",
            "label": "Precio del tratamiento en Dentalink",
            "amount_before_clp": null,
            "amount_after_clp": 120000,
            "delta_clp": 0,
            "external_id": 1,
            "external_plan": 90412
          },
          {
            "order": 2,
            "source": "mirror_snapshot",
            "label": "Descuento registrado en Dentalink",
            "amount_before_clp": 120000,
            "amount_after_clp": 100000,
            "delta_clp": -20000,
            "external_id": 1,
            "external_plan": 90412
          }
        ]
      },
      {
        "id": "a1c0ffee-0000-4000-8000-000000000002",
        "clinic_service_id": "5e5e5e5e-0000-4000-8000-000000000002",
        "label": "Sellante",
        "quantity": 1,
        "unit_price_clp": 50000,
        "discount_clp": 0,
        "total_clp": 50000,
        "price_explain": [
          {
            "order": 1,
            "source": "mirror_snapshot",
            "label": "Precio del tratamiento en Dentalink",
            "amount_before_clp": null,
            "amount_after_clp": 50000,
            "delta_clp": 0,
            "external_id": 2,
            "external_plan": 90412
          }
        ]
      }
    ],
    "version": 1,
    "lineage_size": 1,
    "reissued_by_id": null
  }
}

Answers: 201 · 400 · 401 · 403 · 404 · 409 · 429

GET /clinic/budgets/mirror-plans

A patient's mirrored Dentalink plans, for «Crear desde Dentalink»

The patient's plans in the espejo de ingresos (a Dentalink clinic's tratamientos, copied read-only), newest first, each with lines_count, unmapped_count (lines whose prestación is not in the local catalogue yet — the plan cannot be photographed until the catalog importer runs) and already_budgeted / budget_id (a non-voided presupuesto already photographs it). available: false means the workspace has no enabled Dentalink mirror, and items is empty.

Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.

ParameterInTypeRequiredConstraints
patient_idqueryuuidyes
curl https://api.vitrinadev.com/api/v1/clinic/budgets/mirror-plans \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "available": true,
    "items": [
      {
        "id": "6d2c1f0a-0000-4000-8000-000000000001",
        "external_id": 90412,
        "fecha": "2026-09-10",
        "finalizado": false,
        "bloqueado": false,
        "total": 150000,
        "deuda": 150000,
        "clinic_professional_id": "18342d1b-0000-4000-8000-000000000001",
        "professional_name": "Camila Rojas",
        "lines_count": 2,
        "unmapped_count": 0,
        "already_budgeted": false,
        "budget_id": null
      }
    ],
    "total": 1
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

GET /clinic/budgets/summary

The COMERCIAL badge and funnel, counted by lineage

Counts LINEAGES, not documents: a proposal re-issued three times is one proposal, read at its latest version that is neither a draft nor voided. sent_and_live is the badge and counts on valid_until rather than on status, so a quote that lapsed twelve minutes ago is already out — the expiry sweep runs on a tick and the badge must not tell the clinic to chase a dead number. lineages / by_status are the funnel.

Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.

curl https://api.vitrinadev.com/api/v1/clinic/budgets/summary \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "sent_and_live": 0
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

GET /clinic/imported-balances

«Saldos pendientes de Dentalink»: what patients still owe on imported presupuestos

Read-only. One row per patient whose presupuestos imported from Dentalink by the instructed migration run (#2467) still carry a balance: the balance at cutover (data.imported.deuda of each imported, non-voided presupuesto), what was collected in Vitrina since, the pending balance_clp, the presupuestos it sits on (folio + the Dentalink plan id and date), and the last payment — last_vendor_payment_on from the revenue mirror (voided payments excluded), last_collected_on from collections in Vitrina, last_payment_on the later of the two (YYYY-MM-DD). A balance leaves the list when it is collected in full (POST /clinic/imported-balances/\{budget_id\}/collect), or when its presupuesto is voided; a partial collection lowers it. A collected payment that is later reversed stays owed in the native ledger, not back on this list. No charge, obligation, reminder, follow-up, commission or boleta is ever produced by a listed balance on its own. q matches a name (accent-folded), a RUT prefix (any format) or a phone fragment; sort is balance (default, largest first), name (by apellido) or last_payment. summary is the whole list, whatever q says. Requires clinic_money:read (and clinic_patients:read for an API key).

Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.

ParameterInTypeRequiredConstraints
qquerystringnomáx. 120
sortquerybalance \name \last_payment
offsetqueryinteger | nullno≥ 0, ≤ 100000
limitqueryintegerno≥ 1, ≤ 200
curl https://api.vitrinadev.com/api/v1/clinic/imported-balances \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "rows": [
      {
        "patient": {
          "id": "b1b0b8de-0000-4000-8000-000000000001",
          "nombre": "María José",
          "apellidos": "Fuentes Lagos",
          "rut": "11.111.111-1",
          "numero_ficha": "4412"
        },
        "balance_clp": 110000,
        "cutover_balance_clp": 150000,
        "collected_clp": 40000,
        "last_vendor_payment_on": "2026-10-01",
        "last_collected_on": "2026-11-03",
        "last_payment_on": "2026-11-03",
        "budgets": [
          {
            "budget_id": "81e4a552-0000-4000-8000-000000000011",
            "display_id": "E-412",
            "status": "accepted",
            "external_id": "18233",
            "plan_date": "2026-06-01",
            "cutover_balance_clp": 100000,
            "collected_clp": 40000,
            "balance_clp": 60000
          },
          {
            "budget_id": "81e4a552-0000-4000-8000-000000000012",
            "display_id": "E-398",
            "status": "expired",
            "external_id": "17120",
            "plan_date": "2026-03-01",
            "cutover_balance_clp": 50000,
            "collected_clp": 0,
            "balance_clp": 50000
          }
        ]
      }
    ],
    "total": 1,
    "offset": 0,
    "limit": 50,
    "summary": {
      "patients": 1,
      "balance_clp": 110000,
      "cutover_balance_clp": 150000,
      "collected_clp": 40000
    }
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

GET /clinic/imported-balances/{budget_id}

One imported presupuesto’s Dentalink balance and default payer

The pending balance of one presupuesto imported from Dentalink (0 when voided) and the payer a collection is recorded for when the request names none: the presupuesto’s own contact_id, else the patient’s reading bond or verified link, else a payer bond a collection left. payer: null means a collection must name one. credits lists the payer’s money not yet applied to anything (a transfer the bank-email matcher recorded, the surplus of an earlier payment) — the payer named by contact_id, else the default one; any of them can settle the balance with credit_payment_id. will_bond_payer (with contact_id) says whether collecting with that payer leaves a new payer bond. 409 not_imported for a presupuesto created in Vitrina. Requires clinic_money:read (and clinic_patients:read for an API key).

Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.

ParameterInTypeRequiredConstraints
budget_idpathuuidyes
contact_idqueryuuidno
curl https://api.vitrinadev.com/api/v1/clinic/imported-balances/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "budget_id": "81e4a552-0000-4000-8000-000000000011",
    "display_id": "E-412",
    "status": "accepted",
    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
    "cutover_balance_clp": 100000,
    "balance_clp": 60000,
    "payer": {
      "contact_id": "1a73af9e-0000-4000-8000-000000000001",
      "name": "María José Fuentes Lagos"
    },
    "will_bond_payer": false,
    "credits": [
      {
        "payment_id": "c0ffee00-0000-4000-8000-000000000021",
        "paid_on": "2026-11-02",
        "instrument": "transferencia",
        "amount_clp": 30000,
        "unallocated_clp": 30000
      }
    ]
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/imported-balances/{budget_id}/collect

Collect a payment (or apply credit) against a Dentalink balance

Send exactly one of payment or credit_payment_id. With payment, records it through the payments ledger exactly like any counter payment (caja shift of the presupuesto’s sucursal, card recargo, clinic.payment.registered), and — in the same transaction — opens ONE obligation in the presupuesto’s balance group for the amount applied: min(amount_clp, pending balance), no due date, allocated at once, so it is born settled. Money beyond the balance stays as the payer’s credit. With credit_payment_id, applies what that payment still has unallocated (its payer pays; contact_id is ignored) the same way, as one write; 409 no_credit when nothing is left on it. Two collections of one balance are serialised on the presupuesto; neither can take it below zero. contact_id names the payer; omitted, the default payer of GET /clinic/imported-balances/\{budget_id\} is used (409 payer_unknown when there is none). When the patient had no payer at all, the payer this collection names becomes a bond (can_book, never can_read_record, verified_method: "manual", role otro; payer_bond_id) and is the default payer next time. 409 not_imported, budget_voided, nothing_to_collect. Idempotency-Key is stored on the payment: a repeat records and applies nothing and echoes the first payment (payment.replayed: true once the request cache has expired); a key already used for another gesture is 409 idempotency_key_reused. Requires BOTH clinic_money:write and payments:write. An AGENT cannot reach it.

Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.

ParameterInTypeRequiredConstraints
budget_idpathuuidyes

Body

FieldTypeRequiredConstraints
contact_idstring | null—
paymentobject—
credit_payment_iduuid—
curl -X POST https://api.vitrinadev.com/api/v1/clinic/imported-balances/<id>/collect \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "payment": {
      "instrument": "tarjeta_debito",
      "source": "card_terminal",
      "amount_clp": 40000
    }
  }'

Example response (201)

{
  "data": {
    "budget_id": "81e4a552-0000-4000-8000-000000000011",
    "display_id": "E-412",
    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
    "applied_clp": 40000,
    "balance_before_clp": 100000,
    "balance_after_clp": 60000,
    "obligation_group_id": "a067dce6-0000-4000-8000-000000000011",
    "obligation_id": "b44936d0-0000-4000-8000-000000000011",
    "payer_bond_id": null,
    "payment": {
      "payment": {
        "id": "c0ffee00-0000-4000-8000-000000000011",
        "contact_id": "1a73af9e-0000-4000-8000-000000000001",
        "suggested_contact_id": null,
        "payer_reasons": [
          "payer_recorded_by_staff"
        ],
        "source": "card_terminal",
        "provenance": "manual",
        "instrument": "tarjeta_debito",
        "amount_clp": 40000,
        "paid_on": "2026-11-03",
        "allocated_clp": 40000,
        "unallocated_clp": 0,
        "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
        "recorded_at": "2026-11-03T13:11:26.575Z",
        "reversed": false,
        "allocations": [
          {
            "id": "a11ca7e0-0000-4000-8000-000000000011",
            "customer_payment_id": "c0ffee00-0000-4000-8000-000000000011",
            "customer_obligation_id": "b44936d0-0000-4000-8000-000000000011",
            "amount_clp": 40000,
            "method": "staff",
            "reasons": [
              "allocation_staff_manual"
            ],
            "allocated_at": "2026-11-03T13:11:26.575Z",
            "released_at": null,
            "live": true
          }
        ]
      },
      "applied": [
        {
          "obligationId": "b44936d0-0000-4000-8000-000000000011",
          "amountClp": 40000,
          "allocatedClp": 40000,
          "outstandingClp": 0,
          "expectedClp": 40000,
          "state": "paid",
          "label": "Saldo de tratamiento · E-412"
        }
      ],
      "surplus_clp": 0,
      "reasons": [
        "payer_recorded_by_staff",
        "allocation_staff_manual"
      ],
      "replayed": false
    }
  }
}

Answers: 200 · 201 · 400 · 401 · 403 · 404 · 409 · 429

GET /clinic/patients/{id}/treatment-progress

A patient's treatment progress

Two sources, both reported: pack-backed progress where the clinic sells packs, and performed / unclosed / upcoming citas per service off the mirror for a clinic that does not (Medilink). Reports what was PERFORMED; it never invents the session count quoted at a consultation.

Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.

ParameterInTypeRequiredConstraints
idpathuuidyes
curl https://api.vitrinadev.com/api/v1/clinic/patients/<id>/treatment-progress \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "patient": {
      "id": "b1b0b8de-0000-4000-8000-000000000001",
      "source": "native",
      "nombre": "María José",
      "apellidos": "Fuentes Lagos"
    },
    "today": "2026-09-22",
    "timezone": "America/Santiago",
    "packs": [
      {
        "purchase_id": "d376ca29-0000-4000-8000-000000000001",
        "name": "Pack 4 controles de ortodoncia",
        "pack_id": "6acd6212-0000-4000-8000-000000000001",
        "source": "native",
        "purchased_at": "2026-09-22T22:21:14.782Z",
        "expires_at": "2027-04-20",
        "frozen_from": null,
        "frozen_to": null,
        "freeze_count": 1,
        "stored_status": "active",
        "precio_clp": 96000,
        "obligation_group_id": "a067dce6-0000-4000-8000-000000000001",
        "balance": {
          "session_count": 4,
          "consumed": 0,
          "reversed": 1,
          "remaining": 4,
          "over_consumed": false,
          "frozen": false,
          "date_passed": false,
          "days_to_expiry": 210,
          "effective_status": "active"
        },
        "last_session_at": "2026-09-22T22:21:14.813Z",
        "next_session_due_after": null
      }
    ],
    "services": [],
    "truncated": false
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/treatment-plan-items/{id}/status

Move one phase item along

pending → scheduled → performed (or cancelled). scheduled requires the cita it lands on. The PLAN's own status follows from its items, so nobody has to remember to close a plan whose last phase was performed.

Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.

ParameterInTypeRequiredConstraints
idpathuuidyes

Body

FieldTypeRequiredConstraints
statuspending \scheduled \performed \
appointment_idstring | null—
curl -X POST https://api.vitrinadev.com/api/v1/clinic/treatment-plan-items/<id>/status \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "scheduled",
    "appointment_id": "d93e8eec-0000-4000-8000-000000000001"
  }'

Example response (200)

{
  "data": {
    "id": "1ccffee2-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "source": "native",
    "external_id": null,
    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
    "name": "Ortodoncia fija superior e inferior",
    "diagnosis": "Apiñamiento anterosuperior moderado",
    "professional_id": "18342d1b-0000-4000-8000-000000000001",
    "status": "proposed",
    "accepted_at": null,
    "completed_at": null,
    "abandoned_reason": null,
    "created_at": "2026-09-22T22:21:15.213Z",
    "updated_at": "2026-09-22T22:21:15.285Z",
    "patient_name": "María José Fuentes Lagos",
    "professional_name": "Ana Rojas Vidal",
    "items": [
      {
        "id": "3a7d9767-0000-4000-8000-000000000002",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
        "phase": 1,
        "quantity": 4,
        "tooth_or_zone": null,
        "status": "scheduled",
        "appointment_id": "d93e8eec-0000-4000-8000-000000000001",
        "budget_item_id": null,
        "sort_order": 0,
        "created_at": "2026-09-22T22:21:15.215Z",
        "updated_at": "2026-09-22T22:21:15.337Z",
        "service_name": "Control de ortodoncia"
      },
      {
        "id": "3a7d9767-0000-4000-8000-000000000003",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
        "phase": 2,
        "quantity": 1,
        "tooth_or_zone": null,
        "status": "pending",
        "appointment_id": null,
        "budget_item_id": null,
        "sort_order": 1,
        "created_at": "2026-09-22T22:21:15.215Z",
        "updated_at": "2026-09-22T22:21:15.215Z",
        "service_name": "Limpieza dental"
      }
    ],
    "progress": {
      "pending": 1,
      "scheduled": 4,
      "performed": 0,
      "cancelled": 0,
      "active": 5,
      "percent": 0,
      "phases": [
        {
          "phase": 1,
          "pending": 0,
          "scheduled": 4,
          "performed": 0,
          "cancelled": 0,
          "active": 4,
          "percent": 0
        },
        {
          "phase": 2,
          "pending": 1,
          "scheduled": 0,
          "performed": 0,
          "cancelled": 0,
          "active": 1,
          "percent": 0
        }
      ],
      "current_phase": 1
    }
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

GET /clinic/treatment-plans

List treatment plans, with phase progress

Progress is DERIVED from the items (pending / scheduled / performed), never a stored counter — a counter drifts the first time a cita is cancelled. ?live=true is accepted + in_progress, the work still to do.

Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.

ParameterInTypeRequiredConstraints
statusquerydraft \proposed \accepted \
livequerytrue \falseno
patient_idqueryuuidno
professional_idqueryuuidno
pagequeryintegerno≥ 1, por defecto 1
limitqueryintegerno≥ 1, ≤ 200, por defecto 50
curl https://api.vitrinadev.com/api/v1/clinic/treatment-plans \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "items": [],
    "total": 0,
    "page": 1,
    "limit": 50
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/treatment-plans

Create a treatment plan by hand

Accepting a presupuesto creates one automatically; this is the path for a plan that never had a quote. Clinical authoring: accepts clinic_money:write or clinic_record:write (items carry no price); a clinical-only caller must be able to open the patient's ficha.

Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.

Body

FieldTypeRequiredConstraints
clinic_patient_iduuidyes
namestringyesmín. 1, máx. 200
diagnosisstring | null—máx. 2000
professional_idstring | null—
itemsobject[]—
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",
    "professional_id": "18342d1b-0000-4000-8000-000000000001",
    "items": [
      {
        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
        "phase": 1,
        "quantity": 4
      },
      {
        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
        "phase": 2,
        "quantity": 1
      }
    ]
  }'

Example response (201)

{
  "data": {
    "id": "1ccffee2-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "source": "native",
    "external_id": null,
    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
    "name": "Ortodoncia fija superior e inferior",
    "diagnosis": "Apiñamiento anterosuperior moderado",
    "professional_id": "18342d1b-0000-4000-8000-000000000001",
    "status": "draft",
    "accepted_at": null,
    "completed_at": null,
    "abandoned_reason": null,
    "created_at": "2026-09-22T22:21:15.213Z",
    "updated_at": "2026-09-22T22:21:15.213Z",
    "patient_name": "María José Fuentes Lagos",
    "professional_name": "Ana Rojas Vidal",
    "items": [
      {
        "id": "3a7d9767-0000-4000-8000-000000000002",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
        "phase": 1,
        "quantity": 4,
        "tooth_or_zone": null,
        "status": "pending",
        "appointment_id": null,
        "budget_item_id": null,
        "sort_order": 0,
        "created_at": "2026-09-22T22:21:15.215Z",
        "updated_at": "2026-09-22T22:21:15.215Z",
        "service_name": "Control de ortodoncia"
      },
      {
        "id": "3a7d9767-0000-4000-8000-000000000003",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
        "phase": 2,
        "quantity": 1,
        "tooth_or_zone": null,
        "status": "pending",
        "appointment_id": null,
        "budget_item_id": null,
        "sort_order": 1,
        "created_at": "2026-09-22T22:21:15.215Z",
        "updated_at": "2026-09-22T22:21:15.215Z",
        "service_name": "Limpieza dental"
      }
    ],
    "progress": {
      "pending": 5,
      "scheduled": 0,
      "performed": 0,
      "cancelled": 0,
      "active": 5,
      "percent": 0,
      "phases": [
        {
          "phase": 1,
          "pending": 4,
          "scheduled": 0,
          "performed": 0,
          "cancelled": 0,
          "active": 4,
          "percent": 0
        },
        {
          "phase": 2,
          "pending": 1,
          "scheduled": 0,
          "performed": 0,
          "cancelled": 0,
          "active": 1,
          "percent": 0
        }
      ],
      "current_phase": 1
    }
  }
}

Answers: 201 · 400 · 401 · 403 · 404 · 409 · 429

DELETE /clinic/treatment-plans/{id}

Delete a plan

Delete a plan

Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.

ParameterInTypeRequiredConstraints
idpathuuidyes
curl -X DELETE https://api.vitrinadev.com/api/v1/clinic/treatment-plans/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

Answers: 204 · 400 · 401 · 403 · 404 · 409 · 429

GET /clinic/treatment-plans/{id}

One plan with its phases and progress

One plan with its phases and progress

Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.

ParameterInTypeRequiredConstraints
idpathuuidyes
curl https://api.vitrinadev.com/api/v1/clinic/treatment-plans/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "id": "1ccffee2-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "source": "native",
    "external_id": null,
    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
    "name": "Ortodoncia fija superior e inferior",
    "diagnosis": "Apiñamiento anterosuperior moderado",
    "professional_id": "18342d1b-0000-4000-8000-000000000001",
    "status": "draft",
    "accepted_at": null,
    "completed_at": null,
    "abandoned_reason": null,
    "created_at": "2026-09-22T22:21:15.213Z",
    "updated_at": "2026-09-22T22:21:15.213Z",
    "patient_name": "María José Fuentes Lagos",
    "professional_name": "Ana Rojas Vidal",
    "items": [
      {
        "id": "3a7d9767-0000-4000-8000-000000000002",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
        "phase": 1,
        "quantity": 4,
        "tooth_or_zone": null,
        "status": "pending",
        "appointment_id": null,
        "budget_item_id": null,
        "sort_order": 0,
        "created_at": "2026-09-22T22:21:15.215Z",
        "updated_at": "2026-09-22T22:21:15.215Z",
        "service_name": "Control de ortodoncia"
      },
      {
        "id": "3a7d9767-0000-4000-8000-000000000003",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
        "phase": 2,
        "quantity": 1,
        "tooth_or_zone": null,
        "status": "pending",
        "appointment_id": null,
        "budget_item_id": null,
        "sort_order": 1,
        "created_at": "2026-09-22T22:21:15.215Z",
        "updated_at": "2026-09-22T22:21:15.215Z",
        "service_name": "Limpieza dental"
      }
    ],
    "progress": {
      "pending": 5,
      "scheduled": 0,
      "performed": 0,
      "cancelled": 0,
      "active": 5,
      "percent": 0,
      "phases": [
        {
          "phase": 1,
          "pending": 4,
          "scheduled": 0,
          "performed": 0,
          "cancelled": 0,
          "active": 4,
          "percent": 0
        },
        {
          "phase": 2,
          "pending": 1,
          "scheduled": 0,
          "performed": 0,
          "cancelled": 0,
          "active": 1,
          "percent": 0
        }
      ],
      "current_phase": 1
    }
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

PATCH /clinic/treatment-plans/{id}

Edit a plan

status: "abandoned" requires abandoned_reason — "why do plans die at phase 2" is only answerable if somebody wrote it.

Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.

ParameterInTypeRequiredConstraints
idpathuuidyes

Body

FieldTypeRequiredConstraints
namestring—mín. 1, máx. 200
diagnosisstring | null—máx. 2000
professional_idstring | null—
statusdraft \proposed \accepted \
abandoned_reasonstring | null—máx. 500
curl -X PATCH https://api.vitrinadev.com/api/v1/clinic/treatment-plans/<id> \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "proposed"
  }'

Example response (200)

{
  "data": {
    "id": "1ccffee2-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "source": "native",
    "external_id": null,
    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
    "name": "Ortodoncia fija superior e inferior",
    "diagnosis": "Apiñamiento anterosuperior moderado",
    "professional_id": "18342d1b-0000-4000-8000-000000000001",
    "status": "proposed",
    "accepted_at": null,
    "completed_at": null,
    "abandoned_reason": null,
    "created_at": "2026-09-22T22:21:15.213Z",
    "updated_at": "2026-09-22T22:21:15.285Z",
    "patient_name": "María José Fuentes Lagos",
    "professional_name": "Ana Rojas Vidal",
    "items": [
      {
        "id": "3a7d9767-0000-4000-8000-000000000002",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
        "phase": 1,
        "quantity": 4,
        "tooth_or_zone": null,
        "status": "pending",
        "appointment_id": null,
        "budget_item_id": null,
        "sort_order": 0,
        "created_at": "2026-09-22T22:21:15.215Z",
        "updated_at": "2026-09-22T22:21:15.215Z",
        "service_name": "Control de ortodoncia"
      },
      {
        "id": "3a7d9767-0000-4000-8000-000000000003",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
        "phase": 2,
        "quantity": 1,
        "tooth_or_zone": null,
        "status": "pending",
        "appointment_id": null,
        "budget_item_id": null,
        "sort_order": 1,
        "created_at": "2026-09-22T22:21:15.215Z",
        "updated_at": "2026-09-22T22:21:15.215Z",
        "service_name": "Limpieza dental"
      }
    ],
    "progress": {
      "pending": 5,
      "scheduled": 0,
      "performed": 0,
      "cancelled": 0,
      "active": 5,
      "percent": 0,
      "phases": [
        {
          "phase": 1,
          "pending": 4,
          "scheduled": 0,
          "performed": 0,
          "cancelled": 0,
          "active": 4,
          "percent": 0
        },
        {
          "phase": 2,
          "pending": 1,
          "scheduled": 0,
          "performed": 0,
          "cancelled": 0,
          "active": 1,
          "percent": 0
        }
      ],
      "current_phase": 1
    }
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/treatment-plans/{id}/items

Add prestaciones to an open plan

Clinical authoring (an odontograma finding, a new phase): accepts clinic_money:write or clinic_record:write. Items carry no price — a presupuesto prices them. A clinical-only caller must be able to open the patient's ficha (own patients). A completed or abandoned plan answers 409 clinic_plan_closed.

Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.

ParameterInTypeRequiredConstraints
idpathuuidyes

Body

FieldTypeRequiredConstraints
itemsobject[]yes
curl -X POST https://api.vitrinadev.com/api/v1/clinic/treatment-plans/<id>/items \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      {
        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
        "quantity": 1,
        "tooth_or_zone": {
          "fdi": [
            16
          ]
        }
      }
    ]
  }'

Example response (201)

{
  "data": {
    "id": "1ccffee2-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "source": "native",
    "external_id": null,
    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
    "name": "Ortodoncia fija superior e inferior",
    "diagnosis": "Apiñamiento anterosuperior moderado",
    "professional_id": "18342d1b-0000-4000-8000-000000000001",
    "status": "proposed",
    "accepted_at": null,
    "completed_at": null,
    "abandoned_reason": null,
    "created_at": "2026-09-22T22:21:15.213Z",
    "updated_at": "2026-09-22T22:21:15.285Z",
    "patient_name": "María José Fuentes Lagos",
    "professional_name": "Ana Rojas Vidal",
    "items": [
      {
        "id": "3a7d9767-0000-4000-8000-000000000002",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
        "phase": 1,
        "quantity": 4,
        "tooth_or_zone": null,
        "status": "scheduled",
        "appointment_id": "d93e8eec-0000-4000-8000-000000000001",
        "budget_item_id": null,
        "sort_order": 0,
        "created_at": "2026-09-22T22:21:15.215Z",
        "updated_at": "2026-09-22T22:21:15.337Z",
        "service_name": "Control de ortodoncia"
      },
      {
        "id": "3a7d9767-0000-4000-8000-000000000003",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
        "phase": 2,
        "quantity": 1,
        "tooth_or_zone": null,
        "status": "pending",
        "appointment_id": null,
        "budget_item_id": null,
        "sort_order": 1,
        "created_at": "2026-09-22T22:21:15.215Z",
        "updated_at": "2026-09-22T22:21:15.215Z",
        "service_name": "Limpieza dental"
      }
    ],
    "progress": {
      "pending": 1,
      "scheduled": 4,
      "performed": 0,
      "cancelled": 0,
      "active": 5,
      "percent": 0,
      "phases": [
        {
          "phase": 1,
          "pending": 0,
          "scheduled": 4,
          "performed": 0,
          "cancelled": 0,
          "active": 4,
          "percent": 0
        },
        {
          "phase": 2,
          "pending": 1,
          "scheduled": 0,
          "performed": 0,
          "cancelled": 0,
          "active": 1,
          "percent": 0
        }
      ],
      "current_phase": 1
    }
  }
}

Answers: 201 · 400 · 401 · 403 · 404 · 409 · 429

On this page