REST API · v1

Create invoices with the API

Let your software or AI assistant find clients and prepare invoice drafts. factuurmaken supplies your company details and calculates totals; you review the result in your dashboard.

Before you start

API access requires an active eligible plan (Pro, Business) or qualifying complimentary access. Complete your company profile and bank account settings first.

Create a separate key for your integration with the permissions below. Add clients:write only if the integration also needs to save new clients.

clients:readinvoices:readinvoices:write

The examples read the key from the FM_API_KEY environment variable. Store it in your integration’s secret settings, not in a prompt, URL or source code.

Your integration must support HTTP requests with a Bearer token. Documentation is public; customer data remains protected. MCP and OAuth login are not available yet.

From client to invoice draft

  1. 1Find the client

    Search by name or email and use the returned id. If several clients match, ask the user to choose. If none match, create a client using POST /api/v1/clients or in the dashboard.

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

    Replace REPLACE_WITH_CLIENT_ID with the selected id. This example bills 8 hours at €95.00 excluding VAT. Amounts are integer euro cents. VAT treatment comes from the client; the server calculates totals. This request does not schedule delivery.

    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
        }
      ]
    }'
    Example response · HTTP 201

    Example with standard VAT: €760.00 subtotal, €159.60 VAT and €919.60 total. IDs, dates and payment terms vary per request.

    {
      "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. 3Read back and review

    Replace REPLACE_WITH_INVOICE_ID with invoice.id from the response. Show the returned totals and open /app/invoices/INVOICE_ID in the dashboard with the default administration selected. Review, edit, finalize, download or send the invoice there.

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

API behavior

  • Every key acts in the account’s default administration, independently of the administration selected in the app. The API has no businessId parameter.
  • Creation and line appending are not deduplicated; Idempotency-Key is not supported. After a timeout, check whether the client or invoice already exists before writing again.
  • Use onOpenConcept: create for a new draft. append can add lines to a draft already scheduled for delivery. Dates and notes from the new request are then ignored.
  • Sending is a separate operation requiring invoices:send. A successful request means delivery is scheduled, not that the email has been delivered. An empty body defaults to scheduling for tomorrow.
  • A draft may still need details. Finalization, editing and PDF/UBL downloads happen in the app; these actions do not yet have public API endpoints.

API reference

The technical reference shares its source with OpenAPI. Expand an operation or schema to inspect parameters, fields and error codes.

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
}