VitrinaAPI

Clinic supplies inventory

The clinic's supplies with their stock, minimum and last price; every entry, exit and count is recorded with who, when and why, and the ones below their minimum show as «Por reponer».

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.

MethodPathWhat it does
GET/clinic/suppliesList insumos
POST/clinic/suppliesAdd an insumo
GET/clinic/supplies/{id}Get an insumo
PATCH/clinic/supplies/{id}Edit an insumo
POST/clinic/supplies/{id}/archiveArchive an insumo
GET/clinic/supplies/{id}/movementsAn insumo’s movement history
POST/clinic/supplies/{id}/movementsRecord a manual entry or exit
POST/clinic/supplies/{id}/restoreRestore an archived insumo
GET/clinic/supplies/{id}/service-quantitiesQuantity this insumo consumes per prestación
DELETE/clinic/supplies/{id}/service-quantities/{service_id}Stop consuming this insumo for a prestación
PUT/clinic/supplies/{id}/service-quantities/{service_id}Set the quantity a prestación consumes of this insumo
POST/clinic/supplies/countsRecord a periodic count
GET/clinic/supplies/unmapped-itemsPurchase-invoice items still asking «¿qué insumo es?»
POST/clinic/supplies/unmapped-items/resolveSay which insumo a supplier item is
GET/clinic/supply-ordersList supplier orders
POST/clinic/supply-ordersWrite a supplier order by hand
DELETE/clinic/supply-orders/{id}Delete a draft order
GET/clinic/supply-orders/{id}One supplier order
PATCH/clinic/supply-orders/{id}Edit a draft order
POST/clinic/supply-orders/{id}/cancelCancel an order
POST/clinic/supply-orders/{id}/closeClose an order with what arrived
DELETE/clinic/supply-orders/{id}/invoiceUnlink the purchase invoice from an open order
PUT/clinic/supply-orders/{id}/invoiceReceive an order against a purchase invoice
GET/clinic/supply-orders/{id}/invoice-candidatesPurchase invoices an order could be received against
GET/clinic/supply-orders/{id}/pdfThe order as a PDF for the supplier
POST/clinic/supply-orders/{id}/receiptsReceive an order by hand
POST/clinic/supply-orders/{id}/sendMark an order as sent
POST/clinic/supply-orders/draftDraft the orders from «Por reponer»
GET/clinic/supply-orders/suggestionsSuggested supplier orders from «Por reponer»

GET /clinic/supplies

List insumos

The clinic’s insumos with stock on hand, unit, minimum, last purchase price and supplier, plus totals (active, archived, below minimum). A clinic’s first open materialises the starter catalogue (general consumables) — never a migration, so an archived insumo is not handed back. below_minimum=true is the «Por reponer» filter.

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
statusqueryactive \archived \all
below_minimumquerytrue \falseno
qquerystringnomáx. 120
curl https://api.vitrinadev.com/api/v1/clinic/supplies \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "items": [
      {
        "id": "5a5a5a5a-0000-4000-8000-000000000001",
        "name": "Guantes de examen",
        "unit": "par",
        "category": "Desechables",
        "min_stock": 200,
        "stock_on_hand": 150,
        "below_minimum": true,
        "last_unit_cost_clp": 198,
        "last_purchase_on": "2026-10-05",
        "supplier": "Dental Proveedor SpA",
        "supplier_tax_id": "76543210-K",
        "archived_at": null,
        "created_at": "2026-10-01T14:00:00.000Z",
        "updated_at": "2026-10-05T15:20:00.000Z"
      }
    ],
    "totals": {
      "active": 11,
      "archived": 0,
      "below_minimum": 1
    }
  }
}

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

POST /clinic/supplies

Add an insumo

initial_stock is recorded as a count so the ledger explains it. 409 when a live insumo already has that name.

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
namestringyesmín. 1, máx. 120
unitstring—mín. 1, máx. 30
categorystring | null—máx. 60
min_stocknumber—≥ 0, ≤ 999999999
supplierstring | null—máx. 200
supplier_tax_idstring | null—máx. 20
initial_stocknumber—≥ 0, ≤ 999999999
curl -X POST https://api.vitrinadev.com/api/v1/clinic/supplies \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Hilo dental",
    "unit": "caja",
    "category": "Insumos de box",
    "min_stock": 5,
    "initial_stock": 12
  }'

Example response (201)

{
  "data": {
    "id": "5a5a5a5a-0000-4000-8000-000000000002",
    "name": "Hilo dental",
    "unit": "caja",
    "category": "Insumos de box",
    "min_stock": 5,
    "stock_on_hand": 12,
    "below_minimum": false,
    "last_unit_cost_clp": null,
    "last_purchase_on": null,
    "supplier": null,
    "supplier_tax_id": null,
    "archived_at": null,
    "created_at": "2026-10-01T14:00:00.000Z",
    "updated_at": "2026-10-05T15:20:00.000Z"
  }
}

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

