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.
| Campo | Qué trae |
|---|---|
id | string (uuid) — unique event id; use for idempotency |
type | string — one of the events above |
version | integer — the schema version of this event type; bumped only on a breaking change to data |
livemode | boolean — 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_at | ISO 8601 timestamp |
tenant_id | string (uuid) — your workspace id |
resource | object { type: string, id: string | null, url: string | null } — what the event is about; GET url with your credential to read it |
changes | object | absent — field → { from, to } for the identifiers and states that changed |
author | object { 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 |
data | object | absent — the resource payload (see catalog); present only when the subscription includes data and its owner may read it |
data_omitted | string | 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ón | Qué trae |
|---|---|
notice | The 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_requested | The subscription did not ask for data. |
sensitive | The 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_visibility | The owner sees only some records (assigned-only, or some sucursales). |
owner_unavailable | The 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
| Header | Qué trae |
|---|---|
X-Webhook-Event | The event name (e.g. ticket.opened). |
X-Webhook-Event-Id | Unique event id. Retries reuse this id so you can dedupe. |
X-Webhook-Timestamp | Unix seconds when the signature was generated. |
X-Webhook-Signature | t=<unix>,v1=<hex> — HMAC-SHA256 of ${timestamp}.${rawBody} with your subscription secret. |
Vitrina-Livemode | true 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 windowReintentos
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.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
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.
| Campo | Tipo |
|---|---|
campaign_id | string (uuid) |
name | string |
channel | enum: whatsapp | email |
total_recipients | integer |
sent | integer (includes delivered/read) |
failed | integer |
suppressed | integer |
finished_at | ISO 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.
| Campo | Tipo |
|---|---|
campaign_id | string (uuid) |
name | string |
reason | enum: messaging_limit | reputation_guard |
detail | string | 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.
| Campo | Tipo |
|---|---|
id | string (uuid) |
domain | string |
{
"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.
| Campo | Tipo |
|---|---|
ai_agent_id | string (uuid) |
finding_id | string (uuid) | undefined (present when source=investigation) |
correction_id | string (uuid) | undefined (present when source=correction) |
source | enum: review | investigation | correction |
proposals | number (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.
| Campo | Tipo |
|---|---|
ai_agent_id | string (uuid) |
findings | array of { id: string (uuid), kind: string, topic_key: string, title: string, occurrence_count: number, status: string } |
source | enum: 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.
| Campo | Tipo |
|---|---|
lead_id | string (uuid) |
contact_id | string (uuid) | null |
agent_id | string (uuid) |
autonomy_level | enum: 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.
| Campo | Tipo |
|---|---|
hold_id | string (uuid) |
contact_id | string (uuid) |
scope | enum: run | contact | subject |
kind | enum: safety | identity | manual | policy |
reason | string |
placed_at | ISO 8601 timestamp |
stepKey | string | null |
ticketId | string | null |
ackMinutes | integer (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.
| Campo | Tipo |
|---|---|
hold_id | string (uuid) |
contact_id | string (uuid) |
eventType | literal: ack_overdue |
targetMinutes | integer |
elapsedMinutes | integer (coverage minutes, not wall-clock) |
withinCoverage | boolean — 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.
| Campo | Tipo |
|---|---|
hold_id | string (uuid) |
contact_id | string (uuid) |
eventType | literal: first_response_overdue |
targetMinutes | integer |
elapsedMinutes | integer (coverage minutes, not wall-clock) |
withinCoverage | boolean |
{
"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.
| Campo | Tipo |
|---|---|
lead_id | string (uuid) | null |
contact_id | string |
vehicle_id | string (uuid) | null |
source | enum: chileautos | whatsapp | ChileautosLeadPremium | null |
chileautos_lead_identifier | string |
chileautos_status | string | 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.
| Campo | Tipo |
|---|---|
lead_id | string (uuid) | null |
contact_id | string |
vehicle_id | string (uuid) | null |
yapo_lead_id | string |
yapo_ad_id | string | 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.
| Campo | Tipo |
|---|---|
lead_id | string (uuid) | null |
contact_id | string |
conversation_id | string | null |
vehicle_id | string (uuid) | null |
submission_id | string |
intent | enum: 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.
| Campo | Tipo |
|---|---|
candidate_id | string (uuid) |
patente | string (as typed by the owner) |
make | string |
model | string |
year | integer |
kilometers | integer |
expected_price_clp | integer (whole CLP) | null |
sale_horizon | enum: 7d | 15d | 30d | cotizando |
region_code | string (2-digit CUT) |
photo_count | integer |
submission_id | string |
{
"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.
| Campo | Tipo |
|---|---|
id | string (uuid) |
display_id | string (Q-…) |
vehicle_id | string (uuid) |
buyer_contact_id | string (uuid) |
salesperson_id | string (uuid) | null |
offered_price_clp | integer (whole CLP) |
tax_treatment | enum: afecto | exento | pass_through |
expires_on | string (YYYY-MM-DD) |
state | enum: 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.
| Campo | Tipo |
|---|---|
id | string (uuid) |
display_id | string (Q-…) |
void_reason | string |
voided_by | string (uuid) |
voided_at | string (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.
| Campo | Tipo |
|---|---|
id | string (uuid) |
display_id | string (Q-…) |
expires_on | string (YYYY-MM-DD) |
extend_reason | string |
extended_by | string (uuid) |
extended_at | string (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.
| Campo | Tipo |
|---|---|
id | string (uuid) |
display_id | string (Q-…) |
archive_reason | string |
archived_by | string (uuid) |
archived_at | string (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.
| Campo | Tipo |
|---|---|
id | string (uuid) |
display_id | string (R-…) |
vehicle_id | string (uuid) |
holder_contact_id | string (uuid) |
salesperson_id | string (uuid) | null |
agreed_price_clp | integer (whole CLP) | null |
hold_expires_on | string (YYYY-MM-DD) |
status | enum: 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.
| Campo | Tipo |
|---|---|
id | string (uuid) |
display_id | string (R-…) |
void_reason | string |
voided_by | string (uuid) |
voided_at | string (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.
| Campo | Tipo |
|---|---|
id | string (uuid) |
display_id | string (R-…) |
abono_disposition | enum: retenido | devuelto | aplicado |
abono_disposition_reason | string | null |
abono_disposition_by | string (uuid) |
abono_disposition_at | string (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.
| Campo | Tipo |
|---|---|
id | string (uuid) |
display_id | string (R-…) |
sale_note_id | string (uuid) |
sale_note_display_id | string (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.
| Campo | Tipo |
|---|---|
id | string (uuid) |
display_id | string (V-…) |
vehicle_id | string (uuid) |
buyer_contact_id | string (uuid) |
net_clp | integer (whole CLP) |
tax_clp | integer (whole CLP) | null |
tax_treatment | enum: afecto | exento | pass_through |
status | enum: 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.
| Campo | Tipo |
|---|---|
id | string (uuid) |
display_id | string (V-…) |
approved_by | string (uuid) |
approved_at | string (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.
| Campo | Tipo |
|---|---|
id | string (uuid) |
display_id | string (V-…) |
void_reason | string |
voided_by | string (uuid) |
voided_at | string (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.
| Campo | Tipo |
|---|---|
id | string (uuid) |
display_id | string (P-…) |
seller_contact_id | string (uuid) |
net_clp | integer (whole CLP) |
tax_treatment | enum: afecto | exento | no_gravado |
status | enum: 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}.
| Campo | Tipo |
|---|---|
id | string (uuid) |
display_id | string (P-…) |
corrected_by | string (uuid) |
corrected_at | string (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.
| Campo | Tipo |
|---|---|
id | string (uuid) |
display_id | string (P-…) |
void_reason | string |
voided_by | string (uuid) |
voided_at | string (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.
| Campo | Tipo |
|---|---|
id | string (uuid) |
reservation_id | string (uuid) | null |
sale_note_id | string (uuid) | null |
instrument | enum: efectivo | transferencia | tarjeta_debito | tarjeta_credito | cheque |
amount_clp | integer (whole CLP) |
paid_on | string (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.
| Campo | Tipo |
|---|---|
id | string (uuid) |
vehicle_id | string (uuid) |
dueno_contact_id | string (uuid) | null |
modalidad | enum: en_local | virtual |
comision_type | enum: percentage | fixed | null |
comision_value | number | null |
minimo_clp | integer (whole CLP) | null |
estado | enum: 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".
| Campo | Tipo |
|---|---|
id | string (uuid) |
vehicle_id | string (uuid) |
estado | enum: 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.
| Campo | Tipo |
|---|---|
id | string (uuid) |
vehicle_id | string (uuid) |
liquidacion_id | string (uuid) |
amount_venta_clp | integer (whole CLP) |
comision_amount_clp | integer (whole CLP) |
monto_owner_clp | integer (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.
| Campo | Tipo |
|---|---|
id | string (uuid) |
display_id | string (F-…) |
vehicle_id | string (uuid) |
buyer_contact_id | string (uuid) | null |
institution_id | string (uuid) |
requested_amount_clp | integer (whole CLP) |
state | enum: 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.
| Campo | Tipo |
|---|---|
id | string (uuid) |
display_id | string (F-…) |
submitted_by | string (uuid) |
submitted_at | string (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.
| Campo | Tipo |
|---|---|
id | string (uuid) |
display_id | string (F-…) |
outcome | enum: approved | rejected |
approved_amount_clp | integer (whole CLP) | null |
decided_by | string (uuid) |
decided_at | string (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.
| Campo | Tipo |
|---|---|
id | string (uuid) |
display_id | string (F-…) |
withdrawn_reason | string |
withdrawn_by | string (uuid) |
withdrawn_at | string (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.
| Campo | Tipo |
|---|---|
id | string (uuid) |
quote_id | string (uuid) | null |
reservation_id | string (uuid) | null |
sale_note_id | string (uuid) | null |
requested_price_clp | integer (whole CLP) |
requester_user_id | string (uuid) |
status | enum: 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.
| Campo | Tipo |
|---|---|
id | string (uuid) |
status | enum: approved | rejected |
decision_note | string | null |
decider_user_id | string (uuid) |
decided_at | string (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.
| Campo | Tipo |
|---|---|
conversation_id | string (uuid) |
display_id | string — the ID visible, e.g. C-3052 |
channel | string |
contact_id | string (uuid) | null |
messaging_account_id | string (uuid) | null |
ticket_id | string (uuid) | null |
handler | enum: bot | human | external |
status | enum: open | pending | snoozed | resolved | closed |
source | string | null — the portal or campaign it came from |
created_at | ISO 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).
| Campo | Tipo |
|---|---|
conversation_id | string (uuid) |
display_id | string — the ID visible, e.g. C-3052 |
from_assignee_user_id | string (uuid) | null |
to_assignee_user_id | string (uuid) | null |
from_handler | enum: bot | human | external | null |
to_handler | enum: bot | human | external |
ticket_id | string (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.
| Campo | Tipo |
|---|---|
conversation_id | string (uuid) |
display_id | string — the ID visible, e.g. C-3052 |
from_status | enum: open | pending | snoozed | resolved |
to_status | enum: resolved | closed |
resolved_by | enum: bot | human | system | null |
resolved_at | ISO 8601 timestamp | null |
ticket_id | string (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.
| Campo | Tipo |
|---|---|
message_id | string (uuid) |
conversation_id | string (uuid) |
contact_id | string (uuid) | null |
channel | string |
type | enum: text | image | audio | video | file | location | … |
content | string — empty on a media-only message |
external_message_id | string | null — the provider’s id, not a Vitrina reference |
created_at | ISO 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.
| Campo | Tipo |
|---|---|
message_id | string (uuid) |
conversation_id | string (uuid) |
contact_id | string (uuid) | null |
channel | string |
type | enum: text | image | audio | video | file | location | … |
content | string |
sender_type | enum: ai_agent | human_user | api_key |
delivery_status | enum: sent | delivered | read | retrying | failed | null |
created_at | ISO 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.
| Campo | Tipo |
|---|---|
ticket_id | string (uuid) |
origin_conversation_id | string (uuid) | null |
reason | string | null |
opened_by | enum: bot | human | system | rule |
opened_via | string | null |
brand | string | 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.
| Campo | Tipo |
|---|---|
ticket_id | string (uuid) |
origin_conversation_id | string (uuid) | null |
reason | string | null |
brand | string | 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.
| Campo | Tipo |
|---|---|
ticket_id | string (uuid) |
origin_conversation_id | string (uuid) | null |
resolved_by | string |
resolved_at | ISO 8601 timestamp |
brand | string | 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.
| Campo | Tipo |
|---|---|
ticket_id | string (uuid) |
origin_conversation_id | string (uuid) | null |
closed_by | string |
brand | string | 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.
| Campo | Tipo |
|---|---|
ticket_id | string (uuid) |
ticket_display_id | string | null (e.g. T-1247) |
policy_id | string (uuid) |
policy_name | string |
breach_type | enum: first_response | resolution |
target_minutes | integer |
elapsed_seconds | integer |
assignee | string | null (agent display name) |
breached_at | ISO 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.
| Campo | Tipo |
|---|---|
run_id | string (uuid) |
scenario_id | string (uuid) |
scenario_display_id | string (EV-n) |
ai_agent_id | string (uuid) |
agent_version_kind | "draft" | "live" |
batch_id | string (uuid) | null |
status | "passed" | "partial" | "failed" | "error" |
score | number 0-100 | null |
hard_fails | [{ id, kind, evidence }] |
duration_ms | number |
{
"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.
| Campo | Tipo |
|---|---|
suite_run_id | string (uuid) |
suite_id | string (uuid) |
suite_name | string | null |
kind | enum: golden|nightly|exploratory|on_change (null if the suite was deleted mid-run) |
ai_agent_id | string (uuid) |
batch_id | string (uuid) — the agent_scenario_run.batch_id |
trigger | enum: manual|publish|cron|api |
agent_version_kind | "draft" | "live" |
agent_version_number | number | 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.
| Campo | Tipo |
|---|---|
ai_agent_id | string (uuid) |
name | string | null |
updated_at | ISO 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.
| Campo | Tipo |
|---|---|
tenant_id | string (uuid) |
graph_id | string (uuid) |
version_id | string (uuid) — the snapshot that just went live |
version_number | integer — 1 for the first publish, then monotonic |
topology | enum: 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).
| Campo | Tipo |
|---|---|
domain | enum: copilot — present only on the two Copilot producers; absent on the graph-runtime one |
month | string (YYYY-MM) — Copilot producers only |
severity | enum: credits_exhausted | soft | hard — Copilot producers only |
credit_allowance | integer — credits granted this month; credits_exhausted only |
credits_used | integer — credits consumed; credits_exhausted only |
grace_credits | integer — credits served past the allowance; credits_exhausted only |
used_usd | number — margin spent this month; Copilot producers only |
soft_limit_usd | number — soft/hard severities only |
hard_limit_usd | number — soft/hard severities only |
tenant_id | string (uuid) — graph-runtime producer only |
pct | integer: 50 | 80 | 100 — the threshold crossed; graph-runtime producer only |
budget_usd | number — the monthly budget; graph-runtime producer only |
spent_usd | number — 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.
| Campo | Tipo |
|---|---|
tenant_id | string (uuid) |
graph_id | string (uuid) |
topology | enum: single | sequential | routing | supervisor | parallel | evaluator_loop |
reason | enum: 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.
| Campo | Tipo |
|---|---|
kb_source_id | string (uuid) |
title | string | null |
tags | string[] | 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.
| Campo | Tipo |
|---|---|
kb_source_id | string (uuid) |
chunks | integer |
{
"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.
| Campo | Tipo |
|---|---|
kb_source_id | string (uuid) |
fields | string[] — 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.
| Campo | Tipo |
|---|---|
kb_source_id | string (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).
| Campo | Tipo |
|---|---|
kb_file_id | string (uuid) |
name | string |
content_type | string |
{
"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.
| Campo | Tipo |
|---|---|
kb_file_id | string (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.
| Campo | Tipo |
|---|---|
skill_id | string (uuid) |
name | string |
{
"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.
| Campo | Tipo |
|---|---|
skill_id | string (uuid) |
name | string |
{
"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.
| Campo | Tipo |
|---|---|
skill_id | string (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.
| Campo | Tipo |
|---|---|
custom_tool_id | string (uuid) |
name | string |
{
"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.
| Campo | Tipo |
|---|---|
custom_tool_id | string (uuid) |
name | string |
{
"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.
| Campo | Tipo |
|---|---|
custom_tool_id | string (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.
| Campo | Tipo |
|---|---|
lead_id | string (uuid) |
contact_id | string (uuid) |
pipeline_id | string (uuid) |
stage_id | string (uuid) |
source | enum: conversation | marketplace | manual | import | ai_agent | website | chileautos | yapo | mercadolibre | facebook_marketplace |
intent | enum: buy | sell | financing | trade_in (defaults to buy) |
title | string |
value_amount | number | null |
value_currency | string | null |
owner_user_id | string (uuid) | null |
team_id | string (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.
| Campo | Tipo |
|---|---|
lead_id | string (uuid) |
from_stage_id | string (uuid) | null |
to_stage_id | string (uuid) |
to_stage_slug | string | null |
reason | string | 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.
| Campo | Tipo |
|---|---|
lead_id | string (uuid) |
from_pipeline_id | string (uuid) | null |
to_pipeline_id | string (uuid) |
from_stage_id | string (uuid) | null |
to_stage_id | string (uuid) |
reason | string | 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.
| Campo | Tipo |
|---|---|
lead_id | string (uuid) |
owner_user_id | string (uuid) | null |
team_id | string (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.
| Campo | Tipo |
|---|---|
lead_id | string (uuid) |
reason | string | null |
value_amount | number | null |
value_currency | string | 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.
| Campo | Tipo |
|---|---|
lead_id | string (uuid) |
reason | string | null |
value_amount | number | null |
value_currency | string | 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.
| Campo | Tipo |
|---|---|
lead_id | string (uuid) |
reason | string | null |
value_amount | number | null |
value_currency | string | 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.
| Campo | Tipo |
|---|---|
macro_id | string (uuid) |
macro_name | string |
conversation_id | integer |
ticket_id | integer | null |
contact_id | integer | null |
executed_steps | array of {type: string, ok: boolean, error: string | null} |
applied_by_user_id | string (uuid) | null |
applied_at | ISO 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).
| Campo | Tipo |
|---|---|
integration_id | string (uuid) |
provider | string |
retired | integer |
revived | integer |
kept_hidden | integer |
{
"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).
| Campo | Tipo |
|---|---|
integration_id | string (uuid) |
provider | string |
vehicles_imported | integer |
vehicles_updated | integer |
vehicles_deactivated | integer |
{
"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.
| Campo | Tipo |
|---|---|
vehicle_id | string (uuid) |
integration_id | string (uuid) |
provider | string |
external_id | string |
{
"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).
| Campo | Tipo |
|---|---|
vehicle_id | string (uuid) |
integration_id | string (uuid) |
provider | string |
external_id | string | 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.
| Campo | Tipo |
|---|---|
vehicle_id | string (uuid) |
integration_id | string (uuid) |
provider | string |
error | string |
{
"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.
| Campo | Tipo |
|---|---|
id | string (uuid) — pass to GET /api/v1/vehicles/{id} |
patente | string | null — Chilean plate as entered; null when absent |
vin | string (17 chars) | null |
make | string | null |
model | string | null |
version | string | null |
year | integer | null |
km | integer | null — kilometres (a miles odometer is converted) |
price | integer | null — whole CLP pesos; null = no price recorded, 0 = free |
status | enum: disponible | reservado | vendido |
tenencia | enum: propio | consignacion |
location | object { id: uuid, name: string } | null — the branch, null when untagged |
source | string — manual, or the portal the import came from (chileautos, mercadolibre, yapo, …) |
created_at | string (ISO 8601) |
url | string (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.
| Campo | Tipo |
|---|---|
id | string (uuid) — the attachment |
vehicle_id | string (uuid) — the car this expediente belongs to |
kind | enum: padron | cedula | contrato | certificado_anotaciones | factura | otro |
filename | string — user-chosen; may carry a person’s name (see description) |
mime_type | string — application/pdf, image/jpeg, image/png, image/webp |
byte_size | integer |
uploaded_at | string (ISO 8601) |
content_url | string (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.
| Campo | Tipo |
|---|---|
integration_id | string (uuid) |
order_id | number |
status | string |
total_amount | number |
currency_id | string |
buyer | object |
items | array |
{
"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.
| Campo | Tipo |
|---|---|
integration_id | string (uuid) |
lead_id | string |
item_id | string | null |
contact_type | string (whatsapp|call|question|visit_request|contact_request|reservation) |
status | string | 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).
| Campo | Tipo |
|---|---|
id | string (uuid) |
display_id | string (A-…, the ID visible) |
status | enum: pending_hold|confirmed|cancelled|completed|no_show |
kind | enum: test_drive|external|block|clinic |
starts_at | string (ISO 8601) |
ends_at | string (ISO 8601) |
appointment_type_id | string (uuid) | null |
vehicle_id | string (uuid) | null |
owner_user_id | string (uuid) | null |
location_id | string (uuid) | null |
lead_id | string (uuid) | null |
contact_id | string (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.
| Campo | Tipo |
|---|---|
id | string (uuid) |
display_id | string (A-…, the ID visible) |
status | enum: pending_hold|confirmed|cancelled|completed|no_show |
kind | enum: test_drive|external|block|clinic |
starts_at | string (ISO 8601) |
ends_at | string (ISO 8601) |
previous_starts_at | string (ISO 8601) |
previous_ends_at | string (ISO 8601) |
appointment_type_id | string (uuid) | null |
vehicle_id | string (uuid) | null |
owner_user_id | string (uuid) | null |
location_id | string (uuid) | null |
lead_id | string (uuid) | null |
contact_id | string (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.
| Campo | Tipo |
|---|---|
id | string (uuid) |
display_id | string (A-…, the ID visible) |
status | enum (cancelled) |
previous_status | enum: pending_hold|confirmed|completed|no_show | null |
kind | enum: test_drive|external|block|clinic |
starts_at | string (ISO 8601) |
ends_at | string (ISO 8601) |
appointment_type_id | string (uuid) | null |
vehicle_id | string (uuid) | null |
owner_user_id | string (uuid) | null |
location_id | string (uuid) | null |
lead_id | string (uuid) | null |
contact_id | string (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.
| Campo | Tipo |
|---|---|
id | string (uuid) |
display_id | string (A-…, the ID visible) |
status | enum (completed) |
previous_status | enum: pending_hold|confirmed|cancelled|no_show | null |
kind | enum: test_drive|external|block|clinic |
starts_at | string (ISO 8601) |
ends_at | string (ISO 8601) |
appointment_type_id | string (uuid) | null |
vehicle_id | string (uuid) | null |
owner_user_id | string (uuid) | null |
location_id | string (uuid) | null |
lead_id | string (uuid) | null |
contact_id | string (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.
| Campo | Tipo |
|---|---|
id | string (uuid) |
display_id | string (A-…, the ID visible) |
status | enum (no_show) |
previous_status | enum: pending_hold|confirmed|cancelled|completed | null |
kind | enum: test_drive|external|block|clinic |
starts_at | string (ISO 8601) |
ends_at | string (ISO 8601) |
appointment_type_id | string (uuid) | null |
vehicle_id | string (uuid) | null |
owner_user_id | string (uuid) | null |
location_id | string (uuid) | null |
lead_id | string (uuid) | null |
contact_id | string (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).
| Campo | Tipo |
|---|---|
id | string (uuid) |
display_id | string (A-…, the ID visible) |
starts_at | string (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.
| Campo | Tipo |
|---|---|
id | string (uuid) |
display_id | string (A-…, the ID visible) |
starts_at | string (ISO 8601) |
ends_at | string (ISO 8601) |
source | enum (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.
| Campo | Tipo |
|---|---|
id | string (uuid) — pass to GET /api/v1/contacts/{id} |
external_id | string | null — the identity the creating path keyed on (your own id on an import, meli:{buyerId}, a WhatsApp wa_id, manual:{uuid}) |
name | string | null |
email | string | null |
phone | string | null — E.164 when it could be normalised |
lifecycle_stage | string |
origin_channel | string | null — the «Canal de origen» a human picked |
company_id | string (uuid) | null |
created_at | string (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.
| Campo | Tipo |
|---|---|
id | string (uuid) — pass to GET /api/v1/contacts/{id} |
external_id | string | null |
name | string | null |
email | string | null |
phone | string | null — E.164 when it could be normalised |
lifecycle_stage | string |
origin_channel | string | null |
company_id | string (uuid) | null |
updated_fields | array of string — the column names this write touched, in no particular order |
updated_at | string (ISO 8601) |
from_lifecycle_stage | string | undefined — present only when the stage changed |
to_lifecycle_stage | string | undefined — present only when the stage changed |
from_company_id | string (uuid) | null | undefined — present only when the company changed |
to_company_id | string (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.
| Campo | Tipo |
|---|---|
id | string (uuid) — the survivor |
merged_contact_ids | array of string (uuid) — the contacts that became tombstones |
merged_count | integer |
conversations_reassigned | integer — threads repointed from the tombstones onto the survivor |
filled_fields | array of string — the survivor’s columns that GAINED a value from a tombstone (names, not values) |
name | string | null — the survivor, after the backfill |
email | string | null |
phone | string | null |
merged_at | string (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.
| Campo | Tipo |
|---|---|
id | string (uuid) |
name | string |
brand | string | null |
hours_mode | enum: workspace | override |
created_at | string (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}.
| Campo | Tipo |
|---|---|
id | string (uuid) |
name | string |
from_hours_mode | enum: workspace | override |
to_hours_mode | enum: workspace | override |
created_at | string (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}.
| Campo | Tipo |
|---|---|
id | string (uuid) |
name | string |
{
"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.
| Campo | Tipo |
|---|---|
id | string (uuid) |
name | string |
scopes | string[] |
record_visibility | enum: all | assigned_unassigned | assigned |
stock_visibility | enum: 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}.
| Campo | Tipo |
|---|---|
id | string (uuid) |
name | string |
scopes | string[] |
record_visibility | enum: all | assigned_unassigned | assigned |
stock_visibility | enum: 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}.
| Campo | Tipo |
|---|---|
id | string (uuid) |
name | string |
reassigned_to | string (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.
| Campo | Tipo |
|---|---|
id | string (uuid) |
name | string |
active | boolean |
{
"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).
| Campo | Tipo |
|---|---|
professional_id | string (uuid) |
nombre | string |
apellidos | string | null |
especialidad | string | null |
active | boolean |
source | enum: 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.
| Campo | Tipo |
|---|---|
professional_id | string (uuid) |
nombre | string |
apellidos | string | null |
especialidad | string | null |
active | boolean |
active_before | boolean |
source | enum: 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.
| Campo | Tipo |
|---|---|
professional_id | string (uuid) |
nombre | string |
apellidos | string | 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).
| Campo | Tipo |
|---|---|
service_id | string (uuid) |
nombre | string |
codigo | string | null |
categoria | string | null |
precio_clp | integer | null |
duration_minutes | integer | null |
active | boolean |
{
"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}.
| Campo | Tipo |
|---|---|
id | string (uuid) |
name | string |
active | boolean |
{
"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}.
| Campo | Tipo |
|---|---|
id | string (uuid) |
name | string |
{
"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.
| Campo | Tipo |
|---|---|
id | string (uuid) |
name | string |
priority | number (int) — evaluation order, ascending |
team_id | string (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}.
| Campo | Tipo |
|---|---|
id | string (uuid) |
name | string |
enabled | boolean |
{
"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}.
| Campo | Tipo |
|---|---|
id | string (uuid) |
name | string |
{
"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.
| Campo | Tipo |
|---|---|
id | string (uuid) |
name | string |
target_first_response_minutes | number (int) | null |
target_resolution_minutes | number (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}.
| Campo | Tipo |
|---|---|
id | string (uuid) |
name | string |
target_first_response_minutes | number (int) | null |
target_resolution_minutes | number (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}.
| Campo | Tipo |
|---|---|
id | string (uuid) |
name | string |
{
"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.
| Campo | Tipo |
|---|---|
id | string (uuid) |
name | string |
event | string |
enabled | boolean |
{
"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}.
| Campo | Tipo |
|---|---|
id | string (uuid) |
name | string |
event | string |
enabled | boolean |
{
"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}.
| Campo | Tipo |
|---|---|
id | string (uuid) |
name | string |
{
"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.
| Campo | Tipo |
|---|---|
id | string (uuid) |
domain | string |
{
"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}.
| Campo | Tipo |
|---|---|
id | string (uuid) |
domain | string |
{
"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.
| Campo | Tipo |
|---|---|
id | string (uuid) |
name | string |
language | string |
status | enum: 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.
| Campo | Tipo |
|---|---|
id | string (uuid) |
name | string |
from_status | enum: PENDING | APPROVED | REJECTED | … |
to_status | enum: PENDING | APPROVED | REJECTED | … |
rejection_reason | string | 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}.
| Campo | Tipo |
|---|---|
id | string (uuid) |
name | string |
{
"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.
| Campo | Tipo |
|---|---|
id | string (uuid) |
name | string |
status | enum: 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.
| Campo | Tipo |
|---|---|
id | string (uuid) |
name | string |
status | enum: 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.
| Campo | Tipo |
|---|---|
id | string (uuid) |
name | string |
status | enum: 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}.
| Campo | Tipo |
|---|---|
id | string (uuid) |
name | string |
{
"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.
| Campo | Tipo |
|---|---|
id | string (uuid) |
name | string |
kind | string |
channels | string[] |
{
"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}.
| Campo | Tipo |
|---|---|
id | string (uuid) |
name | string |
kind | string |
{
"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}.
| Campo | Tipo |
|---|---|
id | string (uuid) |
name | string |
kind | string |
{
"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.
| Campo | Tipo |
|---|---|
id | string (uuid) |
user_id | string (uuid) |
role | enum: owner | admin | supervisor | agent | consultant |
custom_role_id | string (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}.
| Campo | Tipo |
|---|---|
id | string (uuid) |
user_id | string (uuid) |
from_role | enum: owner | admin | supervisor | agent | consultant |
to_role | enum: 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}.
| Campo | Tipo |
|---|---|
id | string (uuid) |
user_id | string (uuid) |
handover_mode | enum: unassign | user | round_robin |
conversations_moved | number (int) |
leads_moved | number (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.
| Campo | Tipo |
|---|---|
service_id | string (uuid) |
nombre | string |
precio_clp | integer | null |
precio_clp_before | integer | null |
duration_minutes | integer | null |
active | boolean |
active_before | boolean |
{
"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.
| Campo | Tipo |
|---|---|
service_id | string (uuid) |
nombre | string |
{
"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.
| Campo | Tipo |
|---|---|
price_list_id | string (uuid) |
name | string |
upserted | integer |
removed | integer |
is_default | boolean |
active | boolean |
{
"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.
| Campo | Tipo |
|---|---|
purchase_id | string (uuid) |
pack_id | string (uuid) | null |
clinic_patient_id | string (uuid) | null |
contact_id | string (uuid) | null |
name | string |
session_count | integer |
precio_clp | integer | null |
expires_at | YYYY-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.
| Campo | Tipo |
|---|---|
purchase_id | string (uuid) |
session_id | string (uuid) |
clinic_patient_id | string (uuid) | null |
clinic_service_id | string (uuid) | null |
appointment_id | string (uuid) | null |
sessions_used | integer |
sessions_used_before | integer |
session_count | integer |
{
"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.
| Campo | Tipo |
|---|---|
purchase_id | string (uuid) |
clinic_patient_id | string (uuid) | null |
reason | string |
refunded | boolean |
sessions_remaining | integer |
{
"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.
| Campo | Tipo |
|---|---|
budget_id | string (uuid) |
folio | string (the ID visible, e.g. E-47) | null |
status | enum: draft | sent | accepted | rejected | voided | expired |
clinic_patient_id | string (uuid) | null |
contact_id | string (uuid) | null |
treatment_plan_id | string (uuid) | null |
total_clp | integer | null |
valid_until | YYYY-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.
| Campo | Tipo |
|---|---|
budget_id | string (uuid) |
folio | string | null |
status | enum: sent |
status_before | enum: draft | sent |
clinic_patient_id | string (uuid) | null |
contact_id | string (uuid) | null |
total_clp | integer | null |
delivered | enum: 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.
| Campo | Tipo |
|---|---|
budget_id | string (uuid) |
folio | string | null |
status | enum: accepted |
clinic_patient_id | string (uuid) | null |
contact_id | string (uuid) | null |
total_clp | integer | null |
obligation_group_id | string (uuid) | null |
treatment_plan_id | string (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.
| Campo | Tipo |
|---|---|
budget_id | string (uuid) |
folio | string | null |
status | enum: rejected |
clinic_patient_id | string (uuid) | null |
total_clp | integer | null |
reason | string | 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.
| Campo | Tipo |
|---|---|
budget_id | string (uuid) |
folio | string | null |
status | enum: voided |
clinic_patient_id | string (uuid) | null |
total_clp | integer | null |
reason | string |
{
"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.
| Campo | Tipo |
|---|---|
budget_id | string (uuid) |
folio | string | null |
status | enum: draft | sent | accepted | rejected | expired |
clinic_patient_id | string (uuid) | null |
contact_id | string (uuid) | null |
treatment_plan_id | string (uuid) | null |
total_clp | integer | null |
valid_until | YYYY-MM-DD | null |
mirror_plan_id | string (uuid) | null |
mirror_snapshot_at | string (ISO 8601) | null |
mirror_changed_at | string (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.
| Campo | Tipo |
|---|---|
cash_session_id | string (uuid) |
location_id | string (uuid) |
status | enum: open | closed | reconciled |
opened_by_user_id | string (uuid) | null |
opening_float_clp | integer | null |
opened_at | ISO 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.
| Campo | Tipo |
|---|---|
cash_session_id | string (uuid) |
location_id | string (uuid) |
status | enum: closed |
status_before | enum: open |
expected_clp | integer | null |
counted_clp | integer | null |
difference_clp | integer | null |
closed_at | ISO 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.
| Campo | Tipo |
|---|---|
patient_id | string (uuid) |
source | enum: 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.
| Campo | Tipo |
|---|---|
patient_id | string (uuid) |
fields | string[] (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.
| Campo | Tipo |
|---|---|
patient_id | string (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.
| Campo | Tipo |
|---|---|
patient_id | string (uuid) — the survivor |
merged_patient_id | string (uuid) — the ficha that was merged away |
moved_rows | integer |
{
"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.
| Campo | Tipo |
|---|---|
encounter_id | string (uuid) |
patient_id | string (uuid) |
professional_id | string (uuid) | null |
appointment_id | string (uuid) | null |
kind | enum: 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.
| Campo | Tipo |
|---|---|
encounter_id | string (uuid) |
patient_id | string (uuid) |
closed_at | ISO 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.
| Campo | Tipo |
|---|---|
note_id | string (uuid) |
encounter_id | string (uuid) |
patient_id | string (uuid) |
kind | enum: anamnesis | evolucion | indicaciones | interconsulta | administrativa |
signed_by_user_id | string (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.
| Campo | Tipo |
|---|---|
note_id | string (uuid) — the amendment |
amends_note_id | string (uuid) — the note it corrects |
patient_id | string (uuid) |
{
"note_id": "18e0d9c3-0000-4000-8000-000000000002",
"amends_note_id": "18e0d9c3-0000-4000-8000-000000000001",
"patient_id": "b1b0b8de-0000-4000-8000-000000000001"
}clinic_consent.signed
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.
| Campo | Tipo |
|---|---|
consent_id | string (uuid) |
patient_id | string (uuid) |
template_id | string (uuid) |
signature_method | enum: tablet | public_link | whatsapp_otp | pdf_upload | paper_scan |
expires_at | ISO 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
}clinic_consent.revoked
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.
| Campo | Tipo |
|---|---|
consent_id | string (uuid) |
patient_id | string (uuid) |
revoked_at | ISO 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.
| Campo | Tipo |
|---|---|
document_id | string (uuid) |
patient_id | string (uuid) |
kind | enum: examen | imagen | informe | receta | indicaciones | consentimiento | presupuesto | boleta | certificado |
sensitivity | enum: phi | administrative |
size_bytes | integer |
{
"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.
| Campo | Tipo |
|---|---|
document_id | string (uuid) |
patient_id | string (uuid) |
reason | string |
{
"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.
| Campo | Tipo |
|---|---|
privacy_request_id | string (uuid) |
patient_id | string (uuid) |
kind | enum: export | erase |
status | enum: completed | refused | failed |
completed_at | ISO 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"
}