{
  "openapi": "3.1.0",
  "info": {
    "title": "Amobit Chats — developer API",
    "version": "2026-10-20",
    "summary": "Webhooks and inbound send API for external CRM/ERP/helpdesk systems.",
    "description": "Публичный контракт Amobit Chats для внешних систем: событие message.created и отправка текста в существующий диалог. Документация: https://chats.amobit.io/docs. Чтение диалогов и истории для AI-клиентов доступно через MCP (https://chats.amobit.io/docs/mcp), а не через этот HTTP API.",
    "contact": {
      "name": "Amobit Chats support",
      "email": "info@amobit.io",
      "url": "https://chats.amobit.io/docs"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://amobit.io/ru/legal"
    }
  },
  "servers": [
    {
      "url": "https://chats.amobit.io",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "Messages",
      "description": "Отправка текста в существующий диалог."
    },
    {
      "name": "Webhooks",
      "description": "События, которые Amobit Chats отправляет во внешнюю систему."
    }
  ],
  "security": [
    {
      "bearerAuth": []
    },
    {
      "amobitChatsToken": []
    }
  ],
  "paths": {
    "/api/v1/external-integrations/inbound/messages/": {
      "post": {
        "tags": [
          "Messages"
        ],
        "operationId": "sendInboundMessage",
        "summary": "Отправить текст в существующий диалог",
        "description": "Токен выпускается в кабинете (Профиль → Интеграции → API / Webhook) и действует в пределах одной компании. Токен принимается только этим эндпоинтом. Метод не создаёт новые диалоги и не отправляет медиа.\n\nАдресация диалога: thread_id, либо channel_profile_id + external_chat_id, либо channel + external_chat_id. При неоднозначном совпадении возвращается 409.\n\nИдемпотентность: ключ уникален в пределах компании и диалога. Сравнивается только text; metadata не участвует. Повтор с тем же ключом и тем же текстом возвращает 200 и существующее сообщение; тот же ключ с другим текстом — 409.\n\nНеизвестные поля игнорируются. Ответ содержит объект сообщения интерфейса; стабильны id, thread_id, direction, text, delivery_status, sent_at и основные поля attachments. Ответ через API не останавливает AI-агента на канале.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InboundMessageRequest"
              },
              "examples": {
                "thread": {
                  "summary": "По thread_id из webhook",
                  "value": {
                    "thread_id": "9fd48a99-f5f3-4c3d-8ca1-1f0e047d5b25",
                    "text": "Здравствуйте! Отвечаем из CRM.",
                    "idempotency_key": "crm-outbox-1001",
                    "metadata": {
                      "lead_id": "42"
                    }
                  }
                },
                "legacy_identity": {
                  "summary": "По каналу и external_chat_id",
                  "value": {
                    "channel": "whatsapp",
                    "external_chat_id": "79990001122@c.us",
                    "text": "Ответ из CRM",
                    "idempotency_key": "crm-outbox-1002"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Сообщение записано и принято к отправке: для мессенджеров провайдер принял запрос, для веб-чата сообщение доставлено в диалог.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Message"
                }
              }
            }
          },
          "200": {
            "description": "Повтор по idempotency_key: возвращено существующее сообщение. Возможен и для записанного сообщения без подтверждения провайдера — проверьте delivery_status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Message"
                }
              }
            }
          },
          "202": {
            "description": "Провайдер принял отправку, но локальная сверка/сохранение не завершены. Повторная отправка провайдеру не выполняется; повторите тот же запрос с тем же idempotency_key, чтобы получить текущее состояние.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Message"
                }
              }
            }
          },
          "400": {
            "description": "Ошибка валидации тела запроса.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "401": {
            "description": "Токен отсутствует, неверен или отозван.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": "Invalid external integration token."
                }
              }
            }
          },
          "402": {
            "description": "Отправка заблокирована тарифом или подпиской канала.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Диалог не найден в этой компании.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": "Thread was not found."
                }
              }
            }
          },
          "409": {
            "description": "Неоднозначный диалог; канал не подключён для исходящих; группа исключена настройкой канала; ключ идемпотентности занят другим текстом.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Провайдер не подтвердил отправку; исход неизвестен. Сохраните error_id и повторите с тем же ключом.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProviderError"
                }
              }
            }
          },
          "500": {
            "description": "Внутренняя ошибка.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "webhooks": {
    "messageCreated": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "messageCreated",
        "summary": "message.created — новое сообщение в диалоге",
        "description": "Amobit Chats отправляет событие в публичный HTTPS-эндпоинт интеграции. Доставка асинхронная, at-least-once в пределах 8 попыток с паузами 1, 2, 4, 8, 16, 32 и 60 минут; таймаут ответа 10 секунд; редиректы не выполняются; успех — любой 2xx. После 8 неудач доставка прекращается (dead): успешной доставки может не быть.\n\nСобытие формируется для входящих и исходящих сообщений. Внутренние заметки операторов во внешнюю систему не отправляются.\n\nПодпись: X-Amobit-Chats-Signature = 'sha256=' + HMAC-SHA256(secret, X-Amobit-Chats-Timestamp + '.' + raw_body), где secret — секрет интеграции (≥ 16 символов), timestamp — значение заголовка X-Amobit-Chats-Timestamp.\n\nПорядок не гарантируется; дедупликация — по X-Amobit-Chats-Idempotency-Key или message.id. Ручного повтора после исчерпания попыток нет.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MessageCreatedEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Событие принято. Любой ответ 2xx считается успешной доставкой; тело ответа не используется."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Токен интеграции вида amc_…, выпущенный в кабинете. Срок действия встроен в токен (по умолчанию 365 дней); просроченный токен отвечает 401 и перевыпускается в кабинете. Альтернативный заголовок — X-Amobit-Chats-Token."
      },
      "amobitChatsToken": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Amobit-Chats-Token",
        "description": "Альтернатива Authorization: Bearer для HTTP-клиентов без поддержки стандартного заголовка."
      }
    },
    "schemas": {
      "InboundMessageRequest": {
        "type": "object",
        "additionalProperties": true,
        "description": "Неизвестные поля сервер игнорирует; передавайте только документированные.",
        "properties": {
          "thread_id": {
            "type": "string",
            "format": "uuid",
            "description": "ID диалога из события message.created. Предпочтительный способ адресации."
          },
          "channel_profile_id": {
            "type": "string",
            "format": "uuid",
            "description": "ID канала Amobit Chats; используется вместе с external_chat_id."
          },
          "channel": {
            "type": "string",
            "maxLength": 50,
            "description": "Тип канала: whatsapp, telegram, max и т. п. Устаревшая пара к external_chat_id."
          },
          "external_chat_id": {
            "type": "string",
            "maxLength": 255,
            "description": "ID чата на стороне канала."
          },
          "text": {
            "type": "string",
            "minLength": 1,
            "maxLength": 4000,
            "description": "Текст сообщения. Пробелы по краям обрезаются."
          },
          "idempotency_key": {
            "type": "string",
            "maxLength": 255,
            "pattern": "^[A-Za-z0-9][A-Za-z0-9_.:-]*$",
            "description": "Ключ идемпотентности. Уникален в пределах компании и диалога."
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "description": "Служебные данные внешней системы. Сохраняются с намерением отправки, не влияют на доставку."
          }
        },
        "required": [
          "text"
        ],
        "anyOf": [
          {
            "required": [
              "thread_id"
            ]
          },
          {
            "required": [
              "channel_profile_id",
              "external_chat_id"
            ]
          },
          {
            "required": [
              "channel",
              "external_chat_id"
            ]
          }
        ]
      },
      "Message": {
        "type": "object",
        "additionalProperties": true,
        "description": "Объект сообщения. Ниже перечислены ключевые поля; объект может содержать дополнительные поля, используемые интерфейсом Amobit Chats.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "thread_id": {
            "type": "string",
            "format": "uuid"
          },
          "channel": {
            "type": "string"
          },
          "channel_profile_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "channel_profile_name": {
            "type": "string"
          },
          "direction": {
            "type": "string",
            "enum": [
              "incoming",
              "outgoing",
              "internal"
            ]
          },
          "message_type": {
            "type": "string",
            "enum": [
              "text",
              "internal_note",
              "image",
              "video",
              "audio",
              "ptt",
              "document",
              "file",
              "location",
              "reaction"
            ]
          },
          "text": {
            "type": "string"
          },
          "preview_text": {
            "type": "string"
          },
          "sender_name": {
            "type": "string"
          },
          "delivery_status": {
            "type": "string",
            "enum": [
              "pending",
              "sent",
              "delivered",
              "read",
              "failed"
            ]
          },
          "provider_message_id": {
            "type": "string"
          },
          "provider_transport_id": {
            "type": "string"
          },
          "sent_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "attachments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Attachment"
            }
          }
        }
      },
      "Attachment": {
        "type": "object",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "kind": {
            "type": "string"
          },
          "file_name": {
            "type": "string"
          },
          "mime_type": {
            "type": "string"
          },
          "size_bytes": {
            "type": "integer"
          },
          "share_url": {
            "type": "string"
          },
          "source_url": {
            "type": "string"
          },
          "storage_status": {
            "type": "string"
          },
          "duration_ms": {
            "type": [
              "integer",
              "null"
            ]
          },
          "width": {
            "type": [
              "integer",
              "null"
            ]
          },
          "height": {
            "type": [
              "integer",
              "null"
            ]
          }
        }
      },
      "MessageCreatedEvent": {
        "type": "object",
        "required": [
          "event",
          "event_version",
          "workspace",
          "thread",
          "contact",
          "message",
          "channel_profile",
          "attachments"
        ],
        "properties": {
          "event": {
            "type": "string",
            "const": "message.created"
          },
          "event_version": {
            "type": "string",
            "const": "2026-07-03"
          },
          "workspace": {
            "type": "object",
            "required": [
              "id",
              "slug",
              "name"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "slug": {
                "type": "string"
              },
              "name": {
                "type": "string"
              }
            }
          },
          "thread": {
            "type": "object",
            "additionalProperties": true,
            "required": [
              "id",
              "channel",
              "external_chat_id"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "channel": {
                "type": "string"
              },
              "external_chat_id": {
                "type": "string"
              },
              "display_name": {
                "type": "string"
              },
              "phone": {
                "type": "string"
              },
              "contact_name": {
                "type": "string"
              },
              "is_group": {
                "type": "boolean"
              },
              "status": {
                "type": "string"
              },
              "unread_count": {
                "type": "integer"
              },
              "updated_at": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          "contact": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "id": {
                "type": "string",
                "description": "UUID или пустая строка, если контакта нет."
              },
              "display_name": {
                "type": "string"
              },
              "notes": {
                "type": "string"
              }
            }
          },
          "message": {
            "type": "object",
            "additionalProperties": true,
            "required": [
              "id",
              "direction",
              "message_type"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "direction": {
                "type": "string",
                "enum": [
                  "incoming",
                  "outgoing",
                  "internal"
                ]
              },
              "message_type": {
                "type": "string"
              },
              "text": {
                "type": "string"
              },
              "preview_text": {
                "type": "string"
              },
              "sender_name": {
                "type": "string"
              },
              "delivery_status": {
                "type": "string"
              },
              "provider_message_id": {
                "type": "string"
              },
              "provider_transport_id": {
                "type": "string"
              },
              "source": {
                "type": "string"
              },
              "sent_at": {
                "type": "string",
                "description": "ISO 8601 с часовым поясом или пустая строка, если время не задано."
              },
              "created_at": {
                "type": "string",
                "format": "date-time"
              },
              "updated_at": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          "channel_profile": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "id": {
                "type": "string",
                "description": "UUID или пустая строка, если профиль канала отсутствует."
              },
              "name": {
                "type": "string"
              },
              "channel": {
                "type": "string"
              },
              "status": {
                "type": "string"
              },
              "support_code": {
                "type": "string"
              }
            }
          },
          "attachments": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "kind": {
                  "type": "string"
                },
                "file_name": {
                  "type": "string"
                },
                "mime_type": {
                  "type": "string"
                },
                "size_bytes": {
                  "type": "integer"
                },
                "share_url": {
                  "type": "string"
                },
                "source_url": {
                  "type": "string"
                },
                "storage_status": {
                  "type": "string"
                },
                "duration_ms": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "width": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "height": {
                  "type": [
                    "integer",
                    "null"
                  ]
                }
              }
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "detail"
        ],
        "properties": {
          "detail": {
            "type": "string"
          }
        }
      },
      "ProviderError": {
        "type": "object",
        "required": [
          "detail"
        ],
        "properties": {
          "detail": {
            "type": "string"
          },
          "error_id": {
            "type": "string",
            "description": "Идентификатор инцидента для поддержки."
          }
        }
      },
      "ValidationError": {
        "type": "object",
        "additionalProperties": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "description": "Ошибки валидации: ключ — имя поля, значение — список сообщений."
      }
    }
  }
}