API de Parceiros Fintra · v1

Endpoints

Base: https://api.fintra.co.mz/api/v1/partner

Nos exemplos, a chave está numa variável de ambiente — é onde ela deve estar:

export FINTRA_API_KEY="fintra_sk_live_..."
export FINTRA_API="https://api.fintra.co.mz/api/v1/partner"

São catorze rotas. Não há mais nenhuma.

Método Rota Scope O que faz
GET /ping customers:read Confirma a chave e diz de que empresa é
GET /customers customers:read Lista clientes
GET /customers/{id} customers:read Um cliente
POST /customers customers:write Cria um cliente
PUT /customers/{id} customers:write Altera um cliente
GET /products products:read Lista produtos
GET /products/{id} products:read Um produto
POST /products products:write Cria um produto
PUT /products/{id} products:write Altera um produto
GET /invoices invoices:read Lista facturas
GET /invoices/{id} invoices:read Uma factura, com linhas
POST /invoices invoices:write Cria uma factura em rascunho
GET /payments payments:read Lista recebimentos
GET /payments/{id} payments:read Um recebimento

Não existe DELETE em lado nenhum, não existe PUT /invoices/{id}, e não existe POST /payments. Pedidos a rotas inexistentes devolvem o 404 genérico do servidor, não o envelope da API.

Nas escritas (POST, PUT) só são gravados os campos documentados. Um campo desconhecido não é ignorado em silêncio: devolve 422 validation_failed a dizer qual é.

Os Idempotency-Key que aparecem nos exemplos são ilustrativos. Gere um UUID novo em cada pedido seu: reaproveitar um destes com outro corpo devolve 409 idempotency_key_reuse.


GET /ping

O primeiro pedido de qualquer integração.

curl -s "$FINTRA_API/ping" -H "Authorization: Bearer $FINTRA_API_KEY"
{
  "data": {
    "tenant_id": 37,
    "tenant_name": "Mercearia Bom Preço",
    "mode": "live",
    "scopes": ["customers:read","customers:write","products:read","products:write","invoices:read","invoices:write","payments:read"],
    "customers_visible": 15,
    "request_id": "e6498572-62f0-4e85-ac5a-d7b71eb56445"
  }
}

mode diz live ou test; scopes diz o que a chave pode fazer. Verifique-os aqui em vez de descobrir num 403 no meio do trabalho.


Clientes

GET /customers

Parâmetros: search (nome, email, NUIT ou telefone), type (individual | company), updated_since (ISO 8601), page, per_page, sort (name, created_at, updated_at), order. Por omissão: created_at desc.

curl -s "$FINTRA_API/customers?search=Talho&per_page=2" \
  -H "Authorization: Bearer $FINTRA_API_KEY"
{
  "data": [
    {
      "id": 990060,
      "name": "Talho Nhamavila, Lda",
      "type": "company",
      "email": "geral@talhonhamavila.co.mz",
      "phone": "+258842223344",
      "contact_number": null,
      "nuit": "400123456",
      "address": "Av. 24 de Julho 1520, Maputo",
      "created_at": "2026-08-03T10:57:14+00:00",
      "updated_at": "2026-08-03T10:57:26+00:00"
    }
  ],
  "pagination": { "total": 1, "count": 1, "per_page": 2, "current_page": 1, "last_page": 1 }
}

GET /customers/{id}

curl -s "$FINTRA_API/customers/990060" -H "Authorization: Bearer $FINTRA_API_KEY"

Devolve {"data": { … }} com o mesmo objecto de cima. Um id de outra empresa devolve 404 resource_not_found — igual a um id que não existe.

POST /customers

Campos: name (obrigatório), type (individual | company), email, phone, contact_number, nuit, address. O email, quando enviado, tem de ser único dentro da empresa.

curl -s -X POST "$FINTRA_API/customers" \
  -H "Authorization: Bearer $FINTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 7c9e1a52-3f18-4b2c-9d47-8a0f6e2b1c34" \
  -d '{
    "name": "Talho Nhamavila, Lda",
    "type": "company",
    "nuit": "400123456",
    "email": "geral@talhonhamavila.co.mz",
    "phone": "+258841234567",
    "address": "Av. 24 de Julho 1520, Maputo"
  }'

HTTP 201.

{
  "data": {
    "id": 990060,
    "name": "Talho Nhamavila, Lda",
    "type": "company",
    "email": "geral@talhonhamavila.co.mz",
    "phone": "+258841234567",
    "contact_number": null,
    "nuit": "400123456",
    "address": "Av. 24 de Julho 1520, Maputo",
    "created_at": "2026-08-03T10:57:14+00:00",
    "updated_at": "2026-08-03T10:57:14+00:00"
  }
}

PUT /customers/{id}

