Skip to main content
This page is written for coding agents and for the people who direct them. It explains the two things you need to hold in your head at the same time, GOBL (the document format) and Invopop (the platform that stores, transforms and delivers those documents), and it lists the mistakes that agents make most often, each one verified against the current GOBL release.
If you are an AI agent: read this page in full, then fetch docs.invopop.com/AGENTS.md, a condensed version you can keep in context. Both are plain Markdown. Every other page on this site is available as Markdown by adding .md to its URL, and docs.invopop.com/llms.txt lists them all.

The model in one minute

You write a GOBL document (an invoice, a party, an order) as JSON. You upload it to Invopop, which wraps it in a signed envelope and stores it as a silo entry. You then create a job that runs a workflow over that entry. Each workflow step is provided by an app enabled in your workspace: number the invoice, sign it, convert it to the local XML, send it to the tax authority or network, generate a PDF, notify you. The step results land back on the entry as files, stamps and a state. Seven words carry most of the meaning. Keep them apart. The technical glossary has the full list.

Reading these docs as an agent

Prefer Markdown and structured sources over rendered HTML.
  • Any page as Markdown: append .md to the URL, for example /get-started/quickstart.md. Tabs, accordions and code that are hidden in the rendered page are included.
  • Index of every page: /llms.txt. Read it first, then fetch only the pages you need. /llms-full.txt concatenates the whole site and is several megabytes, so fetch pages instead.
  • Condensed rules for agents: /AGENTS.md. Paste it into your project’s own agent instructions.
  • Install the docs as a skill: npx skills add https://docs.invopop.com adds /skill.md to Claude Code, Cursor and other agents that follow the agent skills specification.
  • GOBL reference: docs.gobl.org/llms.txt indexes every schema, tax regime and addon. Schema pages such as bill/invoice list each property with its validation rules and error codes.
  • API specs: the OpenAPI files behind the API reference are linked from the bottom of /llms.txt.

MCP servers

Both documentation sites expose a Model Context Protocol server. Connect both so your agent can search Invopop and GOBL docs directly.

GOBL tooling for validating documents

You do not need an Invopop account to check whether a GOBL document is valid. Use whichever of these fits your environment.
The GOBL API at gobl.dev builds and validates any document. Wrap your document in a data property.
A valid document comes back fully calculated. An invalid one returns "key": "validation" and a faults array with a code, the JSON paths affected and a message. It is free and stateless, so use it for development and testing. Invopop’s authenticated build endpoint does the same inside your workspace.

GOBL in ten rules

Each rule below was checked against GOBL v0.504.0. Where the behaviour is surprising, the surprise is spelled out.

1. Send the minimum and let build do the maths

A GOBL document is built in two stages. You provide the raw facts, then build normalises, calculates and validates. Never write sum, total, totals or tax percent values yourself.
Build added $regime, type, currency, line indexes, the tax percentage for that regime on that date, and every total. Building an already-built document again returns it unchanged, so build is safe to repeat.
validate is not build. Validating a partial document fails with tax and totals errors because nothing has been calculated yet. Build first, then validate the result if you need to.

2. Numbers are strings

Amounts and percentages are decimal strings: "90.00", "1800.00", "20.0%". Trailing zeros are significant and set the precision. JSON numbers such as 20 or 90 are accepted on input but always come back as strings, so never compare them as floats. If you include totals in your input they are recalculated and silently replaced, which means a mismatch between your arithmetic and GOBL’s will not raise an error. Read totals from the built document, never from your own calculation.

3. $schema says what the document is

Every document needs a $schema. These are the ones Invopop processes. Each schema page on docs.gobl.org lists every property, which ones are calculated, and the validation rules with their error codes. See Document schemas for how Invopop uses them.

4. The regime decides the rules

The tax regime is a country code in $regime. If you omit it, build infers it from the supplier’s tax_id.country. Set it explicitly anyway, and set it to the country of the supplier, not the customer. GOBL currently ships regimes for 27 countries: AE, AR, AT, BE, BR, CA, CH, CO, DE, DK, EL (Greece), ES, FI, FR, GB, IE, IN, IT, MX, NL, NO, PL, PT, SA, SE, SG and US. Each has a page at https://docs.gobl.org/regimes/<code>.md listing tax categories, rate keys, extensions and correction rules.
For a supplier in a country without a regime, build has no defaults to fall back on. You must set currency yourself and give each tax an explicit percent instead of a rate key. The error you get otherwise is the unhelpful currency: missing or invalid.

