Invoices
Emite e gere facturas certificadas pela AGT programaticamente.
Criar Factura
/v1/organisations/:orgId/invoicesUma factura precisa de três elementos: organização, cliente e produto. Usa customers.ensure() e products.create() para garantir que existem antes de emitir.
import { createVeroClient } from '@veroao/node'
const vero = createVeroClient({ secretKey: process.env.VERO_API_KEY! })
const ORG_ID = process.env.VERO_ORG_ID!
// 1. Garantir cliente
const customer = await vero.customers.ensure(ORG_ID, {
externalId: 'user_123',
name: 'Empresa ABC',
taxId: '5000123456',
phone: '923456789',
email: 'financeiro@empresaabc.ao',
addressLine1: 'Rua Amílcar Cabral, 45',
city: 'Luanda',
})
// 2. Garantir produto no catálogo
const product = await vero.products.create(ORG_ID, {
code: 'CONSULTORIA-TI',
name: 'Serviços de Consultoria TI',
description: 'Consultoria técnica mensal',
unitPrice: 15000000, // 150 000,00 AOA
taxRate: 14,
unitOfMeasure: 'UN',
})
// 3. Emitir factura
const invoice = await vero.invoices.create(ORG_ID, {
customerId: customer.id,
documentType: 'FT',
items: [{
description: product.name,
quantity: 1,
unitPrice: product.unitPrice,
taxRate: product.taxRate,
productId: product.id,
}],
idempotencyKey: `order_${orderId}`,
})
console.log(invoice.number) // "FT FT6326S62896N/1"
console.log(invoice.pdfUrl) // URL do PDF certificado
console.log(invoice.atcud) // código de verificação AGTCorpo do pedido
| Campo | Tipo | Descrição |
|---|---|---|
| customerId | string (uuid) | ID do cliente - obrigatório |
| documentType | 'FT' | 'FR' | 'FA' | FT = Factura · FR = Factura-Recibo · FA = Factura de Adiantamento (default: FT). Ver secção abaixo |
| establishmentId | string (uuid)? | Estabelecimento onde se emite (mesmo NIF, séries próprias na AGT). Sem ele, a sede. Ver Estabelecimentos API |
| advances | { invoiceId, amount? }[]? | Só FT/FR: adiantamentos (FA) do mesmo cliente a descontar, máx. 10. amount = quanto, com IVA, em kwanzas × 100; sem amount desconta tudo o que resta |
| receivedIn | string (uuid)? | Só FR/FA: conta de tesouraria onde o dinheiro entrou (GET /treasury-accounts). Default: conta por omissão da empresa |
| receivedAt | string? | Só FR/FA: data do recebimento (AAAA-MM-DD ou ISO 8601). Default: agora |
| items | InvoiceItem[] | Linhas da factura - mínimo 1 item |
| items[].description | string | Descrição do produto/serviço |
| items[].quantity | integer | Quantidade - mínimo 1 |
| items[].unitPrice | integer | Preço unitário em kwanzas × 100 |
| items[].taxRate | 0 | 5 | 14 | undefined | Taxa IVA - opcional. Se omitida, é resolvida a partir do regime de IVA configurado na organização (default 14% se a organização não tiver regime configurado) |
| items[].taxExemptionCode | string (3 chars)? | Código de isenção AGT - obrigatório quando taxRate resultar em 0, excepto para Consumidor Final (ex: M04, M10, M13) |
| items[].lineDiscount | number (0-100)? | Desconto da linha em percentagem (ex: 10 = 10%), aplicado ao preço unitário antes do IVA. Somado em "Valor de descontos" no PDF. Desconto em kwanzas: converte para % da linha |
| items[].productId | string (uuid)? | Produto do catálogo |
| items[].reference | string? | Referência do produto (ex.: HP305-AZ), até 60 caracteres. Opcional: aparece no PDF por baixo do nome. Sem ela, usa-se a do produto (productId), se o cliente a tiver escrito. |
| items[].withholdingTax | boolean? | Retenção na fonte nesta linha (ex.: um serviço sujeito ao Art. 67º). Por omissão, não. Ver secção abaixo. |
| idempotencyKey | string? | Chave de idempotência - gerada automaticamente pelo SDK. Com a mesma chave na mesma organização, devolve a factura já emitida em vez de criar outra. Em sandbox nada é guardado, por isso não há deduplicação |
| notes | string? | Notas internas (não aparecem na factura) |
| applyWithholdingTax | boolean? | Forma antiga: retenção em todas as linhas. Só se usa quando nenhuma linha traz items[].withholdingTax - prefira marcar as linhas. |
Resolução automática de taxRate por regime
Recomendamos omitir items[].taxRate e deixar o servidor resolvê-lo a partir do regime de IVA configurado na organização (Dashboard → Definições → Perfil → Regime de IVA). Organizações em regime isento ou simplificado (rotulado "Exclusão" no dashboard) não podem emitir linhas com taxa diferente de 0% - a API devolve 422 se enviares uma taxa incompatível, em vez de corrigir silenciosamente.
422 tax_rate_incompatible_with_regime- taxRate enviado não é 0 numa organização isenta/simplificada.422 tax_exemption_code_required- taxRate resultou em 0 mas falta taxExemptionCode (e não é Consumidor Final nem regime simplificado).422 invalid_tax_rate- taxRate enviado não é 0, 5 nem 14.
Retenção na fonte (Art. 67º do Código do Imposto Industrial)
Marque as linhas sujeitas com items[].withholdingTax: true: na mesma factura pode haver um serviço com retenção e outro artigo sem ela. Está sempre disponível, sem nada para activar; as linhas vêm sem retenção por omissão. A base é a soma das linhas marcadas, sem IVA e com o desconto da linha; abaixo do limiar mínimo (20 000 Kz por omissão) não há retenção. O cálculo (tipo, taxa, limiar) é feito inteiramente no servidor - a API nunca aceita um valor de retenção calculado pelo chamador. A resposta traz withholdingTax.base e cada linha com o seu withholdingTax.
Importante: a retenção não altera subtotal, taxAmount nem total - esses continuam o valor fiscal oficial da factura, enviado à AGT em documentTotals. A retenção viaja à parte, no campo withholdingTax da resposta e no withholdingTaxList do documento submetido à AGT. É responsabilidade de quem paga a factura reter esse valor e entregá-lo à AGT - o Vero só declara.
Resposta (201 Created)
{
"id": "a1b2c3d4-...",
"number": "FT FT6326S62896N/1",
"displayCode": "B6B402",
"documentType": "FT",
"status": "issued",
"subtotal": 13157895,
"taxAmount": 1842105,
"total": 15000000,
"currency": "AOA",
"atcud": "FT6326S62896N-1",
"hash": "4k92Xm...",
"previousHash": "0",
"jwsSignature": "eyJhbGciOiJSUzI1NiJ9...",
"pdfUrl": "/v1/organisations/779b90ce-.../invoices/a1b2c3d4-.../pdf",
"agtStatus": "pending",
"agtErrors": null,
"withholdingTax": null,
"issuedAt": "2026-06-25T08:30:00.000Z",
"createdAt": "2026-06-25T08:30:00.000Z"
}Campos de Facturação Electrónica (DFe)
number- Número AGT certificado no formato{tipo} {série}/{sequencial}(ex:FT FT6326S62896N/1) - a repetição do tipo/série é intencional, faz parte do formato oficial da AGT.atcud- Código único de validação:validationCode-sequência. Aparece no PDF e é verificável no portal AGT.hash- Hash de integridade do documento (SHA-256 encadeado).previousHash- Hash do documento anterior na série - garante cadeia de integridade.jwsSignature- Assinatura RS256 do documento submetida à AGT. As primeiras 4 letras aparecem no rodapé do PDF.agtStatus- Estado da validação junto da AGT:pending,validatedourejected. A submissão ocorre em segundo plano - fazGETao documento passado alguns segundos para confirmar. Distingue-se destatus, que reflete o ciclo de vida do documento (emitido/pago/cancelado), não a validação fiscal.agtErrors- Lista de erros devolvidos pela AGT quandoagtStatusérejected.withholdingTax-nullexcepto quando há linhas comitems[].withholdingTax: true(ouapplyWithholdingTax) e a base passa o limiar. Formato:{ type: 'II' | 'IRT', rate, base, amount, description }-baseé a soma das linhas marcadas, sem IVA -rateé a percentagem × 100 (ex: 650 = 6,50%),amountem kwanzas × 100.advanceDeductions-nullexcepto numa FT/FR que descontou adiantamentos: uma entrada por FA e taxa de IVA,{ advanceInvoiceId, number, taxRate, net, tax, creditNoteNumber }. A factura vale o total (subtotal/taxAmount/totalsem desconto); o adiantamento é regularizado por uma nota de crédito sobre a FA (creditNoteNumber) e conta como já pago:advanceNet+advanceTax. Falta pagartotal - advanceNet - advanceTax.pdfUrl- Caminho relativo à tua API base. O PDF é gerado na hora a cada pedido (não fica em storage) - pede-o com o mesmo cabeçalhoAuthorizationda tua chave.
Factura de Adiantamento (FA)
O cliente pagou uma parte antes de se facturar tudo (um sinal, uma primeira tranche). Emite uma documentType: 'FA': fica paga no acto, como a FR, e não mexe em stock nem leva retenção. Quando facturares tudo, indica a FA em advances na FT/FR final: a factura sai pelo valor total e o Vero emite logo a seguir uma nota de crédito sobre a FA (a lei manda regularizar o adiantamento assim: Decreto Presidencial 71/25, art. 8.º n.º 10). O cliente paga só a diferença: o recibo da FT já conta o adiantamento como pago.
/v1/organisations/:orgId/advances?customerId=&proformaId=FA emitidas e não canceladas que ainda têm saldo, de um cliente ou de uma pro-forma. remaining é o que ainda se pode descontar, com IVA (há também remainingNet, remainingTax e byRate). Apps OAuth: invoices:read ou invoices:write.
// 1. Sinal recebido: FA paga no acto
const fa = await vero.invoices.create(ORG_ID, {
customerId: customer.id,
documentType: 'FA',
items: [{ description: 'Adiantamento da obra', quantity: 1, unitPrice: 50000000, taxRate: 14 }],
})
// 2. Factura final com o adiantamento descontado
const open = await vero.invoices.advances(ORG_ID, { customerId: customer.id })
const invoice = await vero.invoices.create(ORG_ID, {
customerId: customer.id,
items: [{ description: 'Obra completa', quantity: 1, unitPrice: 100000000, taxRate: 14 }],
advances: [{ invoiceId: open[0].id }], // sem amount: tudo o que resta
})
invoice.total // 114000000 = 1 140 000,00 Kz: o valor total da factura
invoice.advanceNet + invoice.advanceTax // 57000000 = 570 000,00 Kz: o adiantamento, já pago
invoice.advanceDeductions // [{ advanceInvoiceId, number: 'FA ...', taxRate: 14, net, tax, creditNoteNumber: 'NC ...' }]Regras e erros
- Uma NC por FA, com uma linha por taxa de IVA (na proporção do saldo), a referenciar a FA na AGT. O PDF da factura final diz que adiantamento foi descontado, por que NC, e quanto falta pagar.
- Se a NC não puder ser emitida na hora (AGT indisponível), a factura fica emitida e o Vero emite a NC pouco depois;
creditNoteNumberaparece nessa altura. - Sinal de uma pro-forma: POST /proformas/:id/advance.
422 advance_not_available- a FA não existe, é de outro cliente, foi cancelada ou já não tem saldo.422 advance_exceeds_balance-amountmaior do que o saldo (vemavailable).422 advance_exceeds_invoice- os adiantamentos são mais do que a factura.422 advance_on_advance,advance_duplicated,advance_currency_mismatch,advances_with_withholding- FA a descontar FA, a mesma FA duas vezes, outra moeda, factura com retenção.409 advance_in_use- anular ou creditar uma FA já descontada: a regularização já foi à AGT.409 invoice_has_advances- anular ou creditar uma factura final que regularizou adiantamentos: não se desfaz pela API (fala connosco).- Com chave de teste a FA sai em sandbox mas não fica guardada, por isso não há saldo para descontar (
422 advances_not_in_sandbox).
Status da Factura
status reflete o ciclo de vida do documento. Para saber se a AGT já validou o documento, consulta o campo agtStatus (pending / validated / rejected) - são independentes.
| pending | Documento acabado de criar |
| issued | Emitida |
| paid | Marcada como paga (FR paga automaticamente na emissão) |
| cancelled | Anulada - sem valor fiscal |
Cancelar Factura
/v1/organisations/:orgId/invoices/:id/cancelCancela uma factura emitida. Para documentos com valor fiscal (FT), gera automaticamente uma Nota de Crédito para anular o efeito contabilístico.
await vero.invoices.cancel(ORG_ID, invoice.id, 'Pedido do cliente')Valores Monetários
Todos os valores usam kwanzas × 100 (inteiros) - nunca floats.
// 150 000,00 AOA → enviar 15000000
// 1 500,50 AOA → enviar 150050
const toKwanzas = (aoa: number) => Math.round(aoa * 100)
const fromKwanzas = (k: number) => (k / 100).toFixed(2)
toKwanzas(150000) // → 15000000
fromKwanzas(15000000) // → "150000.00"