REST API · v1

Facturen maken via de API

Laat je software of AI-assistent klanten opzoeken en conceptfacturen klaarzetten. Je bedrijfsgegevens en berekeningen komen uit factuurmaken; jij bekijkt het resultaat in je dashboard.

Voordat je begint

API-toegang is beschikbaar met een actief pakket met API-toegang (Pro, Business) of toegekende gratis toegang. Vul eerst je bedrijfsgegevens en bankrekening in.

Maak een aparte sleutel voor je koppeling. Voor dit voorbeeld heb je deze rechten nodig. Voeg clients:write alleen toe als de koppeling ook nieuwe klanten moet opslaan.

clients:readinvoices:readinvoices:write

De voorbeelden lezen de sleutel uit de omgevingsvariabele FM_API_KEY. Bewaar de sleutel in de geheime instellingen van je koppeling; zet hem niet in een prompt, URL of broncode.

Je koppeling moet HTTP-aanroepen met een Bearer-token kunnen uitvoeren. De documentatie is openbaar; klantgegevens blijven afgeschermd. MCP en inloggen via OAuth zijn nog niet beschikbaar.

Van klant naar conceptfactuur

  1. 1Zoek de klant

    Zoek op naam of e-mailadres en gebruik het teruggegeven id. Zijn er meerdere matches, laat de gebruiker kiezen. Geen match? Maak de klant aan via POST /api/v1/clients of in het dashboard.

    curl --fail-with-body --get 'https://factuurmaken.nl/api/v1/clients' \
      --header "Authorization: Bearer $FM_API_KEY" \
      --data-urlencode 'q=Acme'
  2. 2Maak een concept

    Vervang REPLACE_WITH_CLIENT_ID door het gevonden id. Dit voorbeeld factureert 8 uur à € 95,00 exclusief btw. Bedragen zijn gehele eurocenten. De btw-behandeling komt van de klant; de server berekent de totalen. Deze aanroep plant geen verzending.

    curl --fail-with-body 'https://factuurmaken.nl/api/v1/invoices' \
      --header "Authorization: Bearer $FM_API_KEY" \
      --header 'Content-Type: application/json' \
      --data '{
      "clientId": "REPLACE_WITH_CLIENT_ID",
      "onOpenConcept": "create",
      "lines": [
        {
          "description": "Advies",
          "quantity": 8,
          "unit": "uur",
          "unitPriceCents": 9500,
          "vatRate": 21
        }
      ]
    }'
    Voorbeeldantwoord · HTTP 201

    Voorbeeld met standaard-btw: € 760,00 exclusief btw, € 159,60 btw en € 919,60 totaal. IDs, datums en betaaltermijn verschillen per aanroep.

    {
      "invoice": {
        "id": "example_invoice_id",
        "number": null,
        "status": "draft",
        "invoiceType": "invoice",
        "clientId": "example_client_id",
        "recipient": {
          "name": "Acme B.V.",
          "email": "administratie@example.com"
        },
        "issueDate": "2026-09-26",
        "dueDate": "2026-10-26",
        "paidAt": null,
        "scheduledSendAt": null,
        "currency": "EUR",
        "vatMode": "standard",
        "notes": null,
        "subtotalCents": 76000,
        "vatCents": 15960,
        "totalCents": 91960,
        "createdAt": "2026-09-26T10:00:00.000Z",
        "updatedAt": "2026-09-26T10:00:00.000Z",
        "lines": [
          {
            "description": "Advies",
            "quantity": 8,
            "unit": "uur",
            "unitPriceCents": 9500,
            "vatRate": 21,
            "id": "example_line_1",
            "position": 0,
            "details": null,
            "discountPct": 0,
            "periodStart": null,
            "periodEnd": null
          }
        ]
      },
      "appended": false,
      "sendPlanned": false
    }
  3. 3Lees terug en bekijk het resultaat

    Vervang REPLACE_WITH_INVOICE_ID door invoice.id uit het antwoord. Toon de teruggegeven totalen en open /app/invoices/INVOICE_ID in het dashboard, met de standaardadministratie geselecteerd. Daar kun je controleren, aanpassen, definitief maken en downloaden of versturen.

    curl --fail-with-body 'https://factuurmaken.nl/api/v1/invoices/REPLACE_WITH_INVOICE_ID' \
      --header "Authorization: Bearer $FM_API_KEY"

