VitrinaAPI

Leads

The opportunities a workspace is working: open them, move them across the board, close them won, lost or unqualified, and read the funnel.

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.

Move leads across the board explains this resource in prose, with runnable examples.

MethodPathWhat it does
GET/leadsList leads
POST/leadsCreate a lead
DELETE/leads/{id}Delete a lead
GET/leads/{id}Get a lead
PUT/leads/{id}Update a lead
GET/leads/{id}/activityList a lead's activity timeline
PUT/leads/{id}/assignAssign a lead to an owner or team
GET/leads/{id}/conversationsList the conversations this opportunity spans
POST/leads/{id}/conversationsLink a conversation to a lead
DELETE/leads/{id}/conversations/{conversationId}Unlink a conversation from a lead
GET/leads/{id}/interestsList what a lead concretely wants
POST/leads/{id}/interestsAdd an interest to a lead
DELETE/leads/{id}/interests/{interestId}Remove a lead interest
PATCH/leads/{id}/interests/{interestId}Update a lead interest
PUT/leads/{id}/lostMark a lead as lost
PUT/leads/{id}/pipelineMove a lead to another board
PUT/leads/{id}/reopenReopen a closed lead
PUT/leads/{id}/stageMove a lead to another stage
PUT/leads/{id}/unqualifyMark a lead as unqualified
PUT/leads/{id}/wonMark a lead as won
POST/leads/from-conversationConvert a conversation into a lead
GET/leads/funnelLead funnel by stage
POST/leads/importBulk-import leads
GET/leads/kanbanLeads grouped by stage (board view)
GET/leads/statsLead score bands
GET/leads/summaryLead pipeline summary
GET/leads/win-rate/{dimension}Win rate by owner, team or source

GET /leads

List leads

The flat list, paged (page / page_size, max 200) and filterable by board, stage, status, source, intent, owner, team, contact, value band, score band and last activity; q matches the title and the contact. temperature is a SHORTCUT over score (cold 0–39 · warm 40–69 · hot 70–100 · unscored score IS NULL) and explicit min_score / max_score win over it — do not confuse it with the temperature FIELD on a lead, which is a judgement somebody recorded about momentum and is not derived from the score. resource_type and resource_id must be supplied together and narrow to the leads interested in one record. meta.pagination.total is the match count, not the page size. A member whose role restricts record visibility sees only the leads they own.

ParameterInTypeRequiredConstraints
pipeline_idquerystringnomín. 1
stage_idquerystringnomín. 1
statusqueryopen \won \lost \
sourcequeryconversation \marketplace \manual \
intentquerybuy \sell \financing \
owner_user_idqueryuuidno
team_idqueryuuidno
contact_idquerystringnomín. 1
min_valuequerynumber | nullno≥ 0
max_valuequerynumber | nullno≥ 0
min_scorequeryinteger | nullno≥ 0, ≤ 100
max_scorequeryinteger | nullno≥ 0, ≤ 100
temperaturequerycold \warm \hot \
last_activity_afterquerystringnodate-time
qquerystringnomín. 1, máx. 200
sortquerylast_activity_desc \last_activity_asc \created_desc \
resource_typequeryvehicleno
resource_idqueryuuidno
card_kindqueryall \leads \solicitudes
pagequeryintegerno≥ 1, por defecto 1
page_sizequeryintegerno≥ 1, ≤ 200, por defecto 25
curl https://api.vitrinadev.com/api/v1/leads \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": [
    {
      "id": "99999999-0000-4000-8000-000000000001",
      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
      "display_id": "L-89",
      "display_seq": 89,
      "title": "María — prueba de manejo Corolla 2022",
      "notes": null,
      "status": "open",
      "source": "conversation",
      "source_ref": null,
      "intent": "buy",
      "contact_id": "22222222-0000-4000-8000-000000000001",
      "pipeline_id": "44444444-0000-4000-8000-000000000001",
      "stage_id": "55555555-0000-4000-8000-000000000001",
      "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
      "origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
      "owner_user_id": "11111111-0000-4000-8000-000000000001",
      "team_id": null,
      "value_amount": 8990000,
      "value_currency": "CLP",
      "value_amount_close_confirmed_at": null,
      "expected_close_at": "2026-10-15T00:00:00.000Z",
      "score": 72,
      "score_reason": "pidió hora para probar el auto y confirmó día",
      "scored_at": "2026-09-21T14:02:11.000Z",
      "temperature": "warm",
      "temperature_reason": "respondió pidiendo confirmar la hora y no ha vuelto a escribir",
      "temperature_at": "2026-09-21T14:02:11.000Z",
      "won_lost_reason": null,
      "closed_at": null,
      "created_by": "11111111-0000-4000-8000-000000000001",
      "created_at": "2026-09-18T13:44:02.115Z",
      "updated_at": "2026-09-21T14:02:11.000Z",
      "last_activity_at": "2026-09-21T14:02:11.000Z",
      "contact": {
        "id": "22222222-0000-4000-8000-000000000001",
        "name": "María González",
        "email": "[email protected]",
        "phone": "+56912345001"
      },
      "stage": {
        "id": "55555555-0000-4000-8000-000000000001",
        "name": "Nuevo",
        "slug": "new",
        "position": 0,
        "won_state": null,
        "pipeline_id": "44444444-0000-4000-8000-000000000001"
      },
      "pipeline": {
        "id": "44444444-0000-4000-8000-000000000001",
        "kind": "sales",
        "name": "Ventas"
      }
    },
    {
      "id": "99999999-0000-4000-8000-000000000002",
      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
      "display_id": "L-90",
      "display_seq": 90,
      "title": "Consulta por financiamiento — Ana Soto",
      "notes": null,
      "status": "open",
      "source": "website",
      "source_ref": null,
      "intent": "financing",
      "contact_id": "22222222-0000-4000-8000-000000000001",
      "pipeline_id": "44444444-0000-4000-8000-000000000001",
      "stage_id": "55555555-0000-4000-8000-000000000002",
      "conversation_id": null,
      "origin_conversation_id": null,
      "owner_user_id": null,
      "team_id": null,
      "value_amount": null,
      "value_currency": "CLP",
      "value_amount_close_confirmed_at": null,
      "expected_close_at": null,
      "score": null,
      "score_reason": null,
      "scored_at": null,
      "temperature": null,
      "temperature_reason": null,
      "temperature_at": null,
      "won_lost_reason": null,
      "closed_at": null,
      "created_by": null,
      "created_at": "2026-09-19T09:12:44.000Z",
      "updated_at": "2026-09-19T09:12:44.000Z",
      "last_activity_at": "2026-09-19T09:12:44.000Z",
      "contact": {
        "id": "22222222-0000-4000-8000-000000000001",
        "name": "Ana Soto",
        "email": null,
        "phone": "+56912345001"
      },
      "stage": {
        "id": "55555555-0000-4000-8000-000000000002",
        "name": "Contactado",
        "slug": "contacted",
        "position": 1,
        "won_state": null,
        "pipeline_id": "44444444-0000-4000-8000-000000000001"
      },
      "pipeline": {
        "id": "44444444-0000-4000-8000-000000000001",
        "kind": "sales",
        "name": "Ventas"
      }
    }
  ],
  "meta": {
    "pagination": {
      "total": 16,
      "limit": 25
    }
  }
}

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

POST /leads

Create a lead

Opens an opportunity for an existing contact. pipeline_id defaults to the workspace's fallback SALES board (a lead cannot live on a ticket or vehicle board — 400 if you name one) and stage_id to that board's first column. source says WHERE it came from, intent says WHY (buy · sell · financing · trade_in, default buy); both are set at creation and are not part of the update body. With no owner_user_id but a team_id, the team's rotation picks the owner. Emits lead.created. Money is value_amount + value_currency and the pair travels together: a workspace may work in more than one currency, so the analytics endpoints break their totals down by_currency and flag is_mixed_currency rather than summing across them.

