ReferênciaContatos
Buscar contatos por mensagens
GET /contacts/message-search — busca contatos que mencionaram termos específicos em mensagens.
GET
/contacts/message-searchBetaEscopos necessários: contacts:read + messages:read
Retorna contatos de uma organização que mencionaram um termo, frase ou lista de termos em mensagens. Suporta filtros por canal, direção, intervalo de tempo e modo de match (any/all).
Endpoint
GET https://crm.oficinamartech.com/api/contacts/message-searchQuery parameters
| Parâmetro | Tipo | Obrigatório | Padrão | Descrição |
|---|---|---|---|---|
organization_id | UUID | ✅ | — | Identificador da organização |
q | string (até 200 chars) | △ | — | Termo único de busca |
keywords | string (até 1000 chars) | △ | — | Lista separada por vírgula, ponto-e-vírgula ou quebra de linha |
keyword | string[] (repetível) | △ | — | Parâmetro repetível para frases que contêm vírgula |
match | any | all | ❌ | any | Modo de combinação dos termos |
direction | inbound | outbound | all | ❌ | inbound | Filtra mensagens recebidas, enviadas ou ambas |
channel | whatsapp | whatsapp_cloud_api | instagram | telegram | email | ❌ | — | Filtra por canal |
from | ISO 8601 ou YYYY-MM-DD | ❌ | — | Data inicial (inclusiva) |
to | ISO 8601 ou YYYY-MM-DD | ❌ | — | Data final (inclusiva) |
limit | int (1–100) | ❌ | 50 | Tamanho da página |
offset | int (≥0) | ❌ | 0 | Offset de paginação |
messages_limit | int (0–20) | ❌ | 3 | Quantas mensagens de contexto retornar por contato |
△ = pelo menos um de q, keywords ou keyword é obrigatório.
Comportamento dos termos
- Termos são normalizados (lowercase, sem acentos) antes da busca
- Termos com menos de 2 caracteres são descartados
- Termos com mais de 200 caracteres são truncados
- Máximo de 10 termos por requisição; excedentes são ignorados
- Termos duplicados são removidos
Resposta 200
{
"data": [
{
"contact": {
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"organization_id": "...",
"name": "Maria Silva",
"phone": "+5511999999999",
"email": "maria@example.com",
"avatar_url": null,
"source": "whatsapp"
},
"match_count": 3,
"conversations_count": 2,
"first_matched_at": "2026-05-15T14:23:00.000Z",
"last_matched_at": "2026-05-19T18:42:00.000Z",
"messages": [
{
"id": "...",
"conversation_id": "...",
"channel": "whatsapp",
"direction": "inbound",
"content": "Oi, gostaria de saber mais sobre o Plano Pro",
"sent_at": "2026-05-19T18:42:00.000Z"
}
]
}
],
"pagination": {
"limit": 50,
"offset": 0,
"count": 234,
"has_more": true,
"next_offset": 50
},
"query": {
"organization_id": "...",
"terms": ["plano pro"],
"match": "any",
"direction": "inbound",
"channel": null,
"from": null,
"to": null
}
}Campos
| Campo | Tipo | Descrição |
|---|---|---|
data[].contact.id | UUID | Identificador único do contato |
data[].contact.name | string? | Nome do contato (pode ser null se desconhecido) |
data[].contact.phone | string? | Telefone E.164 (+5511999999999) |
data[].contact.email | string? | Email do contato |
data[].contact.source | string? | Canal de origem do contato |
data[].match_count | int | Total de mensagens do contato que casaram com os termos |
data[].conversations_count | int | Quantas conversas diferentes contêm os termos |
data[].first_matched_at | ISO 8601 | Primeira ocorrência dos termos nas mensagens do contato |
data[].last_matched_at | ISO 8601 | Mais recente ocorrência |
data[].messages[] | array | Até messages_limit mensagens mais recentes com o termo |
pagination | object | Veja Paginação |
query | object | Eco dos parâmetros normalizados |
Exemplos
curl -G "https://crm.oficinamartech.com/api/contacts/message-search" \
-H "Authorization: Bearer $OFM_API_KEY" \
--data-urlencode "organization_id=$ORG_ID" \
--data-urlencode "q=orçamento" \
--data-urlencode "direction=inbound" \
--data-urlencode "channel=whatsapp" \
--data-urlencode "from=2026-04-01" \
--data-urlencode "to=2026-05-20" \
--data-urlencode "limit=50" \
--data-urlencode "messages_limit=5"const url = new URL("https://crm.oficinamartech.com/api/contacts/message-search");
url.searchParams.set("organization_id", ORG_ID);
url.searchParams.set("q", "orçamento");
url.searchParams.set("direction", "inbound");
url.searchParams.set("channel", "whatsapp");
url.searchParams.set("from", "2026-04-01");
url.searchParams.set("to", "2026-05-20");
url.searchParams.set("limit", "50");
url.searchParams.set("messages_limit", "5");
const res = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.OFM_API_KEY}` },
});
if (!res.ok) {
const err = await res.json();
throw new Error(`${res.status}: ${err.error}`);
}
const { data, pagination } = await res.json();
console.log(`${data.length} de ${pagination.count} contatos`);import os
import requests
res = requests.get(
"https://crm.oficinamartech.com/api/contacts/message-search",
params={
"organization_id": ORG_ID,
"q": "orçamento",
"direction": "inbound",
"channel": "whatsapp",
"from": "2026-04-01",
"to": "2026-05-20",
"limit": 50,
"messages_limit": 5,
},
headers={
"Authorization": f"Bearer {os.environ['OFM_API_KEY']}",
},
timeout=15,
)
res.raise_for_status()
payload = res.json()
print(f"{len(payload['data'])} de {payload['pagination']['count']} contatos")$query = http_build_query([
"organization_id" => $ORG_ID,
"q" => "orçamento",
"direction" => "inbound",
"channel" => "whatsapp",
"from" => "2026-04-01",
"to" => "2026-05-20",
"limit" => 50,
"messages_limit" => 5,
]);
$ch = curl_init("https://crm.oficinamartech.com/api/contacts/message-search?$query");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("OFM_API_KEY"),
],
CURLOPT_TIMEOUT => 15,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status !== 200) {
throw new Exception("HTTP $status: $response");
}
$payload = json_decode($response, true);
echo count($payload["data"]) . " de " . $payload["pagination"]["count"] . " contatos\n";Múltiplos termos
# Modo "any" — pelo menos um termo
curl -G ".../message-search" \
--data-urlencode "keywords=Plano Pro,Plano Premium,upgrade" \
--data-urlencode "match=any"
# Modo "all" — todos os termos
curl -G ".../message-search" \
--data-urlencode "keywords=preço,desconto" \
--data-urlencode "match=all"
# Termo com vírgula — usa keyword repetível
curl -G ".../message-search" \
--data-urlencode "keyword=R$ 1.500, 12x sem juros" \
--data-urlencode "keyword=preço final"Códigos de erro específicos
| Status | Causa |
|---|---|
| 400 | organization_id ausente ou não é UUID válido |
| 400 | Nenhum termo informado (q, keywords e keyword todos vazios) |
| 400 | from ou to em formato inválido |
| 400 | from posterior a to |
| 400 | limit, offset ou messages_limit fora dos limites permitidos |
| 401 | API key ausente, inválida, revogada ou expirada |
| 403 | API key sem escopo contacts:read ou messages:read |
| 403 | API key autorizada mas organization_id é de outra organização |
| 500 | Erro interno |
Veja Códigos de erro para formato completo.
Notas
- A busca é case-insensitive e diacritic-insensitive (
orçamento≡Orcamento≡ORÇAMENTO) from/tosão inclusivos- Quando filtra por
channel, apenas mensagens daquele canal são consideradas para o match — mas o contato pode ter mensagens em outros canais que não aparecem emmessages[] - O array
messages[]traz as mais recentes que casaram, ordenadas DESC porsent_at
Experimente
Use uma API key dedicada de teste com escopo mínimo. A chave fica em localStorage deste navegador até você remover ou trocar.
Playground · GET /contacts/message-search
Use uma chave de teste dedicada — a chave fica em localStorage deste navegador até você remover.