GET /clinic/supplies/{id}

Get an insumo

Get an insumo

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/supplies/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "id": "5a5a5a5a-0000-4000-8000-000000000001",
    "name": "Guantes de examen",
    "unit": "par",
    "category": "Desechables",
    "min_stock": 200,
    "stock_on_hand": 150,
    "below_minimum": true,
    "last_unit_cost_clp": 198,
    "last_purchase_on": "2026-10-05",
    "supplier": "Dental Proveedor SpA",
    "supplier_tax_id": "76543210-K",
    "archived_at": null,
    "created_at": "2026-10-01T14:00:00.000Z",
    "updated_at": "2026-10-05T15:20:00.000Z"
  }
}

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

PATCH /clinic/supplies/{id}

Edit an insumo

Name, unit, category, minimum and supplier. Stock is never edited here: it moves by movements and counts.

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. 120
unitstring—mín. 1, máx. 30
categorystring | null—máx. 60
min_stocknumber—≥ 0, ≤ 999999999
supplierstring | null—máx. 200
supplier_tax_idstring | null—máx. 20
curl -X PATCH https://api.vitrinadev.com/api/v1/clinic/supplies/<id> \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "min_stock": 300
  }'

Example response (200)

{
  "data": {
    "id": "5a5a5a5a-0000-4000-8000-000000000001",
    "name": "Guantes de examen",
    "unit": "par",
    "category": "Desechables",
    "min_stock": 300,
    "stock_on_hand": 150,
    "below_minimum": true,
    "last_unit_cost_clp": 198,
    "last_purchase_on": "2026-10-05",
    "supplier": "Dental Proveedor SpA",
    "supplier_tax_id": "76543210-K",
    "archived_at": null,
    "created_at": "2026-10-01T14:00:00.000Z",
    "updated_at": "2026-10-05T15:20:00.000Z"
  }
}

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

POST /clinic/supplies/{id}/archive

Archive an insumo

Soft: its movements keep pointing at it and it leaves the list and the alert. Reversible with restore.

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 POST https://api.vitrinadev.com/api/v1/clinic/supplies/<id>/archive \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "id": "5a5a5a5a-0000-4000-8000-000000000001",
    "name": "Guantes de examen",
    "unit": "par",
    "category": "Desechables",
    "min_stock": 200,
    "stock_on_hand": 150,
    "below_minimum": true,
    "last_unit_cost_clp": 198,
    "last_purchase_on": "2026-10-05",
    "supplier": "Dental Proveedor SpA",
    "supplier_tax_id": "76543210-K",
    "archived_at": "2026-10-09T12:00:00.000Z",
    "created_at": "2026-10-01T14:00:00.000Z",
    "updated_at": "2026-10-05T15:20:00.000Z"
  }
}

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

GET /clinic/supplies/{id}/movements

An insumo’s movement history

Newest first: who (actor_user_id), when (occurred_at), why (kind + reason), the delta and the stock after. Cursor-paged. A consumption movement carries consumption: the prestación and the atención (cita, or the mirrored presupuesto) it came from, and reversal when it put the quantity back.

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
limitqueryintegerno≥ 1, ≤ 200, por defecto 50
cursorquerystringnomáx. 200
curl https://api.vitrinadev.com/api/v1/clinic/supplies/<id>/movements \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "items": [
      {
        "id": "5b5b5b5b-0000-4000-8000-000000000001",
        "supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
        "kind": "purchase",
        "delta": 150,
        "stock_after": 150,
        "unit_cost_clp": 198,
        "reason": "GUANTES NITRILO CAJA 100",
        "actor_user_id": null,
        "expense_id": "5c5c5c5c-0000-4000-8000-000000000001",
        "occurred_at": "2026-10-05T15:20:00.000Z",
        "consumption": null
      }
    ],
    "next_cursor": null
  }
}

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

POST /clinic/supplies/{id}/movements

Record a manual entry or exit

An exit needs a reason (merma, vencimiento, préstamo…) and cannot take more than is on hand (409); a count corrects the difference.

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
directionentry \exityes
quantitynumberyes> 0, ≤ 999999999
reasonstring | null—máx. 300
unit_cost_clpnumber | null—≥ 0, ≤ 999999999
curl -X POST https://api.vitrinadev.com/api/v1/clinic/supplies/<id>/movements \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "direction": "exit",
    "quantity": 10,
    "reason": "Vencimiento"
  }'

Example response (201)

