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/lead-outreach/statusFirst-touch status for one lead source
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
PUT/leads/{id}/deal-termsSet a lead's deal terms (human)
DELETE/leads/{id}/deal-terms/suggestions/{field}Dismiss an AI deal-term suggestion (keep the human value)
GET/leads/{id}/first-touchOne lead's first-touch outcome
POST/leads/{id}/first-touch/retrySend the first message again, after fixing the buyer phone
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}/lineaSet a lead's Línea (a person's choice)
PUT/leads/{id}/lostMark a lead as lost
PUT/leads/{id}/pipelineMove a lead to another board
PUT/leads/{id}/reopenReopen a closed lead
POST/leads/{id}/reviewRun the lead reviewer on this lead now
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 /lead-outreach/status

First-touch status for one lead source

Health and recent outcomes of the proactive first message for one lead source (chileautos, yapo, mercadolibre, website, …): which rail it uses, whether its account is usable, sends today, and the latest outcomes with the reason a lead was skipped.

website is ON by default for every workspace, and a portal (chileautos, mercadolibre, yapo) is ON by default once the workspace has an enabled marketplace account for it. With no block of its own such a source borrows the account and template chain of the first enabled source (config_origin: inherited, named in inherited_from), or uses the partner WhatsApp account and Vitrina's own templates — for a portal, the workspace's own first-contact templates first, then Vitrina's fallback ones (platform_default). An explicit block, enabled: false included, always wins. templates lists the WhatsApp ladder in send order with each template's state on that account: only APPROVED templates are ever sent; a PENDING or REJECTED one is shown so it is not silently skipped, and status: null means it is not on that account. When no template can be sent the first message goes by email, from the workspace mailbox. Each recent row carries its lead’s contact_id (one row per person on screen) and, on an error row, the classified error_code (undeliverable is Meta 131026; provider_frequency_capped and recipient_experiment are Meta holding marketing back), never the provider’s message.

ParameterInTypeRequiredConstraints
sourcequerystringyesmín. 1, máx. 64
curl https://api.vitrinadev.com/api/v1/lead-outreach/status \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "source": "website",
    "enabled": true,
    "contact_method": "whatsapp",
    "template_name": "contacto_sitio_web_nombre",
    "account": {
      "id": "c3c3c3c3-0000-4000-8000-000000000001",
      "name": "WhatsApp",
      "enabled": true
    },
    "account_ok": true,
    "config_origin": "platform_default",
    "inherited_from": null,
    "templates": [
      {
        "name": "contacto_sitio_web_nombre",
        "language": "es",
        "status": "APPROVED",
        "category": "MARKETING",
        "rejection_reason": null,
        "synced_at": "2026-09-25T13:00:00.000Z"
      },
      {
        "name": "contacto_sitio_web",
        "language": "es",
        "status": "PENDING",
        "category": "UTILITY",
        "rejection_reason": null,
        "synced_at": "2026-09-25T13:00:00.000Z"
      },
      {
        "name": "contacto_sitio_web_generico",
        "language": "es",
        "status": null,
        "category": null,
        "rejection_reason": null,
        "synced_at": null
      }
    ],
    "sent_today": 3,
    "daily_cap": 100000,
    "recent": [
      {
        "id": "d4d4d4d4-0000-4000-8000-000000000001",
        "status": "skipped",
        "channel": "whatsapp",
        "skip_reason": "no_phone",
        "error_class": null,
        "created_at": "2026-09-25T12:41:00.000Z",
        "conversation_id": "e5e5e5e5-0000-4000-8000-000000000001",
        "lead": {
          "id": "f6f6f6f6-0000-4000-8000-000000000001",
          "display_id": "L-42",
          "title": "Consulta desde el sitio web",
          "conversation_id": "e5e5e5e5-0000-4000-8000-000000000001"
        }
      }
    ]
  }
}

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

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.

fields=summary answers the SAME page with only the identifying and funnel scalars per row — id, display_id, title, status, source, intent, pipeline_id, stage_id, contact_id, value_amount, value_currency, owner_user_id, score, temperature, created_at, last_activity_at, closed_at, origin_conversation_id — plus stage_name and contact_name when the caller holds the scope that embed rides (stages:read, contacts:read); a name the caller may not read is ABSENT, never null. meta is unchanged. A 200-row page is ~525k characters at full (the default, byte-identical to omitting the parameter) and a fraction of that at summary; page with summary to choose, then GET /leads/\{id\} for the ones you need whole.

On a clinic (healthcare) workspace, value_amount on a WON lead is cash the patient paid, credited to that lead; before won it is the open presupuesto (a quote), not a sale. For return, ROAS, paid per ad or which ad brought paying patients, call ads_attributed_sales_list; never sum lead values.

