# Fiz Invoicing

> >-

- **Type:** Skill
- **Install:** `agentstack add skill-fiz-co-fiz-invoicing-skill-fiz-invoicing`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [FIZ-co](https://agentstack.voostack.com/s/fiz-co)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [FIZ-co](https://github.com/FIZ-co)
- **Source:** https://github.com/FIZ-co/fiz-invoicing-skill/tree/main/fiz-invoicing

## Install

```sh
agentstack add skill-fiz-co-fiz-invoicing-skill-fiz-invoicing
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# FIZ Invoicing

Issue invoices with the FIZ Public API — a REST gateway at `https://api.fiz.co`.
This skill walks through the full lifecycle: find or create the customer, find
or create the items, build a **draft** invoice, **issue** it (this is the legally
binding step), and optionally download the PDF.

## Setup

Every request needs the client's API key in the `x-api-key` header. Get a key at
**https://app.fiz.co/settings/integrations**.

Store it in an environment variable so it never gets hardcoded into commands:

```bash
export FIZ_API_KEY="fiz_api_..."     # the user's key from app.fiz.co
export FIZ_API_URL="https://api.fiz.co"   # optional; this is the default
```

All examples below use `$FIZ_API_KEY` and `$FIZ_API_URL`. If `FIZ_API_URL` is
unset, use `https://api.fiz.co`. If `FIZ_API_KEY` is unset, **stop and tell the
user to set it in their environment** (`export FIZ_API_KEY=…` in their shell, or a
secret manager) — **do not ask them to paste the key into the chat**, and never
print or echo the key value. An API key is a credential; keep it out of the
conversation transcript.

A reusable curl helper is provided in `scripts/fiz.sh`. Commands run from the
session's working directory (your project root), **not** the skill directory, so
source the helper by its absolute path via `${CLAUDE_SKILL_DIR}` (Claude Code sets
this to the skill's own directory):

```bash
source "${CLAUDE_SKILL_DIR}/scripts/fiz.sh"   # resolves from any working directory
fiz GET /invoices
```

On a runtime that doesn't provide `CLAUDE_SKILL_DIR`, substitute that runtime's
equivalent skill-directory path (a bare relative `scripts/fiz.sh` will not resolve
from the project root).

The helper prints the HTTP status and **returns non-zero on 4xx/5xx**, so an error
response is never mistaken for success — see "Error handling" below. Plain `curl`
examples are shown inline too; if you use raw `curl`, always check the HTTP status,
not just whether the command exited 0.

## The mental model

- An invoice references a **customer** (by id) and one or more **items** (by id).
  So customers and items must exist *before* you can build the invoice.
- `POST /invoices` creates a **draft**. A draft is not yet a legal document — it
  is not sent to the tax authority and can be edited or deleted freely.
- `POST /invoices/:id/issue` **issues** it — assigns the official number and
  ATCUD, syncs with the Portuguese tax authority (AT), and makes it final.
  **Issuing is not reversible; confirm with the user before issuing.** An issued
  invoice cannot be deleted — the only way to undo it is a credit note.
- The only exception to "create then issue": if you pass a `payment` object on
  creation for the types `INVOICE_RECEIPT` or `SIMPLIFIED_INVOICE`, the document
  records the payment — but you still issue it to finalize.

## Confirmation before writes

These endpoints change state on the user's account. Two levels of care:

- **Plan, then preview all writes before the first one.** Once you know what the
  request needs, list *every* write you intend to make for this task — each
  endpoint plus its JSON payload (the customer to create, each item, the draft
  invoice) — and show that batch to the user in one preview. Proceed through the
  writes once they approve the plan. If the plan changes mid-run (e.g. an item
  lookup fails and you now need to create one), preview the new write before
  sending it. Never invent customer data, tax fields, or amounts — use only what
  the user gave you.
- **Before `POST /invoices/:id/issue`** — this is irreversible and reported to the
  tax authority. **Always get an explicit, separate confirmation** (beyond the plan
  approval above), showing the draft's customer, line items, VAT, and total. Do not
  issue on your own initiative.
- **Before `POST /invoices/credit-notes` and `POST /invoices/:id/cancel`** — these
  also issue a fiscal change to the AT and are not casually undone. Confirm
  separately too: for a credit note show which invoice, the reason, and the amount
  being reversed; for a cancel show which invoice is being voided.

**Invocation policy (deliberate):** this skill keeps model auto-invocation
*enabled* — the `description` is the discovery surface, and the real safety net is
the preview-all-writes + separate issue confirmation above, not gating discovery.
The Codex manifest (`agents/openai.yaml`) is set to match
(`allow_implicit_invocation: true`). An operator who wants manual-only use can set
`disable-model-invocation: true` in the frontmatter (loads only on `/fiz-invoicing`).

## This is Portuguese tax invoicing — why correctness matters

FIZ issues **legally binding fiscal documents** that are reported to the
Portuguese tax authority (Autoridade Tributária, "AT"). A wrong VAT rate or a
missing exemption reason is not a cosmetic bug — it produces an incorrect tax
document that, once issued, can only be corrected with a credit note. Get the
tax fields right *before* issuing.

The two things most likely to be wrong, and that you must reason about:

**1. VAT rate (`vatRate` on each item) — never guess it.** The rate band
(`NORMAL` | `INTERMEDIATE` | `REDUCED` | `EXEMPT`) and the percentage it maps to
depend on the territory (Continental / Açores / Madeira). **Default to `NORMAL`**
— `REDUCED`/`INTERMEDIATE` apply only to specific legally-defined categories
(certain foods, restaurants, cultural events…). If the user hasn't named such a
category, use `NORMAL` and say so, or ask; never silently apply a reduced rate.
The per-territory percentage table is in `domain.md`.

**2. VAT exemption requires a reason code (`vatExemptionReason`).**
If `vatRate` is `EXEMPT`, Portuguese law requires a *motivo de isenção* — an
`M`-code stating the legal basis. Always set one; an exempt item without a valid
reason is an invalid document and can be rejected when you issue. The code must
reflect the *actual* reason: small-business regime → `M10`, activity exempt by
nature (health/education) → `M07`, EU B2B / reverse charge → `M40`/`M16`/`M19`.
If the user is exempt but doesn't know the code, ask *why* and map it — don't pick
arbitrarily. The full code list with legal references is in `domain.md`.

**Other domain rules worth knowing** (details in `domain.md`):
- **CAE** is a 5-digit Portuguese economic-activity code that must be one the
  business has registered. Don't invent it — ask the user, or reuse the CAE from
  a previous invoice. It must be a current (Rev. 4) code.
- **NIF** (`taxpayerNumber`) is a 9-digit Portuguese tax number with a checksum;
  required for B2B, optional for final consumers. For EU B2B, the customer's
  VAT number matters for reverse-charge treatment.
- **Withholding tax** (retenção na fonte: IRS/IRC/IS) is configured on the item;
  common rate is 25% for self-employed services. It is *withheld by the payer*,
  reducing the amount actually paid.
- **Document type** affects rules: a `SIMPLIFIED_INVOICE` is for small retail
  sales and may omit full customer data; an `INVOICE` to a business for over
  €1,000 needs the customer NIF.

## Workflow

Follow these steps. Skip 1–2 if the user already gives you a customer id and item
ids.

### Step 1 — Resolve the customer

Search first to avoid duplicates, then create if needed.

Examples use the `fiz` helper (see Setup) — it checks the HTTP status for you. The
equivalent raw `curl` is in the appendix.

Find by search term (name, tax number, email):
```bash
fiz GET "/customers?search=Joao"
```

Create a customer (only `name` is required):
```bash
fiz POST /customers '{
  "name": "João Silva",
  "email": "joao.silva@example.com",
  "taxpayerNumber": "303741791",
  "country": "PT"
}'
```
Keep the returned `id` — that is the `customerId` for the invoice.

### Step 2 — Resolve the items (products / services)

Find existing items:
```bash
fiz GET "/items?search=consulting"
```

> **Reuse an existing item only if its tax fields match the sale.** A search hit
> with the right *name* is not enough — for a fiscal document the item's `vatRate`,
> `vatTerritory`, `vatExemptionReason`, `taxRate` and withholding fields must match
> the treatment you intend for *this* customer and sale. If any of them differs
> (e.g. same service but the customer is exempt, in another territory, or subject
> to withholding), **create a new item** with the correct fields rather than
> reusing the mismatched one. When unsure, inspect the found item's fields and
> confirm with the user.

Create an item. Required: `name`, `type` (`PRODUCT` | `SERVICE`), `unitPrice`,
`vatRate` (`NORMAL` | `INTERMEDIATE` | `REDUCED` | `EXEMPT`):
```bash
fiz POST /items '{
  "name": "Consulting hour",
  "type": "SERVICE",
  "unitPrice": 80,
  "vatRate": "NORMAL"
}'
```
Keep each returned `id` — those are the item ids for the invoice.

> `unitPrice` is the unit price; per-line totals are computed by FIZ from the
> `quantity` you give in the invoice. **Choose `vatRate` deliberately** — see the
> domain section above; default to `NORMAL` unless the category clearly warrants a
> reduced rate. If `vatRate` is `EXEMPT`, always set a `vatExemptionReason` M-code
> that matches *why* the sale is exempt (e.g. `M10` for the small-business regime).
> The field is technically optional at item creation, but an exempt item without a
> valid reason is incorrect and can be rejected later when you issue the invoice.
> See `domain.md` for the full code list.

### Step 3 — Create the draft invoice

Required fields: `dueDate` (ISO 8601), `cae` (Portuguese economic activity
code), `type`, `customerId`, and `items` (at least one `{ id, quantity }`).

```bash
fiz POST /invoices '{
  "dueDate": "2026-07-18T00:00:00.000Z",
  "cae": "62010",
  "type": "INVOICE",
  "customerId": "6863b1513117c5892ff55296",
  "items": [
    { "id": "68483e978073231c3947077c", "quantity": 10 }
  ]
}'
```

The response includes the new invoice `id` (needed to issue) and `status`
(`DRAFT`). The invoice `date` is set to now and `currency` is `EUR` by the API —
you do not send them.

**Optional — notes** (`notes`): free-text note shown on the invoice, e.g.
`"notes": "PO #118"`.

**Optional — series** (`seriesId`): issue this invoice into a specific numbering
series instead of the account default. Omit it and the account's default series is
used — most accounts only have one, so you rarely need this. When the user does run
multiple series (e.g. two brands on one NIF), list them with `GET /series` and pass
the chosen `id`:
```bash
fiz GET /series
```
Returns the active series, each with `id`, `name`, `isDefault`, `status`,
`managementMode`, and `entries[]` (per document type, with the ATCUD
`validationCode`). Pass the `id` as `seriesId` on create:
```json
"seriesId": "68483b3fa19e44171e3d0808"
```
It must be a valid series id from `GET /series` — there is **no silent fallback to
the default** if it's wrong. A malformed value (not a 24-char Mongo id) is rejected
with **400**; a well-formed but unknown/wrong-account id is rejected with **404**
("that series does not exist"), *not* 400.

**Optional — global discount** (`summary`):
```json
"summary": { "globalDiscountType": "PERCENT", "globalDiscountPercent": 10 }
```
Use `"AMOUNT"` with `globalDiscountAmount` for a fixed-value discount instead.

**Optional — payment on creation** (`payment`): only allowed when `type` is
`INVOICE_RECEIPT` or `SIMPLIFIED_INVOICE`; the API returns 400 otherwise.
```json
"payment": { "method": "mbWay", "date": "2026-07-18T00:00:00.000Z" }
```
`method` is one of: `cash`, `card`, `bankTransfer`, `mbWay`, `multibanco`,
`spin`, `other`.

### Step 3b — Edit the draft (optional)

A draft can be edited before issuing with `PATCH /invoices/:id`. It uses PATCH
semantics — send only the fields you want to change; omitted fields keep their
current value. You can update `notes`, `dueDate`, `cae`, `customerId`, `items`,
`summary`, `payment`, and `seriesId` (e.g. move the draft into another series):
```bash
fiz PATCH /invoices/{id} '{ "notes": "PO #118", "seriesId": "68483b3fa19e44171e3d0808" }'
```
Returns the updated draft. A **404** here means *something* referenced wasn't
found — either the invoice `id` in the path **or** a `seriesId` you passed (a
well-formed but unknown series 404s the same way it does on create). Check which
before telling the user the draft is gone. Editing is intended for drafts — once an
invoice is issued it should be corrected with a credit note, not a PATCH.

### Step 4 — Issue the invoice

This is the binding step. **Confirm with the user**, then:
```bash
fiz POST /invoices/{id}/issue
```
The response has the official `number`, `status: ISSUED`, and a `syncWithAt`
block. **Check `syncWithAt.status`** — if it is `FAILED`, the invoice was created
locally but did not sync with the tax authority; report `atMessage` and whether
`isRetriable` is true to the user instead of claiming success.

### Step 5 — Download the PDF (optional)

```bash
fiz GET /invoices/{id}/pdf
```
Returns `{ id, name, url }`; the `url` is a downloadable link to the PDF.
Add `?format=A4` or `?format=RECEIPT` to choose the layout.

## Correcting or cancelling an issued invoice

An **issued** invoice is a final fiscal document: it cannot be deleted or
`PATCH`ed. There are two ways to undo it, both reported to the AT.

### Credit note — reverse an issued invoice

A credit note (nota de crédito) reverses a previously issued invoice. Use it to
fix a wrong amount, a wrong VAT rate, a document issued by mistake, a return, etc.
It is itself a legal document, so it needs a **reason code** (Anexo 40) and a
short **reason text**.

```bash
fiz POST /invoices/credit-notes '{
  "parentInvoiceId": "",
  "reasonCode": "INCORRECT_VAT_RATE",
  "reason": "Correção de IVA — taxa incorreta"
}'
```

- `parentInvoiceId` — the invoice being reversed. The note copies that invoice's
  customer and lines, so it reverses the **whole** document (a full reversal).
- `reasonCode` — an Anexo 40 code (see `domain.md`), e.g. `INCORRECT_VAT_RATE`,
  `WRONG_INVOICE`, `RETURN_GOODS_SERVICES`, `OPERATION_CANCELLATION`. Pick the one
  that matches *why*, don't default blindly.
- `reason` — a human-readable text. **Required** — the note is rejected without a
  non-empty reason. If you omit it, a default label for the `reasonCode` is used.
- `issue` — optional, defaults to `true`: the note is created **and issued** in one
  call (it does not stay a draft). Pass `"issue": false` to leave a draft.

The response is the issued credit note (`documentType: CREDIT_NOTE`,
`status: ISSUED`, its own `number`/`atcud`, a `syncWithAt` block — check it like
any issue). A **full** credit note also flips the parent invoice to
`status: CANCELED`.

Because a credit note issues immediately and hits the AT, treat it like an
**issue**: confirm with the user first (which invoice, reason, amount).

### To fix a wrong invoice and re-bill

The common "I issued it with the wrong VAT / wrong price" fix is two steps:

1. **Credit note** the wrong invoice (above) — reverses it.
2. **Issue a new, correct invoice** — the normal create + issue flow, with the
   right item/VAT this time.

> After re-billing, **verify the VAT on the *new* document** (`GET` it back and
> check `items[].data.vatRate`/`taxRate`), rather than assuming it inherited what
> you expected. If you re-bill by reusing a catalog item, confirm that item still
> carries the VAT you intend — issuing corrections can leave a catalog item in a
> different state than you last saw it.

### Cancel — for a document with no credit/debit notes yet

```bash
fiz POST /invoices/{id}/cancel
```

Cancels an issued invoice directly (sets `status: CANCELED`). This is **only**
allowed while the invoice has no issued credit/debit notes against it — if it
does, the API returns **400** and you must work through those notes instead.
Prefer a credit note when you need an auditable reversal reason; use cancel for a
clean, reason-less voiding of a document nothing else references yet.

## Document types (`type`)

Val

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [FIZ-co](https://github.com/FIZ-co)
- **Source:** [FIZ-co/fiz-invoicing-skill](https://github.com/FIZ-co/fiz-invoicing-skill)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-fiz-co-fiz-invoicing-skill-fiz-invoicing
- Seller: https://agentstack.voostack.com/s/fiz-co
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
