---
title: "Envía un mensaje al contacto (como operador)"
method: POST
path: "/contacts/{contactId}/messages"
tags: ["Contacts"]
---

# Envía un mensaje al contacto (como operador)

`POST /contacts/{contactId}/messages`

## Request

- Base URL: `https://api.met.meteor.com.co/api/v1`
- URL: `https://api.met.meteor.com.co/api/v1/contacts/{contactId}/messages`
- Auth: one of:
  - `bearer` (a scheme the document does not define)
  - HTTP bearer

El mensaje **siempre** queda guardado en la conversación; `delivery.ok` dice si además salió por el canal. Un `2xx` con `delivery.ok: false` significa "quedó registrado pero no le llegó al contacto" — hay que mirarlo, no alcanza con el código HTTP.

Desde la bandeja (sesión del panel) el envío es una **intervención humana**: pausa el piloto automático por los minutos que tenga configurados el workspace (`paused_until` en la respuesta) y viaja con el tag HUMAN_AGENT en Messenger/IG.

Con **API key** el envío es automático: NO pausa el piloto y respeta la pausa vigente. Si el contacto está silenciado (piloto apagado, pausa vigente o chat en «Hablar con humano») devuelve `409 autopilot_silenced` y no se envía ni se guarda nada.

Pide el scope `channels:send`, no `contacts:write`. Una key de test (`met_test_`) no puede ejercerlo: devuelve `403 test_mode_restricted`.

## Path parameters

- `contactId` number, required

## Request body

- SendContactMessageDto
  - `attachments` ContactMessageAttachmentDto[]
    - `kind` string
    - `mime_type` string
    - `name` string
    - `size` number
    - `url` string, required
  - `client_message_id` string, uuid: Identidad estable de la intención de envío. La genera el navegador antes del primer POST y la conserva en cualquier reintento; así una respuesta HTTP perdida no produce un segundo mensaje para el contacto.
  - `content` string, required: Puede venir vacío cuando el mensaje es solo adjuntos (ej. nota de voz sin texto) — el "ni texto ni adjuntos" lo rechaza el servicio, no el DTO.
  - `line_id` string: Línea emisora, para el conmutador de la cabecera del chat. Sin esto el envío sale siempre por la línea del último entrante, que es lo correcto al responder. Pero con varias líneas conectadas un operador puede querer ESCRIBIR PRIMERO por otra —abrir la conversación desde el número de ventas cuando la persona venía hablando con soporte—, y esa intención no se puede deducir del historial: hay que declararla. Tiene que ser una línea del workspace; si no, el envío falla con detalle en vez de salir por un número equivocado (`strictLineId` del resolver).

## Response `201`

- object: Sobre en el que viaja toda respuesta correcta de la API. El recurso está en `data`. Los SDKs oficiales lo desenvuelven por ti; un cliente hecho a mano tiene que leer `.data`.
  - `success` true, required: Siempre `true` en una respuesta 2xx.
  - `data` SendMessageResponseDto, required
    - `deduplicated` boolean: `true` cuando el servidor reconoció un reintento por `client_message_id` y devolvió la fila original sin volver a entregarla ni pausar el piloto.
    - `delivery` MessageDeliveryDto, required
      - `error` string, nullable: Por qué falló la entrega. Solo viene con `ok: false`.
      - `ok` boolean, required: `false` no revierte nada: el mensaje ya quedó guardado en la conversación.
      - `reason` string, nullable
      - `skipped` boolean: `true` cuando el canal no requiere entrega externa (ej. chat web).
    - `message` ContactMessageDto, required
      - `agent_id` number, nullable: Met que produjo el mensaje, cuando `role` es `agent`.
      - `attachments` object[]: Adjuntos del mensaje (`{ url, mime_type, name, size, kind }`). Un audio transcrito trae además `transcript` con el texto; `content` sigue siendo `"[Audio]"`.
      - `channel_id` number, required
      - `client_message_id` string, uuid, nullable: Id estable generado por el navegador para reconciliar reintentos.
      - `contact_id` number, required
      - `content` string, required: Texto del mensaje. En mensajes de solo adjunto puede venir vacío.
      - `created_at` string, nullable: Cuándo se guardó el mensaje: ISO 8601 en UTC, siempre con `Z`.
      - `id` number, required
      - `message_type` string, required: Qué clase de mensaje es: `text`, `image`, `audio`, `video`, `document`, `location`, `template`, `note` (nota interna, nunca se envió al contacto), `system_event` o `tool_use`.
      - `metadata` object: Metadatos del mensaje. En `template` lleva `template.{name,language,params}`; en `tool_use` el JSON de la invocación; en los de WhatsApp el estado de entrega que reporta Meta.
      - `origin` 'contact' | 'met' | 'panel' | 'api' | 'mcp' | 'flow' | 'automation' | 'broadcast' | 'phone' | 'system' | 'unknown', required: `contact`: lo escribió el contacto. `met`: respuesta de un Met. `panel`: una persona desde la app de Meteor (bandeja, ficha, notas). `api`: una integración con API key o SDK. `mcp`: un asistente de IA conectado por MCP. `flow`: un flujo. `automation`: una automatización programada o disparada por una regla. `broadcast`: una difusión (envío masivo). `phone`: escrito desde el celular con WhatsApp Business (coexistencia). `system`: un evento de la conversación (asignación, cambio de estado, etiqueta). `unknown`: mensaje anterior al 25-sep-2026 cuyo origen no quedó registrado. Los mensajes enviados antes del 25-sep-2026 por una integración con API key aparecen como `panel`: en ese momento el origen no se registraba.
      - `role` 'user' | 'operator' | 'agent', required: Quién habló: el contacto (`user`), tu equipo (`operator`) o el Met (`agent`).
      - `sent_by_name` string, nullable: Nombre visible de `sent_by_user_id`, ya resuelto. `null` cuando no lo mandó una persona. **Solo viene en `GET /contacts/{id}/messages`**; el mensaje que devuelve un envío no lo trae.
      - `sent_by_user_id` number, nullable: Persona de tu equipo que lo envió, cuando `role` es `operator`.
      - `source` string, nullable: De dónde entró o salió el mensaje. `null` para los que no lo declaran.
    - `paused_until` string, nullable, required: Hasta cuándo queda pausado el piloto automático por esta intervención humana. `null` si el workspace tiene la pausa en 0 o si no se pudo aplicar.

## Other responses

- `400`: La request no es válida: un campo no pasa validación, falta un parámetro requerido o el recurso no admite la transición solicitada.
- `401`: API key ausente, inválida, revocada o expirada — o el workspace está bloqueado.
- `403`: La key está autenticada pero no puede ejecutar esta operación: scope faltante, capacidad deshabilitada, modo test restringido o recurso fuera de su cartera.
- `404`: El recurso no existe o no está disponible dentro del alcance de la key. No revela la existencia de recursos de otros tenants.
- `409`: La operación entra en conflicto con el estado actual o con un Idempotency-Key en uso. No reintentes con el mismo key y un body distinto.
- `429`: Se superó el límite por minuto de la key o la cuota del plan. Respeta `Retry-After`; el SDK ya reintenta solo.
- `500`: Error del servidor o del Met que ejecutaba. El SDK reintenta los 5xx con backoff.

---

[API](https://skmtc.dev/meteor/apis/met-public-api.md) · [All operations](https://skmtc.dev/meteor/apis/met-public-api/llms.txt) · [OpenAPI document](https://skmtc.dev/meteor/apis/met-public-api/revisions/5d9ca294295c?raw)
