Como começar
1. No backoffice, em Definições → API para outros programas, crie uma chave com o nome do sistema (ex.: «ERP»). A chave aparece uma só vez: guarde-a no seu programa. Pode revogá-la quando quiser.
2. Envie-a em todos os pedidos: Authorization: Bearer chg_… (ou o cabeçalho X-API-Key).
3. O endereço base é o da sua instalação, por exemplo https://empresa.chegou.pt. Tudo em JSON, UTF-8.
Use a chave só no servidor do seu programa, nunca num navegador ou numa app instalada nos telemóveis. Há limite de pedidos por minuto e por chave; uma chave de só leitura consulta mas não altera nada.
Criar entregas
POST/api/v1/orders
Envie uma entrega (objeto) ou até 200 (lista). A ref é única: se reenviar a mesma, o Chegou não duplica. Se a entrega ainda não saiu para a rota, atualiza o que mudou; se já está em rota ou concluída, recusa a alteração.
curl -X POST https://empresa.chegou.pt/api/v1/orders \
-H "Authorization: Bearer chg_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"ref": "FT 2026/1234",
"customer": "Café Central",
"address": "Rua do Souto 24, 4700-329 Braga",
"phone": "253000000",
"win_from": "09:00", "win_to": "12:00",
"lines": [
{ "item_ref": "A100", "description": "Água 0,5 L (pack 24)", "unit": "CX", "qty": 10 }
]
}'
| Campo | Descrição | |
|---|---|---|
ref | obrigatório | Referência do documento no seu sistema (única). Até 60 caracteres. |
customer | obrigatório | Nome do cliente. |
address | obrigatório | Morada de entrega, com código postal. O Chegou encontra as coordenadas. |
lat, lng | opcional | Coordenadas, se já as tem (sempre as duas). Evitam procurar a morada. |
phone, email | opcional | Contactos do cliente (o email recebe o aviso de seguimento, se estiver ativo). |
notes | opcional | Instruções para o motorista (porta, andar, horários). |
win_from, win_to | opcional | Janela de entrega, HH:MM. |
service_min | opcional | Minutos de paragem (1–240). |
doc_date | opcional | Data do documento, AAAA-MM-DD. |
customer_code, nif | opcional | Código e NIF do cliente, para o ligar à ficha de cliente. |
lines | opcional | Lista de { item_ref, description, unit, qty, price } (até 200 linhas). |
Resposta (uma entrega): 201 se criou, 200 se atualizou ou estava igual, 409 se já saiu para a rota e não se pode alterar, 400 se o pedido é inválido.
{ "ref": "FT 2026/1234", "resultado": "criada" }
Resultados possíveis: criada, atualizada, igual, recusada (já saiu para a rota) e erro (com o motivo em erro). Numa lista, a resposta traz resultados (um por entrega, pela mesma ordem) e as contagens; uma entrega com erro não impede as outras.
Consultar o estado
GET/api/v1/orders/{ref}
{
"ref": "FT 2026/1234",
"estado": "entregue", // pendente | atribuida | em_rota | entregue | falhada
"cliente": "Café Central",
"morada": "Rua do Souto 24, 4700-329 Braga",
"janela": { "de": "09:00", "ate": "12:00" },
"motorista": "Ana",
"previsao_chegada": null,
"entregue_em": "2026-10-20T10:42:11.000Z",
"recebido_por": "Maria Silva",
"motivo_falha": null,
"seguimento": "https://empresa.chegou.pt/t/…",
"comprovativo_pdf": "https://empresa.chegou.pt/api/comprovativo/cafe-central/….pdf",
"linhas": [ { "artigo": "A100", "descricao": "Água 0,5 L (pack 24)", "unidade": "CX", "quantidade": 10, "entregue": 10, "devolvida": null } ]
}
As ligações seguimento e comprovativo_pdf só aparecem se o endereço público estiver definido em Definições → Notificações; o comprovativo só depois de a entrega estar concluída. A API nunca devolve preços.
GET/api/v1/orders?estado=entregue&desde=2026-10-01&ate=2026-10-31&limite=100
Lista as entregas mais recentes (até 500), sem as linhas. Todos os filtros são opcionais.
Cancelar
DELETE/api/v1/orders/{ref}
Só se a entrega ainda não saiu (pendente ou numa rota por iniciar). Em rota, concluída ou falhada devolve 409.
Avisos (webhooks)
Em vez de consultar o estado de vez em quando, deixe o Chegou avisar o seu programa. Em Definições → Integrações → Avisos ao seu programa indique um endereço https público, os eventos que quer e guarde o segredo (aparece uma só vez).
| Evento | Quando |
|---|---|
entrega.em_rota | O motorista vai a caminho da entrega. |
entrega.entregue | Entrega concluída (com quem recebeu). |
entrega.falhada | Entrega não realizada (com o motivo). |
entrega.reaberta | Uma entrega ou falha foi anulada e volta a estar aberta. |
O Chegou faz POST com Content-Type: application/json:
{
"id": "evt_1234", // identificador do envio (igual nas repetições)
"evento": "entrega.entregue",
"criado_em": "2026-10-20T10:42:12.000Z",
"entrega": { "ref": "FT 2026/1234", "estado": "entregue", ... } // o mesmo formato de GET /api/v1/orders/{ref}
}
Cabeçalhos: X-Chegou-Event, X-Chegou-Delivery (o id) e X-Chegou-Signature: t=<segundos>,v1=<assinatura>, onde a assinatura é o HMAC-SHA256, em hexadecimal, de <t>.<corpo> com o segredo do aviso. Confirme sempre a assinatura e rejeite pedidos com t muito antigo (por exemplo, mais de 5 minutos).
// Node.js
const crypto = require('node:crypto');
function valido(segredo, cabecalho, corpoBruto) {
const [t, v1] = cabecalho.split(',').map((p) => p.split('=')[1]);
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const esperado = crypto.createHmac('sha256', segredo).update(t + '.' + corpoBruto).digest('hex');
return v1.length === esperado.length && crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(esperado));
}
Responda com um código 2xx em poucos segundos. Se não responder (ou responder erro), o Chegou repete ao fim de 1 min, 5 min, 30 min, 2 h e 6 h e depois desiste desse envio; o mesmo id pode chegar mais de uma vez, por isso trate-o como idempotente. Ao fim de 30 falhas seguidas o aviso desativa-se sozinho. Por omissão só se avisa das entregas criadas pela API. Os envios não seguem redirecionamentos.
Exemplos prontos
Código completo, só com a biblioteca padrão de cada linguagem: enviar uma entrega, consultar o estado e confirmar a assinatura de um aviso. Ficam também no repositório, em exemplos-api/.
Python 3
# Exemplo de utilização da API do Chegou em Python 3 (só biblioteca padrão).
# Defina CHEGOU_URL (ex.: https://empresa.chegou.pt) e CHEGOU_CHAVE (chg_…) no ambiente.
import hashlib
import hmac
import json
import os
import time
import urllib.error
import urllib.parse
import urllib.request
BASE = os.environ.get("CHEGOU_URL", "https://empresa.chegou.pt")
CHAVE = os.environ.get("CHEGOU_CHAVE", "")
def chamar(metodo, caminho, corpo=None):
"""Faz um pedido à API e devolve (código HTTP, dados em JSON)."""
dados = json.dumps(corpo).encode("utf-8") if corpo is not None else None
pedido = urllib.request.Request(BASE + caminho, data=dados, method=metodo, headers={
"Authorization": "Bearer " + CHAVE,
"Content-Type": "application/json",
})
try:
with urllib.request.urlopen(pedido, timeout=20) as resposta:
return resposta.status, json.loads(resposta.read() or b"{}")
except urllib.error.HTTPError as erro: # 400, 401, 409… trazem {"error": "…"}
return erro.code, json.loads(erro.read() or b"{}")
def enviar_entrega(entrega):
"""Cria (ou atualiza) uma entrega. Repetir o pedido com a mesma ref nunca duplica."""
return chamar("POST", "/api/v1/orders", entrega)
def estado_da_entrega(ref):
return chamar("GET", "/api/v1/orders/" + urllib.parse.quote(ref, safe=""))
def assinatura_valida(segredo, cabecalho, corpo_bruto, tolerancia=300):
"""Confirma um aviso (webhook). `corpo_bruto` são os bytes recebidos, sem os reescrever."""
partes = dict(p.split("=", 1) for p in cabecalho.split(","))
if abs(time.time() - int(partes["t"])) > tolerancia:
return False
esperado = hmac.new(segredo.encode(), partes["t"].encode() + b"." + corpo_bruto, hashlib.sha256).hexdigest()
return hmac.compare_digest(partes["v1"], esperado)
if __name__ == "__main__":
codigo, resposta = enviar_entrega({
"ref": "FT 2026/1234",
"customer": "Café Central",
"address": "Rua do Souto 24, 4700-329 Braga",
"phone": "253000000",
"win_from": "09:00", "win_to": "12:00",
"lines": [{"item_ref": "A100", "description": "Água 0,5 L (pack 24)", "unit": "CX", "qty": 10}],
})
print(codigo, resposta) # 201 {'ref': 'FT 2026/1234', 'resultado': 'criada'}
print(estado_da_entrega("FT 2026/1234"))
PHP 8
<?php
// Exemplo de utilização da API do Chegou em PHP 8 (sem bibliotecas).
// Defina CHEGOU_URL (ex.: https://empresa.chegou.pt) e CHEGOU_CHAVE (chg_…) no ambiente.
$BASE = getenv('CHEGOU_URL') ?: 'https://empresa.chegou.pt';
$CHAVE = getenv('CHEGOU_CHAVE') ?: '';
if (!preg_match('/^[\x21-\x7E]*$/', $CHAVE)) { fwrite(STDERR, "CHEGOU_CHAVE inválida.\n"); exit(1); } // sem espaços nem quebras de linha: a chave vai num cabeçalho HTTP
/** Faz um pedido à API e devolve [código HTTP, dados em JSON]. */
function chamar(string $metodo, string $caminho, ?array $corpo = null): array {
global $BASE, $CHAVE;
$contexto = stream_context_create(['http' => [
'method' => $metodo,
'header' => "Authorization: Bearer $CHAVE\r\nContent-Type: application/json\r\n",
'content' => $corpo === null ? '' : json_encode($corpo, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR),
'ignore_errors' => true, // 400, 401, 409… trazem {"error": "…"}
'timeout' => 20,
]]);
$texto = file_get_contents($BASE . $caminho, false, $contexto);
preg_match('#HTTP/\S+ (\d+)#', $http_response_header[0] ?? '', $m);
return [(int)($m[1] ?? 0), json_decode($texto ?: '{}', true)];
}
/** Cria (ou atualiza) uma entrega. Repetir o pedido com a mesma ref nunca duplica. */
function enviarEntrega(array $entrega): array { return chamar('POST', '/api/v1/orders', $entrega); }
function estadoDaEntrega(string $ref): array { return chamar('GET', '/api/v1/orders/' . rawurlencode($ref)); }
/** Confirma um aviso (webhook). $corpoBruto é o texto recebido, sem o reescrever. */
function assinaturaValida(string $segredo, string $cabecalho, string $corpoBruto, int $tolerancia = 300): bool {
parse_str(str_replace(',', '&', $cabecalho), $p);
if (!isset($p['t'], $p['v1']) || !is_string($p['t']) || !is_string($p['v1']) || !ctype_digit($p['t']) || abs(time() - (int)$p['t']) > $tolerancia) return false; // só texto e dígitos: um cabeçalho com v1[]= não rebenta nem passa
$esperado = hash_hmac('sha256', $p['t'] . '.' . $corpoBruto, $segredo);
return hash_equals($esperado, $p['v1']);
}
if (PHP_SAPI === 'cli' && realpath($argv[0] ?? '') === __FILE__) {
[$codigo, $resposta] = enviarEntrega([
'ref' => 'FT 2026/1234',
'customer' => 'Café Central',
'address' => 'Rua do Souto 24, 4700-329 Braga',
'phone' => '253000000',
'win_from' => '09:00', 'win_to' => '12:00',
'lines' => [['item_ref' => 'A100', 'description' => 'Água 0,5 L (pack 24)', 'unit' => 'CX', 'qty' => 10]],
]);
echo $codigo, ' ', json_encode($resposta, JSON_UNESCAPED_UNICODE), "\n"; // 201 {"ref":"FT 2026/1234","resultado":"criada"}
echo json_encode(estadoDaEntrega('FT 2026/1234'), JSON_UNESCAPED_UNICODE), "\n";
}
Na assinatura, use sempre o corpo tal como chegou (bytes/texto brutos), nunca o JSON já lido e escrito de novo: qualquer espaço a mais muda a assinatura.
Erros
| Código | Significado |
|---|---|
| 400 | Pedido inválido (a mensagem em error diz o campo). |
| 401 | Chave em falta, inválida ou revogada. |
| 403 | A chave é só de leitura. |
| 404 | Entrega não encontrada. |
| 409 | A entrega já saiu e não se pode alterar ou cancelar. |
| 429 | Demasiados pedidos. Espere um minuto. |
As mensagens vêm em { "error": "…" }.
Boas práticas
- Pode repetir um pedido sem medo: a mesma
refnunca cria duas entregas. - Prefira enviar em lista (até 200) em vez de um pedido por entrega.
- Se a ligação falhar, tente outra vez mais tarde; se houver 429, espere antes de repetir.
- Para saber quando uma entrega termina, consulte o estado de vez em quando (a cada poucos minutos chega).