ParameterInTypeRequiredConstraints
pipeline_idquerystringnomín. 1
stage_idquerystringnomín. 1
statusqueryopen \won \lost \
sourcequeryconversation \marketplace \manual \
intentquerybuy \sell \financing \
acquisition_typequerycompra \consignacion \unknown
owner_user_idqueryuuidno
team_idqueryuuidno
contact_idquerystringnomín. 1
linea_idqueryuuidno
project_idqueryuuidno
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
first_touchqueryfailedno
first_touch_fromquerystringnodate-time
first_touch_toquerystringnodate-time
pagequeryintegerno≥ 1, por defecto 1
page_sizequeryintegerno≥ 1, ≤ 200, por defecto 25
fieldsquerysummary \fullno
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,
      "lost_reason": null,
      "lost_reason_source": null,
      "lost_reason_at": null,
      "payment_method": "financing",
      "down_payment_amount": 4000000,
      "budget_max_amount": 11500000,
      "has_trade_in": false,
      "trade_in_description": null,
      "offer_amount": null,
      "offer_note": null,
      "financing_term_months": [
        24,
        36
      ],
      "installment_amount": null,
      "max_down_payment_amount": null,
      "net_income_amount": 1900000,
      "employer": "CGE",
      "employment_tenure": "15 años",
      "budget_currency": null,
      "down_payment_currency": null,
      "mortgage_preapproval_status": null,
      "mortgage_bank": null,
      "mortgage_preapproved_amount": null,
      "mortgage_preapproved_currency": null,
      "meets_rent_income_requirement": null,
      "has_guarantor": null,
      "deal_terms_meta": {
        "payment_method": {
          "source": "ai",
          "actor_id": null,
          "actor_label": null,
          "at": "2026-09-21T14:02:11.000Z"
        },
        "down_payment_amount": {
          "source": "human",
          "actor_id": "11111111-0000-4000-8000-000000000001",
          "actor_label": "Camila Rojas",
          "at": "2026-09-21T15:10:00.000Z"
        }
      },
      "deal_terms_suggestions": {},
      "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,
      "lost_reason": null,
      "lost_reason_source": null,
      "lost_reason_at": null,
      "payment_method": "financing",
      "down_payment_amount": 4000000,
      "budget_max_amount": 11500000,
      "has_trade_in": false,
      "trade_in_description": null,
      "offer_amount": null,
      "offer_note": null,
      "financing_term_months": [
        24,
        36
      ],
      "installment_amount": null,
      "max_down_payment_amount": null,
      "net_income_amount": 1900000,
      "employer": "CGE",
      "employment_tenure": "15 años",
      "budget_currency": null,
      "down_payment_currency": null,
      "mortgage_preapproval_status": null,
      "mortgage_bank": null,
      "mortgage_preapproved_amount": null,
      "mortgage_preapproved_currency": null,
      "meets_rent_income_requirement": null,
      "has_guarantor": null,
      "deal_terms_meta": {
        "payment_method": {
          "source": "ai",
          "actor_id": null,
          "actor_label": null,
          "at": "2026-09-21T14:02:11.000Z"
        },
        "down_payment_amount": {
          "source": "human",
          "actor_id": "11111111-0000-4000-8000-000000000001",
          "actor_label": "Camila Rojas",
          "at": "2026-09-21T15:10:00.000Z"
        }
      },
      "deal_terms_suggestions": {},
      "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 | null—máx. 20000
sourceconversation \marketplace \manual \
intentbuy \sell \financing \
acquisition_typecompra \consignacion \unknown
source_refstring | null—máx. 500
value_amountnumber | null—≥ 0
value_currencystring | null—mín. 1, máx. 8
expected_close_atstring | null—date-time
owner_user_idstring | null—
team_idstring | null—
scoreinteger | null—≥ 0, ≤ 100
project_idstring | null—
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,
    "lost_reason": null,
    "lost_reason_source": null,
    "lost_reason_at": null,
    "payment_method": "financing",
    "down_payment_amount": 4000000,
    "budget_max_amount": 11500000,
    "has_trade_in": false,
    "trade_in_description": null,
    "offer_amount": null,
    "offer_note": null,
    "financing_term_months": [
      24,
      36
    ],
    "installment_amount": null,
    "max_down_payment_amount": null,
    "net_income_amount": 1900000,
    "employer": "CGE",
    "employment_tenure": "15 años",
    "budget_currency": null,
    "down_payment_currency": null,
    "mortgage_preapproval_status": null,
    "mortgage_bank": null,
    "mortgage_preapproved_amount": null,
    "mortgage_preapproved_currency": null,
    "meets_rent_income_requirement": null,
    "has_guarantor": null,
    "deal_terms_meta": {
      "payment_method": {
        "source": "ai",
        "actor_id": null,
        "actor_label": null,
        "at": "2026-09-21T14:02:11.000Z"
      },
      "down_payment_amount": {
        "source": "human",
        "actor_id": "11111111-0000-4000-8000-000000000001",
        "actor_label": "Camila Rojas",
        "at": "2026-09-21T15:10:00.000Z"
      }
    },
    "deal_terms_suggestions": {},
    "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}|L-\d+)$
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. On a clinic workspace it also carries origin: the Origen of its contact, Meta (with the ad), Orgánico (a linked contact with no ad touch) or Sin dato (no contact) — the attribution Vitrina Ads reports. On a clinic (healthcare) workspace, value_amount on a WON lead is cash the patient paid, credited to that lead; before won it is the open presupuesto (a quote), not a sale. For return, ROAS, paid per ad or which ad brought paying patients, call ads_attributed_sales_list; never sum lead values. \{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}|L-\d+)$
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,
    "lost_reason": null,
    "lost_reason_source": null,
    "lost_reason_at": null,
    "payment_method": "financing",
    "down_payment_amount": 4000000,
    "budget_max_amount": 11500000,
    "has_trade_in": false,
    "trade_in_description": null,
    "offer_amount": null,
    "offer_note": null,
    "financing_term_months": [
      24,
      36
    ],
    "installment_amount": null,
    "max_down_payment_amount": null,
    "net_income_amount": 1900000,
    "employer": "CGE",
    "employment_tenure": "15 años",
    "budget_currency": null,
    "down_payment_currency": null,
    "mortgage_preapproval_status": null,
    "mortgage_bank": null,
    "mortgage_preapproved_amount": null,
    "mortgage_preapproved_currency": null,
    "meets_rent_income_requirement": null,
    "has_guarantor": null,
    "deal_terms_meta": {
      "payment_method": {
        "source": "ai",
        "actor_id": null,
        "actor_label": null,
        "at": "2026-09-21T14:02:11.000Z"
      },
      "down_payment_amount": {
        "source": "human",
        "actor_id": "11111111-0000-4000-8000-000000000001",
        "actor_label": "Camila Rojas",
        "at": "2026-09-21T15:10:00.000Z"
      }
    },
    "deal_terms_suggestions": {},
    "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}|L-\d+)$