Os mesmos campos, todos opcionais: envia-se só o que muda.

curl -s -X PUT "$FINTRA_API/customers/990060" \
  -H "Authorization: Bearer $FINTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 2e6f4a90-1b83-4c7d-9e05-6f8a2b4c1d37" \
  -d '{"phone": "+258842223344"}'

HTTP 200, com o cliente completo já actualizado.


Produtos

⚠️ Um produto criado ou alterado por aqui chega ao balcão: entra na sincronização do POS e aparece nos telemóveis e tablets da empresa sem passar por mais nenhum ecrã. É o comportamento desejado, mas convém saber que não há um passo de aprovação pelo meio.

GET /products

Parâmetros: search (nome, código ou SKU), type, is_for_sale (true/false), updated_since, page, per_page, sort (name, price, stock, created_at, updated_at), order.

curl -s "$FINTRA_API/products?search=Farinha&per_page=1" \
  -H "Authorization: Bearer $FINTRA_API_KEY"
{
  "data": [
    {
      "id": 990186,
      "name": "Farinha de milho 5 kg",
      "product_code": "GO-0009",
      "sku": "FAR-MIL-5KG",
      "description": null,
      "type": "GOODS",
      "unity": "saco",
      "price": "475.00",
      "pos_price": null,
      "vat_included": false,
      "tax_rate": null,
      "stock": 0,
      "reorder_level": 0,
      "tracks_inventory": true,
      "is_for_sale": true,
      "categories": [],
      "created_at": "2026-08-03T10:57:15+00:00",
      "updated_at": "2026-08-03T10:57:40+00:00"
    }
  ],
  "pagination": { "total": 1, "count": 1, "per_page": 1, "current_page": 1, "last_page": 1 }
}

Valores monetários vêm como texto ("475.00"), de propósito: em meticais, um float de JavaScript arredonda mal. Converta para decimal, nunca para vírgula flutuante.

GET /products/{id}

curl -s "$FINTRA_API/products/990186" -H "Authorization: Bearer $FINTRA_API_KEY"

POST /products

Obrigatórios: name, type, inventory, price. Opcionais: description, sku, unity, pos_price, vat_included, reorder_level, is_for_sale, is_for_purchase, tax_id.

curl -s -X POST "$FINTRA_API/products" \
  -H "Authorization: Bearer $FINTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: b31d47f0-6c2a-4e91-8f55-2a7d9e0c4b18" \
  -d '{
    "name": "Farinha de milho 5 kg",
    "type": "GOODS",
    "inventory": "INVENTORY",
    "price": 450,
    "sku": "FAR-MIL-5KG",
    "unity": "saco"
  }'

HTTP 201, com o produto criado. O product_code (GO-0009) é atribuído pela Fintra.

PUT /products/{id}

curl -s -X PUT "$FINTRA_API/products/990186" \
  -H "Authorization: Bearer $FINTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 9a1c5e30-7d24-4f8b-a063-1e5c7b9d2f46" \
  -d '{"price": 475}'

HTTP 200. Não há forma de apagar um produto por esta API.


Facturas

GET /invoices

Parâmetros: status (draft, unpaid, partially_paid, paid, cancelled), customer_id, date_from, date_to, updated_since, page, per_page, sort (invoice_date, due_date, grand_total, created_at, updated_at), order. Por omissão: invoice_date desc.

curl -s "$FINTRA_API/invoices?status=draft&per_page=1" \
  -H "Authorization: Bearer $FINTRA_API_KEY"

Cada factura traz cabeçalho, totais, o cliente e as linhas. Nunca traz lançamentos contabilísticos, custos ou margens — isso não sai desta API.

GET /invoices/{id}

curl -s "$FINTRA_API/invoices/992168" -H "Authorization: Bearer $FINTRA_API_KEY"
{
  "data": {
    "id": 992168,
    "invoice_code": "DRAFT-821FA1DA",
    "document_type": "FACTURA",
    "status": "draft",
    "reference": "Encomenda 2026/114",
    "invoice_date": "2026-08-03",
    "due_date": "2026-09-02",
    "currency": "MZN",
    "exchange_rate": 1,
    "subtotal": "4500.00",
    "discount": "0.00",
    "vat_base": "0.00",
    "tax": "0.00",
    "grand_total": "4500.00",
    "paid_amount": "0.00",
    "remaining_amount": "4500.00",
    "customer": { "id": 990060, "name": "Talho Nhamavila, Lda" },
    "items": [
      {
        "id": 3523,
        "product_id": 990186,
        "name": "Farinha de milho 5 kg",
        "quantity": 10,
        "price": "450.00",
        "discount": "0.00",
        "discount_percentage": 0,
        "tax_amount": "0.00",
        "tax_rate": null,
        "tax_included": false,
        "total": "4500.00"
      }
    ],
    "created_at": "2026-08-03T10:57:25+00:00",
    "updated_at": "2026-08-03T10:57:25+00:00"
  }
}

