{
  "info": {
    "_postman_id": "a11e57w1-0000-4000-8000-en16931aw01",
    "name": "Attestwire — EN 16931 e-invoicing validation",
    "description": "Validate an invoice against EN 16931 and its national CIUS rules (XRechnung, Peppol BIS 3) and get back errors that teach the regulation: every failure carries the official rule id, the business term it constrains, what the rule requires, and how to fix it. Invoices go in and come out in both EN 16931 syntaxes, UBL 2.1 and UN/CEFACT CII; the profile chooses the syntax on generate, and on validate the document's root element does. The `facturx-en16931` profile is checked against core EN 16931 only — there are no Factur-X-specific rules. NO PDF, IN EITHER DIRECTION: Factur-X and ZUGFeRD files are CII XML inside a PDF/A-3 container, and this API handles only the XML — it does not build the container and cannot open one.\n\n**Setup — one field.** Open this collection's **Variables** tab and set `email` to your own address. Then send `1 · Get a key`: its test script stores the key in the `apiKey` collection variable and every other request picks it up.\n\nSet it at COLLECTION scope, which is where the shipped environment leaves room for it: `Attestwire — production` defines `baseUrl` and nothing else, on purpose. Postman resolves the narrowest scope first, so an environment variable of the same name — even an empty one — would override whatever you set here.\n\n`email` ships empty deliberately. A shared placeholder address in a published collection is claimed by whoever runs it first, and everyone after them gets `409 key_already_issued`; an empty one fails immediately with a free `400 invalid_email` that says what to do.\n\n**What a document costs.** `POST /v1/validate` and `POST /v1/generate` each spend one document from your monthly allowance (free tier: 100). A `valid: false` verdict still costs one — it is the answer you asked for. A 422 from `/v1/generate`, a 4xx, and every call in `4 · Your key` and `6 · Meta` cost nothing.\n\n**Two different 429s.** `quota_exceeded` means the month is spent and clears on the 1st. `rate_limited` means you are going too fast and clears in about a second — see `Retry-After`. Branch on the `error` field, never on the status.\n\nGenerated from the Attestwire OpenAPI document (`apps/api/src/openapi.js`), which is served at https://api.attestwire.com/openapi.json. Full docs: https://api.attestwire.com/docs",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{apiKey}}",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://api.attestwire.com",
      "type": "string",
      "description": "The API origin. There is one deployment; you should not need to change this."
    },
    {
      "key": "apiKey",
      "value": "",
      "type": "string",
      "description": "Your key. Filled in automatically by `1 · Get a key`, or paste one you already have. Keep it at collection scope: the shipped environment does not define `apiKey`, and an environment variable of the same name would override this one."
    },
    {
      "key": "allowRotate",
      "value": "false",
      "type": "string",
      "description": "Leave this false to run the whole collection safely. Rotation destroys the key every other request is using, and the Runner would otherwise fire it like any other item. Set it to `true` only when you actually mean to rotate."
    },
    {
      "key": "email",
      "value": "",
      "type": "string",
      "description": "REQUIRED — set this HERE, in the collection's Variables tab, before running `1 · Get a key`. One free key per address, and we store only its hash, so an address that already has a key cannot be issued another. The shipped environment does not define `email`: an environment variable is narrower than a collection one and would override whatever you type here."
    }
  ],
  "item": [
    {
      "name": "1 · Get a key",
      "description": "Start here, after setting the `email` collection variable to your own address. One free key per email address, 100 documents a month, no card. The plaintext key is returned once and is not recoverable — the test script below stores it in the `apiKey` collection variable, which every other request already uses.",
      "item": [
        {
          "name": "Get a free API key",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/keys",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "keys"
              ]
            },
            "description": "**Set the `email` collection variable to your own address before sending this** (Variables tab on the collection, not the environment — the shipped environment defines only `baseUrl`, because a same-named environment variable is narrower and would override it). It ships empty on purpose: this collection is published, and a shared placeholder address would be claimed by whoever ran it first and then return `409 `key_already_issued`` for everybody after them. An unset variable fails fast with a `400 invalid_email`, which costs nothing — the address is checked before the per-IP signup limiter.\n\nOne free key per address, 100 documents a month, no card. The plaintext key is returned once and is not recoverable; the test script stores it in the `apiKey` collection variable, so write it down somewhere durable as well.",
            "auth": {
              "type": "noauth"
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"{{email}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "201 — The only time the plaintext key exists on our side.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  },
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/keys",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "keys"
                  ]
                },
                "description": "**Set the `email` collection variable to your own address before sending this** (Variables tab on the collection, not the environment — the shipped environment defines only `baseUrl`, because a same-named environment variable is narrower and would override it). It ships empty on purpose: this collection is published, and a shared placeholder address would be claimed by whoever ran it first and then return `409 `key_already_issued`` for everybody after them. An unset variable fails fast with a `400 invalid_email`, which costs nothing — the address is checked before the per-IP signup limiter.\n\nOne free key per address, 100 documents a month, no card. The plaintext key is returned once and is not recoverable; the test script stores it in the `apiKey` collection variable, so write it down somewhere durable as well.",
                "auth": {
                  "type": "noauth"
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"email\": \"{{email}}\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Created",
              "code": 201,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "body": "{\n  \"key\": \"aw_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\",\n  \"tier\": \"free\",\n  \"monthly_limit\": 100,\n  \"created\": \"2026-08-11T09:12:33.104Z\",\n  \"warning\": \"Store this key now. It is shown once and cannot be recovered.\"\n}"
            },
            {
              "name": "400 — Not an address",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  },
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/keys",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "keys"
                  ]
                },
                "description": "**Set the `email` collection variable to your own address before sending this** (Variables tab on the collection, not the environment — the shipped environment defines only `baseUrl`, because a same-named environment variable is narrower and would override it). It ships empty on purpose: this collection is published, and a shared placeholder address would be claimed by whoever ran it first and then return `409 `key_already_issued`` for everybody after them. An unset variable fails fast with a `400 invalid_email`, which costs nothing — the address is checked before the per-IP signup limiter.\n\nOne free key per address, 100 documents a month, no card. The plaintext key is returned once and is not recoverable; the test script stores it in the `apiKey` collection variable, so write it down somewhere durable as well.",
                "auth": {
                  "type": "noauth"
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"email\": \"{{email}}\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Bad Request",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "body": "{\n  \"error\": \"invalid_email\",\n  \"message\": \"That does not look like an email address.\",\n  \"docs\": \"https://api.attestwire.com/docs\"\n}"
            },
            {
              "name": "409 — One free key per address",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  },
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/keys",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "keys"
                  ]
                },
                "description": "**Set the `email` collection variable to your own address before sending this** (Variables tab on the collection, not the environment — the shipped environment defines only `baseUrl`, because a same-named environment variable is narrower and would override it). It ships empty on purpose: this collection is published, and a shared placeholder address would be claimed by whoever ran it first and then return `409 `key_already_issued`` for everybody after them. An unset variable fails fast with a `400 invalid_email`, which costs nothing — the address is checked before the per-IP signup limiter.\n\nOne free key per address, 100 documents a month, no card. The plaintext key is returned once and is not recoverable; the test script stores it in the `apiKey` collection variable, so write it down somewhere durable as well.",
                "auth": {
                  "type": "noauth"
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"email\": \"{{email}}\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Conflict",
              "code": 409,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "body": "{\n  \"error\": \"key_already_issued\",\n  \"message\": \"A free key already exists for this email address. If you still hold the key, replace it yourself with POST /v1/keys/rotate — your plan and usage carry over. If it is lost, a different email address gets you a working free key immediately, and a plus-alias counts. On a paid plan, email hello@attestwire.com from the address on your Paddle billing receipt and the replacement comes back to that address. Either way we cannot send you the old key: we store only its SHA-256 hash, so there is nothing to show again.\",\n  \"docs\": \"https://api.attestwire.com/docs#rotation\"\n}"
            }
          ],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Postman resolves the NARROWEST scope first: environment beats collection.",
                  "// The shipped environment defines only `baseUrl`, so nothing here is shadowed —",
                  "// but if you have added `apiKey` to an environment of your own, writing only the",
                  "// collection variable would be silently overridden by it. Write both.",
                  "const storeKey = (value) => {",
                  "  if (pm.environment.has('apiKey')) pm.environment.set('apiKey', value);",
                  "  pm.collectionVariables.set('apiKey', value);",
                  "};",
                  "",
                  "// The two answers that mean \"you have not set `email` to your own address yet\".",
                  "// Say so in the console: a bare red assertion does not tell you what to change,",
                  "// and storing `undefined` in apiKey would 401 every request after this one.",
                  "if (pm.response.code === 400) {",
                  "  console.log('Set the `email` collection variable to your own address, then re-send.');",
                  "}",
                  "if (pm.response.code === 409) {",
                  "  console.log('That address already has a free key and we store only its hash, so it',",
                  "    'cannot be shown again. Change the `email` variable and re-send. If the key is',",
                  "    'yours and lost, email hello@attestwire.com.');",
                  "}",
                  "",
                  "pm.test('201 Created', () => pm.response.to.have.status(201));",
                  "",
                  "if (pm.response.code === 201) {",
                  "  const body = pm.response.json();",
                  "  pm.test('the response carries a key and a limit', () => {",
                  "    pm.expect(body.key).to.be.a('string');",
                  "    pm.expect(body.monthly_limit).to.be.a('number');",
                  "  });",
                  "",
                  "  // Store it. This is the only time the key exists outside your notes.",
                  "  storeKey(body.key);",
                  "  console.log('apiKey stored for this collection. Save it somewhere durable too.');",
                  "}"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "2 · Validate",
      "description": "The core call. Send an invoice as JSON, get back every EN 16931 / CIUS rule it breaks, each with the official rule id, the business term it constrains, what the rule requires and how to fix it. An invalid invoice is a 200 — check `valid`.",
      "item": [
        {
          "name": "Validate a compliant XRechnung invoice",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/validate",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "validate"
              ]
            },
            "description": "Counts as one document against your monthly allowance. A response with `valid: false` is a successful call — it still costs a document. Findings come back in three arrays by severity: `errors` (fatal, the only ones that set `valid: false`), `warnings`, and `information` (advisory).\n\nTWO WAYS TO SEND AN INVOICE, chosen by `content-type`.\n\n`application/json` — our `InvoiceInput` model. Unchanged.\n\n`application/xml` or `text/xml` — an **invoice document you already have**. We read it into the same invoice model and run the same rules, and the response adds `syntax` (which reader ran), `customizationId` (BT-24), `profileId` (BT-23), `unmapped` (everything in the file that did not reach the model) and `source`.\n\nThe reader takes **both EN 16931 syntaxes and both document types**: a UBL 2.1 `Invoice`, a UBL 2.1 `CreditNote`, and a UN/CEFACT CII `CrossIndustryInvoice`, which is one document for invoices and credit notes alike. Send the file as it is — there is no parameter for the syntax and none for the document type, because the root element decides both and the response reports what was read.\n\n**It is not a PDF reader.** Factur-X and ZUGFeRD are CII XML inside a PDF/A-3 container; only the XML inside can be read here, so extract it and send that. A PDF, a `ubl:DebitNote`, or any other root element is refused with `415`.\n\nReading a file is a **pre-flight, not an authority**. We validate the model we read, not the XML a receiver judges, so rules that constrain the document itself (BR-DE-13, BR-DE-21 on BT-24) do not run and a document that passes here can still be rejected by KoSIT or by a receiving platform.\n\nMETERING: an XML request costs one document once the file has been read and judged — `valid: true` and `valid: false` cost the same, exactly as on the JSON path. A file that cannot be read at all (`400`, `413`, `415`) never reaches the rules and costs nothing; those responses carry the unchanged `X-RateLimit-*` set so you can see the allowance did not move.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"profile\": \"xrechnung-ubl\",\n  \"invoiceNumber\": \"INV-2026-0042\",\n  \"issueDate\": \"2026-08-09\",\n  \"dueDate\": \"2026-09-08\",\n  \"deliveryDate\": \"2026-07-31\",\n  \"currency\": \"EUR\",\n  \"buyerReference\": \"04011000-1234512345-06\",\n  \"seller\": {\n    \"name\": \"Nordwind Software GmbH\",\n    \"vatId\": \"DE123456789\",\n    \"address\": {\n      \"line1\": \"Hafenstraße 12\",\n      \"city\": \"Hamburg\",\n      \"postalCode\": \"20095\",\n      \"countryCode\": \"DE\"\n    },\n    \"electronicAddress\": {\n      \"schemeId\": \"9930\",\n      \"value\": \"DE123456789\"\n    },\n    \"contact\": {\n      \"name\": \"Buchhaltung\",\n      \"phone\": \"+49 40 1234567\",\n      \"email\": \"rechnungen@nordwind.example\"\n    }\n  },\n  \"buyer\": {\n    \"name\": \"Stadt Musterstadt\",\n    \"address\": {\n      \"line1\": \"Rathausplatz 1\",\n      \"city\": \"Musterstadt\",\n      \"postalCode\": \"80331\",\n      \"countryCode\": \"DE\"\n    },\n    \"electronicAddress\": {\n      \"schemeId\": \"0204\",\n      \"value\": \"04011000-1234512345-06\"\n    }\n  },\n  \"payment\": {\n    \"meansCode\": \"58\",\n    \"iban\": \"DE02120300000000202051\",\n    \"accountName\": \"Nordwind Software GmbH\"\n  },\n  \"paymentTerms\": \"Net 30 days\",\n  \"lines\": [\n    {\n      \"id\": \"1\",\n      \"description\": \"Implementation services, July 2026\",\n      \"quantity\": 12,\n      \"unitCode\": \"HUR\",\n      \"unitPrice\": 145,\n      \"vatCategory\": \"S\",\n      \"vatRate\": 19\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "200 — Nothing to fix.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  },
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/validate",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "validate"
                  ]
                },
                "description": "Counts as one document against your monthly allowance. A response with `valid: false` is a successful call — it still costs a document. Findings come back in three arrays by severity: `errors` (fatal, the only ones that set `valid: false`), `warnings`, and `information` (advisory).\n\nTWO WAYS TO SEND AN INVOICE, chosen by `content-type`.\n\n`application/json` — our `InvoiceInput` model. Unchanged.\n\n`application/xml` or `text/xml` — an **invoice document you already have**. We read it into the same invoice model and run the same rules, and the response adds `syntax` (which reader ran), `customizationId` (BT-24), `profileId` (BT-23), `unmapped` (everything in the file that did not reach the model) and `source`.\n\nThe reader takes **both EN 16931 syntaxes and both document types**: a UBL 2.1 `Invoice`, a UBL 2.1 `CreditNote`, and a UN/CEFACT CII `CrossIndustryInvoice`, which is one document for invoices and credit notes alike. Send the file as it is — there is no parameter for the syntax and none for the document type, because the root element decides both and the response reports what was read.\n\n**It is not a PDF reader.** Factur-X and ZUGFeRD are CII XML inside a PDF/A-3 container; only the XML inside can be read here, so extract it and send that. A PDF, a `ubl:DebitNote`, or any other root element is refused with `415`.\n\nReading a file is a **pre-flight, not an authority**. We validate the model we read, not the XML a receiver judges, so rules that constrain the document itself (BR-DE-13, BR-DE-21 on BT-24) do not run and a document that passes here can still be rejected by KoSIT or by a receiving platform.\n\nMETERING: an XML request costs one document once the file has been read and judged — `valid: true` and `valid: false` cost the same, exactly as on the JSON path. A file that cannot be read at all (`400`, `413`, `415`) never reaches the rules and costs nothing; those responses carry the unchanged `X-RateLimit-*` set so you can see the allowance did not move.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"profile\": \"xrechnung-ubl\",\n  \"invoiceNumber\": \"INV-2026-0042\",\n  \"issueDate\": \"2026-08-09\",\n  \"dueDate\": \"2026-09-08\",\n  \"deliveryDate\": \"2026-07-31\",\n  \"currency\": \"EUR\",\n  \"buyerReference\": \"04011000-1234512345-06\",\n  \"seller\": {\n    \"name\": \"Nordwind Software GmbH\",\n    \"vatId\": \"DE123456789\",\n    \"address\": {\n      \"line1\": \"Hafenstraße 12\",\n      \"city\": \"Hamburg\",\n      \"postalCode\": \"20095\",\n      \"countryCode\": \"DE\"\n    },\n    \"electronicAddress\": {\n      \"schemeId\": \"9930\",\n      \"value\": \"DE123456789\"\n    },\n    \"contact\": {\n      \"name\": \"Buchhaltung\",\n      \"phone\": \"+49 40 1234567\",\n      \"email\": \"rechnungen@nordwind.example\"\n    }\n  },\n  \"buyer\": {\n    \"name\": \"Stadt Musterstadt\",\n    \"address\": {\n      \"line1\": \"Rathausplatz 1\",\n      \"city\": \"Musterstadt\",\n      \"postalCode\": \"80331\",\n      \"countryCode\": \"DE\"\n    },\n    \"electronicAddress\": {\n      \"schemeId\": \"0204\",\n      \"value\": \"04011000-1234512345-06\"\n    }\n  },\n  \"payment\": {\n    \"meansCode\": \"58\",\n    \"iban\": \"DE02120300000000202051\",\n    \"accountName\": \"Nordwind Software GmbH\"\n  },\n  \"paymentTerms\": \"Net 30 days\",\n  \"lines\": [\n    {\n      \"id\": \"1\",\n      \"description\": \"Implementation services, July 2026\",\n      \"quantity\": 12,\n      \"unitCode\": \"HUR\",\n      \"unitPrice\": 145,\n      \"vatCategory\": \"S\",\n      \"vatRate\": 19\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "body": "{\n  \"valid\": true,\n  \"profile\": \"xrechnung-ubl\",\n  \"errors\": [],\n  \"warnings\": [],\n  \"information\": [],\n  \"provenance\": {\n    \"engine\": \"@attestwire/en16931\",\n    \"engine_version\": \"0.9.0\",\n    \"ruleset\": \"en16931@0.9.0\",\n    \"profile\": \"xrechnung-ubl\",\n    \"kosit_conformance\": {\n      \"recorded\": \"2026-08-13\",\n      \"validator\": \"1.6.2\",\n      \"configuration\": \"3.0.2\",\n      \"configuration_published\": \"2026-01-31\"\n    }\n  }\n}"
            },
            {
              "name": "401 — No Authorization header",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  },
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/validate",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "validate"
                  ]
                },
                "description": "Counts as one document against your monthly allowance. A response with `valid: false` is a successful call — it still costs a document. Findings come back in three arrays by severity: `errors` (fatal, the only ones that set `valid: false`), `warnings`, and `information` (advisory).\n\nTWO WAYS TO SEND AN INVOICE, chosen by `content-type`.\n\n`application/json` — our `InvoiceInput` model. Unchanged.\n\n`application/xml` or `text/xml` — an **invoice document you already have**. We read it into the same invoice model and run the same rules, and the response adds `syntax` (which reader ran), `customizationId` (BT-24), `profileId` (BT-23), `unmapped` (everything in the file that did not reach the model) and `source`.\n\nThe reader takes **both EN 16931 syntaxes and both document types**: a UBL 2.1 `Invoice`, a UBL 2.1 `CreditNote`, and a UN/CEFACT CII `CrossIndustryInvoice`, which is one document for invoices and credit notes alike. Send the file as it is — there is no parameter for the syntax and none for the document type, because the root element decides both and the response reports what was read.\n\n**It is not a PDF reader.** Factur-X and ZUGFeRD are CII XML inside a PDF/A-3 container; only the XML inside can be read here, so extract it and send that. A PDF, a `ubl:DebitNote`, or any other root element is refused with `415`.\n\nReading a file is a **pre-flight, not an authority**. We validate the model we read, not the XML a receiver judges, so rules that constrain the document itself (BR-DE-13, BR-DE-21 on BT-24) do not run and a document that passes here can still be rejected by KoSIT or by a receiving platform.\n\nMETERING: an XML request costs one document once the file has been read and judged — `valid: true` and `valid: false` cost the same, exactly as on the JSON path. A file that cannot be read at all (`400`, `413`, `415`) never reaches the rules and costs nothing; those responses carry the unchanged `X-RateLimit-*` set so you can see the allowance did not move.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"profile\": \"xrechnung-ubl\",\n  \"invoiceNumber\": \"INV-2026-0042\",\n  \"issueDate\": \"2026-08-09\",\n  \"dueDate\": \"2026-09-08\",\n  \"deliveryDate\": \"2026-07-31\",\n  \"currency\": \"EUR\",\n  \"buyerReference\": \"04011000-1234512345-06\",\n  \"seller\": {\n    \"name\": \"Nordwind Software GmbH\",\n    \"vatId\": \"DE123456789\",\n    \"address\": {\n      \"line1\": \"Hafenstraße 12\",\n      \"city\": \"Hamburg\",\n      \"postalCode\": \"20095\",\n      \"countryCode\": \"DE\"\n    },\n    \"electronicAddress\": {\n      \"schemeId\": \"9930\",\n      \"value\": \"DE123456789\"\n    },\n    \"contact\": {\n      \"name\": \"Buchhaltung\",\n      \"phone\": \"+49 40 1234567\",\n      \"email\": \"rechnungen@nordwind.example\"\n    }\n  },\n  \"buyer\": {\n    \"name\": \"Stadt Musterstadt\",\n    \"address\": {\n      \"line1\": \"Rathausplatz 1\",\n      \"city\": \"Musterstadt\",\n      \"postalCode\": \"80331\",\n      \"countryCode\": \"DE\"\n    },\n    \"electronicAddress\": {\n      \"schemeId\": \"0204\",\n      \"value\": \"04011000-1234512345-06\"\n    }\n  },\n  \"payment\": {\n    \"meansCode\": \"58\",\n    \"iban\": \"DE02120300000000202051\",\n    \"accountName\": \"Nordwind Software GmbH\"\n  },\n  \"paymentTerms\": \"Net 30 days\",\n  \"lines\": [\n    {\n      \"id\": \"1\",\n      \"description\": \"Implementation services, July 2026\",\n      \"quantity\": 12,\n      \"unitCode\": \"HUR\",\n      \"unitPrice\": 145,\n      \"vatCategory\": \"S\",\n      \"vatRate\": 19\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Unauthorized",
              "code": 401,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "body": "{\n  \"error\": \"missing_api_key\",\n  \"message\": \"Send your key as an Authorization: Bearer header. Get one free at POST /v1/keys.\",\n  \"docs\": \"https://api.attestwire.com/docs#auth\"\n}"
            },
            {
              "name": "429 — Too fast. Carries Retry-After, and NO X-RateLimit-* set.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  },
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/validate",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "validate"
                  ]
                },
                "description": "Counts as one document against your monthly allowance. A response with `valid: false` is a successful call — it still costs a document. Findings come back in three arrays by severity: `errors` (fatal, the only ones that set `valid: false`), `warnings`, and `information` (advisory).\n\nTWO WAYS TO SEND AN INVOICE, chosen by `content-type`.\n\n`application/json` — our `InvoiceInput` model. Unchanged.\n\n`application/xml` or `text/xml` — an **invoice document you already have**. We read it into the same invoice model and run the same rules, and the response adds `syntax` (which reader ran), `customizationId` (BT-24), `profileId` (BT-23), `unmapped` (everything in the file that did not reach the model) and `source`.\n\nThe reader takes **both EN 16931 syntaxes and both document types**: a UBL 2.1 `Invoice`, a UBL 2.1 `CreditNote`, and a UN/CEFACT CII `CrossIndustryInvoice`, which is one document for invoices and credit notes alike. Send the file as it is — there is no parameter for the syntax and none for the document type, because the root element decides both and the response reports what was read.\n\n**It is not a PDF reader.** Factur-X and ZUGFeRD are CII XML inside a PDF/A-3 container; only the XML inside can be read here, so extract it and send that. A PDF, a `ubl:DebitNote`, or any other root element is refused with `415`.\n\nReading a file is a **pre-flight, not an authority**. We validate the model we read, not the XML a receiver judges, so rules that constrain the document itself (BR-DE-13, BR-DE-21 on BT-24) do not run and a document that passes here can still be rejected by KoSIT or by a receiving platform.\n\nMETERING: an XML request costs one document once the file has been read and judged — `valid: true` and `valid: false` cost the same, exactly as on the JSON path. A file that cannot be read at all (`400`, `413`, `415`) never reaches the rules and costs nothing; those responses carry the unchanged `X-RateLimit-*` set so you can see the allowance did not move.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"profile\": \"xrechnung-ubl\",\n  \"invoiceNumber\": \"INV-2026-0042\",\n  \"issueDate\": \"2026-08-09\",\n  \"dueDate\": \"2026-09-08\",\n  \"deliveryDate\": \"2026-07-31\",\n  \"currency\": \"EUR\",\n  \"buyerReference\": \"04011000-1234512345-06\",\n  \"seller\": {\n    \"name\": \"Nordwind Software GmbH\",\n    \"vatId\": \"DE123456789\",\n    \"address\": {\n      \"line1\": \"Hafenstraße 12\",\n      \"city\": \"Hamburg\",\n      \"postalCode\": \"20095\",\n      \"countryCode\": \"DE\"\n    },\n    \"electronicAddress\": {\n      \"schemeId\": \"9930\",\n      \"value\": \"DE123456789\"\n    },\n    \"contact\": {\n      \"name\": \"Buchhaltung\",\n      \"phone\": \"+49 40 1234567\",\n      \"email\": \"rechnungen@nordwind.example\"\n    }\n  },\n  \"buyer\": {\n    \"name\": \"Stadt Musterstadt\",\n    \"address\": {\n      \"line1\": \"Rathausplatz 1\",\n      \"city\": \"Musterstadt\",\n      \"postalCode\": \"80331\",\n      \"countryCode\": \"DE\"\n    },\n    \"electronicAddress\": {\n      \"schemeId\": \"0204\",\n      \"value\": \"04011000-1234512345-06\"\n    }\n  },\n  \"payment\": {\n    \"meansCode\": \"58\",\n    \"iban\": \"DE02120300000000202051\",\n    \"accountName\": \"Nordwind Software GmbH\"\n  },\n  \"paymentTerms\": \"Net 30 days\",\n  \"lines\": [\n    {\n      \"id\": \"1\",\n      \"description\": \"Implementation services, July 2026\",\n      \"quantity\": 12,\n      \"unitCode\": \"HUR\",\n      \"unitPrice\": 145,\n      \"vatCategory\": \"S\",\n      \"vatRate\": 19\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Too Many Requests",
              "code": 429,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "body": "{\n  \"error\": \"rate_limited\",\n  \"message\": \"Too many requests per second on this key. Retry in 1s. This is a pace limit, not your monthly quota — nothing was charged, your allowance is untouched, and the request will succeed if you simply slow down. If you are running a legitimate bulk import, email hello@attestwire.com rather than retrying harder.\",\n  \"docs\": \"https://api.attestwire.com/docs#limits\"\n}"
            }
          ],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200 OK', () => pm.response.to.have.status(200));",
                  "const body = pm.response.json();",
                  "pm.test('the invoice passes', () => pm.expect(body.valid).to.eql(true));",
                  "pm.test('no fatal findings', () => pm.expect(body.errors).to.eql([]));"
                ]
              }
            }
          ]
        },
        {
          "name": "Validate an invoice that fails BR-DE-15",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/validate",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "validate"
              ]
            },
            "description": "The same invoice with the buyer reference (BT-10) removed. XRechnung makes it mandatory — for German public-sector buyers it is the Leitweg-ID — so this is a fatal error, and the response tells you which field, which rule, and what to put in it. This is the one-request demonstration of what the API is for.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"profile\": \"xrechnung-ubl\",\n  \"invoiceNumber\": \"INV-2026-0042\",\n  \"issueDate\": \"2026-08-09\",\n  \"dueDate\": \"2026-09-08\",\n  \"deliveryDate\": \"2026-07-31\",\n  \"currency\": \"EUR\",\n  \"seller\": {\n    \"name\": \"Nordwind Software GmbH\",\n    \"vatId\": \"DE123456789\",\n    \"address\": {\n      \"line1\": \"Hafenstraße 12\",\n      \"city\": \"Hamburg\",\n      \"postalCode\": \"20095\",\n      \"countryCode\": \"DE\"\n    },\n    \"electronicAddress\": {\n      \"schemeId\": \"9930\",\n      \"value\": \"DE123456789\"\n    },\n    \"contact\": {\n      \"name\": \"Buchhaltung\",\n      \"phone\": \"+49 40 1234567\",\n      \"email\": \"rechnungen@nordwind.example\"\n    }\n  },\n  \"buyer\": {\n    \"name\": \"Stadt Musterstadt\",\n    \"address\": {\n      \"line1\": \"Rathausplatz 1\",\n      \"city\": \"Musterstadt\",\n      \"postalCode\": \"80331\",\n      \"countryCode\": \"DE\"\n    },\n    \"electronicAddress\": {\n      \"schemeId\": \"0204\",\n      \"value\": \"04011000-1234512345-06\"\n    }\n  },\n  \"payment\": {\n    \"meansCode\": \"58\",\n    \"iban\": \"DE02120300000000202051\",\n    \"accountName\": \"Nordwind Software GmbH\"\n  },\n  \"paymentTerms\": \"Net 30 days\",\n  \"lines\": [\n    {\n      \"id\": \"1\",\n      \"description\": \"Implementation services, July 2026\",\n      \"quantity\": 12,\n      \"unitCode\": \"HUR\",\n      \"unitPrice\": 145,\n      \"vatCategory\": \"S\",\n      \"vatRate\": 19\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "200 — One fatal error, with the rule id, the business term and the fix.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  },
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/validate",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "validate"
                  ]
                },
                "description": "The same invoice with the buyer reference (BT-10) removed. XRechnung makes it mandatory — for German public-sector buyers it is the Leitweg-ID — so this is a fatal error, and the response tells you which field, which rule, and what to put in it. This is the one-request demonstration of what the API is for.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"profile\": \"xrechnung-ubl\",\n  \"invoiceNumber\": \"INV-2026-0042\",\n  \"issueDate\": \"2026-08-09\",\n  \"dueDate\": \"2026-09-08\",\n  \"deliveryDate\": \"2026-07-31\",\n  \"currency\": \"EUR\",\n  \"seller\": {\n    \"name\": \"Nordwind Software GmbH\",\n    \"vatId\": \"DE123456789\",\n    \"address\": {\n      \"line1\": \"Hafenstraße 12\",\n      \"city\": \"Hamburg\",\n      \"postalCode\": \"20095\",\n      \"countryCode\": \"DE\"\n    },\n    \"electronicAddress\": {\n      \"schemeId\": \"9930\",\n      \"value\": \"DE123456789\"\n    },\n    \"contact\": {\n      \"name\": \"Buchhaltung\",\n      \"phone\": \"+49 40 1234567\",\n      \"email\": \"rechnungen@nordwind.example\"\n    }\n  },\n  \"buyer\": {\n    \"name\": \"Stadt Musterstadt\",\n    \"address\": {\n      \"line1\": \"Rathausplatz 1\",\n      \"city\": \"Musterstadt\",\n      \"postalCode\": \"80331\",\n      \"countryCode\": \"DE\"\n    },\n    \"electronicAddress\": {\n      \"schemeId\": \"0204\",\n      \"value\": \"04011000-1234512345-06\"\n    }\n  },\n  \"payment\": {\n    \"meansCode\": \"58\",\n    \"iban\": \"DE02120300000000202051\",\n    \"accountName\": \"Nordwind Software GmbH\"\n  },\n  \"paymentTerms\": \"Net 30 days\",\n  \"lines\": [\n    {\n      \"id\": \"1\",\n      \"description\": \"Implementation services, July 2026\",\n      \"quantity\": 12,\n      \"unitCode\": \"HUR\",\n      \"unitPrice\": 145,\n      \"vatCategory\": \"S\",\n      \"vatRate\": 19\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "body": "{\n  \"valid\": false,\n  \"profile\": \"xrechnung-ubl\",\n  \"errors\": [\n    {\n      \"rule\": \"BR-DE-15\",\n      \"field\": \"BT-10\",\n      \"severity\": \"fatal\",\n      \"message\": \"XRechnung requires a buyer reference (BT-10). For German public-sector buyers this is the Leitweg-ID; business buyers may supply any reference, but the field must be present.\",\n      \"fix\": \"Ask your client for their Leitweg-ID (public sector) or an order/customer reference, and set buyerReference.\",\n      \"example\": \"\\\"buyerReference\\\": \\\"04011000-1234512345-06\\\"\",\n      \"xpath\": \"/ubl:Invoice/cbc:BuyerReference\",\n      \"docsUrl\": \"https://attestwire.com/rules/BR-DE-15\"\n    }\n  ],\n  \"warnings\": [],\n  \"information\": [],\n  \"provenance\": {\n    \"engine\": \"@attestwire/en16931\",\n    \"engine_version\": \"0.9.0\",\n    \"ruleset\": \"en16931@0.9.0\",\n    \"profile\": \"xrechnung-ubl\",\n    \"kosit_conformance\": {\n      \"recorded\": \"2026-08-13\",\n      \"validator\": \"1.6.2\",\n      \"configuration\": \"3.0.2\",\n      \"configuration_published\": \"2026-01-31\"\n    }\n  }\n}"
            }
          ],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200 OK — an invalid invoice is still a successful call', () =>",
                  "  pm.response.to.have.status(200));",
                  "const body = pm.response.json();",
                  "pm.test('the invoice is rejected', () => pm.expect(body.valid).to.eql(false));",
                  "pm.test('BR-DE-15 is reported against BT-10', () => {",
                  "  const finding = body.errors.find((e) => e.rule === 'BR-DE-15');",
                  "  pm.expect(finding, \"BR-DE-15 missing\").to.be.an('object');",
                  "  pm.expect(finding.field).to.eql('BT-10');",
                  "  pm.expect(finding.fix).to.be.a('string');",
                  "});"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "3 · Generate",
      "description": "Build the XML instead of checking it. The invoice is validated first: a fatal failure returns 422 with the same teaching errors and costs nothing.",
      "item": [
        {
          "name": "Generate XRechnung UBL (JSON envelope)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/generate",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "generate"
              ]
            },
            "description": "Validates first: a fatal rule failure returns 422 with the same teaching errors as /v1/validate, and does not consume a document. Supported profiles in this build: en16931, xrechnung-ubl, peppol-bis-3, xrechnung-cii, facturx-en16931.\n\nTHE PROFILE CHOOSES THE SYNTAX. xrechnung-cii and facturx-en16931 come back as UN/CEFACT CII; every other profile comes back as UBL 2.1. The JSON response says which in its `syntax` field.\n\n**CII output is XML, not a PDF.** Factur-X and ZUGFeRD files are CII XML inside a PDF/A-3 container. This endpoint returns the XML. It does not build the container, does not attach the XML as `factur-x.xml` and does not set `/AFRelationship`, so a `facturx-en16931` response is the payload and not a Factur-X document. Pass it to a Factur-X packaging library if you need the file.\n\n**Output verification is not uniform, and is never per-request.** The generator's `xrechnung-cii` fixture documents are run through the official KoSIT validator on release and accepted. The `facturx-en16931` fixtures are not: that profile's BT-24 is the core EN 16931 one, which matches no XRechnung scenario, so KoSIT answers \"no scenario matched\" instead of a verdict, and an absence of a verdict is not a pass. Neither case says anything about YOUR document: this endpoint does not send anything to KoSIT.\n\nThe JSON envelope carries `provenance`: the engine version and rule set that cleared this document. `?format=xml` returns the bytes alone and therefore carries none — an XML document has nowhere to put it, and this API does not write its own metadata into a customer's tax document. Read GET /v1/versions if you need it for a raw-XML pipeline.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"profile\": \"xrechnung-ubl\",\n  \"invoiceNumber\": \"INV-2026-0042\",\n  \"issueDate\": \"2026-08-09\",\n  \"dueDate\": \"2026-09-08\",\n  \"deliveryDate\": \"2026-07-31\",\n  \"currency\": \"EUR\",\n  \"buyerReference\": \"04011000-1234512345-06\",\n  \"seller\": {\n    \"name\": \"Nordwind Software GmbH\",\n    \"vatId\": \"DE123456789\",\n    \"address\": {\n      \"line1\": \"Hafenstraße 12\",\n      \"city\": \"Hamburg\",\n      \"postalCode\": \"20095\",\n      \"countryCode\": \"DE\"\n    },\n    \"electronicAddress\": {\n      \"schemeId\": \"9930\",\n      \"value\": \"DE123456789\"\n    },\n    \"contact\": {\n      \"name\": \"Buchhaltung\",\n      \"phone\": \"+49 40 1234567\",\n      \"email\": \"rechnungen@nordwind.example\"\n    }\n  },\n  \"buyer\": {\n    \"name\": \"Stadt Musterstadt\",\n    \"address\": {\n      \"line1\": \"Rathausplatz 1\",\n      \"city\": \"Musterstadt\",\n      \"postalCode\": \"80331\",\n      \"countryCode\": \"DE\"\n    },\n    \"electronicAddress\": {\n      \"schemeId\": \"0204\",\n      \"value\": \"04011000-1234512345-06\"\n    }\n  },\n  \"payment\": {\n    \"meansCode\": \"58\",\n    \"iban\": \"DE02120300000000202051\",\n    \"accountName\": \"Nordwind Software GmbH\"\n  },\n  \"paymentTerms\": \"Net 30 days\",\n  \"lines\": [\n    {\n      \"id\": \"1\",\n      \"description\": \"Implementation services, July 2026\",\n      \"quantity\": 12,\n      \"unitCode\": \"HUR\",\n      \"unitPrice\": 145,\n      \"vatCategory\": \"S\",\n      \"vatRate\": 19\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "200 — the complete document, JSON-wrapped",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  },
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/generate",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "generate"
                  ]
                },
                "description": "Validates first: a fatal rule failure returns 422 with the same teaching errors as /v1/validate, and does not consume a document. Supported profiles in this build: en16931, xrechnung-ubl, peppol-bis-3, xrechnung-cii, facturx-en16931.\n\nTHE PROFILE CHOOSES THE SYNTAX. xrechnung-cii and facturx-en16931 come back as UN/CEFACT CII; every other profile comes back as UBL 2.1. The JSON response says which in its `syntax` field.\n\n**CII output is XML, not a PDF.** Factur-X and ZUGFeRD files are CII XML inside a PDF/A-3 container. This endpoint returns the XML. It does not build the container, does not attach the XML as `factur-x.xml` and does not set `/AFRelationship`, so a `facturx-en16931` response is the payload and not a Factur-X document. Pass it to a Factur-X packaging library if you need the file.\n\n**Output verification is not uniform, and is never per-request.** The generator's `xrechnung-cii` fixture documents are run through the official KoSIT validator on release and accepted. The `facturx-en16931` fixtures are not: that profile's BT-24 is the core EN 16931 one, which matches no XRechnung scenario, so KoSIT answers \"no scenario matched\" instead of a verdict, and an absence of a verdict is not a pass. Neither case says anything about YOUR document: this endpoint does not send anything to KoSIT.\n\nThe JSON envelope carries `provenance`: the engine version and rule set that cleared this document. `?format=xml` returns the bytes alone and therefore carries none — an XML document has nowhere to put it, and this API does not write its own metadata into a customer's tax document. Read GET /v1/versions if you need it for a raw-XML pipeline.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"profile\": \"xrechnung-ubl\",\n  \"invoiceNumber\": \"INV-2026-0042\",\n  \"issueDate\": \"2026-08-09\",\n  \"dueDate\": \"2026-09-08\",\n  \"deliveryDate\": \"2026-07-31\",\n  \"currency\": \"EUR\",\n  \"buyerReference\": \"04011000-1234512345-06\",\n  \"seller\": {\n    \"name\": \"Nordwind Software GmbH\",\n    \"vatId\": \"DE123456789\",\n    \"address\": {\n      \"line1\": \"Hafenstraße 12\",\n      \"city\": \"Hamburg\",\n      \"postalCode\": \"20095\",\n      \"countryCode\": \"DE\"\n    },\n    \"electronicAddress\": {\n      \"schemeId\": \"9930\",\n      \"value\": \"DE123456789\"\n    },\n    \"contact\": {\n      \"name\": \"Buchhaltung\",\n      \"phone\": \"+49 40 1234567\",\n      \"email\": \"rechnungen@nordwind.example\"\n    }\n  },\n  \"buyer\": {\n    \"name\": \"Stadt Musterstadt\",\n    \"address\": {\n      \"line1\": \"Rathausplatz 1\",\n      \"city\": \"Musterstadt\",\n      \"postalCode\": \"80331\",\n      \"countryCode\": \"DE\"\n    },\n    \"electronicAddress\": {\n      \"schemeId\": \"0204\",\n      \"value\": \"04011000-1234512345-06\"\n    }\n  },\n  \"payment\": {\n    \"meansCode\": \"58\",\n    \"iban\": \"DE02120300000000202051\",\n    \"accountName\": \"Nordwind Software GmbH\"\n  },\n  \"paymentTerms\": \"Net 30 days\",\n  \"lines\": [\n    {\n      \"id\": \"1\",\n      \"description\": \"Implementation services, July 2026\",\n      \"quantity\": 12,\n      \"unitCode\": \"HUR\",\n      \"unitPrice\": 145,\n      \"vatCategory\": \"S\",\n      \"vatRate\": 19\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "body": "{\n  \"xml\": \"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?>\\n<ubl:Invoice xmlns:ubl=\\\"urn:oasis:names:specification:ubl:schema:xsd:Invoice-2\\\" xmlns:cac=\\\"urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2\\\" xmlns:cbc=\\\"urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2\\\">\\n  <cbc:CustomizationID>urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0</cbc:CustomizationID>\\n  <cbc:ProfileID>urn:fdc:peppol.eu:2017:poacc:billing:01:1.0</cbc:ProfileID>\\n  <cbc:ID>INV-2026-0042</cbc:ID>\\n  <cbc:IssueDate>2026-08-09</cbc:IssueDate>\\n  <cbc:DueDate>2026-09-08</cbc:DueDate>\\n  <cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>\\n  <cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>\\n  <cbc:BuyerReference>04011000-1234512345-06</cbc:BuyerReference>\\n  <cac:AccountingSupplierParty>\\n    <cac:Party>\\n      <cbc:EndpointID schemeID=\\\"9930\\\">DE123456789</cbc:EndpointID>\\n      <cac:PartyName>\\n        <cbc:Name>Nordwind Software GmbH</cbc:Name>\\n      </cac:PartyName>\\n      <cac:PostalAddress>\\n        <cbc:StreetName>Hafenstraße 12</cbc:StreetName>\\n        <cbc:CityName>Hamburg</cbc:CityName>\\n        <cbc:PostalZone>20095</cbc:PostalZone>\\n        <cac:Country>\\n          <cbc:IdentificationCode>DE</cbc:IdentificationCode>\\n        </cac:Country>\\n      </cac:PostalAddress>\\n      <cac:PartyTaxScheme>\\n        <cbc:CompanyID>DE123456789</cbc:CompanyID>\\n        <cac:TaxScheme>\\n          <cbc:ID>VAT</cbc:ID>\\n        </cac:TaxScheme>\\n      </cac:PartyTaxScheme>\\n      <cac:PartyLegalEntity>\\n        <cbc:RegistrationName>Nordwind Software GmbH</cbc:RegistrationName>\\n      </cac:PartyLegalEntity>\\n      <cac:Contact>\\n        <cbc:Name>Buchhaltung</cbc:Name>\\n        <cbc:Telephone>+49 40 1234567</cbc:Telephone>\\n        <cbc:ElectronicMail>rechnungen@nordwind.example</cbc:ElectronicMail>\\n      </cac:Contact>\\n    </cac:Party>\\n  </cac:AccountingSupplierParty>\\n  <cac:AccountingCustomerParty>\\n    <cac:Party>\\n      <cbc:EndpointID schemeID=\\\"0204\\\">04011000-1234512345-06</cbc:EndpointID>\\n      <cac:PartyName>\\n        <cbc:Name>Stadt Musterstadt</cbc:Name>\\n      </cac:PartyName>\\n      <cac:PostalAddress>\\n        <cbc:StreetName>Rathausplatz 1</cbc:StreetName>\\n        <cbc:CityName>Musterstadt</cbc:CityName>\\n        <cbc:PostalZone>80331</cbc:PostalZone>\\n        <cac:Country>\\n          <cbc:IdentificationCode>DE</cbc:IdentificationCode>\\n        </cac:Country>\\n      </cac:PostalAddress>\\n      <cac:PartyLegalEntity>\\n        <cbc:RegistrationName>Stadt Musterstadt</cbc:RegistrationName>\\n      </cac:PartyLegalEntity>\\n    </cac:Party>\\n  </cac:AccountingCustomerParty>\\n  <cac:Delivery>\\n    <cbc:ActualDeliveryDate>2026-07-31</cbc:ActualDeliveryDate>\\n  </cac:Delivery>\\n  <cac:PaymentMeans>\\n    <cbc:PaymentMeansCode>58</cbc:PaymentMeansCode>\\n    <cac:PayeeFinancialAccount>\\n      <cbc:ID>DE02120300000000202051</cbc:ID>\\n      <cbc:Name>Nordwind Software GmbH</cbc:Name>\\n    </cac:PayeeFinancialAccount>\\n  </cac:PaymentMeans>\\n  <cac:PaymentTerms>\\n    <cbc:Note>Net 30 days</cbc:Note>\\n  </cac:PaymentTerms>\\n  <cac:TaxTotal>\\n    <cbc:TaxAmount currencyID=\\\"EUR\\\">330.60</cbc:TaxAmount>\\n    <cac:TaxSubtotal>\\n      <cbc:TaxableAmount currencyID=\\\"EUR\\\">1740.00</cbc:TaxableAmount>\\n      <cbc:TaxAmount currencyID=\\\"EUR\\\">330.60</cbc:TaxAmount>\\n      <cac:TaxCategory>\\n        <cbc:ID>S</cbc:ID>\\n        <cbc:Percent>19.00</cbc:Percent>\\n        <cac:TaxScheme>\\n          <cbc:ID>VAT</cbc:ID>\\n        </cac:TaxScheme>\\n      </cac:TaxCategory>\\n    </cac:TaxSubtotal>\\n  </cac:TaxTotal>\\n  <cac:LegalMonetaryTotal>\\n    <cbc:LineExtensionAmount currencyID=\\\"EUR\\\">1740.00</cbc:LineExtensionAmount>\\n    <cbc:TaxExclusiveAmount currencyID=\\\"EUR\\\">1740.00</cbc:TaxExclusiveAmount>\\n    <cbc:TaxInclusiveAmount currencyID=\\\"EUR\\\">2070.60</cbc:TaxInclusiveAmount>\\n    <cbc:PayableAmount currencyID=\\\"EUR\\\">2070.60</cbc:PayableAmount>\\n  </cac:LegalMonetaryTotal>\\n  <cac:InvoiceLine>\\n    <cbc:ID>1</cbc:ID>\\n    <cbc:InvoicedQuantity unitCode=\\\"HUR\\\">12.0000</cbc:InvoicedQuantity>\\n    <cbc:LineExtensionAmount currencyID=\\\"EUR\\\">1740.00</cbc:LineExtensionAmount>\\n    <cac:Item>\\n      <cbc:Name>Implementation services, July 2026</cbc:Name>\\n      <cac:ClassifiedTaxCategory>\\n        <cbc:ID>S</cbc:ID>\\n        <cbc:Percent>19.00</cbc:Percent>\\n        <cac:TaxScheme>\\n          <cbc:ID>VAT</cbc:ID>\\n        </cac:TaxScheme>\\n      </cac:ClassifiedTaxCategory>\\n    </cac:Item>\\n    <cac:Price>\\n      <cbc:PriceAmount currencyID=\\\"EUR\\\">145.00</cbc:PriceAmount>\\n    </cac:Price>\\n  </cac:InvoiceLine>\\n</ubl:Invoice>\\n\",\n  \"profile\": \"xrechnung-ubl\",\n  \"warnings\": [],\n  \"information\": []\n}"
            },
            {
              "name": "422 — BR-DE-15 blocks the document.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  },
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/generate",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "generate"
                  ]
                },
                "description": "Validates first: a fatal rule failure returns 422 with the same teaching errors as /v1/validate, and does not consume a document. Supported profiles in this build: en16931, xrechnung-ubl, peppol-bis-3, xrechnung-cii, facturx-en16931.\n\nTHE PROFILE CHOOSES THE SYNTAX. xrechnung-cii and facturx-en16931 come back as UN/CEFACT CII; every other profile comes back as UBL 2.1. The JSON response says which in its `syntax` field.\n\n**CII output is XML, not a PDF.** Factur-X and ZUGFeRD files are CII XML inside a PDF/A-3 container. This endpoint returns the XML. It does not build the container, does not attach the XML as `factur-x.xml` and does not set `/AFRelationship`, so a `facturx-en16931` response is the payload and not a Factur-X document. Pass it to a Factur-X packaging library if you need the file.\n\n**Output verification is not uniform, and is never per-request.** The generator's `xrechnung-cii` fixture documents are run through the official KoSIT validator on release and accepted. The `facturx-en16931` fixtures are not: that profile's BT-24 is the core EN 16931 one, which matches no XRechnung scenario, so KoSIT answers \"no scenario matched\" instead of a verdict, and an absence of a verdict is not a pass. Neither case says anything about YOUR document: this endpoint does not send anything to KoSIT.\n\nThe JSON envelope carries `provenance`: the engine version and rule set that cleared this document. `?format=xml` returns the bytes alone and therefore carries none — an XML document has nowhere to put it, and this API does not write its own metadata into a customer's tax document. Read GET /v1/versions if you need it for a raw-XML pipeline.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"profile\": \"xrechnung-ubl\",\n  \"invoiceNumber\": \"INV-2026-0042\",\n  \"issueDate\": \"2026-08-09\",\n  \"dueDate\": \"2026-09-08\",\n  \"deliveryDate\": \"2026-07-31\",\n  \"currency\": \"EUR\",\n  \"buyerReference\": \"04011000-1234512345-06\",\n  \"seller\": {\n    \"name\": \"Nordwind Software GmbH\",\n    \"vatId\": \"DE123456789\",\n    \"address\": {\n      \"line1\": \"Hafenstraße 12\",\n      \"city\": \"Hamburg\",\n      \"postalCode\": \"20095\",\n      \"countryCode\": \"DE\"\n    },\n    \"electronicAddress\": {\n      \"schemeId\": \"9930\",\n      \"value\": \"DE123456789\"\n    },\n    \"contact\": {\n      \"name\": \"Buchhaltung\",\n      \"phone\": \"+49 40 1234567\",\n      \"email\": \"rechnungen@nordwind.example\"\n    }\n  },\n  \"buyer\": {\n    \"name\": \"Stadt Musterstadt\",\n    \"address\": {\n      \"line1\": \"Rathausplatz 1\",\n      \"city\": \"Musterstadt\",\n      \"postalCode\": \"80331\",\n      \"countryCode\": \"DE\"\n    },\n    \"electronicAddress\": {\n      \"schemeId\": \"0204\",\n      \"value\": \"04011000-1234512345-06\"\n    }\n  },\n  \"payment\": {\n    \"meansCode\": \"58\",\n    \"iban\": \"DE02120300000000202051\",\n    \"accountName\": \"Nordwind Software GmbH\"\n  },\n  \"paymentTerms\": \"Net 30 days\",\n  \"lines\": [\n    {\n      \"id\": \"1\",\n      \"description\": \"Implementation services, July 2026\",\n      \"quantity\": 12,\n      \"unitCode\": \"HUR\",\n      \"unitPrice\": 145,\n      \"vatCategory\": \"S\",\n      \"vatRate\": 19\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Unprocessable Content",
              "code": 422,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "body": "{\n  \"valid\": false,\n  \"profile\": \"xrechnung-ubl\",\n  \"errors\": [\n    {\n      \"rule\": \"BR-DE-15\",\n      \"field\": \"BT-10\",\n      \"severity\": \"fatal\",\n      \"message\": \"XRechnung requires a buyer reference (BT-10). For German public-sector buyers this is the Leitweg-ID; business buyers may supply any reference, but the field must be present.\",\n      \"fix\": \"Ask your client for their Leitweg-ID (public sector) or an order/customer reference, and set buyerReference.\",\n      \"example\": \"\\\"buyerReference\\\": \\\"04011000-1234512345-06\\\"\",\n      \"xpath\": \"/ubl:Invoice/cbc:BuyerReference\",\n      \"docsUrl\": \"https://attestwire.com/rules/BR-DE-15\"\n    }\n  ],\n  \"warnings\": [],\n  \"information\": [],\n  \"provenance\": {\n    \"engine\": \"@attestwire/en16931\",\n    \"engine_version\": \"0.9.0\",\n    \"ruleset\": \"en16931@0.9.0\",\n    \"profile\": \"xrechnung-ubl\",\n    \"kosit_conformance\": {\n      \"recorded\": \"2026-08-13\",\n      \"validator\": \"1.6.2\",\n      \"configuration\": \"3.0.2\",\n      \"configuration_published\": \"2026-01-31\"\n    }\n  }\n}"
            }
          ],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200 OK', () => pm.response.to.have.status(200));",
                  "const body = pm.response.json();",
                  "pm.test('an XML document came back', () => {",
                  "  pm.expect(body.xml).to.be.a('string');",
                  "  pm.expect(body.xml).to.include('<ubl:Invoice');",
                  "});",
                  "pm.test('the profile is echoed', () => pm.expect(body.profile).to.be.a('string'));"
                ]
              }
            }
          ]
        },
        {
          "name": "Generate XRechnung UBL (raw XML)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/generate?format=xml",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "generate"
              ],
              "query": [
                {
                  "key": "format",
                  "value": "xml",
                  "description": "Return the document itself, not a JSON envelope."
                }
              ]
            },
            "description": "Validates first: a fatal rule failure returns 422 with the same teaching errors as /v1/validate, and does not consume a document. Supported profiles in this build: en16931, xrechnung-ubl, peppol-bis-3, xrechnung-cii, facturx-en16931.\n\nTHE PROFILE CHOOSES THE SYNTAX. xrechnung-cii and facturx-en16931 come back as UN/CEFACT CII; every other profile comes back as UBL 2.1. The JSON response says which in its `syntax` field.\n\n**CII output is XML, not a PDF.** Factur-X and ZUGFeRD files are CII XML inside a PDF/A-3 container. This endpoint returns the XML. It does not build the container, does not attach the XML as `factur-x.xml` and does not set `/AFRelationship`, so a `facturx-en16931` response is the payload and not a Factur-X document. Pass it to a Factur-X packaging library if you need the file.\n\n**Output verification is not uniform, and is never per-request.** The generator's `xrechnung-cii` fixture documents are run through the official KoSIT validator on release and accepted. The `facturx-en16931` fixtures are not: that profile's BT-24 is the core EN 16931 one, which matches no XRechnung scenario, so KoSIT answers \"no scenario matched\" instead of a verdict, and an absence of a verdict is not a pass. Neither case says anything about YOUR document: this endpoint does not send anything to KoSIT.\n\nThe JSON envelope carries `provenance`: the engine version and rule set that cleared this document. `?format=xml` returns the bytes alone and therefore carries none — an XML document has nowhere to put it, and this API does not write its own metadata into a customer's tax document. Read GET /v1/versions if you need it for a raw-XML pipeline.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"profile\": \"xrechnung-ubl\",\n  \"invoiceNumber\": \"INV-2026-0042\",\n  \"issueDate\": \"2026-08-09\",\n  \"dueDate\": \"2026-09-08\",\n  \"deliveryDate\": \"2026-07-31\",\n  \"currency\": \"EUR\",\n  \"buyerReference\": \"04011000-1234512345-06\",\n  \"seller\": {\n    \"name\": \"Nordwind Software GmbH\",\n    \"vatId\": \"DE123456789\",\n    \"address\": {\n      \"line1\": \"Hafenstraße 12\",\n      \"city\": \"Hamburg\",\n      \"postalCode\": \"20095\",\n      \"countryCode\": \"DE\"\n    },\n    \"electronicAddress\": {\n      \"schemeId\": \"9930\",\n      \"value\": \"DE123456789\"\n    },\n    \"contact\": {\n      \"name\": \"Buchhaltung\",\n      \"phone\": \"+49 40 1234567\",\n      \"email\": \"rechnungen@nordwind.example\"\n    }\n  },\n  \"buyer\": {\n    \"name\": \"Stadt Musterstadt\",\n    \"address\": {\n      \"line1\": \"Rathausplatz 1\",\n      \"city\": \"Musterstadt\",\n      \"postalCode\": \"80331\",\n      \"countryCode\": \"DE\"\n    },\n    \"electronicAddress\": {\n      \"schemeId\": \"0204\",\n      \"value\": \"04011000-1234512345-06\"\n    }\n  },\n  \"payment\": {\n    \"meansCode\": \"58\",\n    \"iban\": \"DE02120300000000202051\",\n    \"accountName\": \"Nordwind Software GmbH\"\n  },\n  \"paymentTerms\": \"Net 30 days\",\n  \"lines\": [\n    {\n      \"id\": \"1\",\n      \"description\": \"Implementation services, July 2026\",\n      \"quantity\": 12,\n      \"unitCode\": \"HUR\",\n      \"unitPrice\": 145,\n      \"vatCategory\": \"S\",\n      \"vatRate\": 19\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "200 — the document itself",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  },
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/generate?format=xml",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "generate"
                  ],
                  "query": [
                    {
                      "key": "format",
                      "value": "xml",
                      "description": "Return the document itself, not a JSON envelope."
                    }
                  ]
                },
                "description": "Validates first: a fatal rule failure returns 422 with the same teaching errors as /v1/validate, and does not consume a document. Supported profiles in this build: en16931, xrechnung-ubl, peppol-bis-3, xrechnung-cii, facturx-en16931.\n\nTHE PROFILE CHOOSES THE SYNTAX. xrechnung-cii and facturx-en16931 come back as UN/CEFACT CII; every other profile comes back as UBL 2.1. The JSON response says which in its `syntax` field.\n\n**CII output is XML, not a PDF.** Factur-X and ZUGFeRD files are CII XML inside a PDF/A-3 container. This endpoint returns the XML. It does not build the container, does not attach the XML as `factur-x.xml` and does not set `/AFRelationship`, so a `facturx-en16931` response is the payload and not a Factur-X document. Pass it to a Factur-X packaging library if you need the file.\n\n**Output verification is not uniform, and is never per-request.** The generator's `xrechnung-cii` fixture documents are run through the official KoSIT validator on release and accepted. The `facturx-en16931` fixtures are not: that profile's BT-24 is the core EN 16931 one, which matches no XRechnung scenario, so KoSIT answers \"no scenario matched\" instead of a verdict, and an absence of a verdict is not a pass. Neither case says anything about YOUR document: this endpoint does not send anything to KoSIT.\n\nThe JSON envelope carries `provenance`: the engine version and rule set that cleared this document. `?format=xml` returns the bytes alone and therefore carries none — an XML document has nowhere to put it, and this API does not write its own metadata into a customer's tax document. Read GET /v1/versions if you need it for a raw-XML pipeline.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"profile\": \"xrechnung-ubl\",\n  \"invoiceNumber\": \"INV-2026-0042\",\n  \"issueDate\": \"2026-08-09\",\n  \"dueDate\": \"2026-09-08\",\n  \"deliveryDate\": \"2026-07-31\",\n  \"currency\": \"EUR\",\n  \"buyerReference\": \"04011000-1234512345-06\",\n  \"seller\": {\n    \"name\": \"Nordwind Software GmbH\",\n    \"vatId\": \"DE123456789\",\n    \"address\": {\n      \"line1\": \"Hafenstraße 12\",\n      \"city\": \"Hamburg\",\n      \"postalCode\": \"20095\",\n      \"countryCode\": \"DE\"\n    },\n    \"electronicAddress\": {\n      \"schemeId\": \"9930\",\n      \"value\": \"DE123456789\"\n    },\n    \"contact\": {\n      \"name\": \"Buchhaltung\",\n      \"phone\": \"+49 40 1234567\",\n      \"email\": \"rechnungen@nordwind.example\"\n    }\n  },\n  \"buyer\": {\n    \"name\": \"Stadt Musterstadt\",\n    \"address\": {\n      \"line1\": \"Rathausplatz 1\",\n      \"city\": \"Musterstadt\",\n      \"postalCode\": \"80331\",\n      \"countryCode\": \"DE\"\n    },\n    \"electronicAddress\": {\n      \"schemeId\": \"0204\",\n      \"value\": \"04011000-1234512345-06\"\n    }\n  },\n  \"payment\": {\n    \"meansCode\": \"58\",\n    \"iban\": \"DE02120300000000202051\",\n    \"accountName\": \"Nordwind Software GmbH\"\n  },\n  \"paymentTerms\": \"Net 30 days\",\n  \"lines\": [\n    {\n      \"id\": \"1\",\n      \"description\": \"Implementation services, July 2026\",\n      \"quantity\": 12,\n      \"unitCode\": \"HUR\",\n      \"unitPrice\": 145,\n      \"vatCategory\": \"S\",\n      \"vatRate\": 19\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "xml",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/xml; charset=utf-8"
                }
              ],
              "body": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ubl:Invoice xmlns:ubl=\"urn:oasis:names:specification:ubl:schema:xsd:Invoice-2\" xmlns:cac=\"urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2\" xmlns:cbc=\"urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2\">\n  <cbc:CustomizationID>urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0</cbc:CustomizationID>\n  <cbc:ProfileID>urn:fdc:peppol.eu:2017:poacc:billing:01:1.0</cbc:ProfileID>\n  <cbc:ID>INV-2026-0042</cbc:ID>\n  <cbc:IssueDate>2026-08-09</cbc:IssueDate>\n  <cbc:DueDate>2026-09-08</cbc:DueDate>\n  <cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>\n  <cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>\n  <cbc:BuyerReference>04011000-1234512345-06</cbc:BuyerReference>\n  <cac:AccountingSupplierParty>\n    <cac:Party>\n      <cbc:EndpointID schemeID=\"9930\">DE123456789</cbc:EndpointID>\n      <cac:PartyName>\n        <cbc:Name>Nordwind Software GmbH</cbc:Name>\n      </cac:PartyName>\n      <cac:PostalAddress>\n        <cbc:StreetName>Hafenstraße 12</cbc:StreetName>\n        <cbc:CityName>Hamburg</cbc:CityName>\n        <cbc:PostalZone>20095</cbc:PostalZone>\n        <cac:Country>\n          <cbc:IdentificationCode>DE</cbc:IdentificationCode>\n        </cac:Country>\n      </cac:PostalAddress>\n      <cac:PartyTaxScheme>\n        <cbc:CompanyID>DE123456789</cbc:CompanyID>\n        <cac:TaxScheme>\n          <cbc:ID>VAT</cbc:ID>\n        </cac:TaxScheme>\n      </cac:PartyTaxScheme>\n      <cac:PartyLegalEntity>\n        <cbc:RegistrationName>Nordwind Software GmbH</cbc:RegistrationName>\n      </cac:PartyLegalEntity>\n      <cac:Contact>\n        <cbc:Name>Buchhaltung</cbc:Name>\n        <cbc:Telephone>+49 40 1234567</cbc:Telephone>\n        <cbc:ElectronicMail>rechnungen@nordwind.example</cbc:ElectronicMail>\n      </cac:Contact>\n    </cac:Party>\n  </cac:AccountingSupplierParty>\n  <cac:AccountingCustomerParty>\n    <cac:Party>\n      <cbc:EndpointID schemeID=\"0204\">04011000-1234512345-06</cbc:EndpointID>\n      <cac:PartyName>\n        <cbc:Name>Stadt Musterstadt</cbc:Name>\n      </cac:PartyName>\n      <cac:PostalAddress>\n        <cbc:StreetName>Rathausplatz 1</cbc:StreetName>\n        <cbc:CityName>Musterstadt</cbc:CityName>\n        <cbc:PostalZone>80331</cbc:PostalZone>\n        <cac:Country>\n          <cbc:IdentificationCode>DE</cbc:IdentificationCode>\n        </cac:Country>\n      </cac:PostalAddress>\n      <cac:PartyLegalEntity>\n        <cbc:RegistrationName>Stadt Musterstadt</cbc:RegistrationName>\n      </cac:PartyLegalEntity>\n    </cac:Party>\n  </cac:AccountingCustomerParty>\n  <cac:Delivery>\n    <cbc:ActualDeliveryDate>2026-07-31</cbc:ActualDeliveryDate>\n  </cac:Delivery>\n  <cac:PaymentMeans>\n    <cbc:PaymentMeansCode>58</cbc:PaymentMeansCode>\n    <cac:PayeeFinancialAccount>\n      <cbc:ID>DE02120300000000202051</cbc:ID>\n      <cbc:Name>Nordwind Software GmbH</cbc:Name>\n    </cac:PayeeFinancialAccount>\n  </cac:PaymentMeans>\n  <cac:PaymentTerms>\n    <cbc:Note>Net 30 days</cbc:Note>\n  </cac:PaymentTerms>\n  <cac:TaxTotal>\n    <cbc:TaxAmount currencyID=\"EUR\">330.60</cbc:TaxAmount>\n    <cac:TaxSubtotal>\n      <cbc:TaxableAmount currencyID=\"EUR\">1740.00</cbc:TaxableAmount>\n      <cbc:TaxAmount currencyID=\"EUR\">330.60</cbc:TaxAmount>\n      <cac:TaxCategory>\n        <cbc:ID>S</cbc:ID>\n        <cbc:Percent>19.00</cbc:Percent>\n        <cac:TaxScheme>\n          <cbc:ID>VAT</cbc:ID>\n        </cac:TaxScheme>\n      </cac:TaxCategory>\n    </cac:TaxSubtotal>\n  </cac:TaxTotal>\n  <cac:LegalMonetaryTotal>\n    <cbc:LineExtensionAmount currencyID=\"EUR\">1740.00</cbc:LineExtensionAmount>\n    <cbc:TaxExclusiveAmount currencyID=\"EUR\">1740.00</cbc:TaxExclusiveAmount>\n    <cbc:TaxInclusiveAmount currencyID=\"EUR\">2070.60</cbc:TaxInclusiveAmount>\n    <cbc:PayableAmount currencyID=\"EUR\">2070.60</cbc:PayableAmount>\n  </cac:LegalMonetaryTotal>\n  <cac:InvoiceLine>\n    <cbc:ID>1</cbc:ID>\n    <cbc:InvoicedQuantity unitCode=\"HUR\">12.0000</cbc:InvoicedQuantity>\n    <cbc:LineExtensionAmount currencyID=\"EUR\">1740.00</cbc:LineExtensionAmount>\n    <cac:Item>\n      <cbc:Name>Implementation services, July 2026</cbc:Name>\n      <cac:ClassifiedTaxCategory>\n        <cbc:ID>S</cbc:ID>\n        <cbc:Percent>19.00</cbc:Percent>\n        <cac:TaxScheme>\n          <cbc:ID>VAT</cbc:ID>\n        </cac:TaxScheme>\n      </cac:ClassifiedTaxCategory>\n    </cac:Item>\n    <cac:Price>\n      <cbc:PriceAmount currencyID=\"EUR\">145.00</cbc:PriceAmount>\n    </cac:Price>\n  </cac:InvoiceLine>\n</ubl:Invoice>\n"
            }
          ],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200 OK', () => pm.response.to.have.status(200));",
                  "pm.test('the body is XML', () =>",
                  "  pm.expect(pm.response.headers.get('Content-Type')).to.include('application/xml'));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "4 · Your key",
      "description": "Free calls. Neither of these consumes a document.",
      "item": [
        {
          "name": "Check usage this month",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/usage",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "usage"
              ]
            },
            "description": "Free, and does not consume a document."
          },
          "response": [
            {
              "name": "200 — A free key. No subscription, so no portal endpoint.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/usage",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "usage"
                  ]
                },
                "description": "Free, and does not consume a document."
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "body": "{\n  \"tier\": \"free\",\n  \"period\": \"2026-08\",\n  \"used\": 12,\n  \"limit\": 100,\n  \"remaining\": 88,\n  \"resets_at\": \"2026-09-01T00:00:00.000Z\",\n  \"past_due\": false\n}"
            }
          ],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200 OK', () => pm.response.to.have.status(200));",
                  "const body = pm.response.json();",
                  "pm.test('the counter is coherent', () =>",
                  "  pm.expect(body.used + body.remaining).to.be.at.most(body.limit));"
                ]
              }
            }
          ]
        },
        {
          "name": "Rotate the key (DESTRUCTIVE — skipped unless you opt in)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/keys/rotate",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "keys",
                "rotate"
              ]
            },
            "description": "REPLACES the key you are holding. The old one starts answering 410 the moment this succeeds, and the new plaintext is shown once. Your tier and this month's usage follow the new key — rotation does not reset your quota. Run it only when you mean to; three per key per UTC day.\n\nThe test script updates `apiKey` for you, so the rest of the collection keeps working — but write the new key down as well."
          },
          "response": [
            {
              "name": "200 — The old key is dead from this response onward.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/keys/rotate",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "keys",
                    "rotate"
                  ]
                },
                "description": "REPLACES the key you are holding. The old one starts answering 410 the moment this succeeds, and the new plaintext is shown once. Your tier and this month's usage follow the new key — rotation does not reset your quota. Run it only when you mean to; three per key per UTC day.\n\nThe test script updates `apiKey` for you, so the rest of the collection keeps working — but write the new key down as well."
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "body": "{\n  \"key\": \"aw_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\",\n  \"tier\": \"starter\",\n  \"monthly_limit\": 2500,\n  \"period\": \"2026-08\",\n  \"used\": 418,\n  \"remaining\": 2082,\n  \"resets_at\": \"2026-09-01T00:00:00.000Z\",\n  \"created\": \"2026-01-15T09:12:04.000Z\",\n  \"rotated_at\": \"2026-08-11T10:04:31.000Z\",\n  \"rotations_remaining_today\": 2,\n  \"warning\": \"Store this key now. It is shown once and cannot be recovered. The previous key stopped working when this response was generated and now answers 410 key_rotated.\"\n}"
            },
            {
              "name": "409 — The replacement exists and this call is not the one that sees it",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/keys/rotate",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "keys",
                    "rotate"
                  ]
                },
                "description": "REPLACES the key you are holding. The old one starts answering 410 the moment this succeeds, and the new plaintext is shown once. Your tier and this month's usage follow the new key — rotation does not reset your quota. Run it only when you mean to; three per key per UTC day.\n\nThe test script updates `apiKey` for you, so the rest of the collection keeps working — but write the new key down as well."
              },
              "status": "Conflict",
              "code": 409,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "body": "{\n  \"error\": \"rotation_already_done\",\n  \"message\": \"This key has already been rotated, so it cannot be rotated again. The replacement was returned exactly once, in the response to the request that performed the rotation — if two rotations were sent at the same time, the other response has your new key. If it is lost, email hello@attestwire.com from the address on your Paddle billing receipt.\",\n  \"docs\": \"https://api.attestwire.com/docs#rotation\"\n}"
            },
            {
              "name": "429 — The daily ceiling, which a rotation does not reset",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/keys/rotate",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "keys",
                    "rotate"
                  ]
                },
                "description": "REPLACES the key you are holding. The old one starts answering 410 the moment this succeeds, and the new plaintext is shown once. Your tier and this month's usage follow the new key — rotation does not reset your quota. Run it only when you mean to; three per key per UTC day.\n\nThe test script updates `apiKey` for you, so the rest of the collection keeps working — but write the new key down as well."
              },
              "status": "Too Many Requests",
              "code": 429,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "body": "{\n  \"error\": \"rotation_rate_limited\",\n  \"message\": \"This key has already been rotated 3 times today, which is the daily limit. The limit follows the key through a rotation, so rotating again does not reset it. It clears at 00:00 UTC. If you are locked out of your own key, email hello@attestwire.com from the address on your Paddle billing receipt.\",\n  \"docs\": \"https://api.attestwire.com/docs#rotation\"\n}"
            },
            {
              "name": "410 — The key was replaced by POST /v1/keys/rotate",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/keys/rotate",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "keys",
                    "rotate"
                  ]
                },
                "description": "REPLACES the key you are holding. The old one starts answering 410 the moment this succeeds, and the new plaintext is shown once. Your tier and this month's usage follow the new key — rotation does not reset your quota. Run it only when you mean to; three per key per UTC day.\n\nThe test script updates `apiKey` for you, so the rest of the collection keeps working — but write the new key down as well."
              },
              "status": "Gone",
              "code": 410,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "body": "{\n  \"error\": \"key_rotated\",\n  \"message\": \"This API key was rotated on 2026-08-01 and no longer works. Use the replacement key returned by that rotation call — it was shown once and we cannot show it again. If you did not rotate it, or the replacement is lost, email hello@attestwire.com from the address on your Paddle billing receipt.\",\n  \"docs\": \"https://api.attestwire.com/docs#rotation\"\n}"
            }
          ],
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Runner fires every request by default; this one kills the key the",
                  "// rest of the collection is using. Send it individually, or set the",
                  "// collection variable `allowRotate` to 'true' to include it in a run.",
                  "if (String(pm.collectionVariables.get('allowRotate')) !== 'true') {",
                  "  console.log('Skipping rotation: set the collection variable allowRotate=true to run it.');",
                  "  if (typeof pm.execution !== 'undefined' && pm.execution.skipRequest) {",
                  "    pm.execution.skipRequest();",
                  "  }",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Postman resolves the NARROWEST scope first: environment beats collection.",
                  "// The shipped environment defines only `baseUrl`, so nothing here is shadowed —",
                  "// but if you have added `apiKey` to an environment of your own, writing only the",
                  "// collection variable would be silently overridden by it. Write both.",
                  "const storeKey = (value) => {",
                  "  if (pm.environment.has('apiKey')) pm.environment.set('apiKey', value);",
                  "  pm.collectionVariables.set('apiKey', value);",
                  "};",
                  "",
                  "if (pm.response.code === 200) {",
                  "  const body = pm.response.json();",
                  "  storeKey(body.key);",
                  "  console.log('Key rotated. The previous key is now dead. New key stored.');",
                  "  pm.test('usage carried over', () => pm.expect(body.used).to.be.a('number'));",
                  "} else {",
                  "  console.log('Not rotated: ' + pm.response.code + ' ' + pm.response.text());",
                  "}"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "5 · Billing",
      "description": "Paddle-hosted checkout, and the customer portal. Nothing is charged until a human completes the checkout on Paddle's page.",
      "item": [
        {
          "name": "Start a checkout session",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/billing/checkout",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "billing",
                "checkout"
              ]
            },
            "description": "Start a hosted checkout to upgrade a key",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"key\": \"{{apiKey}}\",\n  \"plan\": \"starter\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "200 — Send the customer to `url`; nothing is charged until they pay.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  },
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/billing/checkout",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "billing",
                    "checkout"
                  ]
                },
                "description": "Start a hosted checkout to upgrade a key",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"key\": \"{{apiKey}}\",\n  \"plan\": \"starter\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "body": "{\n  \"url\": \"https://attestwire.com/pay?_ptxn=txn_xxxxxxxxxxxxxxxxxxxxxxxxxx\",\n  \"transaction_id\": \"txn_xxxxxxxxxxxxxxxxxxxxxxxxxx\",\n  \"plan\": \"starter\",\n  \"period\": \"monthly\",\n  \"portal_endpoint\": \"/v1/billing/portal\"\n}"
            },
            {
              "name": "409 — Already paying — change the plan, do not buy a second one",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  },
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/billing/checkout",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "billing",
                    "checkout"
                  ]
                },
                "description": "Start a hosted checkout to upgrade a key",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"key\": \"{{apiKey}}\",\n  \"plan\": \"starter\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Conflict",
              "code": 409,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "body": "{\n  \"error\": \"already_subscribed\",\n  \"message\": \"This key is already on the starter plan. Starting a second checkout would create a second subscription and bill you for both. To switch plans or cancel, open the billing portal: POST /v1/billing/portal with this key returns a one-time link, and every Paddle receipt email carries one too. Plan changes there are prorated, and your key follows the new plan automatically. Stuck? hello@attestwire.com.\",\n  \"docs\": \"https://api.attestwire.com/docs#pricing\"\n}"
            },
            {
              "name": "503 — No billing credentials on this deployment",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  },
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/billing/checkout",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "billing",
                    "checkout"
                  ]
                },
                "description": "Start a hosted checkout to upgrade a key",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"key\": \"{{apiKey}}\",\n  \"plan\": \"starter\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Service Unavailable",
              "code": 503,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "body": "{\n  \"error\": \"billing_not_enabled\",\n  \"message\": \"Card payment is not switched on yet for this deployment. Email hello@attestwire.com and we will upgrade your key by hand.\",\n  \"docs\": \"https://api.attestwire.com/docs#pricing\"\n}"
            }
          ],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 200) {",
                  "  pm.test('a checkout URL came back', () =>",
                  "    pm.expect(pm.response.json().url).to.be.a('string').and.match(/^https:/));",
                  "} else {",
                  "  console.log('Checkout not started: ' + pm.response.code);",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Open the billing portal",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/billing/portal",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "billing",
                "portal"
              ]
            },
            "description": "Returns a ONE-TIME URL to the Paddle customer portal for the calling key: change plan, update the card, download every invoice, cancel.\n\nTREAT THE URL AS A CREDENTIAL. Whoever opens it is treated as the account holder — there is no second factor behind it. Do not cache it, do not log it, do not put it in a shared inbox or a chat channel, and do not hand the same one to two people. It expires; request a fresh one instead of storing it. (Paddle documents that portal sessions expire but publishes no duration, so this API does not invent one — there is deliberately no `expires_at` field rather than a guessed one.)\n\nGET and POST are identical. Consumes no documents; it is subject to the same per-second pace limit as everything else on the key.\n\nA key with no subscription behind it — a free key, or a tier granted by hand — gets 409 `no_subscription`. Every receipt email Paddle sends also carries a portal link, which is the path for a customer who cannot reach this API."
          },
          "response": [
            {
              "name": "200 — Send the customer here, once.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/billing/portal",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "billing",
                    "portal"
                  ]
                },
                "description": "Returns a ONE-TIME URL to the Paddle customer portal for the calling key: change plan, update the card, download every invoice, cancel.\n\nTREAT THE URL AS A CREDENTIAL. Whoever opens it is treated as the account holder — there is no second factor behind it. Do not cache it, do not log it, do not put it in a shared inbox or a chat channel, and do not hand the same one to two people. It expires; request a fresh one instead of storing it. (Paddle documents that portal sessions expire but publishes no duration, so this API does not invent one — there is deliberately no `expires_at` field rather than a guessed one.)\n\nGET and POST are identical. Consumes no documents; it is subject to the same per-second pace limit as everything else on the key.\n\nA key with no subscription behind it — a free key, or a tier granted by hand — gets 409 `no_subscription`. Every receipt email Paddle sends also carries a portal link, which is the path for a customer who cannot reach this API."
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "body": "{\n  \"url\": \"https://customer-portal.paddle.com/cpl_xxxxxxxxxxxxxxxxxxxxxxxxxx\",\n  \"note\": \"One-time link to the Paddle customer portal, where the card, the plan and the cancellation live. It expires — request a new one rather than storing it, and do not email it on: whoever opens it is treated as the account holder.\"\n}"
            },
            {
              "name": "409 — A free key, or a tier granted by hand",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/billing/portal",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "billing",
                    "portal"
                  ]
                },
                "description": "Returns a ONE-TIME URL to the Paddle customer portal for the calling key: change plan, update the card, download every invoice, cancel.\n\nTREAT THE URL AS A CREDENTIAL. Whoever opens it is treated as the account holder — there is no second factor behind it. Do not cache it, do not log it, do not put it in a shared inbox or a chat channel, and do not hand the same one to two people. It expires; request a fresh one instead of storing it. (Paddle documents that portal sessions expire but publishes no duration, so this API does not invent one — there is deliberately no `expires_at` field rather than a guessed one.)\n\nGET and POST are identical. Consumes no documents; it is subject to the same per-second pace limit as everything else on the key.\n\nA key with no subscription behind it — a free key, or a tier granted by hand — gets 409 `no_subscription`. Every receipt email Paddle sends also carries a portal link, which is the path for a customer who cannot reach this API."
              },
              "status": "Conflict",
              "code": 409,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "body": "{\n  \"error\": \"no_subscription\",\n  \"message\": \"This key has no subscription behind it, so there is no billing portal to open. Free keys have nothing to manage; if you believe you are a paying customer, your key may have been upgraded by hand — email hello@attestwire.com.\",\n  \"docs\": \"https://api.attestwire.com/docs#billing\"\n}"
            }
          ],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 200) {",
                  "  pm.test('a portal link came back', () =>",
                  "    pm.expect(pm.response.json().url).to.be.a('string'));",
                  "  console.log('Portal link received (not logged: it is a one-time credential).');",
                  "} else {",
                  "  console.log('No portal: ' + pm.response.code);",
                  "}"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "6 · Meta",
      "description": "Unauthenticated. Safe to poll.",
      "item": [
        {
          "name": "Health and build capabilities",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/health",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "health"
              ]
            },
            "description": "Liveness and build capabilities",
            "auth": {
              "type": "noauth"
            }
          },
          "response": [
            {
              "name": "200 — This deployment, generating and billing live.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/health",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "health"
                  ]
                },
                "description": "Liveness and build capabilities",
                "auth": {
                  "type": "noauth"
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "body": "{\n  \"ok\": true,\n  \"runtime\": \"cloudflare-workers\",\n  \"generation\": true,\n  \"billing\": true,\n  \"billing_configured\": true,\n  \"billing_reachable\": true,\n  \"engine_version\": \"0.9.0\",\n  \"deploy_tag\": \"cce4396\",\n  \"metering\": \"durable-object-sqlite\",\n  \"period\": \"2026-08\"\n}"
            }
          ],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200 OK', () => pm.response.to.have.status(200));",
                  "const body = pm.response.json();",
                  "pm.test('the service reports itself up', () => pm.expect(body.ok).to.eql(true));"
                ]
              }
            }
          ]
        }
      ]
    }
  ]
}