Body

FieldTypeRequiredConstraints
titlestring—mín. 1, máx. 500
notesstring | null—máx. 20000
value_amountnumber | null—≥ 0
value_currencystring | null—mín. 1, máx. 8
expected_close_atstring | null—date-time
owner_user_idstring | null—
team_idstring | null—
scoreinteger | null—≥ 0, ≤ 100
project_idstring | null—
temperaturehot \warm \cold
temperature_reasonstring | null—máx. 500
lost_reasonbought_elsewhere \financing_rejected \unit_unavailable \
acquisition_typecompra \consignacion \unknown
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,
    "lost_reason": null,
    "lost_reason_source": null,
    "lost_reason_at": null,
    "payment_method": "financing",
    "down_payment_amount": 4000000,
    "budget_max_amount": 11500000,
    "has_trade_in": false,
    "trade_in_description": null,
    "offer_amount": null,
    "offer_note": null,
    "financing_term_months": [
      24,
      36
    ],
    "installment_amount": null,
    "max_down_payment_amount": null,
    "net_income_amount": 1900000,
    "employer": "CGE",
    "employment_tenure": "15 años",
    "budget_currency": null,
    "down_payment_currency": null,
    "mortgage_preapproval_status": null,
    "mortgage_bank": null,
    "mortgage_preapproved_amount": null,
    "mortgage_preapproved_currency": null,
    "meets_rent_income_requirement": null,
    "has_guarantor": null,
    "deal_terms_meta": {
      "payment_method": {
        "source": "ai",
        "actor_id": null,
        "actor_label": null,
        "at": "2026-09-21T14:02:11.000Z"
      },
      "down_payment_amount": {
        "source": "human",
        "actor_id": "11111111-0000-4000-8000-000000000001",
        "actor_label": "Camila Rojas",
        "at": "2026-09-21T15:10:00.000Z"
      }
    },
    "deal_terms_suggestions": {},
    "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}|L-\d+)$
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}|L-\d+)$

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,
    "lost_reason": null,
    "lost_reason_source": null,
    "lost_reason_at": null,
    "payment_method": "financing",
    "down_payment_amount": 4000000,
    "budget_max_amount": 11500000,
    "has_trade_in": false,
    "trade_in_description": null,
    "offer_amount": null,
    "offer_note": null,
    "financing_term_months": [
      24,
      36
    ],
    "installment_amount": null,
    "max_down_payment_amount": null,
    "net_income_amount": 1900000,
    "employer": "CGE",
    "employment_tenure": "15 años",
    "budget_currency": null,
    "down_payment_currency": null,
    "mortgage_preapproval_status": null,
    "mortgage_bank": null,
    "mortgage_preapproved_amount": null,
    "mortgage_preapproved_currency": null,
    "meets_rent_income_requirement": null,
    "has_guarantor": null,
    "deal_terms_meta": {
      "payment_method": {
        "source": "ai",
        "actor_id": null,
        "actor_label": null,
        "at": "2026-09-21T14:02:11.000Z"
      },
      "down_payment_amount": {
        "source": "human",
        "actor_id": "11111111-0000-4000-8000-000000000001",
        "actor_label": "Camila Rojas",
        "at": "2026-09-21T15:10:00.000Z"
      }
    },
    "deal_terms_suggestions": {},
    "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}|L-\d+)$
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}|L-\d+)$

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}|L-\d+)$
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

PUT /leads/{id}/deal-terms

Set a lead's deal terms (human)

Deal terms are twenty-one nullable facts: payment_method (cash | financing | credit_card | debit_card | mixed), down_payment_amount, budget_max_amount, offer_amount (in the lead's value_currency), has_trade_in (null = unknown, false = none), trade_in_description, offer_note, and the financing ones: financing_term_months (plazo(s) = number of cuotas, e.g. [12, 18, 24]), installment_amount (valor cuota), max_down_payment_amount (pie máximo), net_income_amount (renta líquida), employer, employment_tenure, and the real-estate ones: budget_currency / down_payment_currency / mortgage_preapproved_currency (UF | CLP, the currency the amount was stated in; null = the lead's value_currency), mortgage_preapproval_status (not_started | in_progress | pre_approved | rejected), mortgage_bank, mortgage_preapproved_amount, meets_rent_income_requirement and has_guarantor (aval) for an arriendo. Each carries provenance in deal_terms_meta (ai | human); an AI writer never overwrites a human-owned field — a disagreeing AI reading is kept in deal_terms_suggestions instead. Every field in the body becomes human-owned (source: human, the caller as actor) and its pending suggestion is removed; null clears a value but keeps it human-owned, so the next AI read proposes instead of re-filling it. Accepting a suggestion is this call with the suggested value. applicant_full_name / applicant_rut are the applicant's identity and are written to the lead's PRIMARY contact (legal_name, tax_id kind rut) — no provenance; 400 when the lead has no contact or the RUT check digit is wrong, 409 when another contact already holds that RUT. Changes land on the lead's timeline as deal_terms_changed. Returns the Oportunidad lead object. \{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}|L-\d+)$

