VitrinaAPI
Webhooks

Catálogo de eventos

Cada evento que esta API entrega, con su payload y un ejemplo — generado desde el mismo catálogo que responde GET /webhooks/events.

Esta página se genera desde el catálogo que sirve GET /api/v1/webhooks/events. No puede describir un evento que no existe, y solo queda vieja si el catálogo queda viejo.

El capítulo que explica cómo suscribirse, verificar la firma y revisar las entregas está aquí.

El sobre

Todo evento llega con el mismo cuerpo. Los campos de abajo son el sobre; data cambia según el evento, y llega solo cuando la suscripción lo pidió y su dueño puede leerlo.

CampoQué trae
idstring (uuid) — unique event id; use for idempotency
typestring — one of the events above
versioninteger — the schema version of this event type; bumped only on a breaking change to data
livemodeboolean — false when this event came from a sandbox (test-mode) workspace, true otherwise. Mirrored on the Vitrina-Livemode header; since livemode is in the signed body, verifying the signature verifies this too.
created_atISO 8601 timestamp
tenant_idstring (uuid) — your workspace id
resourceobject { type: string, id: string | null, url: string | null } — what the event is about; GET url with your credential to read it
changesobject | absent — field → { from, to } for the identifiers and states that changed
authorobject { kind: 'member' | 'api_key' | 'ai_agent' | 'system' | 'contact', id: string | null, name: string | null, via?: { kind: 'connected_app' | 'personal_token', name: string } } — who did it
dataobject | absent — the resource payload (see catalog); present only when the subscription includes data and its owner may read it
data_omittedstring | absent — why data is not in this delivery; exactly one of data / data_omitted is present

Aviso o datos

Cada entrega trae exactamente uno de data o data_omitted. Estos son los dos modos y todas las razones por las que data puede no venir:

Modo o razónQué trae
noticeThe default. Every delivery carries the notice — resource, changes, author, time, url — and data_omitted: "not_requested". Read the resource with your own credential, which applies scopes, visibility and the access log.
data«Incluir datos del recurso» (include_data: true). data is added when, at dispatch time, the subscription owner holds the resource read scope and sees every record; otherwise the delivery is a notice with the reason in data_omitted.
not_requestedThe subscription did not ask for data.
sensitiveThe event is about a dato sensible (health data). It is always a notice, whatever the subscription asked for.
missing_scope:<scope>The owner does not hold that read scope (e.g. missing_scope:contacts:read).
restricted_visibilityThe owner sees only some records (assigned-only, or some sucursales).
owner_unavailableThe owner is no longer an active member, or its API key was revoked or expired. Recreate the subscription, or edit its URL or events, to make yourself its owner.

Los headers

HeaderQué trae
X-Webhook-EventThe event name (e.g. ticket.opened).
X-Webhook-Event-IdUnique event id. Retries reuse this id so you can dedupe.
X-Webhook-TimestampUnix seconds when the signature was generated.
X-Webhook-Signaturet=<unix>,v1=<hex> — HMAC-SHA256 of ${timestamp}.${rawBody} with your subscription secret.
Vitrina-Livemodetrue or false, mirroring the body's livemode. Signed as part of the body — verifying the signature verifies this too.

La firma

hmac-sha256, t=<unix>,v1=<hex>.

Ventana de repetición: 300 segundos. Una firma más vieja que eso se rechaza.

Verificar, en pseudocódigo

const [ts, sig] = parseHeader('X-Webhook-Signature');  // 't=…,v1=…'
const expected = hmac_sha256(secret, ts + '.' + rawBody);  // hex
if (!timing_safe_equal(sig.v1, expected)) reject(401);
if (Math.abs(now_unix() - ts) > 300) reject(401);  // 5-min replay window

Reintentos

Hasta 5 intentos con espera exponencial: 5, 10, 20, 40, 80 segundos entre uno y otro.

All retries reuse the same X-Webhook-Event-Id so your endpoint can be idempotent.

Pausa automática

Una suscripción se pausa sola cuando 20 entregas seguidas agotan sus reintentos, o cuando lleva 24 horas fallando sin una sola respuesta correcta.

A subscription is paused when 20 events in a row exhaust their retries, or when it has failed for 24 hours without a single success. The workspace admins are notified once. A paused subscription receives nothing until POST /webhooks/{id}/resume; events in between are not queued — read the resources to catch up, or redeliver from the delivery log.

Los eventos

Son 156 eventos. El nombre es el valor que va en events al crear la suscripción, y el que llega en X-Webhook-Event.

campaign.sent

A campaign finished sending — every recipient reached a terminal pre-delivery state (sent/failed/suppressed).

Versión 1 · Recurso campaign · data requiere campaigns:read

Cuándo se dispara. The campaign worker drains the last pending recipient and flips the campaign to status=sent.

CampoTipo
campaign_idstring (uuid)
namestring
channelenum: whatsapp | email
total_recipientsinteger
sentinteger (includes delivered/read)
failedinteger
suppressedinteger
finished_atISO 8601 timestamp | null
{
  "campaign_id": "0d9b2a4e-1f6c-4e7a-9b3d-5c8e2f1a6b4d",
  "name": "Reactivación de clientes abril",
  "channel": "whatsapp",
  "total_recipients": 1240,
  "sent": 1198,
  "failed": 17,
  "suppressed": 25,
  "finished_at": "2026-06-11T15:04:05Z"
}

campaign.paused

A sending campaign was paused automatically — the WhatsApp number hit its Meta messaging limit, or the email reputation guard tripped (ADR 0032: >8% hard bounces or >0.3% complaints after 200 sends; also blocks the tenant email channel).

Versión 1 · Recurso campaign · data requiere campaigns:read

Cuándo se dispara. A campaign send gets a rate/messaging-limit error from Atribu/Meta (auto-probes hourly), or the SES event ingest crosses a reputation threshold.

CampoTipo
campaign_idstring (uuid)
namestring
reasonenum: messaging_limit | reputation_guard
detailstring | undefined (which threshold, at what rate)
{
  "campaign_id": "0d9b2a4e-1f6c-4e7a-9b3d-5c8e2f1a6b4d",
  "name": "Reactivación de clientes abril",
  "reason": "messaging_limit"
}

sending_domain.verified

A tenant email sending domain finished DKIM verification and can now send email campaigns (ADR 0032).

Versión 1 · Recurso sending_domain · data requiere messaging_accounts:read

Cuándo se dispara. A sending-domain refresh observes SES flip VerifiedForSendingStatus to true for the first time.

CampoTipo
idstring (uuid)
domainstring
{
  "id": "7f2c1b9e-3a5d-4c8e-9f1a-2b6d4e8c0a3f",
  "domain": "mail.cliente.cl"
}

coach.proposals_ready

Conversation Coach generated new improvement proposals for an agent — route to Slack/email/etc. to alert the agent owner.

Versión 1 · Recurso ai_agent · data requiere ai_agents:read

Cuándo se dispara. A finding investigation or a correction synthesis persisted at least one improvement_proposal.

CampoTipo
ai_agent_idstring (uuid)
finding_idstring (uuid) | undefined (present when source=investigation)
correction_idstring (uuid) | undefined (present when source=correction)
sourceenum: review | investigation | correction
proposalsnumber (count of new proposals)
{
  "ai_agent_id": "4ce54eaf-…",
  "finding_id": "00000000-…-0000000f1nd1",
  "source": "investigation",
  "proposals": 3
}

coach.finding_ready

The conversation reviewer detected a RECURRING issue with an agent (Mejoras, ADR 0095) — «promete descuentos que no existen», with an occurrence count — one step before anybody proposes a fix for it.

Versión 1 · Recurso ai_agent · data requiere ai_agents:read

Cuándo se dispara. A conversation review created or bumped a finding.

CampoTipo
ai_agent_idstring (uuid)
findingsarray of { id: string (uuid), kind: string, topic_key: string, title: string, occurrence_count: number, status: string }
sourceenum: review (the only producer today)
{
  "ai_agent_id": "4ce54eaf-…",
  "findings": [
    {
      "id": "00000000-…-0000000f1nd1",
      "kind": "missing_knowledge",
      "topic_key": "financiamiento.pie_minimo",
      "title": "No sabe responder por el pie mínimo",
      "occurrence_count": 7,
      "status": "open"
    }
  ],
  "source": "review"
}

voice.autonomous_call

A proactive/autonomous voice agent initiated an outbound speed-to-lead call on its own (ADR-0016) — route to Slack/email so a human is aware ("warn and be careful").

Versión 1 · Recurso lead · data requiere leads:read

Cuándo se dispara. A new lead arrives and the tenant's voice agent autonomy_level is proactive or autonomous and all guardrails pass.

CampoTipo
lead_idstring (uuid)
contact_idstring (uuid) | null
agent_idstring (uuid)
autonomy_levelenum: proactive | autonomous
{
  "lead_id": "4ce54eaf-…",
  "contact_id": "00000000-…",
  "agent_id": "a1b2…",
  "autonomy_level": "proactive"
}

safety_hold.placed

A clinical SAFETY HOLD was placed on a contact: proactive messaging to that person is blocked until a named human acknowledges and resolves it. Opt-in per tenant (clinical_safety.operational_alert_webhook).

Versión 1 · Recurso outbound_hold · data requiere outbound_holds:read

Dato sensible: llega siempre como aviso, sin data, pida lo que pida la suscripción.

Cuándo se dispara. The service-lifecycle evaluator decides a patient reply requires professional attention, or an operator places a hold by hand. Fires once per hold — a re-raised concern deduplicates onto the live hold.

CampoTipo
hold_idstring (uuid)
contact_idstring (uuid)
scopeenum: run | contact | subject
kindenum: safety | identity | manual | policy
reasonstring
placed_atISO 8601 timestamp
stepKeystring | null
ticketIdstring | null
ackMinutesinteger (the acknowledgement target that now applies)
{
  "hold_id": "018f…",
  "contact_id": "9a1c…",
  "scope": "contact",
  "kind": "safety",
  "reason": "the patient reports worsening swelling",
  "placed_at": "2027-02-09T14:03:11.000Z",
  "stepKey": "check_in_24h",
  "ticketId": "T-1247",
  "ackMinutes": 15
}

safety_hold.ack_overdue

Nobody acknowledged a safety hold within the tenant's acknowledgement target (D6 default: 15 minutes of declared coverage). The backup has been paged.

Versión 1 · Recurso outbound_hold · data requiere outbound_holds:read

Dato sensible: llega siempre como aviso, sin data, pida lo que pida la suscripción.

Cuándo se dispara. The safety-hold SLA sweep (5-min tick) finds a hold still in active past its ack target. Fires once per hold.

CampoTipo
hold_idstring (uuid)
contact_idstring (uuid)
eventTypeliteral: ack_overdue
targetMinutesinteger
elapsedMinutesinteger (coverage minutes, not wall-clock)
withinCoverageboolean — false means the tenant has declared no coverage schedule, which is a configuration problem rather than a missed target
{
  "hold_id": "018f…",
  "contact_id": "9a1c…",
  "eventType": "ack_overdue",
  "targetMinutes": 15,
  "elapsedMinutes": 22,
  "withinCoverage": true
}

safety_hold.first_response_overdue

No human wrote to the patient within the first-response target (D6 default: 60 minutes of declared coverage) after a safety hold was placed. A durable breach row was recorded.

Versión 1 · Recurso outbound_hold · data requiere outbound_holds:read

Dato sensible: llega siempre como aviso, sin data, pida lo que pida la suscripción.

Cuándo se dispara. The safety-hold SLA sweep finds no human outbound message to the contact dated after the hold was placed, past the first-response target. Fires once per hold.

CampoTipo
hold_idstring (uuid)
contact_idstring (uuid)
eventTypeliteral: first_response_overdue
targetMinutesinteger
elapsedMinutesinteger (coverage minutes, not wall-clock)
withinCoverageboolean
{
  "hold_id": "018f…",
  "contact_id": "9a1c…",
  "eventType": "first_response_overdue",
  "targetMinutes": 60,
  "elapsedMinutes": 74,
  "withinCoverage": true
}

chileautos.lead

A buyer lead arrived from Chileautos ("envío de leads") and was ingested as a Vitrina lead.

Versión 1 · Recurso lead · data requiere leads:read

Cuándo se dispara. The Chileautos leads webhook (POST /integrations/chileautos/leads) is processed by the worker.

CampoTipo
lead_idstring (uuid) | null
contact_idstring
vehicle_idstring (uuid) | null
sourceenum: chileautos | whatsapp | ChileautosLeadPremium | null
chileautos_lead_identifierstring
chileautos_statusstring | null
{
  "lead_id": "7c1f0e2a-6b3d-4a9e-8f12-2b9a1c0d4e5f",
  "contact_id": "4821",
  "vehicle_id": "a35d4ff0-8612-4ff0-8612-a8ee57f4f095",
  "source": "chileautos",
  "chileautos_lead_identifier": "88d404ca-a35d-4ff0-8612-a8ee57f4f095",
  "chileautos_status": "New"
}

yapo.lead

A buyer lead arrived from Yapo.cl and was ingested as a Vitrina lead.

Versión 1 · Recurso lead · data requiere leads:read

Cuándo se dispara. The per-account yapo leads webhook (POST /integrations/yapo/leads/:accountId/:token) is processed by the worker.

CampoTipo
lead_idstring (uuid) | null
contact_idstring
vehicle_idstring (uuid) | null
yapo_lead_idstring
yapo_ad_idstring | null
{
  "lead_id": "7c1f0e2a-6b3d-4a9e-8f12-2b9a1c0d4e5f",
  "contact_id": "4821",
  "vehicle_id": "a35d4ff0-8612-4ff0-8612-a8ee57f4f095",
  "yapo_lead_id": "90815723",
  "yapo_ad_id": "75481920"
}

website.lead

A buyer inquiry arrived through a dealer website's "Consulta del sitio" form and was ingested as a Vitrina lead.

Versión 1 · Recurso lead · data requiere leads:read

Cuándo se dispara. POST /api/v1/website-leads (Bearer api_key, scope leads:intake) is processed.

CampoTipo
lead_idstring (uuid) | null
contact_idstring
conversation_idstring | null
vehicle_idstring (uuid) | null
submission_idstring
intentenum: buy | sell | financing | trade_in (defaults to buy)
{
  "lead_id": "7c1f0e2a-6b3d-4a9e-8f12-2b9a1c0d4e5f",
  "contact_id": "4821",
  "conversation_id": "9b1e2a4e-1f6c-4e7a-9b3d-5c8e2f1a6b4d",
  "vehicle_id": "a35d4ff0-8612-4ff0-8612-a8ee57f4f095",
  "submission_id": "3f7a1c9e2b4d5f6a7b8c9d0e1f2a3b4c",
  "intent": "sell"
}

consignment.candidate.created

A private owner offered their car through a dealer website's "vende tu auto" form and the submission landed as a consignment candidate.

Versión 1 · Recurso consignment_candidate · data requiere consignments:read

Cuándo se dispara. POST /api/v1/widget/consignments (publishable pk_ key, origin-locked, scope leads:intake) is processed. A honeypot-tripped or duplicate submission fires nothing.

CampoTipo
candidate_idstring (uuid)
patentestring (as typed by the owner)
makestring
modelstring
yearinteger
kilometersinteger
expected_price_clpinteger (whole CLP) | null
sale_horizonenum: 7d | 15d | 30d | cotizando
region_codestring (2-digit CUT)
photo_countinteger
submission_idstring
{
  "candidate_id": "d41f2b8e-5a17-4c93-8b0e-6f1a2c3d4e5f",
  "patente": "KXTR52",
  "make": "Toyota",
  "model": "Yaris",
  "year": 2019,
  "kilometers": 68000,
  "expected_price_clp": 9500000,
  "sale_horizon": "15d",
  "region_code": "13",
  "photo_count": 3,
  "submission_id": "8b1c0a5d7e2f4a6b9c3d0e1f2a3b4c5d"
}

quote.issued

A cotización was issued — a price offer on a unit, frozen at issuance and holding no reservation on the car.

Versión 1 · Recurso quote · data requiere quotes:read

Cuándo se dispara. POST /api/v1/quotes.

