API de Parceiros Fintra · v1

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.