Guias
Documentos fiscais
Os tipos de documento, as regras que a lei impõe e como emitir, corrigir e cobrar.
Emitir um documento por API é o mesmo que emiti-lo no balcão: o servidor atribui o número, assina o documento na cadeia do seu tipo, movimenta o stock e regista o dinheiro. O que muda é quem pede. Este guia reúne o que precisa de saber para o fazer bem.
#Os tipos de documento
| Sigla | Documento | Como se emite | Stock | Pagamentos |
|---|---|---|---|---|
FT | Factura | POST /api/documentos | sai | nenhum: fica a crédito (cliente com conta corrente) |
FR | Factura/Recibo | POST /api/documentos | sai | a soma tem de igualar o total |
PP | Factura pró-forma | POST /api/documentos | não mexe | nenhum |
OR | Orçamento | POST /api/documentos | não mexe | nenhum |
NC | Nota de crédito | POST /api/documentos/notas-credito | volta | reembolso, ou abate à conta corrente |
RG | Recibo | POST /api/documentos/recibos | não mexe | um método, liquida facturas |
GR | Guia de remessa | POST /api/documentos + transporte | sai | nenhum |
GT | Guia de transporte | POST /api/documentos + transporte | sai | nenhum |
GD | Guia de devolução | POST /api/documentos + transporte | volta | nenhum |
NR | Nota de entrega | POST /api/documentos + transporte | sai | nenhum |
#Regras que valem para todos
- O número é do servidor. Sequencial por série (tipo, estabelecimento e ano), sem lacunas nem repetições. Uma emissão que falha não consome número.
- Assinado e encadeado. Cada documento leva a assinatura digital e entra na cadeia do seu tipo: retirar ou alterar um documento quebra a cadeia, e a verificação (
GET /api/saft/cadeia-assinaturas) vê-o. - Imutável. Um documento emitido não se altera nem se apaga. Corrige-se com uma nota de crédito.
- O imposto não se envia. O IVA e a retenção derivam do artigo e do cliente, no servidor. Envia-se o preço, a quantidade e o desconto (0 a 99 %).
- Tudo ou nada. Se uma linha falha (stock, regra), nada é emitido.
- Cliente omitido = Consumidor Final. Sem
clienteId, o documento vai para o «Consumidor Final», sem número de contribuinte. - Hora do servidor. A data do documento é a do servidor; não se envia.
#Vender e receber no acto: factura/recibo
É o caso de uma loja online que cobra na encomenda. Os pagamentos somam exactamente o total (com o IVA); pode haver até dez, por métodos diferentes.
Emitir uma Factura/Recibo paga em numerário
curl -X POST 'https://<endereço-da-API>/api/documentos' \
-H 'Idempotency-Key: 4756676e-a135-4e46-9ce8-3a5502ea85f0' \
-H 'X-Api-Key: assw_v1_<chave>' \
-H 'Content-Type: application/json' \
-d '{
"tipoDocumentoId": "6d3f9c9b-6d0a-4e4d-9a9a-2c9f8b1e4d52",
"clienteId": "01a12f69-98f8-7688-85b9-8d829c101c8b",
"armazemId": "01a12201-f4d4-78a0-8dde-1efc41b94776",
"lojaId": "01a12201-f4d4-7c07-86b7-e5b24035993e",
"observacao": "Pedido #4821 da loja online",
"itens": [
{
"produtoId": "01a12201-fa97-7f9b-8eec-2f731b91a53b",
"quantidade": 2,
"precoUnitario": 6500,
"descontoPercentual": 0
}
],
"pagamentos": [
{
"metodoPagamentoId": "9dde532b-0092-4a68-b2dc-18649f0c97c4",
"valor": 14820
}
]
}'
const resposta = await fetch('https://<endereço-da-API>/api/documentos', {
method: 'POST',
headers: {
"Idempotency-Key": "4756676e-a135-4e46-9ce8-3a5502ea85f0",
"X-Api-Key": "assw_v1_<chave>",
"Content-Type": "application/json"
},
body: JSON.stringify({
"tipoDocumentoId": "6d3f9c9b-6d0a-4e4d-9a9a-2c9f8b1e4d52",
"clienteId": "01a12f69-98f8-7688-85b9-8d829c101c8b",
"armazemId": "01a12201-f4d4-78a0-8dde-1efc41b94776",
"lojaId": "01a12201-f4d4-7c07-86b7-e5b24035993e",
"observacao": "Pedido #4821 da loja online",
"itens": [
{
"produtoId": "01a12201-fa97-7f9b-8eec-2f731b91a53b",
"quantidade": 2,
"precoUnitario": 6500,
"descontoPercentual": 0
}
],
"pagamentos": [
{
"metodoPagamentoId": "9dde532b-0092-4a68-b2dc-18649f0c97c4",
"valor": 14820
}
]
}),
})
const dados = await resposta.json()
import requests
resposta = requests.post(
'https://<endereço-da-API>/api/documentos',
headers={
'Idempotency-Key': '4756676e-a135-4e46-9ce8-3a5502ea85f0',
'X-Api-Key': 'assw_v1_<chave>',
'Content-Type': 'application/json'
},
json={
'tipoDocumentoId': '6d3f9c9b-6d0a-4e4d-9a9a-2c9f8b1e4d52',
'clienteId': '01a12f69-98f8-7688-85b9-8d829c101c8b',
'armazemId': '01a12201-f4d4-78a0-8dde-1efc41b94776',
'lojaId': '01a12201-f4d4-7c07-86b7-e5b24035993e',
'observacao': 'Pedido #4821 da loja online',
'itens': [
{
'produtoId': '01a12201-fa97-7f9b-8eec-2f731b91a53b',
'quantidade': 2,
'precoUnitario': 6500,
'descontoPercentual': 0
}
],
'pagamentos': [
{
'metodoPagamentoId': '9dde532b-0092-4a68-b2dc-18649f0c97c4',
'valor': 14820
}
]
},
)
dados = resposta.json()
using var cliente = new HttpClient { BaseAddress = new Uri("https://<endereço-da-API>") };
using var pedido = new HttpRequestMessage(HttpMethod.Post, "/api/documentos");
pedido.Headers.Add("Idempotency-Key", "4756676e-a135-4e46-9ce8-3a5502ea85f0");
pedido.Headers.Add("X-Api-Key", "assw_v1_<chave>");
pedido.Content = new StringContent("""
{
"tipoDocumentoId": "6d3f9c9b-6d0a-4e4d-9a9a-2c9f8b1e4d52",
"clienteId": "01a12f69-98f8-7688-85b9-8d829c101c8b",
"armazemId": "01a12201-f4d4-78a0-8dde-1efc41b94776",
"lojaId": "01a12201-f4d4-7c07-86b7-e5b24035993e",
"observacao": "Pedido #4821 da loja online",
"itens": [
{
"produtoId": "01a12201-fa97-7f9b-8eec-2f731b91a53b",
"quantidade": 2,
"precoUnitario": 6500,
"descontoPercentual": 0
}
],
"pagamentos": [
{
"metodoPagamentoId": "9dde532b-0092-4a68-b2dc-18649f0c97c4",
"valor": 14820
}
]
}
""", Encoding.UTF8, "application/json");
using var resposta = await cliente.SendAsync(pedido);
var texto = await resposta.Content.ReadAsStringAsync();
201 Created Resposta
content-type: application/json; charset=utf-8
x-mucanda-api-version: 1
location: /api/documentos/01a12f69-9a59-714a-9d67-10d65ba202cf
{
"id": "01a12f69-9a59-714a-9d67-10d65ba202cf"
}
#Vender a crédito e cobrar depois
Uma factura (FT) não leva pagamentos: o total passa para a conta corrente do cliente. Só serve a um cliente «Especial» (com conta corrente) e dentro do seu limite de crédito. Mais tarde, um recibo liquida-a.
Emitir uma Factura a crédito (conta corrente do cliente)
curl -X POST 'https://<endereço-da-API>/api/documentos' \
-H 'Idempotency-Key: 585c94ee-881b-440f-b97d-078148409eb8' \
-H 'X-Api-Key: assw_v1_<chave>' \
-H 'Content-Type: application/json' \
-d '{
"tipoDocumentoId": "2a6a1e94-6e0d-4b8e-9f42-8f6a2f0c7b41",
"clienteId": "01a12201-fa0a-7aac-93c4-f2d42f3c31cd",
"armazemId": "01a12201-f4d4-78a0-8dde-1efc41b94776",
"lojaId": "01a12201-f4d4-7c07-86b7-e5b24035993e",
"dataVencimento": "2026-11-11",
"itens": [
{
"produtoId": "01a12201-fa97-7f9b-8eec-2f731b91a53b",
"quantidade": 1,
"precoUnitario": 6500,
"descontoPercentual": 0
}
],
"pagamentos": []
}'
const resposta = await fetch('https://<endereço-da-API>/api/documentos', {
method: 'POST',
headers: {
"Idempotency-Key": "585c94ee-881b-440f-b97d-078148409eb8",
"X-Api-Key": "assw_v1_<chave>",
"Content-Type": "application/json"
},
body: JSON.stringify({
"tipoDocumentoId": "2a6a1e94-6e0d-4b8e-9f42-8f6a2f0c7b41",
"clienteId": "01a12201-fa0a-7aac-93c4-f2d42f3c31cd",
"armazemId": "01a12201-f4d4-78a0-8dde-1efc41b94776",
"lojaId": "01a12201-f4d4-7c07-86b7-e5b24035993e",
"dataVencimento": "2026-11-11",
"itens": [
{
"produtoId": "01a12201-fa97-7f9b-8eec-2f731b91a53b",
"quantidade": 1,
"precoUnitario": 6500,
"descontoPercentual": 0
}
],
"pagamentos": []
}),
})
const dados = await resposta.json()
import requests
resposta = requests.post(
'https://<endereço-da-API>/api/documentos',
headers={
'Idempotency-Key': '585c94ee-881b-440f-b97d-078148409eb8',
'X-Api-Key': 'assw_v1_<chave>',
'Content-Type': 'application/json'
},
json={
'tipoDocumentoId': '2a6a1e94-6e0d-4b8e-9f42-8f6a2f0c7b41',
'clienteId': '01a12201-fa0a-7aac-93c4-f2d42f3c31cd',
'armazemId': '01a12201-f4d4-78a0-8dde-1efc41b94776',
'lojaId': '01a12201-f4d4-7c07-86b7-e5b24035993e',
'dataVencimento': '2026-11-11',
'itens': [
{
'produtoId': '01a12201-fa97-7f9b-8eec-2f731b91a53b',
'quantidade': 1,
'precoUnitario': 6500,
'descontoPercentual': 0
}
],
'pagamentos': []
},
)
dados = resposta.json()
using var cliente = new HttpClient { BaseAddress = new Uri("https://<endereço-da-API>") };
using var pedido = new HttpRequestMessage(HttpMethod.Post, "/api/documentos");
pedido.Headers.Add("Idempotency-Key", "585c94ee-881b-440f-b97d-078148409eb8");
pedido.Headers.Add("X-Api-Key", "assw_v1_<chave>");
pedido.Content = new StringContent("""
{
"tipoDocumentoId": "2a6a1e94-6e0d-4b8e-9f42-8f6a2f0c7b41",
"clienteId": "01a12201-fa0a-7aac-93c4-f2d42f3c31cd",
"armazemId": "01a12201-f4d4-78a0-8dde-1efc41b94776",
"lojaId": "01a12201-f4d4-7c07-86b7-e5b24035993e",
"dataVencimento": "2026-11-11",
"itens": [
{
"produtoId": "01a12201-fa97-7f9b-8eec-2f731b91a53b",
"quantidade": 1,
"precoUnitario": 6500,
"descontoPercentual": 0
}
],
"pagamentos": []
}
""", Encoding.UTF8, "application/json");
using var resposta = await cliente.SendAsync(pedido);
var texto = await resposta.Content.ReadAsStringAsync();
201 Created Resposta
content-type: application/json; charset=utf-8
x-mucanda-api-version: 1
location: /api/documentos/01a12f69-9b31-7bbb-9089-d3f4b0f4f194
{
"id": "01a12f69-9b31-7bbb-9089-d3f4b0f4f194"
}
Para cobrar, peça as facturas pendentes do cliente (da mais antiga para a mais recente):
As facturas por liquidar de um cliente
curl 'https://<endereço-da-API>/api/documentos/pendentes?clienteId=01a12201-fa0a-7aac-93c4-f2d42f3c31cd' \
-H 'X-Api-Key: assw_v1_<chave>'
const resposta = await fetch('https://<endereço-da-API>/api/documentos/pendentes?clienteId=01a12201-fa0a-7aac-93c4-f2d42f3c31cd', {
method: 'GET',
headers: {
"X-Api-Key": "assw_v1_<chave>"
},
})
const dados = await resposta.json()
import requests
resposta = requests.get(
'https://<endereço-da-API>/api/documentos/pendentes?clienteId=01a12201-fa0a-7aac-93c4-f2d42f3c31cd',
headers={
'X-Api-Key': 'assw_v1_<chave>'
},
)
dados = resposta.json()
using var cliente = new HttpClient { BaseAddress = new Uri("https://<endereço-da-API>") };
using var pedido = new HttpRequestMessage(HttpMethod.Get, "/api/documentos/pendentes?clienteId=01a12201-fa0a-7aac-93c4-f2d42f3c31cd");
pedido.Headers.Add("X-Api-Key", "assw_v1_<chave>");
using var resposta = await cliente.SendAsync(pedido);
var texto = await resposta.Content.ReadAsStringAsync();
200 OK Resposta
content-type: application/json; charset=utf-8
x-mucanda-api-version: 1
[
{
"id": "01a12f22-0dd0-7906-98a2-607105acccc1",
"numero": "FT LC2026/9",
"data": "2026-10-12T08:02:04.430629Z",
"tipoDocumentoId": "2a6a1e94-6e0d-4b8e-9f42-8f6a2f0c7b41",
"siglaDocumento": "FT",
"totalGeral": 2850,
"valorLiquidado": 0,
"valorCreditado": 0,
"valorPendente": 2850
},
{
"id": "01a12f68-fb42-7bb7-b7e7-e451a26e5d66",
"numero": "FT LC2026/10",
"data": "2026-10-12T09:19:31.690877Z",
"tipoDocumentoId": "2a6a1e94-6e0d-4b8e-9f42-8f6a2f0c7b41",
"siglaDocumento": "FT",
"totalGeral": 7410,
"valorLiquidado": 0,
"valorCreditado": 0,
"valorPendente": 7410
},
{
"id": "01a12f69-873b-73f9-8fb4-120df044e6a5",
"numero": "FT LC2026/11",
"data": "2026-10-12T09:20:07.506771Z",
"tipoDocumentoId": "2a6a1e94-6e0d-4b8e-9f42-8f6a2f0c7b41",
"siglaDocumento": "FT",
"totalGeral": 7410,
"valorLiquidado": 0,
"valorCreditado": 0,
"valorPendente": 7410
},
{
"id": "01a12f69-9b31-7bbb-9089-d3f4b0f4f194",
"numero": "FT LC2026/12",
"data": "2026-10-12T09:20:12.613133Z",
"tipoDocumentoId": "2a6a1e94-6e0d-4b8e-9f42-8f6a2f0c7b41",
"siglaDocumento": "FT",
"totalGeral": 7410,
"valorLiquidado": 0,
"valorCreditado": 0,
"valorPendente": 7410
}
]
O recibo liquida uma ou várias facturas, cada uma por um valor que não exceda o pendente. Liquide sempre as mais antigas primeiro: o sistema recusa avançar uma factura recente deixando uma mais antiga por cobrir.
Emitir um Recibo que liquida a factura mais antiga do cliente
curl -X POST 'https://<endereço-da-API>/api/documentos/recibos' \
-H 'Idempotency-Key: 4b293f87-4473-4cf1-b647-a4f1dd78ed76' \
-H 'X-Api-Key: assw_v1_<chave>' \
-H 'Content-Type: application/json' \
-d '{
"clienteId": "01a12201-fa0a-7aac-93c4-f2d42f3c31cd",
"metodoPagamentoId": "9dde532b-0092-4a68-b2dc-18649f0c97c4",
"lojaId": "01a12201-f4d4-7c07-86b7-e5b24035993e",
"observacao": "Pagamento recebido por transferência",
"liquidacoes": [
{
"documentoId": "01a12f22-0dd0-7906-98a2-607105acccc1",
"valor": 2850
}
]
}'
const resposta = await fetch('https://<endereço-da-API>/api/documentos/recibos', {
method: 'POST',
headers: {
"Idempotency-Key": "4b293f87-4473-4cf1-b647-a4f1dd78ed76",
"X-Api-Key": "assw_v1_<chave>",
"Content-Type": "application/json"
},
body: JSON.stringify({
"clienteId": "01a12201-fa0a-7aac-93c4-f2d42f3c31cd",
"metodoPagamentoId": "9dde532b-0092-4a68-b2dc-18649f0c97c4",
"lojaId": "01a12201-f4d4-7c07-86b7-e5b24035993e",
"observacao": "Pagamento recebido por transferência",
"liquidacoes": [
{
"documentoId": "01a12f22-0dd0-7906-98a2-607105acccc1",
"valor": 2850
}
]
}),
})
const dados = await resposta.json()
import requests
resposta = requests.post(
'https://<endereço-da-API>/api/documentos/recibos',
headers={
'Idempotency-Key': '4b293f87-4473-4cf1-b647-a4f1dd78ed76',
'X-Api-Key': 'assw_v1_<chave>',
'Content-Type': 'application/json'
},
json={
'clienteId': '01a12201-fa0a-7aac-93c4-f2d42f3c31cd',
'metodoPagamentoId': '9dde532b-0092-4a68-b2dc-18649f0c97c4',
'lojaId': '01a12201-f4d4-7c07-86b7-e5b24035993e',
'observacao': 'Pagamento recebido por transferência',
'liquidacoes': [
{
'documentoId': '01a12f22-0dd0-7906-98a2-607105acccc1',
'valor': 2850
}
]
},
)
dados = resposta.json()
using var cliente = new HttpClient { BaseAddress = new Uri("https://<endereço-da-API>") };
using var pedido = new HttpRequestMessage(HttpMethod.Post, "/api/documentos/recibos");
pedido.Headers.Add("Idempotency-Key", "4b293f87-4473-4cf1-b647-a4f1dd78ed76");
pedido.Headers.Add("X-Api-Key", "assw_v1_<chave>");
pedido.Content = new StringContent("""
{
"clienteId": "01a12201-fa0a-7aac-93c4-f2d42f3c31cd",
"metodoPagamentoId": "9dde532b-0092-4a68-b2dc-18649f0c97c4",
"lojaId": "01a12201-f4d4-7c07-86b7-e5b24035993e",
"observacao": "Pagamento recebido por transferência",
"liquidacoes": [
{
"documentoId": "01a12f22-0dd0-7906-98a2-607105acccc1",
"valor": 2850
}
]
}
""", Encoding.UTF8, "application/json");
using var resposta = await cliente.SendAsync(pedido);
var texto = await resposta.Content.ReadAsStringAsync();
201 Created Resposta
content-type: application/json; charset=utf-8
x-mucanda-api-version: 1
location: /api/documentos/01a12f69-9b9e-7ca9-9b4b-f4b060a860a3
{
"id": "01a12f69-9b9e-7ca9-9b4b-f4b060a860a3"
}
#Corrigir: a nota de crédito
Uma devolução, um erro de preço ou uma factura emitida por engano corrigem-se com uma nota de crédito sobre o documento original. Creditar todas as linhas anula a factura; creditar algumas rectifica-a. O stock volta ao armazém.
Creditar uma linha (devolução): repõe o stock e reembolsa pelo método indicado
curl -X POST 'https://<endereço-da-API>/api/documentos/notas-credito' \
-H 'Idempotency-Key: 1a2be5c9-50c6-4b73-a8df-9a4c010b7624' \
-H 'X-Api-Key: assw_v1_<chave>' \
-H 'Content-Type: application/json' \
-d '{
"documentoOriginalId": "01a12f69-9a59-714a-9d67-10d65ba202cf",
"motivo": "Devolução do cliente",
"metodoReembolsoId": "9dde532b-0092-4a68-b2dc-18649f0c97c4",
"itens": [
{
"documentoItemId": "01a12f69-9a75-71ff-8c4f-163cce92fd02",
"quantidade": 1
}
]
}'
const resposta = await fetch('https://<endereço-da-API>/api/documentos/notas-credito', {
method: 'POST',
headers: {
"Idempotency-Key": "1a2be5c9-50c6-4b73-a8df-9a4c010b7624",
"X-Api-Key": "assw_v1_<chave>",
"Content-Type": "application/json"
},
body: JSON.stringify({
"documentoOriginalId": "01a12f69-9a59-714a-9d67-10d65ba202cf",
"motivo": "Devolução do cliente",
"metodoReembolsoId": "9dde532b-0092-4a68-b2dc-18649f0c97c4",
"itens": [
{
"documentoItemId": "01a12f69-9a75-71ff-8c4f-163cce92fd02",
"quantidade": 1
}
]
}),
})
const dados = await resposta.json()
import requests
resposta = requests.post(
'https://<endereço-da-API>/api/documentos/notas-credito',
headers={
'Idempotency-Key': '1a2be5c9-50c6-4b73-a8df-9a4c010b7624',
'X-Api-Key': 'assw_v1_<chave>',
'Content-Type': 'application/json'
},
json={
'documentoOriginalId': '01a12f69-9a59-714a-9d67-10d65ba202cf',
'motivo': 'Devolução do cliente',
'metodoReembolsoId': '9dde532b-0092-4a68-b2dc-18649f0c97c4',
'itens': [
{
'documentoItemId': '01a12f69-9a75-71ff-8c4f-163cce92fd02',
'quantidade': 1
}
]
},
)
dados = resposta.json()
using var cliente = new HttpClient { BaseAddress = new Uri("https://<endereço-da-API>") };
using var pedido = new HttpRequestMessage(HttpMethod.Post, "/api/documentos/notas-credito");
pedido.Headers.Add("Idempotency-Key", "1a2be5c9-50c6-4b73-a8df-9a4c010b7624");
pedido.Headers.Add("X-Api-Key", "assw_v1_<chave>");
pedido.Content = new StringContent("""
{
"documentoOriginalId": "01a12f69-9a59-714a-9d67-10d65ba202cf",
"motivo": "Devolução do cliente",
"metodoReembolsoId": "9dde532b-0092-4a68-b2dc-18649f0c97c4",
"itens": [
{
"documentoItemId": "01a12f69-9a75-71ff-8c4f-163cce92fd02",
"quantidade": 1
}
]
}
""", Encoding.UTF8, "application/json");
using var resposta = await cliente.SendAsync(pedido);
var texto = await resposta.Content.ReadAsStringAsync();
201 Created Resposta
content-type: application/json; charset=utf-8
x-mucanda-api-version: 1
location: /api/documentos/01a12f69-9ae0-786d-b5e9-b0aad1e5ab09
{
"id": "01a12f69-9ae0-786d-b5e9-b0aad1e5ab09"
}
- Numa factura/recibo,
metodoReembolsoIddiz por onde o dinheiro volta (e sai da conta financeira desse método). - Numa factura a crédito, o valor creditado abate à conta corrente do cliente; não há reembolso.
- Cada linha só se credita uma vez (
DOCUMENTO_JA_CREDITADO).
#Cotações e guias
- Pró-forma e orçamento não movimentam stock nem dinheiro e não levam pagamentos. Podem ter
dataVencimento(a validade da proposta). Num orçamento, a primeira linha tem de ser um serviço e tem de haver pelo menos um. - Guias exigem
transporte: a data e hora de início, a origem (local de carga), o destino (local de descarga) e a matrícula. Movimentam o stock (saída ou, na devolução, entrada). - Uma factura emitida a partir de uma pró-forma ou de uma guia leva
referenciaExternaIdcom o documento de origem.
#Promoções
Por omissão, uma emissão por API não aplica promoções: os preços são os que enviou. Com aplicarPromocoes: true, o servidor aplica as promoções em vigor e o total pago tem de ser o total já com elas.
#Facturação electrónica
Se a empresa aderiu à facturação electrónica, o documento entra na fila de comunicação à administração fiscal como qualquer outro, sem esperar por ela: a emissão responde logo. O estado da comunicação vê-se no documento.
#Imprimir e enviar
GET /api/documentos/{id}/pdfdevolve o PDF A4, igual ao da aplicação (comviaspara o original e as cópias).GET /api/documentos/{id}/impressaodevolve o modelo de impressão em JSON, para o desenhar à sua maneira.POST /api/documentos/{id}/enviar-emailenvia o documento ao destinatário indicado.