{
  "data": {
    "movement": {
      "id": "5b5b5b5b-0000-4000-8000-000000000001",
      "supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
      "kind": "manual_exit",
      "delta": -10,
      "stock_after": 140,
      "unit_cost_clp": null,
      "reason": "Vencimiento",
      "actor_user_id": "20000000-0000-4000-8000-000000000001",
      "expense_id": null,
      "occurred_at": "2026-10-05T15:20:00.000Z",
      "consumption": null
    },
    "supply": {
      "id": "5a5a5a5a-0000-4000-8000-000000000001",
      "name": "Guantes de examen",
      "unit": "par",
      "category": "Desechables",
      "min_stock": 200,
      "stock_on_hand": 140,
      "below_minimum": true,
      "last_unit_cost_clp": 198,
      "last_purchase_on": "2026-10-05",
      "supplier": "Dental Proveedor SpA",
      "supplier_tax_id": "76543210-K",
      "archived_at": null,
      "created_at": "2026-10-01T14:00:00.000Z",
      "updated_at": "2026-10-05T15:20:00.000Z"
    }
  }
}

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

POST /clinic/supplies/{id}/restore

Restore an archived insumo

Restore an archived insumo

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 POST https://api.vitrinadev.com/api/v1/clinic/supplies/<id>/restore \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "id": "5a5a5a5a-0000-4000-8000-000000000001",
    "name": "Guantes de examen",
    "unit": "par",
    "category": "Desechables",
    "min_stock": 200,
    "stock_on_hand": 150,
    "below_minimum": true,
    "last_unit_cost_clp": 198,
    "last_purchase_on": "2026-10-05",
    "supplier": "Dental Proveedor SpA",
    "supplier_tax_id": "76543210-K",
    "archived_at": null,
    "created_at": "2026-10-01T14:00:00.000Z",
    "updated_at": "2026-10-05T15:20:00.000Z"
  }
}

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

GET /clinic/supplies/{id}/service-quantities

Quantity this insumo consumes per prestación

The optional quantities that make a performed prestación take this insumo out of stock. An insumo with none is never touched by a prestación and keeps the periodic count.

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/supplies/<id>/service-quantities \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "items": [
      {
        "id": "5d5d5d5d-0000-4000-8000-000000000001",
        "supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
        "clinic_service_id": "5e5e5e5e-0000-4000-8000-000000000001",
        "service_name": "Limpieza dental",
        "quantity": 2,
        "effective_from": "2026-10-10T09:00:00.000Z",
        "updated_at": "2026-10-10T09:00:00.000Z"
      }
    ]
  }
}

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

DELETE /clinic/supplies/{id}/service-quantities/{service_id}

Stop consuming this insumo for a prestación

Removes the quantity. What was already consumed stays in the history.

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
service_idpathuuidyes
curl -X DELETE https://api.vitrinadev.com/api/v1/clinic/supplies/<id>/service-quantities/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "removed": true
  }
}

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

PUT /clinic/supplies/{id}/service-quantities/{service_id}

Set the quantity a prestación consumes of this insumo

Per ONE performance of the prestación, in the insumo’s unit (a plan item of quantity 3 consumes 3×). Performing the prestación records a consumption movement linked to the atención; un-performing it puts the quantity back; a line is never counted twice, including mirrored (Dentalink) lines on every re-sync. A NEW quantity counts from now on: work performed before it is never consumed. Stock cannot go negative: a taking larger than the stock on hand is clamped and says so in the movement’s reason. 404 for an unknown insumo or prestación, 409 when the insumo is archived.

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
service_idpathuuidyes

Body

FieldTypeRequiredConstraints
quantitynumberyes> 0, ≤ 999999999
curl -X PUT https://api.vitrinadev.com/api/v1/clinic/supplies/<id>/service-quantities/<id> \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "quantity": 2
  }'

Example response (200)

{
  "data": {
    "id": "5d5d5d5d-0000-4000-8000-000000000001",
    "supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
    "clinic_service_id": "5e5e5e5e-0000-4000-8000-000000000001",
    "service_name": "Limpieza dental",
    "quantity": 2,
    "effective_from": "2026-10-10T09:00:00.000Z",
    "updated_at": "2026-10-10T09:00:00.000Z"
  }
}

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

POST /clinic/supplies/counts

Record a periodic count

Sets the stock of each counted insumo and records the difference as a count movement. All or nothing.

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
countsobject[]yes
reasonstring | null—máx. 300
curl -X POST https://api.vitrinadev.com/api/v1/clinic/supplies/counts \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "counts": [
      {
        "supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
        "counted_quantity": 320
      }
    ],
    "reason": "Conteo de octubre"
  }'

Example response (200)