CampoTipo
idstring (uuid)
display_idstring (Q-…)
vehicle_idstring (uuid)
buyer_contact_idstring (uuid)
salesperson_idstring (uuid) | null
offered_price_clpinteger (whole CLP)
tax_treatmentenum: afecto | exento | pass_through
expires_onstring (YYYY-MM-DD)
stateenum: vigente | expirada | nula
{
  "id": "d2d2d2d2-0000-4000-8000-000000000001",
  "display_id": "Q-47",
  "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
  "buyer_contact_id": "22222222-0000-4000-8000-000000000002",
  "salesperson_id": "11111111-0000-4000-8000-000000000001",
  "offered_price_clp": 11900000,
  "tax_treatment": "afecto",
  "expires_on": "2026-09-23",
  "state": "vigente"
}

quote.voided

A cotización was voided. Terminal — a voided quote is never restored.

Versión 1 · Recurso quote · data requiere quotes:read

Cuándo se dispara. POST /api/v1/quotes/{id}/void.

CampoTipo
idstring (uuid)
display_idstring (Q-…)
void_reasonstring
voided_bystring (uuid)
voided_atstring (ISO 8601)
{
  "id": "d2d2d2d2-0000-4000-8000-000000000002",
  "display_id": "Q-52",
  "void_reason": "el cliente desistió de la compra",
  "voided_by": "11111111-0000-4000-8000-000000000001",
  "voided_at": "2026-09-15T11:20:03.000Z"
}

quote.extended

A cotización’s validity window was moved to a new date, including bringing an expired one back to vigente.

Versión 1 · Recurso quote · data requiere quotes:read

Cuándo se dispara. POST /api/v1/quotes/{id}/extend.

CampoTipo
idstring (uuid)
display_idstring (Q-…)
expires_onstring (YYYY-MM-DD)
extend_reasonstring
extended_bystring (uuid)
extended_atstring (ISO 8601)
{
  "id": "d2d2d2d2-0000-4000-8000-000000000001",
  "display_id": "Q-47",
  "expires_on": "2026-10-20",
  "extend_reason": "el cliente sigue esperando la aprobación del crédito",
  "extended_by": "11111111-0000-4000-8000-000000000001",
  "extended_at": "2026-09-20T09:11:44.000Z"
}

quote.archived

A cotización was filed away from the working register. Not a delete and not a state — its folio and state are unchanged.

Versión 1 · Recurso quote · data requiere quotes:read

Cuándo se dispara. POST /api/v1/quotes/{id}/archive.

CampoTipo
idstring (uuid)
display_idstring (Q-…)
archive_reasonstring
archived_bystring (uuid)
archived_atstring (ISO 8601)
{
  "id": "d2d2d2d2-0000-4000-8000-000000000003",
  "display_id": "Q-31",
  "archive_reason": "cotización antigua, ya no vigente en el seguimiento",
  "archived_by": "11111111-0000-4000-8000-000000000001",
  "archived_at": "2026-09-10T08:00:00.000Z"
}

reservation.created

A reserva took a hold on a unit — the only Wave 2a document that locks inventory.

Versión 1 · Recurso reservation · data requiere reservations:read

Cuándo se dispara. POST /api/v1/reservations.

CampoTipo
idstring (uuid)
display_idstring (R-…)
vehicle_idstring (uuid)
holder_contact_idstring (uuid)
salesperson_idstring (uuid) | null
agreed_price_clpinteger (whole CLP) | null
hold_expires_onstring (YYYY-MM-DD)
statusenum: activa | completada | anulada
{
  "id": "d3d3d3d3-0000-4000-8000-000000000001",
  "display_id": "R-18",
  "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
  "holder_contact_id": "22222222-0000-4000-8000-000000000002",
  "salesperson_id": "11111111-0000-4000-8000-000000000001",
  "agreed_price_clp": 11900000,
  "hold_expires_on": "2026-09-25",
  "status": "activa"
}

reservation.voided

A reserva was voided, releasing the hold on the unit for good.

Versión 1 · Recurso reservation · data requiere reservations:read

Cuándo se dispara. POST /api/v1/reservations/{id}/void.

CampoTipo
idstring (uuid)
display_idstring (R-…)
void_reasonstring
voided_bystring (uuid)
voided_atstring (ISO 8601)
{
  "id": "d3d3d3d3-0000-4000-8000-000000000002",
  "display_id": "R-22",
  "void_reason": "el cliente no completó el pago del abono a tiempo",
  "voided_by": "11111111-0000-4000-8000-000000000001",
  "voided_at": "2026-09-20T10:05:00.000Z"
}

reservation.abono_disposed

The deposit on a closed reserva was disposed of — kept, refunded or applied.

Versión 1 · Recurso reservation · data requiere reservations:read

Cuándo se dispara. POST /api/v1/reservations/{id}/abono-disposition.

CampoTipo
idstring (uuid)
display_idstring (R-…)
abono_dispositionenum: retenido | devuelto | aplicado
abono_disposition_reasonstring | null
abono_disposition_bystring (uuid)
abono_disposition_atstring (ISO 8601)
{
  "id": "d3d3d3d3-0000-4000-8000-000000000001",
  "display_id": "R-18",
  "abono_disposition": "devuelto",
  "abono_disposition_reason": "el cliente desistió; se devolvió el abono",
  "abono_disposition_by": "11111111-0000-4000-8000-000000000001",
  "abono_disposition_at": "2026-09-21T09:00:00.000Z"
}

reservation.converted

A reserva was converted into a nota de venta, closing the hold and carrying its payments onto the new document.

Versión 1 · Recurso reservation · data requiere reservations:read

Cuándo se dispara. POST /api/v1/reservations/{id}/convert.

CampoTipo
idstring (uuid)
display_idstring (R-…)
sale_note_idstring (uuid)
sale_note_display_idstring (V-…)
{
  "id": "d3d3d3d3-0000-4000-8000-000000000001",
  "display_id": "R-18",
  "sale_note_id": "d4d4d4d4-0000-4000-8000-000000000001",
  "sale_note_display_id": "V-118"
}

sale_note.issued

A nota de venta was issued, allocating its folio.

Versión 1 · Recurso sale_note · data requiere sale_notes:read

Cuándo se dispara. POST /api/v1/sale-notes.

CampoTipo
idstring (uuid)
display_idstring (V-…)
vehicle_idstring (uuid)
buyer_contact_idstring (uuid)
net_clpinteger (whole CLP)
tax_clpinteger (whole CLP) | null
tax_treatmentenum: afecto | exento | pass_through
statusenum: issued | approved | voided
{
  "id": "d4d4d4d4-0000-4000-8000-000000000001",
  "display_id": "V-118",
  "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000002",
  "buyer_contact_id": "22222222-0000-4000-8000-000000000002",
  "net_clp": 11900000,
  "tax_clp": 1900000,
  "tax_treatment": "afecto",
  "status": "issued"
}

sale_note.approved

A nota de venta was approved — the instant it becomes legally valid, the car is marked sold and its deal closes won.

Versión 1 · Recurso sale_note · data requiere sale_notes:read

Cuándo se dispara. POST /api/v1/sale-notes/{id}/approve.

CampoTipo
idstring (uuid)
display_idstring (V-…)
approved_bystring (uuid)
approved_atstring (ISO 8601)
{
  "id": "d4d4d4d4-0000-4000-8000-000000000001",
  "display_id": "V-118",
  "approved_by": "11111111-0000-4000-8000-000000000001",
  "approved_at": "2026-09-19T14:10:00.000Z"
}

sale_note.voided

A nota de venta was voided.

Versión 1 · Recurso sale_note · data requiere sale_notes:read

Cuándo se dispara. POST /api/v1/sale-notes/{id}/void.

CampoTipo
idstring (uuid)
display_idstring (V-…)
void_reasonstring
voided_bystring (uuid)
voided_atstring (ISO 8601)
{
  "id": "d4d4d4d4-0000-4000-8000-000000000001",
  "display_id": "V-118",
  "void_reason": "el banco rechazó el financiamiento del comprador",
  "voided_by": "11111111-0000-4000-8000-000000000001",
  "voided_at": "2026-09-20T09:00:00.000Z"
}

purchase_note.issued

A nota de compra was issued — the instrument that creates a vehicle’s cost basis.

Versión 1 · Recurso purchase_note · data requiere purchase_notes:read

Cuándo se dispara. POST /api/v1/purchase-notes.

CampoTipo
idstring (uuid)
display_idstring (P-…)
seller_contact_idstring (uuid)
net_clpinteger (whole CLP)
tax_treatmentenum: afecto | exento | no_gravado
statusenum: issued | voided
{
  "id": "d5d5d5d5-0000-4000-8000-000000000001",
  "display_id": "P-9",
  "seller_contact_id": "22222222-0000-4000-8000-000000000003",
  "net_clp": 6200000,
  "tax_treatment": "no_gravado",
  "status": "issued"
}

purchase_note.corrected

A live nota de compra was corrected, stamped with who changed it.

Versión 1 · Recurso purchase_note · data requiere purchase_notes:read

Cuándo se dispara. PATCH /api/v1/purchase-notes/{id}.

CampoTipo
idstring (uuid)
display_idstring (P-…)
corrected_bystring (uuid)
corrected_atstring (ISO 8601)
{
  "id": "d5d5d5d5-0000-4000-8000-000000000001",
  "display_id": "P-9",
  "corrected_by": "11111111-0000-4000-8000-000000000001",
  "corrected_at": "2026-09-13T09:00:00.000Z"
}

purchase_note.voided

A nota de compra was voided.

Versión 1 · Recurso purchase_note · data requiere purchase_notes:read

Cuándo se dispara. POST /api/v1/purchase-notes/{id}/void.

CampoTipo
idstring (uuid)
display_idstring (P-…)
void_reasonstring
voided_bystring (uuid)
voided_atstring (ISO 8601)
{
  "id": "d5d5d5d5-0000-4000-8000-000000000001",
  "display_id": "P-9",
  "void_reason": "se anuló la compra: el vendedor desistió antes de firmar",
  "voided_by": "11111111-0000-4000-8000-000000000001",
  "voided_at": "2026-09-14T11:30:00.000Z"
}

payment.recorded

A payment was recorded against a reservation or a nota de venta.

Versión 1 · Recurso document_payment · data requiere document_payments:read

Cuándo se dispara. POST /api/v1/document-payments (deprecated door) or POST /api/v1/payments.

CampoTipo
idstring (uuid)
reservation_idstring (uuid) | null
sale_note_idstring (uuid) | null
instrumentenum: efectivo | transferencia | tarjeta_debito | tarjeta_credito | cheque
amount_clpinteger (whole CLP)
paid_onstring (YYYY-MM-DD)
{
  "id": "d6d6d6d6-0000-4000-8000-000000000001",
  "reservation_id": "d3d3d3d3-0000-4000-8000-000000000001",
  "sale_note_id": null,
  "instrument": "transferencia",
  "amount_clp": 300000,
  "paid_on": "2026-09-18"
}

consignment.created

A consignment contract was created — the vehicle’s tenencia becomes consignacion.

Versión 1 · Recurso consignment_contract · data requiere consignments:read

Cuándo se dispara. POST /api/v1/consignments or POST /api/v1/consignments/bulk.

CampoTipo
idstring (uuid)
vehicle_idstring (uuid)
dueno_contact_idstring (uuid) | null
modalidadenum: en_local | virtual
comision_typeenum: percentage | fixed | null
comision_valuenumber | null
minimo_clpinteger (whole CLP) | null
estadoenum: activo | devuelto | vendido
{
  "id": "d7d7d7d7-0000-4000-8000-000000000001",
  "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
  "dueno_contact_id": "22222222-0000-4000-8000-000000000004",
  "modalidad": "en_local",
  "comision_type": "percentage",
  "comision_value": 10,
  "minimo_clp": 8000000,
  "estado": "activo"
}

consignment.returned

A consignment contract was returned — the car goes back to its owner and its tenencia becomes propio.

Versión 1 · Recurso consignment_contract · data requiere consignments:read

Cuándo se dispara. POST /api/v1/consignments/{id}/return or PATCH .../{id} with estado: "devuelto".

CampoTipo
idstring (uuid)
vehicle_idstring (uuid)
estadoenum: devuelto
{
  "id": "d7d7d7d7-0000-4000-8000-000000000001",
  "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
  "estado": "devuelto"
}

consignment.sold

A consigned car was sold and settled with its owner — the contract moves to vendido and produces a Liquidación.

Versión 1 · Recurso consignment_contract · data requiere consignments:read

Cuándo se dispara. POST /api/v1/consignments/{id}/sell.

CampoTipo
idstring (uuid)
vehicle_idstring (uuid)
liquidacion_idstring (uuid)
amount_venta_clpinteger (whole CLP)
comision_amount_clpinteger (whole CLP)
monto_owner_clpinteger (whole CLP)
{
  "id": "d7d7d7d7-0000-4000-8000-000000000001",
  "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
  "liquidacion_id": "d8d8d8d8-0000-4000-8000-000000000001",
  "amount_venta_clp": 8900000,
  "comision_amount_clp": 890000,
  "monto_owner_clp": 8010000
}

credit_application.recorded

A credit application dossier was recorded.

Versión 1 · Recurso credit_application · data requiere credit_applications:read

Cuándo se dispara. POST /api/v1/credit-applications.

CampoTipo
idstring (uuid)
display_idstring (F-…)
vehicle_idstring (uuid)
buyer_contact_idstring (uuid) | null
institution_idstring (uuid)
requested_amount_clpinteger (whole CLP)
stateenum: recorded | submitted | approved | rejected | withdrawn
{
  "id": "d9d9d9d9-0000-4000-8000-000000000001",
  "display_id": "F-12",
  "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
  "buyer_contact_id": "22222222-0000-4000-8000-000000000002",
  "institution_id": "dadadada-0000-4000-8000-000000000001",
  "requested_amount_clp": 9500000,
  "state": "recorded"
}

credit_application.submitted

A credit application’s carpeta was recorded as sent to the lender.

Versión 1 · Recurso credit_application · data requiere credit_applications:read

Cuándo se dispara. POST /api/v1/credit-applications/{id}/submission.

CampoTipo
idstring (uuid)
display_idstring (F-…)
submitted_bystring (uuid)
submitted_atstring (ISO 8601)
{
  "id": "d9d9d9d9-0000-4000-8000-000000000001",
  "display_id": "F-12",
  "submitted_by": "11111111-0000-4000-8000-000000000001",
  "submitted_at": "2026-09-10T14:00:00.000Z"
}

credit_application.decided

The lender’s verdict was recorded on a credit application.

Versión 1 · Recurso credit_application · data requiere credit_applications:read

Cuándo se dispara. POST /api/v1/credit-applications/{id}/decision.

CampoTipo
idstring (uuid)
display_idstring (F-…)
outcomeenum: approved | rejected
approved_amount_clpinteger (whole CLP) | null
decided_bystring (uuid)
decided_atstring (ISO 8601)
{
  "id": "d9d9d9d9-0000-4000-8000-000000000001",
  "display_id": "F-12",
  "outcome": "approved",
  "approved_amount_clp": 9200000,
  "decided_by": "11111111-0000-4000-8000-000000000001",
  "decided_at": "2026-09-16T10:00:00.000Z"
}

credit_application.withdrawn

A credit application was pulled by the dealership — terminal.

Versión 1 · Recurso credit_application · data requiere credit_applications:read

Cuándo se dispara. POST /api/v1/credit-applications/{id}/withdrawal.

CampoTipo
idstring (uuid)
display_idstring (F-…)
withdrawn_reasonstring
withdrawn_bystring (uuid)
withdrawn_atstring (ISO 8601)
{
  "id": "d9d9d9d9-0000-4000-8000-000000000001",
  "display_id": "F-12",
  "withdrawn_reason": "el cliente obtuvo mejores condiciones en otra financiera",
  "withdrawn_by": "11111111-0000-4000-8000-000000000001",
  "withdrawn_at": "2026-09-18T09:00:00.000Z"
}

price_approval.requested

Authority over a below-reference price was requested on a quote, a reservation or a nota de venta.

Versión 1 · Recurso price_approval · data requiere price_approval:read

Cuándo se dispara. POST /api/v1/price-approvals.

CampoTipo
idstring (uuid)
quote_idstring (uuid) | null
reservation_idstring (uuid) | null
sale_note_idstring (uuid) | null
requested_price_clpinteger (whole CLP)
requester_user_idstring (uuid)
statusenum: pending | approved | rejected
{
  "id": "dbdbdbdb-0000-4000-8000-000000000001",
  "quote_id": null,
  "reservation_id": null,
  "sale_note_id": "d4d4d4d4-0000-4000-8000-000000000001",
  "requested_price_clp": 10900000,
  "requester_user_id": "11111111-0000-4000-8000-000000000002",
  "status": "pending"
}