Body

FieldTypeRequiredConstraints
contact_iduuidyes
pipeline_iduuid
stage_iduuid
conversation_iduuid
titlestringyesmín. 1, máx. 500
notesstring | nullmáx. 20000
sourceconversation \marketplace \manual \
intentbuy \sell \financing \
source_refstring | nullmáx. 500
value_amountnumber | null≥ 0
value_currencystring | nullmín. 1, máx. 8
expected_close_atstring | nulldate-time
owner_user_idstring | null
team_idstring | null
scoreinteger | null≥ 0, ≤ 100
curl -X POST https://api.vitrinadev.com/api/v1/leads \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "contact_id": "22222222-0000-4000-8000-000000000001",
    "pipeline_id": "44444444-0000-4000-8000-000000000001",
    "title": "María — prueba de manejo Corolla 2022",
    "source": "manual",
    "intent": "buy",
    "value_amount": 8990000,
    "value_currency": "CLP",
    "expected_close_at": "2026-10-15T00:00:00.000Z",
    "owner_user_id": "11111111-0000-4000-8000-000000000001"
  }'

Example response (201)

{
  "data": {
    "id": "99999999-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "display_id": "L-89",
    "display_seq": 89,
    "title": "María — prueba de manejo Corolla 2022",
    "notes": null,
    "status": "open",
    "source": "conversation",
    "source_ref": null,
    "intent": "buy",
    "contact_id": "22222222-0000-4000-8000-000000000001",
    "pipeline_id": "44444444-0000-4000-8000-000000000001",
    "stage_id": "55555555-0000-4000-8000-000000000001",
    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
    "origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
    "owner_user_id": "11111111-0000-4000-8000-000000000001",
    "team_id": null,
    "value_amount": 8990000,
    "value_currency": "CLP",
    "value_amount_close_confirmed_at": null,
    "expected_close_at": "2026-10-15T00:00:00.000Z",
    "score": 72,
    "score_reason": "pidió hora para probar el auto y confirmó día",
    "scored_at": "2026-09-21T14:02:11.000Z",
    "temperature": "warm",
    "temperature_reason": "respondió pidiendo confirmar la hora y no ha vuelto a escribir",
    "temperature_at": "2026-09-21T14:02:11.000Z",
    "won_lost_reason": null,
    "closed_at": null,
    "created_by": "11111111-0000-4000-8000-000000000001",
    "created_at": "2026-09-18T13:44:02.115Z",
    "updated_at": "2026-09-21T14:02:11.000Z",
    "last_activity_at": "2026-09-21T14:02:11.000Z",
    "contact": {
      "id": "22222222-0000-4000-8000-000000000001",
      "name": "María González",
      "email": "[email protected]",
      "phone": "+56912345001"
    },
    "stage": {
      "id": "55555555-0000-4000-8000-000000000001",
      "name": "Nuevo",
      "slug": "new",
      "position": 0,
      "won_state": null,
      "pipeline_id": "44444444-0000-4000-8000-000000000001"
    },
    "pipeline": {
      "id": "44444444-0000-4000-8000-000000000001",
      "kind": "sales",
      "name": "Ventas"
    }
  }
}

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

DELETE /leads/{id}

Delete a lead

Removes the opportunity and its history. The CONTACT is untouched — a deleted lead is an opportunity that should never have been opened, not a person who should be forgotten. A lead that really ended belongs on /lost or /unqualify, which keep the record and the reason.

ParameterInTypeRequiredConstraints
idpathstringyespatrón `^(?:[0-9a-f]8-[0-9a-f]4-[0-9a-f]4-[0-9a-f]4-[0-9a-f]12\
curl -X DELETE https://api.vitrinadev.com/api/v1/leads/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

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

GET /leads/{id}

Get a lead

One opportunity, with its contact, stage and pipeline embedded. \{id\} accepts the lead uuid or its ID visible (L-89) — the label is a lookup shortcut on the path only, never a value to store.

ParameterInTypeRequiredConstraints
idpathstringyespatrón `^(?:[0-9a-f]8-[0-9a-f]4-[0-9a-f]4-[0-9a-f]4-[0-9a-f]12\
curl https://api.vitrinadev.com/api/v1/leads/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "id": "99999999-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "display_id": "L-89",
    "display_seq": 89,
    "title": "María — prueba de manejo Corolla 2022",
    "notes": null,
    "status": "open",
    "source": "conversation",
    "source_ref": null,
    "intent": "buy",
    "contact_id": "22222222-0000-4000-8000-000000000001",
    "pipeline_id": "44444444-0000-4000-8000-000000000001",
    "stage_id": "55555555-0000-4000-8000-000000000001",
    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
    "origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
    "owner_user_id": "11111111-0000-4000-8000-000000000001",
    "team_id": null,
    "value_amount": 8990000,
    "value_currency": "CLP",
    "value_amount_close_confirmed_at": null,
    "expected_close_at": "2026-10-15T00:00:00.000Z",
    "score": 72,
    "score_reason": "pidió hora para probar el auto y confirmó día",
    "scored_at": "2026-09-21T14:02:11.000Z",
    "temperature": "warm",
    "temperature_reason": "respondió pidiendo confirmar la hora y no ha vuelto a escribir",
    "temperature_at": "2026-09-21T14:02:11.000Z",
    "won_lost_reason": null,
    "closed_at": null,
    "created_by": "11111111-0000-4000-8000-000000000001",
    "created_at": "2026-09-18T13:44:02.115Z",
    "updated_at": "2026-09-21T14:02:11.000Z",
    "last_activity_at": "2026-09-21T14:02:11.000Z",
    "contact": {
      "id": "22222222-0000-4000-8000-000000000001",
      "name": "María González",
      "email": "[email protected]",
      "phone": "+56912345001"
    },
    "stage": {
      "id": "55555555-0000-4000-8000-000000000001",
      "name": "Nuevo",
      "slug": "new",
      "position": 0,
      "won_state": null,
      "pipeline_id": "44444444-0000-4000-8000-000000000001"
    },
    "pipeline": {
      "id": "44444444-0000-4000-8000-000000000001",
      "kind": "sales",
      "name": "Ventas"
    }
  }
}

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

PUT /leads/{id}

Update a lead

Only the fields present in the body are written; at least one is required. source and intent are set at creation and are not editable, and the stage is moved with PUT /leads/\{id\}/stage rather than here. temperature is the recorded JUDGEMENT about momentum (hot · warm · cold): omitting it leaves it alone, sending null RETRACTS it — which is how a person undoes a wrong verdict. Setting owner_user_id reassigns the lead's conversations with it. Money is value_amount + value_currency and the pair travels together: a workspace may work in more than one currency, so the analytics endpoints break their totals down by_currency and flag is_mixed_currency rather than summing across them.