5. Taxes are keys, not numbers

A line tax is a category plus a key or rate, and GOBL resolves the percentage for the regime and the issue_date. Writing {"cat": "VAT", "rate": "standard"} for a Spanish invoice dated 2012 yields 18%, the rate in force at the time. This is how the same document works in every country. Category codes are also regime specific: VAT in Europe, ST in the US, IVA in Mexico, GST in Singapore. When in doubt, read the regime page or call the GOBL MCP regime tool instead of guessing.

6. Addons switch on a country format

$addons lists the format rules a document must satisfy in addition to the regime, for example ["es-verifactu-v1"] to report to Spain’s VERI*FACTU or ["pl-favat-v3"] for Poland’s KSeF. An addon adds extensions, coded values under ext at document, line or tax level. Build fills in the defaults it can work out and reports the rest as validation faults that name the missing extension key. The addons available today are ar-arca-v4, br-nfe-v4, br-nfse-v1, co-dian-v2, de-xrechnung-v3, de-zugferd-v2, dk-oioubl-v2, es-facturae-v3, es-sii-v1, es-tbai-v1, es-verifactu-v1, eu-en16931-v2017, fi-finvoice-v3, fr-choruspro-v1, fr-ctc-flow2-v1, fr-ctc-flow6-v1, fr-ctc-flow10-v1, fr-facturx-v1, gr-mydata-v1, it-sdi-v1, it-ticket-v1, mx-cfdi-v4, pl-favat-v3, pt-saft-v1 and sa-zatca-v1. Each is documented at https://docs.gobl.org/addons/<key>.md, and the country guide under Guides tells you which one its workflow needs.
Never invent an extension value. Extension codes come from official code lists. Look them up on the addon page or with the GOBL MCP addon tool, and copy a working example from the country guide.

7. series and code are the invoice number, and Invopop usually assigns them

code is the sequential identifier of the invoice and series groups codes. Both may be empty at build time, but code is required before the document can be signed. Most workflows start with an Add sequential code step that fills code from a series, so leave it empty when you use such a workflow, and set it yourself only if your own system owns the numbering. Tax authorities require codes to be unique and consecutive per series. Invopop enforces the signing rule: creating an entry with sign: true and no code fails with GOBL-ENVELOPE-13, envelope doc is not ready to be signed.

8. Tax IDs are normalised and checksum-checked

A tax_id has a country and a code. Build strips prefixes and punctuation, so es-b-986.026.42 becomes B98602642, then validates the format and checksum. An invalid identity fails the build with codes such as GOBL-GB-TAX-IDENTITY-03, and on the Invopop API becomes a 422 whose fields object points at the property. Use real, valid identities from the country’s sandbox guide when testing, never made-up digits.

9. Never edit an issued invoice, correct it

Once an invoice has been signed or reported, it is corrected with a new document. Set type to credit-note (a refund that extends the original), debit-note (extra charges) or corrective (a full replacement, used in Spain, Poland and a few others), and reference the original in preceding. The set of allowed types depends on the regime. Do not assemble corrections by hand. Ask for one: gobl correct -i --credit invoice.json locally, or on Invopop, create an entry with previous_id set to the original entry and a correct object. Invopop copies the lines, builds the preceding block with the original’s UUID, series, code and issue date, and adds the regime-specific references that Colombia, Mexico, Greece and VERI*FACTU demand. Correction options are type (required; the older credit: true form is rejected with missing correction type), series (defaults to the original’s), issue_date, reason, copy_tax and stamps. The correct invoices guide walks through both paths.

10. Know your three UUIDs

All three are different values, even when you supply the entry ID yourself, and a credit note’s preceding[].uuid carries the original document’s uuid, not its entry id. Provide your own entry UUID with PUT so retries are idempotent. Use version 1 or 7 (time-based) for documents with a lifespan such as invoices, and version 3, 4 or 5 for long-lived data such as parties and items. Invopop enforces these per folder.

11. Tags describe scenarios