price_approval.decided

A price approval request was granted or rejected. Terminal — no second decision.

Versión 1 · Recurso price_approval · data requiere price_approval:read

Cuándo se dispara. POST /api/v1/price-approvals/{id}/decision.

CampoTipo
idstring (uuid)
statusenum: approved | rejected
decision_notestring | null
decider_user_idstring (uuid)
decided_atstring (ISO 8601)
{
  "id": "dbdbdbdb-0000-4000-8000-000000000001",
  "status": "approved",
  "decision_note": "autorizado por gerencia dado el tiempo en stock",
  "decider_user_id": "11111111-0000-4000-8000-000000000001",
  "decided_at": "2026-09-19T16:05:00.000Z"
}

conversation.created

A new conversation was opened — a customer wrote in on a channel, or the workspace opened the thread from its side.

Versión 1 · Recurso conversation · data requiere conversations:read

Cuándo se dispara. The first inbound message of a thread (any channel), POST /conversations, or a proactive outbound conversation (ADR 0108). NOT on a historical backfill: importing a dealer’s past threads is inert by design, so a coexistence or history sync fires nothing.

CampoTipo
conversation_idstring (uuid)
display_idstring — the ID visible, e.g. C-3052
channelstring
contact_idstring (uuid) | null
messaging_account_idstring (uuid) | null
ticket_idstring (uuid) | null
handlerenum: bot | human | external
statusenum: open | pending | snoozed | resolved | closed
sourcestring | null — the portal or campaign it came from
created_atISO 8601 timestamp
{
  "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
  "display_id": "C-3052",
  "channel": "whatsapp",
  "contact_id": "22222222-0000-4000-8000-000000000001",
  "messaging_account_id": "16161616-0000-4000-8000-000000000001",
  "ticket_id": null,
  "handler": "bot",
  "status": "open",
  "source": null,
  "created_at": "2026-09-22T11:06:33.744Z"
}

conversation.assigned

A conversation changed hands: a new assignee, a new handler, or both.

Versión 1 · Recurso conversation · data requiere conversations:read

Cuándo se dispara. POST /conversations/:id/assign, /claim, /handoff or /return-to-ai, the assignment rules routing an unowned inbound, and a ticket assignment mirroring onto its conversation. NOT on a no-op (the same assignee and the same handler).

CampoTipo
conversation_idstring (uuid)
display_idstring — the ID visible, e.g. C-3052
from_assignee_user_idstring (uuid) | null
to_assignee_user_idstring (uuid) | null
from_handlerenum: bot | human | external | null
to_handlerenum: bot | human | external
ticket_idstring (uuid) | null
{
  "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
  "display_id": "C-3052",
  "from_assignee_user_id": null,
  "to_assignee_user_id": "11111111-0000-4000-8000-000000000001",
  "from_handler": "bot",
  "to_handler": "human",
  "ticket_id": "dddddddd-0000-4000-8000-000000000001"
}

conversation.resolved

A conversation stopped being open work. changes.status.to is resolved (dealt with, reopened by the next inbound message) or closed (terminal).

Versión 1 · Recurso conversation · data requiere conversations:read

Cuándo se dispara. POST /conversations/:id/resolve, POST /conversations/:id/close on a thread that was not already resolved, and the auto-resolve worker. The auto-CLOSE of an already-resolved thread fires nothing: the conclusion was already reported, and reporting the grace window as a second conclusion is the two-events-for-one-action mistake. A ticket.resolved / ticket.closed follows only when no sibling conversation on that ticket is still active.

CampoTipo
conversation_idstring (uuid)
display_idstring — the ID visible, e.g. C-3052
from_statusenum: open | pending | snoozed | resolved
to_statusenum: resolved | closed
resolved_byenum: bot | human | system | null
resolved_atISO 8601 timestamp | null
ticket_idstring (uuid) | null
{
  "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
  "display_id": "C-3052",
  "from_status": "open",
  "to_status": "resolved",
  "resolved_by": "human",
  "resolved_at": "2026-09-22T11:15:06.653Z",
  "ticket_id": "dddddddd-0000-4000-8000-000000000001"
}

message.received

The customer sent a message.

Versión 1 · Recurso message · data requiere conversations:read

Cuándo se dispara. A customer message is persisted on any channel. NOT on a history backfill (a coexistence or Instagram history sync writes customer messages with the fan-out switched off, so importing months of threads does not replay months of events), and not for the internal system lines the thread carries for assignments and status changes.

CampoTipo
message_idstring (uuid)
conversation_idstring (uuid)
contact_idstring (uuid) | null
channelstring
typeenum: text | image | audio | video | file | location | …
contentstring — empty on a media-only message
external_message_idstring | null — the provider’s id, not a Vitrina reference
created_atISO 8601 timestamp
{
  "message_id": "eeeeeeee-0000-4000-8000-000000000001",
  "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
  "contact_id": "22222222-0000-4000-8000-000000000001",
  "channel": "whatsapp",
  "type": "text",
  "content": "¿Me pueden confirmar la hora del jueves?",
  "external_message_id": "wamid.HBgLNTY5NzcwMDAwMjE",
  "created_at": "2026-09-22T11:07:02.824Z"
}

message.sent

The workspace sent a message to the customer. The envelope’s author says who wrote it: a member, a member through a connected app or a personal token, an API key, or the AI agent.

Versión 1 · Recurso message · data requiere conversations:read

Cuándo se dispara. An outbound message is persisted — the AI answering, a teammate replying, an API send, a template, a flow, a location pin, an attachment or a voice note. It fires at PERSIST, which on most channels is the moment Vitrina commits to the send rather than the moment the provider confirms it: delivery_status carries what was known then, and a later failure or delivery receipt shows on the message row (GET /conversations/{id}/messages), not as a second event. Internal system lines and internal notes are not messages and fire nothing.

CampoTipo
message_idstring (uuid)
conversation_idstring (uuid)
contact_idstring (uuid) | null
channelstring
typeenum: text | image | audio | video | file | location | …
contentstring
sender_typeenum: ai_agent | human_user | api_key
delivery_statusenum: sent | delivered | read | retrying | failed | null
created_atISO 8601 timestamp
{
  "message_id": "eeeeeeee-0000-4000-8000-000000000002",
  "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
  "contact_id": "22222222-0000-4000-8000-000000000001",
  "channel": "whatsapp",
  "type": "text",
  "content": "Hola Rodrigo, te confirmo la visita del jueves a las 10:00.",
  "sender_type": "api_key",
  "delivery_status": "sent",
  "created_at": "2026-09-22T11:08:10.882Z"
}

ticket.opened

A new ticket has been opened on a conversation.

Versión 1 · Recurso ticket · data requiere tickets:read

Cuándo se dispara. POST /conversations/:id/tickets, the open_ticket / handoff tools, or a stale handoff being reopened with a brand-new ticket.

CampoTipo
ticket_idstring (uuid)
origin_conversation_idstring (uuid) | null
reasonstring | null
opened_byenum: bot | human | system | rule
opened_viastring | null
brandstring | null
{
  "ticket_id": "dddddddd-0000-4000-8000-000000000001",
  "origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
  "reason": "El cliente pide reagendar su visita",
  "opened_by": "human",
  "opened_via": "admin_ui",
  "brand": null
}

ticket.reopened

A previously resolved or closed ticket has been reopened.

Versión 1 · Recurso ticket · data requiere tickets:read

Cuándo se dispara. reopenTicket() in the ticket service — typically triggered by new inbound activity on a stale handoff.

CampoTipo
ticket_idstring (uuid)
origin_conversation_idstring (uuid) | null
reasonstring | null
brandstring | null
{
  "ticket_id": "dddddddd-0000-4000-8000-000000000001",
  "origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
  "reason": "customer_replied",
  "brand": null
}

ticket.resolved

A ticket has transitioned to status=resolved.

Versión 1 · Recurso ticket · data requiere tickets:read

Cuándo se dispara. PUT /tickets/:id/close (which resolves, despite the path), the auto-resolve worker, and a conversation resolve cascading to its ticket when no sibling conversation is still active. NOT on PUT /tickets/:id/status with status=resolved — that route sets the column without stamping resolved_by/resolved_at, so it has never emitted this.

CampoTipo
ticket_idstring (uuid)
origin_conversation_idstring (uuid) | null
resolved_bystring
resolved_atISO 8601 timestamp
brandstring | null
{
  "ticket_id": "dddddddd-0000-4000-8000-000000000001",
  "origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
  "resolved_by": "human",
  "resolved_at": "2026-09-22T11:15:06.653Z",
  "brand": null
}

ticket.closed

A ticket has transitioned to status=closed (terminal).

Versión 1 · Recurso ticket · data requiere tickets:read

Cuándo se dispara. A conversation is closed (auto-close worker after the grace window, or a manual close) and no other open conversation holds the ticket.

CampoTipo
ticket_idstring (uuid)
origin_conversation_idstring (uuid) | null
closed_bystring
brandstring | null
{
  "ticket_id": "dddddddd-0000-4000-8000-000000000001",
  "origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
  "closed_by": "auto",
  "brand": null
}

sla.breached

An open ticket crossed an applicable SLA policy target (first response or resolution). Fired once per (ticket, policy, breach type).

Versión 1 · Recurso ticket · data requiere tickets:read

Cuándo se dispara. The SLA monitor worker (10-min tick) detects a newly-breached open ticket. Business hours are honoured when the policy is business_hours_only.

CampoTipo
ticket_idstring (uuid)
ticket_display_idstring | null (e.g. T-1247)
policy_idstring (uuid)
policy_namestring
breach_typeenum: first_response | resolution
target_minutesinteger
elapsed_secondsinteger
assigneestring | null (agent display name)
breached_atISO 8601 timestamp
{
  "ticket_id": "9b1c…",
  "ticket_display_id": "T-1247",
  "policy_id": "ec95…",
  "policy_name": "Soporte estándar",
  "breach_type": "resolution",
  "target_minutes": 1440,
  "elapsed_seconds": 95400,
  "assignee": "Paulina Rojas",
  "breached_at": "2026-06-07T18:20:00.000Z"
}

agent_scenario_run.completed

An Agent Evals scenario run reached a terminal VERDICT (passed | partial | failed | error) — ADR 0098. A cancelled run does not fire it: the configuration moved under the run (or a human stopped it), so there is no result to report and a mass cancellation would otherwise be one webhook per run.

Versión 1 · Recurso agent_scenario_run · data requiere ai_agents:read

Cuándo se dispara. The scenario-run worker finishes a run queued by POST /ai-agents/:id/scenarios/run, a single-scenario run, or a publish.

CampoTipo
run_idstring (uuid)
scenario_idstring (uuid)
scenario_display_idstring (EV-n)
ai_agent_idstring (uuid)
agent_version_kind"draft" | "live"
batch_idstring (uuid) | null
status"passed" | "partial" | "failed" | "error"
scorenumber 0-100 | null
hard_fails[{ id, kind, evidence }]
duration_msnumber
{
  "run_id": "3c1e2d4f-…",
  "scenario_id": "9a7b6c5d-…",
  "scenario_display_id": "EV-12",
  "ai_agent_id": "7c2e3a0e-…",
  "agent_version_kind": "draft",
  "batch_id": "b1a2c3d4-…",
  "status": "failed",
  "score": 0,
  "hard_fails": [
    {
      "id": "grounded-3",
      "kind": "grounded",
      "evidence": "turn 1: not in any tool result → 10:20"
    }
  ],
  "duration_ms": 48210
}

agent_scenario_suite.completed

An Agent Evals SUITE run finished — every scenario run in its batch reached a terminal status (ADR 0098 phase 1B).

Versión 1 · Recurso agent_scenario_suite_run · data requiere ai_agents:read

Cuándo se dispara. The last run of the suite's batch finishes, whatever started it: POST /ai-agents/:id/scenario-suites/:suiteId/run, the nightly cron, or a publish of an agent that has a golden suite. Exactly once per suite run — the completion flip is a conditional update.

CampoTipo
suite_run_idstring (uuid)
suite_idstring (uuid)
suite_namestring | null
kindenum: golden|nightly|exploratory|on_change (null if the suite was deleted mid-run)
ai_agent_idstring (uuid)
batch_idstring (uuid) — the agent_scenario_run.batch_id
triggerenum: manual|publish|cron|api
agent_version_kind"draft" | "live"
agent_version_numbernumber | null
summary{ n, passed, partial, failed, error, hard_fails, pass_rate (0-100 | null), flaky, cost_usd, duration_ms }
delta{ pass_rate: number | null, vs_suite_run_id: string | null } | null
{
  "suite_run_id": "5f6e7d8c-…",
  "suite_id": "1a2b3c4d-…",
  "suite_name": "Golden",
  "kind": "golden",
  "ai_agent_id": "7c2e3a0e-…",
  "batch_id": "b1a2c3d4-…",
  "trigger": "cron",
  "agent_version_kind": "live",
  "agent_version_number": 41,
  "summary": {
    "n": 66,
    "passed": 54,
    "partial": 7,
    "failed": 4,
    "error": 1,
    "hard_fails": 4,
    "pass_rate": 83.1,
    "flaky": 2,
    "cost_usd": 1.842,
    "duration_ms": 412000
  },
  "delta": {
    "pass_rate": -3.1,
    "vs_suite_run_id": "4e5d6c7b-…"
  }
}

ai_agent.publish

An AI agent draft has been published as the live config.

Versión 1 · Recurso ai_agent · data requiere ai_agents:read

Cuándo se dispara. POST /ai-agents/:id/publish.

CampoTipo
ai_agent_idstring (uuid)
namestring | null
updated_atISO 8601 timestamp | null
{
  "ai_agent_id": "7c2e3a0e-…",
  "name": "Soporte Vitrina",
  "updated_at": "2026-05-15T03:47:40.357Z"
}

ai_agent_graph.publish

A multi-agent graph was published — the draft topology became the live one, snapshotted as a new immutable version.

Versión 1 · Recurso ai_agent_graph · data requiere ai_agents:read

Cuándo se dispara. The graph publish seam validates the draft and inserts the version row (ai-agent-graph.service). The eval cascade that may follow does not gate the event.

CampoTipo
tenant_idstring (uuid)
graph_idstring (uuid)
version_idstring (uuid) — the snapshot that just went live
version_numberinteger — 1 for the first publish, then monotonic
topologyenum: single | sequential | routing | supervisor | parallel | evaluator_loop
{
  "tenant_id": "00000000-0000-4000-8000-000000000001",
  "graph_id": "9f1c6b40-3a2e-4f58-9d17-70c4f1a2b3d5",
  "version_id": "d0a7e912-5c84-4b6f-8e1a-2f9c7b3d0e45",
  "version_number": 7,
  "topology": "supervisor"
}

cost.budget_alert

An AI spend threshold was crossed. ⚠ ONE NAME, THREE PRODUCERS, THREE SHAPES — branch on the keys you get, not on a single schema. The Copilot credit gate emits domain: "copilot" with the credit counters; the Copilot dollar-margin guard emits domain: "copilot" with the USD limits; the graph runtime budget emits tenant_id + pct and no domain at all. Every field below is therefore optional in practice.

Versión 1 · Recurso workspace · data requiere billing:read

Cuándo se dispara. A tenant exhausts its monthly Copilot credit allowance, crosses its soft or hard USD margin limit (once each per month — the row carries the dedupe stamp), or crosses 50%, 80% or 100% of its monthly graph-runtime cost budget (once per threshold per month).

CampoTipo
domainenum: copilot — present only on the two Copilot producers; absent on the graph-runtime one
monthstring (YYYY-MM) — Copilot producers only
severityenum: credits_exhausted | soft | hard — Copilot producers only
credit_allowanceinteger — credits granted this month; credits_exhausted only
credits_usedinteger — credits consumed; credits_exhausted only
grace_creditsinteger — credits served past the allowance; credits_exhausted only
used_usdnumber — margin spent this month; Copilot producers only
soft_limit_usdnumber — soft/hard severities only
hard_limit_usdnumber — soft/hard severities only
tenant_idstring (uuid) — graph-runtime producer only
pctinteger: 50 | 80 | 100 — the threshold crossed; graph-runtime producer only
budget_usdnumber — the monthly budget; graph-runtime producer only
spent_usdnumber — spent so far this month; graph-runtime producer only
{
  "domain": "copilot",
  "month": "2026-09",
  "severity": "soft",
  "used_usd": 41.28,
  "soft_limit_usd": 40,
  "hard_limit_usd": 60
}