ParameterInTypeRequiredConstraints
idpathstringyespatrón `^(?:[0-9a-f]8-[0-9a-f]4-[0-9a-f]4-[0-9a-f]4-[0-9a-f]12\

Body

FieldTypeRequiredConstraints
titlestringmín. 1, máx. 500
notesstring | nullmáx. 20000
value_amountnumber | null≥ 0
value_currencystring | nullmín. 1, máx. 8
expected_close_atstring | nulldate-time
owner_user_idstring | null
team_idstring | null
scoreinteger | null≥ 0, ≤ 100
temperaturehot \warm \cold
temperature_reasonstring | nullmáx. 500
curl -X PUT https://api.vitrinadev.com/api/v1/leads/<id> \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "value_amount": 8750000,
    "expected_close_at": "2026-10-31T00:00:00.000Z",
    "temperature": "hot",
    "temperature_reason": "volvió a escribir pidiendo la reserva"
  }'

Example response (200)

{
  "data": {
    "id": "99999999-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "display_id": "L-89",
    "display_seq": 89,
    "title": "María — prueba de manejo Corolla 2022",
    "notes": null,
    "status": "open",
    "source": "conversation",
    "source_ref": null,
    "intent": "buy",
    "contact_id": "22222222-0000-4000-8000-000000000001",
    "pipeline_id": "44444444-0000-4000-8000-000000000001",
    "stage_id": "55555555-0000-4000-8000-000000000001",
    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
    "origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
    "owner_user_id": "11111111-0000-4000-8000-000000000001",
    "team_id": null,
    "value_amount": 8750000,
    "value_currency": "CLP",
    "value_amount_close_confirmed_at": null,
    "expected_close_at": "2026-10-31T00:00:00.000Z",
    "score": 72,
    "score_reason": "pidió hora para probar el auto y confirmó día",
    "scored_at": "2026-09-21T14:02:11.000Z",
    "temperature": "hot",
    "temperature_reason": "volvió a escribir pidiendo la reserva",
    "temperature_at": "2026-09-21T14:02:11.000Z",
    "won_lost_reason": null,
    "closed_at": null,
    "created_by": "11111111-0000-4000-8000-000000000001",
    "created_at": "2026-09-18T13:44:02.115Z",
    "updated_at": "2026-09-22T08:39:20.206Z",
    "last_activity_at": "2026-09-22T08:39:20.206Z",
    "contact": {
      "id": "22222222-0000-4000-8000-000000000001",
      "name": "María González",
      "email": "[email protected]",
      "phone": "+56912345001"
    },
    "stage": {
      "id": "55555555-0000-4000-8000-000000000001",
      "name": "Nuevo",
      "slug": "new",
      "position": 0,
      "won_state": null,
      "pipeline_id": "44444444-0000-4000-8000-000000000001"
    },
    "pipeline": {
      "id": "44444444-0000-4000-8000-000000000001",
      "kind": "sales",
      "name": "Ventas"
    }
  }
}

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

GET /leads/{id}/activity

List a lead's activity timeline

Newest first. Every line carries its author (ADR 0106 §3.1): the member who acted (with via when they acted through a connected app or a personal token — «Camila vía Claude»), the API key that acted («CRM propio (API)», named as it was then), the AI agent, or the system. kind says what happened and data is kind-specific; id is the timeline row's own legacy sequence number, not a uuid — lead_id beside it is the reference.

ParameterInTypeRequiredConstraints
idpathstringyespatrón `^(?:[0-9a-f]8-[0-9a-f]4-[0-9a-f]4-[0-9a-f]4-[0-9a-f]12\
curl https://api.vitrinadev.com/api/v1/leads/<id>/activity \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": [
    {
      "id": 474,
      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
      "lead_id": "99999999-0000-4000-8000-000000000001",
      "kind": "stage_changed",
      "actor_user_id": "11111111-0000-4000-8000-000000000001",
      "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
      "data": {
        "reason": "la llamamos y confirmó la hora",
        "from_stage_id": "55555555-0000-4000-8000-000000000001",
        "to_stage_id": "55555555-0000-4000-8000-000000000002",
        "from_stage_slug": "new",
        "to_stage_slug": "contacted",
        "from_stage_name": "Nuevo",
        "to_stage_name": "Contactado",
        "to_stage_category": "open"
      },
      "author": {
        "kind": "member",
        "id": "11111111-0000-4000-8000-000000000001",
        "name": "Camila Rojas",
        "via": {
          "kind": "connected_app",
          "name": "Claude"
        }
      },
      "created_at": "2026-09-22T08:39:25.338Z"
    },
    {
      "id": 473,
      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
      "lead_id": "99999999-0000-4000-8000-000000000001",
      "kind": "assigned",
      "actor_user_id": null,
      "conversation_id": null,
      "data": {
        "from_owner_user_id": null,
        "to_owner_user_id": "11111111-0000-4000-8000-000000000001"
      },
      "author": {
        "kind": "api_key",
        "id": "c1c1c1c1-0000-4000-8000-000000000001",
        "name": "CRM propio"
      },
      "created_at": "2026-09-21T14:02:11.000Z"
    },
    {
      "id": 470,
      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
      "lead_id": "99999999-0000-4000-8000-000000000001",
      "kind": "created",
      "actor_user_id": null,
      "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
      "data": {
        "source": "conversation",
        "title": "María — prueba de manejo Corolla 2022"
      },
      "author": {
        "kind": "system",
        "id": null,
        "name": null
      },
      "created_at": "2026-09-18T13:44:02.115Z"
    }
  ]
}

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

PUT /leads/{id}/assign

Assign a lead to an owner or team

ONE ASSIGNEE OWNS THE WHOLE RELATIONSHIP: reassigning a lead moves every conversation linked to it, and their tickets, to the same person. Send owner_user_id, team_id or both; an omitted key leaves that side alone, and null clears it. A team with no owner draws from the team's rotation. Emits lead.assigned.

ParameterInTypeRequiredConstraints
idpathstringyespatrón `^(?:[0-9a-f]8-[0-9a-f]4-[0-9a-f]4-[0-9a-f]4-[0-9a-f]12\

Body

FieldTypeRequiredConstraints
owner_user_idstring | null
team_idstring | null
curl -X PUT https://api.vitrinadev.com/api/v1/leads/<id>/assign \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "owner_user_id": "11111111-0000-4000-8000-000000000001",
    "team_id": "cccccccc-0000-4000-8000-000000000001"
  }'

Example response (200)

{
  "data": {
    "id": "99999999-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "display_id": "L-89",
    "display_seq": 89,
    "title": "María — prueba de manejo Corolla 2022",
    "notes": null,
    "status": "open",
    "source": "conversation",
    "source_ref": null,
    "intent": "buy",
    "contact_id": "22222222-0000-4000-8000-000000000001",
    "pipeline_id": "44444444-0000-4000-8000-000000000001",
    "stage_id": "55555555-0000-4000-8000-000000000001",
    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
    "origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
    "owner_user_id": "11111111-0000-4000-8000-000000000001",
    "team_id": "cccccccc-0000-4000-8000-000000000001",
    "value_amount": 8990000,
    "value_currency": "CLP",
    "value_amount_close_confirmed_at": null,
    "expected_close_at": "2026-10-15T00:00:00.000Z",
    "score": 72,
    "score_reason": "pidió hora para probar el auto y confirmó día",
    "scored_at": "2026-09-21T14:02:11.000Z",
    "temperature": "warm",
    "temperature_reason": "respondió pidiendo confirmar la hora y no ha vuelto a escribir",
    "temperature_at": "2026-09-21T14:02:11.000Z",
    "won_lost_reason": null,
    "closed_at": null,
    "created_by": "11111111-0000-4000-8000-000000000001",
    "created_at": "2026-09-18T13:44:02.115Z",
    "updated_at": "2026-09-21T14:02:11.000Z",
    "last_activity_at": "2026-09-21T14:02:11.000Z",
    "contact": {
      "id": "22222222-0000-4000-8000-000000000001",
      "name": "María González",
      "email": "[email protected]",
      "phone": "+56912345001"
    },
    "stage": {
      "id": "55555555-0000-4000-8000-000000000001",
      "name": "Nuevo",
      "slug": "new",
      "position": 0,
      "won_state": null,
      "pipeline_id": "44444444-0000-4000-8000-000000000001"
    },
    "pipeline": {
      "id": "44444444-0000-4000-8000-000000000001",
      "kind": "sales",
      "name": "Ventas"
    }
  }
}

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

GET /leads/{id}/conversations

List the conversations this opportunity spans

A lead is ONE closable opportunity; a conversation is a temporary session. The customer comes back next week on another channel and it is still the same opportunity, so this relation — not a single conversation_id — is the answer to "which conversations belong to this lead". is_origin marks where the opportunity STARTED (a historical fact) and is_primary the thread it is worked on now (editable).

ParameterInTypeRequiredConstraints
idpathstringyespatrón `^(?:[0-9a-f]8-[0-9a-f]4-[0-9a-f]4-[0-9a-f]4-[0-9a-f]12\
curl https://api.vitrinadev.com/api/v1/leads/<id>/conversations \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": [
    {
      "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
      "display_id": "C-122",
      "channel": "whatsapp",
      "status": "open",
      "is_primary": true,
      "is_origin": true,
      "linked_at": "2026-09-18T13:44:02.115Z",
      "linked_by_type": "system",
      "last_message_date": "2026-09-21T14:01:58.000Z"
    }
  ],
  "meta": {
    "total": 1
  }
}

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

