API de Parceiros Fintra — v1
Esta API deixa um sistema externo ler e criar dados de uma empresa que usa a Fintra: clientes, produtos, facturas e pagamentos.
É servidor-para-servidor. Não é uma API de browser, não é uma API de utilizador final, e não substitui a Fintra: há coisas que continuam a acontecer dentro da aplicação, por uma pessoa.
O que esta API faz, e o que não faz
| Faz | Não faz |
|---|---|
| Ler clientes, produtos, facturas e pagamentos | Emitir facturas fiscais |
| Criar e alterar clientes | Registar pagamentos |
| Criar e alterar produtos | Apagar seja o que for |
| Criar facturas em rascunho | Alterar ou cancelar facturas |
Três limites permanentes da v1, e é melhor sabê-los antes de desenhar a integração:
- As facturas nascem em rascunho (
status: "draft",invoice_code: "DRAFT-XXXXXXXX"). A emissão do documento fiscal é feita por uma pessoa, dentro da Fintra. Não existe nenhum endpoint de emissão — não o procure e não tente contorná-lo com outro verbo. - Pagamentos são só de leitura.
GET /paymentseGET /payments/{id}existem;POST /paymentsnão existe e devolve 404. - Nada se apaga. Não há
DELETEem nenhum recurso.
Base
https://api.fintra.co.mz/api/v1/partner
Todas as respostas são JSON, em UTF-8. Datas com hora vêm em ISO 8601
(2026-08-03T10:14:22+00:00); datas sem hora vêm como 2026-08-03.
Autenticação
Uma chave, num cabeçalho:
Authorization: Bearer fintra_sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
O que precisa de saber sobre a chave:
- A chave é da EMPRESA, não da pessoa. Ela não representa um utilizador da Fintra, não tem perfil e não muda de empresa. Tudo o que ela alcança é o que pertence à empresa a que foi emitida.
- É emitida pelo suporte Fintra (
suporte@fintra.co.mz) e mostrada uma única vez. Se a perder, pede-se uma nova; não há como recuperá-la. - Prefixos:
fintra_sk_live_(dados reais) efintra_sk_test_— leia a secção Modo de teste antes de contar com esta última. - Scopes: cada chave leva uma lista de permissões, uma por recurso e verbo:
customers:read,customers:write,products:read,products:write,invoices:read,invoices:write,payments:read. Uma chave só de leitura não escreve, e um pedido fora do scope devolve403 insufficient_scope. Peça ao suporte exactamente os scopes de que precisa. - A API de parceiros vem desligada em cada empresa. Se receber
403 partner_api_disabled, o suporte ainda não a activou nessa empresa.
Regra número um: a chave nunca vai para um browser
Não use esta API a partir de JavaScript numa página. Nem numa app móvel que embrulhe uma página, nem "só para o painel interno", nem "só em desenvolvimento".
Uma chave fintra_sk_live_... dentro de código que corre num browser é uma chave publicada: qualquer
visitante abre o inspector e fica com os livros da empresa — clientes, facturas, valores.
A API impede-o, e sem lhe perguntar a opinião: qualquer pedido que traga o cabeçalho Origin
leva 403 browser_use_forbidden antes de tocar em dados. Um fetch() de browser envia sempre
Origin; um cliente de servidor nunca envia.
Chame a API a partir do seu servidor, guarde a chave numa variável de ambiente, e nunca a escreva num ficheiro versionado.
Modo de teste — a verdade
As chaves fintra_sk_test_... existem, mas hoje só lêem.
Não existe empresa-sandbox: uma chave de teste aponta para os dados reais da mesma empresa, e a
única forma honesta de a manter inofensiva é bloquear-lhe a escrita. Qualquer POST ou PUT com
uma chave de teste devolve 403 test_mode_read_only.
Consequência prática: para desenvolver a parte de escrita da sua integração precisa de uma chave
live, e o que ela criar é real (rascunhos reais, clientes reais). Faça-o numa empresa de ensaio
da sua conta, não na do cliente. Um sandbox com dados próprios está previsto, mas não existe — não
desenhe a sua integração a contar com ele.
Idempotência — obrigatória em todas as escritas
Todo o POST e todo o PUT exigem o cabeçalho Idempotency-Key. Sem ele: 400 idempotency_key_required.
Idempotency-Key: 3f2a9c1e-7b44-4f0e-9a1d-6c5e2b8d0417
Porquê: se a rede falhar depois de a Fintra ter gravado, mas antes de a resposta lhe chegar, você
não sabe se o registo existe. Com a chave, basta repetir o pedido igual, com a mesma chave:
recebe a resposta original — o mesmo corpo e o mesmo estado HTTP (um POST repetido volta a
responder 201, não 200) — mais o cabeçalho Idempotency-Replay: true, e não se cria um segundo
registo.
Regras:
- Gere um UUID v4 novo por cada pedido novo.
- 8 a 191 caracteres, apenas letras, dígitos,
.,_,:e-. - A mesma chave com um corpo diferente →
409 idempotency_key_reuse. - ⚠️ "Igual" quer dizer byte a byte. A comparação é feita sobre o corpo cru do pedido, não sobre
o JSON interpretado: um espaço a mais, uma quebra de linha ou os campos por outra ordem já contam
como um pedido diferente e dão
409. Na sua rotina de repetição reenvie exactamente a mesma string que enviou da primeira vez — guarde-a, em vez de voltar a serializar o objecto. - A mesma chave enquanto o primeiro pedido ainda corre →
409 idempotency_request_in_flight; espere e repita. - A chave é guardada 24 horas. Passado esse prazo fica livre outra vez — não a reutilize à espera do replay.
Gerar um UUID v4:
uuidgen # macOS, Linux
$key = \Illuminate\Support\Str::uuid()->toString(); // PHP / Laravel
import uuid; key = str(uuid.uuid4()) # Python
Paginação
Todas as listas são paginadas. Não existe forma de pedir "tudo".
| Parâmetro | Default | Máximo |
|---|---|---|
page |
1 | — |
per_page |
25 | 100 |
Pedir per_page=1000 não é erro: é reduzido a 100 em silêncio. Confirme sempre o per_page que
vem na resposta em vez de assumir o que pediu.
"pagination": { "total": 214, "count": 25, "per_page": 25, "current_page": 1, "last_page": 9 }
Percorra até current_page == last_page. Para sincronizações incrementais use updated_since
(clientes, produtos, facturas) em vez de reler tudo.
Ordenação
sort e order (asc | desc). Cada recurso aceita uma lista fechada de campos —
um campo fora da lista devolve 422 invalid_query_parameter com a lista válida dentro do hint.
Erros
Todas as falhas — todas, sem excepção — têm a mesma forma:
{
"error": {
"code": "insufficient_scope",
"message": "A chave não tem permissão para esta operação.",
"hint": "Esta chave não inclui o scope \"invoices:write\". Peça ao suporte Fintra uma chave que o inclua.",
"doc_url": "https://developers.fintra.co.mz/erros#insufficient_scope",
"request_id": "0f1c2f26-9b7e-4a6b-8f2a-1d3c5b7e9a01"
}
}
Escreva um parser de erros: error.code é estável e é por ele que deve ramificar o seu código.
message e hint são para humanos e podem mudar. O request_id vem também no cabeçalho
X-Request-Id — guarde-o nos seus logs, é por ele que o suporte Fintra encontra o pedido.
A lista completa está no catálogo de erros.
Ids de outra empresa dão 404
Um id que não existe, um id mal formado e um id de outra empresa devolvem exactamente o mesmo
404 resource_not_found. É deliberado: se a resposta distinguisse os casos, a API dizia
"existe, mas não é seu" — e isso já é informação sobre os dados de outra empresa.
Portanto: um 404 não prova que o registo não existe. Prova que a sua chave não lhe chega.
Limites de tráfego
Por chave:
| Limite | Valor |
|---|---|
| Pedidos por minuto | 60 |
| Escritas por minuto | 10 |
| Pedidos por dia | 5 000 |
Por empresa (somando todas as chaves dela): 120 pedidos/minuto. Pedidos sem chave válida, por IP: 30/minuto.
Ao exceder recebe 429 rate_limit_exceeded com o cabeçalho Retry-After em segundos.
Respeite-o: espere esse tempo exacto e repita. Não repita em ciclo apertado — cada tentativa
recusada conta na mesma para o limite e afasta-o do momento em que voltaria a passar.
Um cliente decente faz exponential backoff a partir do Retry-After e nunca mais de 5 tentativas.
Primeiro pedido
GET /ping confirma, de uma vez, que a chave é válida, qual a empresa a que ela pertence, em que
modo está e que scopes tem:
curl -s https://api.fintra.co.mz/api/v1/partner/ping \
-H "Authorization: Bearer $FINTRA_API_KEY"
{
"data": {
"tenant_id": 36,
"tenant_name": "Bar Kanimambo",
"mode": "live",
"scopes": ["customers:read","customers:write","products:read","products:write","invoices:read","invoices:write","payments:read"],
"customers_visible": 12,
"request_id": "6a2f0f4e-0e40-4a0a-9c6a-2c1b8d5f7a10"
}
}
Se isto responder, a integração pode começar. Continue nos endpoints.
A escrever a integração com a ajuda de uma IA? Dê-lhe primeiro o ficheiro llms.txt — traz estas regras em forma de instruções.