runtime.budget_exceeded

A multi-agent graph turn hit one of its runtime ceilings and was cut short. The turn still answers; what stopped is the orchestration, so a graph firing this repeatedly is mis-shaped rather than merely busy.

Versión 1 · Recurso ai_agent_graph · data requiere ai_agents:read

Cuándo se dispara. The graph runtime refuses to go further on one of four ceilings: recursion depth, fan-out width, per-node turns, or evaluator-loop iterations.

CampoTipo
tenant_idstring (uuid)
graph_idstring (uuid)
topologyenum: single | sequential | routing | supervisor | parallel | evaluator_loop
reasonenum: depth | fan_out | node_turns | evaluator_iterations
{
  "tenant_id": "00000000-0000-4000-8000-000000000001",
  "graph_id": "9f1c6b40-3a2e-4f58-9d17-70c4f1a2b3d5",
  "topology": "evaluator_loop",
  "reason": "evaluator_iterations"
}

kb.source.create

A new knowledge-base source has been added to the workspace.

Versión 1 · Recurso kb_source · data requiere kb:read

Cuándo se dispara. POST /kb/sources.

CampoTipo
kb_source_idstring (uuid)
titlestring | null
tagsstring[] | null
{
  "kb_source_id": "b2…",
  "title": "Devoluciones — política",
  "tags": [
    "returns",
    "policy"
  ]
}

kb.source.embed

A KB source has been (re-)embedded; vectors are ready for retrieval.

Versión 1 · Recurso kb_source · data requiere kb:read

Cuándo se dispara. POST /kb/sources/:id/embed (manual) or the kb-ingestion worker after content changes.

CampoTipo
kb_source_idstring (uuid)
chunksinteger
{
  "kb_source_id": "b2…",
  "chunks": 47
}

kb.source.update

A knowledge-base source’s summary, tags, status or verified_until changed.

Versión 1 · Recurso kb_source · data requiere kb:read

Cuándo se dispara. PUT /kb/sources/:id.

CampoTipo
kb_source_idstring (uuid)
fieldsstring[] — the body keys that were sent
{
  "kb_source_id": "a5a5a5a5-0000-4000-8000-000000000001",
  "fields": [
    "status"
  ]
}

kb.source.delete

A knowledge-base source was HARD-deleted — the row and its embedded chunks are gone; retrieval can no longer return it.

Versión 1 · Recurso kb_source · data requiere kb:read

Cuándo se dispara. DELETE /kb/sources/:id.

CampoTipo
kb_source_idstring (uuid)
{
  "kb_source_id": "a5a5a5a5-0000-4000-8000-000000000001"
}

kb_file.uploaded

A document was added to the workspace’s knowledge file library. A 201 here means the bytes are stored, not that the file is searchable yet — ingestion runs best-effort on a worker.

Versión 1 · Recurso kb_file · data requiere kb:read

Cuándo se dispara. POST /kb-files (multipart upload).

CampoTipo
kb_file_idstring (uuid)
namestring
content_typestring
{
  "kb_file_id": "a6a6a6a6-0000-4000-8000-000000000001",
  "name": "Garantías 2026.pdf",
  "content_type": "application/pdf"
}

kb_file.deleted

A knowledge file was removed from the workspace (ADR 0093 — a soft delete: the row survives tombstoned, but every agent attachment goes and the embedded chunks are purged, so this is the customer-visible ending).

Versión 1 · Recurso kb_file · data requiere kb:read

Cuándo se dispara. DELETE /kb-files/:id.

CampoTipo
kb_file_idstring (uuid)
{
  "kb_file_id": "a6a6a6a6-0000-4000-8000-000000000001"
}

skill.created

A new skill was added to the tenant library.

Versión 1 · Recurso skill · data requiere ai_agents:read

Cuándo se dispara. POST /skills.

CampoTipo
skill_idstring (uuid)
namestring
{
  "skill_id": "a4a4a4a4-0000-4000-8000-000000000001",
  "name": "Agendar hora"
}

skill.updated

A skill’s content or channel_overrides changed — applies instantly to every agent it is attached to; there is no draft/publish step for a skill.

Versión 1 · Recurso skill · data requiere ai_agents:read

Cuándo se dispara. PUT /skills/:id.

CampoTipo
skill_idstring (uuid)
namestring
{
  "skill_id": "a4a4a4a4-0000-4000-8000-000000000001",
  "name": "Agendar hora"
}

skill.deleted

A skill was removed (ADR 0093 — soft delete): it leaves the library and every agent it was attached to immediately.

Versión 1 · Recurso skill · data requiere ai_agents:read

Cuándo se dispara. DELETE /skills/:id.

CampoTipo
skill_idstring (uuid)
{
  "skill_id": "a4a4a4a4-0000-4000-8000-000000000001"
}

custom_tool.created

A new custom tool was added to the Tool Store.

Versión 1 · Recurso custom_tool · data requiere functions:read

Cuándo se dispara. POST /custom-tools.

CampoTipo
custom_tool_idstring (uuid)
namestring
{
  "custom_tool_id": "a7a7a7a7-0000-4000-8000-000000000001",
  "name": "consultar_clima"
}

custom_tool.updated

A custom tool’s definition changed — its request template, parameters, auth configuration or enabled state. Fires from both the full-replace (PUT) and partial (PATCH) routes.

Versión 1 · Recurso custom_tool · data requiere functions:read

Cuándo se dispara. PUT or PATCH /custom-tools/:id.

CampoTipo
custom_tool_idstring (uuid)
namestring
{
  "custom_tool_id": "a7a7a7a7-0000-4000-8000-000000000001",
  "name": "consultar_clima"
}

custom_tool.deleted

A custom tool was removed. Every agent wired to it loses it immediately; past tool_invocation audit rows survive.

Versión 1 · Recurso custom_tool · data requiere functions:read

Cuándo se dispara. DELETE /custom-tools/:id.

CampoTipo
custom_tool_idstring (uuid)
{
  "custom_tool_id": "a7a7a7a7-0000-4000-8000-000000000001"
}

lead.created

A new lead has been opened — an opportunity the workspace is now working: a buyer asking about a car, a patient asking for a treatment, a company asking for a quote.

Versión 1 · Recurso lead · data requiere leads:read

Cuándo se dispara. POST /leads, POST /leads/from-conversation, POST /leads/import, the create_lead AI tool, or a marketplace / dealer-website ingest.

CampoTipo
lead_idstring (uuid)
contact_idstring (uuid)
pipeline_idstring (uuid)
stage_idstring (uuid)
sourceenum: conversation | marketplace | manual | import | ai_agent | website | chileautos | yapo | mercadolibre | facebook_marketplace
intentenum: buy | sell | financing | trade_in (defaults to buy)
titlestring
value_amountnumber | null
value_currencystring | null
owner_user_idstring (uuid) | null
team_idstring (uuid) | null
{
  "lead_id": "b9e4a1c2-3d4e-4f5a-8b6c-7d8e9f0a1b2c",
  "contact_id": "22222222-0000-4000-8000-000000000001",
  "pipeline_id": "44444444-0000-4000-8000-000000000001",
  "stage_id": "55555555-0000-4000-8000-000000000001",
  "source": "conversation",
  "intent": "buy",
  "title": "María — prueba de manejo Corolla 2022",
  "value_amount": 8990000,
  "value_currency": "CLP",
  "owner_user_id": "3897d483-9e84-4971-8b17-9a41a6811966",
  "team_id": null
}

lead.stage_changed

A lead moved to a different stage of its board. EVERY stage change fires this — the REST move, a drag on the board, an AI tool, a quick action that closes the lead, the lead reviewer — so a receiver that tracks progress needs only this one event.

Versión 1 · Recurso lead · data requiere leads:read

Cuándo se dispara. PUT /leads/:id/stage, PUT /leads/:id/won | /lost | /unqualify | /reopen, the update_lead_stage AI tool, or the lead reviewer moving a card. NOT on a no-op move (already on that stage), and not on PUT /leads/:id/pipeline — that one reports its own stage move inside lead.pipeline_changed.

CampoTipo
lead_idstring (uuid)
from_stage_idstring (uuid) | null
to_stage_idstring (uuid)
to_stage_slugstring | null
reasonstring | null
{
  "lead_id": "b9e4a1c2-3d4e-4f5a-8b6c-7d8e9f0a1b2c",
  "from_stage_id": "55555555-0000-4000-8000-000000000001",
  "to_stage_id": "55555555-0000-4000-8000-000000000002",
  "to_stage_slug": "contacted",
  "reason": "la llamamos y confirmó la hora"
}

lead.pipeline_changed

A lead was reclassified onto a different sales board, landing on that board’s first stage.

Versión 1 · Recurso lead · data requiere leads:read

Cuándo se dispara. PUT /leads/:id/pipeline or the update_lead_pipeline AI tool. No separate lead.stage_changed fires for the landing stage — this event already carries it.

CampoTipo
lead_idstring (uuid)
from_pipeline_idstring (uuid) | null
to_pipeline_idstring (uuid)
from_stage_idstring (uuid) | null
to_stage_idstring (uuid)
reasonstring | null
{
  "lead_id": "b9e4a1c2-3d4e-4f5a-8b6c-7d8e9f0a1b2c",
  "from_pipeline_id": "44444444-0000-4000-8000-000000000001",
  "to_pipeline_id": "44444444-0000-4000-8000-000000000002",
  "from_stage_id": "55555555-0000-4000-8000-000000000002",
  "to_stage_id": "55555555-0000-4000-8000-000000000003",
  "reason": "es una venta mayorista"
}

lead.assigned

A lead was (re)assigned to an owner or a team. One assignee owns the whole relationship, so the lead’s conversations and their tickets move with it.

Versión 1 · Recurso lead · data requiere leads:read

Cuándo se dispara. PUT /leads/:id/assign, PUT /leads/:id with owner_user_id, or the team rotation picking an owner on create.

CampoTipo
lead_idstring (uuid)
owner_user_idstring (uuid) | null
team_idstring (uuid) | null
{
  "lead_id": "b9e4a1c2-3d4e-4f5a-8b6c-7d8e9f0a1b2c",
  "owner_user_id": "3897d483-9e84-4971-8b17-9a41a6811966",
  "team_id": "cccccccc-0000-4000-8000-000000000001"
}

lead.won

A lead closed in the workspace’s favour.

Versión 1 · Recurso lead · data requiere leads:read

Cuándo se dispara. PUT /leads/:id/won, or any move onto a stage whose category is won. Only on the STATUS FLIP: moving an already-won lead between two won columns is a board tidy-up, not a second sale.

CampoTipo
lead_idstring (uuid)
reasonstring | null
value_amountnumber | null
value_currencystring | null
{
  "lead_id": "b9e4a1c2-3d4e-4f5a-8b6c-7d8e9f0a1b2c",
  "reason": "cerró con financiamiento propio",
  "value_amount": 8750000,
  "value_currency": "CLP"
}

lead.lost

A real opportunity did not close.

Versión 1 · Recurso lead · data requiere leads:read

Cuándo se dispara. PUT /leads/:id/lost, or any move onto a stage whose category is lost. Status flip only, as for lead.won.

CampoTipo
lead_idstring (uuid)
reasonstring | null
value_amountnumber | null
value_currencystring | null
{
  "lead_id": "b9e4a1c2-3d4e-4f5a-8b6c-7d8e9f0a1b2c",
  "reason": "compró en otra parte",
  "value_amount": 8990000,
  "value_currency": "CLP"
}

lead.unqualified

There was never an opportunity here — the wrong product, the wrong workspace, a duplicate, a test. A DISTINCT outcome from lead.lost: unqualified leads are excluded from win rate on both sides, so qualifying early never looks like losing.

Versión 1 · Recurso lead · data requiere leads:read

Cuándo se dispara. PUT /leads/:id/unqualify, or any move onto a stage whose category is unqualified. Status flip only, as for lead.won.

CampoTipo
lead_idstring (uuid)
reasonstring | null
value_amountnumber | null
value_currencystring | null
{
  "lead_id": "b9e4a1c2-3d4e-4f5a-8b6c-7d8e9f0a1b2c",
  "reason": "buscaba un servicio que no ofrecemos",
  "value_amount": null,
  "value_currency": "CLP"
}

macro.applied

A macro has been applied to a conversation AND the macro opts into the webhook notification (i.e. its action list includes a run_webhook step). Lean payload — consumers use the ids to fetch detail if they need it.

Versión 1 · Recurso conversation · data requiere conversations:read

Cuándo se dispara. POST /macros/:id/apply with a conversation_id, when the macro's actions array contains at least one step with type=run_webhook. Fires once per apply regardless of how many run_webhook steps the macro has.

CampoTipo
macro_idstring (uuid)
macro_namestring
conversation_idinteger
ticket_idinteger | null
contact_idinteger | null
executed_stepsarray of {type: string, ok: boolean, error: string | null}
applied_by_user_idstring (uuid) | null
applied_atISO 8601 timestamp
{
  "macro_id": "8f74aa7c-df40-478d-b5f5-5cfc2eb98da4",
  "macro_name": "Send return label",
  "conversation_id": 10031,
  "ticket_id": 10042,
  "contact_id": 142,
  "executed_steps": [
    {
      "type": "send_message",
      "ok": true
    },
    {
      "type": "add_label",
      "ok": true
    },
    {
      "type": "change_status",
      "ok": true
    },
    {
      "type": "run_webhook",
      "ok": true
    }
  ],
  "applied_by_user_id": "3897d483-9e84-4971-8b17-9a41a6811966",
  "applied_at": "2026-05-15T03:47:40.357Z"
}

stock.source_retired

Retiro de fuente (ADR 0031): a decommissioned import source handed its vehicles to dealer ownership.

Versión 1 · Recurso marketplace_integration · data requiere marketplace:read

Cuándo se dispara. POST /api/v1/vehicles/source-retirement/execute completes (once per retired source).

CampoTipo
integration_idstring (uuid)
providerstring
retiredinteger
revivedinteger
kept_hiddeninteger
{
  "integration_id": "a3f1…",
  "provider": "veekls",
  "retired": 156,
  "revived": 2,
  "kept_hidden": 3
}

stock.synced

A stock integration finished importing active inventory from a marketplace portal.

Versión 1 · Recurso marketplace_integration · data requiere marketplace:read

Cuándo se dispara. syncStock() completes successfully (manual POST /marketplaces/:id/sync or the scheduled tick).

CampoTipo
integration_idstring (uuid)
providerstring
vehicles_importedinteger
vehicles_updatedinteger
vehicles_deactivatedinteger
{
  "integration_id": "a3f1…",
  "provider": "chileautos",
  "vehicles_imported": 12,
  "vehicles_updated": 38,
  "vehicles_deactivated": 3
}

vehicle.published

A vehicle was published (or re-published) to a marketplace portal.

Versión 1 · Recurso vehicle · data requiere stock:read

Cuándo se dispara. publishVehicle() succeeds for a portal via POST /vehicles/:id/publish.

CampoTipo
vehicle_idstring (uuid)
integration_idstring (uuid)
providerstring
external_idstring
{
  "vehicle_id": "c8d2…",
  "integration_id": "a3f1…",
  "provider": "chileautos",
  "external_id": "6f2b1f7e-1f2a-4c3d-9e8f-0a1b2c3d4e5f"
}

vehicle.unpublished

A vehicle’s publication was removed from a marketplace portal — either a synchronous unpublish or a Facebook Marketplace device-agent task confirming a remove it had only enqueued.

Versión 1 · Recurso vehicle · data requiere stock:read

Cuándo se dispara. unpublishVehicle() succeeds via DELETE /vehicles/{id}/publications/{pubId}, or the device agent reports a remove task done via POST /device-agent/tasks/{id}/result (facebook_marketplace — the enqueue itself fires nothing; only the confirmed removal does).

CampoTipo
vehicle_idstring (uuid)
integration_idstring (uuid)
providerstring
external_idstring | null — the portal’s ad id, when one was ever assigned
{
  "vehicle_id": "c8d2…",
  "integration_id": "a3f1…",
  "provider": "mercadolibre",
  "external_id": "MLC-1234567890"
}

vehicle.publish_failed

A publish, update or unpublish attempt against a marketplace portal failed — the portal rejected it, the connection errored, or (Facebook Marketplace) the device agent reported the task could not complete.