POST /leads/{id}/conversations

Link a conversation to a lead

IDEMPOTENT: re-linking an existing pair answers linked: false and writes no second timeline entry. make_primary moves the primary flag onto this thread, demoting whichever held it. 404 for a lead or a conversation this credential cannot see — BOTH sides are checked, so a member restricted to their own records cannot attach a colleague's thread to a lead they own.

ParameterInTypeRequiredConstraints
idpathstringyespatrón `^(?:[0-9a-f]8-[0-9a-f]4-[0-9a-f]4-[0-9a-f]4-[0-9a-f]12\

Body

FieldTypeRequiredConstraints
conversation_iduuidyes
make_primaryboolean
curl -X POST https://api.vitrinadev.com/api/v1/leads/<id>/conversations \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
    "make_primary": true
  }'

Example response (200)

{
  "data": {
    "linked": true,
    "is_primary": true
  }
}

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

DELETE /leads/{id}/conversations/{conversationId}

Unlink a conversation from a lead

Detaching the ORIGIN conversation is allowed: the association is editable, but origin_conversation_id is a historical fact ("where this opportunity started") and stays put, because attribution keeps reading it. Answers \{ unlinked \}false when there was no link to remove.

ParameterInTypeRequiredConstraints
idpathstringyespatrón `^(?:[0-9a-f]8-[0-9a-f]4-[0-9a-f]4-[0-9a-f]4-[0-9a-f]12\
conversationIdpathuuidyes
curl -X DELETE https://api.vitrinadev.com/api/v1/leads/<id>/conversations/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "unlinked": true
  }
}

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

GET /leads/{id}/interests

List what a lead concretely wants

A lead is the opportunity; an INTEREST is the thing being asked for, and there can be several. resource_type is vehicle · property · product · service · repair_order · custom, with resource_id pointing at the record when it exists in Vitrina and title carrying it in words when it does not ("un SUV gris, 2020 o más nuevo").

ParameterInTypeRequiredConstraints
idpathstringyespatrón `^(?:[0-9a-f]8-[0-9a-f]4-[0-9a-f]4-[0-9a-f]4-[0-9a-f]12\
curl https://api.vitrinadev.com/api/v1/leads/<id>/interests \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": [
    {
      "id": "aaaaaaaa-0000-4000-8000-000000000001",
      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
      "lead_id": "99999999-0000-4000-8000-000000000001",
      "resource_type": "vehicle",
      "resource_id": "e1e1e1e1-0000-4000-8000-000000000001",
      "title": "Toyota Corolla 2022",
      "quantity": 1,
      "priority": "high",
      "notes": "lo quiere gris, con mantenimiento al día",
      "metadata": {},
      "created_at": "2026-09-22T08:39:26.884Z",
      "updated_at": "2026-09-22T08:39:26.884Z",
      "superseded_at": null
    }
  ],
  "meta": {
    "total": 1
  }
}

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

POST /leads/{id}/interests

Add an interest to a lead

Provide resource_id, title, or both — one of the two is required, because an interest that names neither a record nor a description says nothing. 400 otherwise.

ParameterInTypeRequiredConstraints
idpathstringyespatrón `^(?:[0-9a-f]8-[0-9a-f]4-[0-9a-f]4-[0-9a-f]4-[0-9a-f]12\

Body

FieldTypeRequiredConstraints
resource_typevehicle \property \product \
resource_idstring | null
titlestring | nullmín. 1, máx. 500
quantitynumber | null≥ 0
prioritylow \medium \high
notesstring | nullmáx. 20000
metadataobject
curl -X POST https://api.vitrinadev.com/api/v1/leads/<id>/interests \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "resource_type": "vehicle",
    "resource_id": "e1e1e1e1-0000-4000-8000-000000000001",
    "quantity": 1,
    "priority": "high",
    "notes": "lo quiere gris, con mantenimiento al día"
  }'

Example response (201)

{
  "data": {
    "id": "aaaaaaaa-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "lead_id": "99999999-0000-4000-8000-000000000001",
    "resource_type": "vehicle",
    "resource_id": "e1e1e1e1-0000-4000-8000-000000000001",
    "title": "Toyota Corolla 2022",
    "quantity": 1,
    "priority": "high",
    "notes": "lo quiere gris, con mantenimiento al día",
    "metadata": {},
    "created_at": "2026-09-22T08:39:26.884Z",
    "updated_at": "2026-09-22T08:39:26.884Z",
    "superseded_at": null
  }
}

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

DELETE /leads/{id}/interests/{interestId}

Remove a lead interest

Deletes the row and answers \{ id, deleted: true \} (200, not 204). Removes it from the record entirely — an interest the customer moved ON from is history worth keeping, so prefer adding the new one.

ParameterInTypeRequiredConstraints
idpathstringyespatrón `^(?:[0-9a-f]8-[0-9a-f]4-[0-9a-f]4-[0-9a-f]4-[0-9a-f]12\
interestIdpathuuidyes
curl -X DELETE https://api.vitrinadev.com/api/v1/leads/<id>/interests/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "id": "aaaaaaaa-0000-4000-8000-000000000001",
    "deleted": true
  }
}

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

PATCH /leads/{id}/interests/{interestId}

Update a lead interest

Only the fields present in the body are written; at least one is required. Use this when the ask itself changed — a different unit, a different quantity, a different priority.

ParameterInTypeRequiredConstraints
idpathstringyespatrón `^(?:[0-9a-f]8-[0-9a-f]4-[0-9a-f]4-[0-9a-f]4-[0-9a-f]12\
interestIdpathuuidyes

Body

FieldTypeRequiredConstraints
resource_typevehicle \property \product \
resource_idstring | null
titlestring | nullmín. 1, máx. 500
quantitynumber | null≥ 0
prioritylow \medium \high
notesstring | nullmáx. 20000
metadataobject
curl -X PATCH https://api.vitrinadev.com/api/v1/leads/<id>/interests/<id> \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "priority": "medium"
  }'

Example response (200)

{
  "data": {
    "id": "aaaaaaaa-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "lead_id": "99999999-0000-4000-8000-000000000001",
    "resource_type": "vehicle",
    "resource_id": "e1e1e1e1-0000-4000-8000-000000000001",
    "title": "Toyota Corolla 2022",
    "quantity": 1,
    "priority": "medium",
    "notes": "lo quiere gris, con mantenimiento al día",
    "metadata": {},
    "created_at": "2026-09-22T08:39:26.884Z",
    "updated_at": "2026-09-22T08:39:26.884Z",
    "superseded_at": null
  }
}

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

PUT /leads/{id}/lost

Mark a lead as lost

