Emitir Documento
POST /v2/documentos/emitir
Envia documentos fiscais para validação na AGT. Um mesmo endpoint trata todos os tipos de documentos: facturas, facturas/recibo, notas de crédito, notas de débito, recibos e mais.
Porquê usar este endpoint? Este é o endpoint principal do gateway. Após criares as séries necessárias, usas este endpoint para submeter cada documento fiscal. A AGT aceita o documento imediatamente e devolve um requestIdAGT. A validação final é diferida — deves consultar o resultado posteriormente via status.
Request
Autenticação
Authorization: Bearer feak_...
ou
x-api-key: feak_...
Scope necessário: facturas:emitir ou *
Body (JSON)
Estrutura geral
| Campo | Tipo | Obrigatório | Descrição |
|---|
nif | string | ✅ Sim | NIF do contribuinte emissor |
privateKey | string | ✅ Sim | Chave privada RSA do contribuinte em PEM |
companyName | string | ✅ Sim | Nome/denominação do contribuinte emissor |
idempotencyKey | string | Não | Chave única para evitar duplicados |
documentos | array | ✅ Sim | Lista de documentos (mín 1, máx 30) |
Objecto documentos[]
| Campo | Tipo | Obrigatório | Descrição |
|---|
numero | string | ✅ Sim | Nº único do documento (ex: "FT FT3926S9043N/1") |
tipo | string | ✅ Sim | Tipo: FT, FR, NC, ND, RC, RG, etc. |
data | string | ✅ Sim | Data de emissão no formato YYYY-MM-DD |
cliente | object | ✅ Sim | Dados do cliente |
cliente.nome | string | ✅ Sim | Nome do cliente |
cliente.nif | string | ✅ Sim | NIF do cliente ("999999999" para consumidor final) |
cliente.pais | string | Não | Código ISO do país (default: "AO") |
itens | array | ⚠️ | Linhas do documento. Não usar para RC/RG/AR |
reciboDe | array | ⚠️ | Docs de origem do recibo. Apenas para RC/RG/AR |
totais | object | Não | Totais do documento (calculados automaticamente se omitido) |
totais.total | number | ✅ | Total com imposto |
totais.subtotal | number | Não | Total sem imposto |
totais.totalImpostos | number | Não | Total de imposto |
eacCode | string | Não | Código CAE (5 caracteres) |
Objecto itens[] — Linhas do documento
| Campo | Tipo | Obrigatório | Descrição |
|---|
codigo | string | ✅ Sim | Código do produto/serviço |
descricao | string | ✅ Sim | Descrição do produto/serviço |
quantidade | number | ✅ Sim | Quantidade (inteiro ou decimal) |
unidade | string | ✅ Sim | Unidade de medida (ex: "UN", "KG", "H") |
precoUnitario | number | ✅ Sim | Preço unitário sem imposto |
taxa | number | Não | Percentagem de IVA (default: 14). Usa 0 para isento |
motivoIsencao | string | Não | Código de isenção quando taxa: 0 (ex: "M00") |
desconto | number | Não | Valor do desconto na linha (default: 0) |
isServico | boolean | Não | true = serviço, false = bem (default) |
operationType | string | Não | Override do tipo de operação AGT |
retencaoTipo | string | Não | Tipo de retenção: "IRT", "II", "IS", "IVA" |
retencaoTaxa | number | Não | Percentagem de retenção (ex: 6.5 para 6.5%) |
referencia | object | ⚠️ | Documento origem. Obrigatório para NC e ND |
referencia.documento | string | ✅ | Nº do documento origem (ex: "FT FT3926S9043N/1") |
referencia.motivo | string | Não | Motivo da rectificação |
Objecto reciboDe[] — Apenas para RC/RG/AR
| Campo | Tipo | Obrigatório | Descrição |
|---|
documento | string | ✅ Sim | Nº do documento a regularizar |
dataDocumento | string | Não | Data do documento origem (YYYY-MM-DD) |
valor | number | ✅ Sim | Valor a regularizar (sem imposto) |
Response
Sucesso (200 OK)
{
"success": true,
"requestIdAGT": "202600002471491",
"dataEnvio": "2026-07-25T18:50:30.257Z",
"documentos": [
{ "numero": "FT FT3926S9043N/1", "tipo": "FT" }
]
}
Campos da Resposta
| Campo | Tipo | Descrição |
|---|
success | boolean | true — documento submetido à AGT |
requestIdAGT | string | ID da AGT para consultar o estado depois |
dataEnvio | string | Timestamp ISO 8601 do envio |
documentos | array | Lista de documentos submetidos |
⚠️ Importante: A AGT faz validação diferida. O success: true significa apenas que o documento foi aceite para processamento. Para saber se foi validado ou rejeitado, usa o status com o requestIdAGT.
Exemplos de Payloads
Factura Simples (FT)
{
"nif": "5000537039",
"privateKey": "-----BEGIN PRIVATE KEY-----\n...",
"companyName": "SONHE TESTE",
"documentos": [{
"numero": "FT FT3926S9043N/1",
"tipo": "FT",
"data": "2026-07-25",
"cliente": { "nome": "Cliente Exemplo", "nif": "999999999", "pais": "AO" },
"itens": [{
"codigo": "PROD001",
"descricao": "Produto de exemplo",
"quantidade": 2,
"unidade": "UN",
"precoUnitario": 5000,
"taxa": 14
}]
}]
}
Factura com Retenção na Fonte (FT + IRT)
{
"nif": "5000537039",
"privateKey": "-----BEGIN PRIVATE KEY-----\n...",
"companyName": "SONHE TESTE",
"documentos": [{
"numero": "FT FT3926S9043N/2",
"tipo": "FT",
"data": "2026-07-25",
"cliente": { "nome": "Cliente Serviço", "nif": "999999999", "pais": "AO" },
"itens": [{
"codigo": "CONS001",
"descricao": "Serviço de Consultoria",
"quantidade": 1,
"unidade": "UN",
"precoUnitario": 100000,
"taxa": 14,
"isServico": true,
"retencaoTipo": "IRT",
"retencaoTaxa": 6.5
}]
}]
}
Nota de Crédito (NC) — Referência a documento origem
{
"nif": "5000537039",
"privateKey": "-----BEGIN PRIVATE KEY-----\n...",
"companyName": "SONHE TESTE",
"documentos": [{
"numero": "NC NC3926S7016N/1",
"tipo": "NC",
"data": "2026-07-25",
"cliente": { "nome": "Cliente Devolução", "nif": "999999999", "pais": "AO" },
"itens": [{
"codigo": "PROD001",
"descricao": "Devolução de produto com defeito",
"quantidade": 1,
"unidade": "UN",
"precoUnitario": 5000,
"taxa": 14,
"referencia": {
"documento": "FT FT3926S9043N/1",
"motivo": "Produto com defeito"
}
}]
}]
}
Recibo (RC) — Sem linhas, com referência a documentos pagos
{
"nif": "5000537039",
"privateKey": "-----BEGIN PRIVATE KEY-----\n...",
"companyName": "SONHE TESTE",
"documentos": [{
"numero": "RC RC3926S2186N/1",
"tipo": "RC",
"data": "2026-07-25",
"cliente": { "nome": "Cliente Pagamento", "nif": "999999999", "pais": "AO" },
"reciboDe": [{
"documento": "FT FT3926S9043N/1",
"dataDocumento": "2026-07-25",
"valor": 10000
}]
}]
}
Lote / Batch (múltiplos documentos)
{
"nif": "5000537039",
"privateKey": "-----BEGIN PRIVATE KEY-----\n...",
"companyName": "SONHE TESTE",
"documentos": [
{
"numero": "FT FT3926S9043N/3",
"tipo": "FT",
"data": "2026-07-25",
"cliente": { "nome": "Cliente A", "nif": "999999999", "pais": "AO" },
"itens": [{ "codigo": "A001", "descricao": "Produto A", "quantidade": 1, "unidade": "UN", "precoUnitario": 3000, "taxa": 14 }]
},
{
"numero": "FR FR3926S6237N/1",
"tipo": "FR",
"data": "2026-07-25",
"cliente": { "nome": "Cliente B", "nif": "999999999", "pais": "AO" },
"itens": [{ "codigo": "B001", "descricao": "Produto B", "quantidade": 2, "unidade": "UN", "precoUnitario": 1500, "taxa": 14 }]
}
]
}
Erros
Erros de Autenticação
| HTTP | code | Mensagem | Causa |
|---|
| 401 | UNAUTHORIZED | Nao autorizado. | API Key não foi enviada |
| 401 | UNAUTHORIZED | API Key invalida. | API Key não existe ou foi revogada |
| 403 | FORBIDDEN | Scope insuficiente. | A key não tem o scope facturas:emitir |
Erros de Validação
| HTTP | code | Mensagem | Causa |
|---|
| 400 | INVALID_JSON | Body JSON invalido. | Corpo do request não é JSON válido |
| 400 | VALIDATION_ERROR | Payload invalido. Verifique a documentacao. | Campos obrigatórios em falta ou inválidos |
| 400 | VALIDATION_ERROR | Invalid option: expected one of “FT”|“FR”|… | tipo de documento inválido |
Erros da AGT
| HTTP | code | Mensagem | Causa |
|---|
| 502 | AGT_ERROR | secretOrPrivateKey must be an asymmetric key… | Chave privada inválida ou corrompida |
| 502 | AGT_ERROR | Erro de comunicacao com a AGT | Serviço AGT indisponível |
⚠️ Documento duplicado: A AGT aceita documentos com o mesmo número no momento do registo (HTTP 200). A rejeição por duplicado só aparece na validação diferida, ao consultar o status.
Exemplos de Código
TypeScript / Node.js
const API_KEY = "feak_bfc76ba48c0c286c9ce4bb2fcd7e178a399e329c59e529776bb5e01b0bfe8712";
const GATEWAY_URL = "https://connect.factflexi.com";
interface EmitirResultado {
success: boolean;
requestIdAGT: string;
dataEnvio: string;
documentos: { numero: string; tipo: string }[];
}
async function emitirDocumento(payload: object): Promise<EmitirResultado> {
const response = await fetch(`${GATEWAY_URL}/v2/documentos/emitir`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": API_KEY,
},
body: JSON.stringify(payload),
});
const data = await response.json();
if (!data.success) throw new Error(`${data.code}: ${data.error}`);
return data;
}
const resultado = await emitirDocumento({
nif: "5000537039",
privateKey: "-----BEGIN PRIVATE KEY-----\n...",
companyName: "SONHE TESTE",
documentos: [{
numero: "FT FT3926S9043N/1",
tipo: "FT",
data: "2026-07-25",
cliente: { nome: "Cliente", nif: "999999999", pais: "AO" },
itens: [{
codigo: "PROD001", descricao: "Produto",
quantidade: 2, unidade: "UN", precoUnitario: 5000, taxa: 14,
}],
}],
});
console.log(`Documento submetido. requestIdAGT: ${resultado.requestIdAGT}`);
Python
import requests
API_KEY = "feak_bfc76ba48c0c286c9ce4bb2fcd7e178a399e329c59e529776bb5e01b0bfe8712"
GATEWAY_URL = "https://connect.factflexi.com"
def emitir_documento(payload: dict) -> dict:
response = requests.post(
f"{GATEWAY_URL}/v2/documentos/emitir",
json=payload,
headers={
"Content-Type": "application/json",
"x-api-key": API_KEY,
},
)
data = response.json()
if not data.get("success"):
raise Exception(f"{data.get('code')}: {data.get('error')}")
return data
resultado = emitir_documento({
"nif": "5000537039",
"privateKey": "-----BEGIN PRIVATE KEY-----\n...",
"companyName": "SONHE TESTE",
"documentos": [{
"numero": "FT FT3926S9043N/3",
"tipo": "FT",
"data": "2026-07-25",
"cliente": {"nome": "Cliente", "nif": "999999999", "pais": "AO"},
"itens": [{
"codigo": "CONS001", "descricao": "Consultoria",
"quantidade": 1, "unidade": "UN", "precoUnitario": 100000,
"taxa": 14, "isServico": True,
"retencaoTipo": "IRT", "retencaoTaxa": 6.5,
}],
}],
})
print(f"Submetido. requestIdAGT: {resultado['requestIdAGT']}")
PHP
<?php
define('API_KEY', 'feak_bfc76ba48c0c286c9ce4bb2fcd7e178a399e329c59e529776bb5e01b0bfe8712');
define('GATEWAY_URL', 'https://connect.factflexi.com');
function emitirDocumento(array $payload): array {
$ch = curl_init(GATEWAY_URL . '/v2/documentos/emitir');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'x-api-key: ' . API_KEY,
],
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_RETURNTRANSFER => true,
]);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
if (!$data['success']) {
throw new Exception("{$data['code']}: {$data['error']}");
}
return $data;
}
$resultado = emitirDocumento([
'nif' => '5000537039',
'privateKey' => "-----BEGIN PRIVATE KEY-----\n...",
'companyName' => 'SONHE TESTE',
'documentos' => [[
'numero' => 'FT FT3926S9043N/5',
'tipo' => 'FT',
'data' => '2026-07-25',
'cliente' => ['nome' => 'Cliente', 'nif' => '999999999', 'pais' => 'AO'],
'itens' => [[
'codigo' => 'PROD001', 'descricao' => 'Produto',
'quantidade' => 2, 'unidade' => 'UN',
'precoUnitario' => 5000, 'taxa' => 14,
]],
]],
]);
echo "Submetido. requestIdAGT: {$resultado['requestIdAGT']}\n";
Regras por Tipo de Documento
| Tipo | itens | reciboDe | referencia | Notas |
|---|
| FT Factura | ✅ Obrigatório | ❌ | ❌ | Documento fiscal normal |
| FR Factura/Recibo | ✅ Obrigatório | ❌ | ❌ | Factura que também serve de recibo |
| NC Nota de Crédito | ✅ Obrigatório | ❌ | ✅ Obrigatório | Devolução/desconto — referenciar doc origem |
| ND Nota de Débito | ✅ Obrigatório | ❌ | Opcional | Correção de valor a maior |
| RC Recibo | ❌ | ✅ Obrigatório | ❌ | Recibo de pagamento |
| RG Recibo Geral | ❌ | ✅ Obrigatório | ❌ | Outros recibos |
| AR Aviso Cobrança/Recibo | ❌ | ✅ Obrigatório | ❌ | Aviso + recibo |