Event catalogue
Every event this API delivers, with its payload and a sample — generated from the same catalogue GET /webhooks/events answers with.
This page is generated from the catalogue GET /api/v1/webhooks/events serves. It cannot describe an event that does not exist, and it goes stale only if the catalogue does.
The chapter that explains how to subscribe, verify the signature and read the delivery log is here.
The envelope
Every event arrives with the same body. The fields below are the envelope; data is what changes per event, and it arrives only when the subscription asked for it and its owner may read it.
| Field | What it carries |
|---|---|
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 |
Notice or data
Every delivery carries exactly one of data or data_omitted. These are the two modes and every reason data can be missing:
| Mode or reason | What it carries |
|---|---|
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. |
The headers
| Header | What it carries |
|---|---|
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. |
The signature
hmac-sha256, t=<unix>,v1=<hex>.
Replay window: 300 seconds. A signature older than that is rejected.
Verifying, in pseudocode
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 windowRetries
Up to 5 attempts with exponential backoff: 5, 10, 20, 40, 80 seconds apart.
All retries reuse the same X-Webhook-Event-Id so your endpoint can be idempotent.
Auto-pause
A subscription pauses itself when 20 deliveries in a row exhaust their retries, or when it has been failing for 24 hours without a single successful response.
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.
The events
156 events. The name is what goes in events when you create the subscription, and what arrives in 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).
Version 1 · Resource campaign · data requires campaigns:read
Fires when. The campaign worker drains the last pending recipient and flips the campaign to status=sent.
| Field | Type |
|---|---|
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).
Version 1 · Resource campaign · data requires campaigns:read
Fires when. A campaign send gets a rate/messaging-limit error from Atribu/Meta (auto-probes hourly), or the SES event ingest crosses a reputation threshold.
| Field | Type |
|---|---|
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).
Version 1 · Resource sending_domain · data requires messaging_accounts:read
Fires when. A sending-domain refresh observes SES flip VerifiedForSendingStatus to true for the first time.
| Field | Type |
|---|---|
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.
Version 1 · Resource ai_agent · data requires ai_agents:read
Fires when. A finding investigation or a correction synthesis persisted at least one improvement_proposal.
| Field | Type |
|---|---|
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.
Version 1 · Resource ai_agent · data requires ai_agents:read
Fires when. A conversation review created or bumped a finding.
| Field | Type |
|---|---|
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").
Version 1 · Resource lead · data requires leads:read
Fires when. A new lead arrives and the tenant's voice agent autonomy_level is proactive or autonomous and all guardrails pass.
| Field | Type |
|---|---|
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).
Version 1 · Resource outbound_hold · data requires outbound_holds:read
Dato sensible (health data): always delivered as the notice, without data, whatever the subscription asked for.
Fires when. 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.
| Field | Type |
|---|---|
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.
Version 1 · Resource outbound_hold · data requires outbound_holds:read
Dato sensible (health data): always delivered as the notice, without data, whatever the subscription asked for.
Fires when. The safety-hold SLA sweep (5-min tick) finds a hold still in active past its ack target. Fires once per hold.
| Field | Type |
|---|---|
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.
Version 1 · Resource outbound_hold · data requires outbound_holds:read
Dato sensible (health data): always delivered as the notice, without data, whatever the subscription asked for.
Fires when. 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.
| Field | Type |
|---|---|
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.
Version 1 · Resource lead · data requires leads:read
Fires when. The Chileautos leads webhook (POST /integrations/chileautos/leads) is processed by the worker.
| Field | Type |
|---|---|
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.
Version 1 · Resource lead · data requires leads:read
Fires when. The per-account yapo leads webhook (POST /integrations/yapo/leads/:accountId/:token) is processed by the worker.
| Field | Type |
|---|---|
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.
Version 1 · Resource lead · data requires leads:read
Fires when. POST /api/v1/website-leads (Bearer api_key, scope leads:intake) is processed.
| Field | Type |
|---|---|
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.
Version 1 · Resource consignment_candidate · data requires consignments:read
Fires when. POST /api/v1/widget/consignments (publishable pk_ key, origin-locked, scope leads:intake) is processed. A honeypot-tripped or duplicate submission fires nothing.
| Field | Type |
|---|---|
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.
Version 1 · Resource quote · data requires quotes:read
Fires when. POST /api/v1/quotes.
| Field | Type |
|---|---|
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.
Version 1 · Resource quote · data requires quotes:read
Fires when. POST /api/v1/quotes/{id}/void.
| Field | Type |
|---|---|
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.
Version 1 · Resource quote · data requires quotes:read
Fires when. POST /api/v1/quotes/{id}/extend.
| Field | Type |
|---|---|
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.
Version 1 · Resource quote · data requires quotes:read
Fires when. POST /api/v1/quotes/{id}/archive.
| Field | Type |
|---|---|
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.
Version 1 · Resource reservation · data requires reservations:read
Fires when. POST /api/v1/reservations.
| Field | Type |
|---|---|
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.
Version 1 · Resource reservation · data requires reservations:read
Fires when. POST /api/v1/reservations/{id}/void.
| Field | Type |
|---|---|
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.
Version 1 · Resource reservation · data requires reservations:read
Fires when. POST /api/v1/reservations/{id}/abono-disposition.
| Field | Type |
|---|---|
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.
Version 1 · Resource reservation · data requires reservations:read
Fires when. POST /api/v1/reservations/{id}/convert.
| Field | Type |
|---|---|
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.
Version 1 · Resource sale_note · data requires sale_notes:read
Fires when. POST /api/v1/sale-notes.
| Field | Type |
|---|---|
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.
Version 1 · Resource sale_note · data requires sale_notes:read
Fires when. POST /api/v1/sale-notes/{id}/approve.
| Field | Type |
|---|---|
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.
Version 1 · Resource sale_note · data requires sale_notes:read
Fires when. POST /api/v1/sale-notes/{id}/void.
| Field | Type |
|---|---|
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.
Version 1 · Resource purchase_note · data requires purchase_notes:read
Fires when. POST /api/v1/purchase-notes.
| Field | Type |
|---|---|
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.
Version 1 · Resource purchase_note · data requires purchase_notes:read
Fires when. PATCH /api/v1/purchase-notes/{id}.
| Field | Type |
|---|---|
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.
Version 1 · Resource purchase_note · data requires purchase_notes:read
Fires when. POST /api/v1/purchase-notes/{id}/void.
| Field | Type |
|---|---|
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.
Version 1 · Resource document_payment · data requires document_payments:read
Fires when. POST /api/v1/document-payments (deprecated door) or POST /api/v1/payments.
| Field | Type |
|---|---|
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.
Version 1 · Resource consignment_contract · data requires consignments:read
Fires when. POST /api/v1/consignments or POST /api/v1/consignments/bulk.
| Field | Type |
|---|---|
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.
Version 1 · Resource consignment_contract · data requires consignments:read
Fires when. POST /api/v1/consignments/{id}/return or PATCH .../{id} with estado: "devuelto".
| Field | Type |
|---|---|
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.
Version 1 · Resource consignment_contract · data requires consignments:read
Fires when. POST /api/v1/consignments/{id}/sell.
| Field | Type |
|---|---|
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.
Version 1 · Resource credit_application · data requires credit_applications:read
Fires when. POST /api/v1/credit-applications.
| Field | Type |
|---|---|
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.
Version 1 · Resource credit_application · data requires credit_applications:read
Fires when. POST /api/v1/credit-applications/{id}/submission.
| Field | Type |
|---|---|
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.
Version 1 · Resource credit_application · data requires credit_applications:read
Fires when. POST /api/v1/credit-applications/{id}/decision.
| Field | Type |
|---|---|
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.
Version 1 · Resource credit_application · data requires credit_applications:read
Fires when. POST /api/v1/credit-applications/{id}/withdrawal.
| Field | Type |
|---|---|
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.
Version 1 · Resource price_approval · data requires price_approval:read
Fires when. POST /api/v1/price-approvals.
| Field | Type |
|---|---|
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.
Version 1 · Resource price_approval · data requires price_approval:read
Fires when. POST /api/v1/price-approvals/{id}/decision.
| Field | Type |
|---|---|
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.
Version 1 · Resource conversation · data requires conversations:read
Fires when. 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.
| Field | Type |
|---|---|
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.
Version 1 · Resource conversation · data requires conversations:read
Fires when. 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).
| Field | Type |
|---|---|
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).
Version 1 · Resource conversation · data requires conversations:read
Fires when. 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.
| Field | Type |
|---|---|
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.
Version 1 · Resource message · data requires conversations:read
Fires when. 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.
| Field | Type |
|---|---|
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.
Version 1 · Resource message · data requires conversations:read
Fires when. 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.
| Field | Type |
|---|---|
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.
Version 1 · Resource ticket · data requires tickets:read
Fires when. POST /conversations/:id/tickets, the open_ticket / handoff tools, or a stale handoff being reopened with a brand-new ticket.
| Field | Type |
|---|---|
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.
Version 1 · Resource ticket · data requires tickets:read
Fires when. reopenTicket() in the ticket service — typically triggered by new inbound activity on a stale handoff.
| Field | Type |
|---|---|
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.
Version 1 · Resource ticket · data requires tickets:read
Fires when. 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.
| Field | Type |
|---|---|
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).
Version 1 · Resource ticket · data requires tickets:read
Fires when. A conversation is closed (auto-close worker after the grace window, or a manual close) and no other open conversation holds the ticket.
| Field | Type |
|---|---|
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).
Version 1 · Resource ticket · data requires tickets:read
Fires when. The SLA monitor worker (10-min tick) detects a newly-breached open ticket. Business hours are honoured when the policy is business_hours_only.
| Field | Type |
|---|---|
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.
Version 1 · Resource agent_scenario_run · data requires ai_agents:read
Fires when. The scenario-run worker finishes a run queued by POST /ai-agents/:id/scenarios/run, a single-scenario run, or a publish.
| Field | Type |
|---|---|
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).
Version 1 · Resource agent_scenario_suite_run · data requires ai_agents:read
Fires when. 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.
| Field | Type |
|---|---|
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.
Version 1 · Resource ai_agent · data requires ai_agents:read
Fires when. POST /ai-agents/:id/publish.
| Field | Type |
|---|---|
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.
Version 1 · Resource ai_agent_graph · data requires ai_agents:read
Fires when. 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.
| Field | Type |
|---|---|
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.
Version 1 · Resource workspace · data requires billing:read
Fires when. 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).
| Field | Type |
|---|---|
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.
Version 1 · Resource ai_agent_graph · data requires ai_agents:read
Fires when. The graph runtime refuses to go further on one of four ceilings: recursion depth, fan-out width, per-node turns, or evaluator-loop iterations.
| Field | Type |
|---|---|
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.
Version 1 · Resource kb_source · data requires kb:read
Fires when. POST /kb/sources.
| Field | Type |
|---|---|
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.
Version 1 · Resource kb_source · data requires kb:read
Fires when. POST /kb/sources/:id/embed (manual) or the kb-ingestion worker after content changes.
| Field | Type |
|---|---|
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.
Version 1 · Resource kb_source · data requires kb:read
Fires when. PUT /kb/sources/:id.
| Field | Type |
|---|---|
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.
Version 1 · Resource kb_source · data requires kb:read
Fires when. DELETE /kb/sources/:id.
| Field | Type |
|---|---|
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.
Version 1 · Resource kb_file · data requires kb:read
Fires when. POST /kb-files (multipart upload).
| Field | Type |
|---|---|
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).
Version 1 · Resource kb_file · data requires kb:read
Fires when. DELETE /kb-files/:id.
| Field | Type |
|---|---|
kb_file_id | string (uuid) |
{
"kb_file_id": "a6a6a6a6-0000-4000-8000-000000000001"
}skill.created
A new skill was added to the tenant library.
Version 1 · Resource skill · data requires ai_agents:read
Fires when. POST /skills.
| Field | Type |
|---|---|
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.
Version 1 · Resource skill · data requires ai_agents:read
Fires when. PUT /skills/:id.
| Field | Type |
|---|---|
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.
Version 1 · Resource skill · data requires ai_agents:read
Fires when. DELETE /skills/:id.
| Field | Type |
|---|---|
skill_id | string (uuid) |
{
"skill_id": "a4a4a4a4-0000-4000-8000-000000000001"
}custom_tool.created
A new custom tool was added to the Tool Store.
Version 1 · Resource custom_tool · data requires functions:read
Fires when. POST /custom-tools.
| Field | Type |
|---|---|
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.
Version 1 · Resource custom_tool · data requires functions:read
Fires when. PUT or PATCH /custom-tools/:id.
| Field | Type |
|---|---|
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.
Version 1 · Resource custom_tool · data requires functions:read
Fires when. DELETE /custom-tools/:id.
| Field | Type |
|---|---|
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.
Version 1 · Resource lead · data requires leads:read
Fires when. POST /leads, POST /leads/from-conversation, POST /leads/import, the create_lead AI tool, or a marketplace / dealer-website ingest.
| Field | Type |
|---|---|
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.
Version 1 · Resource lead · data requires leads:read
Fires when. 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.
| Field | Type |
|---|---|
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.
Version 1 · Resource lead · data requires leads:read
Fires when. 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.
| Field | Type |
|---|---|
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.
Version 1 · Resource lead · data requires leads:read
Fires when. PUT /leads/:id/assign, PUT /leads/:id with owner_user_id, or the team rotation picking an owner on create.
| Field | Type |
|---|---|
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.
Version 1 · Resource lead · data requires leads:read
Fires when. 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.
| Field | Type |
|---|---|
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.
Version 1 · Resource lead · data requires leads:read
Fires when. PUT /leads/:id/lost, or any move onto a stage whose category is lost. Status flip only, as for lead.won.
| Field | Type |
|---|---|
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.
Version 1 · Resource lead · data requires leads:read
Fires when. PUT /leads/:id/unqualify, or any move onto a stage whose category is unqualified. Status flip only, as for lead.won.
| Field | Type |
|---|---|
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.
Version 1 · Resource conversation · data requires conversations:read
Fires when. 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.
| Field | Type |
|---|---|
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.
Version 1 · Resource marketplace_integration · data requires marketplace:read
Fires when. POST /api/v1/vehicles/source-retirement/execute completes (once per retired source).
| Field | Type |
|---|---|
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.
Version 1 · Resource marketplace_integration · data requires marketplace:read
Fires when. syncStock() completes successfully (manual POST /marketplaces/:id/sync or the scheduled tick).
| Field | Type |
|---|---|
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.
Version 1 · Resource vehicle · data requires stock:read
Fires when. publishVehicle() succeeds for a portal via POST /vehicles/:id/publish.
| Field | Type |
|---|---|
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.
Version 1 · Resource vehicle · data requires stock:read
Fires when. 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).
| Field | Type |
|---|---|
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.
Version 1 · Resource vehicle · data requires stock:read
Fires when. 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.
| Field | Type |
|---|---|
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.
Version 1 · Resource vehicle · data requires stock:read
Fires when. 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.
| Field | Type |
|---|---|
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.
Version 1 · Resource vehicle_attachment · data requires vehicle_registry:read
Fires when. 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.
| Field | Type |
|---|---|
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.
Version 1 · Resource marketplace_integration · data requires marketplace:read
Fires when. 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.
| Field | Type |
|---|---|
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.
Version 1 · Resource marketplace_integration · data requires marketplace:read
Fires when. 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.
| Field | Type |
|---|---|
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.
Version 1 · Resource appointment · data requires appointments:read
Fires when. 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).
| Field | Type |
|---|---|
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.
Version 1 · Resource appointment · data requires appointments:read
Fires when. 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.
| Field | Type |
|---|---|
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.
Version 1 · Resource appointment · data requires appointments:read
Fires when. 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.
| Field | Type |
|---|---|
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.
Version 1 · Resource appointment · data requires appointments:read
Fires when. 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.
| Field | Type |
|---|---|
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.
Version 1 · Resource appointment · data requires appointments:read
Fires when. 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.
| Field | Type |
|---|---|
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.
Version 1 · Resource appointment · data requires appointments:read
Fires when. The delayed reminder job fires, reminder_lead_minutes before the appointment (GET /appointments/config).
| Field | Type |
|---|---|
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.
Version 1 · Resource appointment · data requires appointments:read
Fires when. Incremental Google Calendar sync inserts a new untagged event.
| Field | Type |
|---|---|
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.
Version 1 · Resource contact · data requires contacts:read
Fires when. 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.
| Field | Type |
|---|---|
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.
Version 1 · Resource contact · data requires contacts:read
Fires when. 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.
| Field | Type |
|---|---|
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.
Version 1 · Resource contact · data requires contacts:read
Fires when. 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.
| Field | Type |
|---|---|
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.
Version 1 · Resource team · data requires teams:read
Fires when. POST /teams.
| Field | Type |
|---|---|
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.
Version 1 · Resource team · data requires teams:read
Fires when. PUT /teams/{id}.
| Field | Type |
|---|---|
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.
Version 1 · Resource team · data requires teams:read
Fires when. DELETE /teams/{id}.
| Field | Type |
|---|---|
id | string (uuid) |
name | string |
{
"id": "cccccccc-0000-4000-8000-000000000001",
"name": "Ventas Providencia"
}custom_role.created
A custom role was created.
Version 1 · Resource custom_role · data requires roles:read
Fires when. POST /roles.
| Field | Type |
|---|---|
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.
Version 1 · Resource custom_role · data requires roles:read
Fires when. PUT /roles/{id}.
| Field | Type |
|---|---|
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).
Version 1 · Resource custom_role · data requires roles:read
Fires when. DELETE /roles/{id}.
| Field | Type |
|---|---|
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.
Version 1 · Resource macro · data requires macros:read
Fires when. POST /macros.
| Field | Type |
|---|---|
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.
Version 1 · Resource clinic_professional · data requires clinic:read
Fires when. POST /clinic/professionals succeeds (native clinics only).
| Field | Type |
|---|---|
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.
Version 1 · Resource clinic_professional · data requires clinic:read
Fires when. PATCH /clinic/professionals/{id} succeeds.
| Field | Type |
|---|---|
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.
Version 1 · Resource clinic_professional · data requires clinic:read
Fires when. DELETE /clinic/professionals/{id} succeeds.
| Field | Type |
|---|---|
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.
Version 1 · Resource clinic_service · data requires clinic:read
Fires when. POST /clinic/services succeeds (native clinics only).
| Field | Type |
|---|---|
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.
Version 1 · Resource macro · data requires macros:read
Fires when. PUT /macros/{id}.
| Field | Type |
|---|---|
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.
Version 1 · Resource macro · data requires macros:read
Fires when. DELETE /macros/{id}.
| Field | Type |
|---|---|
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.
Version 1 · Resource assignment_rule · data requires routing:read
Fires when. POST /assignment-rules.
| Field | Type |
|---|---|
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.)
Version 1 · Resource assignment_rule · data requires routing:read
Fires when. PATCH /assignment-rules/{id}.
| Field | Type |
|---|---|
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.
Version 1 · Resource assignment_rule · data requires routing:read
Fires when. DELETE /assignment-rules/{id}.
| Field | Type |
|---|---|
id | string (uuid) |
name | string |
{
"id": "d5d5d5d5-0000-4000-8000-000000000001",
"name": "Mercado Libre → equipo Providencia"
}sla_policy.created
An SLA policy was created.
Version 1 · Resource sla_policy · data requires slas:read
Fires when. POST /slas.
| Field | Type |
|---|---|
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.
Version 1 · Resource sla_policy · data requires slas:read
Fires when. PUT /slas/{id}.
| Field | Type |
|---|---|
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.
Version 1 · Resource sla_policy · data requires slas:read
Fires when. DELETE /slas/{id}.
| Field | Type |
|---|---|
id | string (uuid) |
name | string |
{
"id": "d7d7d7d7-0000-4000-8000-000000000001",
"name": "Soporte estándar"
}automation_trigger.created
An automation trigger was created.
Version 1 · Resource automation_trigger · data requires triggers:read
Fires when. POST /triggers.
| Field | Type |
|---|---|
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.
Version 1 · Resource automation_trigger · data requires triggers:read
Fires when. PUT /triggers/{id}.
| Field | Type |
|---|---|
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.
Version 1 · Resource automation_trigger · data requires triggers:read
Fires when. DELETE /triggers/{id}.
| Field | Type |
|---|---|
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.
Version 1 · Resource sending_domain · data requires messaging_accounts:read
Fires when. POST /sending-domains.
| Field | Type |
|---|---|
id | string (uuid) |
domain | string |
{
"id": "d9d9d9d9-0000-4000-8000-000000000001",
"domain": "mail.autosdelvalle.cl"
}sending_domain.deleted
A sending domain was removed.
Version 1 · Resource sending_domain · data requires messaging_accounts:read
Fires when. DELETE /sending-domains/{id}.
| Field | Type |
|---|---|
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.
Version 1 · Resource whatsapp_template · data requires campaigns:read
Fires when. POST /whatsapp-templates.
| Field | Type |
|---|---|
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.
Version 1 · Resource whatsapp_template · data requires campaigns:read
Fires when. The template-sync worker, or POST /whatsapp-templates/sync, sees a status change.
| Field | Type |
|---|---|
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.
Version 1 · Resource whatsapp_template · data requires campaigns:read
Fires when. DELETE /whatsapp-templates/{id}.
| Field | Type |
|---|---|
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.
Version 1 · Resource whatsapp_flow · data requires campaigns:read
Fires when. POST /whatsapp-flows/managed.
| Field | Type |
|---|---|
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.
Version 1 · Resource whatsapp_flow · data requires campaigns:read
Fires when. POST /whatsapp-flows/managed/{id}/publish.
| Field | Type |
|---|---|
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.
Version 1 · Resource whatsapp_flow · data requires campaigns:read
Fires when. POST /whatsapp-flows/managed/{id}/deprecate.
| Field | Type |
|---|---|
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).
Version 1 · Resource whatsapp_flow · data requires campaigns:read
Fires when. DELETE /whatsapp-flows/managed/{id}.
| Field | Type |
|---|---|
id | string (uuid) |
name | string |
{
"id": "e3e3e3e3-0000-4000-8000-000000000001",
"name": "Agenda tu visita"
}channel.connected
A messaging channel finished connecting.
Version 1 · Resource channel · data requires messaging_accounts:read
Fires when. POST /messaging-accounts, or a provider OAuth/Atribu callback completing.
| Field | Type |
|---|---|
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).
Version 1 · Resource channel · data requires messaging_accounts:read
Fires when. PUT /messaging-accounts/{id}.
| Field | Type |
|---|---|
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.
Version 1 · Resource channel · data requires messaging_accounts:read
Fires when. DELETE /messaging-accounts/{id}.
| Field | Type |
|---|---|
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.
Version 1 · Resource membership · data requires memberships:read
Fires when. POST /memberships, or POST /memberships/invitations/by-token/{token}/accept.
| Field | Type |
|---|---|
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.
Version 1 · Resource membership · data requires memberships:read
Fires when. PUT /memberships/{id}.
| Field | Type |
|---|---|
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.
Version 1 · Resource membership · data requires memberships:read
Fires when. DELETE /memberships/{id}.
| Field | Type |
|---|---|
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.
Version 1 · Resource clinic_service · data requires clinic:read
Fires when. PATCH /clinic/services/{id} succeeds.
| Field | Type |
|---|---|
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.
Version 1 · Resource clinic_service · data requires clinic:read
Fires when. DELETE /clinic/services/{id} succeeds.
| Field | Type |
|---|---|
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.
Version 1 · Resource clinic_price_list · data requires clinic:read
Fires when. PATCH /clinic/pricing/lists/{id} or PUT /clinic/pricing/lists/{id}/entries succeeds.
| Field | Type |
|---|---|
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.
Version 1 · Resource clinic_pack_purchase · data requires clinic:read
Fires when. POST /clinic/packs/purchases succeeds.
| Field | Type |
|---|---|
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.
Version 1 · Resource clinic_pack_purchase · data requires clinic:read
Fires when. POST /clinic/packs/purchases/{id}/consume succeeds, or a cita consumes a session automatically.
| Field | Type |
|---|---|
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.
Version 1 · Resource clinic_pack_purchase · data requires clinic:read
Fires when. POST /clinic/packs/purchases/{id}/cancel succeeds.
| Field | Type |
|---|---|
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.
Version 1 · Resource clinic_budget · data requires clinic_money:read
Fires when. POST /clinic/budgets succeeds.
| Field | Type |
|---|---|
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.
Version 1 · Resource clinic_budget · data requires clinic_money:read
Fires when. POST /clinic/budgets/{id}/send succeeds.
| Field | Type |
|---|---|
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.
Version 1 · Resource clinic_budget · data requires clinic_money:read
Fires when. POST /clinic/budgets/{id}/accept succeeds, or the patient accepts from their own link.
| Field | Type |
|---|---|
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.
Version 1 · Resource clinic_budget · data requires clinic_money:read
Fires when. POST /clinic/budgets/{id}/reject succeeds, or the patient declines from their link.
| Field | Type |
|---|---|
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.
Version 1 · Resource clinic_budget · data requires clinic_money:read
Fires when. POST /clinic/budgets/{id}/void succeeds.
| Field | Type |
|---|---|
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.
Version 1 · Resource clinic_budget · data requires clinic_money:read
Fires when. The drift sweep after a mirror sync finds the live plan different from the snapshot.
| Field | Type |
|---|---|
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.
Version 1 · Resource clinic_cash_session · data requires clinic_money:read
Fires when. POST /clinic/cash-sessions succeeds.
| Field | Type |
|---|---|
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.
Version 1 · Resource clinic_cash_session · data requires clinic_money:read
Fires when. POST /clinic/cash-sessions/{id}/close succeeds.
| Field | Type |
|---|---|
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.
Version 1 · Resource clinic_patient · data requires clinic_patients:read
Dato sensible (health data): always delivered as the notice, without data, whatever the subscription asked for.
Fires when. POST /clinic/patients succeeds.
| Field | Type |
|---|---|
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.
Version 1 · Resource clinic_patient · data requires clinic_patients:read
Dato sensible (health data): always delivered as the notice, without data, whatever the subscription asked for.
Fires when. PATCH /clinic/patients/{id} succeeds.
| Field | Type |
|---|---|
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.
Version 1 · Resource clinic_patient · data requires clinic_patients:read
Dato sensible (health data): always delivered as the notice, without data, whatever the subscription asked for.
Fires when. DELETE /clinic/patients/{id} succeeds.
| Field | Type |
|---|---|
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.
Version 1 · Resource clinic_patient · data requires clinic_patients:read
Dato sensible (health data): always delivered as the notice, without data, whatever the subscription asked for.
Fires when. POST /clinic/patients/{id}/merge succeeds.
| Field | Type |
|---|---|
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.
Version 1 · Resource clinic_encounter · data requires clinic_record:read
Dato sensible (health data): always delivered as the notice, without data, whatever the subscription asked for.
Fires when. POST /clinic/patients/{id}/encounters succeeds.
| Field | Type |
|---|---|
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.
Version 1 · Resource clinic_encounter · data requires clinic_record:read
Dato sensible (health data): always delivered as the notice, without data, whatever the subscription asked for.
Fires when. POST /clinic/encounters/{id}/close succeeds.
| Field | Type |
|---|---|
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.
Version 1 · Resource clinic_note · data requires clinic_record:read
Dato sensible (health data): always delivered as the notice, without data, whatever the subscription asked for.
Fires when. POST /clinic/encounters/{id}/notes with sign: true, or POST /clinic/notes/{id}/sign.
| Field | Type |
|---|---|
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.
Version 1 · Resource clinic_note · data requires clinic_record:read
Dato sensible (health data): always delivered as the notice, without data, whatever the subscription asked for.
Fires when. POST /clinic/notes/{id}/amend succeeds.
| Field | Type |
|---|---|
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.
Version 1 · Resource clinic_consent · data requires clinic_record:read
Dato sensible (health data): always delivered as the notice, without data, whatever the subscription asked for.
Fires when. POST /clinic/consents/{id}/sign succeeds, or the patient signs from their own link.
| Field | Type |
|---|---|
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.
Version 1 · Resource clinic_consent · data requires clinic_record:read
Dato sensible (health data): always delivered as the notice, without data, whatever the subscription asked for.
Fires when. POST /clinic/consents/{id}/revoke succeeds.
| Field | Type |
|---|---|
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.
Version 1 · Resource clinic_document · data requires clinic_record:read
Dato sensible (health data): always delivered as the notice, without data, whatever the subscription asked for.
Fires when. POST /clinic/patients/{id}/documents succeeds.
| Field | Type |
|---|---|
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.
Version 1 · Resource clinic_document · data requires clinic_record:read
Dato sensible (health data): always delivered as the notice, without data, whatever the subscription asked for.
Fires when. POST /clinic/documents/{id}/delete succeeds, or retention swept it.
| Field | Type |
|---|---|
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.
Version 1 · Resource clinic_privacy_request · data requires clinic_record:read
Dato sensible (health data): always delivered as the notice, without data, whatever the subscription asked for.
Fires when. The export archive is written, or the erasure completes.
| Field | Type |
|---|---|
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"
}