Envio em massa
Importe até 10.000 contatos e envie até 1.000 mensagens numa requisição, acompanhando o resultado item por item.
Os endpoints em massa respondem na hora com 202 e fazem o trabalho em segundo plano, em lotes de 100. Um item com problema nunca interrompe os outros: cada um tem o seu resultado.
| Endpoint | Máximo por requisição | Como acompanhar |
|---|---|---|
POST /bulk/contacts | 10.000 contatos | Aviso na lista de Contatos do Vamo e webhook de resumo |
POST /bulk/messages | 1.000 mensagens | GET /bulk/jobs/:id e webhook de resumo |
POST /bulk/contacts
Importa contatos para a loja. Acesso: chave secreta com Leitura e escrita.
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
storeId | string (UUID) | Sim | A sua loja |
contacts | array | Sim | De 1 a 10.000 contatos |
Cada contato:
| Campo | Obrigatório | Descrição |
|---|---|---|
name | Sim | Nome |
phone | Sim | Telefone (veja as regras abaixo) |
status | Não | active, lead ou unresolved. Padrão: lead |
tags | Não | Lista de etiquetas |
email, document, city | Não | E-mail, CPF/CNPJ (ou documento) e cidade |
properties | Não | Valores dos seus campos personalizados, pela chave do campo |
waId | Não | Padrão: o telefone sem o + |
curl -X POST https://vamo.ai/api/v1/bulk/contacts \
-H "Authorization: Bearer sk_live_SUA_CHAVE" \
-H "Content-Type: application/json" \
-d '{
"storeId": "7b1e2c3d-4a5f-4b6c-8d7e-9f0a1b2c3d4e",
"contacts": [
{ "name": "Mariana Costa", "phone": "+55 11 98765-4321", "tags": ["black-friday"] },
{ "name": "Rafael Souza", "phone": "(21) 99887-7665", "city": "Rio de Janeiro" },
{ "name": "Ana Lima", "phone": "11 9123", "email": "ana@email.com" }
]
}'Resposta 202
{
"data": {
"taskId": "run_cm3x9k2a0001",
"importJobId": "e7f8a9b0-c1d2-4e3f-8a4b-5c6d7e8f9a0b",
"status": "processing",
"totalContacts": 3
}
}Regras da importação
- Telefone. Com
+(ou00) e código do país, o número é mantido. Sem código do país, ele vira um número do país em que a sua organização vende: em reais,(21) 99887-7665vira+5521998877665; em guaranis,0981 123456vira+595981123456. Uma organização em dólar precisa enviar o código do país. Um número que não dá para entender é recusado (no exemplo, o de Ana). - Repetidos são pulados. Um número que a loja já tem, ou que aparece duas vezes na lista, não cria outro contato e conta como pulado. A importação não atualiza contatos existentes.
- Campo opcional inválido (e-mail ou documento inválido, campo personalizado que não existe) é deixado de fora; o contato é importado com os outros dados.
Acompanhar
O andamento e o resultado aparecem no aviso no topo da lista de Contatos, no Vamo. Não há rota da API para consultar uma importação de contatos.
Ao terminar, o Vamo dispara um webhook contact.created de resumo (não um por contato):
{
"event": "contact.created",
"data": { "bulk": true, "created": 2, "skipped": 0, "failed": 1 },
"timestamp": "2026-10-31T15:00:12.000Z"
}POST /bulk/messages
Envia mensagens livres para vários contatos. Acesso: chave secreta com Leitura e escrita.
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
messages | array | Sim | De 1 a 1.000 mensagens |
storeId | string (UUID) | Não | A loja em que o envio fica registrado (404 se não for sua) |
Cada mensagem tem os mesmos campos de POST /messages/send: contactId e content (obrigatórios), mediaUrl e mediaType.
curl -X POST https://vamo.ai/api/v1/bulk/messages \
-H "Authorization: Bearer sk_live_SUA_CHAVE" \
-H "Content-Type: application/json" \
-d '{
"messages": [
{ "contactId": "2c9d8e7f-6a5b-4c3d-9e2f-1a0b9c8d7e6f", "content": "Mariana, sua capinha chegou na loja!" },
{ "contactId": "3d0e9f8a-7b6c-4d5e-8f1a-2b3c4d5e6f70", "content": "Rafael, seu fone foi enviado hoje." }
]
}'Resposta 202
{
"data": {
"jobId": "c5d6e7f8-a9b0-4c1d-9e2f-d6e7f8a9b0c1",
"taskId": "run_cm3x9k7b0002",
"status": "processing",
"totalMessages": 2
}
}Só para quem está na janela de 24 horas
Cada mensagem segue as regras de POST /messages/send: ela só sai para um contato que escreveu nas últimas 24 horas. As outras voltam como erro whatsapp_window_closed. A API não envia modelos em massa: para avisar muitos clientes fora da janela, use as Campanhas do Vamo.
Como o envio roda
- As mensagens saem na ordem da lista, 100 por vez.
- Se o WhatsApp pedir para esperar, o Vamo espera o tempo indicado (60 segundos, se a Meta não disser) e tenta aquela mensagem mais uma vez.
- Um lote que falha não é repetido, para nenhum cliente receber a mesma mensagem duas vezes. O job fica
failedeprocessedmostra até onde chegou: as mensagens depois desse ponto não saíram. - Cada mensagem enviada aparece na conversa do contato, com origem
api.
GET /bulk/jobs/:id
O andamento de um envio de mensagens. Acesso: chave secreta (leitura). O job de outra organização responde 404, como um que não existe.
curl https://vamo.ai/api/v1/bulk/jobs/c5d6e7f8-a9b0-4c1d-9e2f-d6e7f8a9b0c1 \
-H "Authorization: Bearer sk_live_SUA_CHAVE"Resposta 200
{
"data": {
"id": "c5d6e7f8-a9b0-4c1d-9e2f-d6e7f8a9b0c1",
"status": "running",
"total": 250,
"processed": 100,
"sentCount": 99,
"failedCount": 1,
"errors": [
{ "index": 7, "contactId": "3d0e9f8a-7b6c-4d5e-8f1a-2b3c4d5e6f70", "code": "whatsapp_window_closed", "message": "The 24-hour customer service window is closed. Send an approved template instead." }
],
"createdAt": "2026-10-31T15:02:11.000Z",
"finishedAt": null
}
}| Campo | Descrição |
|---|---|
status | running, depois completed; failed se o job parou antes do fim |
processed | Quantas mensagens já foram tratadas (enviadas ou com erro) |
sentCount, failedCount | Enviadas e não enviadas |
errors | Uma entrada por mensagem não enviada: a posição dela na lista (index, começando em 0), o contato, o code e o motivo. messageId aparece quando a tentativa ficou gravada na conversa |
finishedAt | Quando terminou; null enquanto roda |
Os códigos são os de Erros, mais not_found (o contato não existe na sua organização) e internal_error.
Webhook de resumo
Ao terminar, o Vamo dispara um message.created com o resultado de cada item (as mensagens do envio em massa não geram um message.created cada):
{
"event": "message.created",
"data": {
"bulk": true,
"jobId": "c5d6e7f8-a9b0-4c1d-9e2f-d6e7f8a9b0c1",
"sent": 1,
"failed": 1,
"results": [
{ "index": 0, "messageId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "conversationId": "5e4d3c2b-1a09-4f8e-8d7c-6b5a49382716" }
],
"errors": [
{ "index": 1, "contactId": "3d0e9f8a-7b6c-4d5e-8f1a-2b3c4d5e6f70", "code": "whatsapp_window_closed", "error": "The 24-hour customer service window is closed. Send an approved template instead." }
]
},
"timestamp": "2026-10-31T15:04:40.000Z"
}Repare que no webhook o motivo vem em error, e em GET /bulk/jobs/:id em message.
Erros
Os erros vêm no formato de sempre: { "error": { "code": "…", "message": "…" } }.
| Status | code | Quando |
|---|---|---|
400 | validation_error | Falta storeId (contatos) ou a lista; lista vazia (contacts array must not be empty); lista acima do limite (Maximum 10,000 contacts per bulk import / Maximum 1,000 messages per bulk send); um item inválido (message diz qual, por exemplo messages.3.contactId: Invalid uuid). Nada é enfileirado |
404 | not_found | Store not found / Job not found: a loja ou o job não é da sua organização |
429 | rate_limited | Limite de requisições da chave (veja Limites de requisição) |
As duas rotas aceitam Idempotency-Key: repetir um envio em massa com a mesma chave devolve o mesmo jobId, sem enfileirar outro (veja Idempotência).