{
  "data": {
    "results": [
      {
        "movement": {
          "id": "5b5b5b5b-0000-4000-8000-000000000001",
          "supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
          "kind": "count",
          "delta": 170,
          "stock_after": 320,
          "unit_cost_clp": null,
          "reason": "Conteo de octubre",
          "actor_user_id": null,
          "expense_id": null,
          "occurred_at": "2026-10-05T15:20:00.000Z",
          "consumption": null
        },
        "supply": {
          "id": "5a5a5a5a-0000-4000-8000-000000000001",
          "name": "Guantes de examen",
          "unit": "par",
          "category": "Desechables",
          "min_stock": 200,
          "stock_on_hand": 320,
          "below_minimum": false,
          "last_unit_cost_clp": 198,
          "last_purchase_on": "2026-10-05",
          "supplier": "Dental Proveedor SpA",
          "supplier_tax_id": "76543210-K",
          "archived_at": null,
          "created_at": "2026-10-01T14:00:00.000Z",
          "updated_at": "2026-10-05T15:20:00.000Z"
        }
      }
    ]
  }
}

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

GET /clinic/supplies/unmapped-items

Purchase-invoice items still asking «¿qué insumo es?»

Pending lines of confirmed purchase invoices, one row per (supplier, item code). The question is asked ONCE; after it is answered the same supplier item applies automatically. If a purchase feed carries totals only (no lines), nothing appears here and manual entry covers 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.

curl https://api.vitrinadev.com/api/v1/clinic/supplies/unmapped-items \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "items": [
      {
        "supplier_key": "76543210-K",
        "supplier_name": "Dental Proveedor SpA",
        "item_key": "code:gn-100",
        "item_code": "GN-100",
        "description": "GUANTES NITRILO CAJA 100",
        "lines": 1,
        "total_quantity": 3,
        "last_unit_price_clp": 9900,
        "last_seen_on": "2026-10-05"
      }
    ]
  }
}

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

POST /clinic/supplies/unmapped-items/resolve

Say which insumo a supplier item is

Send exactly one of supply_id (an existing insumo), new_supply (create it now) or ignore (not an insumo: a service, freight). Pending lines of that supplier item are applied (stock goes up by quantity × units_per_item) and future ones apply automatically.

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
supplier_keystringyesmín. 1, máx. 200
item_keystringyesmín. 1, máx. 300
units_per_itemnumber—> 0, ≤ 1000000
supply_iduuid—
new_supplyobject—
ignoretrue—
curl -X POST https://api.vitrinadev.com/api/v1/clinic/supplies/unmapped-items/resolve \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "supplier_key": "76543210-K",
    "item_key": "code:gn-100",
    "supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
    "units_per_item": 50
  }'

Example response (200)

{
  "data": {
    "supply": {
      "id": "5a5a5a5a-0000-4000-8000-000000000001",
      "name": "Guantes de examen",
      "unit": "par",
      "category": "Desechables",
      "min_stock": 200,
      "stock_on_hand": 150,
      "below_minimum": true,
      "last_unit_cost_clp": 198,
      "last_purchase_on": "2026-10-05",
      "supplier": "Dental Proveedor SpA",
      "supplier_tax_id": "76543210-K",
      "archived_at": null,
      "created_at": "2026-10-01T14:00:00.000Z",
      "updated_at": "2026-10-05T15:20:00.000Z"
    },
    "applied_lines": 1,
    "ignored_lines": 0
  }
}

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

GET /clinic/supply-orders

List supplier orders

Newest first. status is draft, sent, partially_received, received, closed or cancelled; the two received states are derived from the ledger. Each order carries its lines with ordered, received and the difference.

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 \partially_received \
limitqueryintegerno≥ 1, ≤ 200, por defecto 100
curl https://api.vitrinadev.com/api/v1/clinic/supply-orders \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "items": [
      {
        "id": "5d5d5d5d-0000-4000-8000-000000000001",
        "number": 7,
        "status": "partially_received",
        "supplier_name": "Dental Proveedor SpA",
        "supplier_tax_id": "76543210-K",
        "notes": null,
        "expected_on": "2026-10-14",
        "sent_at": "2026-10-09T15:00:00.000Z",
        "closed_at": null,
        "cancelled_at": null,
        "created_at": "2026-10-09T14:30:00.000Z",
        "updated_at": "2026-10-12T12:00:00.000Z",
        "lines": [
          {
            "id": "5e5e5e5e-0000-4000-8000-000000000001",
            "supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
            "supply_name": "Guantes de examen",
            "unit": "par",
            "quantity_ordered": 300,
            "quantity_received": 150,
            "difference": -150,
            "unit_cost_clp": 198
          }
        ],
        "totals": {
          "lines": 1,
          "ordered": 300,
          "received": 150,
          "estimated_total_clp": 59400
        },
        "invoice": {
          "expense_id": "5c5c5c5c-0000-4000-8000-000000000001",
          "document_number": "F-2210",
          "supplier": "Dental Proveedor SpA",
          "spent_on": "2026-10-12",
          "amount_clp": 29700,
          "pending_lines": 0
        },
        "received_by_hand": false
      }
    ]
  }
}

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