Body

FieldTypeRequiredConstraints
payment_methodcash \financing \credit_card \
down_payment_amountnumber | null—≥ 0
budget_max_amountnumber | null—≥ 0
has_trade_inboolean | null—
trade_in_descriptionstring | null—mín. 1, máx. 300
offer_amountnumber | null—≥ 0
offer_notestring | null—mín. 1, máx. 300
financing_term_monthsarray | null—
installment_amountnumber | null—≥ 0
max_down_payment_amountnumber | null—≥ 0
net_income_amountnumber | null—≥ 0
employerstring | null—mín. 1, máx. 300
employment_tenurestring | null—mín. 1, máx. 300
budget_currencyUF \CLP—
down_payment_currencyUF \CLP—
mortgage_preapproval_statusnot_started \in_progress \pre_approved \
mortgage_bankstring | null—mín. 1, máx. 300
mortgage_preapproved_amountnumber | null—≥ 0
mortgage_preapproved_currencyUF \CLP—
meets_rent_income_requirementboolean | null—
has_guarantorboolean | null—
applicant_full_namestring | null—mín. 1, máx. 200
applicant_rutstring | null—mín. 1, máx. 20
curl -X PUT https://api.vitrinadev.com/api/v1/leads/<id>/deal-terms \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "payment_method": "financing",
    "financing_term_months": [
      12,
      18,
      24
    ],
    "net_income_amount": 1900000,
    "applicant_rut": "12.345.678-5"
  }'

Example response (200)

{
  "id": "99999999-0000-4000-8000-000000000001",
  "display_id": "L-89",
  "title": "María — prueba de manejo Corolla 2022",
  "status": "open",
  "source": "mercadolibre",
  "stage": {
    "id": "55555555-0000-4000-8000-000000000001",
    "name": "Contactado"
  },
  "pipeline": {
    "id": "44444444-0000-4000-8000-000000000001",
    "name": "Venta de vehículos"
  },
  "score": 72,
  "score_reason": "pidió hora para probar el auto y confirmó día",
  "scored_at": "2026-09-21T14:02:11.000Z",
  "facts_changed_at": "2026-09-21T13:40:00.000Z",
  "temperature": "warm",
  "value_currency": "CLP",
  "deal_terms": {
    "payment_method": "financing",
    "down_payment_amount": 4000000,
    "budget_max_amount": 11500000,
    "has_trade_in": true,
    "trade_in_description": "Chery Tiggo 2 Pro 2023, 32.000 km",
    "offer_amount": 10500000,
    "offer_note": "Financiado · pendiente de aprobación",
    "financing_term_months": [
      12,
      18,
      24
    ],
    "installment_amount": null,
    "max_down_payment_amount": 5000000,
    "net_income_amount": 1900000,
    "employer": "CGE",
    "employment_tenure": "15 años",
    "budget_currency": null,
    "down_payment_currency": null,
    "mortgage_preapproval_status": null,
    "mortgage_bank": null,
    "mortgage_preapproved_amount": null,
    "mortgage_preapproved_currency": null,
    "meets_rent_income_requirement": null,
    "has_guarantor": null
  },
  "deal_terms_meta": {
    "payment_method": {
      "source": "ai",
      "actor_id": null,
      "actor_label": null,
      "at": "2026-09-21T14:02:11.000Z"
    },
    "offer_amount": {
      "source": "human",
      "actor_id": "11111111-0000-4000-8000-000000000001",
      "actor_label": "Camila Rojas",
      "at": "2026-09-21T15:10:00.000Z"
    }
  },
  "deal_terms_suggestions": {
    "offer_amount": {
      "value": 10800000,
      "reason": "subió la oferta a 10,8 millones",
      "at": "2026-09-22T10:00:00.000Z"
    }
  }
}

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

DELETE /leads/{id}/deal-terms/suggestions/{field}

Dismiss an AI deal-term suggestion (keep the human value)

«Mantener la mía»: removes the pending AI suggestion for one field and leaves the human value as it is. Idempotent — no suggestion is a no-op. Returns the Oportunidad lead object. \{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}|L-\d+)$
fieldpathpayment_method \down_payment_amount \budget_max_amount \
curl -X DELETE https://api.vitrinadev.com/api/v1/leads/<id>/deal-terms/suggestions/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "id": "99999999-0000-4000-8000-000000000001",
  "display_id": "L-89",
  "title": "María — prueba de manejo Corolla 2022",
  "status": "open",
  "source": "mercadolibre",
  "stage": {
    "id": "55555555-0000-4000-8000-000000000001",
    "name": "Contactado"
  },
  "pipeline": {
    "id": "44444444-0000-4000-8000-000000000001",
    "name": "Venta de vehículos"
  },
  "score": 72,
  "score_reason": "pidió hora para probar el auto y confirmó día",
  "scored_at": "2026-09-21T14:02:11.000Z",
  "facts_changed_at": "2026-09-21T13:40:00.000Z",
  "temperature": "warm",
  "value_currency": "CLP",
  "deal_terms": {
    "payment_method": "financing",
    "down_payment_amount": 4000000,
    "budget_max_amount": 11500000,
    "has_trade_in": true,
    "trade_in_description": "Chery Tiggo 2 Pro 2023, 32.000 km",
    "offer_amount": 10500000,
    "offer_note": "Financiado · pendiente de aprobación",
    "financing_term_months": [
      12,
      18,
      24
    ],
    "installment_amount": null,
    "max_down_payment_amount": 5000000,
    "net_income_amount": 1900000,
    "employer": "CGE",
    "employment_tenure": "15 años",
    "budget_currency": null,
    "down_payment_currency": null,
    "mortgage_preapproval_status": null,
    "mortgage_bank": null,
    "mortgage_preapproved_amount": null,
    "mortgage_preapproved_currency": null,
    "meets_rent_income_requirement": null,
    "has_guarantor": null
  },
  "deal_terms_meta": {
    "payment_method": {
      "source": "ai",
      "actor_id": null,
      "actor_label": null,
      "at": "2026-09-21T14:02:11.000Z"
    },
    "offer_amount": {
      "source": "human",
      "actor_id": "11111111-0000-4000-8000-000000000001",
      "actor_label": "Camila Rojas",
      "at": "2026-09-21T15:10:00.000Z"
    }
  },
  "deal_terms_suggestions": {
    "offer_amount": {
      "value": 10800000,
      "reason": "subió la oferta a 10,8 millones",
      "at": "2026-09-22T10:00:00.000Z"
    }
  }
}

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