The opportunity was real and did not close. The lead MOVES to the lowest-position stage of that category on its board, so the outcome and the column can never disagree — 400 when the board has no stage of that category, which is a board a person has to fix rather than a request to retry. status, closed_at and won_lost_reason are derived from the landing stage, and a lead.stage_changed Evento fires alongside the outcome one. reason is the one field worth filling: it is what the win-rate and funnel reads are read against. Emits lead.lost.

ParameterInTypeRequiredConstraints
idpathstringyespatrón `^(?:[0-9a-f]8-[0-9a-f]4-[0-9a-f]4-[0-9a-f]4-[0-9a-f]12\

Body

FieldTypeRequiredConstraints
statuswon \lost \unqualified \
reasonstring | nullmáx. 500
value_amountnumber | null≥ 0
value_currencystring | nullmín. 1, máx. 8
curl -X PUT https://api.vitrinadev.com/api/v1/leads/<id>/lost \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "reason": "compró en otra parte"
  }'

Example response (200)

{
  "data": {
    "id": "99999999-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "display_id": "L-89",
    "display_seq": 89,
    "title": "María — prueba de manejo Corolla 2022",
    "notes": null,
    "status": "lost",
    "source": "conversation",
    "source_ref": null,
    "intent": "buy",
    "contact_id": "22222222-0000-4000-8000-000000000001",
    "pipeline_id": "44444444-0000-4000-8000-000000000001",
    "stage_id": "55555555-0000-4000-8000-000000000007",
    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
    "origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
    "owner_user_id": "11111111-0000-4000-8000-000000000001",
    "team_id": null,
    "value_amount": 8990000,
    "value_currency": "CLP",
    "value_amount_close_confirmed_at": null,
    "expected_close_at": "2026-10-15T00:00:00.000Z",
    "score": 72,
    "score_reason": "pidió hora para probar el auto y confirmó día",
    "scored_at": "2026-09-21T14:02:11.000Z",
    "temperature": "warm",
    "temperature_reason": "respondió pidiendo confirmar la hora y no ha vuelto a escribir",
    "temperature_at": "2026-09-21T14:02:11.000Z",
    "won_lost_reason": "compró en otra parte",
    "closed_at": "2026-09-22T08:44:10.331Z",
    "created_by": "11111111-0000-4000-8000-000000000001",
    "created_at": "2026-09-18T13:44:02.115Z",
    "updated_at": "2026-09-21T14:02:11.000Z",
    "last_activity_at": "2026-09-21T14:02:11.000Z",
    "contact": {
      "id": "22222222-0000-4000-8000-000000000001",
      "name": "María González",
      "email": "[email protected]",
      "phone": "+56912345001"
    },
    "stage": {
      "id": "55555555-0000-4000-8000-000000000007",
      "name": "Perdido",
      "slug": "lost",
      "position": 7,
      "won_state": "lost",
      "pipeline_id": "44444444-0000-4000-8000-000000000001"
    },
    "pipeline": {
      "id": "44444444-0000-4000-8000-000000000001",
      "kind": "sales",
      "name": "Ventas"
    }
  }
}

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

PUT /leads/{id}/pipeline

Move a lead to another board

Reclassifies the opportunity onto a different SALES board (400 for a ticket or vehicle board) and lands it on that board's first stage, re-deriving the lifecycle from the landing stage rather than leaving a closed lead parked in an open column. Emits lead.pipeline_changed, whose changes carries BOTH the board and the stage that moved with it; no separate lead.stage_changed fires, so a receiver never sees one board change twice.

ParameterInTypeRequiredConstraints
idpathstringyespatrón `^(?:[0-9a-f]8-[0-9a-f]4-[0-9a-f]4-[0-9a-f]4-[0-9a-f]12\

Body

FieldTypeRequiredConstraints
pipeline_iduuidyes
reasonstring | nullmáx. 500
curl -X PUT https://api.vitrinadev.com/api/v1/leads/<id>/pipeline \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "pipeline_id": "44444444-0000-4000-8000-000000000002",
    "reason": "es una venta mayorista"
  }'

Example response (200)

{
  "data": {
    "id": "99999999-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "display_id": "L-89",
    "display_seq": 89,
    "title": "María — prueba de manejo Corolla 2022",
    "notes": null,
    "status": "open",
    "source": "conversation",
    "source_ref": null,
    "intent": "buy",
    "contact_id": "22222222-0000-4000-8000-000000000001",
    "pipeline_id": "44444444-0000-4000-8000-000000000002",
    "stage_id": "55555555-0000-4000-8000-000000000003",
    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
    "origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
    "owner_user_id": "11111111-0000-4000-8000-000000000001",
    "team_id": null,
    "value_amount": 8990000,
    "value_currency": "CLP",
    "value_amount_close_confirmed_at": null,
    "expected_close_at": "2026-10-15T00:00:00.000Z",
    "score": 72,
    "score_reason": "pidió hora para probar el auto y confirmó día",
    "scored_at": "2026-09-21T14:02:11.000Z",
    "temperature": "warm",
    "temperature_reason": "respondió pidiendo confirmar la hora y no ha vuelto a escribir",
    "temperature_at": "2026-09-21T14:02:11.000Z",
    "won_lost_reason": null,
    "closed_at": null,
    "created_by": "11111111-0000-4000-8000-000000000001",
    "created_at": "2026-09-18T13:44:02.115Z",
    "updated_at": "2026-09-21T14:02:11.000Z",
    "last_activity_at": "2026-09-21T14:02:11.000Z",
    "contact": {
      "id": "22222222-0000-4000-8000-000000000001",
      "name": "María González",
      "email": "[email protected]",
      "phone": "+56912345001"
    },
    "stage": {
      "id": "55555555-0000-4000-8000-000000000003",
      "name": "Nuevo",
      "slug": "new",
      "position": 0,
      "won_state": null,
      "pipeline_id": "44444444-0000-4000-8000-000000000002"
    },
    "pipeline": {
      "id": "44444444-0000-4000-8000-000000000002",
      "kind": "sales",
      "name": "Ventas mayoristas"
    }
  }
}

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

PUT /leads/{id}/reopen

Reopen a closed lead

Puts a won, lost or unqualified lead back in play on the first OPEN stage of its board, clearing closed_at and won_lost_reason. No body. The close is not erased — the timeline and the transition log keep it — so a reopened-and-re-won lead reads as the two events it was.