POST /clinic/supply-orders

Write a supplier order by hand

A draft for one supplier. 400 when an insumo does not exist or is archived. An order never moves stock by itself: goods enter through the purchase invoice or a manual receipt, and the order reads what arrived from the ledger.

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
supplier_namestring | null—máx. 200
supplier_tax_idstring | null—máx. 20
notesstring | null—máx. 1000
expected_onstring | null—patrón ^\d{4}-\d{2}-\d{2}$
linesobject[]yes
curl -X POST https://api.vitrinadev.com/api/v1/clinic/supply-orders \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "supplier_name": "Dental Proveedor SpA",
    "supplier_tax_id": "76.543.210-K",
    "expected_on": "2026-10-14",
    "lines": [
      {
        "supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
        "quantity": 300
      }
    ]
  }'

Example response (201)

{
  "data": {
    "id": "5d5d5d5d-0000-4000-8000-000000000001",
    "number": 7,
    "status": "draft",
    "supplier_name": "Dental Proveedor SpA",
    "supplier_tax_id": "76543210-K",
    "notes": null,
    "expected_on": "2026-10-14",
    "sent_at": null,
    "closed_at": null,
    "cancelled_at": null,
    "created_at": "2026-10-09T14:30:00.000Z",
    "updated_at": "2026-10-12T12:00:00.000Z",
    "lines": [
      {
        "id": "5e5e5e5e-0000-4000-8000-000000000001",
        "supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
        "supply_name": "Guantes de examen",
        "unit": "par",
        "quantity_ordered": 300,
        "quantity_received": 150,
        "difference": -150,
        "unit_cost_clp": 198
      }
    ],
    "totals": {
      "lines": 1,
      "ordered": 300,
      "received": 150,
      "estimated_total_clp": 59400
    },
    "invoice": null,
    "received_by_hand": false
  }
}

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

DELETE /clinic/supply-orders/{id}

Delete a draft order

Drafts only; a sent order is cancelled instead.

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/supply-orders/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "removed": true
  }
}

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

GET /clinic/supply-orders/{id}

One supplier order

With its lines (ordered, received, difference), the purchase invoice it was received against and whether it was received by hand.

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/supply-orders/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "id": "5d5d5d5d-0000-4000-8000-000000000001",
    "number": 7,
    "status": "partially_received",
    "supplier_name": "Dental Proveedor SpA",
    "supplier_tax_id": "76543210-K",
    "notes": null,
    "expected_on": "2026-10-14",
    "sent_at": "2026-10-09T15:00:00.000Z",
    "closed_at": null,
    "cancelled_at": null,
    "created_at": "2026-10-09T14:30:00.000Z",
    "updated_at": "2026-10-12T12:00:00.000Z",
    "lines": [
      {
        "id": "5e5e5e5e-0000-4000-8000-000000000001",
        "supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
        "supply_name": "Guantes de examen",
        "unit": "par",
        "quantity_ordered": 300,
        "quantity_received": 150,
        "difference": -150,
        "unit_cost_clp": 198
      }
    ],
    "totals": {
      "lines": 1,
      "ordered": 300,
      "received": 150,
      "estimated_total_clp": 59400
    },
    "invoice": {
      "expense_id": "5c5c5c5c-0000-4000-8000-000000000001",
      "document_number": "F-2210",
      "supplier": "Dental Proveedor SpA",
      "spent_on": "2026-10-12",
      "amount_clp": 29700,
      "pending_lines": 0
    },
    "received_by_hand": false
  }
}

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

PATCH /clinic/supply-orders/{id}

Edit a draft order

Supplier, notes, expected date and (replacing the whole list) lines. 409 once the order was sent.

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
supplier_namestring | null—máx. 200
supplier_tax_idstring | null—máx. 20
notesstring | null—máx. 1000
expected_onstring | null—patrón ^\d{4}-\d{2}-\d{2}$
linesobject[]—
curl -X PATCH https://api.vitrinadev.com/api/v1/clinic/supply-orders/<id> \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "notes": "Entregar en recepción",
    "lines": [
      {
        "supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
        "quantity": 300
      }
    ]
  }'

Example response (200)