GET /leads/{id}/first-touch

One lead's first-touch outcome

Whether the proactive first message for this lead went out, and if not, why: status (none = never attempted, pending, sent, skipped, error), the skip_reason (invalid_phone carries the number the portal delivered in skip_evidence.raw_phone), the classified error_class/error_code of a failed send (undeliverable = WhatsApp could not reach the number), the conversation it opened, and the contact's current phone with phone_dialable. retryable says whether POST /leads/\{id\}/first-touch/retry would be accepted; retry_blocked_reason names why not (already_sent, in_progress, do_not_contact, source_disabled, no_contact).

ParameterInTypeRequiredConstraints
idpathstringyespatrón ^(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|L-\d+)$
curl https://api.vitrinadev.com/api/v1/leads/<id>/first-touch \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "status": "skipped",
    "skip_reason": "invalid_phone",
    "skip_evidence": {
      "raw_phone": "+90773361"
    },
    "error_class": null,
    "error_code": null,
    "conversation_id": null,
    "source": "chileautos",
    "contact_id": "01a14658-50d7-764b-89dd-601c87c24fd7",
    "phone": "+90773361",
    "phone_dialable": false,
    "retryable": true,
    "retry_blocked_reason": null
  }
}

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

POST /leads/{id}/first-touch/retry

Send the first message again, after fixing the buyer phone

Re-runs the proactive first touch for a lead whose first attempt was skipped or failed — typically because the portal delivered a phone WhatsApp cannot reach. Pass phone to correct the contact first (it must pass the dial gate: E.164 or a 9-digit Chilean number, else 400 invalid_phone). Uses the configured template and account of the lead's source, reopens the buyer's original inquiry as the context card, and answers with the rail's outcome: sent with the new conversation_id, or sent: false with the reason (burst_skip, no_template, daily_cap, …). 409 with the blocking reason when the first touch already went out, is in flight, the person asked not to be contacted, or the source is off.

ParameterInTypeRequiredConstraints
idpathstringyespatrón ^(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|L-\d+)$

Body

FieldTypeRequiredConstraints
phonestring—mín. 6, máx. 32
curl -X POST https://api.vitrinadev.com/api/v1/leads/<id>/first-touch/retry \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "+56990773361"
  }'

Example response (200)

{
  "data": {
    "sent": true,
    "reason": null,
    "conversation_id": "c0c0c0c0-0000-4000-8000-000000000459",
    "phone": "+56990773361"
  }
}

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}|L-\d+)$
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}|L-\d+)$

Body

FieldTypeRequiredConstraints
resource_typevehicle \property \product \
resource_idstring | null—
titlestring | null—mín. 1, máx. 500
quantitynumber | null—≥ 0
prioritylow \medium \high
notesstring | null—má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}|L-\d+)$
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}|L-\d+)$
interestIdpathuuidyes

Body

FieldTypeRequiredConstraints
resource_typevehicle \property \product \
resource_idstring | null—
titlestring | null—mín. 1, máx. 500
quantitynumber | null—≥ 0
prioritylow \medium \high
notesstring | null—má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}/linea

Set a lead's Línea (a person's choice)

Every lead of a clinic carries exactly one Línea (linea on the lead: id, nombre, source). It starts from the ad that opened the conversation (source: ad), else from the lead's own words matched against the keywords of each Línea (keyword), is refined by the booked primera visita's prestación (prestacion), is General otherwise (default), and staff can correct it here (staff). A person's choice is final: nothing automatic changes it afterwards. The Línea must be one of the clinic's live Líneas (GET /clinic/lineas). Requires leads:write.

ParameterInTypeRequiredConstraints
idpathstringyespatrón ^(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|L-\d+)$

Body

FieldTypeRequiredConstraints
linea_iduuidyes
curl -X PUT https://api.vitrinadev.com/api/v1/leads/<id>/linea \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "linea_id": "5b1f0c1e-0000-4000-8000-000000000001"
  }'

Example response (200)

{
  "data": {
    "lead_id": "7c2d9a10-0000-4000-8000-000000000011",
    "linea": {
      "id": "5b1f0c1e-0000-4000-8000-000000000001",
      "nombre": "Implantes",
      "source": "staff"
    }
  }
}

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}|L-\d+)$

Body