(Resposta abreviada: o objecto real traz ainda payment_notice_code, is_payment_notice, notice_kind, is_pos_sale, payment_reference, global_discount_amount e notes.)

POST /invoices

Cria a factura em rascunho, e só isso.

Obrigatórios: customer_id, invoice_date, items (1 a 200 linhas). Opcionais: due_date, reference, notes, currency (MZN, USD, ZAR), exchange_rate.

Cada linha aceita exactamente estes campos: product_id, name, description, quantity (obrigatório), price (obrigatório), discount, tax_id. Qualquer outro é recusado com o índice da linha na mensagem.

⚠️ Toda a linha tem de trazer um product_id. A v1 não suporta linhas de texto livre: uma linha só com name (por exemplo "Serviço de entrega") não é aceite. Se precisa de facturar um serviço, crie-o primeiro como produto (POST /products com "inventory": "NON_INVENTORY") e use o id dele.

discount numa linha é uma percentagem (0 a 100), não um valor em meticais.

O price de cada linha é obrigatório e é o seu. A Fintra não vai buscar o preço ao produto: se enviar product_id sem price, o pedido é recusado com 422 validation_failed. Leia o preço em GET /products e envie-o — é o que lhe permite facturar um preço combinado diferente do de tabela.

curl -s -X POST "$FINTRA_API/invoices" \
  -H "Authorization: Bearer $FINTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 5d8b3c17-9a04-4e6f-b21d-7c3f0e9a8b52" \
  -d '{
    "customer_id": 990060,
    "invoice_date": "2026-08-03",
    "due_date": "2026-09-02",
    "currency": "MZN",
    "reference": "Encomenda 2026/114",
    "items": [
      { "product_id": 990186, "quantity": 10, "price": 450 }
    ]
  }'

HTTP 201, com exactamente o mesmo objecto que um GET /invoices/{id} devolveria.

Repare no que volta:

Isto não é uma factura fiscal. O número fiscal não foi consumido, o stock não se mexeu e não houve lançamento contabilístico nenhum. Alguém da empresa abre a Fintra, confere o rascunho e emite-o — e é aí que o documento passa a existir para as Finanças.

Não existe endpoint de emissão. Se a sua integração precisa de facturas emitidas automaticamente, essa conversa é com a Fintra, não com esta API.

Se o customer_id não for da empresa da chave, recebe 404 resource_not_found.


Pagamentos (só leitura)

Não existe forma de registar um pagamento por esta API. Estas duas rotas mostram os recebimentos contra factura que já existem na Fintra.

GET /payments

Parâmetros: invoice_id, date_from, date_to, page, per_page, sort (payment_date, amount, created_at, updated_at), order. Por omissão: payment_date desc.

curl -s "$FINTRA_API/payments?per_page=1" -H "Authorization: Bearer $FINTRA_API_KEY"
{
  "data": [
    {
      "id": 990637,
      "payment_code": "PA-2026-0375",
      "payment_date": "2026-08-01",
      "amount": "1572.00",
      "payment_method": "Dinheiro",
      "payment_method_type": "cash",
      "reference": "Payment for POS Sale #991779",
      "notes": null,
      "invoice": { "id": 991779, "invoice_code": "FR-2026-0377" },
      "created_at": "2026-08-01T22:26:37+00:00",
      "updated_at": "2026-08-01T22:26:37+00:00"
    }
  ],
  "pagination": { "total": 377, "count": 1, "per_page": 1, "current_page": 1, "last_page": 377 }
}

Só saem recebimentos ligados a uma factura da empresa. Salários, pagamentos a fornecedores e qualquer outro movimento de tesouraria não aparecem aqui, e isso é deliberado.

GET /payments/{id}

curl -s "$FINTRA_API/payments/990637" -H "Authorization: Bearer $FINTRA_API_KEY"

Uma integração típica, por ordem

  1. GET /ping — confirma chave, empresa e scopes.
  2. GET /products — traz o catálogo (pagine até last_page).
  3. GET /customers?search=… — procura o cliente; se não existir, POST /customers.
  4. POST /invoices — cria o rascunho, com Idempotency-Key.
  5. Uma pessoa emite o documento dentro da Fintra.
  6. GET /invoices/{id} mais tarde — para ver o status e o invoice_code fiscal.
  7. GET /payments?invoice_id=… — para saber se já foi pago.

Para sincronizar depois, use updated_since em vez de reler tudo: é o que mantém o seu consumo dentro dos 5 000 pedidos por dia.

Todos os erros possíveis estão no catálogo de erros.