Publicidad
Gasto, ROAS e ingresos atribuidos por campaña, el ranking de creativos y la salud de la atribución, más el tag único: el fragmento que pegas (o su instalador de Google Tag Manager) y si ya está enviando visitas. Lecturas de Atribu con la clave delegada del workspace.
Descarga la proyección completa de la API pública: openapi.json.
| Método | Ruta | Qué hace |
|---|---|---|
GET | /ads/actions | The suggested actions for one Ads screen |
POST | /ads/actions/{key}/dismiss | Hide an action for a few days |
POST | /ads/actions/{key}/execute | Confirm and run a previewed action |
POST | /ads/actions/{key}/preview | Preview an action before confirming it |
GET | /ads/actions/executions | The actions journal («Lo que hice») |
GET | /ads/actions/executions/{id} | The status of an action execution |
POST | /ads/actions/executions/{id}/rollback | Undo an executed action |
GET | /ads/attributed-sales | One campaign’s attributed conversions (the drill-down) |
GET | /ads/briefing | The briefing: ≤3 sentences about a screen’s figures |
GET | /ads/campaigns | Campaign performance for a window |
GET | /ads/conversion-sync/catalog | Conversion Sync catalog: definitions, connections, destinations, privacy settings |
GET | /ads/conversion-sync/connections/{id}/pixels | The Meta pixels on a Meta Ads connection's ad account |
GET | /ads/conversion-sync/destinations | The configured export destinations (pre-fills a re-run) |
POST | /ads/conversion-sync/destinations | Create or update the Meta CAPI destination |
POST | /ads/conversion-sync/rules/sync | Create or update one export rule per outcome Vitrina emits |
POST | /ads/conversion-sync/setup | One action: privacy mode (clinics) → destination → rules |
GET | /ads/conversion-sync/wiring | Whether the dealer's ad sets optimize for the events Vitrina sends |
POST | /ads/conversion-sync/wiring/recheck | Re-verify wiring and self-heal (creates a missing Custom Conversion) |
GET | /ads/creatives | Top-performing ads (cohort-normalized creative scoring) |
GET | /ads/feed | The live feed of recorded outcomes |
GET | /ads/feed/{id}/journey | One feed row’s path to its outcome |
DELETE | /ads/goal | Clear the monthly Ads goal |
GET | /ads/goal | The workspace’s monthly Ads goal |
PUT | /ads/goal | Set the monthly Ads goal |
GET | /ads/health | Attribution trust, UTM quality and attribution coverage |
GET | /ads/overview | Ad spend, attributed revenue, ROAS and outcomes for a window |
GET | /ads/state | The tenant's Vitrina Ads entitlement and delegated-key state |
GET | /ads/tracking/installers/gtm | The tag as a ready-to-paste Google Tag Manager installer |
GET | /ads/tracking/key | The workspace's tracking key and the tag to paste |
GET | /ads/tracking/status | Is the tag installed? Last event seen, events in 24 h, domain |
GET /ads/actions
The suggested actions for one Ads screen
Up to 6 suggested actions for a screen, primary first, derived from the screen’s figures and the open recommendations of the measurement engine: pause a worn-out ad, raise a winning ad set’s budget, re-check the conversion wiring, re-scan the ad links — or, when Meta is not connected, only a nav card to reconnect it. Nothing here runs by itself: preview → confirm → execute.
A key is <kind>:<target external id>:<YYYY-MM-DD> and is stable for the day. Dismissed actions stay on the list with dismissed_until set, sorted last. include=adset_duplicate_swap also lists the duplicate-and-swap actions (hidden by default).
Card state machine (client): proposed → previewing → confirming → running → done | failed | refused → (done) reverted; dismissed for 7 days; simulated replaces done in sample mode.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
screen | query | resumen \ | campanas \ | atribuidos \ |
from | query | string | sí | patrón ^\d{4}-\d{2}-\d{2}$ |
to | query | string | sí | patrón ^\d{4}-\d{2}-\d{2}$ |
include | query | adset_duplicate_swap | no | |
sample | query | 1 \ | true | no |
curl https://api.vitrinadev.com/api/v1/ads/actions \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": [
{
"key": "pause_ad:120212000000000006:2026-09-23",
"kind": "pause_ad",
"screen": [
"creativos",
"resumen"
],
"primary": true,
"title": "Pausar «Video testimonio · 30 s»",
"why": "Está desgastado: la gente ya lo vio demasiadas veces y **$184.300** de gasto rinde cada vez menos.",
"effect": "Deja de mostrarse de inmediato; puedes reactivarlo cuando quieras.",
"target": {
"level": "ad",
"external_id": "120212000000000006",
"name": "Video testimonio · 30 s",
"campaign_external_id": null
},
"params": {},
"reversible": true,
"risk": "reversible",
"engine_ref": {
"op": "ads.pause"
},
"facts": [
"critical_creative_name",
"critical_creative_spend",
"fatigued_creatives"
],
"dismissed_until": null
},
{
"key": "budget_change:120211000000000077:2026-09-23",
"kind": "budget_change",
"screen": [
"campanas",
"resumen"
],
"primary": false,
"title": "Subir 20 % el presupuesto de «Implantes · Conjunto principal»",
"why": "Está trayendo tratamientos a buen costo y tiene espacio para crecer. Es parte de «Implantes · Septiembre», tu campaña con mejor retorno (**4,1×**).",
"effect": "El presupuesto diario sube 20 %; puedes deshacerlo.",
"target": {
"level": "ad_set",
"external_id": "120211000000000077",
"name": "Implantes · Conjunto principal",
"campaign_external_id": null
},
"params": {
"budget_change_pct": 20
},
"reversible": true,
"risk": "reversible",
"engine_ref": {
"op": "recommendations.apply",
"recommendation_id": "a0a0a0a0-0000-4000-8000-000000000001"
},
"facts": [
"best_campaign_name",
"best_campaign_roas"
],
"dismissed_until": null
}
]
}Responde: 200 · 400 · 401 · 402 · 403 · 404 · 409 · 429
POST /ads/actions/{key}/dismiss
Hide an action for a few days
Hides the action from the rail for days (default 7, max 30): it stays on GET /ads/actions with dismissed_until set and sorted last. Stored in the workspace’s Ads settings; not persisted in sample mode (persisted: false).
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
key | path | string | sí | mín. 5, máx. 200, patrón ^[a-z_]+:.+:\d{4}-\d{2}-\d{2}$ |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
days | integer | — | ≥ 1, ≤ 30, por defecto 7 |
sample | boolean | — |
curl -X POST https://api.vitrinadev.com/api/v1/ads/actions/<id>/dismiss \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"days": 7
}'Ejemplo de respuesta (200)
{
"data": {
"dismissed_until": "2026-09-30T15:00:00.000Z",
"persisted": true
}
}Responde: 200 · 400 · 401 · 402 · 403 · 404 · 409 · 429
POST /ads/actions/{key}/execute
Confirm and run a previewed action
Spends the preview’s single-use approval and queues the engine write; poll GET /ads/actions/executions/\{id\}. Idempotency-Key is required (400 without): a replay with the same key answers the first execution and writes nothing.
409 ADS_ACTION_REPLAY_BLOCKED when the approval cannot be spent — details.reason is expired (preview older than 10 minutes), consumed (already confirmed, e.g. from another tab), not_found or binding_mismatch (another person’s preview, or another action — incl. a real preview sent with sample: true). In sample mode nothing reaches the engine: the execution is born succeeded with simulated: true, and only a sample preview can be spent.
Duplicate-and-swap needs the owner’s acceptance of the preview’s consent_terms (the verbatim consent text): send consent: \{ version, text_hash, accepted_at? \}. Missing, different or older than 24 h → 409 ADS_ACTION_CONSENT_REQUIRED (details.reason missing|mismatch|stale, details.consent = the current version and hash) — nothing is spent and nothing reaches the engine.
If the job cannot be queued the answer is still 202, with status: failed: the execution exists and says why (error.code: queue_unavailable).
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
key | path | string | sí | mín. 5, máx. 200, patrón ^[a-z_]+:.+:\d{4}-\d{2}-\d{2}$ |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
preview_id | uuid | sí | |
sample | boolean | — | |
consent | object | — |
curl -X POST https://api.vitrinadev.com/api/v1/ads/actions/<id>/execute \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"preview_id": "b1b1b1b1-0000-4000-8000-000000000001"
}'Ejemplo de respuesta (202)
{
"data": {
"execution_id": "e5e5e5e5-0000-4000-8000-000000000001",
"status": "queued"
}
}Responde: 202 · 400 · 401 · 402 · 403 · 404 · 409 · 429
POST /ads/actions/{key}/preview
Preview an action before confirming it
Reads the target’s live state and returns what will change (rows: before → after), the warnings, and — for a duplicate-and-swap — the consent lines the owner must accept. It also records ONE single-use approval bound to the caller, the action and its exact parameters, valid for 10 minutes (expires_at): preview_id is what execute spends. No engine write happens here.
409 ADS_ACTION_NOT_AVAILABLE when the action is no longer on today’s list (or is a nav card, which has nothing to execute).
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
key | path | string | sí | mín. 5, máx. 200, patrón ^[a-z_]+:.+:\d{4}-\d{2}-\d{2}$ |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
from | string | sí | patrón ^\d{4}-\d{2}-\d{2}$ |
to | string | sí | patrón ^\d{4}-\d{2}-\d{2}$ |
sample | boolean | — |
curl -X POST https://api.vitrinadev.com/api/v1/ads/actions/<id>/preview \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "2026-08-24",
"to": "2026-09-23"
}'Ejemplo de respuesta (200)
{
"data": {
"preview_id": "b1b1b1b1-0000-4000-8000-000000000001",
"expires_at": "2026-09-23T15:10:00.000Z",
"action_key": "budget_change:120211000000000077:2026-09-23",
"rows": [
{
"key": "daily_budget",
"label": "Presupuesto diario",
"before": "$10.000",
"after": "$12.000"
}
],
"warnings": [
"Solo se permite cambiar el presupuesto hasta 50 % por vez."
],
"consent": [],
"consent_terms": null,
"reversible": true,
"engine_op": "recommendations.apply"
}
}Responde: 200 · 400 · 401 · 402 · 403 · 404 · 409 · 429
GET /ads/actions/executions
The actions journal («Lo que hice»)
Every action execution of the workspace (execute and rollback rows), newest first, each with what it touched (target) and who confirmed it (created_by, a display name — only for a caller holding memberships:read). from/to bound created_at by America/Santiago day; page with before=<meta.next_before>. Real mode lists real executions only; with sample=1 it lists what was simulated plus two example rows of today (also readable on GET /ads/actions/executions/\{id\}; not undoable: rollback_available: false).
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
from | query | string | no | patrón ^\d{4}-\d{2}-\d{2}$ |
to | query | string | no | patrón ^\d{4}-\d{2}-\d{2}$ |
before | query | string | no | máx. 120, patrón ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,6})?Z(,[0-9a-f-]{36})?$ |
limit | query | integer | no | ≥ 1, ≤ 50 |
sample | query | 1 \ | true | no |
curl https://api.vitrinadev.com/api/v1/ads/actions/executions \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": [
{
"id": "e5e5e5e5-0000-4000-8000-000000000003",
"action_key": "pause_ad:120212000000000006:2026-09-23",
"kind": "pause_ad",
"status": "succeeded",
"outcome": {
"summary": "Anuncio pausado en Meta.",
"engine_action_id": "f7f7f7f7-0000-4000-8000-000000000002",
"applied_at": "2026-09-23T16:20:04.000Z"
},
"error": null,
"rollback_available": true,
"rollback_of": null,
"simulated": false,
"created_at": "2026-09-23T16:20:01.000Z",
"finished_at": "2026-09-23T16:20:04.000Z",
"target": {
"level": "ad",
"external_id": "120212000000000006",
"name": "Blanqueamiento · Sonrisa"
},
"created_by": "Dueña Ejemplo"
},
{
"id": "e5e5e5e5-0000-4000-8000-000000000001",
"action_key": "budget_change:120211000000000077:2026-09-23",
"kind": "budget_change",
"status": "succeeded",
"outcome": {
"summary": "Cambio aplicado en Meta.",
"engine_action_id": "f7f7f7f7-0000-4000-8000-000000000001",
"applied_at": "2026-09-23T15:01:12.000Z"
},
"error": null,
"rollback_available": true,
"rollback_of": null,
"simulated": false,
"created_at": "2026-09-23T15:01:10.000Z",
"finished_at": "2026-09-23T15:01:12.000Z",
"target": {
"level": "ad_set",
"external_id": "120211000000000077",
"name": "Implantes · Las Condes"
},
"created_by": null
}
],
"meta": {
"next_before": "2026-09-23T15:01:10.000000Z,e5e5e5e5-0000-4000-8000-000000000001"
}
}Responde: 200 · 400 · 401 · 402 · 403 · 404 · 409 · 429
GET /ads/actions/executions/{id}
The status of an action execution
queued → running → succeeded | failed | refused; a succeeded execution becomes rolled_back when its rollback succeeds. error.reason is one of promoted_object_frozen (Meta froze the ad set’s conversion — the duplicate-and-swap is the alternative), reconnect_required, permission_gap (Meta connected without permission to manage ads — reconnect keeping it), rate_limited, not_reversible, forbidden (no person could be put on the record for the change — not a Meta grant problem), consent_required (the duplicate-and-swap consent is missing or stale — preview again), engine_error (incl. code: budget_change_out_of_bounds — outside the ±50 % allowed per change — code: not_active and code: no_recommendation). error.message is Spanish copy ready for the card.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí | |
sample | query | 1 \ | true | no |
curl https://api.vitrinadev.com/api/v1/ads/actions/executions/<id> \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"id": "e5e5e5e5-0000-4000-8000-000000000001",
"action_key": "budget_change:120211000000000077:2026-09-23",
"kind": "budget_change",
"status": "succeeded",
"outcome": {
"summary": "Cambio aplicado en Meta.",
"engine_action_id": "f7f7f7f7-0000-4000-8000-000000000001",
"applied_at": "2026-09-23T15:01:12.000Z"
},
"error": null,
"rollback_available": true,
"rollback_of": null,
"simulated": false,
"created_at": "2026-09-23T15:01:10.000Z",
"finished_at": "2026-09-23T15:01:12.000Z"
}
}Responde: 200 · 400 · 401 · 402 · 403 · 404 · 409 · 429
POST /ads/actions/executions/{id}/rollback
Undo an executed action
Queues the engine’s rollback of a succeeded execution with rollback_available: true and answers the NEW execution (rollback_of = the original). The engine restores the pre-change state only while the object still carries what the action wrote; otherwise the rollback is refused with reason: not_reversible (code: state_drifted). Idempotency-Key is required. 409 ADS_ACTION_NOT_REVERSIBLE when the execution has nothing to undo.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí | |
sample | query | 1 \ | true | no |
curl -X POST https://api.vitrinadev.com/api/v1/ads/actions/executions/<id>/rollback \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (202)
{
"data": {
"execution_id": "e5e5e5e5-0000-4000-8000-000000000002",
"status": "queued"
}
}Responde: 202 · 400 · 401 · 402 · 403 · 404 · 409 · 429
GET /ads/attributed-sales
One campaign’s attributed conversions (the drill-down)
Proxies Atribu’s campaigns/\{id\}/conversions operation: every conversion credited to campaign_id under the window/model, each carrying its credited share (credited_value/credited_weight), its position in the touch path, and whether the credit is is_inherited (bridged lead→cash, never counted in ROAS).
customer_name/customer_email are null unless the tenant’s delegated key also carries customers:read — every other field is visible under campaigns:read alone.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
from | query | string | sí | patrón ^\d{4}-\d{2}-\d{2}$ |
to | query | string | sí | patrón ^\d{4}-\d{2}-\d{2}$ |
model | query | string | no | |
sample | query | 1 \ | true | no |
campaign_id | query | string | sí | mín. 1 |
curl https://api.vitrinadev.com/api/v1/ads/attributed-sales \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"data": [
{
"conversion_id": "a8a8a8a8-0000-4000-8000-000000000001",
"event_type": "closed_won",
"event_time": "2026-09-12T16:40:00.000Z",
"customer_profile_id": "a9a9a9a9-0000-4000-8000-000000000001",
"customer_key": null,
"customer_name": "María José",
"customer_email": null,
"value_amount": 1200000,
"revenue_type": "accepted_value",
"credited_value": 1200000,
"credited_weight": 1,
"touch_count": 3,
"path_length": 3,
"first_touch_time": "2026-09-06T21:14:00.000Z",
"last_touch_time": "2026-09-11T13:02:00.000Z",
"is_inherited": false
}
]
}
}Responde: 200 · 400 · 401 · 402 · 403 · 404 · 409 · 429
GET /ads/briefing
The briefing: ≤3 sentences about a screen’s figures
A short Spanish briefing for one Ads screen, written from PINNED aggregate facts: every figure in lead/lines is quoted verbatim from facts[].display, and facts is returned alongside so a client can show «¿Por qué?». Facts are aggregates only — campaign and ad names, never a person.
source says who wrote the sentences: model (a narrator bound by a guard that rejects any number, name or sentence outside the rules) or template (deterministic — first week, Meta disconnected, no spend reading, the model slow or rejected). Both read identically. A briefing is cached per facts hash for 24 h, so two tabs and a reload read the same text; when the model misses its budget the template answers and the model briefing is finished in the background for the next load.
lead.figure names the number the lead is built around (display is its exact substring in lead.text). lines[].ask is a suggested follow-up question. from/to are required except for creativos, which takes the rolling window.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
screen | query | resumen \ | campanas \ | atribuidos \ |
from | query | string | no | patrón ^\d{4}-\d{2}-\d{2}$ |
to | query | string | no | patrón ^\d{4}-\d{2}-\d{2}$ |
window | query | 7d \ | 14d \ | 28d \ |
model | query | string | no | |
sample | query | 1 \ | true | no |
curl https://api.vitrinadev.com/api/v1/ads/briefing \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"screen": "resumen",
"window": {
"from": "2026-08-24",
"to": "2026-09-22"
},
"lead": {
"text": "Tus anuncios devolvieron **3,7×** lo invertido: $845.200 se convirtieron en $3.120.000 en presupuestos aceptados.",
"fact_keys": [
"roas",
"spend",
"revenue"
],
"figure": {
"fact_key": "roas",
"value": 3.69,
"format": "times",
"display": "3,7×",
"offset": 27
}
},
"lines": [
{
"text": "La mejor campaña fue «Evaluación sin costo · Meta», con 5,4× de retorno, y 36,4% de tus tratamientos vino de anuncios.",
"fact_keys": [
"best_campaign_name",
"best_campaign_roas",
"ads_share"
],
"ask": "¿Qué campaña me conviene subir?"
}
],
"facts": [
{
"key": "roas",
"label": "Retorno",
"value": 3.69,
"display": "3,7×",
"format": "times",
"source": "overview.current.roas",
"privacy": "aggregate"
},
{
"key": "spend",
"label": "Inversión en anuncios",
"value": 845200,
"display": "$845.200",
"format": "clp",
"source": "overview.current.spend",
"privacy": "aggregate"
}
],
"actions": [],
"source": "model",
"generated_at": "2026-09-22T16:40:12.000Z",
"vocabulary": "healthcare",
"sample": false
}
}Responde: 200 · 400 · 401 · 402 · 403 · 404 · 409 · 429
GET /ads/campaigns
Campaign performance for a window
Proxies Atribu’s campaigns operation (the ranked top-N shape — no level is ever sent, so this is always the campaign rollup, not the per-ad-set/per-ad ranking). One row per campaign: spend, attributed outcome count/value and ROAS, plus the direct/inherited cash split when Atribu can compute it.
campaign_external_id on each row is the PLATFORM id — pass it as campaign_id to /ads/attributed-sales for the drill-down.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
from | query | string | sí | patrón ^\d{4}-\d{2}-\d{2}$ |
to | query | string | sí | patrón ^\d{4}-\d{2}-\d{2}$ |
model | query | string | no | |
sample | query | 1 \ | true | no |
limit | query | integer | no | > 0, ≤ 500 |
series | query | 1 \ | true | no |
curl https://api.vitrinadev.com/api/v1/ads/campaigns \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"data": [
{
"campaign_id": "a7a7a7a7-0000-4000-8000-000000000001",
"campaign_name": "Leads · Septiembre",
"campaign_external_id": "120211000000000001",
"spend": 845200,
"outcome_count": 31,
"outcome_value": 3120000,
"roas": 3.69,
"direct_cash_value": 2100000,
"inherited_cash_value": 600000,
"top_ad": {
"external_id": "120212000000000009",
"name": "Video testimonio 15s",
"creative_thumbnail_url": "https://cdn.vitrinadev.com/examples/ads/video-testimonio-15s.jpg"
},
"series": [
{
"date": "2026-08-24",
"spend": 31800,
"outcome_count": 1
},
{
"date": "2026-08-25",
"spend": 35700,
"outcome_count": 2
},
{
"date": "2026-08-26",
"spend": 28700,
"outcome_count": 1
},
{
"date": "2026-08-27",
"spend": 33400,
"outcome_count": 1
},
{
"date": "2026-08-28",
"spend": 30300,
"outcome_count": 1
},
{
"date": "2026-08-29",
"spend": 19700,
"outcome_count": 1
},
{
"date": "2026-08-30",
"spend": 22100,
"outcome_count": 1
},
{
"date": "2026-08-31",
"spend": 28700,
"outcome_count": 1
},
{
"date": "2026-09-01",
"spend": 33400,
"outcome_count": 1
},
{
"date": "2026-09-02",
"spend": 30300,
"outcome_count": 1
},
{
"date": "2026-09-03",
"spend": 31800,
"outcome_count": 1
},
{
"date": "2026-09-04",
"spend": 35700,
"outcome_count": 1
},
{
"date": "2026-09-05",
"spend": 17800,
"outcome_count": 1
},
{
"date": "2026-09-06",
"spend": 20700,
"outcome_count": 1
},
{
"date": "2026-09-07",
"spend": 30300,
"outcome_count": 1
},
{
"date": "2026-09-08",
"spend": 31800,
"outcome_count": 1
},
{
"date": "2026-09-09",
"spend": 35700,
"outcome_count": 1
},
{
"date": "2026-09-10",
"spend": 28700,
"outcome_count": 1
},
{
"date": "2026-09-11",
"spend": 33400,
"outcome_count": 1
},
{
"date": "2026-09-12",
"spend": 18800,
"outcome_count": 1
},
{
"date": "2026-09-13",
"spend": 19700,
"outcome_count": 1
},
{
"date": "2026-09-14",
"spend": 35700,
"outcome_count": 1
},
{
"date": "2026-09-15",
"spend": 28700,
"outcome_count": 1
},
{
"date": "2026-09-16",
"spend": 33400,
"outcome_count": 1
},
{
"date": "2026-09-17",
"spend": 30200,
"outcome_count": 1
},
{
"date": "2026-09-18",
"spend": 31800,
"outcome_count": 1
},
{
"date": "2026-09-19",
"spend": 22100,
"outcome_count": 1
},
{
"date": "2026-09-20",
"spend": 17800,
"outcome_count": 1
},
{
"date": "2026-09-21",
"spend": 33400,
"outcome_count": 1
},
{
"date": "2026-09-22",
"spend": 13600,
"outcome_count": 1
}
]
}
]
}
}Responde: 200 · 400 · 401 · 402 · 403 · 404 · 409 · 429
GET /ads/conversion-sync/catalog
Conversion Sync catalog: definitions, connections, destinations, privacy settings
Step 0 of the setup. definitions[].id is a rule's source_ref_id; connections[].id is a destination's connection_id. Relayed verbatim from Atribu. Gated by the Add-on like every /ads/* route but /ads/state. Requires ads:read.
curl https://api.vitrinadev.com/api/v1/ads/conversion-sync/catalog \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"definitions": [
{
"id": "f1f1f1f1-0000-4000-8000-000000000001",
"conversion_key": "lead_created",
"source_event_names": [
"lead_created"
]
},
{
"id": "f1f1f1f1-0000-4000-8000-000000000002",
"conversion_key": "closed_won",
"source_event_names": [
"closed_won"
]
}
],
"connections": [
{
"id": "c0c0c0c0-0000-4000-8000-000000000001",
"provider": "meta_ads"
}
],
"destinations": [
{
"id": "d0d0d0d0-0000-4000-8000-000000000001",
"destination": "meta_capi",
"connection_id": "c0c0c0c0-0000-4000-8000-000000000001",
"meta_pixel_id": "1234567890123456",
"enabled": true
}
],
"settings": {
"privacy_mode": "platform_safe",
"privacy_config": {
"parameter_allowlist": [
"value",
"currency"
]
},
"export_meta_enabled": true,
"export_google_enabled": false
}
}
}Responde: 200 · 400 · 401 · 402 · 403 · 404 · 409 · 429
GET /ads/conversion-sync/connections/{id}/pixels
The Meta pixels on a Meta Ads connection's ad account
Where meta_pixel_id comes from. One live Meta Graph call per request. Requires ads:read.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
curl https://api.vitrinadev.com/api/v1/ads/conversion-sync/connections/<id>/pixels \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": [
{
"id": "1234567890123456",
"name": "Píxel del sitio",
"created_at": "2025-11-03T15:20:00.000Z",
"last_fired_at": "2026-09-22T11:58:40.000Z",
"is_unavailable": false
}
]
}Responde: 200 · 400 · 401 · 402 · 403 · 404 · 409 · 429
GET /ads/conversion-sync/destinations
The configured export destinations (pre-fills a re-run)
Relayed verbatim from Atribu, including each destination's routing. Requires ads:read.
curl https://api.vitrinadev.com/api/v1/ads/conversion-sync/destinations \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"privacy_mode": "platform_safe",
"export_meta_enabled": true,
"export_google_enabled": false,
"destinations": [
{
"id": "d0d0d0d0-0000-4000-8000-000000000001",
"destination": "meta_capi",
"connection_id": "c0c0c0c0-0000-4000-8000-000000000001",
"meta_pixel_id": "1234567890123456",
"enabled": true
}
]
}
}Responde: 200 · 400 · 401 · 402 · 403 · 404 · 409 · 429
POST /ads/conversion-sync/destinations
Create or update the Meta CAPI destination
Creates the meta_capi destination (or updates the existing one on a re-run). For a clinic (vertical = healthcare) the profile is first switched to privacy_mode = platform_safe and the write is refused unless the read-back confirms it. Requires ads:write. Errors: 402 ENTITLEMENT_NOT_ACTIVE (details.feature: vitrina_ads, with hint) when the Vitrina Ads Add-on is not active — never retried; 409 ADS_KEY_NEEDS_REMINT when the Add-on is on but the delegated key has not finished rotating, or still lacks exports:write after one automatic re-mint (the entitlement rail converges it; see GET /ads/state).
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
connection_id | uuid | sí | |
meta_pixel_id | string | sí | patrón ^\d{5,32}$ |
curl -X POST https://api.vitrinadev.com/api/v1/ads/conversion-sync/destinations \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"connection_id": "c0c0c0c0-0000-4000-8000-000000000001",
"meta_pixel_id": "1234567890123456"
}'Ejemplo de respuesta (200)
{
"data": {
"action": "created",
"destination": {
"id": "d0d0d0d0-0000-4000-8000-000000000001",
"destination": "meta_capi",
"connection_id": "c0c0c0c0-0000-4000-8000-000000000001",
"meta_pixel_id": "1234567890123456",
"enabled": true
},
"privacy": {
"required": "platform_safe",
"actual": "platform_safe",
"applied": true
}
}
}Responde: 200 · 400 · 401 · 402 · 403 · 404 · 409 · 429
POST /ads/conversion-sync/rules/sync
Create or update one export rule per outcome Vitrina emits
Automotive: lead_created→Lead, appointment_booked→Schedule, payment_received→Purchase (event value). Clinic: lead_created→Lead, appointment_booked→Schedule, appointment_attended and quote_presented→generic custom events, closed_won→Purchase (event value), and payment_received with its Meta destination DISABLED (cash ledger only). Idempotent: matched to existing rules by definition. A definition missing from the catalog is reported as skipped. Requires ads:write. Errors: 402 ENTITLEMENT_NOT_ACTIVE (details.feature: vitrina_ads, with hint) when the Vitrina Ads Add-on is not active — never retried; 409 ADS_KEY_NEEDS_REMINT when the Add-on is on but the delegated key has not finished rotating, or still lacks exports:write after one automatic re-mint (the entitlement rail converges it; see GET /ads/state).
curl -X POST https://api.vitrinadev.com/api/v1/ads/conversion-sync/rules/sync \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"vertical": "healthcare",
"rules": [
{
"stage": "lead_created",
"action": "created",
"rule_id": "e1e1e1e1-0000-4000-8000-000000000001",
"meta_enabled": true,
"meta_event_name": "Lead"
},
{
"stage": "appointment_booked",
"action": "created",
"rule_id": "e1e1e1e1-0000-4000-8000-000000000002",
"meta_enabled": true,
"meta_event_name": "Schedule"
},
{
"stage": "closed_won",
"action": "created",
"rule_id": "e1e1e1e1-0000-4000-8000-000000000003",
"meta_enabled": true,
"meta_event_name": "Purchase"
},
{
"stage": "payment_received",
"action": "created",
"rule_id": "e1e1e1e1-0000-4000-8000-000000000004",
"meta_enabled": false,
"meta_event_name": "Purchase"
}
]
}
}Responde: 200 · 400 · 401 · 402 · 403 · 404 · 409 · 429
POST /ads/conversion-sync/setup
One action: privacy mode (clinics) → destination → rules
The setup in one call — the same work as the destination write followed by the rules sync. Requires ads:write. Errors: 402 ENTITLEMENT_NOT_ACTIVE (details.feature: vitrina_ads, with hint) when the Vitrina Ads Add-on is not active — never retried; 409 ADS_KEY_NEEDS_REMINT when the Add-on is on but the delegated key has not finished rotating, or still lacks exports:write after one automatic re-mint (the entitlement rail converges it; see GET /ads/state).
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
connection_id | uuid | sí | |
meta_pixel_id | string | sí | patrón ^\d{5,32}$ |
curl -X POST https://api.vitrinadev.com/api/v1/ads/conversion-sync/setup \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"connection_id": "c0c0c0c0-0000-4000-8000-000000000001",
"meta_pixel_id": "1234567890123456"
}'Ejemplo de respuesta (200)
{
"data": {
"destination": {
"action": "created",
"destination": {
"id": "d0d0d0d0-0000-4000-8000-000000000001",
"destination": "meta_capi",
"connection_id": "c0c0c0c0-0000-4000-8000-000000000001",
"meta_pixel_id": "1234567890123456",
"enabled": true
},
"privacy": {
"required": "platform_safe",
"actual": "platform_safe",
"applied": true
}
},
"rules": {
"vertical": "healthcare",
"rules": [
{
"stage": "lead_created",
"action": "created",
"rule_id": "e1e1e1e1-0000-4000-8000-000000000001",
"meta_enabled": true,
"meta_event_name": "Lead"
},
{
"stage": "appointment_booked",
"action": "created",
"rule_id": "e1e1e1e1-0000-4000-8000-000000000002",
"meta_enabled": true,
"meta_event_name": "Schedule"
},
{
"stage": "closed_won",
"action": "created",
"rule_id": "e1e1e1e1-0000-4000-8000-000000000003",
"meta_enabled": true,
"meta_event_name": "Purchase"
},
{
"stage": "payment_received",
"action": "created",
"rule_id": "e1e1e1e1-0000-4000-8000-000000000004",
"meta_enabled": false,
"meta_event_name": "Purchase"
}
]
}
}
}Responde: 200 · 400 · 401 · 402 · 403 · 404 · 409 · 429
GET /ads/conversion-sync/wiring
Whether the dealer's ad sets optimize for the events Vitrina sends
Atribu's per-rule verdicts and remediation, verbatim (served from a 6h cache; force=1 re-verifies live — still a read). privacy reports whether a clinic profile carries platform_safe. Requires ads:read.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
force | query | 0 \ | 1 \ | true \ |
curl https://api.vitrinadev.com/api/v1/ads/conversion-sync/wiring \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"results": [
{
"rule_id": "e1e1e1e1-0000-4000-8000-000000000001",
"status": "wired",
"remediation": null
}
],
"checked_at": "2026-09-22T12:00:00.000Z",
"stale": false,
"ttl_ms": 21600000,
"privacy": {
"required": "platform_safe",
"actual": "platform_safe",
"applied": true
}
}
}Responde: 200 · 400 · 401 · 402 · 403 · 404 · 409 · 429
POST /ads/conversion-sync/wiring/recheck
Re-verify wiring and self-heal (creates a missing Custom Conversion)
A WRITE: the self-heal creates a Custom Conversion in the dealer's Meta ad account. Bind it to an explicit operator action only — never a page load or a poll. Requires ads:write. Errors: 402 ENTITLEMENT_NOT_ACTIVE (details.feature: vitrina_ads, with hint) when the Vitrina Ads Add-on is not active — never retried; 409 ADS_KEY_NEEDS_REMINT when the Add-on is on but the delegated key has not finished rotating, or still lacks exports:write after one automatic re-mint (the entitlement rail converges it; see GET /ads/state).
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
rule_id | uuid | — |
curl -X POST https://api.vitrinadev.com/api/v1/ads/conversion-sync/wiring/recheck \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"rule_id": "e1e1e1e1-0000-4000-8000-000000000001"
}'Ejemplo de respuesta (200)
{
"data": {
"results": [
{
"rule_id": "e1e1e1e1-0000-4000-8000-000000000001",
"status": "wired",
"remediation": null
}
],
"checked_at": "2026-09-22T12:00:00.000Z",
"stale": false,
"ttl_ms": 21600000,
"privacy": {
"required": "platform_safe",
"actual": "platform_safe",
"applied": true
}
}
}Responde: 200 · 400 · 401 · 402 · 403 · 404 · 409 · 429
GET /ads/creatives
Top-performing ads (cohort-normalized creative scoring)
Proxies Atribu’s top-performers operation — a ROLLING lookback (window), not a date range: that is the real endpoint’s own shape. Each ad carries three distinct measures, presented separately — composite_score (0-100, a transparent rule blend), top_performer_likelihood (0-1, a probability, never ROAS) and attributed_revenue/roas (real cash attribution, present when truth_grade is attributed).
This is the PROFILE-scoped read — every Vitrina tenant’s delegated key is minted for exactly one Atribu profile, so it cannot call Atribu’s cross-profile /workspaces/\{id\}/top-performers (that route is session-bearer-only and answers 403 insufficient_scope to any API key).
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
window | query | 7d \ | 14d \ | 28d \ |
limit | query | integer | no | > 0, ≤ 100 |
series | query | 1 \ | true | no |
sample | query | 1 \ | true | no |
curl https://api.vitrinadev.com/api/v1/ads/creatives \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"data": [
{
"ad_external_id": "120212000000000009",
"ad_name": "Video testimonio 15s",
"campaign_name": "Leads · Septiembre",
"campaign": {
"external_id": "120211000000000001",
"name": "Leads · Septiembre"
},
"ad_set_name": "Santiago 25-45",
"creative_thumbnail_url": null,
"score_window": "28d",
"composite_score": 0.82,
"top_performer_likelihood": 0.71,
"maturity_stage": "mature",
"truth_grade": "attributed",
"primary_outcome_kind": "closed_won",
"spend": 150000,
"impressions": 48200,
"ctr": 1.9,
"attributed_revenue": 480000,
"roas": 3.2,
"fatigue_state": "active",
"fatigue_risk_tier": "low",
"series": [
{
"date": "2026-09-09",
"spend": 6300,
"ctr": 1.79
},
{
"date": "2026-09-10",
"spend": 7000,
"ctr": 1.8
},
{
"date": "2026-09-11",
"spend": 5600,
"ctr": 1.82
},
{
"date": "2026-09-12",
"spend": 4100,
"ctr": 1.84
},
{
"date": "2026-09-13",
"spend": 3700,
"ctr": 1.86
},
{
"date": "2026-09-14",
"spend": 6300,
"ctr": 1.87
},
{
"date": "2026-09-15",
"spend": 7000,
"ctr": 1.89
},
{
"date": "2026-09-16",
"spend": 5600,
"ctr": 1.91
},
{
"date": "2026-09-17",
"spend": 6600,
"ctr": 1.93
},
{
"date": "2026-09-18",
"spend": 6000,
"ctr": 1.94
},
{
"date": "2026-09-19",
"spend": 3900,
"ctr": 1.96
},
{
"date": "2026-09-20",
"spend": 4300,
"ctr": 1.98
},
{
"date": "2026-09-21",
"spend": 5600,
"ctr": 2
},
{
"date": "2026-09-22",
"spend": 3000,
"ctr": 2.01
}
]
}
]
}
}Responde: 200 · 400 · 401 · 402 · 403 · 404 · 409 · 429
GET /ads/feed
The live feed of recorded outcomes
Every outcome Vitrina recorded for the workspace (a lead, a booking, an attended visit, a presented quote, a closed sale, a payment), newest first — Vitrina’s own ledger, so a row appears the moment it happens, not after the engine’s daily sync. A row names the campaign the attribution engine credited (ad, from a cache refreshed every 5 minutes over the requested window, up to 92 days back; null until then — meta.enrichment says whether it is complete — with matched: true that reads «anuncio por confirmar»).
contact.display_name is filled only for a caller that also holds contacts:read; everyone else gets null and contact.key (the last 4 hex of the contact id).
Polling: call with since=<newest created_at seen> (verbatim); the answer re-includes the 5 seconds before it, so dedupe by id. Paging: before=<meta.next_before> (verbatim; when a polling page comes back full, page down with before + the same since to close the gap). only_ads=1 / campaign_id keep the rows the engine credited (to that campaign). With sample=1 answers the sample dataset’s feed.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
from | query | string | sí | patrón ^\d{4}-\d{2}-\d{2}$ |
to | query | string | sí | patrón ^\d{4}-\d{2}-\d{2}$ |
stage | query | lead_created \ | appointment_booked \ | appointment_attended \ |
only_ads | query | 1 \ | true | no |
campaign_id | query | string | no | mín. 1, máx. 64 |
since | query | string | no | patrón ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,6})?Z$ |
before | query | string | no | máx. 80 |
limit | query | integer | no | ≥ 1, ≤ 50 |
sample | query | 1 \ | true | no |
curl https://api.vitrinadev.com/api/v1/ads/feed \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": [
{
"id": "01926f3e-7c1a-7b2e-9f10-3a5b7c9d0e1f",
"stage": "closed_won",
"occurred_at": "2026-09-22T19:40:12.000Z",
"created_at": "2026-09-22T19:40:13.482913Z",
"matched": true,
"value": 1200000,
"action_source": "business_messaging",
"contact": {
"id": "5b0c2f1e-8a3d-4c6b-9e7f-1a2b3c4d5e6f",
"display_name": "Paciente Ejemplo",
"key": "5e6f"
},
"ad": {
"name": null,
"ad_set_name": null,
"creative_thumbnail_url": null,
"campaign": {
"external_id": "120211000000000001",
"name": "Ortodoncia invisible · Septiembre"
}
},
"credited_weight": 1,
"touch_count": 3
},
{
"id": "01926f3a-1b2c-7d3e-8f40-5a6b7c8d9e0f",
"stage": "appointment_booked",
"occurred_at": "2026-09-22T18:02:55.000Z",
"created_at": "2026-09-22T18:02:56.104220Z",
"matched": true,
"value": null,
"action_source": "business_messaging",
"contact": {
"id": "7c1d3e2f-9b4a-4d5c-8e6f-2b3c4d5e6f70",
"display_name": null,
"key": "6f70"
},
"ad": null,
"credited_weight": null,
"touch_count": null
}
],
"meta": {
"next_before": "2026-09-22T18:02:56.104220Z,01926f3a-1b2c-7d3e-8f40-5a6b7c8d9e0f",
"server_time": "2026-09-22T19:41:00.000Z",
"enrichment": "ready"
}
}Responde: 200 · 400 · 401 · 402 · 403 · 404 · 409 · 429
GET /ads/feed/{id}/journey
One feed row’s path to its outcome
The touches that led to one recorded outcome, oldest first, ending at the outcome: ad views and clicks (ad and campaign names), site visits, conversations. From the attribution engine (source: engine) when it credited the outcome and the delegated key may read people; otherwise Vitrina’s own record of this contact’s outcomes (source: vitrina) — still a path, without the ad touches. Never carries a URL, referrer, email or device. 404 when the id is not a live row of this workspace. With sample=1 answers the sample dataset’s journey (pass the feed’s from/to when it was not the last 30 days).
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí | |
from | query | string | no | patrón ^\d{4}-\d{2}-\d{2}$ |
to | query | string | no | patrón ^\d{4}-\d{2}-\d{2}$ |
sample | query | 1 \ | true | no |
curl https://api.vitrinadev.com/api/v1/ads/feed/<id>/journey \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"steps": [
{
"kind": "ad",
"label": "Reel · Antes y después",
"detail": "Ortodoncia invisible · Septiembre",
"at": "2026-09-19T21:10:00.000Z",
"channel": "meta"
},
{
"kind": "click",
"label": "Reel · Antes y después",
"detail": "Ortodoncia invisible · Septiembre",
"at": "2026-09-20T13:02:00.000Z",
"channel": "meta"
},
{
"kind": "chat",
"label": "Conversación",
"detail": null,
"at": "2026-09-20T13:03:00.000Z",
"channel": "whatsapp"
},
{
"kind": "outcome",
"label": "Presupuesto aceptado",
"detail": null,
"at": "2026-09-22T19:40:12.000Z"
}
],
"touch_count": 3,
"credited_weight": 1,
"model": "last_touch",
"source": "engine"
}
}Responde: 200 · 400 · 401 · 402 · 403 · 404 · 409 · 429
DELETE /ads/goal
Clear the monthly Ads goal
Removes the goal. Idempotent. With sample=1 nothing is stored or removed.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
sample | query | 1 \ | true | no |
curl -X DELETE https://api.vitrinadev.com/api/v1/ads/goal \
-H "Authorization: Bearer $VITRINA_KEY"Responde: 204 · 400 · 401 · 402 · 403 · 404 · 409 · 429
GET /ads/goal
The workspace’s monthly Ads goal
The goal the Ads hero measures pace against — results from ads, attributed value or return for the calendar month — or null when none is set. With sample=1 answers the sample dataset’s goal.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
sample | query | 1 \ | true | no |
curl https://api.vitrinadev.com/api/v1/ads/goal \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"kind": "outcomes",
"target": 30,
"period": "month",
"updated_at": "2026-09-01T12:00:00.000Z",
"updated_by": "20000000-0000-4000-8000-000000000001"
}
}Responde: 200 · 400 · 401 · 402 · 403 · 404 · 409 · 429
PUT /ads/goal
Set the monthly Ads goal
Replaces the goal. target > 0; a roas goal is at most 100, a value goal at most 10^10 CLP, an outcomes goal a whole number. The server stamps updated_at/updated_by. With sample=1 the goal is validated and echoed with persisted: false, never stored.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
sample | query | 1 \ | true | no |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
kind | outcomes \ | value \ | roas |
target | number | sí | > 0 |
period | month | sí |
curl -X PUT https://api.vitrinadev.com/api/v1/ads/goal \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"kind": "outcomes",
"target": 30,
"period": "month"
}'Ejemplo de respuesta (200)
{
"data": {
"kind": "outcomes",
"target": 30,
"period": "month",
"updated_at": "2026-09-23T15:00:00.000Z",
"updated_by": "20000000-0000-4000-8000-000000000001",
"persisted": true
}
}Responde: 200 · 400 · 401 · 402 · 403 · 404 · 409 · 429
GET /ads/health
Attribution trust, UTM quality and attribution coverage
One combined read over three Atribu operations, so a dashboard panel needs one call, not three:
trust— Atribu’strustoperation: how much of the profile’s cash is traceable to an ad (traceable_pct_by_value/_by_count), whether the instrumentation producing that number is sound (instrumentation.tracking_healthy), and which way the traceable half is moving (traceable_roas_trend). READinstrumentationBEFORE renderingtraceable_pct_by_valueas an indictment — a low number reads as "great organic" when tracking is healthy and "broken tracking" when it is not.utm— Atribu’squality/utmoperation: the cached UTM-health badge (malformed/missing/dangling-ad-id anomaly counts over the traffic side) plus the dangling-ad-ids rollup (attributed cash that cannot be placed in the ad hierarchy).attribution_coverage— Atribu’squality/attributionoperation: the conversion side — how many of the window’s conversions carry a real ad id, anfbclidonly, or nothing, partitioningtotal_conversions.
⚠ Named /attribution-coverage in this ticket’s original prose — the live Atribu spec names this operation quality/attribution; there is no separate /attribution-coverage path. Same read, corrected name.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
from | query | string | sí | patrón ^\d{4}-\d{2}-\d{2}$ |
to | query | string | sí | patrón ^\d{4}-\d{2}-\d{2}$ |
model | query | string | no | |
sample | query | 1 \ | true | no |
days | query | integer | no | ≥ 1, ≤ 30 |
window_days | query | integer | no | ≥ 2, ≤ 366 |
curl https://api.vitrinadev.com/api/v1/ads/health \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"trust": {
"model": "last_touch",
"currency": "CLP",
"traceable_pct_by_count": 41.5,
"traceable_pct_by_value": 38.2,
"total_count": 53,
"total_value": 5000000,
"instrumentation": {
"score": 0.86,
"verdict": "healthy_tracking",
"tracking_healthy": true,
"coverage_interpretation": "La mayoría de las ventas llegan con su origen publicitario.",
"missing": []
},
"traceable_roas_trend": {
"recent_roas": 3.4,
"prior_roas": 2.9,
"recent_spend": 845200,
"recent_traceable_revenue": 2873680,
"days_with_cash": 11
}
},
"utm": {
"scan_window_days": 14,
"events_scanned": 312,
"high_count": 3,
"medium_count": 7,
"refreshed_at": "2026-09-22T06:00:00.000Z",
"dangling_ad_ids": {
"window_days": 28,
"model": "last_touch",
"dangling_conversions": 2,
"attributed_cash": 90000,
"reporting_currency": "CLP",
"distinct_ad_ids": 1
}
},
"attribution_coverage": {
"total_conversions": 44,
"with_full_utms": 27,
"with_fbclid_only": 5,
"with_no_tracking": 12,
"coverage_percent": 72.7
},
"outcomes_coverage": {
"outcomes": 42,
"attributed_outcomes": 31,
"coverage_percent": 73.8
}
}
}Responde: 200 · 400 · 401 · 402 · 403 · 404 · 409 · 429
GET /ads/overview
Ad spend, attributed revenue, ROAS and outcomes for a window
Proxies Atribu’s overview operation: current and previous (the immediately-preceding window of equal length), each carrying spend, clicks, ROAS, the cash/first-payment/recurring/refund/dispute legs and per-conversion-type outcome counts.
spend, roas and clicks are null — never 0 — exactly when spend_available is false (#1809): the engine could not scope spend to the window/model requested. A measured zero still arrives as 0 with spend_available: true. Render null as "—", never as zero, and let every ratio you derive from it inherit the null.
Every other money field (revenue, cash_revenue, first_payment_revenue, …) is parsed from Atribu’s exact-decimal wire string (ADR 0018) to a number ONCE, at the provider mapper.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
from | query | string | sí | patrón ^\d{4}-\d{2}-\d{2}$ |
to | query | string | sí | patrón ^\d{4}-\d{2}-\d{2}$ |
model | query | string | no | |
sample | query | 1 \ | true | no |
grain | query | total \ | day | no |
compare | query | previous_period | no |
curl https://api.vitrinadev.com/api/v1/ads/overview \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"current": {
"spend": 845200,
"spend_available": true,
"clicks": 3120,
"revenue": 3120000,
"organic_direct_revenue": 410000,
"roas": 3.69,
"outcomes": 42,
"attributed_outcomes": 31,
"attributed_cash_outcomes": 9,
"coverage_percent": 73.8,
"cash_revenue": 2700000,
"cash_payments": 11,
"first_payment_revenue": 1950000,
"first_payment_customers": 8,
"recurring_revenue": 750000,
"recurring_customers": 3,
"refunds": 0,
"refund_amount": 0,
"disputes": 0,
"dispute_amount": 0,
"dispute_reversed_amount": 0,
"outcome_counts": {
"lead_created": 30,
"appointment_booked": 9,
"closed_won": 3
}
},
"previous": {
"spend": null,
"spend_available": false,
"clicks": null,
"revenue": 2480000,
"organic_direct_revenue": 410000,
"roas": null,
"outcomes": 42,
"attributed_outcomes": 31,
"attributed_cash_outcomes": 9,
"coverage_percent": 73.8,
"cash_revenue": 2700000,
"cash_payments": 11,
"first_payment_revenue": 1950000,
"first_payment_customers": 8,
"recurring_revenue": 750000,
"recurring_customers": 3,
"refunds": 0,
"refund_amount": 0,
"disputes": 0,
"dispute_amount": 0,
"dispute_reversed_amount": 0,
"outcome_counts": {
"lead_created": 30,
"appointment_booked": 9,
"closed_won": 3
}
},
"days": [
{
"date": "2026-08-24",
"spend": 31800,
"revenue": 120000,
"outcomes": 2,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-08-25",
"spend": 35700,
"revenue": 130000,
"outcomes": 3,
"attributed_outcomes": 2,
"partial": false
},
{
"date": "2026-08-26",
"spend": 28700,
"revenue": 110000,
"outcomes": 1,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-08-27",
"spend": 33400,
"revenue": 120000,
"outcomes": 2,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-08-28",
"spend": 30300,
"revenue": 110000,
"outcomes": 1,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-08-29",
"spend": 19700,
"revenue": 70000,
"outcomes": 1,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-08-30",
"spend": 22100,
"revenue": 80000,
"outcomes": 1,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-08-31",
"spend": 28700,
"revenue": 110000,
"outcomes": 1,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-09-01",
"spend": 33400,
"revenue": 120000,
"outcomes": 2,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-09-02",
"spend": 30300,
"revenue": 110000,
"outcomes": 1,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-09-03",
"spend": 31800,
"revenue": 120000,
"outcomes": 2,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-09-04",
"spend": 35700,
"revenue": 130000,
"outcomes": 2,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-09-05",
"spend": 17800,
"revenue": 70000,
"outcomes": 1,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-09-06",
"spend": 20700,
"revenue": 80000,
"outcomes": 1,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-09-07",
"spend": 30300,
"revenue": 110000,
"outcomes": 1,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-09-08",
"spend": 31800,
"revenue": 120000,
"outcomes": 1,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-09-09",
"spend": 35700,
"revenue": 130000,
"outcomes": 2,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-09-10",
"spend": 28700,
"revenue": 110000,
"outcomes": 1,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-09-11",
"spend": 33400,
"revenue": 120000,
"outcomes": 2,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-09-12",
"spend": 18800,
"revenue": 70000,
"outcomes": 1,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-09-13",
"spend": 19700,
"revenue": 70000,
"outcomes": 1,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-09-14",
"spend": 35700,
"revenue": 130000,
"outcomes": 2,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-09-15",
"spend": 28700,
"revenue": 110000,
"outcomes": 1,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-09-16",
"spend": 33400,
"revenue": 120000,
"outcomes": 2,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-09-17",
"spend": 30200,
"revenue": 110000,
"outcomes": 1,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-09-18",
"spend": 31800,
"revenue": 120000,
"outcomes": 1,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-09-19",
"spend": 22100,
"revenue": 80000,
"outcomes": 1,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-09-20",
"spend": 17800,
"revenue": 70000,
"outcomes": 1,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-09-21",
"spend": 33400,
"revenue": 120000,
"outcomes": 2,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-09-22",
"spend": 13600,
"revenue": 50000,
"outcomes": 1,
"attributed_outcomes": 1,
"partial": true
}
],
"previous_days": [
{
"date": "2026-07-25",
"spend": null,
"revenue": 60000,
"outcomes": 1,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-07-26",
"spend": null,
"revenue": 70000,
"outcomes": 1,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-07-27",
"spend": null,
"revenue": 80000,
"outcomes": 1,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-07-28",
"spend": null,
"revenue": 100000,
"outcomes": 2,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-07-29",
"spend": null,
"revenue": 90000,
"outcomes": 1,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-07-30",
"spend": null,
"revenue": 90000,
"outcomes": 2,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-07-31",
"spend": null,
"revenue": 110000,
"outcomes": 3,
"attributed_outcomes": 2,
"partial": false
},
{
"date": "2026-08-01",
"spend": null,
"revenue": 50000,
"outcomes": 1,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-08-02",
"spend": null,
"revenue": 60000,
"outcomes": 1,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-08-03",
"spend": null,
"revenue": 90000,
"outcomes": 1,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-08-04",
"spend": null,
"revenue": 90000,
"outcomes": 2,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-08-05",
"spend": null,
"revenue": 110000,
"outcomes": 2,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-08-06",
"spend": null,
"revenue": 80000,
"outcomes": 1,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-08-07",
"spend": null,
"revenue": 100000,
"outcomes": 2,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-08-08",
"spend": null,
"revenue": 60000,
"outcomes": 1,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-08-09",
"spend": null,
"revenue": 60000,
"outcomes": 1,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-08-10",
"spend": null,
"revenue": 110000,
"outcomes": 2,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-08-11",
"spend": null,
"revenue": 80000,
"outcomes": 1,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-08-12",
"spend": null,
"revenue": 100000,
"outcomes": 2,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-08-13",
"spend": null,
"revenue": 90000,
"outcomes": 1,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-08-14",
"spend": null,
"revenue": 90000,
"outcomes": 2,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-08-15",
"spend": null,
"revenue": 70000,
"outcomes": 1,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-08-16",
"spend": null,
"revenue": 50000,
"outcomes": 1,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-08-17",
"spend": null,
"revenue": 100000,
"outcomes": 2,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-08-18",
"spend": null,
"revenue": 90000,
"outcomes": 1,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-08-19",
"spend": null,
"revenue": 90000,
"outcomes": 1,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-08-20",
"spend": null,
"revenue": 110000,
"outcomes": 2,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-08-21",
"spend": null,
"revenue": 80000,
"outcomes": 1,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-08-22",
"spend": null,
"revenue": 60000,
"outcomes": 1,
"attributed_outcomes": 1,
"partial": false
},
{
"date": "2026-08-23",
"spend": null,
"revenue": 60000,
"outcomes": 1,
"attributed_outcomes": 1,
"partial": false
}
]
}
}Responde: 200 · 400 · 401 · 402 · 403 · 404 · 409 · 429
GET /ads/state
The tenant's Vitrina Ads entitlement and delegated-key state
Distinguishes "not entitled" (entitlement: off) from "needs re-mint" (needs_remint: true — the live key is the wrong kind for the declared entitlement) and from an in-flight change (pending_on / pending_off). grant_scopes are the scopes Atribu returned on the last provision or entitlement call. Requires ads:read.
curl https://api.vitrinadev.com/api/v1/ads/state \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"entitlement": "on",
"needs_remint": false,
"key_kind": "ads",
"grant_scopes": [
"connections",
"attribution"
],
"last_error": null
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
GET /ads/tracking/installers/gtm
The tag as a ready-to-paste Google Tag Manager installer
The tag name, "All Pages" trigger, snippet and setup checklist for a Google Tag Manager Custom HTML tag. The snippet is the COMBINED loader (<script src=".../tag.js?site=…">): pasting this ONE tag boots the assistant AND starts the measurement. tag_name and setup_checklist are Vitrina-branded user-facing copy (Spanish) that names only tag.js.
site_id (optional) is the publishable pk_… key of the site the tag is being installed on — embedded into the snippet's ?site= query so the tag survives GTM stripping data-* off the <script> tag it re-creates. Absent → the workspace's own site key (the site_key of GET /ads/tracking/key, minted on first read); 400 when the workspace has none and no origin to mint one for.
Same entitlement gate as GET /ads/tracking/key. Admin-scoped (ads:read).
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
site_id | query | string | no | mín. 1 |
curl https://api.vitrinadev.com/api/v1/ads/tracking/installers/gtm \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"key_id": "abababab-0000-4000-8000-000000000001",
"public_key": "trk_live_abc123",
"site_key": "pk_live_sitekey000000000000",
"tag_name": "Vitrina Ads · tag",
"trigger": "All Pages",
"snippet": "<script src=\"https://api.vitrinadev.com/tag.js?site=pk_live_sitekey000000000000\" async></script>",
"setup_checklist": [
"En Google Tag Manager, crea una etiqueta nueva de tipo «HTML personalizado».",
"Pega el fragmento de Vitrina (tag.js) tal cual.",
"Activador: All Pages.",
"Guarda la etiqueta y publica el contenedor.",
"El asistente y la medición se cargan desde este mismo tag.js: no agregues otro fragmento de Vitrina aparte."
]
}
}Responde: 200 · 400 · 401 · 402 · 403 · 404 · 409 · 429
GET /ads/tracking/key
The workspace's tracking key and the tag to paste
Ensures an active tracking key exists for the workspace (minting one on the first call — idempotent, a repeat never mints a second key) and returns the tag to paste. combined_snippet is THE tag: Vitrina's one-line loader (<script src=".../tag.js?site=pk_…">), which boots the assistant and the measurement from the same tag. Its site key (site_key) is the newest unexpired publishable key of the workspace, or — when it has none — one minted on this read for the workspace's Vitrina site, registered domains and declared website, and listed afterwards among its publishable keys. combined_snippet and site_key are null only when no origin can be derived at all.
snippet is the measurement provider's raw tracker script (the tracking half alone), kept for API consumers that self-host it; Vitrina's own UI renders combined_snippet only. meta_pixel_id is present when the workspace has a Meta pixel configured.
402 ENTITLEMENT_NOT_ACTIVE (feature: vitrina_ads) when Vitrina Ads is not active for this workspace; 409 ADS_KEY_NEEDS_REMINT when the Add-on is on but the delegated key has not finished rotating. Admin-scoped (ads:read).
curl https://api.vitrinadev.com/api/v1/ads/tracking/key \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"key_id": "abababab-0000-4000-8000-000000000001",
"public_key": "trk_live_abc123",
"snippet": "<script src=\"https://track.atribu.app/t.js?k=trk_live_abc123\" async></script>",
"combined_snippet": "<script src=\"https://api.vitrinadev.com/tag.js?site=pk_live_sitekey000000000000\" async></script>",
"site_key": "pk_live_sitekey000000000000",
"meta_pixel_id": null
}
}Responde: 200 · 400 · 401 · 402 · 403 · 404 · 409 · 429
GET /ads/tracking/status
Is the tag installed? Last event seen, events in 24 h, domain
Tag detection for the workspace's tracking key. source: tracker (the normal case) reads the measurement provider's per-key last-event time (last_seen_at), the tracker events of the last 24 hours (events_24h) and the hostname of the latest verified install check (domain). installed is true only when the tag sent an event in the last 30 days — never without a timestamp.
When the per-key read is unavailable the answer falls back to the health read's daily UTM scan: source: health, installed = the refreshed scan counted events, last_seen_at / events_24h / domain = null. events_24h is null (never 0) whenever the count could not be read.
Same entitlement gate as GET /ads/tracking/key, except that sample=1 (or a sandbox workspace) answers a fixed sample status (installed: true, seen minutes ago, source: tracker) without the entitlement and without reading the real tag — like every other Ads read. Admin-scoped (ads:read).
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
sample | query | 1 \ | true | no |
curl https://api.vitrinadev.com/api/v1/ads/tracking/status \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"installed": true,
"last_seen_at": "2026-09-23T14:02:11.000Z",
"events_24h": 184,
"domain": "www.clinica-ejemplo.cl",
"source": "tracker"
}
}Responde: 200 · 400 · 401 · 402 · 403 · 404 · 409 · 429
Agentes de IA
El agente que conversa por el workspace: su configuración en borrador y en vivo, versiones, herramientas y skills, el canal de voz, y las Solicitudes con las que un cliente pide un cambio.
Analytics
Los reportes del panel: volumen y resolución de conversaciones, desempeño humano y de IA, uso de herramientas y de tokens, y latencia.