Pipelines
The boards work moves across, and the versioned templates one is built from.
Download the full API pública projection: openapi.json.
Move leads across the board explains this resource in prose, with runnable examples.
| Method | Path | What it does |
|---|---|---|
GET | /pipeline-templates | List pipeline templates |
POST | /pipeline-templates/{key}/apply | Apply a pipeline template to this workspace |
GET | /pipeline-templates/{key}/preview | Preview a localized pipeline template |
GET | /pipelines | List pipelines |
POST | /pipelines | Create pipeline |
DELETE | /pipelines/{id} | Delete pipeline |
GET | /pipelines/{id} | Get pipeline |
PUT | /pipelines/{id} | Update pipeline |
GET /pipeline-templates
List pipeline templates
The shipped, versioned board templates, localized to the resolved locale (es-CL → es, anything unshipped → en). Filter by kind and by vertical — the platform ships generic templates plus ones built for a particular vertical (automotive, healthcare), and a workspace may apply any of them. locales_available says which languages a template can be materialised in.
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
kind | query | sales \ | ticket \ | vehicle |
vertical | query | generic \ | automotive \ | healthcare |
locale | query | string | no | mín. 1, máx. 35 |
curl https://api.vitrinadev.com/api/v1/pipeline-templates \
-H "Authorization: Bearer $VITRINA_KEY"Example response (200)
{
"data": [
{
"key": "generic_sales",
"version": 1,
"kind": "sales",
"vertical": "generic",
"name": "Ventas",
"description": "Pipeline de ventas general: sigue una oportunidad comercial desde la primera consulta concreta hasta un cierre ganado, perdido o no calificado.",
"stage_count": 9,
"locale": "es",
"locales_available": [
"en",
"es"
]
},
{
"key": "clinic_treatments",
"version": 1,
"kind": "sales",
"vertical": "healthcare",
"name": "Tratamientos",
"description": "Pipeline comercial de la clínica: desde una consulta de precio hasta un tratamiento presupuestado, aceptado e iniciado.",
"stage_count": 7,
"locale": "es",
"locales_available": [
"en",
"es"
]
}
],
"meta": {
"total": 2
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /pipeline-templates/{key}/apply
Apply a pipeline template to this workspace
Materialises the template into tenant-owned, fully editable pipeline + stage rows — after this the board is yours, and editing it never diverges from anything. IDEMPOTENT per (workspace, template key): a second call returns the existing board with already_applied: true and changes nothing. A workspace holds exactly one FALLBACK board per kind — the board a card lands on when nobody named one. is_fallback: true promotes this board and demotes the current holder of that kind; false is ignored (promote the other board instead, so there is never a moment with no fallback). 400 when is_fallback is asked for and another board of that kind already holds the flag.
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
key | path | string | yes | mín. 1, máx. 80 |
Body
| Field | Type | Required | Constraints |
|---|---|---|---|
locale | string | — | mín. 1, máx. 35 |
version | integer | — | > 0 |
name_override | string | — | mín. 1, máx. 120 |
is_fallback | boolean | — | por defecto false |
curl -X POST https://api.vitrinadev.com/api/v1/pipeline-templates/<id>/apply \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"locale": "es",
"name_override": "Ventas usados",
"is_fallback": false
}'Example response (200)
{
"data": {
"already_applied": false,
"template": {
"key": "generic_sales",
"version": 1,
"locale": "es"
},
"pipeline": {
"id": "44444444-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"name": "Ventas usados",
"description": "Sigue una oportunidad comercial desde la primera consulta concreta hasta un cierre ganado, perdido o no calificado.",
"slug": "generic-sales",
"kind": "sales",
"is_fallback": true,
"template_key": "generic_sales",
"template_version": 1,
"template_locale": "es",
"created_at": "2026-09-04T19:07:05.226Z",
"updated_at": "2026-09-04T19:07:05.226Z"
},
"stages": [
{
"id": "55555555-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"pipeline_id": "44444444-0000-4000-8000-000000000001",
"name": "Nuevo",
"description": "Existe una consulta concreta, pero todavía no hubo contacto sustantivo ni calificación.",
"slug": "new",
"position": 0,
"category": "open",
"is_terminal": false,
"won_state": null,
"sla_days": null,
"allowed_transitions_to": [
"55555555-0000-4000-8000-000000000002",
"55555555-0000-4000-8000-000000000006",
"55555555-0000-4000-8000-000000000007"
],
"ai_agent_id": null,
"ai_agent_graph_id": null,
"is_human": false,
"is_marketing": false,
"template_stage_key": "new",
"created_at": "2026-09-04T19:07:05.226Z"
},
{
"id": "55555555-0000-4000-8000-000000000002",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"pipeline_id": "44444444-0000-4000-8000-000000000001",
"name": "Contactado",
"description": "Alguien del equipo hizo contacto real y está entendiendo la necesidad.",
"slug": "contacted",
"position": 1,
"category": "open",
"is_terminal": false,
"won_state": null,
"sla_days": 3,
"allowed_transitions_to": [
"55555555-0000-4000-8000-000000000003",
"55555555-0000-4000-8000-000000000006",
"55555555-0000-4000-8000-000000000007"
],
"ai_agent_id": null,
"ai_agent_graph_id": null,
"is_human": false,
"is_marketing": false,
"template_stage_key": "contacted",
"created_at": "2026-09-04T19:07:05.226Z"
},
{
"id": "55555555-0000-4000-8000-000000000006",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"pipeline_id": "44444444-0000-4000-8000-000000000001",
"name": "Ganado",
"description": "La oportunidad se cerró a favor.",
"slug": "won",
"position": 6,
"category": "won",
"is_terminal": true,
"won_state": "won",
"sla_days": null,
"allowed_transitions_to": [],
"ai_agent_id": null,
"ai_agent_graph_id": null,
"is_human": false,
"is_marketing": false,
"template_stage_key": "won",
"created_at": "2026-09-04T19:07:05.226Z"
}
]
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429
GET /pipeline-templates/{key}/preview
Preview a localized pipeline template
The exact board an apply would create: the pipeline name, description and kind, plus every stage with its slug, position, category, derived is_terminal, localized name/description and its allowed transitions (as stage KEYS — the uuids only exist once the board does). Read-only: previewing never writes.
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
key | path | string | yes | mín. 1, máx. 80 |
locale | query | string | no | mín. 1, máx. 35 |
version | query | integer | no | > 0 |
curl https://api.vitrinadev.com/api/v1/pipeline-templates/<id>/preview \
-H "Authorization: Bearer $VITRINA_KEY"Example response (200)
{
"data": {
"key": "generic_sales",
"version": 1,
"kind": "sales",
"vertical": "generic",
"locale": "es",
"locales_available": [
"en",
"es"
],
"pipeline": {
"name": "Ventas",
"description": "Pipeline de ventas general: sigue una oportunidad comercial desde la primera consulta concreta hasta un cierre ganado, perdido o no calificado.",
"kind": "sales"
},
"stages": [
{
"key": "new",
"slug": "new",
"position": 0,
"category": "open",
"is_terminal": false,
"name": "Nuevo",
"description": "Existe una consulta concreta, pero todavía no hubo contacto sustantivo ni calificación.",
"transitions": [
"contacted",
"won",
"lost",
"unqualified"
]
},
{
"key": "contacted",
"slug": "contacted",
"position": 1,
"category": "open",
"is_terminal": false,
"name": "Contactado",
"description": "Alguien del equipo hizo contacto real y está entendiendo la necesidad.",
"transitions": [
"new",
"qualified",
"won",
"lost",
"unqualified"
]
}
]
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429
GET /pipelines
List pipelines
Every board of the workspace. ?include=counts adds card_count to each row: THE WORK IN FLIGHT on that board — "how many cards would I see if I opened it" — per kind in that kind's own vocabulary: OPEN leads on a sales board, tickets that are neither resolved nor closed on a ticket board (pending and snoozed still count — deferred work is work), and currently-PLACED units on a vehicle board. It is NOT a historical total, so a long-finished funnel reads 0. Without the parameter the response is byte-identical to what it has always been; an unrecognised include value is a 400 rather than a silently ignored filter.
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
include | query | counts | no |
curl https://api.vitrinadev.com/api/v1/pipelines \
-H "Authorization: Bearer $VITRINA_KEY"Example response (200)
{
"data": [
{
"id": "44444444-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"name": "Ventas",
"description": "Sigue una oportunidad comercial desde la primera consulta concreta hasta un cierre ganado, perdido o no calificado.",
"slug": "generic-sales",
"kind": "sales",
"is_fallback": true,
"template_key": "generic_sales",
"template_version": 1,
"template_locale": "es",
"created_at": "2026-09-04T19:07:05.226Z",
"updated_at": "2026-09-04T19:07:05.226Z",
"card_count": 13
},
{
"id": "44444444-0000-4000-8000-000000000002",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"name": "Soporte",
"description": "Sigue una solicitud resoluble desde la clasificación hasta la resolución, por todas las conversaciones que haga falta.",
"slug": "generic-support",
"kind": "ticket",
"is_fallback": true,
"template_key": "generic_support",
"template_version": 1,
"template_locale": "es",
"created_at": "2026-09-04T19:07:05.226Z",
"updated_at": "2026-09-04T19:07:05.226Z",
"card_count": 4
}
],
"meta": {
"total": 2
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /pipelines
Create pipeline
Creates an EMPTY board — a board with no stages accepts no cards, so follow it with POST /stages/bulk, or start from a shipped template with POST /pipeline-templates/\{key\}/apply instead. A workspace holds exactly one FALLBACK board per kind — the board a card lands on when nobody named one. is_fallback: true promotes this board and demotes the current holder of that kind; false is ignored (promote the other board instead, so there is never a moment with no fallback).
Body
| Field | Type | Required | Constraints |
|---|---|---|---|
name | string | yes | mín. 1, máx. 120 |
description | string | — | máx. 4000, por defecto "" |
slug | string | — | mín. 1, máx. 120 |
kind | ticket \ | sales \ | vehicle |
is_fallback | boolean | — | por defecto false |
curl -X POST https://api.vitrinadev.com/api/v1/pipelines \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Ventas mayoristas",
"description": "Oportunidades con empresas, con aprobación de precio.",
"kind": "sales",
"is_fallback": false
}'Example response (201)
{
"data": {
"id": "44444444-0000-4000-8000-000000000002",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"name": "Ventas mayoristas",
"description": "Oportunidades con empresas, con aprobación de precio.",
"slug": "ventas-mayoristas",
"kind": "sales",
"is_fallback": false,
"template_key": null,
"template_version": null,
"template_locale": null,
"created_at": "2026-09-22T08:41:22.273Z",
"updated_at": "2026-09-22T08:41:22.273Z"
}
}Answers: 201 · 400 · 401 · 403 · 404 · 409 · 429
DELETE /pipelines/{id}
Delete pipeline
DELETING A BOARD NEVER DELETES ITS CARDS. Every lead and ticket on it is MOVED first — to ?reassign_to=<pipeline id> (same kind), or to the kind's fallback board when it is omitted — and the response reports how many of each moved and where to. 409 when the board is the fallback of its kind (promote another one first) or the last board of its kind; 400 when reassign_to names a board of a different kind.
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
id | path | string | yes | mín. 1 |
reassign_to | query | uuid | no |
curl -X DELETE https://api.vitrinadev.com/api/v1/pipelines/<id> \
-H "Authorization: Bearer $VITRINA_KEY"Example response (200)
{
"data": {
"tickets": 0,
"leads": 13,
"reassignedTo": "44444444-0000-4000-8000-000000000001"
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429
GET /pipelines/{id}
Get pipeline
One board WITH its columns nested under stage[], in position order — the single call that renders a board. A stage's ai_agent embed is omitted for a caller without ai_agents:read.
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
id | path | string | yes | mín. 1 |
curl https://api.vitrinadev.com/api/v1/pipelines/<id> \
-H "Authorization: Bearer $VITRINA_KEY"Example response (200)
{
"data": {
"id": "44444444-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"name": "Ventas",
"description": "Sigue una oportunidad comercial desde la primera consulta concreta hasta un cierre ganado, perdido o no calificado.",
"slug": "generic-sales",
"kind": "sales",
"is_fallback": true,
"template_key": "generic_sales",
"template_version": 1,
"template_locale": "es",
"created_at": "2026-09-04T19:07:05.226Z",
"updated_at": "2026-09-04T19:07:05.226Z",
"stage": [
{
"id": "55555555-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"pipeline_id": "44444444-0000-4000-8000-000000000001",
"name": "Nuevo",
"description": "Existe una consulta concreta, pero todavía no hubo contacto sustantivo ni calificación.",
"slug": "new",
"position": 0,
"category": "open",
"is_terminal": false,
"won_state": null,
"sla_days": null,
"allowed_transitions_to": [
"55555555-0000-4000-8000-000000000002",
"55555555-0000-4000-8000-000000000006",
"55555555-0000-4000-8000-000000000007"
],
"ai_agent_id": null,
"ai_agent_graph_id": null,
"is_human": false,
"is_marketing": false,
"template_stage_key": "new",
"created_at": "2026-09-04T19:07:05.226Z",
"ai_agent": null
},
{
"id": "55555555-0000-4000-8000-000000000002",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"pipeline_id": "44444444-0000-4000-8000-000000000001",
"name": "Contactado",
"description": "Alguien del equipo hizo contacto real y está entendiendo la necesidad.",
"slug": "contacted",
"position": 1,
"category": "open",
"is_terminal": false,
"won_state": null,
"sla_days": 3,
"allowed_transitions_to": [
"55555555-0000-4000-8000-000000000003",
"55555555-0000-4000-8000-000000000006",
"55555555-0000-4000-8000-000000000007"
],
"ai_agent_id": null,
"ai_agent_graph_id": null,
"is_human": false,
"is_marketing": false,
"template_stage_key": "contacted",
"created_at": "2026-09-04T19:07:05.226Z",
"ai_agent": null
},
{
"id": "55555555-0000-4000-8000-000000000006",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"pipeline_id": "44444444-0000-4000-8000-000000000001",
"name": "Ganado",
"description": "La oportunidad se cerró a favor.",
"slug": "won",
"position": 6,
"category": "won",
"is_terminal": true,
"won_state": "won",
"sla_days": null,
"allowed_transitions_to": [],
"ai_agent_id": null,
"ai_agent_graph_id": null,
"is_human": false,
"is_marketing": false,
"template_stage_key": "won",
"created_at": "2026-09-04T19:07:05.226Z",
"ai_agent": null
}
]
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429
PUT /pipelines/{id}
Update pipeline
Only the fields present in the body are written. kind is editable only for a non-fallback, EMPTY board that is not the last of its kind — otherwise 409. A workspace holds exactly one FALLBACK board per kind — the board a card lands on when nobody named one. is_fallback: true promotes this board and demotes the current holder of that kind; false is ignored (promote the other board instead, so there is never a moment with no fallback).
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
id | path | string | yes | mín. 1 |
Body
| Field | Type | Required | Constraints |
|---|---|---|---|
name | string | — | mín. 1, máx. 120 |
description | string | — | máx. 4000 |
slug | string | — | mín. 1, máx. 120 |
kind | ticket \ | sales \ | vehicle |
is_fallback | boolean | — |
curl -X PUT https://api.vitrinadev.com/api/v1/pipelines/<id> \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"description": "Oportunidades con empresas. Requiere aprobación de precio sobre 20 %."
}'Example response (200)
{
"data": {
"id": "44444444-0000-4000-8000-000000000002",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"name": "Ventas mayoristas",
"description": "Oportunidades con empresas. Requiere aprobación de precio sobre 20 %.",
"slug": "ventas-mayoristas",
"kind": "sales",
"is_fallback": false,
"template_key": null,
"template_version": null,
"template_locale": null,
"created_at": "2026-09-22T08:41:22.273Z",
"updated_at": "2026-09-22T08:41:22.273Z"
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429