ParameterInTypeRequiredConstraints
idpathstringyespatrón `^(?:[0-9a-f]8-[0-9a-f]4-[0-9a-f]4-[0-9a-f]4-[0-9a-f]12\
curl -X PUT https://api.vitrinadev.com/api/v1/leads/<id>/reopen \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "id": "99999999-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "display_id": "L-89",
    "display_seq": 89,
    "title": "María — prueba de manejo Corolla 2022",
    "notes": null,
    "status": "open",
    "source": "conversation",
    "source_ref": null,
    "intent": "buy",
    "contact_id": "22222222-0000-4000-8000-000000000001",
    "pipeline_id": "44444444-0000-4000-8000-000000000001",
    "stage_id": "55555555-0000-4000-8000-000000000001",
    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
    "origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
    "owner_user_id": "11111111-0000-4000-8000-000000000001",
    "team_id": null,
    "value_amount": 8990000,
    "value_currency": "CLP",
    "value_amount_close_confirmed_at": null,
    "expected_close_at": "2026-10-15T00:00:00.000Z",
    "score": 72,
    "score_reason": "pidió hora para probar el auto y confirmó día",
    "scored_at": "2026-09-21T14:02:11.000Z",
    "temperature": "warm",
    "temperature_reason": "respondió pidiendo confirmar la hora y no ha vuelto a escribir",
    "temperature_at": "2026-09-21T14:02:11.000Z",
    "won_lost_reason": null,
    "closed_at": null,
    "created_by": "11111111-0000-4000-8000-000000000001",
    "created_at": "2026-09-18T13:44:02.115Z",
    "updated_at": "2026-09-21T14:02:11.000Z",
    "last_activity_at": "2026-09-21T14:02:11.000Z",
    "contact": {
      "id": "22222222-0000-4000-8000-000000000001",
      "name": "María González",
      "email": "[email protected]",
      "phone": "+56912345001"
    },
    "stage": {
      "id": "55555555-0000-4000-8000-000000000001",
      "name": "Nuevo",
      "slug": "new",
      "position": 0,
      "won_state": null,
      "pipeline_id": "44444444-0000-4000-8000-000000000001"
    },
    "pipeline": {
      "id": "44444444-0000-4000-8000-000000000001",
      "kind": "sales",
      "name": "Ventas"
    }
  }
}

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

PUT /leads/{id}/stage

Move a lead to another stage

THE move. The target must be a stage of the lead's own board and must be reachable from the current one — a stage graph that forbids the hop is a 400. Landing on a terminal column re-derives the lifecycle from that stage's category, so dragging a card to «Ganado» closes the lead exactly as PUT /leads/\{id\}/won does. Emits lead.stage_changed with changes.stage = \{ from, to \} and the author, plus the outcome Evento when the status actually flipped. A no-op move (already on that stage) emits nothing.

ParameterInTypeRequiredConstraints
idpathstringyespatrón `^(?:[0-9a-f]8-[0-9a-f]4-[0-9a-f]4-[0-9a-f]4-[0-9a-f]12\

Body

FieldTypeRequiredConstraints
stage_iduuidyes
reasonstring | nullmáx. 500
suggestion_iduuid
curl -X PUT https://api.vitrinadev.com/api/v1/leads/<id>/stage \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "stage_id": "55555555-0000-4000-8000-000000000002",
    "reason": "la llamamos y confirmó la hora"
  }'

Example response (200)

{
  "data": {
    "id": "99999999-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "display_id": "L-89",
    "display_seq": 89,
    "title": "María — prueba de manejo Corolla 2022",
    "notes": null,
    "status": "open",
    "source": "conversation",
    "source_ref": null,
    "intent": "buy",
    "contact_id": "22222222-0000-4000-8000-000000000001",
    "pipeline_id": "44444444-0000-4000-8000-000000000001",
    "stage_id": "55555555-0000-4000-8000-000000000002",
    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
    "origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
    "owner_user_id": "11111111-0000-4000-8000-000000000001",
    "team_id": null,
    "value_amount": 8990000,
    "value_currency": "CLP",
    "value_amount_close_confirmed_at": null,
    "expected_close_at": "2026-10-15T00:00:00.000Z",
    "score": 72,
    "score_reason": "pidió hora para probar el auto y confirmó día",
    "scored_at": "2026-09-21T14:02:11.000Z",
    "temperature": "warm",
    "temperature_reason": "respondió pidiendo confirmar la hora y no ha vuelto a escribir",
    "temperature_at": "2026-09-21T14:02:11.000Z",
    "won_lost_reason": null,
    "closed_at": null,
    "created_by": "11111111-0000-4000-8000-000000000001",
    "created_at": "2026-09-18T13:44:02.115Z",
    "updated_at": "2026-09-22T08:39:25.338Z",
    "last_activity_at": "2026-09-22T08:39:25.338Z",
    "contact": {
      "id": "22222222-0000-4000-8000-000000000001",
      "name": "María González",
      "email": "[email protected]",
      "phone": "+56912345001"
    },
    "stage": {
      "id": "55555555-0000-4000-8000-000000000002",
      "name": "Contactado",
      "slug": "contacted",
      "position": 1,
      "won_state": null,
      "pipeline_id": "44444444-0000-4000-8000-000000000001"
    },
    "pipeline": {
      "id": "44444444-0000-4000-8000-000000000001",
      "kind": "sales",
      "name": "Ventas"
    }
  }
}

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

PUT /leads/{id}/unqualify

Mark a lead as unqualified

There was never an opportunity here — the wrong product, the wrong workspace, a duplicate, a test. A DISTINCT outcome from lost, and the distinction is the point: unqualified leads are excluded from win rate on both sides, so qualifying early never looks like losing. The lead MOVES to the lowest-position stage of that category on its board, so the outcome and the column can never disagree — 400 when the board has no stage of that category, which is a board a person has to fix rather than a request to retry. status, closed_at and won_lost_reason are derived from the landing stage, and a lead.stage_changed Evento fires alongside the outcome one. Emits lead.unqualified.

ParameterInTypeRequiredConstraints
idpathstringyespatrón `^(?:[0-9a-f]8-[0-9a-f]4-[0-9a-f]4-[0-9a-f]4-[0-9a-f]12\

Body

FieldTypeRequiredConstraints
statuswon \lost \unqualified \
reasonstring | nullmáx. 500
value_amountnumber | null≥ 0
value_currencystring | nullmín. 1, máx. 8
curl -X PUT https://api.vitrinadev.com/api/v1/leads/<id>/unqualify \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "reason": "buscaba un servicio que no ofrecemos"
  }'

Example response (200)

{
  "data": {
    "id": "99999999-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "display_id": "L-89",
    "display_seq": 89,
    "title": "María — prueba de manejo Corolla 2022",
    "notes": null,
    "status": "unqualified",
    "source": "conversation",
    "source_ref": null,
    "intent": "buy",
    "contact_id": "22222222-0000-4000-8000-000000000001",
    "pipeline_id": "44444444-0000-4000-8000-000000000001",
    "stage_id": "55555555-0000-4000-8000-000000000003",
    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
    "origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
    "owner_user_id": "11111111-0000-4000-8000-000000000001",
    "team_id": null,
    "value_amount": 8990000,
    "value_currency": "CLP",
    "value_amount_close_confirmed_at": null,
    "expected_close_at": "2026-10-15T00:00:00.000Z",
    "score": 72,
    "score_reason": "pidió hora para probar el auto y confirmó día",
    "scored_at": "2026-09-21T14:02:11.000Z",
    "temperature": "warm",
    "temperature_reason": "respondió pidiendo confirmar la hora y no ha vuelto a escribir",
    "temperature_at": "2026-09-21T14:02:11.000Z",
    "won_lost_reason": "buscaba un servicio que no ofrecemos",
    "closed_at": "2026-09-22T08:44:10.331Z",
    "created_by": "11111111-0000-4000-8000-000000000001",
    "created_at": "2026-09-18T13:44:02.115Z",
    "updated_at": "2026-09-21T14:02:11.000Z",
    "last_activity_at": "2026-09-21T14:02:11.000Z",
    "contact": {
      "id": "22222222-0000-4000-8000-000000000001",
      "name": "María González",
      "email": "[email protected]",
      "phone": "+56912345001"
    },
    "stage": {
      "id": "55555555-0000-4000-8000-000000000003",
      "name": "No calificado",
      "slug": "unqualified",
      "position": 8,
      "won_state": null,
      "pipeline_id": "44444444-0000-4000-8000-000000000001"
    },
    "pipeline": {
      "id": "44444444-0000-4000-8000-000000000001",
      "kind": "sales",
      "name": "Ventas"
    }
  }
}

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

PUT /leads/{id}/won

Mark a lead as won

The opportunity closed in the workspace's favour. The lead MOVES to the lowest-position stage of that category on its board, so the outcome and the column can never disagree — 400 when the board has no stage of that category, which is a board a person has to fix rather than a request to retry. status, closed_at and won_lost_reason are derived from the landing stage, and a lead.stage_changed Evento fires alongside the outcome one. value_amount here is the REAL closing figure — distinct from any earlier estimate on the lead — and is the only status endpoint that acts on it: it overwrites the value and stamps value_amount_close_confirmed_at. Omit it to leave the estimate untouched. Emits lead.won.

ParameterInTypeRequiredConstraints
idpathstringyespatrón `^(?:[0-9a-f]8-[0-9a-f]4-[0-9a-f]4-[0-9a-f]4-[0-9a-f]12\

Body

FieldTypeRequiredConstraints
statuswon \lost \unqualified \
reasonstring | nullmáx. 500
value_amountnumber | null≥ 0
value_currencystring | nullmín. 1, máx. 8
curl -X PUT https://api.vitrinadev.com/api/v1/leads/<id>/won \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "reason": "cerró con financiamiento propio",
    "value_amount": 8750000,
    "value_currency": "CLP"
  }'