{
  "data": {
    "id": "5d5d5d5d-0000-4000-8000-000000000001",
    "number": 7,
    "status": "draft",
    "supplier_name": "Dental Proveedor SpA",
    "supplier_tax_id": "76543210-K",
    "notes": "Entregar en recepción",
    "expected_on": "2026-10-14",
    "sent_at": null,
    "closed_at": null,
    "cancelled_at": null,
    "created_at": "2026-10-09T14:30:00.000Z",
    "updated_at": "2026-10-12T12:00:00.000Z",
    "lines": [
      {
        "id": "5e5e5e5e-0000-4000-8000-000000000001",
        "supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
        "supply_name": "Guantes de examen",
        "unit": "par",
        "quantity_ordered": 300,
        "quantity_received": 150,
        "difference": -150,
        "unit_cost_clp": 198
      }
    ],
    "totals": {
      "lines": 1,
      "ordered": 300,
      "received": 150,
      "estimated_total_clp": 59400
    },
    "invoice": null,
    "received_by_hand": false
  }
}

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

POST /clinic/supply-orders/{id}/cancel

Cancel an order

A draft or a sent order that has received nothing. One that already received goods is closed instead (409).

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 POST https://api.vitrinadev.com/api/v1/clinic/supply-orders/<id>/cancel \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "id": "5d5d5d5d-0000-4000-8000-000000000001",
    "number": 7,
    "status": "cancelled",
    "supplier_name": "Dental Proveedor SpA",
    "supplier_tax_id": "76543210-K",
    "notes": null,
    "expected_on": "2026-10-14",
    "sent_at": "2026-10-09T15:00:00.000Z",
    "closed_at": null,
    "cancelled_at": "2026-10-10T12:00:00.000Z",
    "created_at": "2026-10-09T14:30:00.000Z",
    "updated_at": "2026-10-12T12:00:00.000Z",
    "lines": [
      {
        "id": "5e5e5e5e-0000-4000-8000-000000000001",
        "supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
        "supply_name": "Guantes de examen",
        "unit": "par",
        "quantity_ordered": 300,
        "quantity_received": 150,
        "difference": -150,
        "unit_cost_clp": 198
      }
    ],
    "totals": {
      "lines": 1,
      "ordered": 300,
      "received": 150,
      "estimated_total_clp": 59400
    },
    "invoice": null,
    "received_by_hand": false
  }
}

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

POST /clinic/supply-orders/{id}/close

Close an order with what arrived

Stop waiting for the rest: a short delivery is accepted and the difference stays visible on the order. Stock is untouched.

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 POST https://api.vitrinadev.com/api/v1/clinic/supply-orders/<id>/close \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "id": "5d5d5d5d-0000-4000-8000-000000000001",
    "number": 7,
    "status": "closed",
    "supplier_name": "Dental Proveedor SpA",
    "supplier_tax_id": "76543210-K",
    "notes": null,
    "expected_on": "2026-10-14",
    "sent_at": "2026-10-09T15:00:00.000Z",
    "closed_at": "2026-10-15T12:00:00.000Z",
    "cancelled_at": null,
    "created_at": "2026-10-09T14:30:00.000Z",
    "updated_at": "2026-10-12T12:00:00.000Z",
    "lines": [
      {
        "id": "5e5e5e5e-0000-4000-8000-000000000001",
        "supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
        "supply_name": "Guantes de examen",
        "unit": "par",
        "quantity_ordered": 300,
        "quantity_received": 150,
        "difference": -150,
        "unit_cost_clp": 198
      }
    ],
    "totals": {
      "lines": 1,
      "ordered": 300,
      "received": 150,
      "estimated_total_clp": 59400
    },
    "invoice": {
      "expense_id": "5c5c5c5c-0000-4000-8000-000000000001",
      "document_number": "F-2210",
      "supplier": "Dental Proveedor SpA",
      "spent_on": "2026-10-12",
      "amount_clp": 29700,
      "pending_lines": 0
    },
    "received_by_hand": false
  }
}

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

DELETE /clinic/supply-orders/{id}/invoice

Unlink the purchase invoice from an open order

The invoice’s stock stays; the order simply stops reading 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
curl -X DELETE https://api.vitrinadev.com/api/v1/clinic/supply-orders/<id>/invoice \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "id": "5d5d5d5d-0000-4000-8000-000000000001",
    "number": 7,
    "status": "sent",
    "supplier_name": "Dental Proveedor SpA",
    "supplier_tax_id": "76543210-K",
    "notes": null,
    "expected_on": "2026-10-14",
    "sent_at": "2026-10-09T15:00:00.000Z",
    "closed_at": null,
    "cancelled_at": null,
    "created_at": "2026-10-09T14:30:00.000Z",
    "updated_at": "2026-10-12T12:00:00.000Z",
    "lines": [
      {
        "id": "5e5e5e5e-0000-4000-8000-000000000001",
        "supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
        "supply_name": "Guantes de examen",
        "unit": "par",
        "quantity_ordered": 300,
        "quantity_received": 150,
        "difference": -150,
        "unit_cost_clp": 198
      }
    ],
    "totals": {
      "lines": 1,
      "ordered": 300,
      "received": 150,
      "estimated_total_clp": 59400
    },
    "invoice": null,
    "received_by_hand": false
  }
}

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

