Catálogo de erros
Toda a falha da API de Parceiros Fintra devolve o mesmo envelope:
{
"error": {
"code": "…",
"message": "…",
"hint": "…",
"doc_url": "https://developers.fintra.co.mz/erros#…",
"request_id": "…"
}
}
Ramifique sempre por error.code, nunca pelo texto de message: o código é estável, o texto não.
O request_id repete-se no cabeçalho X-Request-Id; guarde-o nos seus logs.
Só o validation_failed traz um campo a mais (error.errors).
Estes 15 códigos são os únicos que a v1 emite. Se receber um code que não esteja aqui, é um erro
nosso — envie o request_id para suporte@fintra.co.mz.
| Código | HTTP | Repetir o pedido ajuda? |
|---|---|---|
invalid_api_key |
401 | Não |
api_key_expired |
401 | Não |
api_key_revoked |
401 | Não |
partner_api_disabled |
403 | Não |
browser_use_forbidden |
403 | Não |
test_mode_read_only |
403 | Não |
insufficient_scope |
403 | Não |
resource_not_found |
404 | Não |
invalid_query_parameter |
422 | Não |
validation_failed |
422 | Não |
idempotency_key_required |
400 | Não |
idempotency_key_invalid |
400 | Não |
idempotency_request_in_flight |
409 | Sim, com a MESMA chave |
idempotency_key_reuse |
409 | Não |
rate_limit_exceeded |
429 | Sim, após Retry-After |
401 — a chave
invalid_api_key #
HTTP 401. A chave não veio, veio noutro formato, ou não é conhecida.
Causas concretas: falta o cabeçalho Authorization; o cabeçalho não começa por Bearer (com
espaço); a chave foi copiada com espaços ou quebra de linha; a chave não existe.
O que fazer: confirme o cabeçalho exacto — Authorization: Bearer fintra_sk_live_.... Se estiver
correcto e continuar a falhar, a chave não é válida: peça uma nova ao suporte Fintra. Não repita
o pedido em ciclo — pedidos sem chave válida têm um tecto de 30/minuto por IP e passará a apanhar 429.
{"error":{"code":"invalid_api_key","message":"Chave de API inválida ou em falta.","hint":"Envie o cabeçalho \"Authorization: Bearer fintra_sk_live_...\". A chave é mostrada uma única vez na emissão; se a perdeu, peça uma nova ao suporte Fintra.","doc_url":"https://developers.fintra.co.mz/erros#invalid_api_key","request_id":"…"}}
api_key_expired #
HTTP 401. A chave existe mas passou da data de validade (máximo 365 dias).
O que fazer: peça uma chave nova ao suporte. Repetir não adianta. Se a sua integração é de longo prazo, ponha um lembrete 30 dias antes da expiração — a API não avisa.
api_key_revoked #
HTTP 401. A chave foi desactivada pelo suporte Fintra.
Normalmente isto acontece porque a chave foi exposta, porque a empresa pediu, ou porque foi substituída. O que fazer: pare de a usar e peça uma nova. Se não sabe porque foi revogada, pergunte antes de pedir outra — pode haver um incidente de segurança por trás.
403 — a porta está fechada
partner_api_disabled #
HTTP 403. A chave é válida, mas a API de parceiros não está activada nesta empresa. Vem desligada por omissão em todas as empresas.
O que fazer: peça ao suporte Fintra para a activar nessa empresa. Nada do lado do seu código resolve isto.
browser_use_forbidden #
HTTP 403. O pedido trouxe o cabeçalho Origin — sinal de JavaScript a correr num browser.
Este é o erro número um de quem começa, e o mais caro: uma chave dentro de código de browser fica visível a qualquer visitante da página, e com ela vêm os clientes, as facturas e os valores da empresa.
O que fazer: mova a chamada para o seu servidor. O browser fala com o seu servidor; o seu
servidor fala com a Fintra. Não tente remover o Origin do pedido — se conseguisse, a chave
continuaria publicada na mesma.
test_mode_read_only #
HTTP 403. Tentou escrever (POST/PUT) com uma chave fintra_sk_test_....
Não existe empresa-sandbox: as chaves de teste apontam para os dados reais e por isso só respondem a
GET. O que fazer: para desenvolver escrita use uma chave fintra_sk_live_... — e faça-o numa
empresa de ensaio, porque o que criar é real.
insufficient_scope #
HTTP 403. A chave é válida mas não inclui o scope exigido pela rota. O hint diz qual falta.
Scopes da v1: customers:read, customers:write, products:read, products:write,
invoices:read, invoices:write, payments:read.
O que fazer: peça ao suporte uma chave que inclua o scope em falta. Verifique os scopes que tem
com GET /ping antes de assumir que a rota está avariada.
404
resource_not_found #
HTTP 404. O id pedido não é alcançável por esta chave.
Um só código para três situações, de propósito: o id não existe, o id está mal formado
(/customers/abc), ou o id pertence a outra empresa. Se a resposta distinguisse os casos, a API
estava a confirmar a existência de dados de terceiros.
O que fazer: liste o recurso (GET /customers, GET /products, …) e use um id de lá.
Não conclua que o registo não existe — conclua que a sua chave não lhe chega.
Também é o que recebe ao criar uma factura para um customer_id que não é da empresa da chave.
422 — os dados
invalid_query_parameter #
HTTP 422. Um parâmetro da query string não é aceite. A message diz qual e o hint diz o que
serve.
Acontece com: sort fora da lista do recurso; order diferente de asc/desc; status de factura
fora dos estados válidos; datas (date_from, date_to, updated_since) que não são ISO 8601;
customer_id/invoice_id não numéricos.
Não acontece com per_page acima de 100 — esse é reduzido a 100 em silêncio.
O que fazer: corrija o parâmetro. O hint traz a lista de valores válidos; não a adivinhe.
validation_failed #
HTTP 422. O corpo de uma escrita não passou na validação. É o único erro com um campo extra,
error.errors: um mapa campo → lista de mensagens.
{
"error": {
"code": "validation_failed",
"message": "Os dados enviados não passaram na validação.",
"hint": "Corrija os campos indicados em \"errors\" e repita o pedido — pode usar a MESMA chave de idempotência, porque um pedido recusado não cria nada.",
"doc_url": "https://developers.fintra.co.mz/erros#validation_failed",
"request_id": "…",
"errors": {
"items.0.quantity": ["The items.0.quantity field must be at least 0.0001."],
"items.0.cost_price": ["Campo não aceite numa linha de factura. Campos aceites: product_id, name, description, quantity, price, discount, tax_id."]
}
}
}
As chaves de errors seguem a notação com pontos das linhas: items.0.price é o price da
primeira linha da factura (contagem a partir de 0).
⚠️ As mensagens dentro de errors vêm em inglês quando são as validações genéricas do
servidor (The … field must be …); só as regras próprias desta API é que trazem texto em português.
Não as mostre ao utilizador final tal como vêm, e não escreva código que dependa do texto delas —
o que é estável é a chave (items.0.quantity), não a frase.
O que fazer: corrija os campos e repita. Pode reutilizar a mesma Idempotency-Key — um
pedido recusado não criou nada, logo a chave não ficou gasta.
400 e 409 — a idempotência
idempotency_key_required #
HTTP 400. Um POST ou PUT sem o cabeçalho Idempotency-Key. É obrigatório em todas as
escritas, sem excepção.
O que fazer: gere um UUID v4 e envie-o em Idempotency-Key. Guarde-o do seu lado antes de fazer
o pedido: se a rede falhar, é ele que lhe permite repetir sem duplicar.
idempotency_key_invalid #
HTTP 400. O cabeçalho veio, mas com um formato que não serve.
Regras: 8 a 191 caracteres; apenas letras, dígitos, ., _, : e -. Sem espaços, sem /, sem
acentos. Um UUID v4 cumpre tudo isto.
O que fazer: use uuidgen, Str::uuid(), uuid.uuid4() ou equivalente. Não construa a chave
concatenando o nome do cliente.
idempotency_request_in_flight #
HTTP 409. Já existe um pedido a decorrer com esta mesma chave, e ainda não terminou.
Vem com Retry-After: 2.
Acontece quando o seu código repete demasiado cedo, ou quando dois processos seus disparam o mesmo pedido ao mesmo tempo.
O que fazer: espere os segundos indicados e repita com a MESMA chave — receberá a resposta original. Não gere uma chave nova: isso criaria um segundo registo, que é exactamente o que a idempotência existe para evitar. Este é o único 409 em que repetir é a acção certa.
idempotency_key_reuse #
HTTP 409. Esta chave já criou um registo, mas com um corpo diferente do que enviou agora.
Uma chave de idempotência serve um pedido. Repetir o mesmo pedido, sim; enviar outro pedido com a chave antiga, não.
⚠️ "Corpo diferente" é medido byte a byte, sobre o texto cru do pedido — não sobre o JSON
interpretado. {"phone":"+258849990001"} e {"phone": "+258849990001"} (um espaço) são, para este
efeito, dois pedidos diferentes, e o segundo dá 409. Se a sua rotina de repetição volta a serializar
o objecto, pode gerar bytes diferentes sem querer — sobretudo se a ordem dos campos não for estável.
Guarde a string exacta que enviou e reenvie essa.
O que fazer: gere um UUID novo para este pedido. E confirme o seu código: reutilizar a chave entre pedidos diferentes é normalmente um bug de uma variável que não foi reiniciada dentro do ciclo. As chaves são guardadas 24 horas; passado esse prazo ficam livres.
429
rate_limit_exceeded #
HTTP 429. Excedeu um dos limites. Vem com Retry-After (segundos) e, quando aplicável,
X-RateLimit-Limit e X-RateLimit-Remaining.
| Limite | Valor |
|---|---|
| Pedidos por minuto, por chave | 60 |
| Escritas por minuto, por chave | 10 |
| Pedidos por dia, por chave | 5 000 |
| Pedidos por minuto, por empresa | 120 |
| Pedidos por minuto sem chave válida, por IP | 30 |
O que fazer: leia o Retry-After, espere esse tempo, repita. Depois disso, faça backoff
exponencial (2×, 4×, 8× o intervalo) e desista ao fim de 5 tentativas com um erro no seu log.
Não repita em ciclo apertado: cada tentativa recusada continua a contar para o limite e só adia o
momento em que voltaria a passar. Se bate no tecto diário, o problema é o desenho — use
updated_since para sincronizar só o que mudou, em vez de reler tudo.