VitrinaAPI

Configure your locations

Create, correct and deactivate the workspace's locations.

GET /locations returns the workspace's locations. A location is the physical place the business works out of, where people are seen and things are booked. Almost everything that has a place either embeds it or references it by id: an appointment, a unit of the catalogue, a member of the team.

It is the sucursal block embedded in every unit of the stock and the value of the ?sucursal= filter.

Five endpoints, full CRUD. Reading asks for tenant:read; writing, for tenant:write. The field-by-field contract is in the reference.

Two names for one thing

The resource is /locations on the wire and sucursal inside the objects that embed it. Both are the contract and neither is going to be renamed for a translation, so this page uses whichever one the API uses at that point.

Seeing what exists

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

On a freshly created workspace:

{ "data": [] }

This list does not paginate and carries no meta.

Creating one

Only name is required. Everything else helps the location look right wherever it is shown:

curl -X POST https://api.vitrinadev.com/api/v1/locations \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Sucursal Providencia",
    "address_street": "Av. Nueva Providencia",
    "address_number": "2214",
    "comuna_code": "13123",
    "phone": "+56229876543",
    "email": "[email protected]",
    "timezone": "America/Santiago"
  }'
{
  "data": {
    "id": "6812d9f0-9bed-44b5-91df-4e5b90941f6b",
    "tenant_id": "00000000-0000-4000-8000-000000000001",
    "name": "Sucursal Providencia",
    "address": null,
    "address_street": "Av. Nueva Providencia",
    "address_number": "2214",
    "address_unit": null,
    "comuna_code": "13123",
    "region_code": "13",
    "address_source": "manual",
    "phone": "+56229876543",
    "email": "[email protected]",
    "manager_name": null,
    "hours": null,
    "timezone": "America/Santiago",
    "geo": null,
    "notes": null,
    "arrival_info": null,
    "metadata": {},
    "is_active": true,
    "display_seq": 1,
    "display_id": "B-1",
    "created_at": "2026-09-15T18:34:09.375Z",
    "updated_at": "2026-09-15T18:34:09.375Z"
  }
}

The names and the address here are in Spanish; the API stores whatever string you send.

Three fields that weren't in the body, and yet they're there:

region_code was derived from comuna_code. Don't send it: sending it changes nothing.

address_source came out as "manual" because the address went in field by field. The API stamps it so an address a person typed can be told apart from one that arrived some other way.

display_id is B-1, and the second location came out as B-2. It's a short, readable identifier assigned in creation order, so a person can name the location without reading a uuid. The endpoints that take {id} accept either one.

The comuna code

comuna_code is the official five-digit CUT code, Chile's national comuna code. It's never the comuna's name and never a portal's id. Sending anything else fails, and the message says so:

curl -X POST https://api.vitrinadev.com/api/v1/locations \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Sucursal Maipú", "comuna_code": "131" }'
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "details": {
      "body": [{
        "path": "comuna_code",
        "message": "comuna_code must be the official 5-digit CUT code (13114 = Las Condes), never a comuna name and never a portal id",
        "code": "invalid_string"
      }]
    },
    "field_errors": {
      "comuna_code": "comuna_code must be the official 5-digit CUT code (13114 = Las Condes), never a comuna name and never a portal id"
    },
    "requestId": "4c1e5e23-ae89-4e57-a711-5e962e8f4a8b"
  }
}

Look at field_errors: it is the same message as details, already indexed by field, ready to paint under the matching input. It is on every body validation.

Leaving out a required field looks the same:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "details": { "body": [{ "path": "name", "message": "Required", "code": "invalid_type" }] },
    "field_errors": { "name": "Required" },
    "requestId": "45c582f0-741c-4592-95bc-c2b4f679b7d2"
  }
}

Reading and correcting

curl https://api.vitrinadev.com/api/v1/locations/6812d9f0-9bed-44b5-91df-4e5b90941f6b \
  -H "Authorization: Bearer $VITRINA_KEY"

The PATCH is partial: send only what changes.

curl -X PATCH https://api.vitrinadev.com/api/v1/locations/6812d9f0-9bed-44b5-91df-4e5b90941f6b \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "manager_name": "Paula Riquelme",
    "hours": { "lun_vie": "09:00-19:00", "sab": "10:00-14:00" }
  }'
{
  "data": {
    "id": "6812d9f0-9bed-44b5-91df-4e5b90941f6b",
    "name": "Sucursal Providencia",
    "manager_name": "Paula Riquelme",
    "hours": { "sab": "10:00-14:00", "lun_vie": "09:00-19:00" },
    "updated_at": "2026-09-15T18:34:26.470Z"
  }
}

hours is a free-form object: the API stores it as it is and never validates its keys. The keys above are Spanish weekday abbreviations. Pick a convention and use it across all your locations.

A location that does not exist answers 404:

{
  "error": {
    "code": "NOT_FOUND",
    "message": "Sucursal no encontrada",
    "requestId": "75a3d88b-8703-42a2-9c40-127fec0dfcc3"
  }
}

Deactivating

The DELETE does not delete: it switches off.

curl -X DELETE https://api.vitrinadev.com/api/v1/locations/094e2408-9b01-4ab6-ae65-c102a02310d1 \
  -H "Authorization: Bearer $VITRINA_KEY"

204, no body. The location disappears from the ordinary listing:

[
  { "name": "Sucursal Providencia", "display_id": "B-1", "is_active": true },
  { "name": "Sucursal Vitacura",    "display_id": "B-3", "is_active": true }
]

…and is still there when you ask for it:

curl "https://api.vitrinadev.com/api/v1/locations?include_inactive=true" \
  -H "Authorization: Bearer $VITRINA_KEY"
[
  { "name": "Sucursal Providencia", "display_id": "B-1", "is_active": true },
  { "name": "Sucursal Maipú",       "display_id": "B-2", "is_active": false },
  { "name": "Sucursal Vitacura",    "display_id": "B-3", "is_active": true }
]

Everything pointing at that location (the calendar, the sales, the catalogue) keeps pointing at something with a name. To bring it back, PATCH with { "is_active": true }.

Trap

Switching a location off does not hide what points at it

is_active governs the location listing, and nothing else. Whatever already pointed at the switched-off location is still there, still comes out of its own reads, and still carries the location block in full.

After the DELETE above on Sucursal Maipú, a car at that location still shows up in GET /stock, with its sucursal block pointing at the switched-off location, and GET /stock?sucursal=094e2408-… still returns it. If you're really closing a location, move its stock somewhere else or pull it out of the lot first.

On this page