Example response (200)

{
  "data": {
    "id": "99999999-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "display_id": "L-89",
    "display_seq": 89,
    "title": "María — prueba de manejo Corolla 2022",
    "notes": null,
    "status": "won",
    "source": "conversation",
    "source_ref": null,
    "intent": "buy",
    "contact_id": "22222222-0000-4000-8000-000000000001",
    "pipeline_id": "44444444-0000-4000-8000-000000000001",
    "stage_id": "55555555-0000-4000-8000-000000000006",
    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
    "origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
    "owner_user_id": "11111111-0000-4000-8000-000000000001",
    "team_id": null,
    "value_amount": 8750000,
    "value_currency": "CLP",
    "value_amount_close_confirmed_at": "2026-09-22T08:41:32.967Z",
    "expected_close_at": "2026-10-15T00:00:00.000Z",
    "score": 72,
    "score_reason": "pidió hora para probar el auto y confirmó día",
    "scored_at": "2026-09-21T14:02:11.000Z",
    "temperature": "warm",
    "temperature_reason": "respondió pidiendo confirmar la hora y no ha vuelto a escribir",
    "temperature_at": "2026-09-21T14:02:11.000Z",
    "won_lost_reason": "cerró con financiamiento propio",
    "closed_at": "2026-09-22T08:41:32.977Z",
    "created_by": "11111111-0000-4000-8000-000000000001",
    "created_at": "2026-09-18T13:44:02.115Z",
    "updated_at": "2026-09-21T14:02:11.000Z",
    "last_activity_at": "2026-09-21T14:02:11.000Z",
    "contact": {
      "id": "22222222-0000-4000-8000-000000000001",
      "name": "María González",
      "email": "[email protected]",
      "phone": "+56912345001"
    },
    "stage": {
      "id": "55555555-0000-4000-8000-000000000006",
      "name": "Ganado",
      "slug": "won",
      "position": 6,
      "won_state": "won",
      "pipeline_id": "44444444-0000-4000-8000-000000000001"
    },
    "pipeline": {
      "id": "44444444-0000-4000-8000-000000000001",
      "kind": "sales",
      "name": "Ventas"
    }
  }
}

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

POST /leads/from-conversation

Convert a conversation into a lead

Opens an opportunity out of a conversation that already has a contact: the contact is resolved from the thread (404 when it has none), the conversation becomes the lead's ORIGIN and its primary link, and a lead opened on a thread somebody already owns inherits that owner unless you name one. Emits lead.created with source: conversation.

Body

FieldTypeRequiredConstraints
conversation_iduuidyes
titlestringmín. 1, máx. 500
notesstringmáx. 20000
pipeline_iduuid
stage_iduuid
owner_user_idstring | null
value_amountnumber | null≥ 0
curl -X POST https://api.vitrinadev.com/api/v1/leads/from-conversation \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
    "title": "María — prueba de manejo Corolla 2022",
    "pipeline_id": "44444444-0000-4000-8000-000000000001",
    "owner_user_id": "11111111-0000-4000-8000-000000000001"
  }'

Example response (201)

{
  "data": {
    "id": "99999999-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "display_id": "L-89",
    "display_seq": 89,
    "title": "María — prueba de manejo Corolla 2022",
    "notes": null,
    "status": "open",
    "source": "conversation",
    "source_ref": null,
    "intent": "buy",
    "contact_id": "22222222-0000-4000-8000-000000000001",
    "pipeline_id": "44444444-0000-4000-8000-000000000001",
    "stage_id": "55555555-0000-4000-8000-000000000001",
    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
    "origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
    "owner_user_id": "11111111-0000-4000-8000-000000000001",
    "team_id": null,
    "value_amount": 8990000,
    "value_currency": "CLP",
    "value_amount_close_confirmed_at": null,
    "expected_close_at": "2026-10-15T00:00:00.000Z",
    "score": 72,
    "score_reason": "pidió hora para probar el auto y confirmó día",
    "scored_at": "2026-09-21T14:02:11.000Z",
    "temperature": "warm",
    "temperature_reason": "respondió pidiendo confirmar la hora y no ha vuelto a escribir",
    "temperature_at": "2026-09-21T14:02:11.000Z",
    "won_lost_reason": null,
    "closed_at": null,
    "created_by": "11111111-0000-4000-8000-000000000001",
    "created_at": "2026-09-18T13:44:02.115Z",
    "updated_at": "2026-09-21T14:02:11.000Z",
    "last_activity_at": "2026-09-21T14:02:11.000Z",
    "contact": {
      "id": "22222222-0000-4000-8000-000000000001",
      "name": "María González",
      "email": "[email protected]",
      "phone": "+56912345001"
    },
    "stage": {
      "id": "55555555-0000-4000-8000-000000000001",
      "name": "Nuevo",
      "slug": "new",
      "position": 0,
      "won_state": null,
      "pipeline_id": "44444444-0000-4000-8000-000000000001"
    },
    "pipeline": {
      "id": "44444444-0000-4000-8000-000000000001",
      "kind": "sales",
      "name": "Ventas"
    }
  }
}

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

GET /leads/funnel

Lead funnel by stage

One row per stage of a board, in position order: how many OPEN leads sit there, what they are worth, and the MEDIAN hours a lead has been sitting on that stage — the number that says where the funnel is stuck. Money is value_amount + value_currency and the pair travels together: a workspace may work in more than one currency, so the analytics endpoints break their totals down by_currency and flag is_mixed_currency rather than summing across them.

ParameterInTypeRequiredConstraints
pipeline_idquerystringyesmín. 1
card_kindqueryall \leads \solicitudes
curl https://api.vitrinadev.com/api/v1/leads/funnel \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": [
    {
      "stage_id": "55555555-0000-4000-8000-000000000001",
      "stage_name": "Nuevo",
      "stage_slug": "new",
      "position": 0,
      "open_count": 6,
      "total_value": 193990000,
      "total_value_by_currency": {
        "CLP": 193990000
      },
      "is_mixed_currency": false,
      "median_time_in_stage_hours": 350.91
    },
    {
      "stage_id": "55555555-0000-4000-8000-000000000002",
      "stage_name": "Contactado",
      "stage_slug": "contacted",
      "position": 1,
      "open_count": 2,
      "total_value": 15450000,
      "total_value_by_currency": {
        "CLP": 15450000
      },
      "is_mixed_currency": false,
      "median_time_in_stage_hours": 427.53
    }
  ]
}

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

POST /leads/import

Bulk-import leads

Up to 2 000 rows in one call — a trade-show list, a spreadsheet, a migration from another system. Each row is matched to an existing contact by contact_email or contact_phone, and to a stage by stage_slug / an owner by owner_email. PARTIAL SUCCESS IS THE NORMAL OUTCOME and the status is 200 either way: the report says how many rows landed and gives a per-row ok with the lead_id or the reason it failed. Imported leads skip owner rotation — the file decides the owner.

Body

