Faturação FIZ
Emitir faturas com a API Pública FIZ — um gateway REST em https://api.fiz.co.
Esta skill percorre o ciclo de vida completo: localizar ou criar o cliente, localizar
ou criar os artigos, construir um rascunho de fatura, emiti-la (este é o passo
legalmente vinculativo) e, opcionalmente, descarregar o PDF.
Configuração
Cada pedido necessita da chave de API do cliente no cabeçalho x-api-key. Obtenha uma chave em
https://app.fiz.co/settings/integrations.
Armazene-a numa variável de ambiente para que nunca seja codificada em comandos:
export FIZ_API_KEY="fiz_api_..." # a chave do utilizador em app.fiz.co
export FIZ_API_URL="https://api.fiz.co" # opcional; este é o valor predefinido
Todos os exemplos abaixo usam $FIZ_API_KEY e $FIZ_API_URL. Se FIZ_API_URL não
estiver definido, use https://api.fiz.co. Se FIZ_API_KEY não estiver definido,
pare e diga ao utilizador para a definir no seu ambiente (export FIZ_API_KEY=… no
seu terminal, ou num gestor de segredos) — não lhe peça para colar a chave no chat, e nunca
imprima ou mostre o valor da chave. Uma chave de API é uma credencial; mantenha-a fora do
transcrito da conversa.
Um script curl reutilizável é fornecido em scripts/fiz.sh. Os comandos são executados a partir do
diretório de trabalho da sessão (a raiz do seu projeto), não do diretório da skill, por isso
importe o helper pelo seu caminho absoluto via ${CLAUDE_SKILL_DIR} (o Claude Code define
este caminho para o diretório da própria skill):
source "${CLAUDE_SKILL_DIR}/scripts/fiz.sh" # resolve a partir de qualquer diretório de trabalho
fiz GET /invoices
Num runtime que não forneça CLAUDE_SKILL_DIR, substitua pelo caminho equivalente do
diretório da skill desse runtime (um scripts/fiz.sh relativo simples não será resolvido
a partir da raiz do projeto).
O helper imprime o estado HTTP e retorna um código diferente de zero em casos de 4xx/5xx,
pelo que uma resposta de erro nunca é confundida com sucesso — veja "Tratamento de erros" abaixo. Exemplos
curl simples também são mostrados; se usar curl puro, verifique sempre o estado HTTP,
não apenas se o comando terminou com código 0.
O modelo mental
- Uma fatura referencia um cliente (pelo id) e um ou mais artigos (pelo id). Portanto, clientes e artigos devem existir antes de poder construir a fatura.
POST /invoicescria um rascunho. Um rascunho ainda não é um documento legal — não é enviado para a autoridade fiscal e pode ser editado ou eliminado livremente.POST /invoices/:id/issueemite-a — atribui o número oficial e o ATCUD, sincroniza com a Autoridade Tributária (AT) e torna-a definitiva. A emissão é irreversível; confirme com o utilizador antes de emitir. Uma fatura emitida não pode ser eliminada — a única forma de a anular é com uma nota de crédito.- A única exceção a "criar e depois emitir": se passar um objeto
paymentna criação para os tiposINVOICE_RECEIPTouSIMPLIFIED_INVOICE, o documento regista o pagamento — mas ainda precisa de a emitir para finalizar.
Confirmação antes de escritas
Estes endpoints alteram o estado na conta do utilizador. Dois níveis de cuidado:
- Planear e, em seguida, pré-visualizar todas as escritas antes da primeira. Quando souber o que o pedido necessita, liste todas as escritas que pretende fazer para esta tarefa — cada endpoint mais o seu payload JSON (o cliente a criar, cada artigo, o rascunho de fatura) — e mostre esse lote ao utilizador numa única pré-visualização. Prossiga com as escritas assim que eles aprovarem o plano. Se o plano mudar a meio da execução (por exemplo, uma pesquisa de artigo falha e agora precisa de criar um novo), pré-visualize a nova escrita antes de a enviar. Nunca invente dados do cliente, campos fiscais ou valores — use apenas o que o utilizador lhe forneceu.
- Antes de
POST /invoices/:id/issue— isto é irreversível e reportado à autoridade fiscal. Obtenha sempre uma confirmação explícita e separada (para além da aprovação do plano acima), mostrando o cliente do rascunho, as linhas de artigo, o IVA e o total. Não emita por sua própria iniciativa.
Política de invocação (deliberada): esta skill mantém a auto-invocação do modelo
ativada — a description é a superfície de descoberta, e a verdadeira rede de segurança é a
pré-visualização de todas as escritas + confirmação de emissão separada acima, e não a limitação da descoberta.
O manifesto Codex (agents/openai.yaml) está configurado para corresponder
(allow_implicit_invocation: true). Um operador que pretenda uso apenas manual pode definir
disable-model-invocation: true no frontmatter (carrega apenas em /fiz-invoicing).
Isto é faturação fiscal portuguesa — porque a correção é importante
A FIZ emite documentos fiscais legalmente vinculativos que são reportados à autoridade fiscal portuguesa (Autoridade Tributária, "AT"). Uma taxa de IVA errada ou uma razão de isenção em falta não é um bug cosmético — produz um documento fiscal incorreto que, uma vez emitido, só pode ser corrigido com uma nota de crédito. Acerte os campos fiscais antes de emitir.
As duas coisas com maior probabilidade de estarem erradas, e sobre as quais deve refletir:
1. Taxa de IVA (vatRate em cada artigo) — nunca a adivinhe. O escalão de taxa
(NORMAL | INTERMEDIATE | REDUCED | EXEMPT) e a percentagem para a qual mapeia
dependem do território (Continental / Açores / Madeira). A predefinição é NORMAL
— REDUCED/INTERMEDIATE aplicam-se apenas a categorias legalmente definidas
(certos alimentos, restaurantes, eventos culturais…). Se o utilizador não nomeou uma dessas
categorias, use NORMAL e diga-o, ou pergunte; nunca aplique silenciosamente uma taxa reduzida.
A tabela de percentagens por território está em domain.md.
2. Isenção de IVA requer um código de razão (vatExemptionReason).
Se vatRate for EXEMPT, a lei portuguesa exige um motivo de isenção — um
código M que estabelece a base legal. Defina sempre um; um artigo isento sem uma razão
válida é um documento inválido e pode ser rejeitado quando emitir. O código deve
refletir a razão real: regime de pequenos negócios → M10, atividade isenta por
natureza (saúde/educação) → M07, B2B intracomunitário / reverse charge → M40/M16/M19.
Se o utilizador estiver isento mas não souber o código, pergunte porquê e mapeie-o — não escolha
arbitrariamente. A lista completa de códigos com referências legais está em domain.md.
Outras regras de domínio que vale a pena conhecer (detalhes em domain.md):
- CAE é um código de atividade económica português de 5 dígitos que deve corresponder a um que a empresa tenha registado. Não o invente — pergunte ao utilizador, ou reutilize o CAE de uma fatura anterior. Deve ser um código atual (Rev. 4).
- NIF (
taxpayerNumber) é um número de contribuinte português de 9 dígitos com um dígito de controlo; obrigatório para B2B, opcional para consumidores finais. Para B2B intracomunitário, o número de IVA do cliente é relevante para o tratamento de reverse charge. - Retenção na fonte (retenção na fonte: IRS/IRC/IS) é configurada no artigo; a taxa comum é 25% para serviços de trabalhadores independentes. É retida pelo pagador, reduzindo o montante efetivamente pago.
- Tipo de documento afeta as regras: uma
SIMPLIFIED_INVOICEé para pequenas vendas a retalho e pode omitir dados completos do cliente; umaINVOICEpara uma empresa com valor superior a €1.000 necessita do NIF do cliente.
Fluxo de trabalho
Siga estes passos. Salte 1–2 se o utilizador já lhe fornecer um id de cliente e ids de artigos.
Passo 1 — Localizar o cliente
Pesquise primeiro para evitar duplicados, depois crie se necessário.
Os exemplos usam o helper fiz (veja Configuração) — ele verifica o estado HTTP por si. O
curl puro equivalente está no apêndice.
Encontrar por termo de pesquisa (nome, número de contribuinte, email):
fiz GET "/customers?search=Joao"
Criar um cliente (apenas name é obrigatório):
fiz POST /customers '{
"name": "João Silva",
"email": "[email protected]",
"taxpayerNumber": "303741791",
"country": "PT"
}'
Guarde o id retornado — esse é o customerId para a fatura.
Passo 2 — Localizar os artigos (produtos / serviços)
Encontrar artigos existentes:
fiz GET "/items?search=consulting"
Reutilize um artigo existente apenas se os seus campos fiscais corresponderem à venda. Um resultado de pesquisa com o nome certo não é suficiente — para um documento fiscal, os campos
vatRate,vatTerritory,vatExemptionReason,taxRatee de retenção do artigo devem corresponder ao tratamento que pretende para este cliente e venda. Se algum deles diferir (por exemplo, o mesmo serviço mas o cliente está isento, noutro território, ou sujeito a retenção), crie um novo artigo com os campos corretos em vez de reutilizar o que não corresponde. Em caso de dúvida, inspecione os campos do artigo encontrado e confirme com o utilizador.
Criar um artigo. Obrigatórios: name, type (PRODUCT | SERVICE), unitPrice,
vatRate (NORMAL | INTERMEDIATE | REDUCED | EXEMPT):
fiz POST /items '{
"name": "Hora de consultoria",
"type": "SERVICE",
"unitPrice": 80,
"vatRate": "NORMAL"
}'
Guarde cada id retornado — esses são os ids dos artigos para a fatura.
unitPriceé o preço unitário; os totais por linha são calculados pela FIZ a partir daquantityque fornece na fatura. EscolhavatRatedeliberadamente — veja a secção de domínio acima; a predefinição éNORMALa menos que a categoria justifique claramente uma taxa reduzida. SevatRateforEXEMPT, defina sempre um código MvatExemptionReasonque corresponda a porque a venda está isenta (por exemplo,M10para o regime de pequenos negócios). O campo é tecnicamente opcional na criação do artigo, mas um artigo isento sem uma razão válida está incorreto e pode ser rejeitado mais tarde quando emitir a fatura. Vejadomain.mdpara a lista completa de códigos.
Passo 3 — Criar o rascunho da fatura
Campos obrigatórios: dueDate (ISO 8601), cae (código de atividade económica português),
type, customerId e items (pelo menos um { id, quantity }).
fiz POST /invoices '{
"dueDate": "2026-07-18T00:00:00.000Z",
"cae": "62010",
"type": "INVOICE",
"customerId": "6863b1513117c5892ff55296",
"items": [
{ "id": "68483e978073231c3947077c", "quantity": 10 }
]
}'
A resposta inclui o novo id da fatura (necessário para emitir) e o status
(DRAFT). A date da fatura é definida para agora e a currency é EUR pela API —
não as envia.
Opcional — desconto global (summary):
"summary": { "globalDiscountType": "PERCENT", "globalDiscountPercent": 10 }
Use "AMOUNT" com globalDiscountAmount para um desconto de valor fixo em alternativa.
Opcional — pagamento na criação (payment): apenas permitido quando type é
INVOICE_RECEIPT ou SIMPLIFIED_INVOICE; a API retorna 400 caso contrário.
"payment": { "method": "mbWay", "date": "2026-07-18T00:00:00.000Z" }
method é um de: cash, card, bankTransfer, mbWay, multibanco,
spin, other.
Passo 4 — Emitir a fatura
Este é o passo vinculativo. Confirme com o utilizador, depois:
fiz POST /invoices/{id}/issue
A resposta contém o number oficial, status: ISSUED e um bloco syncWithAt.
Verifique syncWithAt.status — se for FAILED, a fatura foi criada localmente
mas não sincronizou com a autoridade fiscal; reporte atMessage e se
isRetriable é verdadeiro ao utilizador em vez de reivindicar sucesso.
Passo 5 — Descarregar o PDF (opcional)
fiz GET /invoices/{id}/pdf
Retorna { id, name, url }; o url é uma hiperligação para descarregar o PDF.
Adicione ?format=A4 ou ?format=RECEIPT para escolher o layout.
Tipos de documento (type)
Valores: INVOICE · INVOICE_RECEIPT · SIMPLIFIED_INVOICE · RECEIPT ·
CREDIT_NOTE · DEBIT_NOTE. A predefinição é INVOICE a menos que o utilizador peça um
recibo ou fatura simplificada. Apenas INVOICE_RECEIPT e SIMPLIFIED_INVOICE
aceitam um objeto payment na criação. Veja domain.md para o que cada tipo significa e
quando usá-lo.
Referência de operações comuns
| Ação | Método e caminho |
|---|---|
| Listar/pesquisar clientes | GET /customers?search=… |
| Criar cliente | POST /customers |
| Listar/pesquisar artigos | GET /items?search=… |
| Criar artigo | POST /items |
| Criar rascunho | POST /invoices |
| Emitir | POST /invoices/:id/issue |
| Listar faturas | GET /invoices |
| Obter uma fatura | GET /invoices/:id |
| Descarregar PDF | GET /invoices/:id/pdf |
| Eliminar (rascunho) | DELETE /invoices/:id |
Tratamento de erros
Verifique sempre o estado HTTP, não apenas se o comando foi executado. Em caso de erro, a API
retorna um corpo JSON de aspeto normal — { "statusCode": 400, "message": "...", "timestamp": "..." } — com o estado HTTP correspondente. Com curl -s puro,
esse corpo é impresso e o curl ainda sai com código 0, por isso é fácil confundir um erro com um
resultado. O helper fiz protege contra isto: imprime uma linha HTTP <code> e
retorna um código diferente de zero em casos de 4xx/5xx. Se tiver de usar curl puro, adicione
-w '\n%{http_code}' (ou -o body -w '%{http_code}') e inspecione o código.
Estados comuns:
- 401 — chave de API ausente ou inválida. Diga ao utilizador para definir
FIZ_API_KEYno seu ambiente (não no chat). - 400 — erro de validação; o
messageindica qual o campo. Casos comuns:itemsvazio,paymentnumtypenão suportado, um campo desconhecido/extra (lista de permissões estrita),dueDatemalformado (deve ser ISO 8601 completo com fuso horário, por exemplo,2026-07-18T00:00:00.000Z). - 5xx — problema no backend/AT; exponha-o, não repita a mesma escrita cegamente
(uma criação pode ter sido parcialmente bem-sucedida — verifique primeiro com um
GET).
Devolva sempre a message da API ao utilizador em vez de repetir cegamente.
O contrato completo e autoritativo de pedido/resposta é o Swagger UI em https://api.fiz.co/ — consulte-o se algum campo aqui parecer desatualizado.
Quando precisar de mais detalhes
Dois ficheiros complementares neste diretório da skill:
domain.md— o domínio fiscal português: taxas de IVA por território, a lista completa de códigos de isenção (M) e quando cada um se aplica, tipos de documento, regras de CAE/NIF, retenção na fonte e como funciona a sincronização com a AT / irreversibilidade da emissão. Leia-o sempre que uma decisão fiscal não for óbvia (especialmente ao escolher umavatRateou uma razão de isenção).reference.md— a referência completa campo-a-campo da API (todos os campos de cliente/artigo/fatura, todos os valores de enum, formas completas de pedido/resposta).
Apêndice — curl puro (sem o helper)
Prefira o helper fiz acima; ele verifica o estado HTTP. Se não estiver disponível, use
curl puro com -w '\n%{http_code}' (ou -o body -w '%{http_code}') e verifique o código
antes de interpretar o corpo. Exemplo:
status=$(curl -s -o body -w '%{http_code}' -H "x-api-key: $FIZ_API_KEY" \
"$FIZ_API_URL/customers")
if [ "$status" -ge 400 ]; then
echo "Erro $status: $(cat body)"
else
cat body
fi
Os exemplos ao longo do texto mostram o formato do corpo tal como seria com -s;
adicione explicitamente -o e -w para capturar o estado.