FieldTypeRequiredConstraints
statuswon \lost \unqualified \
reasonstring | null—máx. 500
value_amountnumber | null—≥ 0
value_currencystring | null—mí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",
    "lost_reason": "bought_elsewhere",
    "lost_reason_source": "jev",
    "lost_reason_at": "2026-09-22T08:44:12.501Z",
    "payment_method": "financing",
    "down_payment_amount": 4000000,
    "budget_max_amount": 11500000,
    "has_trade_in": false,
    "trade_in_description": null,
    "offer_amount": null,
    "offer_note": null,
    "financing_term_months": [
      24,
      36
    ],
    "installment_amount": null,
    "max_down_payment_amount": null,
    "net_income_amount": 1900000,
    "employer": "CGE",
    "employment_tenure": "15 años",
    "budget_currency": null,
    "down_payment_currency": null,
    "mortgage_preapproval_status": null,
    "mortgage_bank": null,
    "mortgage_preapproved_amount": null,
    "mortgage_preapproved_currency": null,
    "meets_rent_income_requirement": null,
    "has_guarantor": null,
    "deal_terms_meta": {
      "payment_method": {
        "source": "ai",
        "actor_id": null,
        "actor_label": null,
        "at": "2026-09-21T14:02:11.000Z"
      },
      "down_payment_amount": {
        "source": "human",
        "actor_id": "11111111-0000-4000-8000-000000000001",
        "actor_label": "Camila Rojas",
        "at": "2026-09-21T15:10:00.000Z"
      }
    },
    "deal_terms_suggestions": {},
    "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}|L-\d+)$

Body

FieldTypeRequiredConstraints
pipeline_iduuidyes
reasonstring | null—má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,
    "lost_reason": null,
    "lost_reason_source": null,
    "lost_reason_at": null,
    "payment_method": "financing",
    "down_payment_amount": 4000000,
    "budget_max_amount": 11500000,
    "has_trade_in": false,
    "trade_in_description": null,
    "offer_amount": null,
    "offer_note": null,
    "financing_term_months": [
      24,
      36
    ],
    "installment_amount": null,
    "max_down_payment_amount": null,
    "net_income_amount": 1900000,
    "employer": "CGE",
    "employment_tenure": "15 años",
    "budget_currency": null,
    "down_payment_currency": null,
    "mortgage_preapproval_status": null,
    "mortgage_bank": null,
    "mortgage_preapproved_amount": null,
    "mortgage_preapproved_currency": null,
    "meets_rent_income_requirement": null,
    "has_guarantor": null,
    "deal_terms_meta": {
      "payment_method": {
        "source": "ai",
        "actor_id": null,
        "actor_label": null,
        "at": "2026-09-21T14:02:11.000Z"
      },
      "down_payment_amount": {
        "source": "human",
        "actor_id": "11111111-0000-4000-8000-000000000001",
        "actor_label": "Camila Rojas",
        "at": "2026-09-21T15:10:00.000Z"
      }
    },
    "deal_terms_suggestions": {},
    "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}|L-\d+)$
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,
    "lost_reason": null,
    "lost_reason_source": null,
    "lost_reason_at": null,
    "payment_method": "financing",
    "down_payment_amount": 4000000,
    "budget_max_amount": 11500000,
    "has_trade_in": false,
    "trade_in_description": null,
    "offer_amount": null,
    "offer_note": null,
    "financing_term_months": [
      24,
      36
    ],
    "installment_amount": null,
    "max_down_payment_amount": null,
    "net_income_amount": 1900000,
    "employer": "CGE",
    "employment_tenure": "15 años",
    "budget_currency": null,
    "down_payment_currency": null,
    "mortgage_preapproval_status": null,
    "mortgage_bank": null,
    "mortgage_preapproved_amount": null,
    "mortgage_preapproved_currency": null,
    "meets_rent_income_requirement": null,
    "has_guarantor": null,
    "deal_terms_meta": {
      "payment_method": {
        "source": "ai",
        "actor_id": null,
        "actor_label": null,
        "at": "2026-09-21T14:02:11.000Z"
      },
      "down_payment_amount": {
        "source": "human",
        "actor_id": "11111111-0000-4000-8000-000000000001",
        "actor_label": "Camila Rojas",
        "at": "2026-09-21T15:10:00.000Z"
      }
    },
    "deal_terms_suggestions": {},
    "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

POST /leads/{id}/review

Run the lead reviewer on this lead now

Synchronous (typically 5–20 s): the same lead-reviewer pass the background worker runs — score, temperature, the stage ladder (auto move or suggestion) and the deal terms the conversation states. Returns the Oportunidad lead object. At most once per lead per 60 s: a repeat answers 429 with error.retry_after_seconds (and a Retry-After header). 409 when lead review is off for the workspace or the lead is closed; 502 when the review could not complete. \{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}|L-\d+)$
curl -X POST https://api.vitrinadev.com/api/v1/leads/<id>/review \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "id": "99999999-0000-4000-8000-000000000001",
  "display_id": "L-89",
  "title": "María — prueba de manejo Corolla 2022",
  "status": "open",
  "source": "mercadolibre",
  "stage": {
    "id": "55555555-0000-4000-8000-000000000001",
    "name": "Contactado"
  },
  "pipeline": {
    "id": "44444444-0000-4000-8000-000000000001",
    "name": "Venta de vehículos"
  },
  "score": 72,
  "score_reason": "pidió hora para probar el auto y confirmó día",
  "scored_at": "2026-09-21T14:02:11.000Z",
  "facts_changed_at": "2026-09-21T13:40:00.000Z",
  "temperature": "warm",
  "value_currency": "CLP",
  "deal_terms": {
    "payment_method": "financing",
    "down_payment_amount": 4000000,
    "budget_max_amount": 11500000,
    "has_trade_in": true,
    "trade_in_description": "Chery Tiggo 2 Pro 2023, 32.000 km",
    "offer_amount": 10500000,
    "offer_note": "Financiado · pendiente de aprobación",
    "financing_term_months": [
      12,
      18,
      24
    ],
    "installment_amount": null,
    "max_down_payment_amount": 5000000,
    "net_income_amount": 1900000,
    "employer": "CGE",
    "employment_tenure": "15 años",
    "budget_currency": null,
    "down_payment_currency": null,
    "mortgage_preapproval_status": null,
    "mortgage_bank": null,
    "mortgage_preapproved_amount": null,
    "mortgage_preapproved_currency": null,
    "meets_rent_income_requirement": null,
    "has_guarantor": null
  },
  "deal_terms_meta": {
    "payment_method": {
      "source": "ai",
      "actor_id": null,
      "actor_label": null,
      "at": "2026-09-21T14:02:11.000Z"
    },
    "offer_amount": {
      "source": "human",
      "actor_id": "11111111-0000-4000-8000-000000000001",
      "actor_label": "Camila Rojas",
      "at": "2026-09-21T15:10:00.000Z"
    }
  },
  "deal_terms_suggestions": {
    "offer_amount": {
      "value": 10800000,
      "reason": "subió la oferta a 10,8 millones",
      "at": "2026-09-22T10:00:00.000Z"
    }
  }
}

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. value_amount (+ value_currency) is the REAL closing price and is acted on only when the target is a won stage: it overwrites the value and stamps value_amount_close_confirmed_at before the move, as PUT /leads/\{id\}/won does; for any other target it is ignored.