FieldTypeRequiredConstraints
rowsobject[]yes
curl -X POST https://api.vitrinadev.com/api/v1/leads/import \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "rows": [
      {
        "title": "Consulta feria de autos — stand 12",
        "contact_email": "[email protected]",
        "value_amount": 7500000,
        "currency": "CLP",
        "stage_slug": "new",
        "source": "import"
      },
      {
        "title": "Consulta feria de autos — stand 14",
        "contact_phone": "+56988887777",
        "owner_email": "[email protected]"
      }
    ]
  }'

Example response (200)

{
  "data": {
    "total": 2,
    "inserted": 1,
    "failed": 1,
    "rows": [
      {
        "row": 1,
        "ok": true,
        "lead_id": "99999999-0000-4000-8000-000000000001"
      },
      {
        "row": 2,
        "ok": false,
        "error": "No contact matched +56988887777"
      }
    ]
  }
}

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

GET /leads/kanban

Leads grouped by stage (board view)

The board itself: every stage with its leads already nested, up to 200 per column — one request instead of one per column. The cards carry the same embeds and the same scope gating as GET /leads.

ParameterInTypeRequiredConstraints
pipeline_idquerystringyesmín. 1
card_kindqueryall \leads \solicitudes
curl https://api.vitrinadev.com/api/v1/leads/kanban \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "stages": [
      {
        "id": "55555555-0000-4000-8000-000000000001",
        "name": "Nuevo",
        "slug": "new",
        "position": 0,
        "won_state": null,
        "is_terminal": false,
        "leads": [
          {
            "id": "99999999-0000-4000-8000-000000000001",
            "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
            "display_id": "L-89",
            "display_seq": 89,
            "title": "María — prueba de manejo Corolla 2022",
            "notes": null,
            "status": "open",
            "source": "conversation",
            "source_ref": null,
            "intent": "buy",
            "contact_id": "22222222-0000-4000-8000-000000000001",
            "pipeline_id": "44444444-0000-4000-8000-000000000001",
            "stage_id": "55555555-0000-4000-8000-000000000001",
            "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
            "origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
            "owner_user_id": "11111111-0000-4000-8000-000000000001",
            "team_id": null,
            "value_amount": 8990000,
            "value_currency": "CLP",
            "value_amount_close_confirmed_at": null,
            "expected_close_at": "2026-10-15T00:00:00.000Z",
            "score": 72,
            "score_reason": "pidió hora para probar el auto y confirmó día",
            "scored_at": "2026-09-21T14:02:11.000Z",
            "temperature": "warm",
            "temperature_reason": "respondió pidiendo confirmar la hora y no ha vuelto a escribir",
            "temperature_at": "2026-09-21T14:02:11.000Z",
            "won_lost_reason": null,
            "closed_at": null,
            "created_by": "11111111-0000-4000-8000-000000000001",
            "created_at": "2026-09-18T13:44:02.115Z",
            "updated_at": "2026-09-21T14:02:11.000Z",
            "last_activity_at": "2026-09-21T14:02:11.000Z",
            "contact": {
              "id": "22222222-0000-4000-8000-000000000001",
              "name": "María González",
              "email": "[email protected]",
              "phone": "+56912345001"
            },
            "stage": {
              "id": "55555555-0000-4000-8000-000000000001",
              "name": "Nuevo",
              "slug": "new",
              "position": 0,
              "won_state": null,
              "pipeline_id": "44444444-0000-4000-8000-000000000001"
            },
            "pipeline": {
              "id": "44444444-0000-4000-8000-000000000001",
              "kind": "sales",
              "name": "Ventas"
            }
          }
        ]
      },
      {
        "id": "55555555-0000-4000-8000-000000000002",
        "name": "Contactado",
        "slug": "contacted",
        "position": 1,
        "won_state": null,
        "is_terminal": false,
        "leads": [
          {
            "id": "99999999-0000-4000-8000-000000000002",
            "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
            "display_id": "L-90",
            "display_seq": 90,
            "title": "Consulta por financiamiento — Ana Soto",
            "notes": null,
            "status": "open",
            "source": "website",
            "source_ref": null,
            "intent": "financing",
            "contact_id": "22222222-0000-4000-8000-000000000001",
            "pipeline_id": "44444444-0000-4000-8000-000000000001",
            "stage_id": "55555555-0000-4000-8000-000000000002",
            "conversation_id": null,
            "origin_conversation_id": null,
            "owner_user_id": null,
            "team_id": null,
            "value_amount": null,
            "value_currency": "CLP",
            "value_amount_close_confirmed_at": null,
            "expected_close_at": null,
            "score": null,
            "score_reason": null,
            "scored_at": null,
            "temperature": null,
            "temperature_reason": null,
            "temperature_at": null,
            "won_lost_reason": null,
            "closed_at": null,
            "created_by": null,
            "created_at": "2026-09-19T09:12:44.000Z",
            "updated_at": "2026-09-19T09:12:44.000Z",
            "last_activity_at": "2026-09-19T09:12:44.000Z",
            "contact": {
              "id": "22222222-0000-4000-8000-000000000001",
              "name": "Ana Soto",
              "email": null,
              "phone": "+56912345001"
            },
            "stage": {
              "id": "55555555-0000-4000-8000-000000000002",
              "name": "Contactado",
              "slug": "contacted",
              "position": 1,
              "won_state": null,
              "pipeline_id": "44444444-0000-4000-8000-000000000001"
            },
            "pipeline": {
              "id": "44444444-0000-4000-8000-000000000001",
              "kind": "sales",
              "name": "Ventas"
            }
          }
        ]
      }
    ]
  }
}

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

GET /leads/stats

Lead score bands

The count in each SCORE band — cold 0–39, warm 40–69, hot 70–100, unscored — plus the average score. Bands over lead.score, not over the recorded temperature judgement; the two are different facts about a lead and neither is derived from the other.

ParameterInTypeRequiredConstraints
pipeline_idquerystringnomín. 1
curl https://api.vitrinadev.com/api/v1/leads/stats \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "total": 16,
    "hot": 2,
    "warm": 0,
    "cold": 8,
    "unscored": 6,
    "avg_score": 28.5
  }
}

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

GET /leads/summary

Lead pipeline summary

How many leads are open, won, lost and unqualified, and what they are worth. Optionally narrowed to one board with pipeline_id. Money is value_amount + value_currency and the pair travels together: a workspace may work in more than one currency, so the analytics endpoints break their totals down by_currency and flag is_mixed_currency rather than summing across them.

ParameterInTypeRequiredConstraints
pipeline_idquerystringnomín. 1
curl https://api.vitrinadev.com/api/v1/leads/summary \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "open": 13,
    "won": 2,
    "lost": 1,
    "unqualified": 0,
    "total_value_open": 194077750,
    "total_value_won": 2499000,
    "total_value_open_by_currency": {
      "CLP": 193990000,
      "USD": 42750
    },
    "total_value_won_by_currency": {
      "CLP": 2499000
    },
    "is_mixed_currency": true,
    "is_mixed_currency_open": true
  }
}

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

GET /leads/win-rate/{dimension}

Win rate by owner, team or source

Won / (won + lost) grouped by owner_user_id, team_id or source. UNQUALIFIED leads are excluded from both sides on purpose: they were never a sale to win or lose, and counting them as losses would punish whoever is best at qualifying early. key is the owner or team uuid, or the source name — and the literal string unassigned for the bucket of closed leads that carried no owner or team at all.

ParameterInTypeRequiredConstraints
dimensionpathowner_user_id \team_id \source
pipeline_idquerystringnomín. 1
curl https://api.vitrinadev.com/api/v1/leads/win-rate/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": [
    {
      "dimension": "owner_user_id",
      "key": "11111111-0000-4000-8000-000000000001",
      "won": 2,
      "lost": 1,
      "win_rate": 0.6666666666666666
    }
  ]
}

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

On this page