$tags lists scenario keys that change how a regime interprets the document: simplified when there is no customer, reverse-charge, self-billed, partial, customer-rates when taxes follow the customer’s country. Older examples put the same keys under tax.tags, which still works but is superseded. Regime and addon pages list the tags they recognise.

Invopop in ten rules

1. One token, one workspace

API keys are created in the Console under Configuration → API Keys and are JSON Web Tokens scoped to a single workspace. Send them as Authorization: Bearer <token>. Test with the ping endpoint before anything else.
The API sits behind Cloudflare, which rejects some default HTTP client signatures with a bare 403 whose body is error code: 1010. Python’s urllib is one of them. Always send a User-Agent header that names your application. curl, requests, httpx, Go, Node and Java defaults pass.

2. Sandbox and live share one API

There is no separate sandbox host. https://api.invopop.com serves every workspace and the token decides which one you are in. GET /access/v1/workspace tells you which: it returns the workspace name, slug, country and sandbox: true or false. Check it before you create anything. Start in a sandbox workspace, where government apps run against test environments and most countries ship a pre-enabled test supplier. Going live means a paid subscription, a live workspace and registering each real supplier.

3. Every create is idempotent if you let it be

Prefer PUT with a UUID you generate to POST. Entries and jobs do not replay: repeating a PUT with the same ID, or a POST with the same key, returns 409 Conflict (entry already exists with same id) even when the body is identical. Treat a 409 as “already created” and GET the existing record rather than generating a new ID. Jobs also take a key, unique for two years, and GET /transform/v1/jobs/key/{key} finds them. See idempotency.

4. The entry body wraps the document in data

data may be a bare document or a full envelope, which keeps its head.uuid. Invopop builds it exactly as GOBL would, so everything in the GOBL rules applies here, and a document without $schema fails with unknown-schema. A 200 returns the entry: id, folder (invoices, contacts, …), doc_schema, a snippet summarising the document, version and versions, and the built envelope in data. signed and state are omitted until something sets them, and the deprecated draft: true marks an unsigned entry. Listing with GET /silo/v1/entries?folder=invoices&limit=10 returns the same shape per item; limit must be between 10 and 100. A 4xx means nothing was stored. The body has key: "validation", a message, a faults array (each with a GOBL code, the JSON paths affected and a message) and a fields object mirroring the document path. Two useful options: sign: true signs on creation, which requires a code; allow_invalid: true stores a document that fails validation, marked invalid: true with its faults on the entry.

5. Workflows must be published and match the schema

A workflow is created for one schema (bill/invoice, org/party) and only accepts entries of that schema. It runs jobs only once it is published, not while it is a draft. The fastest way to get one is the Console’s Load template dialog, or a deep link such as https://console.invopop.com/redirect/workflows/new?template=pdf-invoice. Country guides ship their templates and the JSON behind them. Copy the workflow ID from the Console, you need it for every job. The Console’s workflow JSON is accepted as-is by PUT /transform/v1/workflows/{id}, which publishes it unless you pass draft: true. A job for a draft or unknown workflow fails at once with 404 (invalid workflow id or not published). Sending an entry of the wrong schema does not fail the request: the job runs, its first step ends KO with the code schema-mismatch, and the fault appears in the job’s faults. The workflows guide covers steps, conditions and error handling.

6. Jobs are asynchronous

Creating a job returns 202 Accepted with a stub: status: "NA" and no intents yet. Add ?wait=30 to block for up to thirty seconds; if the job finishes in time the response is 200 with the complete job instead. In production, add a Send Webhook step to the workflow and its error branch rather than polling. A complete job has completed_at, status, intents (one per executed step, each with events whose status goes RUN then OK, KO, SKIP or TIMEOUT, plus a provider code and message), the generated attachments and the resulting envelope. When something failed, the job’s faults array (provider, code, message) is the authoritative record; it is absent when nothing failed. Do not read success off status alone: a job whose step failed and whose error branch then ran reports status: "OK" and still carries faults. Some authorities answer in seconds, Italy’s SDI can take days, so design for the asynchronous outcome.

7. States are labels, not truth

An entry’s state (empty, processing, sent, error, paid, void) is set by Set state steps in the workflow. It tracks progress, it does not certify anything. You can also set one yourself with POST /silo/v1/entries/{id}/states and a body of {"key": "paid"}. To find out why a job failed, read the job’s faults, not the entry’s state or the entry’s own faults field, which exists only for backwards compatibility. See document states.