Gedrag om rekening mee te houden

  • Elke sleutel werkt in de standaardadministratie van het account, onafhankelijk van de administratie die je in de app bekijkt. De API heeft geen businessId-parameter.
  • Aanmaken en regels toevoegen worden niet ontdubbeld; Idempotency-Key wordt niet ondersteund. Controleer na een time-out eerst of de klant of factuur al bestaat voordat je opnieuw schrijft.
  • Gebruik onOpenConcept: create voor een nieuw concept. Met append kun je regels toevoegen aan een concept dat al gepland staat om te versturen. Datums en notities uit de nieuwe aanvraag worden dan genegeerd.
  • Versturen is een aparte handeling met invoices:send. Een geslaagde aanroep betekent dat verzending gepland is, niet dat de e-mail bezorgd is. Een lege body plant standaard voor morgen.
  • Een concept kan nog gegevens missen. Definitief maken, wijzigen en PDF/UBL downloaden doe je in de app; deze handelingen hebben nog geen publieke API-endpoints.

API-referentie

De technische referentie is in het Engels en gebruikt dezelfde bron als OpenAPI. Vouw een aanroep of schema open voor parameters, velden en foutcodes.

get

/api/v1/clients

Find clients

Clients in the account default administration, newest first. Search name/email with q. No pagination. Ask the user when multiple clients match.

Required permission: clients:read

Parameters
[
  {
    "name": "q",
    "in": "query",
    "required": false,
    "description": "Case-insensitive name or email search.",
    "schema": {
      "type": "string"
    }
  }
]
Responses and errors
{
  "200": {
    "description": "Matching clients.",
    "content": {
      "application/json": {
        "schema": {
          "type": "object",
          "properties": {
            "clients": {
              "type": "array",
              "items": {
                "$ref": "#/components/schemas/Client"
              }
            }
          },
          "required": [
            "clients"
          ]
        }
      }
    }
  },
  "401": {
    "description": "invalid_api_key: missing, malformed, unknown or revoked Bearer key.",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Error"
        }
      }
    }
  },
  "403": {
    "description": "subscription_inactive, plan_not_allowed or insufficient_scope.",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Error"
        }
      }
    }
  }
}
post

/api/v1/clients

Create a client

Creates a saved client. Omit number to assign the next client number. Search first to avoid duplicates; name/email are not unique. Retries are not deduplicated. Individual clients discard business-only fields.

Required permission: clients:write

JSON request · required
{
  "required": true,
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/ClientCreate"
      },
      "example": {
        "name": "Acme B.V.",
        "email": "administratie@example.com",
        "type": "business",
        "vatMode": "standard"
      }
    }
  }
}
Responses and errors
{
  "201": {
    "description": "Created client; null if removed before read-back.",
    "content": {
      "application/json": {
        "schema": {
          "type": "object",
          "properties": {
            "client": {
              "anyOf": [
                {
                  "$ref": "#/components/schemas/Client"
                },
                {
                  "type": "null"
                }
              ]
            }
          },
          "required": [
            "client"
          ]
        }
      }
    }
  },
  "400": {
    "description": "invalid_json or validation; issues groups messages by top-level field.",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Error"
        }
      }
    }
  },
  "401": {
    "description": "invalid_api_key: missing, malformed, unknown or revoked Bearer key.",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Error"
        }
      }
    }
  },
  "403": {
    "description": "subscription_inactive, plan_not_allowed or insufficient_scope.",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Error"
        }
      }
    }
  },
  "409": {
    "description": "duplicate_number: client number already exists or automatic numbering could not find a free number.",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Error"
        }
      }
    }
  }
}
get

/api/v1/clients/{id}

Read a client

Reads a client in the account default administration.

Required permission: clients:read

