VitrinaAPI

Reservas

La única de estas ocho que bloquea inventario: toma un cupo sobre una unidad, con un abono opcional y un plazo.

Beta
Puede cambiar en cualquier momento, con una entrada en el changelog y aviso a quienes la llamaron recientemente — ver versionado.

Descarga la proyección completa de la API pública: openapi.json.

Vender una unidad explica este recurso en prosa, con ejemplos ejecutables.

MétodoRutaQué hace
GET/reservationsList reservations — the active-holds worklist
POST/reservationsTake a reservation
GET/reservations/{id}Fetch one reservation, with its payments and totals
POST/reservations/{id}/abono-dispositionDispose of the abono on a reservation
POST/reservations/{id}/convertConvert a reservation into a nota de venta
POST/reservations/{id}/voidVoid a reservation

GET /reservations

List reservations — the active-holds worklist

The dealership’s HOLD REGISTER, most urgent first: ordered by hold_expires_on ascending, then by folio descending. Every status is included by default — a voided or completed reserva keeps its folio and stays listable so "where is reserva R-47?" has an answer (ADR 0058 §10) — so pass status to narrow to the live ones.

Each row carries its money. total_pagos_clp is the sum of every ledger payment (customer_payment allocated via payment_allocation) recorded against the hold and payment_count is how many there are; a hold with no payments reports 0 / 0. Both come from one grouped join, so the list is one query rather than one per row.

The saldo is NOT returned, deliberately. It is agreed_price_clp - total_pagos_clp, and agreed_price_clp is nullable (null = "not recorded", which is not the same claim as 0). Compute it client-side and render "—" when the price is absent; a server-side number would have to invent one of the two.

salesperson_id is BR-312’s filter — "which holds are mine?". vehicle_id returns a unit’s hold HISTORY and answers nothing about whether that unit is available: vehicle.status via /vehicles is the only availability truth (ADR 0030).

Unknown query parameters are a 400, not a silent drop: a filter the caller believes is being applied, returning a full unfiltered list with a 200, is a worse answer than a refusal.

ParámetroEnTipoObligatorioRestricciones
statusqueryactiva \completada \archivada \
salesperson_idqueryuuidno
vehicle_idqueryuuidno
holder_contact_idqueryuuidno
limitqueryintegerno≥ 1, ≤ 200
curl https://api.vitrinadev.com/api/v1/reservations \
  -H "Authorization: Bearer $VITRINA_KEY"

Ejemplo de respuesta (200)

{
  "data": [
    {
      "id": "d3d3d3d3-0000-4000-8000-000000000001",
      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
      "display_id": "R-18",
      "display_seq": 18,
      "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
      "holder_contact_id": "22222222-0000-4000-8000-000000000002",
      "salesperson_id": "11111111-0000-4000-8000-000000000001",
      "agreed_price_clp": 11900000,
      "abono_expected_clp": 300000,
      "taken_at": "2026-09-18T16:40:00.000Z",
      "hold_expires_on": "2026-09-25",
      "keep_advertised": true,
      "status": "activa",
      "abono_disposition": null,
      "abono_disposition_reason": null,
      "abono_disposition_at": null,
      "abono_disposition_by": null,
      "voided_at": null,
      "void_reason": null,
      "voided_by": null,
      "created_at": "2026-09-18T16:40:00.000Z",
      "updated_at": "2026-09-18T16:40:00.000Z"
    }
  ],
  "meta": {
    "total": 1
  }
}

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

POST /reservations

Take a reservation

Takes a hold on a unit — the ONLY thing on this wave that locks inventory (a cotización creates no hold). Born activa; there is no draft state and no status field to supply.

vehicle_id and holder_contact_id are required and must belong to the calling workspace; a foreign id and a nonexistent one answer identically (404), deliberately, because a difference between them is an existence oracle.

hold_expires_on is REQUIRED and has no default (BR-241, corrected): unlike a cotización, a reserva locks the unit and can take a non-refundable deposit, so every hold states its own term rather than inheriting a silent policy number. agreed_price_clp, omitted, copies the vehicle's current asking price (BR-242) — that is a real default, not zero. keep_advertised, omitted, keeps the column default true (the marketplace ads stay live); an explicit false is the per-reserva opt-out that retracts them through the existing cierre queue, and it is set-once — there is no endpoint to change it on a standing hold.

Exactly one ACTIVE hold per unit. A second take on a unit that already has one is a 409, enforced by a partial unique index so a race loses too. Unknown body keys are a 400. Answers 201.

Cuerpo

CampoTipoObligatorioRestricciones
vehicle_iduuid
holder_contact_iduuid
salesperson_idstring | null
agreed_price_clpinteger | null≥ 0, ≤ 999999999999
taken_atstring | nulldate-time
hold_expires_onstringpatrón ^\d{4}-\d{2}-\d{2}$
keep_advertisedboolean
curl -X POST https://api.vitrinadev.com/api/v1/reservations \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
    "holder_contact_id": "22222222-0000-4000-8000-000000000002",
    "salesperson_id": "11111111-0000-4000-8000-000000000001",
    "agreed_price_clp": 11900000,
    "hold_expires_on": "2026-09-25",
    "keep_advertised": true
  }'

