Convenções e erros
As regras que valem para todas as rotas — leia uma vez e não precise reler em cada página.
URL base e formato
Toda rota mora sob https://linkzap.orion.moe/api/v1. Pedido e resposta são JSON em UTF-8; as rotas que recebem corpo esperam Content-Type: application/json.
O que volta fica sempre dentro de data. É uma casca aparentemente inútil, e ela existe para que acrescentar um campo ao lado (page, total, created) nunca quebre quem já lê o que está dentro.
Paginação
Três listas são paginadas — GET /contacts, GET /conversations e GET /messages. Elas aceitam page (a partir de 1) e limit (1 a 200, padrão 50), e devolvem total no corpo: o número de itens que casam com o filtro, não o da página.
Duas não são, porque não crescem: GET /channels e GET /flows devolvem tudo de uma vez, num { data: [...] } seco, sem page, limit nem total. Uma conta tem alguns números e algumas dezenas de fluxos — paginar ali seria cerimônia sem motivo.
curl "https://linkzap.orion.moe/api/v1/contacts?page=2&limit=20" \
-H "Authorization: Bearer lz_SUA_CHAVE"Datas e telefones
- Datas saem em ISO-8601 com fuso UTC (
2026-09-21T14:03:11.000Z). Converta para o fuso da sua operação na hora de mostrar — o painel faz isso com o fuso que está em Configurações → Geral. - Telefones entram como DDI + DDD + número, só dígitos:
5592999990000. Aceitamos com+, espaço, parêntese e traço e normalizamos; o que não vira um número brasileiro válido responde 422. Nas respostas o telefone volta sempre normalizado. - Identificadores são strings opacas. Não tente derivar nada delas nem presuma tamanho.
Erros
Todo erro é um JSON com error — uma frase em português, escrita para aparecer no seu log ou para a sua equipe ler. Quando o problema é de validação, vem junto issues, apontando campo por campo.
{
"error": "Dados inválidos.",
"issues": [
{ "path": "phone", "message": "String must contain at least 8 character(s)" }
]
}Os status que você vai encontrar
200Deu certoO corpo traz
data, e nas listas tambémpage,limitetotal.201CriouAlgo passou a existir: um contato novo, uma mensagem, um fluxo iniciado.
400O corpo não é JSONSó em
POST: o texto enviado não foi lido como JSON. Confira a vírgula sobrando e oContent-Type. Campo faltando ou fora do formato não é 400, é 422.401Chave ausente, inválida ou revogadaNão tente de novo com a mesma chave — ela não vai passar a valer.
403Fora do escopoUma chave de canal pedindo outro canal. É 403 e não 404 de propósito: não dizemos se o outro canal existe.
404Não existeCanal, conversa, contato, fluxo ou arquivo que não é desta organização.
409Existe, mas não dá agoraO caso comum é o número não estar pronto para enviar; o motivo vem em palavras.
422Os dados não passamTelefone fora do formato, campo faltando, valor longo demais. Vem com
issues[]apontando o campo.429Rápido demaisEspere o que o
Retry-Afterdisser.500Foi culpa nossaTente de novo com recuo. Se persistir, fale com o suporte com o horário da chamada.
Limite de chamadas
Cada chave pode fazer 120 chamadas por minuto, e a organização inteira, 600 somando todas as chaves. Passando disso, a resposta é 429 com Retry-After em segundos — espere o que ele disser em vez de tentar em laço.
Precisa de mais?
Repetir sem estragar
POST /contacts é idempotente pelo telefone: chamar duas vezes atualiza, não duplica (o created da resposta diz qual dos dois aconteceu). POST /messages não é — cada chamada manda uma mensagem. Se o seu código tem retentativa automática, guarde o messageId que voltou antes de tentar de novo.