PUT /clinic/supply-orders/{id}/invoice

Receive an order against a purchase invoice

Links the invoice to the order. Writes NO movement: the invoice’s own lines already moved (or will move, once mapped) the stock, and the order reads those movements as what it received, so nothing is counted twice. 409 when the order was already received by hand, the invoice has no insumo lines or it is already another order’s receipt.

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
expense_iduuidyes
curl -X PUT https://api.vitrinadev.com/api/v1/clinic/supply-orders/<id>/invoice \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "expense_id": "5c5c5c5c-0000-4000-8000-000000000001"
  }'

Example response (200)

{
  "data": {
    "id": "5d5d5d5d-0000-4000-8000-000000000001",
    "number": 7,
    "status": "partially_received",
    "supplier_name": "Dental Proveedor SpA",
    "supplier_tax_id": "76543210-K",
    "notes": null,
    "expected_on": "2026-10-14",
    "sent_at": "2026-10-09T15:00:00.000Z",
    "closed_at": null,
    "cancelled_at": null,
    "created_at": "2026-10-09T14:30:00.000Z",
    "updated_at": "2026-10-12T12:00:00.000Z",
    "lines": [
      {
        "id": "5e5e5e5e-0000-4000-8000-000000000001",
        "supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
        "supply_name": "Guantes de examen",
        "unit": "par",
        "quantity_ordered": 300,
        "quantity_received": 150,
        "difference": -150,
        "unit_cost_clp": 198
      }
    ],
    "totals": {
      "lines": 1,
      "ordered": 300,
      "received": 150,
      "estimated_total_clp": 59400
    },
    "invoice": {
      "expense_id": "5c5c5c5c-0000-4000-8000-000000000001",
      "document_number": "F-2210",
      "supplier": "Dental Proveedor SpA",
      "spent_on": "2026-10-12",
      "amount_clp": 29700,
      "pending_lines": 0
    },
    "received_by_hand": false
  }
}

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

GET /clinic/supply-orders/{id}/invoice-candidates

Purchase invoices an order could be received against

Recent invoices (180 days) with insumo lines from the same supplier or for insumos on the order, not already the receipt of another order. pending_lines are lines still asking «¿qué insumo es?».

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/supply-orders/<id>/invoice-candidates \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "items": [
      {
        "expense_id": "5c5c5c5c-0000-4000-8000-000000000001",
        "document_number": "F-2210",
        "supplier": "Dental Proveedor SpA",
        "spent_on": "2026-10-12",
        "amount_clp": 29700,
        "pending_lines": 0,
        "matching_lines": 1
      }
    ]
  }
}

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

GET /clinic/supply-orders/{id}/pdf

The order as a PDF for the supplier

Insumo, quantity and unit, the clinic, the date and the notes; none of the clinic’s stock levels or prices.

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/supply-orders/<id>/pdf \
  -H "Authorization: Bearer $VITRINA_KEY"

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

POST /clinic/supply-orders/{id}/receipts

Receive an order by hand

The goods are here and the invoice is not: each line raises the stock as an order_receipt movement tied to the order. 409 when the order is received against an invoice (that would count the goods twice) or is not open.

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
linesobject[]yes
notestring | null—máx. 300
curl -X POST https://api.vitrinadev.com/api/v1/clinic/supply-orders/<id>/receipts \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "lines": [
      {
        "line_id": "5e5e5e5e-0000-4000-8000-000000000001",
        "quantity": 150
      }
    ],
    "note": "Guía de despacho 8841"
  }'

Example response (200)

{
  "data": {
    "id": "5d5d5d5d-0000-4000-8000-000000000001",
    "number": 7,
    "status": "partially_received",
    "supplier_name": "Dental Proveedor SpA",
    "supplier_tax_id": "76543210-K",
    "notes": null,
    "expected_on": "2026-10-14",
    "sent_at": "2026-10-09T15:00:00.000Z",
    "closed_at": null,
    "cancelled_at": null,
    "created_at": "2026-10-09T14:30:00.000Z",
    "updated_at": "2026-10-12T12:00:00.000Z",
    "lines": [
      {
        "id": "5e5e5e5e-0000-4000-8000-000000000001",
        "supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
        "supply_name": "Guantes de examen",
        "unit": "par",
        "quantity_ordered": 300,
        "quantity_received": 150,
        "difference": -150,
        "unit_cost_clp": 198
      }
    ],
    "totals": {
      "lines": 1,
      "ordered": 300,
      "received": 150,
      "estimated_total_clp": 59400
    },
    "invoice": null,
    "received_by_hand": true
  }
}

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