Ejemplo de respuesta (201)

{
  "data": {
    "id": "d3d3d3d3-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "display_id": "R-18",
    "display_seq": 18,
    "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
    "holder_contact_id": "22222222-0000-4000-8000-000000000002",
    "salesperson_id": "11111111-0000-4000-8000-000000000001",
    "agreed_price_clp": 11900000,
    "abono_expected_clp": 300000,
    "taken_at": "2026-09-18T16:40:00.000Z",
    "hold_expires_on": "2026-09-25",
    "keep_advertised": true,
    "status": "activa",
    "abono_disposition": null,
    "abono_disposition_reason": null,
    "abono_disposition_at": null,
    "abono_disposition_by": null,
    "voided_at": null,
    "void_reason": null,
    "voided_by": null,
    "created_at": "2026-09-18T16:40:00.000Z",
    "updated_at": "2026-09-18T16:40:00.000Z"
  }
}

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

GET /reservations/{id}

Fetch one reservation, with its payments and totals

Accepts the UUID or the folio (R-12).

ParámetroEnTipoObligatorioRestricciones
idpathany
curl https://api.vitrinadev.com/api/v1/reservations/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

Ejemplo de respuesta (200)

{
  "data": {
    "id": "d3d3d3d3-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "display_id": "R-18",
    "display_seq": 18,
    "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
    "holder_contact_id": "22222222-0000-4000-8000-000000000002",
    "salesperson_id": "11111111-0000-4000-8000-000000000001",
    "agreed_price_clp": 11900000,
    "abono_expected_clp": 300000,
    "taken_at": "2026-09-18T16:40:00.000Z",
    "hold_expires_on": "2026-09-25",
    "keep_advertised": true,
    "status": "activa",
    "abono_disposition": null,
    "abono_disposition_reason": null,
    "abono_disposition_at": null,
    "abono_disposition_by": null,
    "voided_at": null,
    "void_reason": null,
    "voided_by": null,
    "created_at": "2026-09-18T16:40:00.000Z",
    "updated_at": "2026-09-18T16:40:00.000Z",
    "payments": [
      {
        "id": "d6d6d6d6-0000-4000-8000-000000000001",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "reservation_id": "d3d3d3d3-0000-4000-8000-000000000001",
        "sale_note_id": null,
        "carried_from_reservation_id": null,
        "instrument": "transferencia",
        "amount_clp": 300000,
        "paid_on": "2026-09-18",
        "bank": "Banco Estado",
        "account_number": null,
        "document_number": null,
        "note": "abono al tomar la reserva",
        "card_fee_bps": null,
        "card_surcharge_clp": null,
        "card_surcharge_source": null,
        "created_at": "2026-09-18T16:41:02.000Z"
      }
    ],
    "totals": {
      "total_a_pagar_clp": 300000,
      "total_pagos_clp": 300000,
      "saldo_clp": 0,
      "recargo_tarjeta_clp": 0,
      "total_a_cobrar_clp": 300000,
      "payment_count": 1,
      "complete": true,
      "gaps": []
    },
    "converted_to_sale_note": null
  }
}

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

POST /reservations/{id}/abono-disposition

Dispose of the abono on a reservation

A FOURTH distinct scope, reservations:dispose_abono, not OR’d with :write/:void. May also close a still-activa hold in the same act.

ParámetroEnTipoObligatorioRestricciones
idpathany

Cuerpo

CampoTipoObligatorioRestricciones
abono_dispositionaplicado \devuelto \perdido
abono_disposition_reasonstringmáx. 2000
void_reasonstring | nullmáx. 2000
curl -X POST https://api.vitrinadev.com/api/v1/reservations/<id>/abono-disposition \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "abono_disposition": "devuelto",
    "abono_disposition_reason": "el cliente desistió; se devolvió el abono"
  }'

Ejemplo de respuesta (200)

{
  "data": {
    "id": "d3d3d3d3-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "display_id": "R-18",
    "display_seq": 18,
    "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
    "holder_contact_id": "22222222-0000-4000-8000-000000000002",
    "salesperson_id": "11111111-0000-4000-8000-000000000001",
    "agreed_price_clp": 11900000,
    "abono_expected_clp": 300000,
    "taken_at": "2026-09-18T16:40:00.000Z",
    "hold_expires_on": "2026-09-25",
    "keep_advertised": true,
    "status": "anulada",
    "abono_disposition": "devuelto",
    "abono_disposition_reason": "el cliente desistió; se devolvió el abono",
    "abono_disposition_at": "2026-09-21T09:00:00.000Z",
    "abono_disposition_by": "11111111-0000-4000-8000-000000000001",
    "voided_at": "2026-09-21T09:00:00.000Z",
    "void_reason": "cierre por disposición del abono",
    "voided_by": "11111111-0000-4000-8000-000000000001",
    "created_at": "2026-09-18T16:40:00.000Z",
    "updated_at": "2026-09-18T16:40:00.000Z"
  }
}

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

