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.