Parameters
[
  {
    "name": "id",
    "in": "path",
    "required": true,
    "description": "ID returned by this API.",
    "schema": {
      "type": "string",
      "minLength": 1
    }
  }
]
Responses and errors
{
  "200": {
    "description": "Client.",
    "content": {
      "application/json": {
        "schema": {
          "type": "object",
          "properties": {
            "client": {
              "$ref": "#/components/schemas/Client"
            }
          },
          "required": [
            "client"
          ]
        }
      }
    }
  },
  "401": {
    "description": "invalid_api_key: missing, malformed, unknown or revoked Bearer key.",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Error"
        }
      }
    }
  },
  "403": {
    "description": "subscription_inactive, plan_not_allowed or insufficient_scope.",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Error"
        }
      }
    }
  },
  "404": {
    "description": "not_found: unknown client or outside this administration.",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Error"
        }
      }
    }
  }
}
get

/api/v1/invoices

List invoices

Invoices in the account default administration, newest first. Includes drafts and existing credit invoices.

Required permission: invoices:read

Parameters
[
  {
    "name": "clientId",
    "in": "query",
    "required": false,
    "description": "Only invoices linked to this client.",
    "schema": {
      "type": "string"
    }
  },
  {
    "name": "status",
    "in": "query",
    "required": false,
    "description": "Derived display status; overdue can include an unsent draft.",
    "schema": {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "string",
      "enum": [
        "draft",
        "final",
        "sent",
        "overdue",
        "paid"
      ],
      "description": "Derived display status. An unpaid draft with a past due date can be overdue."
    }
  },
  {
    "name": "limit",
    "in": "query",
    "required": false,
    "description": "Page size.",
    "schema": {
      "type": "integer",
      "minimum": 1,
      "maximum": 100,
      "default": 50
    }
  },
  {
    "name": "offset",
    "in": "query",
    "required": false,
    "description": "Number of rows to skip.",
    "schema": {
      "type": "integer",
      "minimum": 0,
      "default": 0
    }
  }
]
Responses and errors
{
  "200": {
    "description": "Page of invoices.",
    "content": {
      "application/json": {
        "schema": {
          "type": "object",
          "properties": {
            "invoices": {
              "type": "array",
              "items": {
                "$ref": "#/components/schemas/Invoice"
              }
            },
            "total": {
              "type": "integer"
            },
            "limit": {
              "type": "integer"
            },
            "offset": {
              "type": "integer"
            }
          },
          "required": [
            "invoices",
            "total",
            "limit",
            "offset"
          ]
        }
      }
    }
  },
  "400": {
    "description": "invalid_status: unsupported status filter.",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Error"
        }
      }
    }
  },
  "401": {
    "description": "invalid_api_key: missing, malformed, unknown or revoked Bearer key.",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Error"
        }
      }
    }
  },
  "403": {
    "description": "subscription_inactive, plan_not_allowed or insufficient_scope.",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Error"
        }
      }
    }
  }
}
post

/api/v1/invoices

Create a draft or append lines

Creates an unnumbered draft for a saved client. Sender, bank account and payment term come from account settings; VAT treatment comes from the client. The server calculates totals. Use onOpenConcept=create for a separate draft. append may change a draft already scheduled for delivery. Creation/appending is NOT idempotent; an Idempotency-Key header is not supported. Dates and notes are ignored on append. A successful draft can still need details before finalization; review it in the app.

Required permission: invoices:write

