VitrinaAPI

Channels

A messaging channel read + disconnect — connecting one stays a browser flow.

Beta
This may change at any time, with a changelog entry and a notice to recent callers — see versioning.

Download the full API pública projection: openapi.json.

Check and disconnect channels explains this resource in prose, with runnable examples.

MethodPathWhat it does
GET/messaging-accountsList channels
DELETE/messaging-accounts/{id}Disconnect a channel
GET/messaging-accounts/{id}Fetch one channel
GET/messaging-accounts/{id}/ai-availabilityRead the channel AI availability policy and current status
PATCH/messaging-accounts/{id}/ai-availabilitySet the channel AI availability policy
GET/messaging-accounts/{id}/healthLive channel identity & health
GET/messaging-accounts/agent-optionsList the agents a new channel can be answered by
GET/messaging-accounts/statsPer-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.

ParameterInTypeRequiredConstraints
idpathuuidyes
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.

ParameterInTypeRequiredConstraints
idpathuuidyes
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.

ParameterInTypeRequiredConstraints
idpathuuidyes
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.

ParameterInTypeRequiredConstraints
idpathuuidyes

Body

FieldTypeRequiredConstraints
modealways \outside_business_hours \custom
resume_human_conversationsbooleanyes
business_hours_sourceworkspace \teamyes
team_idstring | nullyes
timezonestring—mín. 1, máx. 100, por defecto "America/Santiago"
weekly_windowsobject[]yes
human_silence_resumeobject—
handoff_renotifyobject—
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.

ParameterInTypeRequiredConstraints
idpathuuidyes
refreshquerytrue \falseno
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

On this page