Versión 1 · Recurso vehicle · data requiere stock:read

Cuándo se dispara. The per-integration catch in publishVehicle() via POST /vehicles/{id}/publish, or the device agent reporting a publish/update/remove task failed via POST /device-agent/tasks/{id}/result.

CampoTipo
vehicle_idstring (uuid)
integration_idstring (uuid)
providerstring
errorstring
{
  "vehicle_id": "c8d2…",
  "integration_id": "a3f1…",
  "provider": "chileautos",
  "error": "The portal rejected the listing: missing required field \"version\"."
}

vehicle.created

A vehicle entered this workspace — one event per car, whichever door it came through: the manual/API create (POST /vehicles) or a marketplace stock import. Use it to mirror the lot: id is the handle every other endpoint takes, and url fetches the full record. A car that is MERGED into one you already know about does not fire this event (the identity resolver attached the new listing to the existing vehicle, so no car appeared); neither does a re-import of a car already in stock.

Versión 1 · Recurso vehicle · data requiere stock:read

Cuándo se dispara. A vehicle row is inserted — vehicle.service create (manual/API intake) or either insert door of the marketplace-sync importer (a newcomer, and a newcomer whose plate conflicted and was imported plate-absent). Retries of one delivery reuse the event id.

CampoTipo
idstring (uuid) — pass to GET /api/v1/vehicles/{id}
patentestring | null — Chilean plate as entered; null when absent
vinstring (17 chars) | null
makestring | null
modelstring | null
versionstring | null
yearinteger | null
kminteger | null — kilometres (a miles odometer is converted)
priceinteger | null — whole CLP pesos; null = no price recorded, 0 = free
statusenum: disponible | reservado | vendido
tenenciaenum: propio | consignacion
locationobject { id: uuid, name: string } | null — the branch, null when untagged
sourcestring — manual, or the portal the import came from (chileautos, mercadolibre, yapo, …)
created_atstring (ISO 8601)
urlstring (absolute URL) | null — this vehicle on the Vitrina API; null only on a deployment with no public API base configured
{
  "id": "c8d24f0e-7a1b-4c3d-9e8f-0a1b2c3d4e5f",
  "patente": "KXRT52",
  "vin": "JTDBR32E730123456",
  "make": "Toyota",
  "model": "Yaris",
  "version": "XLS 1.5 AT",
  "year": 2019,
  "km": 48200,
  "price": 8990000,
  "status": "disponible",
  "tenencia": "propio",
  "location": {
    "id": "25424bd5-57e1-412c-a0fe-5c375608cef7",
    "name": "Manquehue"
  },
  "source": "chileautos",
  "created_at": "2026-09-15T13:22:41.118Z",
  "url": "https://api.vitrinadev.com/api/v1/vehicles/c8d24f0e-7a1b-4c3d-9e8f-0a1b2c3d4e5f"
}

vehicle.attachment.created

A document was filed into a vehicle's expediente — padrón, cédula del consignante, contrato firmado, certificado de anotaciones vigentes or factura. The event carries METADATA AND A FETCH URL, never file bytes and never a signed URL: GET content_url with an API key holding vehicle_registry:read and the bytes stream back (every read is audited). ⚠ PERSONAL DATA: filename is chosen by the person who uploaded the file and regularly carries a natural person's name or RUT, and a cedula is a third party's identity document. Receiving these makes your platform a PROCESSOR of the dealership's personal data under Ley 21.719 — store them under the dealership's retention rules, keep them out of logs, and be able to erase them on request.

Versión 1 · Recurso vehicle_attachment · data requiere vehicle_registry:read

Cuándo se dispara. The expediente upload seam persists the row (POST /vehicle-attachments, the only upload door — a transferencia references an already-filed document rather than uploading its own). Retries of one delivery reuse the event id.

CampoTipo
idstring (uuid) — the attachment
vehicle_idstring (uuid) — the car this expediente belongs to
kindenum: padron | cedula | contrato | certificado_anotaciones | factura | otro
filenamestring — user-chosen; may carry a person’s name (see description)
mime_typestring — application/pdf, image/jpeg, image/png, image/webp
byte_sizeinteger
uploaded_atstring (ISO 8601)
content_urlstring (absolute URL) | null — streams the bytes to a key holding vehicle_registry:read; 403 without it. Not signed and not public: authenticate the request. Null only on a deployment with no public API base configured.
{
  "id": "3f9c1a52-64bd-4a71-8e2c-9b7d5e3a1c04",
  "vehicle_id": "c8d24f0e-7a1b-4c3d-9e8f-0a1b2c3d4e5f",
  "kind": "padron",
  "filename": "padron_KXRT52.pdf",
  "mime_type": "application/pdf",
  "byte_size": 184320,
  "uploaded_at": "2026-09-15T13:31:08.402Z",
  "content_url": "https://api.vitrinadev.com/api/v1/vehicle-attachments/3f9c1a52-64bd-4a71-8e2c-9b7d5e3a1c04/content"
}

mercadolibre.order

A Mercado Libre order notification (orders_v2) was received for a connected seller.

Versión 1 · Recurso marketplace_integration · data requiere marketplace:read

Cuándo se dispara. The MELI notifications worker processes an orders_v2 topic. It also upserts the buyer as a contact and creates a sales lead (source=marketplace, deduped by order id) when the tenant has a sales pipeline; this event fires regardless for downstream automation/reporting.

CampoTipo
integration_idstring (uuid)
order_idnumber
statusstring
total_amountnumber
currency_idstring
buyerobject
itemsarray
{
  "integration_id": "a3f1…",
  "order_id": 2000003508419013,
  "status": "paid",
  "total_amount": 8990000,
  "currency_id": "CLP",
  "buyer": {
    "id": 123456789,
    "nickname": "COMPRADOR123"
  },
  "items": [
    {
      "id": "MLC123",
      "title": "Toyota Yaris XLS 2019",
      "qty": 1
    }
  ]
}

mercadolibre.vis_lead

A Mercado Libre vehicle-classified buyer lead (VIS Leads) was received for a connected seller.

Versión 1 · Recurso marketplace_integration · data requiere marketplace:read

Cuándo se dispara. The MELI notifications worker processes a vis_leads topic (buyer contact via whatsapp/call/question/visit/contact request/reservation). It also upserts the buyer as a contact and creates a sales lead (source=marketplace, deduped by lead id) when the tenant has a sales pipeline; this event fires regardless for downstream automation/reporting.

CampoTipo
integration_idstring (uuid)
lead_idstring
item_idstring | null
contact_typestring (whatsapp|call|question|visit_request|contact_request|reservation)
statusstring | null
{
  "integration_id": "a3f1…",
  "lead_id": "44115522",
  "item_id": "MLC123",
  "contact_type": "whatsapp",
  "status": "active"
}

appointment.booked

An appointment was booked and confirmed — a test drive, a consulta, a service visit: whatever the workspace puts in its diary.

Versión 1 · Recurso appointment · data requiere appointments:read

Cuándo se dispara. POST /appointments, the agent booking tool, the embed widget, the clinic diary (POST /clinic/agenda/appointments), or a hold that was confirmed. NOT for kind=block (an admin unavailability marker is not a booking).

CampoTipo
idstring (uuid)
display_idstring (A-…, the ID visible)
statusenum: pending_hold|confirmed|cancelled|completed|no_show
kindenum: test_drive|external|block|clinic
starts_atstring (ISO 8601)
ends_atstring (ISO 8601)
appointment_type_idstring (uuid) | null
vehicle_idstring (uuid) | null
owner_user_idstring (uuid) | null
location_idstring (uuid) | null
lead_idstring (uuid) | null
contact_idstring (uuid) | null
{
  "id": "7c1f0e2a-6b3d-4a9e-8f12-2b9a1c0d4e5f",
  "display_id": "A-42",
  "status": "confirmed",
  "kind": "clinic",
  "starts_at": "2026-05-28T17:00:00.000Z",
  "ends_at": "2026-05-28T18:00:00.000Z",
  "appointment_type_id": null,
  "vehicle_id": null,
  "owner_user_id": "b1d2c3e4-5f60-4a71-8b92-0c3d4e5f6a70",
  "location_id": "9e8d7c6b-5a49-4382-9271-6f5e4d3c2b10",
  "lead_id": null,
  "contact_id": "5f3a2b1c-0d9e-48f7-a6b5-c4d3e2f10987"
}

appointment.rescheduled

An appointment moved to a new time. changes always carries starts_at and ends_at; it ALSO carries owner and/or location when the same request reassigned the appointment or moved it to another sucursal — one action, one event, so a receiver never has to stitch a move and a reassignment back together.

Versión 1 · Recurso appointment · data requiere appointments:read

Cuándo se dispara. PATCH /appointments/:id with a new starts_at + ends_at, the agent reschedule tool, or the clinic diary (PATCH /clinic/agenda/appointments/:id). Only for a CONFIRMED appointment: moving a pending hold is not a reschedule anybody has been told about yet. A reassignment or a sucursal move on its own does not fire it — nothing moved in the diary.

CampoTipo
idstring (uuid)
display_idstring (A-…, the ID visible)
statusenum: pending_hold|confirmed|cancelled|completed|no_show
kindenum: test_drive|external|block|clinic
starts_atstring (ISO 8601)
ends_atstring (ISO 8601)
previous_starts_atstring (ISO 8601)
previous_ends_atstring (ISO 8601)
appointment_type_idstring (uuid) | null
vehicle_idstring (uuid) | null
owner_user_idstring (uuid) | null
location_idstring (uuid) | null
lead_idstring (uuid) | null
contact_idstring (uuid) | null
{
  "id": "7c1f0e2a-6b3d-4a9e-8f12-2b9a1c0d4e5f",
  "display_id": "A-42",
  "status": "confirmed",
  "kind": "clinic",
  "starts_at": "2026-05-29T14:00:00.000Z",
  "ends_at": "2026-05-29T15:00:00.000Z",
  "previous_starts_at": "2026-05-28T17:00:00.000Z",
  "previous_ends_at": "2026-05-28T17:45:00.000Z",
  "appointment_type_id": null,
  "vehicle_id": null,
  "owner_user_id": "b1d2c3e4-5f60-4a71-8b92-0c3d4e5f6a70",
  "location_id": "9e8d7c6b-5a49-4382-9271-6f5e4d3c2b10",
  "lead_id": null,
  "contact_id": "5f3a2b1c-0d9e-48f7-a6b5-c4d3e2f10987"
}

appointment.cancelled

An appointment was called off. The slot is free again, the mirrored calendar event is gone and the pending reminder is dropped; the row survives as cancelled rather than being deleted.

Versión 1 · Recurso appointment · data requiere appointments:read

Cuándo se dispara. POST /appointments/:id/cancel, the agent cancel tool, the clinic agenda moving a cita to a cancelled status, a car leaving stock with visits booked on it, or an external calendar / vendor cancellation picked up by sync.

CampoTipo
idstring (uuid)
display_idstring (A-…, the ID visible)
statusenum (cancelled)
previous_statusenum: pending_hold|confirmed|completed|no_show | null
kindenum: test_drive|external|block|clinic
starts_atstring (ISO 8601)
ends_atstring (ISO 8601)
appointment_type_idstring (uuid) | null
vehicle_idstring (uuid) | null
owner_user_idstring (uuid) | null
location_idstring (uuid) | null
lead_idstring (uuid) | null
contact_idstring (uuid) | null
{
  "id": "7c1f0e2a-6b3d-4a9e-8f12-2b9a1c0d4e5f",
  "display_id": "A-42",
  "status": "cancelled",
  "previous_status": "confirmed",
  "kind": "external",
  "starts_at": "2026-05-29T14:00:00.000Z",
  "ends_at": "2026-05-29T14:45:00.000Z",
  "appointment_type_id": "3b7c9d10-4e52-4a63-9f81-0c5d2e6a7b48",
  "vehicle_id": null,
  "owner_user_id": "b1d2c3e4-5f60-4a71-8b92-0c3d4e5f6a70",
  "location_id": "9e8d7c6b-5a49-4382-9271-6f5e4d3c2b10",
  "lead_id": null,
  "contact_id": "5f3a2b1c-0d9e-48f7-a6b5-c4d3e2f10987"
}

appointment.completed

The appointment happened: somebody marked it attended. This is the signal an aftercare, a post-visit survey or a billing step hangs off — and the one that says the slot was USED rather than freed.

Versión 1 · Recurso appointment · data requiere appointments:read

Cuándo se dispara. PATCH /appointments/:id with status=completed, the clinic agenda moving a cita to an attended status, or a vendor poller mirroring the same outcome. Idempotent on the appointment's status epoch: re-reporting the same completion writes no second event, while a real flip-flop (marked attended, put back, attended later) writes two.

CampoTipo
idstring (uuid)
display_idstring (A-…, the ID visible)
statusenum (completed)
previous_statusenum: pending_hold|confirmed|cancelled|no_show | null
kindenum: test_drive|external|block|clinic
starts_atstring (ISO 8601)
ends_atstring (ISO 8601)
appointment_type_idstring (uuid) | null
vehicle_idstring (uuid) | null
owner_user_idstring (uuid) | null
location_idstring (uuid) | null
lead_idstring (uuid) | null
contact_idstring (uuid) | null
{
  "id": "7c1f0e2a-6b3d-4a9e-8f12-2b9a1c0d4e5f",
  "display_id": "A-42",
  "status": "completed",
  "previous_status": "confirmed",
  "kind": "external",
  "starts_at": "2026-05-29T14:00:00.000Z",
  "ends_at": "2026-05-29T14:45:00.000Z",
  "appointment_type_id": "3b7c9d10-4e52-4a63-9f81-0c5d2e6a7b48",
  "vehicle_id": null,
  "owner_user_id": "b1d2c3e4-5f60-4a71-8b92-0c3d4e5f6a70",
  "location_id": "9e8d7c6b-5a49-4382-9271-6f5e4d3c2b10",
  "lead_id": null,
  "contact_id": "5f3a2b1c-0d9e-48f7-a6b5-c4d3e2f10987"
}

appointment.no_show

Nobody turned up. A DIFFERENT fact from a cancellation: the slot was held and burned rather than released, which is why the two are separate events and not one status change to branch on.

Versión 1 · Recurso appointment · data requiere appointments:read

Cuándo se dispara. PATCH /appointments/:id with status=no_show, the clinic agenda moving a cita to a no-show status, or a vendor poller mirroring the same outcome. Same status-epoch idempotency as appointment.completed.

CampoTipo
idstring (uuid)
display_idstring (A-…, the ID visible)
statusenum (no_show)
previous_statusenum: pending_hold|confirmed|cancelled|completed | null
kindenum: test_drive|external|block|clinic
starts_atstring (ISO 8601)
ends_atstring (ISO 8601)
appointment_type_idstring (uuid) | null
vehicle_idstring (uuid) | null
owner_user_idstring (uuid) | null
location_idstring (uuid) | null
lead_idstring (uuid) | null
contact_idstring (uuid) | null
{
  "id": "7c1f0e2a-6b3d-4a9e-8f12-2b9a1c0d4e5f",
  "display_id": "A-42",
  "status": "no_show",
  "previous_status": "confirmed",
  "kind": "external",
  "starts_at": "2026-05-29T14:00:00.000Z",
  "ends_at": "2026-05-29T14:45:00.000Z",
  "appointment_type_id": "3b7c9d10-4e52-4a63-9f81-0c5d2e6a7b48",
  "vehicle_id": null,
  "owner_user_id": "b1d2c3e4-5f60-4a71-8b92-0c3d4e5f6a70",
  "location_id": "9e8d7c6b-5a49-4382-9271-6f5e4d3c2b10",
  "lead_id": null,
  "contact_id": "5f3a2b1c-0d9e-48f7-a6b5-c4d3e2f10987"
}

appointment.reminded

The reminder for an upcoming appointment went out to the customer, on the conversation it was booked from.

Versión 1 · Recurso appointment · data requiere appointments:read

Cuándo se dispara. The delayed reminder job fires, reminder_lead_minutes before the appointment (GET /appointments/config).

CampoTipo
idstring (uuid)
display_idstring (A-…, the ID visible)
starts_atstring (ISO 8601)
{
  "id": "7c1f0e2a-6b3d-4a9e-8f12-2b9a1c0d4e5f",
  "display_id": "A-42",
  "starts_at": "2026-05-28T17:00:00.000Z"
}

appointment.imported

An event a person created directly on the shared external calendar was imported as an appointment, and now consumes the same capacity as one booked in Vitrina.

