CRM Oficina Martech
ReferênciaContatos

Buscar contatos por mensagens

GET /contacts/message-search — busca contatos que mencionaram termos específicos em mensagens.

GET/contacts/message-searchBeta

Escopos 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-search

Query parameters

ParâmetroTipoObrigatórioPadrãoDescrição
organization_idUUIDIdentificador da organização
qstring (até 200 chars)Termo único de busca
keywordsstring (até 1000 chars)Lista separada por vírgula, ponto-e-vírgula ou quebra de linha
keywordstring[] (repetível)Parâmetro repetível para frases que contêm vírgula
matchany | allanyModo de combinação dos termos
directioninbound | outbound | allinboundFiltra mensagens recebidas, enviadas ou ambas
channelwhatsapp | whatsapp_cloud_api | instagram | telegram | emailFiltra por canal
fromISO 8601 ou YYYY-MM-DDData inicial (inclusiva)
toISO 8601 ou YYYY-MM-DDData final (inclusiva)
limitint (1–100)50Tamanho da página
offsetint (≥0)0Offset de paginação
messages_limitint (0–20)3Quantas 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

CampoTipoDescrição
data[].contact.idUUIDIdentificador único do contato
data[].contact.namestring?Nome do contato (pode ser null se desconhecido)
data[].contact.phonestring?Telefone E.164 (+5511999999999)
data[].contact.emailstring?Email do contato
data[].contact.sourcestring?Canal de origem do contato
data[].match_countintTotal de mensagens do contato que casaram com os termos
data[].conversations_countintQuantas conversas diferentes contêm os termos
data[].first_matched_atISO 8601Primeira ocorrência dos termos nas mensagens do contato
data[].last_matched_atISO 8601Mais recente ocorrência
data[].messages[]arrayAté messages_limit mensagens mais recentes com o termo
paginationobjectVeja Paginação
queryobjectEco 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

StatusCausa
400organization_id ausente ou não é UUID válido
400Nenhum termo informado (q, keywords e keyword todos vazios)
400from ou to em formato inválido
400from posterior a to
400limit, offset ou messages_limit fora dos limites permitidos
401API key ausente, inválida, revogada ou expirada
403API key sem escopo contacts:read ou messages:read
403API key autorizada mas organization_id é de outra organização
500Erro interno

Veja Códigos de erro para formato completo.

Notas

  • A busca é case-insensitive e diacritic-insensitive (orçamentoOrcamentoORÇAMENTO)
  • from/to sã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 em messages[]
  • O array messages[] traz as mais recentes que casaram, ordenadas DESC por sent_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.