Invoices

Emite e gere facturas certificadas pela AGT programaticamente.

Criar Factura

POST/v1/organisations/:orgId/invoices

Uma factura precisa de três elementos: organização, cliente e produto. Usa customers.ensure() e products.create() para garantir que existem antes de emitir.

TypeScript
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 AGT

Corpo do pedido

CampoTipoDescrição
customerIdstring (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
establishmentIdstring (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
receivedInstring (uuid)?Só FR/FA: conta de tesouraria onde o dinheiro entrou (GET /treasury-accounts). Default: conta por omissão da empresa
receivedAtstring?Só FR/FA: data do recebimento (AAAA-MM-DD ou ISO 8601). Default: agora
itemsInvoiceItem[]Linhas da factura - mínimo 1 item
items[].descriptionstringDescrição do produto/serviço
items[].quantityintegerQuantidade - mínimo 1
items[].unitPriceintegerPreço unitário em kwanzas × 100
items[].taxRate0 | 5 | 14 | undefinedTaxa 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[].taxExemptionCodestring (3 chars)?Código de isenção AGT - obrigatório quando taxRate resultar em 0, excepto para Consumidor Final (ex: M04, M10, M13)
items[].lineDiscountnumber (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[].productIdstring (uuid)?Produto do catálogo
items[].referencestring?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[].withholdingTaxboolean?Retenção na fonte nesta linha (ex.: um serviço sujeito ao Art. 67º). Por omissão, não. Ver secção abaixo.
idempotencyKeystring?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
notesstring?Notas internas (não aparecem na factura)
applyWithholdingTaxboolean?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)

JSON
{
  "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, validated ou rejected. A submissão ocorre em segundo plano - faz GET ao documento passado alguns segundos para confirmar. Distingue-se de status, que reflete o ciclo de vida do documento (emitido/pago/cancelado), não a validação fiscal.
  • agtErrors - Lista de erros devolvidos pela AGT quando agtStatus é rejected.
  • withholdingTax - null excepto quando há linhas com items[].withholdingTax: true (ou applyWithholdingTax) 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%), amount em kwanzas × 100.
  • advanceDeductions - null excepto 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/total sem desconto); o adiantamento é regularizado por uma nota de crédito sobre a FA (creditNoteNumber) e conta como já pago: advanceNet + advanceTax. Falta pagar total - 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çalho Authorization da 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.

GET/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.

TypeScript
// 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; creditNoteNumber aparece 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 - amount maior do que o saldo (vem available).
  • 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.

pendingDocumento acabado de criar
issuedEmitida
paidMarcada como paga (FR paga automaticamente na emissão)
cancelledAnulada - sem valor fiscal

Cancelar Factura

POST/v1/organisations/:orgId/invoices/:id/cancel

Cancela uma factura emitida. Para documentos com valor fiscal (FT), gera automaticamente uma Nota de Crédito para anular o efeito contabilístico.

TypeScript
await vero.invoices.cancel(ORG_ID, invoice.id, 'Pedido do cliente')

Valores Monetários

Todos os valores usam kwanzas × 100 (inteiros) - nunca floats.

TypeScript
// 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"