Versión 1 · Recurso appointment · data requiere appointments:read

Cuándo se dispara. Incremental Google Calendar sync inserts a new untagged event.

CampoTipo
idstring (uuid)
display_idstring (A-…, the ID visible)
starts_atstring (ISO 8601)
ends_atstring (ISO 8601)
sourceenum (google)
{
  "id": "9a2b8c7d-6e5f-4a30-b1c2-d3e4f5a60718",
  "display_id": "A-43",
  "starts_at": "2026-05-30T13:00:00.000Z",
  "ends_at": "2026-05-30T14:00:00.000Z",
  "source": "google"
}

contact.created

A contact appeared in the workspace, by ANY path. The notice names the contact (resource.id, resource.url); read it with a credential holding contacts:read. With «Incluir datos del recurso», data carries the contact as it was created — when the subscription owner holds contacts:read with unrestricted visibility.

Versión 1 · Recurso contact · data requiere contacts:read

Cuándo se dispara. Any path that inserts a contact, exactly once each: POST /contacts, a public booking, a CSV or pre-mapped import (one event per created row), an inbound conversation from an unknown sender on any channel, a manual conversation, a marketplace or portal lead, a Mercado Libre order, a website form, and a voice call from an unknown number. A merge creates nothing and fires contact.merged instead; an import row that MATCHED an existing contact fires contact.updated.

CampoTipo
idstring (uuid) — pass to GET /api/v1/contacts/{id}
external_idstring | null — the identity the creating path keyed on (your own id on an import, meli:{buyerId}, a WhatsApp wa_id, manual:{uuid})
namestring | null
emailstring | null
phonestring | null — E.164 when it could be normalised
lifecycle_stagestring
origin_channelstring | null — the «Canal de origen» a human picked
company_idstring (uuid) | null
created_atstring (ISO 8601)
{
  "id": "5f3a9c1e-2b7d-4e8a-9c0f-1d2e3f4a5b6c",
  "external_id": null,
  "name": "María González",
  "email": "[email protected]",
  "phone": "+56912345678",
  "lifecycle_stage": "unknown",
  "origin_channel": "manual",
  "company_id": null,
  "created_at": "2026-09-21T14:03:11.000Z"
}

contact.updated

A contact's own columns were written. data.updated_fields names WHICH — field names, never values — so a receiver can ignore the writes it does not care about without reading the contact back. The legal identity (RUT, razón social, giro) and the structured address are never in data; read them with a credential holding contacts:read.

Versión 1 · Recurso contact · data requiere contacts:read

Cuándo se dispara. PATCH /contacts/{id}, an import row that matched an existing contact, block/archive/report-spam, the AI-off switch, and the automatic enrichment that fills a name, an avatar or a channel handle from an inbound message. Consent writes (POST /contacts/bulk-consent, the outreach-consent ledger) and a merge do not fire it.

CampoTipo
idstring (uuid) — pass to GET /api/v1/contacts/{id}
external_idstring | null
namestring | null
emailstring | null
phonestring | null — E.164 when it could be normalised
lifecycle_stagestring
origin_channelstring | null
company_idstring (uuid) | null
updated_fieldsarray of string — the column names this write touched, in no particular order
updated_atstring (ISO 8601)
from_lifecycle_stagestring | undefined — present only when the stage changed
to_lifecycle_stagestring | undefined — present only when the stage changed
from_company_idstring (uuid) | null | undefined — present only when the company changed
to_company_idstring (uuid) | null | undefined — present only when the company changed
{
  "id": "5f3a9c1e-2b7d-4e8a-9c0f-1d2e3f4a5b6c",
  "external_id": null,
  "name": "María González",
  "email": "[email protected]",
  "phone": "+56912345678",
  "lifecycle_stage": "customer",
  "origin_channel": "manual",
  "company_id": null,
  "updated_fields": [
    "lifecycle_stage"
  ],
  "updated_at": "2026-09-22T11:20:45.000Z",
  "from_lifecycle_stage": "prospect",
  "to_lifecycle_stage": "customer"
}

contact.merged

Two or more contacts were collapsed into one. resource.id is the SURVIVOR and changes.contact_id.from lists the ids that became tombstones — a tombstone still answers GET /contacts/{id}, carrying merged_into_contact_id, so a stored id can be repointed rather than lost.

Versión 1 · Recurso contact · data requiere contacts:read

Cuándo se dispara. POST /contacts/{id}/merge, the MCP contacts_merge tool, and the automatic collapse the webchat ingress performs when a visitor’s email AND phone both resolve to one existing contact.

CampoTipo
idstring (uuid) — the survivor
merged_contact_idsarray of string (uuid) — the contacts that became tombstones
merged_countinteger
conversations_reassignedinteger — threads repointed from the tombstones onto the survivor
filled_fieldsarray of string — the survivor’s columns that GAINED a value from a tombstone (names, not values)
namestring | null — the survivor, after the backfill
emailstring | null
phonestring | null
merged_atstring (ISO 8601)
{
  "id": "5f3a9c1e-2b7d-4e8a-9c0f-1d2e3f4a5b6c",
  "merged_contact_ids": [
    "8c41b0d2-6e5f-4a19-b73c-0d5e9f2a1b84"
  ],
  "merged_count": 1,
  "conversations_reassigned": 3,
  "filled_fields": [
    "email",
    "tax_id",
    "tax_id_kind"
  ],
  "name": "María González",
  "email": "[email protected]",
  "phone": "+56912345678",
  "merged_at": "2026-09-22T11:48:02.000Z"
}

team.created

A team was created.

Versión 1 · Recurso team · data requiere teams:read

Cuándo se dispara. POST /teams.

CampoTipo
idstring (uuid)
namestring
brandstring | null
hours_modeenum: workspace | override
created_atstring (ISO 8601)
{
  "id": "cccccccc-0000-4000-8000-000000000001",
  "name": "Ventas Providencia",
  "brand": "Providencia",
  "hours_mode": "override",
  "created_at": "2026-09-22T13:00:00.000Z"
}

team.updated

A team’s name, brand or coverage hours changed.

Versión 1 · Recurso team · data requiere teams:read

Cuándo se dispara. PUT /teams/{id}.

CampoTipo
idstring (uuid)
namestring
from_hours_modeenum: workspace | override
to_hours_modeenum: workspace | override
created_atstring (ISO 8601)
{
  "id": "cccccccc-0000-4000-8000-000000000001",
  "name": "Ventas Providencia",
  "from_hours_mode": "workspace",
  "to_hours_mode": "override",
  "created_at": "2026-09-22T13:00:00.000Z"
}

team.deleted

A team was deleted. Every messaging_account.default_team_id, lead.team_id and appointment sales_team_id pointing here was set to NULL in the same write — the fan-out DELETE /teams/{id} documents.

Versión 1 · Recurso team · data requiere teams:read

Cuándo se dispara. DELETE /teams/{id}.

CampoTipo
idstring (uuid)
namestring
{
  "id": "cccccccc-0000-4000-8000-000000000001",
  "name": "Ventas Providencia"
}

custom_role.created

A custom role was created.

Versión 1 · Recurso custom_role · data requiere roles:read

Cuándo se dispara. POST /roles.

CampoTipo
idstring (uuid)
namestring
scopesstring[]
record_visibilityenum: all | assigned_unassigned | assigned
stock_visibilityenum: all | own_locations
{
  "id": "d2d2d2d2-0000-4000-8000-000000000001",
  "name": "Vendedor sucursal",
  "scopes": [
    "conversations:read",
    "conversations:write",
    "leads:read",
    "leads:write"
  ],
  "record_visibility": "assigned",
  "stock_visibility": "own_locations"
}

custom_role.updated

A custom role’s scopes or either visibility ceiling changed — a PERMISSION CHANGE for every member already on it.

Versión 1 · Recurso custom_role · data requiere roles:read

Cuándo se dispara. PUT /roles/{id}.

CampoTipo
idstring (uuid)
namestring
scopesstring[]
record_visibilityenum: all | assigned_unassigned | assigned
stock_visibilityenum: all | own_locations
{
  "id": "d2d2d2d2-0000-4000-8000-000000000001",
  "name": "Vendedor sucursal",
  "scopes": [
    "conversations:read",
    "conversations:write",
    "leads:read",
    "leads:write"
  ],
  "record_visibility": "assigned_unassigned",
  "stock_visibility": "own_locations"
}

custom_role.deleted

A custom role was deleted (only possible once it holds no members, or with reassign_to moving them onto a replacement in the same call).

Versión 1 · Recurso custom_role · data requiere roles:read

Cuándo se dispara. DELETE /roles/{id}.

CampoTipo
idstring (uuid)
namestring
reassigned_tostring (uuid) | null
{
  "id": "d2d2d2d2-0000-4000-8000-000000000001",
  "name": "Vendedor sucursal",
  "reassigned_to": null
}

macro.created

A macro (saved reply + actions) was created.

Versión 1 · Recurso macro · data requiere macros:read

Cuándo se dispara. POST /macros.

CampoTipo
idstring (uuid)
namestring
activeboolean
{
  "id": "d3d3d3d3-0000-4000-8000-000000000001",
  "name": "Cierre agradecido",
  "active": true
}

clinic_professional.created

A professional was added to the clinic roster.

Versión 1 · Recurso clinic_professional · data requiere clinic:read

Cuándo se dispara. POST /clinic/professionals succeeds (native clinics only).

CampoTipo
professional_idstring (uuid)
nombrestring
apellidosstring | null
especialidadstring | null
activeboolean
sourceenum: native | healthatom | reservo
{
  "professional_id": "18342d1b-0000-4000-8000-000000000001",
  "nombre": "Ana",
  "apellidos": "Rojas Vidal",
  "especialidad": "Ortodoncia",
  "active": true,
  "source": "native"
}

clinic_professional.updated

A professional's profile changed — their name, speciality, online-booking flag or active state.

Versión 1 · Recurso clinic_professional · data requiere clinic:read

Cuándo se dispara. PATCH /clinic/professionals/{id} succeeds.

CampoTipo
professional_idstring (uuid)
nombrestring
apellidosstring | null
especialidadstring | null
activeboolean
active_beforeboolean
sourceenum: native | healthatom | reservo
{
  "professional_id": "18342d1b-0000-4000-8000-000000000001",
  "nombre": "Ana",
  "apellidos": "Rojas Vidal",
  "especialidad": "Ortodoncia",
  "active": true,
  "active_before": true,
  "source": "native"
}

clinic_professional.deleted

A native professional was removed from the roster. Their citas keep the id as provenance.

Versión 1 · Recurso clinic_professional · data requiere clinic:read

Cuándo se dispara. DELETE /clinic/professionals/{id} succeeds.

CampoTipo
professional_idstring (uuid)
nombrestring
apellidosstring | null
{
  "professional_id": "18342d1b-0000-4000-8000-000000000001",
  "nombre": "Ana",
  "apellidos": "Rojas Vidal"
}

clinic_service.created

A prestación was added to the catálogo.

Versión 1 · Recurso clinic_service · data requiere clinic:read

Cuándo se dispara. POST /clinic/services succeeds (native clinics only).

CampoTipo
service_idstring (uuid)
nombrestring
codigostring | null
categoriastring | null
precio_clpinteger | null
duration_minutesinteger | null
activeboolean
{
  "service_id": "4cf5bc59-0000-4000-8000-000000000001",
  "nombre": "Control de ortodoncia",
  "codigo": "ORT-CTRL",
  "categoria": "Ortodoncia",
  "precio_clp": 28000,
  "duration_minutes": 30,
  "active": true
}

macro.updated

A macro’s content, actions or active flag changed. Takes effect immediately for anyone about to run it.

Versión 1 · Recurso macro · data requiere macros:read

Cuándo se dispara. PUT /macros/{id}.

CampoTipo
idstring (uuid)
namestring
activeboolean
{
  "id": "d3d3d3d3-0000-4000-8000-000000000001",
  "name": "Cierre agradecido",
  "active": false
}

macro.deleted

A macro was deleted. Everything it already did — messages sent, labels applied — stays; only the saved reply itself is gone.

Versión 1 · Recurso macro · data requiere macros:read

Cuándo se dispara. DELETE /macros/{id}.

CampoTipo
idstring (uuid)
namestring
{
  "id": "d3d3d3d3-0000-4000-8000-000000000001",
  "name": "Cierre agradecido"
}

assignment_rule.created

A routing rule was created. New rules always append last.

Versión 1 · Recurso assignment_rule · data requiere routing:read

Cuándo se dispara. POST /assignment-rules.

CampoTipo
idstring (uuid)
namestring
prioritynumber (int) — evaluation order, ascending
team_idstring (uuid) | null
{
  "id": "d5d5d5d5-0000-4000-8000-000000000001",
  "name": "Mercado Libre → equipo Providencia",
  "priority": 0,
  "team_id": "cccccccc-0000-4000-8000-000000000001"
}

assignment_rule.updated

A rule’s match filters, destination or enabled flag changed. (Its priority changes only via PUT /assignment-rules/order, which rewrites every rule at once and does not fire this per-rule event.)

Versión 1 · Recurso assignment_rule · data requiere routing:read

Cuándo se dispara. PATCH /assignment-rules/{id}.

CampoTipo
idstring (uuid)
namestring
enabledboolean
{
  "id": "d5d5d5d5-0000-4000-8000-000000000001",
  "name": "Mercado Libre → equipo Providencia",
  "enabled": false
}

assignment_rule.deleted

A routing rule was deleted. Every other rule keeps its own priority.

Versión 1 · Recurso assignment_rule · data requiere routing:read

Cuándo se dispara. DELETE /assignment-rules/{id}.

CampoTipo
idstring (uuid)
namestring
{
  "id": "d5d5d5d5-0000-4000-8000-000000000001",
  "name": "Mercado Libre → equipo Providencia"
}

sla_policy.created

An SLA policy was created.

Versión 1 · Recurso sla_policy · data requiere slas:read

Cuándo se dispara. POST /slas.

CampoTipo
idstring (uuid)
namestring
target_first_response_minutesnumber (int) | null
target_resolution_minutesnumber (int) | null
{
  "id": "d7d7d7d7-0000-4000-8000-000000000001",
  "name": "Soporte estándar",
  "target_first_response_minutes": 30,
  "target_resolution_minutes": 1440
}

sla_policy.updated

An SLA policy’s targets, applies_to filter or business_hours_only changed.

Versión 1 · Recurso sla_policy · data requiere slas:read

Cuándo se dispara. PUT /slas/{id}.

CampoTipo
idstring (uuid)
namestring
target_first_response_minutesnumber (int) | null
target_resolution_minutesnumber (int) | null
{
  "id": "d7d7d7d7-0000-4000-8000-000000000001",
  "name": "Soporte estándar",
  "target_first_response_minutes": 20,
  "target_resolution_minutes": 1440
}

sla_policy.deleted

An SLA policy was deleted. Tickets it already applied to keep whatever coverage state they already reached; nothing is recomputed.

Versión 1 · Recurso sla_policy · data requiere slas:read

Cuándo se dispara. DELETE /slas/{id}.

CampoTipo
idstring (uuid)
namestring
{
  "id": "d7d7d7d7-0000-4000-8000-000000000001",
  "name": "Soporte estándar"
}

automation_trigger.created

An automation trigger was created.

Versión 1 · Recurso automation_trigger · data requiere triggers:read

Cuándo se dispara. POST /triggers.

CampoTipo
idstring (uuid)
namestring
eventstring
enabledboolean
{
  "id": "d8d8d8d8-0000-4000-8000-000000000001",
  "name": "Avisar si nadie responde en 2 horas",
  "event": "conversation_idle",
  "enabled": true
}

automation_trigger.updated

A trigger’s event, conditions, actions or enabled flag changed.

Versión 1 · Recurso automation_trigger · data requiere triggers:read

Cuándo se dispara. PUT /triggers/{id}.

CampoTipo
idstring (uuid)
namestring
eventstring
enabledboolean
{
  "id": "d8d8d8d8-0000-4000-8000-000000000001",
  "name": "Avisar si nadie responde en 2 horas",
  "event": "conversation_idle",
  "enabled": false
}

automation_trigger.deleted

A trigger was deleted. It stops firing again; past runs are not undone.

Versión 1 · Recurso automation_trigger · data requiere triggers:read

Cuándo se dispara. DELETE /triggers/{id}.