JSON request · required
{
  "required": true,
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/InvoiceCreate"
      },
      "example": {
        "clientId": "REPLACE_WITH_CLIENT_ID",
        "onOpenConcept": "create",
        "lines": [
          {
            "description": "Advies",
            "quantity": 8,
            "unit": "uur",
            "unitPriceCents": 9500,
            "vatRate": 21
          }
        ]
      }
    }
  }
}
Responses and errors
{
  "200": {
    "description": "Lines appended; appended=true. sendPlanned=true means these lines will join an existing scheduled send.",
    "content": {
      "application/json": {
        "schema": {
          "type": "object",
          "properties": {
            "invoice": {
              "anyOf": [
                {
                  "$ref": "#/components/schemas/InvoiceDetail"
                },
                {
                  "type": "null"
                }
              ]
            },
            "appended": {
              "type": "boolean"
            },
            "sendPlanned": {
              "type": "boolean"
            }
          },
          "required": [
            "invoice",
            "appended",
            "sendPlanned"
          ]
        }
      }
    }
  },
  "201": {
    "description": "New draft; appended=false and sendPlanned=false. invoice can be null if removed before read-back.",
    "content": {
      "application/json": {
        "schema": {
          "type": "object",
          "properties": {
            "invoice": {
              "anyOf": [
                {
                  "$ref": "#/components/schemas/InvoiceDetail"
                },
                {
                  "type": "null"
                }
              ]
            },
            "appended": {
              "type": "boolean"
            },
            "sendPlanned": {
              "type": "boolean"
            }
          },
          "required": [
            "invoice",
            "appended",
            "sendPlanned"
          ]
        }
      }
    }
  },
  "400": {
    "description": "invalid_json or validation; issues groups messages by top-level field.",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Error"
        }
      }
    }
  },
  "401": {
    "description": "invalid_api_key: missing, malformed, unknown or revoked Bearer key.",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Error"
        }
      }
    }
  },
  "402": {
    "description": "invoice_cap: account invoice allowance reached.",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Error"
        }
      }
    }
  },
  "403": {
    "description": "subscription_inactive, plan_not_allowed or insufficient_scope.",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Error"
        }
      }
    }
  },
  "404": {
    "description": "client_not_found: unknown client or outside this administration.",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Error"
        }
      }
    }
  },
  "422": {
    "description": "no_company_profile: complete company settings in the app.",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Error"
        }
      }
    }
  }
}
get

/api/v1/invoices/{id}

Read an invoice with lines

Returns the invoice and calculated totals. No PDF, UBL, public recipient token or download URL is returned.

Required permission: invoices:read

Parameters
[
  {
    "name": "id",
    "in": "path",
    "required": true,
    "description": "ID returned by this API.",
    "schema": {
      "type": "string",
      "minLength": 1
    }
  }
]
Responses and errors
{
  "200": {
    "description": "Invoice with lines.",
    "content": {
      "application/json": {
        "schema": {
          "type": "object",
          "properties": {
            "invoice": {
              "$ref": "#/components/schemas/InvoiceDetail"
            }
          },
          "required": [
            "invoice"
          ]
        }
      }
    }
  },
  "401": {
    "description": "invalid_api_key: missing, malformed, unknown or revoked Bearer key.",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Error"
        }
      }
    }
  },
  "403": {
    "description": "subscription_inactive, plan_not_allowed or insufficient_scope.",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Error"
        }
      }
    }
  },
  "404": {
    "description": "not_found: unknown invoice or outside this administration.",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Error"
        }
      }
    }
  }
}
post

/api/v1/invoices/{id}/send

Schedule invoice delivery

Schedules a real email to the recipient. Only call when the user requests delivery. mode=now schedules after the 20-second undo window; delivery runs asynchronously. mode=scheduled (also an empty request body) defaults to tomorrow in the account send window. date may be at most 180 days ahead. An existing schedule is preserved with alreadyPlanned=true. After dispatch a repeat may return not_a_draft. A successful response means scheduled, not delivered. The invoice is numbered during dispatch if it is not already finalized.

Required permission: invoices:send