ParameterInTypeRequiredConstraints
idpathstringyespatrón ^(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|L-\d+)$

Body

FieldTypeRequiredConstraints
stage_iduuidyes
reasonstring | null—máx. 500
suggestion_iduuid—
value_amountnumber | null—≥ 0
value_currencystring | null—mín. 1, máx. 8
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,
    "lost_reason": null,
    "lost_reason_source": null,
    "lost_reason_at": null,
    "payment_method": "financing",
    "down_payment_amount": 4000000,
    "budget_max_amount": 11500000,
    "has_trade_in": false,
    "trade_in_description": null,
    "offer_amount": null,
    "offer_note": null,
    "financing_term_months": [
      24,
      36
    ],
    "installment_amount": null,
    "max_down_payment_amount": null,
    "net_income_amount": 1900000,
    "employer": "CGE",
    "employment_tenure": "15 años",
    "budget_currency": null,
    "down_payment_currency": null,
    "mortgage_preapproval_status": null,
    "mortgage_bank": null,
    "mortgage_preapproved_amount": null,
    "mortgage_preapproved_currency": null,
    "meets_rent_income_requirement": null,
    "has_guarantor": null,
    "deal_terms_meta": {
      "payment_method": {
        "source": "ai",
        "actor_id": null,
        "actor_label": null,
        "at": "2026-09-21T14:02:11.000Z"
      },
      "down_payment_amount": {
        "source": "human",
        "actor_id": "11111111-0000-4000-8000-000000000001",
        "actor_label": "Camila Rojas",
        "at": "2026-09-21T15:10:00.000Z"
      }
    },
    "deal_terms_suggestions": {},
    "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}|L-\d+)$

Body

FieldTypeRequiredConstraints
statuswon \lost \unqualified \
reasonstring | null—máx. 500
value_amountnumber | null—≥ 0
value_currencystring | null—mí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",
    "lost_reason": null,
    "lost_reason_source": null,
    "lost_reason_at": null,
    "payment_method": "financing",
    "down_payment_amount": 4000000,
    "budget_max_amount": 11500000,
    "has_trade_in": false,
    "trade_in_description": null,
    "offer_amount": null,
    "offer_note": null,
    "financing_term_months": [
      24,
      36
    ],
    "installment_amount": null,
    "max_down_payment_amount": null,
    "net_income_amount": 1900000,
    "employer": "CGE",
    "employment_tenure": "15 años",
    "budget_currency": null,
    "down_payment_currency": null,
    "mortgage_preapproval_status": null,
    "mortgage_bank": null,
    "mortgage_preapproved_amount": null,
    "mortgage_preapproved_currency": null,
    "meets_rent_income_requirement": null,
    "has_guarantor": null,
    "deal_terms_meta": {
      "payment_method": {
        "source": "ai",
        "actor_id": null,
        "actor_label": null,
        "at": "2026-09-21T14:02:11.000Z"
      },
      "down_payment_amount": {
        "source": "human",
        "actor_id": "11111111-0000-4000-8000-000000000001",
        "actor_label": "Camila Rojas",
        "at": "2026-09-21T15:10:00.000Z"
      }
    },
    "deal_terms_suggestions": {},
    "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}|L-\d+)$

Body

FieldTypeRequiredConstraints
statuswon \lost \unqualified \
reasonstring | null—máx. 500
value_amountnumber | null—≥ 0
value_currencystring | null—mí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",
    "lost_reason": null,
    "lost_reason_source": null,
    "lost_reason_at": null,
    "payment_method": "financing",
    "down_payment_amount": 4000000,
    "budget_max_amount": 11500000,
    "has_trade_in": false,
    "trade_in_description": null,
    "offer_amount": null,
    "offer_note": null,
    "financing_term_months": [
      24,
      36
    ],
    "installment_amount": null,
    "max_down_payment_amount": null,
    "net_income_amount": 1900000,
    "employer": "CGE",
    "employment_tenure": "15 años",
    "budget_currency": null,
    "down_payment_currency": null,
    "mortgage_preapproval_status": null,
    "mortgage_bank": null,
    "mortgage_preapproved_amount": null,
    "mortgage_preapproved_currency": null,
    "meets_rent_income_requirement": null,
    "has_guarantor": null,
    "deal_terms_meta": {
      "payment_method": {
        "source": "ai",
        "actor_id": null,
        "actor_label": null,
        "at": "2026-09-21T14:02:11.000Z"
      },
      "down_payment_amount": {
        "source": "human",
        "actor_id": "11111111-0000-4000-8000-000000000001",
        "actor_label": "Camila Rojas",
        "at": "2026-09-21T15:10:00.000Z"
      }
    },
    "deal_terms_suggestions": {},
    "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