CampoTipo
idstring (uuid)
namestring
{
  "id": "d8d8d8d8-0000-4000-8000-000000000001",
  "name": "Avisar si nadie responde en 2 horas"
}

sending_domain.created

A workspace requested its own sending domain. dkim_status/mail_from_status start pending — see sending_domain.verified for the transition once DNS propagates.

Versión 1 · Recurso sending_domain · data requiere messaging_accounts:read

Cuándo se dispara. POST /sending-domains.

CampoTipo
idstring (uuid)
domainstring
{
  "id": "d9d9d9d9-0000-4000-8000-000000000001",
  "domain": "mail.autosdelvalle.cl"
}

sending_domain.deleted

A sending domain was removed.

Versión 1 · Recurso sending_domain · data requiere messaging_accounts:read

Cuándo se dispara. DELETE /sending-domains/{id}.

CampoTipo
idstring (uuid)
domainstring
{
  "id": "d9d9d9d9-0000-4000-8000-000000000001",
  "domain": "mail.autosdelvalle.cl"
}

whatsapp_template.created

A WhatsApp template was submitted to Meta. status starts PENDING — see whatsapp_template.status_changed for the approval verdict.

Versión 1 · Recurso whatsapp_template · data requiere campaigns:read

Cuándo se dispara. POST /whatsapp-templates.

CampoTipo
idstring (uuid)
namestring
languagestring
statusenum: PENDING | APPROVED | REJECTED | …
{
  "id": "e2e2e2e2-0000-4000-8000-000000000001",
  "name": "seguimiento_stock",
  "language": "es_CL",
  "status": "PENDING"
}

whatsapp_template.status_changed

Meta’s async review landed — read off the periodic reconcile poll or a manual POST /whatsapp-templates/sync. rejection_reason is set only when to_status is REJECTED.

Versión 1 · Recurso whatsapp_template · data requiere campaigns:read

Cuándo se dispara. The template-sync worker, or POST /whatsapp-templates/sync, sees a status change.

CampoTipo
idstring (uuid)
namestring
from_statusenum: PENDING | APPROVED | REJECTED | …
to_statusenum: PENDING | APPROVED | REJECTED | …
rejection_reasonstring | null
{
  "id": "e2e2e2e2-0000-4000-8000-000000000001",
  "name": "seguimiento_stock",
  "from_status": "PENDING",
  "to_status": "APPROVED",
  "rejection_reason": null
}

whatsapp_template.deleted

A WhatsApp template’s local mirror was removed. Does not withdraw it from Meta.

Versión 1 · Recurso whatsapp_template · data requiere campaigns:read

Cuándo se dispara. DELETE /whatsapp-templates/{id}.

CampoTipo
idstring (uuid)
namestring
{
  "id": "e2e2e2e2-0000-4000-8000-000000000001",
  "name": "seguimiento_stock"
}

whatsapp_flow.created

A WhatsApp Flow was created in the builder. Always starts DRAFT.

Versión 1 · Recurso whatsapp_flow · data requiere campaigns:read

Cuándo se dispara. POST /whatsapp-flows/managed.

CampoTipo
idstring (uuid)
namestring
statusenum: DRAFT | PUBLISHED | DEPRECATED | BLOCKED | THROTTLED
{
  "id": "e3e3e3e3-0000-4000-8000-000000000001",
  "name": "Agenda tu visita",
  "status": "DRAFT"
}

whatsapp_flow.published

A Flow was published. Publishing is one-way — POST /whatsapp-flows/managed/{id}/duplicate is the only way to keep editing after this.

Versión 1 · Recurso whatsapp_flow · data requiere campaigns:read

Cuándo se dispara. POST /whatsapp-flows/managed/{id}/publish.

CampoTipo
idstring (uuid)
namestring
statusenum: DRAFT | PUBLISHED | DEPRECATED | BLOCKED | THROTTLED
{
  "id": "e3e3e3e3-0000-4000-8000-000000000001",
  "name": "Agenda tu visita",
  "status": "PUBLISHED"
}

whatsapp_flow.deprecated

A published Flow was deprecated — stops being offered as new; conversations already inside it are unaffected.

Versión 1 · Recurso whatsapp_flow · data requiere campaigns:read

Cuándo se dispara. POST /whatsapp-flows/managed/{id}/deprecate.

CampoTipo
idstring (uuid)
namestring
statusenum: DRAFT | PUBLISHED | DEPRECATED | BLOCKED | THROTTLED
{
  "id": "e3e3e3e3-0000-4000-8000-000000000001",
  "name": "Agenda tu visita",
  "status": "DEPRECATED"
}

whatsapp_flow.deleted

A draft Flow was deleted. A published Flow cannot be (409).

Versión 1 · Recurso whatsapp_flow · data requiere campaigns:read

Cuándo se dispara. DELETE /whatsapp-flows/managed/{id}.

CampoTipo
idstring (uuid)
namestring
{
  "id": "e3e3e3e3-0000-4000-8000-000000000001",
  "name": "Agenda tu visita"
}

channel.connected

A messaging channel finished connecting.

Versión 1 · Recurso channel · data requiere messaging_accounts:read

Cuándo se dispara. POST /messaging-accounts, or a provider OAuth/Atribu callback completing.

CampoTipo
idstring (uuid)
namestring
kindstring
channelsstring[]
{
  "id": "e4e4e4e4-0000-4000-8000-000000000001",
  "name": "WhatsApp Providencia",
  "kind": "whatsapp_cloud",
  "channels": [
    "whatsapp"
  ]
}

channel.updated

A channel’s config changed (routing default, assignment mode, name).

Versión 1 · Recurso channel · data requiere messaging_accounts:read

Cuándo se dispara. PUT /messaging-accounts/{id}.

CampoTipo
idstring (uuid)
namestring
kindstring
{
  "id": "e4e4e4e4-0000-4000-8000-000000000001",
  "name": "WhatsApp Providencia",
  "kind": "whatsapp_cloud"
}

channel.disconnected

A channel was disconnected. Every conversation, message and lead it already produced is untouched — this ends the connection, not the history.

Versión 1 · Recurso channel · data requiere messaging_accounts:read

Cuándo se dispara. DELETE /messaging-accounts/{id}.

CampoTipo
idstring (uuid)
namestring
kindstring
{
  "id": "e4e4e4e4-0000-4000-8000-000000000001",
  "name": "WhatsApp Providencia",
  "kind": "whatsapp_cloud"
}

membership.created

A person joined the workspace — added directly (POST /memberships) or by accepting an invitation.

Versión 1 · Recurso membership · data requiere memberships:read

Cuándo se dispara. POST /memberships, or POST /memberships/invitations/by-token/{token}/accept.

CampoTipo
idstring (uuid)
user_idstring (uuid)
roleenum: owner | admin | supervisor | agent | consultant
custom_role_idstring (uuid) | null
{
  "id": "11111111-0000-4000-8000-000000000001",
  "user_id": "11111111-0000-4000-8000-000000000001",
  "role": "agent",
  "custom_role_id": "d2d2d2d2-0000-4000-8000-000000000001"
}

membership.updated

A member’s role, custom role, account status or sucursal posting changed.

Versión 1 · Recurso membership · data requiere memberships:read

Cuándo se dispara. PUT /memberships/{id}.

CampoTipo
idstring (uuid)
user_idstring (uuid)
from_roleenum: owner | admin | supervisor | agent | consultant
to_roleenum: owner | admin | supervisor | agent | consultant
{
  "id": "11111111-0000-4000-8000-000000000001",
  "user_id": "11111111-0000-4000-8000-000000000001",
  "from_role": "agent",
  "to_role": "supervisor"
}

membership.removed

A person was removed from the workspace. Their open conversations and leads were handed over in the SAME write (handover: unassign | user | round_robin) — a removal never leaves them assigned to someone who can no longer see them.

Versión 1 · Recurso membership · data requiere memberships:read

Cuándo se dispara. DELETE /memberships/{id}.

CampoTipo
idstring (uuid)
user_idstring (uuid)
handover_modeenum: unassign | user | round_robin
conversations_movednumber (int)
leads_movednumber (int)
{
  "id": "11111111-0000-4000-8000-000000000002",
  "user_id": "11111111-0000-4000-8000-000000000003",
  "handover_mode": "round_robin",
  "conversations_moved": 6,
  "leads_moved": 3
}

clinic_service.updated

A prestación changed — its price, its duration or whether it is bookable. The price here is the catálogo's own; a patient's price also depends on the arancel and the rules.

Versión 1 · Recurso clinic_service · data requiere clinic:read

Cuándo se dispara. PATCH /clinic/services/{id} succeeds.

CampoTipo
service_idstring (uuid)
nombrestring
precio_clpinteger | null
precio_clp_beforeinteger | null
duration_minutesinteger | null
activeboolean
active_beforeboolean
{
  "service_id": "4cf5bc59-0000-4000-8000-000000000001",
  "nombre": "Control de ortodoncia",
  "precio_clp": 28000,
  "precio_clp_before": 25000,
  "duration_minutes": 30,
  "active": true,
  "active_before": true
}

clinic_service.deleted

A native prestación was deleted from the catálogo.

Versión 1 · Recurso clinic_service · data requiere clinic:read

Cuándo se dispara. DELETE /clinic/services/{id} succeeds.

CampoTipo
service_idstring (uuid)
nombrestring
{
  "service_id": "4cf5bc59-0000-4000-8000-000000000001",
  "nombre": "Control de ortodoncia"
}

clinic_price_list.updated

An arancel changed — its own fields, or the prices inside it. upserted and removed count the entries the edit touched; a removed entry means "this list does not price it", which is not the same as pricing it at zero.

Versión 1 · Recurso clinic_price_list · data requiere clinic:read

Cuándo se dispara. PATCH /clinic/pricing/lists/{id} or PUT /clinic/pricing/lists/{id}/entries succeeds.

CampoTipo
price_list_idstring (uuid)
namestring
upsertedinteger
removedinteger
is_defaultboolean
activeboolean
{
  "price_list_id": "38b62be4-0000-4000-8000-000000000001",
  "name": "Arancel particular 2026",
  "upserted": 2,
  "removed": 0,
  "is_default": true,
  "active": true
}

clinic_pack_purchase.created

A patient bought a pack of sessions. The patient is named by ID only; read the purchase to see who, with a credential that may.

Versión 1 · Recurso clinic_pack_purchase · data requiere clinic:read

Cuándo se dispara. POST /clinic/packs/purchases succeeds.

CampoTipo
purchase_idstring (uuid)
pack_idstring (uuid) | null
clinic_patient_idstring (uuid) | null
contact_idstring (uuid) | null
namestring
session_countinteger
precio_clpinteger | null
expires_atYYYY-MM-DD | null
{
  "purchase_id": "9a1f0c77-0000-4000-8000-000000000001",
  "pack_id": "7d2b51a0-0000-4000-8000-000000000001",
  "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
  "contact_id": "22222222-0000-4000-8000-000000000001",
  "name": "Pack 4 controles de ortodoncia",
  "session_count": 4,
  "precio_clp": 96000,
  "expires_at": "2027-03-21"
}

clinic_pack_purchase.session_consumed

A session of a pack was used — at a cita, or recorded by hand. A reversal fires nothing: it restores the count, and the next consumption reports it.

Versión 1 · Recurso clinic_pack_purchase · data requiere clinic:read

Cuándo se dispara. POST /clinic/packs/purchases/{id}/consume succeeds, or a cita consumes a session automatically.

CampoTipo
purchase_idstring (uuid)
session_idstring (uuid)
clinic_patient_idstring (uuid) | null
clinic_service_idstring (uuid) | null
appointment_idstring (uuid) | null
sessions_usedinteger
sessions_used_beforeinteger
session_countinteger
{
  "purchase_id": "9a1f0c77-0000-4000-8000-000000000001",
  "session_id": "2f9c8e51-0000-4000-8000-000000000001",
  "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
  "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
  "appointment_id": null,
  "sessions_used": 1,
  "sessions_used_before": 0,
  "session_count": 4
}

clinic_pack_purchase.cancelled

A pack purchase was anulada — with or without a refund. The sessions it still held are gone with it.

Versión 1 · Recurso clinic_pack_purchase · data requiere clinic:read

Cuándo se dispara. POST /clinic/packs/purchases/{id}/cancel succeeds.

CampoTipo
purchase_idstring (uuid)
clinic_patient_idstring (uuid) | null
reasonstring
refundedboolean
sessions_remaininginteger
{
  "purchase_id": "9a1f0c77-0000-4000-8000-000000000001",
  "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
  "reason": "El paciente desistió antes de la primera sesión",
  "refunded": false,
  "sessions_remaining": 4
}

clinic_budget.created

A presupuesto was created as a draft — and drew its folio. The folio is a legal sequence with no gaps, so a draft that is thrown away leaves its number spent.

Versión 1 · Recurso clinic_budget · data requiere clinic_money:read

Cuándo se dispara. POST /clinic/budgets succeeds.

CampoTipo
budget_idstring (uuid)
foliostring (the ID visible, e.g. E-47) | null
statusenum: draft | sent | accepted | rejected | voided | expired
clinic_patient_idstring (uuid) | null
contact_idstring (uuid) | null
treatment_plan_idstring (uuid) | null
total_clpinteger | null
valid_untilYYYY-MM-DD | null
{
  "budget_id": "4b1f9d62-0000-4000-8000-000000000001",
  "folio": "E-47",
  "status": "draft",
  "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
  "contact_id": "1a73af9e-0000-4000-8000-000000000001",
  "treatment_plan_id": "8c5a2e11-0000-4000-8000-000000000001",
  "total_clp": 139000,
  "valid_until": "2026-12-31"
}

clinic_budget.sent

A presupuesto was sent to the patient, with a link they can accept from. delivered says how it left — a channel, or none when the clinic had no way to reach them and handed it over in person.

Versión 1 · Recurso clinic_budget · data requiere clinic_money:read

Cuándo se dispara. POST /clinic/budgets/{id}/send succeeds.

CampoTipo
budget_idstring (uuid)
foliostring | null
statusenum: sent
status_beforeenum: draft | sent
clinic_patient_idstring (uuid) | null
contact_idstring (uuid) | null
total_clpinteger | null
deliveredenum: whatsapp | email | none
{
  "budget_id": "4b1f9d62-0000-4000-8000-000000000001",
  "folio": "E-47",
  "status": "sent",
  "status_before": "draft",
  "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
  "contact_id": "1a73af9e-0000-4000-8000-000000000001",
  "total_clp": 139000,
  "delivered": "whatsapp"
}

clinic_budget.accepted

The presupuesto was accepted — by the patient from their link, or by the clinic recording it. Acceptance opens what the patient owes: obligation_group_id is the group the cobros hang from.

Versión 1 · Recurso clinic_budget · data requiere clinic_money:read

Cuándo se dispara. POST /clinic/budgets/{id}/accept succeeds, or the patient accepts from their own link.

CampoTipo
budget_idstring (uuid)
foliostring | null
statusenum: accepted
clinic_patient_idstring (uuid) | null
contact_idstring (uuid) | null
total_clpinteger | null
obligation_group_idstring (uuid) | null
treatment_plan_idstring (uuid) | null
{
  "budget_id": "4b1f9d62-0000-4000-8000-000000000001",
  "folio": "E-47",
  "status": "accepted",
  "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
  "contact_id": "1a73af9e-0000-4000-8000-000000000001",
  "total_clp": 139000,
  "obligation_group_id": "a067dce6-0000-4000-8000-000000000001",
  "treatment_plan_id": "8c5a2e11-0000-4000-8000-000000000001"
}

clinic_budget.rejected

The patient declined the presupuesto. The folio stays spent.

Versión 1 · Recurso clinic_budget · data requiere clinic_money:read

Cuándo se dispara. POST /clinic/budgets/{id}/reject succeeds, or the patient declines from their link.

CampoTipo
budget_idstring (uuid)
foliostring | null
statusenum: rejected
clinic_patient_idstring (uuid) | null
total_clpinteger | null
reasonstring | null
{
  "budget_id": "4b1f9d62-0000-4000-8000-000000000001",
  "folio": "E-47",
  "status": "rejected",
  "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
  "total_clp": 139000,
  "reason": "El paciente prefiere esperar al próximo semestre"
}

clinic_budget.voided

A presupuesto was voided — never deleted. The folio stays, the document stays listable, and the reason is part of it.

Versión 1 · Recurso clinic_budget · data requiere clinic_money:read

Cuándo se dispara. POST /clinic/budgets/{id}/void succeeds.