8. Apps must be enabled, suppliers must be registered

A fresh workspace has no apps enabled. Enable the ones your workflows need under Configuration → Apps. Government apps additionally need the supplier registered with the authority before its first invoice: upload an org/party entry and run the country’s registration workflow, which leaves it in the registered state. Sandbox test suppliers skip this. Every country has a supplier registration guide next to its invoicing guide under Guides.

9. Files live on the entry

PDFs, XML submissions and authority receipts are attachments on the silo entry, each with a key, mime type and url. Fetch the entry after a job completes and download what you need, or upload your own files with the files endpoints. Authority identifiers such as a VERI*FACTU hash or a SAT UUID appear as stamps in the envelope header.

10. Signed means stop editing

Before a workflow’s Sign envelope step has run you may PATCH the entry freely, with a full document or a JSON patch. After it, the API still accepts a PATCH as long as the new document is complete enough to sign again, and stores the result as a new signed version in the entry’s versions list. Do not rely on that. A signed invoice has usually been numbered and reported, and every regime forbids changing it: correct it with a credit note or replicate it into a new draft instead. Signing fixes the document’s $schema and type.
Use one workspace per tax regime. Each gets its own apps, workflows, series and keys, so your code routes each supplier’s documents to the workspace for their country. See multi-country setup.

Your first document in seven calls

This sequence works in any sandbox workspace and needs only curl. It assumes INVOPOP_TOKEN holds an API key for the workspace.
1

Check the token

The response is {"ping":"pong"}.
2

Create the PDF invoice workflow

Open this template in the Console, click Publish, and note its ID. The workflows endpoint lists it too. It numbers the invoice, signs it and renders a PDF. The steps behind the template are:
PDF invoice workflow
To create workflows without the Console, paste that JSON into a new Empty Invoice workflow in code view, or use the workflows API.
3

Preview the calculation

Optional, but it shows you exactly what Invopop will store and catches validation errors before anything is written.
The response mirrors the request, {"data": <built document>}; pass "envelop": true to get a full envelope instead. Note there is no code: the workflow’s first step will assign one.
4

Create the entry

Generate a time-based UUID (GET /utils/v1/uuid?v=7 returns one) and PUT the same body.
A 200 returns the entry: id, folder: "invoices", doc_schema, a snippet of the document and the built envelope in data. There is no state or signed yet; those keys appear once a workflow sets them.
5

Run the workflow

With wait, the response is 200 and the completed job. Check faults (absent on success) and intents[].events[].status for the step-by-step trace. Without wait you get 202 and a stub; poll GET /transform/v1/jobs/$JOB_ID until completed_at is set.
6

Read the results

The entry now has signed: true, the document’s code is 000001 from the series, and attachments holds one file with key: "pdf", named after the series and code (TEST-000001.pdf). Download it from its url or with GET /silo/v1/entries/$ENTRY_ID/files/<file id>.
7

Correct it

Issued invoices are never edited. Ask Invopop to draft a credit note from the original entry, then run the same workflow on the new entry.
The response is a new entry whose document has type: credit-note and a preceding block naming the original by its document uuid, series, code and issue date. series is optional and defaults to the original’s.
Country regimes add two things to this loop: a registered supplier, and the country’s own workflow template, which converts the document and sends it to the authority or network between the sign and PDF steps. Nothing else changes.

Prompts to copy

Each card copies a complete prompt for your coding agent. They point the agent at the Markdown sources above so it works from current documentation rather than memory.

Set up a project to integrate Invopop, with the docs wired into your agent.

Open in Cursor

Turn your own invoice data into a valid GOBL document and prove it builds.

Open in Cursor

Run a document end to end in a sandbox workspace and report what happened.

Open in Cursor

Diagnose a failed Invopop job from its ID.

Open in Cursor

Add Invopop and GOBL guidance to this project's agent instructions.

Open in Cursor

Where to look

Page paths use the full country name for compliance, timeline and FAQ pages (spain, saudi-arabia) and the ISO code for guides (es-verifactu, sa-zatca-registration). The complete list is in /llms.txt.

Vocabulary map

Participate in our community

Ask and answer questions about building with agents →