POST /reservations/{id}/convert

Convert a reservation into a nota de venta

Produces a NEW sale-note document — 201, the same as POST /sale-notes. Scoped sale_notes:write, borrowed rather than a fifth reservations scope.

ParámetroEnTipoObligatorioRestricciones
idpathany

Cuerpo

CampoTipoObligatorioRestricciones
seller_of_recordconsignante \retoma \automotora
tax_treatmentafecto \exento \no_gravado \
tax_clpinteger | null≥ 0, ≤ 999999999999
issued_atstring | nulldate-time
lead_idany
curl -X POST https://api.vitrinadev.com/api/v1/reservations/<id>/convert \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "seller_of_record": "automotora",
    "tax_treatment": "afecto",
    "tax_clp": 1900000,
    "issued_at": "2026-09-24T12:00:00.000Z"
  }'

Ejemplo de respuesta (201)

{
  "data": {
    "reservation": {
      "id": "d3d3d3d3-0000-4000-8000-000000000001",
      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
      "display_id": "R-18",
      "display_seq": 18,
      "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
      "holder_contact_id": "22222222-0000-4000-8000-000000000002",
      "salesperson_id": "11111111-0000-4000-8000-000000000001",
      "agreed_price_clp": 11900000,
      "abono_expected_clp": 300000,
      "taken_at": "2026-09-18T16:40:00.000Z",
      "hold_expires_on": "2026-09-25",
      "keep_advertised": true,
      "status": "completada",
      "abono_disposition": "aplicado",
      "abono_disposition_reason": null,
      "abono_disposition_at": "2026-09-24T12:00:00.000Z",
      "abono_disposition_by": "11111111-0000-4000-8000-000000000001",
      "voided_at": null,
      "void_reason": null,
      "voided_by": null,
      "created_at": "2026-09-18T16:40:00.000Z",
      "updated_at": "2026-09-18T16:40:00.000Z"
    },
    "sale_note": {
      "id": "d4d4d4d4-0000-4000-8000-000000000001",
      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
      "display_id": "V-118",
      "display_seq": 118,
      "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000002",
      "buyer_contact_id": "22222222-0000-4000-8000-000000000002",
      "lead_id": null,
      "converted_from_reservation_id": "d3d3d3d3-0000-4000-8000-000000000001",
      "seller_of_record": "automotora",
      "salesperson_id": "11111111-0000-4000-8000-000000000001",
      "net_clp": 11900000,
      "tax_clp": 1900000,
      "tax_treatment": "afecto",
      "status": "issued",
      "issued_at": "2026-09-19T13:00:00.000Z",
      "approved_by": null,
      "approved_at": null,
      "voided_at": null,
      "void_reason": null,
      "voided_by": null,
      "created_at": "2026-09-19T13:00:00.000Z",
      "updated_at": "2026-09-19T13:00:00.000Z"
    },
    "carried_payments": [
      {
        "id": "d6d6d6d6-0000-4000-8000-000000000001",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "reservation_id": null,
        "sale_note_id": "d4d4d4d4-0000-4000-8000-000000000001",
        "carried_from_reservation_id": "d3d3d3d3-0000-4000-8000-000000000001",
        "instrument": "transferencia",
        "amount_clp": 300000,
        "paid_on": "2026-09-18",
        "bank": "Banco Estado",
        "account_number": null,
        "document_number": null,
        "note": "abono al tomar la reserva",
        "card_fee_bps": null,
        "card_surcharge_clp": null,
        "card_surcharge_source": null,
        "created_at": "2026-09-18T16:41:02.000Z"
      }
    ]
  }
}

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

POST /reservations/{id}/void

Void a reservation

A distinct reservations:void authority from reservations:write.

ParámetroEnTipoObligatorioRestricciones
idpathany

Cuerpo

CampoTipoObligatorioRestricciones
void_reasonstringmáx. 2000
curl -X POST https://api.vitrinadev.com/api/v1/reservations/<id>/void \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "void_reason": "el cliente no completó el pago del abono a tiempo"
  }'

Ejemplo de respuesta (200)

{
  "data": {
    "id": "d3d3d3d3-0000-4000-8000-000000000002",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "display_id": "R-22",
    "display_seq": 22,
    "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
    "holder_contact_id": "22222222-0000-4000-8000-000000000002",
    "salesperson_id": "11111111-0000-4000-8000-000000000001",
    "agreed_price_clp": 11900000,
    "abono_expected_clp": null,
    "taken_at": "2026-09-18T16:40:00.000Z",
    "hold_expires_on": "2026-09-25",
    "keep_advertised": true,
    "status": "anulada",
    "abono_disposition": null,
    "abono_disposition_reason": null,
    "abono_disposition_at": null,
    "abono_disposition_by": null,
    "voided_at": "2026-09-20T10:05:00.000Z",
    "void_reason": "el cliente no completó el pago del abono a tiempo",
    "voided_by": "11111111-0000-4000-8000-000000000001",
    "created_at": "2026-09-18T16:40:00.000Z",
    "updated_at": "2026-09-18T16:40:00.000Z"
  }
}

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

En esta página