CampoTipo
budget_idstring (uuid)
foliostring | null
statusenum: voided
clinic_patient_idstring (uuid) | null
total_clpinteger | null
reasonstring
{
  "budget_id": "4b1f9d62-0000-4000-8000-000000000001",
  "folio": "E-47",
  "status": "voided",
  "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
  "total_clp": 139000,
  "reason": "Se emitió con el arancel equivocado"
}

clinic_budget.mirror_changed

A presupuesto composed from a mirrored Dentalink plan no longer matches that plan: a line was added, removed or repriced in Dentalink after the photo was taken — even when the total stayed the same. The presupuesto itself does not change; the clinic issues a new version, which re-photographs the plan. Fires once per presupuesto.

Versión 1 · Recurso clinic_budget · data requiere clinic_money:read

Cuándo se dispara. The drift sweep after a mirror sync finds the live plan different from the snapshot.

CampoTipo
budget_idstring (uuid)
foliostring | null
statusenum: draft | sent | accepted | rejected | expired
clinic_patient_idstring (uuid) | null
contact_idstring (uuid) | null
treatment_plan_idstring (uuid) | null
total_clpinteger | null
valid_untilYYYY-MM-DD | null
mirror_plan_idstring (uuid) | null
mirror_snapshot_atstring (ISO 8601) | null
mirror_changed_atstring (ISO 8601) | null
{
  "budget_id": "4b1f9d62-0000-4000-8000-000000000001",
  "folio": "E-47",
  "status": "sent",
  "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
  "contact_id": "1a73af9e-0000-4000-8000-000000000001",
  "treatment_plan_id": null,
  "total_clp": 139000,
  "valid_until": "2026-12-31",
  "mirror_plan_id": "6d2c1f0a-0000-4000-8000-000000000001",
  "mirror_snapshot_at": "2026-09-20T14:02:11.000Z",
  "mirror_changed_at": "2026-09-23T10:15:40.000Z"
}

clinic_cash_session.opened

Somebody opened the drawer at a branch. A shift always records WHO opened it, which is why an API key cannot open one.

Versión 1 · Recurso clinic_cash_session · data requiere clinic_money:read

Cuándo se dispara. POST /clinic/cash-sessions succeeds.

CampoTipo
cash_session_idstring (uuid)
location_idstring (uuid)
statusenum: open | closed | reconciled
opened_by_user_idstring (uuid) | null
opening_float_clpinteger | null
opened_atISO 8601 timestamp | null
{
  "cash_session_id": "5e7c11a4-0000-4000-8000-000000000001",
  "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
  "status": "open",
  "opened_by_user_id": "11111111-0000-4000-8000-000000000001",
  "opening_float_clp": 30000,
  "opened_at": "2026-09-24T12:00:00.000Z"
}

clinic_cash_session.closed

The drawer was closed against a count. difference_clp is what the till held minus what the ledger expected — a number worth an alert when it is not zero.

Versión 1 · Recurso clinic_cash_session · data requiere clinic_money:read

Cuándo se dispara. POST /clinic/cash-sessions/{id}/close succeeds.

CampoTipo
cash_session_idstring (uuid)
location_idstring (uuid)
statusenum: closed
status_beforeenum: open
expected_clpinteger | null
counted_clpinteger | null
difference_clpinteger | null
closed_atISO 8601 timestamp | null
{
  "cash_session_id": "5e7c11a4-0000-4000-8000-000000000001",
  "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
  "status": "closed",
  "status_before": "open",
  "expected_clp": 50000,
  "counted_clp": 50000,
  "difference_clp": 0,
  "closed_at": "2026-09-24T21:05:00.000Z"
}

clinic_patient.created

A ficha was opened for a patient. The notice names the id and nothing else — a name is the first thing the register is sensitive about.

Versión 1 · Recurso clinic_patient · data requiere clinic_patients:read

Dato sensible: llega siempre como aviso, sin data, pida lo que pida la suscripción.

Cuándo se dispara. POST /clinic/patients succeeds.

CampoTipo
patient_idstring (uuid)
sourceenum: native | healthatom | reservo
{
  "patient_id": "b1b0b8de-0000-4000-8000-000000000001",
  "source": "native"
}

clinic_patient.updated

A patient's identity page changed. fields names WHICH fields moved, never their values.

Versión 1 · Recurso clinic_patient · data requiere clinic_patients:read

Dato sensible: llega siempre como aviso, sin data, pida lo que pida la suscripción.

Cuándo se dispara. PATCH /clinic/patients/{id} succeeds.

CampoTipo
patient_idstring (uuid)
fieldsstring[] (the field names that changed)
{
  "patient_id": "b1b0b8de-0000-4000-8000-000000000001",
  "fields": [
    "prevision",
    "comuna"
  ]
}

clinic_patient.deleted

A native ficha was deleted. Its contact link survives with a null clinic_patient_id.

Versión 1 · Recurso clinic_patient · data requiere clinic_patients:read

Dato sensible: llega siempre como aviso, sin data, pida lo que pida la suscripción.

Cuándo se dispara. DELETE /clinic/patients/{id} succeeds.

CampoTipo
patient_idstring (uuid)
{
  "patient_id": "b1b0b8de-0000-4000-8000-000000000001"
}

clinic_patient.merged

Two fichas became one. patient_id is the SURVIVOR and merged_patient_id the one that stopped existing — a mirror that kept the loser has to re-point it.

Versión 1 · Recurso clinic_patient · data requiere clinic_patients:read

Dato sensible: llega siempre como aviso, sin data, pida lo que pida la suscripción.

Cuándo se dispara. POST /clinic/patients/{id}/merge succeeds.

CampoTipo
patient_idstring (uuid) — the survivor
merged_patient_idstring (uuid) — the ficha that was merged away
moved_rowsinteger
{
  "patient_id": "b1b0b8de-0000-4000-8000-000000000001",
  "merged_patient_id": "42db9ec9-0000-4000-8000-000000000001",
  "moved_rows": 14
}

clinic_encounter.opened

An atención was opened for a patient. Nothing of what is written in it ever rides in the event.

Versión 1 · Recurso clinic_encounter · data requiere clinic_record:read

Dato sensible: llega siempre como aviso, sin data, pida lo que pida la suscripción.

Cuándo se dispara. POST /clinic/patients/{id}/encounters succeeds.

CampoTipo
encounter_idstring (uuid)
patient_idstring (uuid)
professional_idstring (uuid) | null
appointment_idstring (uuid) | null
kindenum: consulta | sesion | control | evaluacion | procedimiento | teleconsulta
{
  "encounter_id": "14141414-0000-4000-8000-000000000001",
  "patient_id": "b1b0b8de-0000-4000-8000-000000000001",
  "professional_id": "18342d1b-0000-4000-8000-000000000001",
  "appointment_id": "eeeeeeee-0000-4000-8000-000000000001",
  "kind": "control"
}

clinic_encounter.closed

An atención was closed. From here its signed notes are amended, never edited.

Versión 1 · Recurso clinic_encounter · data requiere clinic_record:read

Dato sensible: llega siempre como aviso, sin data, pida lo que pida la suscripción.

Cuándo se dispara. POST /clinic/encounters/{id}/close succeeds.

CampoTipo
encounter_idstring (uuid)
patient_idstring (uuid)
closed_atISO 8601 timestamp
{
  "encounter_id": "14141414-0000-4000-8000-000000000001",
  "patient_id": "b1b0b8de-0000-4000-8000-000000000001",
  "closed_at": "2026-09-24T14:05:11.900Z"
}

clinic_note.signed

A clinical note was signed. Signing is what makes it part of the record — and what stops it being edited.

Versión 1 · Recurso clinic_note · data requiere clinic_record:read

Dato sensible: llega siempre como aviso, sin data, pida lo que pida la suscripción.

Cuándo se dispara. POST /clinic/encounters/{id}/notes with sign: true, or POST /clinic/notes/{id}/sign.

CampoTipo
note_idstring (uuid)
encounter_idstring (uuid)
patient_idstring (uuid)
kindenum: anamnesis | evolucion | indicaciones | interconsulta | administrativa
signed_by_user_idstring (uuid) | null
{
  "note_id": "18e0d9c3-0000-4000-8000-000000000001",
  "encounter_id": "14141414-0000-4000-8000-000000000001",
  "patient_id": "b1b0b8de-0000-4000-8000-000000000001",
  "kind": "evolucion",
  "signed_by_user_id": "11111111-0000-4000-8000-000000000001"
}

clinic_note.amended

A signed note was amended. The amendment is a NEW signed note pointing at the one it corrects; both stay.

Versión 1 · Recurso clinic_note · data requiere clinic_record:read

Dato sensible: llega siempre como aviso, sin data, pida lo que pida la suscripción.

Cuándo se dispara. POST /clinic/notes/{id}/amend succeeds.

CampoTipo
note_idstring (uuid) — the amendment
amends_note_idstring (uuid) — the note it corrects
patient_idstring (uuid)
{
  "note_id": "18e0d9c3-0000-4000-8000-000000000002",
  "amends_note_id": "18e0d9c3-0000-4000-8000-000000000001",
  "patient_id": "b1b0b8de-0000-4000-8000-000000000001"
}

A patient signed a consent. What they agreed to — photographs, marketing use — is in the consent, not here.

Versión 1 · Recurso clinic_consent · data requiere clinic_record:read

Dato sensible: llega siempre como aviso, sin data, pida lo que pida la suscripción.

Cuándo se dispara. POST /clinic/consents/{id}/sign succeeds, or the patient signs from their own link.

CampoTipo
consent_idstring (uuid)
patient_idstring (uuid)
template_idstring (uuid)
signature_methodenum: tablet | public_link | whatsapp_otp | pdf_upload | paper_scan
expires_atISO 8601 timestamp | null
{
  "consent_id": "1b1f8a70-0000-4000-8000-000000000001",
  "patient_id": "b1b0b8de-0000-4000-8000-000000000001",
  "template_id": "2c9d4e11-0000-4000-8000-000000000001",
  "signature_method": "tablet",
  "expires_at": null
}

A patient withdrew a consent. The signed document stays — what changed is the permission, and a system holding their photographs has to act on it.

Versión 1 · Recurso clinic_consent · data requiere clinic_record:read

Dato sensible: llega siempre como aviso, sin data, pida lo que pida la suscripción.

Cuándo se dispara. POST /clinic/consents/{id}/revoke succeeds.

CampoTipo
consent_idstring (uuid)
patient_idstring (uuid)
revoked_atISO 8601 timestamp
{
  "consent_id": "1b1f8a70-0000-4000-8000-000000000001",
  "patient_id": "b1b0b8de-0000-4000-8000-000000000001",
  "revoked_at": "2026-09-24T18:40:02.119Z"
}

clinic_document.uploaded

A file landed in a patient's ficha. The bytes are fetched deliberately, with a credential; the notice says only that they exist.

Versión 1 · Recurso clinic_document · data requiere clinic_record:read

Dato sensible: llega siempre como aviso, sin data, pida lo que pida la suscripción.

Cuándo se dispara. POST /clinic/patients/{id}/documents succeeds.

CampoTipo
document_idstring (uuid)
patient_idstring (uuid)
kindenum: examen | imagen | informe | receta | indicaciones | consentimiento | presupuesto | boleta | certificado
sensitivityenum: phi | administrative
size_bytesinteger
{
  "document_id": "16161616-0000-4000-8000-000000000001",
  "patient_id": "b1b0b8de-0000-4000-8000-000000000001",
  "kind": "informe",
  "sensitivity": "phi",
  "size_bytes": 148223
}

clinic_document.deleted

A clinical document was deleted, with its reason. A mirror that copied it should delete its copy.

Versión 1 · Recurso clinic_document · data requiere clinic_record:read

Dato sensible: llega siempre como aviso, sin data, pida lo que pida la suscripción.

Cuándo se dispara. POST /clinic/documents/{id}/delete succeeds, or retention swept it.

CampoTipo
document_idstring (uuid)
patient_idstring (uuid)
reasonstring
{
  "document_id": "16161616-0000-4000-8000-000000000001",
  "patient_id": "b1b0b8de-0000-4000-8000-000000000001",
  "reason": "Cargado en la ficha equivocada"
}

clinic_privacy_request.completed

A Ley 21.719 request finished — an export the patient can download, or an erase that removed their record. An integration holding a copy has to do the same.

Versión 1 · Recurso clinic_privacy_request · data requiere clinic_record:read

Dato sensible: llega siempre como aviso, sin data, pida lo que pida la suscripción.

Cuándo se dispara. The export archive is written, or the erasure completes.

CampoTipo
privacy_request_idstring (uuid)
patient_idstring (uuid)
kindenum: export | erase
statusenum: completed | refused | failed
completed_atISO 8601 timestamp | null
{
  "privacy_request_id": "17171717-0000-4000-8000-000000000001",
  "patient_id": "b1b0b8de-0000-4000-8000-000000000001",
  "kind": "export",
  "status": "completed",
  "completed_at": "2026-09-24T18:20:07.430Z"
}

En esta página

El sobreAviso o datosLos headersLa firmaVerificar, en pseudocódigoReintentosPausa automáticaLos eventoscampaign.sentcampaign.pausedsending_domain.verifiedcoach.proposals_readycoach.finding_readyvoice.autonomous_callsafety_hold.placedsafety_hold.ack_overduesafety_hold.first_response_overduechileautos.leadyapo.leadwebsite.leadconsignment.candidate.createdquote.issuedquote.voidedquote.extendedquote.archivedreservation.createdreservation.voidedreservation.abono_disposedreservation.convertedsale_note.issuedsale_note.approvedsale_note.voidedpurchase_note.issuedpurchase_note.correctedpurchase_note.voidedpayment.recordedconsignment.createdconsignment.returnedconsignment.soldcredit_application.recordedcredit_application.submittedcredit_application.decidedcredit_application.withdrawnprice_approval.requestedprice_approval.decidedconversation.createdconversation.assignedconversation.resolvedmessage.receivedmessage.sentticket.openedticket.reopenedticket.resolvedticket.closedsla.breachedagent_scenario_run.completedagent_scenario_suite.completedai_agent.publishai_agent_graph.publishcost.budget_alertruntime.budget_exceededkb.source.createkb.source.embedkb.source.updatekb.source.deletekb_file.uploadedkb_file.deletedskill.createdskill.updatedskill.deletedcustom_tool.createdcustom_tool.updatedcustom_tool.deletedlead.createdlead.stage_changedlead.pipeline_changedlead.assignedlead.wonlead.lostlead.unqualifiedmacro.appliedstock.source_retiredstock.syncedvehicle.publishedvehicle.unpublishedvehicle.publish_failedvehicle.createdvehicle.attachment.createdmercadolibre.ordermercadolibre.vis_leadappointment.bookedappointment.rescheduledappointment.cancelledappointment.completedappointment.no_showappointment.remindedappointment.importedcontact.createdcontact.updatedcontact.mergedteam.createdteam.updatedteam.deletedcustom_role.createdcustom_role.updatedcustom_role.deletedmacro.createdclinic_professional.createdclinic_professional.updatedclinic_professional.deletedclinic_service.createdmacro.updatedmacro.deletedassignment_rule.createdassignment_rule.updatedassignment_rule.deletedsla_policy.createdsla_policy.updatedsla_policy.deletedautomation_trigger.createdautomation_trigger.updatedautomation_trigger.deletedsending_domain.createdsending_domain.deletedwhatsapp_template.createdwhatsapp_template.status_changedwhatsapp_template.deletedwhatsapp_flow.createdwhatsapp_flow.publishedwhatsapp_flow.deprecatedwhatsapp_flow.deletedchannel.connectedchannel.updatedchannel.disconnectedmembership.createdmembership.updatedmembership.removedclinic_service.updatedclinic_service.deletedclinic_price_list.updatedclinic_pack_purchase.createdclinic_pack_purchase.session_consumedclinic_pack_purchase.cancelledclinic_budget.createdclinic_budget.sentclinic_budget.acceptedclinic_budget.rejectedclinic_budget.voidedclinic_budget.mirror_changedclinic_cash_session.openedclinic_cash_session.closedclinic_patient.createdclinic_patient.updatedclinic_patient.deletedclinic_patient.mergedclinic_encounter.openedclinic_encounter.closedclinic_note.signedclinic_note.amendedclinic_consent.signedclinic_consent.revokedclinic_document.uploadedclinic_document.deletedclinic_privacy_request.completed