Channels
A messaging channel read + disconnect — connecting one stays a browser flow.
Download the full API pública projection: openapi.json.
Check and disconnect channels explains this resource in prose, with runnable examples.
| Method | Path | What it does |
|---|---|---|
GET | /messaging-accounts | List channels |
DELETE | /messaging-accounts/{id} | Disconnect a channel |
GET | /messaging-accounts/{id} | Fetch one channel |
GET | /messaging-accounts/{id}/ai-availability | Read the channel AI availability policy and current status |
PATCH | /messaging-accounts/{id}/ai-availability | Set the channel AI availability policy |
GET | /messaging-accounts/{id}/health | Live channel identity & health |
GET | /messaging-accounts/agent-options | List the agents a new channel can be answered by |
GET | /messaging-accounts/stats | Per-account traffic stats |
GET /messaging-accounts
List channels
Every messaging account in the workspace, secrets redacted. Connecting a new one is a browser flow (OAuth, a partner connection or IMAP) — see the MessagingAccounts reference — but everything already connected is readable here.
curl https://api.vitrinadev.com/api/v1/messaging-accounts \
-H "Authorization: Bearer $VITRINA_KEY"Example response (200)
{
"data": [
{
"id": "e4e4e4e4-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"name": "WhatsApp Providencia",
"kind": "whatsapp_cloud",
"channels": [
"whatsapp"
],
"config": {
"connected_via": "atribu_partner"
},
"secrets": {
"atribu_access_token": "<redacted>"
},
"enabled": true,
"default_team_id": "cccccccc-0000-4000-8000-000000000001",
"assignment_mode": "auto_round_robin",
"assignee_user_ids": [],
"assignee_weights": {},
"assignee_ai_agent_id": null,
"help_center_id": null,
"public_number_id": null,
"display_phone": null,
"voice_attachable": false,
"voice_attach_source": null,
"created_at": "2026-01-10T13:00:00.000Z",
"updated_at": "2026-09-10T13:00:00.000Z"
}
],
"meta": {
"total": 1
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429
DELETE /messaging-accounts/{id}
Disconnect a channel
Tears down the provider-side connection first (best-effort, a no-op for a non-partner kind — so a fan-out subscription never keeps delivering to a connection the workspace already deleted locally), then removes the account row. Every conversation, message and lead the channel already produced is untouched: this ends the CONNECTION, not the history.
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
id | path | uuid | yes |
curl -X DELETE https://api.vitrinadev.com/api/v1/messaging-accounts/<id> \
-H "Authorization: Bearer $VITRINA_KEY"Answers: 204 · 400 · 401 · 403 · 404 · 409 · 429
GET /messaging-accounts/{id}
Fetch one channel
The account row, secrets redacted.
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
id | path | uuid | yes |
curl https://api.vitrinadev.com/api/v1/messaging-accounts/<id> \
-H "Authorization: Bearer $VITRINA_KEY"Example response (200)
{
"data": {
"id": "e4e4e4e4-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"name": "WhatsApp Providencia",
"kind": "whatsapp_cloud",
"channels": [
"whatsapp"
],
"config": {
"connected_via": "atribu_partner"
},
"secrets": {
"atribu_access_token": "<redacted>"
},
"enabled": true,
"default_team_id": "cccccccc-0000-4000-8000-000000000001",
"assignment_mode": "auto_round_robin",
"assignee_user_ids": [],
"assignee_weights": {},
"assignee_ai_agent_id": null,
"help_center_id": null,
"public_number_id": null,
"display_phone": null,
"voice_attachable": false,
"voice_attach_source": null,
"created_at": "2026-01-10T13:00:00.000Z",
"updated_at": "2026-09-10T13:00:00.000Z"
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429
GET /messaging-accounts/{id}/ai-availability
Read the channel AI availability policy and current status
Returns the per-channel coverage clock and its current evaluation. Schedule availability does not override agent assignment, channel kill switches, or a human takeover. An account without a saved policy reports configured: false and preserves the legacy always-eligible schedule. policy.timezone and status.effective_timezone report the server-resolved clock inherited from General settings or selected business hours. policy.human_silence_resume and policy.handoff_renotify are always present in the response with defaults applied (resume: enabled, 30 minutes; re-notify: disabled, 30 minutes once enabled). human_silence_resume is RETIRED: since 2026-10-25 a conversation a person handles is silent until «Devolver a la IA», so the channel switch no longer makes the AI answer. Whether the AI answers a waiting customer is the workspace setting after_handoff.ai_answers_waiting (GET /followups/recontacto), for every tenant.
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
id | path | uuid | yes |
curl https://api.vitrinadev.com/api/v1/messaging-accounts/<id>/ai-availability \
-H "Authorization: Bearer $VITRINA_KEY"Example response (200)
{
"data": {
"policy": {
"mode": "outside_business_hours",
"resume_human_conversations": false,
"business_hours_source": "workspace",
"team_id": null,
"weekly_windows": [],
"human_silence_resume": {
"enabled": true,
"minutes": 30
},
"handoff_renotify": {
"enabled": false,
"minutes": 30
},
"timezone": "America/Santiago"
},
"status": {
"configured": true,
"available": false,
"reason": "inside_business_hours",
"window_start": "2026-09-15T12:00:00.000Z",
"window_end": "2026-09-15T22:00:00.000Z",
"next_transition": "2026-09-15T22:00:00.000Z",
"effective_timezone": "America/Santiago",
"policy_revision": "sha256:<revision>"
}
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429
PATCH /messaging-accounts/{id}/ai-availability
Set the channel AI availability policy
Persists an always, outside-business-hours, or custom weekly schedule. Custom windows may cross midnight; overlapping windows are rejected. Custom schedules inherit General timezone, then workspace business-hours timezone, then America/Santiago. The optional legacy timezone field is accepted for compatibility and normalized by the server. Outside-business-hours mode requires configured workspace or tenant-owned team hours. The follow-through rules are optional: human_silence_resume (retired: accepted and stored for compatibility, it does nothing; use after_handoff.ai_answers_waiting on PUT /followups/recontacto) and handoff_renotify (after an AI handoff, re-notify the team every N minutes while the customer waits). Omitting either keeps the value already saved on the channel.
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
id | path | uuid | yes |
Body
| Field | Type | Required | Constraints |
|---|---|---|---|
mode | always \ | outside_business_hours \ | custom |
resume_human_conversations | boolean | yes | |
business_hours_source | workspace \ | team | yes |
team_id | string | null | yes | |
timezone | string | — | mín. 1, máx. 100, por defecto "America/Santiago" |
weekly_windows | object[] | yes | |
human_silence_resume | object | — | |
handoff_renotify | object | — |
curl -X PATCH https://api.vitrinadev.com/api/v1/messaging-accounts/<id>/ai-availability \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"mode": "outside_business_hours",
"resume_human_conversations": false,
"business_hours_source": "workspace",
"team_id": null,
"weekly_windows": [],
"human_silence_resume": {
"enabled": true,
"minutes": 30
},
"handoff_renotify": {
"enabled": false,
"minutes": 30
}
}'Example response (200)
{
"data": {
"policy": {
"mode": "outside_business_hours",
"resume_human_conversations": false,
"business_hours_source": "workspace",
"team_id": null,
"weekly_windows": [],
"human_silence_resume": {
"enabled": true,
"minutes": 30
},
"handoff_renotify": {
"enabled": false,
"minutes": 30
},
"timezone": "America/Santiago"
},
"status": {
"configured": true,
"available": false,
"reason": "inside_business_hours",
"window_start": "2026-09-15T12:00:00.000Z",
"window_end": "2026-09-15T22:00:00.000Z",
"next_transition": "2026-09-15T22:00:00.000Z",
"effective_timezone": "America/Santiago",
"policy_revision": "sha256:<revision>"
}
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429
GET /messaging-accounts/{id}/health
Live channel identity & health
Dials the provider (or the partner connection provider, for a partner-connected account) right now — the connected phone/IG account/mailbox identity, webhook delivery state, and WhatsApp account health (quality rating, messaging limit) where applicable. stored rides along: the last health snapshot the periodic checker wrote, for comparing a live read against what was already known. stored.reasonCode: send_refused with stored.aiRepliesPaused: true means Meta refuses every send from the number (replies included), so its AI is paused until one send gets through or the channel is reconnected. ?refresh=true forces a fresh probe instead of the provider’s own cache. A live check that itself fails (provider down, expired token) still answers 200, with a fallback identity/webhook reporting the failure — this call never breaks a health dashboard by 500ing.
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
id | path | uuid | yes | |
refresh | query | true \ | false | no |
curl https://api.vitrinadev.com/api/v1/messaging-accounts/<id>/health \
-H "Authorization: Bearer $VITRINA_KEY"Example response (200)
{
"data": {
"channel": "whatsapp",
"kind": "whatsapp",
"connected_via": "atribu_partner",
"identity": {
"phone_number": "+56912345000",
"display_name": "Autos del Valle"
},
"webhook": {
"status": "delivering",
"last_event_at": "2026-09-15T18:20:00.000Z"
},
"whatsapp": {
"quality_rating": "GREEN",
"messaging_limit": "TIER_10K"
},
"stored": {
"status": "healthy",
"reason": null,
"reasonCode": null,
"lastCheckedAt": "2026-09-15T18:00:00.000Z",
"lastOkAt": "2026-09-15T18:00:00.000Z",
"unhealthySince": null,
"consecutiveFailures": 0,
"updatedAt": "2026-09-15T18:00:00.000Z",
"aiRepliesPaused": false
}
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429
GET /messaging-accounts/agent-options
List the agents a new channel can be answered by
The workspace's active AI agents, most-used first (agent runs in the last 90 days), with the preselected one. When a channel is connected without choosing an agent, a workspace with exactly one active agent gets it pinned automatically on WhatsApp, Instagram, Messenger, TikTok, web chat and email; with two or more, choice_required is true and the agent must be set on the new channel (assignee_ai_agent_id). On email, the pinned agent only ever answers mail classified as a customer.
curl https://api.vitrinadev.com/api/v1/messaging-accounts/agent-options \
-H "Authorization: Bearer $VITRINA_KEY"Example response (200)
{
"data": {
"agents": [
{
"id": "a3a3a3a3-0000-4000-8000-000000000001",
"name": "Asistente de ventas",
"runs": 412
},
{
"id": "a3a3a3a3-0000-4000-8000-000000000002",
"name": "Asistente de postventa",
"runs": 37
}
],
"suggested_agent_id": "a3a3a3a3-0000-4000-8000-000000000001",
"choice_required": true
}
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429
GET /messaging-accounts/stats
Per-account traffic stats
One row per account that has traffic in the last 30 days — an idle account is absent rather than present with zeros, so join against GET /messaging-accounts for a complete list. Registered before /\{id\} on the router so stats is never read as an account id.
curl https://api.vitrinadev.com/api/v1/messaging-accounts/stats \
-H "Authorization: Bearer $VITRINA_KEY"Example response (200)
{
"data": [
{
"messaging_account_id": "e4e4e4e4-0000-4000-8000-000000000001",
"conversations_total": 128,
"messages_30d": 640,
"inbound_30d": 410,
"outbound_30d": 230,
"last_message_at": "2026-09-15T18:20:00.000Z"
}
]
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429
Campaigns
Bulk WhatsApp or email sends to an audience: draft, schedule, pause and recipients.
Clinic diagnóstico and commercial numbers
The clinic's free Diagnóstico in one read (status, day of the window and the plan band) and its commercial numbers: presupuesto acceptance, cierres and cash, always aggregated, never a patient.