POST /clinic/supply-orders/{id}/send

Mark an order as sent

Freezes the lines. Needs a supplier name and at least one line. The PDF (GET …/pdf) is the sheet to send. An order never moves stock by itself: goods enter through the purchase invoice or a manual receipt, and the order reads what arrived from the ledger.

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 POST https://api.vitrinadev.com/api/v1/clinic/supply-orders/<id>/send \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "id": "5d5d5d5d-0000-4000-8000-000000000001",
    "number": 7,
    "status": "sent",
    "supplier_name": "Dental Proveedor SpA",
    "supplier_tax_id": "76543210-K",
    "notes": null,
    "expected_on": "2026-10-14",
    "sent_at": "2026-10-09T15:00:00.000Z",
    "closed_at": null,
    "cancelled_at": null,
    "created_at": "2026-10-09T14:30:00.000Z",
    "updated_at": "2026-10-12T12:00:00.000Z",
    "lines": [
      {
        "id": "5e5e5e5e-0000-4000-8000-000000000001",
        "supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
        "supply_name": "Guantes de examen",
        "unit": "par",
        "quantity_ordered": 300,
        "quantity_received": 150,
        "difference": -150,
        "unit_cost_clp": 198
      }
    ],
    "totals": {
      "lines": 1,
      "ordered": 300,
      "received": 150,
      "estimated_total_clp": 59400
    },
    "invoice": null,
    "received_by_hand": false
  }
}

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

POST /clinic/supply-orders/draft

Draft the orders from «Por reponer»

One draft per supplier with the suggested quantities (supply_ids narrows it). A supplier that already has an open draft gets the missing insumos added to it; quantities already on it are never overwritten. Insumos without a supplier go on a draft with no supplier name, to be named before sending. An order never moves stock by itself: goods enter through the purchase invoice or a manual receipt, and the order reads what arrived from the ledger.

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
coverage_daysinteger—≥ 0, ≤ 365, por defecto 30
supply_idsuuid[]—
curl -X POST https://api.vitrinadev.com/api/v1/clinic/supply-orders/draft \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "coverage_days": 30
  }'

Example response (200)

{
  "data": {
    "orders": [
      {
        "id": "5d5d5d5d-0000-4000-8000-000000000001",
        "number": 7,
        "status": "draft",
        "supplier_name": "Dental Proveedor SpA",
        "supplier_tax_id": "76543210-K",
        "notes": null,
        "expected_on": "2026-10-14",
        "sent_at": null,
        "closed_at": null,
        "cancelled_at": null,
        "created_at": "2026-10-09T14:30:00.000Z",
        "updated_at": "2026-10-12T12:00:00.000Z",
        "lines": [
          {
            "id": "5e5e5e5e-0000-4000-8000-000000000001",
            "supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
            "supply_name": "Guantes de examen",
            "unit": "par",
            "quantity_ordered": 245,
            "quantity_received": 0,
            "difference": -245,
            "unit_cost_clp": 198
          }
        ],
        "totals": {
          "lines": 1,
          "ordered": 245,
          "received": 0,
          "estimated_total_clp": 48510
        },
        "invoice": null,
        "received_by_hand": false
      }
    ],
    "nothing_to_order": false
  }
}

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

GET /clinic/supply-orders/suggestions

Suggested supplier orders from «Por reponer»

The insumos below their minimum, grouped by supplier, each with a suggested quantity: minimum + daily consumption (last 60 days, from the prestaciones performed) × coverage_days, or twice the minimum when there is no consumption, minus what is already on sent orders not yet received. open_draft_id is the draft «Crear pedido» would extend.

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
coverage_daysqueryinteger | nullno≥ 0, ≤ 365, por defecto 30
curl https://api.vitrinadev.com/api/v1/clinic/supply-orders/suggestions \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "items": [
      {
        "supplier_key": "76543210-K",
        "supplier_name": "Dental Proveedor SpA",
        "supplier_tax_id": "76543210-K",
        "open_draft_id": null,
        "lines": [
          {
            "supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
            "name": "Guantes de examen",
            "unit": "par",
            "min_stock": 200,
            "stock_on_hand": 150,
            "daily_consumption": 6.5,
            "on_order": 0,
            "last_unit_cost_clp": 198,
            "suggested_quantity": 245
          }
        ]
      }
    ]
  }
}

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

On this page