Parameters
[
  {
    "name": "id",
    "in": "path",
    "required": true,
    "description": "ID returned by this API.",
    "schema": {
      "type": "string",
      "minLength": 1
    }
  }
]
JSON request · optional
{
  "required": false,
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/InvoiceSend"
      },
      "example": {
        "mode": "scheduled"
      }
    }
  }
}
Responses and errors
{
  "200": {
    "description": "Delivery scheduled or existing schedule preserved.",
    "content": {
      "application/json": {
        "schema": {
          "type": "object",
          "properties": {
            "scheduledSendAt": {
              "type": "string",
              "format": "date-time"
            },
            "alreadyPlanned": {
              "type": "boolean"
            }
          },
          "required": [
            "scheduledSendAt",
            "alreadyPlanned"
          ]
        }
      }
    }
  },
  "400": {
    "description": "invalid_json, validation or date_too_far.",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Error"
        }
      }
    }
  },
  "401": {
    "description": "invalid_api_key: missing, malformed, unknown or revoked Bearer key.",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Error"
        }
      }
    }
  },
  "403": {
    "description": "subscription_inactive, plan_not_allowed or insufficient_scope.",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Error"
        }
      }
    }
  },
  "404": {
    "description": "not_found.",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Error"
        }
      }
    }
  },
  "409": {
    "description": "not_a_draft: invoice has already been sent or paid.",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Error"
        }
      }
    }
  },
  "422": {
    "description": "email_required: recipient has no email address.",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Error"
        }
      }
    }
  }
}
ClientCreate · JSON Schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "type": {
      "default": "business",
      "type": "string",
      "enum": [
        "business",
        "individual"
      ]
    },
    "name": {
      "type": "string",
      "minLength": 1
    },
    "number": {
      "type": "string",
      "maxLength": 40
    },
    "vatMode": {
      "default": "standard",
      "type": "string",
      "enum": [
        "standard",
        "reverse_charge",
        "vat_exempt"
      ]
    },
    "bankAccountId": {
      "description": "An existing bank account ID from this administration. Omit to use the default; this API does not list bank accounts.",
      "type": "string"
    },
    "contactName": {
      "type": "string"
    },
    "email": {
      "anyOf": [
        {
          "type": "string",
          "format": "email",
          "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
        },
        {
          "type": "string",
          "const": ""
        }
      ]
    },
    "address": {
      "type": "string"
    },
    "postalCode": {
      "type": "string"
    },
    "city": {
      "type": "string"
    },
    "country": {
      "description": "Country name. Omitted or empty defaults to Nederland.",
      "type": "string"
    },
    "kvk": {
      "description": "Optional KVK number: eight digits, ignoring whitespace.",
      "type": "string"
    },
    "vatNumber": {
      "description": "Optional VAT number. NL numbers must match NL + 9 digits + B + 2 digits (ignoring spaces/dots/case); foreign prefixes are accepted.",
      "type": "string"
    }
  },
  "required": [
    "name"
  ]
}
InvoiceCreate · JSON Schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "clientId": {
      "type": "string",
      "minLength": 1,
      "description": "ID returned by the clients API. Do not invent IDs."
    },
    "lines": {
      "minItems": 1,
      "maxItems": 200,
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "description": {
            "type": "string",
            "minLength": 1
          },
          "details": {
            "type": "string",
            "maxLength": 2000
          },
          "quantity": {
            "description": "Quantity; rounded server-side to 3 decimal places.",
            "type": "number",
            "minimum": 0.001,
            "maximum": 100000
          },
          "unit": {
            "type": "string",
            "maxLength": 30
          },
          "unitPriceCents": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100000000,
            "description": "Unit price excluding VAT, in integer euro cents. 9500 means EUR 95.00."
          },
          "vatRate": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "enum": [
              21,
              9,
              0
            ],
            "description": "VAT percentage; the client VAT mode determines whether VAT is charged."
          },
          "discountPct": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "periodStart": {
            "type": "string",
            "minLength": 1
          },
          "periodEnd": {
            "description": "ISO timestamp; exclusive end of the service period.",
            "type": "string",
            "minLength": 1
          }
        },
        "required": [
          "description",
          "quantity",
          "unitPriceCents",
          "vatRate"
        ]
      }
    },
    "issueDate": {
      "description": "YYYY-MM-DD. Defaults to today in UTC. Ignored when appending.",
      "type": "string",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
    },
    "dueDate": {
      "description": "YYYY-MM-DD. Defaults to issueDate plus the account payment term. Ignored when appending.",
      "type": "string",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
    },
    "notes": {
      "description": "Invoice notes. Ignored when appending to an existing draft.",
      "type": "string",
      "maxLength": 2000
    },
    "onOpenConcept": {
      "default": "create",
      "description": "create always makes a new draft. append adds lines to an existing open draft (even one scheduled for delivery), or creates one if none exists. Neither mode deduplicates retries.",
      "type": "string",
      "enum": [
        "create",
        "append"
      ]
    }
  },
  "required": [
    "clientId",
    "lines"
  ]
}
InvoiceSend · JSON Schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "mode": {
      "default": "scheduled",
      "type": "string",
      "enum": [
        "now",
        "scheduled"
      ]
    },
    "date": {
      "type": "string",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
    }
  }
}
Client · JSON Schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "id": {
      "type": "string"
    },
    "type": {
      "type": "string",
      "enum": [
        "business",
        "individual"
      ]
    },
    "number": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ]
    },
    "name": {
      "type": "string"
    },
    "contactName": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ]
    },
    "email": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ]
    },
    "address": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ]
    },
    "postalCode": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ]
    },
    "city": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ]
    },
    "country": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ]
    },
    "kvk": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ]
    },
    "vatNumber": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ]
    },
    "vatMode": {
      "type": "string",
      "enum": [
        "standard",
        "reverse_charge",
        "vat_exempt"
      ]
    },
    "createdAt": {
      "anyOf": [
        {
          "type": "string",
          "format": "date-time",
          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
        },
        {
          "type": "null"
        }
      ]
    }
  },
  "required": [
    "id",
    "type",
    "number",
    "name",
    "contactName",
    "email",
    "address",
    "postalCode",
    "city",
    "country",
    "kvk",
    "vatNumber",
    "vatMode",
    "createdAt"
  ],
  "additionalProperties": false
}
Invoice · JSON Schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "id": {
      "type": "string"
    },
    "number": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "description": "Null until the invoice is finalized in the app or during dispatch."
    },
    "status": {
      "type": "string",
      "enum": [
        "draft",
        "final",
        "sent",
        "overdue",
        "paid"
      ],
      "description": "Derived display status. An unpaid draft with a past due date can be overdue."
    },
    "invoiceType": {
      "type": "string",
      "enum": [
        "invoice",
        "credit"
      ]
    },
    "clientId": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ]
    },
    "recipient": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string"
        },
        "email": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ]
        }
      },
      "required": [
        "name",
        "email"
      ],
      "additionalProperties": false
    },
    "issueDate": {
      "anyOf": [
        {
          "type": "string",
          "format": "date",
          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
        },
        {
          "type": "null"
        }
      ]
    },
    "dueDate": {
      "anyOf": [
        {
          "type": "string",
          "format": "date",
          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
        },
        {
          "type": "null"
        }
      ]
    },
    "paidAt": {
      "anyOf": [
        {
          "type": "string",
          "format": "date-time",
          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
        },
        {
          "type": "null"
        }
      ]
    },
    "scheduledSendAt": {
      "anyOf": [
        {
          "type": "string",
          "format": "date-time",
          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
        },
        {
          "type": "null"
        }
      ]
    },
    "currency": {
      "type": "string"
    },
    "vatMode": {
      "type": "string",
      "enum": [
        "standard",
        "reverse_charge",
        "vat_exempt"
      ]
    },
    "notes": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ]
    },
    "subtotalCents": {
      "type": "integer",
      "minimum": -9007199254740991,
      "maximum": 9007199254740991
    },
    "vatCents": {
      "type": "integer",
      "minimum": -9007199254740991,
      "maximum": 9007199254740991
    },
    "totalCents": {
      "type": "integer",
      "minimum": -9007199254740991,
      "maximum": 9007199254740991
    },
    "createdAt": {
      "anyOf": [
        {
          "type": "string",
          "format": "date-time",
          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
        },
        {
          "type": "null"
        }
      ]
    },
    "updatedAt": {
      "anyOf": [
        {
          "type": "string",
          "format": "date-time",
          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
        },
        {
          "type": "null"
        }
      ]
    }
  },
  "required": [
    "id",
    "number",
    "status",
    "invoiceType",
    "clientId",
    "recipient",
    "issueDate",
    "dueDate",
    "paidAt",
    "scheduledSendAt",
    "currency",
    "vatMode",
    "notes",
    "subtotalCents",
    "vatCents",
    "totalCents",
    "createdAt",
    "updatedAt"
  ],
  "additionalProperties": false
}
InvoiceDetail · JSON Schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "id": {
      "type": "string"
    },
    "number": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "description": "Null until the invoice is finalized in the app or during dispatch."
    },
    "status": {
      "type": "string",
      "enum": [
        "draft",
        "final",
        "sent",
        "overdue",
        "paid"
      ],
      "description": "Derived display status. An unpaid draft with a past due date can be overdue."
    },
    "invoiceType": {
      "type": "string",
      "enum": [
        "invoice",
        "credit"
      ]
    },
    "clientId": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ]
    },
    "recipient": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string"
        },
        "email": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ]
        }
      },
      "required": [
        "name",
        "email"
      ],
      "additionalProperties": false
    },
    "issueDate": {
      "anyOf": [
        {
          "type": "string",
          "format": "date",
          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
        },
        {
          "type": "null"
        }
      ]
    },
    "dueDate": {
      "anyOf": [
        {
          "type": "string",
          "format": "date",
          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
        },
        {
          "type": "null"
        }
      ]
    },
    "paidAt": {
      "anyOf": [
        {
          "type": "string",
          "format": "date-time",
          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
        },
        {
          "type": "null"
        }
      ]
    },
    "scheduledSendAt": {
      "anyOf": [
        {
          "type": "string",
          "format": "date-time",
          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
        },
        {
          "type": "null"
        }
      ]
    },
    "currency": {
      "type": "string"
    },
    "vatMode": {
      "type": "string",
      "enum": [
        "standard",
        "reverse_charge",
        "vat_exempt"
      ]
    },
    "notes": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ]
    },
    "subtotalCents": {
      "type": "integer",
      "minimum": -9007199254740991,
      "maximum": 9007199254740991
    },
    "vatCents": {
      "type": "integer",
      "minimum": -9007199254740991,
      "maximum": 9007199254740991
    },
    "totalCents": {
      "type": "integer",
      "minimum": -9007199254740991,
      "maximum": 9007199254740991
    },
    "createdAt": {
      "anyOf": [
        {
          "type": "string",
          "format": "date-time",
          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
        },
        {
          "type": "null"
        }
      ]
    },
    "updatedAt": {
      "anyOf": [
        {
          "type": "string",
          "format": "date-time",
          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
        },
        {
          "type": "null"
        }
      ]
    },
    "lines": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "position": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "description": {
            "type": "string"
          },
          "details": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "quantity": {
            "type": "number"
          },
          "unit": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "unitPriceCents": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "description": "May be negative on an existing credit invoice."
          },
          "vatRate": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "discountPct": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "periodStart": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time",
                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
              },
              {
                "type": "null"
              }
            ]
          },
          "periodEnd": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time",
                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "id",
          "position",
          "description",
          "details",
          "quantity",
          "unit",
          "unitPriceCents",
          "vatRate",
          "discountPct",
          "periodStart",
          "periodEnd"
        ],
        "additionalProperties": false
      }
    }
  },
  "required": [
    "id",
    "number",
    "status",
    "invoiceType",
    "clientId",
    "recipient",
    "issueDate",
    "dueDate",
    "paidAt",
    "scheduledSendAt",
    "currency",
    "vatMode",
    "notes",
    "subtotalCents",
    "vatCents",
    "totalCents",
    "createdAt",
    "updatedAt",
    "lines"
  ],
  "additionalProperties": false
}
Error · JSON Schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "error": {
      "type": "string"
    },
    "issues": {
      "description": "Validation messages grouped by top-level field, for example lines or clientId.",
      "type": "object",
      "propertyNames": {
        "type": "string"
      },
      "additionalProperties": {
        "type": "array",
        "items": {
          "type": "string"
        }
      }
    }
  },
  "required": [
    "error"
  ],
  "additionalProperties": false
}