titlestring—mín. 1, máx. 500
notesstring—má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,
    "lost_reason": null,
    "lost_reason_source": null,
    "lost_reason_at": null,
    "payment_method": "financing",
    "down_payment_amount": 4000000,
    "budget_max_amount": 11500000,
    "has_trade_in": false,
    "trade_in_description": null,
    "offer_amount": null,
    "offer_note": null,
    "financing_term_months": [
      24,
      36
    ],
    "installment_amount": null,
    "max_down_payment_amount": null,
    "net_income_amount": 1900000,
    "employer": "CGE",
    "employment_tenure": "15 años",
    "budget_currency": null,
    "down_payment_currency": null,
    "mortgage_preapproval_status": null,
    "mortgage_bank": null,
    "mortgage_preapproved_amount": null,
    "mortgage_preapproved_currency": null,
    "meets_rent_income_requirement": null,
    "has_guarantor": null,
    "deal_terms_meta": {
      "payment_method": {
        "source": "ai",
        "actor_id": null,
        "actor_label": null,
        "at": "2026-09-21T14:02:11.000Z"
      },
      "down_payment_amount": {
        "source": "human",
        "actor_id": "11111111-0000-4000-8000-000000000001",
        "actor_label": "Camila Rojas",
        "at": "2026-09-21T15:10:00.000Z"
      }
    },
    "deal_terms_suggestions": {},
    "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
linea_idqueryuuidno
card_kindqueryall \leads \solicitudes
first_touchqueryfailedno
first_touch_fromquerystringnodate-time
first_touch_toquerystringnodate-time
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. On a clinic (healthcare) workspace, value_amount on a WON lead is cash the patient paid, credited to that lead; before won it is the open presupuesto (a quote), not a sale. For return, ROAS, paid per ad or which ad brought paying patients, call ads_attributed_sales_list; never sum lead values.

ParameterInTypeRequiredConstraints
pipeline_idquerystringyesmín. 1
linea_idqueryuuidno
card_kindqueryall \leads \solicitudes
first_touchqueryfailedno
first_touch_fromquerystringnodate-time
first_touch_toquerystringnodate-time
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,
            "lost_reason": null,
            "lost_reason_source": null,
            "lost_reason_at": null,
            "payment_method": "financing",
            "down_payment_amount": 4000000,
            "budget_max_amount": 11500000,
            "has_trade_in": false,
            "trade_in_description": null,
            "offer_amount": null,
            "offer_note": null,
            "financing_term_months": [
              24,
              36
            ],
            "installment_amount": null,
            "max_down_payment_amount": null,
            "net_income_amount": 1900000,
            "employer": "CGE",
            "employment_tenure": "15 años",
            "budget_currency": null,
            "down_payment_currency": null,
            "mortgage_preapproval_status": null,
            "mortgage_bank": null,
            "mortgage_preapproved_amount": null,
            "mortgage_preapproved_currency": null,
            "meets_rent_income_requirement": null,
            "has_guarantor": null,
            "deal_terms_meta": {
              "payment_method": {
                "source": "ai",
                "actor_id": null,
                "actor_label": null,
                "at": "2026-09-21T14:02:11.000Z"
              },
              "down_payment_amount": {
                "source": "human",
                "actor_id": "11111111-0000-4000-8000-000000000001",
                "actor_label": "Camila Rojas",
                "at": "2026-09-21T15:10:00.000Z"
              }
            },
            "deal_terms_suggestions": {},
            "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,
            "lost_reason": null,
            "lost_reason_source": null,
            "lost_reason_at": null,
            "payment_method": "financing",
            "down_payment_amount": 4000000,
            "budget_max_amount": 11500000,
            "has_trade_in": false,
            "trade_in_description": null,
            "offer_amount": null,
            "offer_note": null,
            "financing_term_months": [
              24,
              36
            ],
            "installment_amount": null,
            "max_down_payment_amount": null,
            "net_income_amount": 1900000,
            "employer": "CGE",
            "employment_tenure": "15 años",
            "budget_currency": null,
            "down_payment_currency": null,
            "mortgage_preapproval_status": null,
            "mortgage_bank": null,
            "mortgage_preapproved_amount": null,
            "mortgage_preapproved_currency": null,
            "meets_rent_income_requirement": null,
            "has_guarantor": null,
            "deal_terms_meta": {
              "payment_method": {
                "source": "ai",
                "actor_id": null,
                "actor_label": null,
                "at": "2026-09-21T14:02:11.000Z"
              },
              "down_payment_amount": {
                "source": "human",
                "actor_id": "11111111-0000-4000-8000-000000000001",
                "actor_label": "Camila Rojas",
                "at": "2026-09-21T15:10:00.000Z"
              }
            },
            "deal_terms_suggestions": {},
            "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. On a clinic (healthcare) workspace, value_amount on a WON lead is cash the patient paid, credited to that lead; before won it is the open presupuesto (a quote), not a sale. For return, ROAS, paid per ad or which ad brought paying patients, call ads_attributed_sales_list; never sum lead values.

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