From a73af353d47c2d0f1e07e8c8d532e9c2c61d3731 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 11 Jul 2026 19:10:55 +0200 Subject: [PATCH] Harden server; add authenticated Streamable HTTP transport and Nix module - Streamable HTTP transport (MCP_TRANSPORT=http) with mandatory auth: static bearer tokens or OIDC resource-server mode (JWT via JWKS, or RFC 7662 introspection), RFC 9728 protected-resource metadata, and DNS-rebinding protection - secrets loadable from files via *_FILE variants - timeouts on all outbound requests; JORTT_READ_ONLY tool filtering - OpenAPI spec committed and bundled; no implicit network fetches - Nix flake: sandboxed package build + hardened NixOS module taking an EnvironmentFile (agenix-friendly) Co-Authored-By: Claude Fable 5 --- .env.example | 30 ++++++ .gitignore | 4 +- README.md | 173 ++++++++++++++++++++++++++++--- flake.lock | 27 +++++ flake.nix | 35 +++++++ nix/module.nix | 258 ++++++++++++++++++++++++++++++++++++++++++++++ nix/package.nix | 31 ++++++ package-lock.json | 7 +- package.json | 8 +- src/auth.ts | 3 +- src/bearer.ts | 247 ++++++++++++++++++++++++++++++++++++++++++++ src/client.ts | 6 +- src/config.ts | 50 +++++++++ src/http.ts | 236 ++++++++++++++++++++++++++++++++++++++++++ src/index.ts | 170 ++++++++++++++++++++++++------ src/openapi.json | 1 + src/openapi.ts | 46 +++++---- 17 files changed, 1255 insertions(+), 77 deletions(-) create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 nix/module.nix create mode 100644 nix/package.nix create mode 100644 src/bearer.ts create mode 100644 src/config.ts create mode 100644 src/http.ts create mode 100644 src/openapi.json diff --git a/.env.example b/.env.example index 946f107..f2a4c26 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,36 @@ # Copy to .env and fill in credentials from your registered Jortt API application: # https://app.jortt.nl/new_profile/api/jortt/oauth_applications/list +# Every secret below can alternatively be read from a file via _FILE, +# e.g. JORTT_CLIENT_SECRET_FILE=/run/agenix/jortt-client-secret JORTT_CLIENT_ID= JORTT_CLIENT_SECRET= + # Optional: space-separated scopes to request (defaults to all registered scopes) # JORTT_SCOPES=invoices:read customers:read reports:read + +# Optional: expose only GET (list/get) tools +# JORTT_READ_ONLY=1 + +# --- HTTP transport (default is stdio) --- +# MCP_TRANSPORT=http +# MCP_HOST=127.0.0.1 +# MCP_PORT=3910 + +# Authentication for incoming MCP requests: "token" or "oidc" (required for http) +# MCP_AUTH_MODE=token +# MCP_AUTH_TOKEN= # generate with: openssl rand -hex 32 + +# OIDC resource-server mode (auth by an external OAuth provider / proxy): +# MCP_AUTH_MODE=oidc +# MCP_OIDC_ISSUER=https://auth.example.com +# MCP_RESOURCE_URL=https://jortt-mcp.example.com/mcp +# MCP_OIDC_AUDIENCE= # defaults to MCP_RESOURCE_URL +# MCP_OIDC_ALLOWED_SUBJECTS=kate,kate@example.com +# For issuers with opaque tokens (e.g. Authelia), use RFC 7662 introspection: +# MCP_OIDC_INTROSPECTION_URL= # discovered from the issuer when omitted +# MCP_OIDC_CLIENT_ID= +# MCP_OIDC_CLIENT_SECRET= + +# DNS-rebinding protection: exact Host / Origin header allowlists +# MCP_ALLOWED_HOSTS=jortt-mcp.example.com +# MCP_ALLOWED_ORIGINS= diff --git a/.gitignore b/.gitignore index 0288076..7abc743 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,4 @@ dist/ *.log .env .DS_Store - -# Fetched at build time by scripts/fetch-spec.mjs -src/openapi.json +result diff --git a/README.md b/README.md index 6e7fd2c..e8ad5d9 100644 --- a/README.md +++ b/README.md @@ -14,17 +14,21 @@ faithful to the API: **82 tools** across every documented resource. ## How it works -- The build step downloads the Jortt OpenAPI spec to `src/openapi.json` and - bundles it into `dist/`. On startup the server reads that spec, resolves all +- The Jortt OpenAPI spec is committed at `src/openapi.json` and bundled into + `dist/` at build time. On startup the server reads that spec, resolves all schema references, and turns every operation into an MCP tool with a proper - JSON Schema for its arguments. (If no bundled spec is found, it fetches one - live from Jortt as a fallback.) -- Authentication uses the OAuth 2.0 **client credentials** grant (the flow Jortt - recommends for applications bound to your own administration). The server - fetches a bearer token, caches it until shortly before it expires, and - transparently refreshes on expiry or a `401`. + JSON Schema for its arguments. Refresh the spec explicitly with + `npm run fetch-spec`; the server never loads it from the network unless you + set `JORTT_OPENAPI_URL` deliberately. +- Authentication towards Jortt uses the OAuth 2.0 **client credentials** grant + (the flow Jortt recommends for applications bound to your own + administration). The server fetches a bearer token, caches it until shortly + before it expires, and transparently refreshes on expiry or a `401`. - Path parameters, query parameters, and JSON request bodies are all handled automatically. Request bodies are passed under a single `body` argument. +- Two transports: **stdio** (default, for local clients like Claude Desktop) + and **Streamable HTTP** (`MCP_TRANSPORT=http`) for remote access. The HTTP + transport refuses to start without authentication configured. ## Prerequisites @@ -40,23 +44,59 @@ faithful to the API: **82 tools** across every documented resource. ```bash git clone https://github.com/deprekated/jortt-mcp.git cd jortt-mcp -npm install # runs the build, which downloads the current Jortt OpenAPI spec +npm install +npm run build ``` -> The build fetches `https://api.jortt.nl/swagger_doc`, so it needs outbound -> network access. Set `JORTT_OPENAPI_URL` to point at a local copy or mirror if -> you build offline. +On Nix, `nix build github:deprekated/jortt-mcp` — or see [NixOS](#nixos) below +for the module. ## Configuration -The server is configured entirely through environment variables: +The server is configured entirely through environment variables. Every +secret-bearing variable can instead be read from a file by appending `_FILE` +(e.g. `JORTT_CLIENT_SECRET_FILE=/run/agenix/jortt-client-secret`), which keeps +secrets out of process environments and works directly with agenix / systemd +credentials. + +### Core | Variable | Required | Description | | -------------------- | -------- | --------------------------------------------------------------------------- | | `JORTT_CLIENT_ID` | yes | OAuth client ID of your registered application. | | `JORTT_CLIENT_SECRET`| yes | OAuth client secret. | | `JORTT_SCOPES` | no | Space-separated scopes to request. Defaults to all scopes; the token server only grants scopes your application was registered with. | -| `JORTT_OPENAPI_URL` | no | Override the OpenAPI spec source (used at build time and as a runtime fallback). Defaults to `https://api.jortt.nl/swagger_doc`. | +| `JORTT_READ_ONLY` | no | `1`/`true`: expose only GET (list/get) tools — a hard stop on writes regardless of scopes. | +| `JORTT_OPENAPI_URL` | no | Load the OpenAPI spec from this URL instead of the bundled copy. Off by default. | + +### HTTP transport & authentication + +| Variable | Description | +| --------------------- | ------------------------------------------------------------------------------ | +| `MCP_TRANSPORT` | `stdio` (default) or `http` (Streamable HTTP). | +| `MCP_HOST` / `MCP_PORT` | Bind address and port. Defaults: `127.0.0.1:3910`. The server speaks plain HTTP — terminate TLS in a reverse proxy. | +| `MCP_AUTH_MODE` | `token` or `oidc`. **Required** in http mode; the server refuses to run unauthenticated. | +| `MCP_AUTH_TOKEN` | Static bearer token(s), comma/newline separated (`token` mode). Generate with `openssl rand -hex 32`. Compared in constant time; minimum 16 chars. | +| `MCP_OIDC_ISSUER` | Issuer URL of your OAuth authorization server / proxy (`oidc` mode). | +| `MCP_RESOURCE_URL` | Public URL of the MCP endpoint (e.g. `https://jortt-mcp.example.com/mcp`). Used as token audience and published in RFC 9728 protected-resource metadata for MCP client discovery. | +| `MCP_OIDC_AUDIENCE` | Override the audience to require in tokens (default: `MCP_RESOURCE_URL`). | +| `MCP_OIDC_ALLOWED_SUBJECTS` | Comma-separated allowlist matched against `sub`, `preferred_username`, and `email`. Strongly recommended — without it, *any* account on your IdP is accepted. | +| `MCP_OIDC_INTROSPECTION_URL`, `MCP_OIDC_CLIENT_ID`, `MCP_OIDC_CLIENT_SECRET` | Use RFC 7662 token introspection instead of JWT validation, for issuers with opaque access tokens (e.g. Authelia). The endpoint is discovered from the issuer when the URL is omitted. | +| `MCP_ALLOWED_HOSTS` / `MCP_ALLOWED_ORIGINS` | Exact-match allowlists for the `Host` / `Origin` headers (DNS-rebinding protection). Include the public hostname (with `:port` if non-standard). | + +In `oidc` mode the server is a standard OAuth 2.0 **resource server** per the +[MCP authorization spec](https://modelcontextprotocol.io/specification/draft/basic/authorization): +it validates bearer tokens issued by your IdP/OAuth proxy (Keycloak, Authelia, +Pomerium, Kanidm, ...) and serves `/.well-known/oauth-protected-resource` so +clients like the claude.ai remote-connector UI can discover the authorization +server and run the authorization-code + PKCE flow against it. For claude.ai, +your IdP needs either dynamic client registration or a manually registered +client whose ID/secret you paste into the connector settings; the redirect URL +to register is `https://claude.ai/api/mcp/auth_callback`. + +Endpoints in http mode: `POST/GET/DELETE /mcp` (MCP, authenticated), +`GET /healthz` (unauthenticated liveness probe), +`GET /.well-known/oauth-protected-resource` (only when OIDC is configured). ### Claude Desktop / Claude Code @@ -88,6 +128,84 @@ Restrict what the assistant can do by narrowing the scopes: } ``` +### Remote (Streamable HTTP) + +With a static token, from Claude Code: + +```bash +claude mcp add --transport http jortt https://jortt-mcp.example.com/mcp \ + --header "Authorization: Bearer " +``` + +With OIDC, add `https://jortt-mcp.example.com/mcp` as a custom connector on +claude.ai — the OAuth flow against your IdP is discovered automatically. + +## NixOS + +The repository is a flake providing a package and a NixOS module. The module +runs the server as a hardened systemd service (DynamicUser, syscall filtering, +read-only filesystem view) listening on loopback; put nginx/caddy in front for +TLS. Secrets are supplied via a systemd `EnvironmentFile`, which is where +[agenix](https://github.com/ryantm/agenix) comes in: + +```nix +{ + inputs.jortt-mcp.url = "github:deprekated/jortt-mcp"; + + # in your host configuration: + imports = [ inputs.jortt-mcp.nixosModules.default ]; + + age.secrets.jortt-mcp-env.file = ./secrets/jortt-mcp.env.age; + # The encrypted file contains: + # JORTT_CLIENT_ID=... + # JORTT_CLIENT_SECRET=... + # MCP_AUTH_TOKEN=... # token mode only + # MCP_OIDC_CLIENT_SECRET=... # only for introspection mode + + services.jortt-mcp = { + enable = true; + environmentFile = config.age.secrets.jortt-mcp-env.path; + port = 3910; + + # Simplest: static bearer token (set MCP_AUTH_TOKEN in the env file) + auth.mode = "token"; + + # Or: OAuth via your IdP / OAuth proxy, as needed for claude.ai + # auth.mode = "oidc"; + # auth.oidc.issuer = "https://auth.example.com"; + # auth.oidc.allowedSubjects = [ "kate" ]; + # resourceUrl = "https://jortt-mcp.example.com/mcp"; + + allowedHosts = [ "jortt-mcp.example.com" ]; + # readOnly = true; + # scopes = [ "invoices:read" "customers:read" "reports:read" ]; + }; + + services.nginx.virtualHosts."jortt-mcp.example.com" = { + enableACME = true; + forceSSL = true; + locations."/".proxyPass = "http://127.0.0.1:3910"; + # SSE responses must not be buffered: + locations."/".extraConfig = '' + proxy_buffering off; + proxy_read_timeout 1h; + ''; + }; +} +``` + +If your IdP issues opaque access tokens (Authelia does), switch the OIDC block +to introspection: + +```nix +auth.oidc = { + issuer = "https://auth.example.com"; + introspectionClientId = "jortt-mcp"; + allowedSubjects = [ "kate" ]; +}; +# and put MCP_OIDC_CLIENT_SECRET=... in the agenix env file +``` + ## Tool overview Tool names follow a predictable `verb_resource` convention derived from the API @@ -127,14 +245,37 @@ npm run typecheck # type-check without emitting ### Updating to the latest API -The tool set is generated from the OpenAPI spec, which the build downloads -fresh each time. To pick up new Jortt endpoints, just rebuild: +The tool set is generated from the committed OpenAPI spec. To pick up new +Jortt endpoints, refresh it and rebuild: ```bash +npm run fetch-spec # downloads https://api.jortt.nl/swagger_doc to src/openapi.json npm run build ``` No code changes are needed — new operations become new tools automatically. +Review and commit the updated `src/openapi.json`. + +## Security model + +- **The MCP endpoint fronts your entire administration.** Whoever can call it + can do whatever the Jortt application's scopes allow. Layer the controls: + narrow the scopes at registration, set `JORTT_READ_ONLY` if writes aren't + needed, and in OIDC mode always set `MCP_OIDC_ALLOWED_SUBJECTS`. +- The HTTP transport refuses to start without `MCP_AUTH_MODE`; there is no + unauthenticated mode. +- Static tokens are compared in constant time (over SHA-256 digests); JWTs are + validated for signature, issuer, expiry, and audience; introspection results + are cached for at most 60 s so revocation takes effect quickly. +- The server binds to loopback by default and expects a TLS-terminating + reverse proxy in front. `MCP_ALLOWED_HOSTS`/`MCP_ALLOWED_ORIGINS` add + DNS-rebinding protection. +- Secrets can be read from files (`*_FILE`) instead of the environment; the + NixOS module keeps them in a root-owned `EnvironmentFile` (e.g. agenix) and + runs the service fully sandboxed. +- All outbound HTTP calls (Jortt API, token endpoint, JWKS/introspection) + carry timeouts. The OpenAPI spec that defines the tool surface is bundled at + build time, never fetched implicitly at runtime. ## Notes & limits diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..fe90317 --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1783522502, + "narHash": "sha256-iffAls3iaNTyJC2faYcUXSI+Gp02cDjYl+MygxKl2GI=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "0bb7ec54c8483066ec9d7720e780a5caa71f8612", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..90c0f47 --- /dev/null +++ b/flake.nix @@ -0,0 +1,35 @@ +{ + description = "MCP server for the Jortt accounting & invoicing API"; + + inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + + outputs = { self, nixpkgs }: + let + systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]; + forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f nixpkgs.legacyPackages.${system}); + mkPackage = pkgs: pkgs.callPackage ./nix/package.nix { }; + in + { + packages = forAllSystems (pkgs: rec { + jortt-mcp = mkPackage pkgs; + default = jortt-mcp; + }); + + overlays.default = final: prev: { + jortt-mcp = mkPackage final; + }; + + nixosModules.jortt-mcp = { pkgs, lib, ... }: { + imports = [ ./nix/module.nix ]; + services.jortt-mcp.package = + lib.mkDefault self.packages.${pkgs.stdenv.hostPlatform.system}.jortt-mcp; + }; + nixosModules.default = self.nixosModules.jortt-mcp; + + devShells = forAllSystems (pkgs: { + default = pkgs.mkShell { + packages = [ pkgs.nodejs_22 pkgs.typescript ]; + }; + }); + }; +} diff --git a/nix/module.nix b/nix/module.nix new file mode 100644 index 0000000..0bddf5c --- /dev/null +++ b/nix/module.nix @@ -0,0 +1,258 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.services.jortt-mcp; +in +{ + options.services.jortt-mcp = { + enable = lib.mkEnableOption "jortt-mcp, an MCP server for the Jortt accounting API"; + + package = lib.mkOption { + type = lib.types.package; + description = "The jortt-mcp package to run."; + }; + + host = lib.mkOption { + type = lib.types.str; + default = "127.0.0.1"; + description = '' + Address to bind the Streamable HTTP listener to. Keep this on + loopback and terminate TLS in a reverse proxy (nginx, caddy, ...); + the server itself speaks plain HTTP. + ''; + }; + + port = lib.mkOption { + type = lib.types.port; + default = 3910; + description = "Port for the Streamable HTTP listener."; + }; + + environmentFile = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + example = "/run/agenix/jortt-mcp.env"; + description = '' + systemd EnvironmentFile with the secrets, in KEY=value format. + Intended for an agenix-managed file, e.g. + {command}`age.secrets.jortt-mcp-env.file`. + + Required keys: {env}`JORTT_CLIENT_ID`, {env}`JORTT_CLIENT_SECRET`. + For `auth.mode = "token"` also set {env}`MCP_AUTH_TOKEN` (one or + more tokens, comma- or newline-separated). For OIDC token + introspection also set {env}`MCP_OIDC_CLIENT_SECRET`. + ''; + }; + + readOnly = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + Expose only GET operations (list/get tools). A useful hard stop on + top of OAuth scopes when the assistant should not create or modify + anything in the administration. + ''; + }; + + scopes = lib.mkOption { + type = lib.types.nullOr (lib.types.listOf lib.types.str); + default = null; + example = [ "invoices:read" "customers:read" "reports:read" ]; + description = '' + Jortt OAuth scopes to request. Defaults to all scopes the + registered Jortt application was granted. + ''; + }; + + auth = { + mode = lib.mkOption { + type = lib.types.enum [ "token" "oidc" ]; + description = '' + How incoming MCP requests are authenticated. + + `token`: static bearer token(s), supplied via + {env}`MCP_AUTH_TOKEN` in {option}`environmentFile`. Works with + clients that can send an Authorization header (Claude Code, + the Claude API). + + `oidc`: validate OAuth 2.0 access tokens issued by an external + authorization server or OAuth proxy (Keycloak, Authelia, + Pomerium, ...). The server acts as an OAuth resource server per + the MCP authorization spec and publishes RFC 9728 + protected-resource metadata for client discovery. Required for + claude.ai remote connectors. + ''; + }; + + oidc = { + issuer = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + example = "https://auth.example.com"; + description = "Issuer URL of the authorization server / OAuth proxy."; + }; + + audience = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = '' + Audience the access token must carry. Defaults to + {option}`resourceUrl`. + ''; + }; + + allowedSubjects = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + example = [ "kate" "kate@example.com" ]; + description = '' + When non-empty, only tokens whose subject, preferred_username + or email matches one of these values are accepted. Strongly + recommended: any account on the IdP can otherwise obtain a + valid token for this (single-administration) server. + ''; + }; + + introspectionUrl = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = '' + RFC 7662 token introspection endpoint, for authorization + servers that issue opaque (non-JWT) access tokens, e.g. + Authelia. When set (or when {option}`introspectionClientId` + is set, using endpoint discovery), tokens are introspected + instead of validated as JWTs; the introspection client + secret comes from {env}`MCP_OIDC_CLIENT_SECRET` in + {option}`environmentFile`. + ''; + }; + + introspectionClientId = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "Client ID used to authenticate to the introspection endpoint."; + }; + }; + }; + + resourceUrl = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + example = "https://jortt-mcp.example.com/mcp"; + description = '' + Public URL of the MCP endpoint as reached through the reverse + proxy. Used as the OAuth resource identifier / token audience and + in the published protected-resource metadata. + ''; + }; + + allowedHosts = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + example = [ "jortt-mcp.example.com" ]; + description = '' + Exact values the HTTP Host header must match (DNS-rebinding + protection). Include the public hostname (add `:port` if + non-standard). Leave empty to disable the check, e.g. when a + reverse proxy already enforces the virtual host. + ''; + }; + + allowedOrigins = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + description = "Exact Origin header values to accept (browser-based clients)."; + }; + + extraEnvironment = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + default = { }; + description = "Extra environment variables for the service (non-secret)."; + }; + + openFirewall = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Open the firewall for the listener port (only sensible with a non-loopback host)."; + }; + }; + + config = lib.mkIf cfg.enable { + assertions = [ + { + assertion = cfg.environmentFile != null; + message = "services.jortt-mcp.environmentFile is required: it must provide JORTT_CLIENT_ID and JORTT_CLIENT_SECRET (and MCP_AUTH_TOKEN in token mode)."; + } + { + assertion = cfg.auth.mode != "oidc" || cfg.auth.oidc.issuer != null; + message = "services.jortt-mcp.auth.oidc.issuer must be set when auth.mode = \"oidc\"."; + } + { + assertion = cfg.auth.mode != "oidc" || cfg.resourceUrl != null || cfg.auth.oidc.audience != null; + message = "services.jortt-mcp: with auth.mode = \"oidc\", set resourceUrl (recommended) or auth.oidc.audience."; + } + ]; + + networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ cfg.port ]; + + systemd.services.jortt-mcp = { + description = "Jortt MCP server"; + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + + environment = { + MCP_TRANSPORT = "http"; + MCP_HOST = cfg.host; + MCP_PORT = toString cfg.port; + MCP_AUTH_MODE = cfg.auth.mode; + } + // lib.optionalAttrs cfg.readOnly { JORTT_READ_ONLY = "1"; } + // lib.optionalAttrs (cfg.scopes != null) { JORTT_SCOPES = lib.concatStringsSep " " cfg.scopes; } + // lib.optionalAttrs (cfg.resourceUrl != null) { MCP_RESOURCE_URL = cfg.resourceUrl; } + // lib.optionalAttrs (cfg.allowedHosts != [ ]) { MCP_ALLOWED_HOSTS = lib.concatStringsSep "," cfg.allowedHosts; } + // lib.optionalAttrs (cfg.allowedOrigins != [ ]) { MCP_ALLOWED_ORIGINS = lib.concatStringsSep "," cfg.allowedOrigins; } + // lib.optionalAttrs (cfg.auth.oidc.issuer != null) { MCP_OIDC_ISSUER = cfg.auth.oidc.issuer; } + // lib.optionalAttrs (cfg.auth.oidc.audience != null) { MCP_OIDC_AUDIENCE = cfg.auth.oidc.audience; } + // lib.optionalAttrs (cfg.auth.oidc.allowedSubjects != [ ]) { + MCP_OIDC_ALLOWED_SUBJECTS = lib.concatStringsSep "," cfg.auth.oidc.allowedSubjects; + } + // lib.optionalAttrs (cfg.auth.oidc.introspectionUrl != null) { MCP_OIDC_INTROSPECTION_URL = cfg.auth.oidc.introspectionUrl; } + // lib.optionalAttrs (cfg.auth.oidc.introspectionClientId != null) { MCP_OIDC_CLIENT_ID = cfg.auth.oidc.introspectionClientId; } + // cfg.extraEnvironment; + + serviceConfig = { + ExecStart = lib.getExe cfg.package; + EnvironmentFile = [ cfg.environmentFile ]; + Restart = "on-failure"; + RestartSec = 5; + + # Hardening + DynamicUser = true; + NoNewPrivileges = true; + UMask = "0077"; + ProtectSystem = "strict"; + ProtectHome = true; + PrivateTmp = true; + PrivateDevices = true; + ProtectClock = true; + ProtectControlGroups = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectHostname = true; + ProtectProc = "invisible"; + ProcSubset = "pid"; + RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + LockPersonality = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ]; + CapabilityBoundingSet = ""; + AmbientCapabilities = ""; + }; + }; + }; +} diff --git a/nix/package.nix b/nix/package.nix new file mode 100644 index 0000000..000eec4 --- /dev/null +++ b/nix/package.nix @@ -0,0 +1,31 @@ +{ lib, buildNpmPackage, nodejs_22 }: + +buildNpmPackage { + pname = "jortt-mcp"; + version = "0.2.0"; + + # Explicit fileset: never pull .env, dist/ or other stray files into the store. + src = lib.fileset.toSource { + root = ../.; + fileset = lib.fileset.unions [ + ../package.json + ../package-lock.json + ../tsconfig.json + ../src + ../scripts + ../LICENSE + ../README.md + ]; + }; + + nodejs = nodejs_22; + + npmDepsHash = "sha256-eKdcd++SsI9gi3exUoNogbCpsUxSrVGRM1iQMUcF9Y4="; + + meta = { + description = "Model Context Protocol server for the Jortt accounting API"; + homepage = "https://github.com/deprekated/jortt-mcp"; + license = lib.licenses.mit; + mainProgram = "jortt-mcp"; + }; +} diff --git a/package-lock.json b/package-lock.json index 92201f8..45c226f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,16 @@ { "name": "jortt-mcp", - "version": "0.1.0", + "version": "0.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "jortt-mcp", - "version": "0.1.0", + "version": "0.2.0", "license": "MIT", "dependencies": { - "@modelcontextprotocol/sdk": "^1.12.0" + "@modelcontextprotocol/sdk": "^1.29.0", + "jose": "^6.0.0" }, "bin": { "jortt-mcp": "dist/index.js" diff --git a/package.json b/package.json index 84befcd..2f9a19c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jortt-mcp", - "version": "0.1.0", + "version": "0.2.0", "description": "Model Context Protocol server for the Jortt accounting API", "license": "MIT", "type": "module", @@ -13,8 +13,7 @@ ], "scripts": { "fetch-spec": "node scripts/fetch-spec.mjs", - "build": "npm run fetch-spec && tsc && node -e \"require('fs').copyFileSync('src/openapi.json','dist/openapi.json')\"", - "prepare": "npm run build", + "build": "tsc && node -e \"require('fs').copyFileSync('src/openapi.json','dist/openapi.json')\"", "start": "node dist/index.js", "dev": "tsc --watch", "typecheck": "tsc --noEmit" @@ -31,7 +30,8 @@ "node": ">=18" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.12.0" + "@modelcontextprotocol/sdk": "^1.29.0", + "jose": "^6.0.0" }, "devDependencies": { "@types/node": "^22.10.0", diff --git a/src/auth.ts b/src/auth.ts index 95b62e8..d2bd65b 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -78,12 +78,13 @@ export class TokenManager { Accept: "application/json", }, body: body.toString(), + signal: AbortSignal.timeout(15_000), }); if (!res.ok) { const text = await res.text().catch(() => ""); throw new Error( - `Failed to obtain Jortt access token (HTTP ${res.status}): ${text || res.statusText}`, + `Failed to obtain Jortt access token (HTTP ${res.status}): ${(text || res.statusText).slice(0, 500)}`, ); } diff --git a/src/bearer.ts b/src/bearer.ts new file mode 100644 index 0000000..6e73588 --- /dev/null +++ b/src/bearer.ts @@ -0,0 +1,247 @@ +import { createHash, timingSafeEqual } from "node:crypto"; +import { + createRemoteJWKSet, + jwtVerify, + type JWTPayload, + type JWTVerifyGetKey, +} from "jose"; + +export type VerifyResult = + | { ok: true; subject?: string; scopes?: string[] } + | { ok: false; error: string }; + +/** Verifies the bearer token presented on incoming MCP HTTP requests. */ +export interface BearerVerifier { + verify(token: string): Promise; +} + +function sha256(input: string): Buffer { + return createHash("sha256").update(input, "utf8").digest(); +} + +/** + * Static shared-secret tokens. Comparison is constant-time over SHA-256 + * digests so token length is not observable either. + */ +export class StaticTokenVerifier implements BearerVerifier { + private readonly digests: Buffer[]; + + constructor(tokens: string[]) { + if (tokens.length === 0) { + throw new Error("StaticTokenVerifier requires at least one token"); + } + for (const t of tokens) { + if (t.length < 16) { + throw new Error( + "Refusing a static bearer token shorter than 16 characters; " + + "generate one with e.g. `openssl rand -hex 32`.", + ); + } + } + this.digests = tokens.map(sha256); + } + + async verify(token: string): Promise { + const digest = sha256(token); + for (const expected of this.digests) { + if (timingSafeEqual(digest, expected)) return { ok: true }; + } + return { ok: false, error: "invalid token" }; + } +} + +interface SubjectPolicy { + /** When non-empty, only these `sub` (or preferred_username/email) values are accepted. */ + allowedSubjects: string[]; +} + +function subjectAllowed( + policy: SubjectPolicy, + candidates: Array, +): boolean { + if (policy.allowedSubjects.length === 0) return true; + return candidates.some((c) => c !== undefined && policy.allowedSubjects.includes(c)); +} + +async function discoverAuthServer( + issuer: string, +): Promise<{ jwks_uri?: string; introspection_endpoint?: string }> { + const base = issuer.replace(/\/$/, ""); + const candidates = [ + `${base}/.well-known/openid-configuration`, + `${base}/.well-known/oauth-authorization-server`, + ]; + let lastError = ""; + for (const url of candidates) { + try { + const res = await fetch(url, { + headers: { Accept: "application/json" }, + signal: AbortSignal.timeout(10_000), + }); + if (res.ok) { + return (await res.json()) as { + jwks_uri?: string; + introspection_endpoint?: string; + }; + } + lastError = `HTTP ${res.status} from ${url}`; + } catch (err) { + lastError = `${url}: ${err instanceof Error ? err.message : String(err)}`; + } + } + throw new Error(`OAuth discovery failed for issuer ${issuer}: ${lastError}`); +} + +/** + * Validates JWT access tokens issued by an external OAuth/OIDC authorization + * server (the "OAuth proxy"): signature via the issuer's JWKS, plus issuer, + * expiry, and audience checks. + */ +export class JwtVerifier implements BearerVerifier { + private jwks: JWTVerifyGetKey | null = null; + + constructor( + private readonly issuer: string, + private readonly audience: string, + private readonly policy: SubjectPolicy, + ) {} + + private async getJwks(): Promise { + if (this.jwks) return this.jwks; + const meta = await discoverAuthServer(this.issuer); + if (!meta.jwks_uri) { + throw new Error( + `Issuer ${this.issuer} does not advertise a jwks_uri; ` + + `if it issues opaque tokens, configure token introspection instead.`, + ); + } + this.jwks = createRemoteJWKSet(new URL(meta.jwks_uri)); + return this.jwks; + } + + async verify(token: string): Promise { + let payload: JWTPayload; + try { + const jwks = await this.getJwks(); + ({ payload } = await jwtVerify(token, jwks, { + issuer: this.issuer, + audience: this.audience, + })); + } catch (err) { + return { + ok: false, + error: err instanceof Error ? err.message : String(err), + }; + } + + const username = + typeof payload.preferred_username === "string" + ? payload.preferred_username + : undefined; + const email = typeof payload.email === "string" ? payload.email : undefined; + if (!subjectAllowed(this.policy, [payload.sub, username, email])) { + return { ok: false, error: `subject ${payload.sub} not allowed` }; + } + + const scopes = + typeof payload.scope === "string" ? payload.scope.split(" ") : undefined; + return { ok: true, subject: payload.sub, scopes }; + } +} + +interface IntrospectionResponse { + active: boolean; + sub?: string; + username?: string; + scope?: string; + aud?: string | string[]; + exp?: number; +} + +/** + * RFC 7662 token introspection, for authorization servers that issue opaque + * access tokens (e.g. Authelia). Successful results are cached briefly (keyed + * by token digest) to avoid a round-trip to the AS on every MCP request. + */ +export class IntrospectionVerifier implements BearerVerifier { + private endpoint: string | null; + private readonly cache = new Map(); + + constructor( + private readonly issuer: string, + private readonly clientId: string, + private readonly clientSecret: string, + private readonly audience: string | undefined, + private readonly policy: SubjectPolicy, + endpoint?: string, + ) { + this.endpoint = endpoint ?? null; + } + + private async getEndpoint(): Promise { + if (this.endpoint) return this.endpoint; + const meta = await discoverAuthServer(this.issuer); + if (!meta.introspection_endpoint) { + throw new Error( + `Issuer ${this.issuer} does not advertise an introspection_endpoint; ` + + `set MCP_OIDC_INTROSPECTION_URL explicitly.`, + ); + } + this.endpoint = meta.introspection_endpoint; + return this.endpoint; + } + + async verify(token: string): Promise { + const key = sha256(token).toString("hex"); + const now = Date.now(); + const cached = this.cache.get(key); + if (cached && cached.until > now) return cached.result; + + const result = await this.introspect(token); + // Cache positive results for up to 60s (bounded by token expiry); + // negative results are not cached so revocations aren't masked longer + // than necessary and probing stays expensive for the AS, not for us. + if (result.ok) { + this.cache.set(key, { until: now + 60_000, result }); + if (this.cache.size > 1000) { + for (const [k, v] of this.cache) { + if (v.until <= now) this.cache.delete(k); + } + } + } + return result; + } + + private async introspect(token: string): Promise { + const endpoint = await this.getEndpoint(); + const basic = Buffer.from(`${this.clientId}:${this.clientSecret}`).toString( + "base64", + ); + const res = await fetch(endpoint, { + method: "POST", + headers: { + Authorization: `Basic ${basic}`, + "Content-Type": "application/x-www-form-urlencoded", + Accept: "application/json", + }, + body: new URLSearchParams({ token }).toString(), + signal: AbortSignal.timeout(10_000), + }); + if (!res.ok) { + throw new Error(`Token introspection failed: HTTP ${res.status}`); + } + const data = (await res.json()) as IntrospectionResponse; + if (!data.active) return { ok: false, error: "token not active" }; + + if (this.audience) { + const aud = Array.isArray(data.aud) ? data.aud : data.aud ? [data.aud] : []; + if (!aud.includes(this.audience)) { + return { ok: false, error: "token audience mismatch" }; + } + } + if (!subjectAllowed(this.policy, [data.sub, data.username])) { + return { ok: false, error: `subject ${data.sub} not allowed` }; + } + return { ok: true, subject: data.sub, scopes: data.scope?.split(" ") }; + } +} diff --git a/src/client.ts b/src/client.ts index a575a57..336c2c8 100644 --- a/src/client.ts +++ b/src/client.ts @@ -66,7 +66,11 @@ export class JorttClient { Accept: "application/json", }; if (init.body) headers["Content-Type"] = "application/json"; - return fetch(url, { ...init, headers }); + return fetch(url, { + ...init, + headers, + signal: AbortSignal.timeout(60_000), + }); }; let res = await doFetch(); diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..137bd56 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,50 @@ +import { readFileSync } from "node:fs"; + +/** + * Read a configuration value from the environment, with `_FILE` + * indirection: if `_FILE` is set, the value is read from that file + * (trailing whitespace stripped). This keeps secrets out of process + * environments and lets systemd credentials / agenix-managed files be used + * directly. + */ +export function envOrFile(name: string): string | undefined { + const direct = process.env[name]; + const file = process.env[`${name}_FILE`]; + if (direct && file) { + throw new Error(`Both ${name} and ${name}_FILE are set; use only one.`); + } + if (file) { + try { + return readFileSync(file, "utf8").replace(/\s+$/, ""); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + throw new Error(`Could not read ${name}_FILE (${file}): ${message}`); + } + } + return direct || undefined; +} + +/** Like envOrFile, but exits with a helpful message when unset. */ +export function requireEnvOrFile(name: string, hint: string): string { + const value = envOrFile(name); + if (!value) { + console.error(`[jortt-mcp] Missing required configuration: ${name}\n${hint}`); + process.exit(1); + } + return value; +} + +export function envFlag(name: string): boolean { + const value = process.env[name]?.toLowerCase(); + return value === "1" || value === "true" || value === "yes"; +} + +/** Parse a comma- or whitespace-separated env list. */ +export function envList(name: string): string[] { + const value = process.env[name]; + if (!value) return []; + return value + .split(/[,\s]+/) + .map((s) => s.trim()) + .filter(Boolean); +} diff --git a/src/http.ts b/src/http.ts new file mode 100644 index 0000000..7ea2247 --- /dev/null +++ b/src/http.ts @@ -0,0 +1,236 @@ +import { randomUUID } from "node:crypto"; +import { createServer as createHttpServer, type IncomingMessage, type ServerResponse } from "node:http"; +import type { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import type { BearerVerifier } from "./bearer.js"; + +const MCP_PATH = "/mcp"; +const SESSION_TTL_MS = 8 * 60 * 60 * 1000; +const SESSION_SWEEP_MS = 10 * 60 * 1000; +const MAX_SESSIONS = 64; + +export interface HttpConfig { + host: string; + port: number; + verifier: BearerVerifier; + /** + * Public URL of the MCP endpoint (e.g. https://mcp.example.com/mcp). When + * set together with `issuer`, OAuth protected-resource metadata (RFC 9728) + * is served so MCP clients can discover the authorization server. + */ + resourceUrl?: string; + /** Authorization server (OAuth proxy / IdP) issuer URL, for metadata. */ + issuer?: string; + /** Exact `host[:port]` values accepted in the Host header. */ + allowedHosts: string[]; + /** Exact origins accepted in the Origin header (browser clients). */ + allowedOrigins: string[]; + /** Creates a fresh MCP Server instance for each session. */ + createMcpServer: () => Server; +} + +interface Session { + transport: StreamableHTTPServerTransport; + lastSeen: number; +} + +function protectedResourceMetadata(config: HttpConfig): string { + return JSON.stringify({ + resource: config.resourceUrl, + authorization_servers: [config.issuer], + bearer_methods_supported: ["header"], + }); +} + +function send(res: ServerResponse, status: number, body: string, headers: Record = {}): void { + res.writeHead(status, { "Content-Type": "application/json", ...headers }); + res.end(body); +} + +function jsonRpcError(res: ServerResponse, status: number, code: number, message: string, headers: Record = {}): void { + send( + res, + status, + JSON.stringify({ jsonrpc: "2.0", error: { code, message }, id: null }), + headers, + ); +} + +export function startHttpServer(config: HttpConfig): Promise { + const sessions = new Map(); + + const metadataUrl = config.resourceUrl + ? new URL("/.well-known/oauth-protected-resource", config.resourceUrl).toString() + : undefined; + + const unauthorized = (res: ServerResponse, detail: string): void => { + const challenge = [ + `Bearer realm="jortt-mcp"`, + `error="invalid_token"`, + metadataUrl && config.issuer ? `resource_metadata="${metadataUrl}"` : null, + ] + .filter(Boolean) + .join(", "); + jsonRpcError(res, 401, -32001, `Unauthorized: ${detail}`, { + "WWW-Authenticate": challenge, + }); + }; + + const authenticate = async (req: IncomingMessage, res: ServerResponse): Promise => { + const header = req.headers.authorization; + if (!header?.startsWith("Bearer ")) { + unauthorized(res, "missing bearer token"); + return false; + } + try { + const result = await config.verifier.verify(header.slice("Bearer ".length).trim()); + if (!result.ok) { + console.error(`[jortt-mcp] auth rejected: ${result.error}`); + unauthorized(res, "invalid or expired token"); + return false; + } + return true; + } catch (err) { + console.error(`[jortt-mcp] auth verification error:`, err); + jsonRpcError(res, 503, -32000, "Authorization backend unavailable"); + return false; + } + }; + + const evictIfNeeded = (): void => { + if (sessions.size < MAX_SESSIONS) return; + let oldestId: string | null = null; + let oldestSeen = Infinity; + for (const [id, s] of sessions) { + if (s.lastSeen < oldestSeen) { + oldestSeen = s.lastSeen; + oldestId = id; + } + } + if (oldestId) { + const session = sessions.get(oldestId)!; + sessions.delete(oldestId); + void session.transport.close().catch(() => {}); + } + }; + + const newTransport = async (): Promise => { + evictIfNeeded(); + const transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: () => randomUUID(), + enableDnsRebindingProtection: + config.allowedHosts.length > 0 || config.allowedOrigins.length > 0, + allowedHosts: config.allowedHosts.length ? config.allowedHosts : undefined, + allowedOrigins: config.allowedOrigins.length ? config.allowedOrigins : undefined, + onsessioninitialized: (sessionId) => { + sessions.set(sessionId, { transport, lastSeen: Date.now() }); + }, + onsessionclosed: (sessionId) => { + sessions.delete(sessionId); + }, + }); + transport.onclose = () => { + if (transport.sessionId) sessions.delete(transport.sessionId); + }; + const mcpServer = config.createMcpServer(); + await mcpServer.connect(transport); + return transport; + }; + + const handleMcp = async (req: IncomingMessage, res: ServerResponse): Promise => { + if (!(await authenticate(req, res))) return; + + const sessionId = req.headers["mcp-session-id"]; + if (typeof sessionId === "string") { + const session = sessions.get(sessionId); + if (!session) { + jsonRpcError(res, 404, -32001, "Session not found"); + return; + } + session.lastSeen = Date.now(); + await session.transport.handleRequest(req, res); + return; + } + + if (req.method !== "POST") { + jsonRpcError(res, 400, -32000, "Mcp-Session-Id header required"); + return; + } + // No session yet: only an initialize request is valid here. The transport + // enforces that and responds 400 itself for anything else. + const transport = await newTransport(); + await transport.handleRequest(req, res); + }; + + const httpServer = createHttpServer((req, res) => { + const path = (req.url ?? "/").split("?")[0].replace(/\/+$/, "") || "/"; + + if (req.method === "GET" && path === "/healthz") { + send(res, 200, JSON.stringify({ status: "ok" })); + return; + } + + if ( + req.method === "GET" && + (path === "/.well-known/oauth-protected-resource" || + path === `/.well-known/oauth-protected-resource${MCP_PATH}`) + ) { + if (config.resourceUrl && config.issuer) { + send(res, 200, protectedResourceMetadata(config), { + "Cache-Control": "public, max-age=3600", + }); + } else { + send(res, 404, JSON.stringify({ error: "no OAuth metadata configured" })); + } + return; + } + + if (path === MCP_PATH) { + handleMcp(req, res).catch((err) => { + console.error("[jortt-mcp] request error:", err); + if (!res.headersSent) { + jsonRpcError(res, 500, -32603, "Internal server error"); + } else { + res.end(); + } + }); + return; + } + + send(res, 404, JSON.stringify({ error: "not found" })); + }); + + // Reap sessions that have been idle past the TTL. + const sweeper = setInterval(() => { + const cutoff = Date.now() - SESSION_TTL_MS; + for (const [id, session] of sessions) { + if (session.lastSeen < cutoff) { + sessions.delete(id); + void session.transport.close().catch(() => {}); + } + } + }, SESSION_SWEEP_MS); + sweeper.unref(); + + const shutdown = (): void => { + clearInterval(sweeper); + for (const [id, session] of sessions) { + sessions.delete(id); + void session.transport.close().catch(() => {}); + } + httpServer.close(() => process.exit(0)); + setTimeout(() => process.exit(0), 3000).unref(); + }; + process.on("SIGINT", shutdown); + process.on("SIGTERM", shutdown); + + return new Promise((resolve, reject) => { + httpServer.once("error", reject); + httpServer.listen(config.port, config.host, () => { + console.error( + `[jortt-mcp] listening on http://${config.host}:${config.port}${MCP_PATH}`, + ); + resolve(); + }); + }); +} diff --git a/src/index.ts b/src/index.ts index 2bd3387..9fe5ebb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,37 +7,26 @@ import { type Tool, } from "@modelcontextprotocol/sdk/types.js"; import { TokenManager } from "./auth.js"; +import { + IntrospectionVerifier, + JwtVerifier, + StaticTokenVerifier, + type BearerVerifier, +} from "./bearer.js"; import { JorttClient } from "./client.js"; +import { envFlag, envList, envOrFile, requireEnvOrFile } from "./config.js"; +import { startHttpServer } from "./http.js"; import { generateTools, type JorttTool } from "./openapi.js"; -function requireEnv(name: string): string { - const value = process.env[name]; - if (!value) { - console.error( - `[jortt-mcp] Missing required environment variable: ${name}\n` + - `Set JORTT_CLIENT_ID and JORTT_CLIENT_SECRET with an application registered at\n` + - `https://app.jortt.nl/new_profile/api/jortt/oauth_applications/list`, - ); - process.exit(1); - } - return value; -} - -async function main(): Promise { - const clientId = requireEnv("JORTT_CLIENT_ID"); - const clientSecret = requireEnv("JORTT_CLIENT_SECRET"); - const scopes = process.env.JORTT_SCOPES; // optional space-separated override - - const tokens = new TokenManager(clientId, clientSecret, scopes || undefined); - const client = new JorttClient(tokens); - - const jorttTools = await generateTools(); - const toolsByName = new Map( - jorttTools.map((t) => [t.name, t]), - ); +const VERSION = "0.2.0"; +function buildMcpServer( + jorttTools: JorttTool[], + toolsByName: Map, + client: JorttClient, +): Server { const server = new Server( - { name: "jortt-mcp", version: "0.1.0" }, + { name: "jortt-mcp", version: VERSION }, { capabilities: { tools: {} } }, ); @@ -85,11 +74,132 @@ async function main(): Promise { } }); - const transport = new StdioServerTransport(); - await server.connect(transport); - console.error( - `[jortt-mcp] ready — ${jorttTools.length} tools exposed over stdio`, + return server; +} + +function buildVerifier(resourceUrl: string | undefined): { + verifier: BearerVerifier; + issuer?: string; +} { + const mode = process.env.MCP_AUTH_MODE; + switch (mode) { + case "token": { + const raw = requireEnvOrFile( + "MCP_AUTH_TOKEN", + "MCP_AUTH_MODE=token requires MCP_AUTH_TOKEN or MCP_AUTH_TOKEN_FILE " + + "(one or more tokens, newline- or comma-separated).", + ); + const tokens = raw + .split(/[\n,]+/) + .map((t) => t.trim()) + .filter(Boolean); + return { verifier: new StaticTokenVerifier(tokens) }; + } + case "oidc": { + const issuer = requireEnvOrFile( + "MCP_OIDC_ISSUER", + "MCP_AUTH_MODE=oidc requires MCP_OIDC_ISSUER (the authorization server / OAuth proxy issuer URL).", + ).replace(/\/$/, ""); + const audience = process.env.MCP_OIDC_AUDIENCE || resourceUrl; + const policy = { allowedSubjects: envList("MCP_OIDC_ALLOWED_SUBJECTS") }; + + const introspectionUrl = process.env.MCP_OIDC_INTROSPECTION_URL; + const clientId = envOrFile("MCP_OIDC_CLIENT_ID"); + if (introspectionUrl || clientId) { + const clientSecret = requireEnvOrFile( + "MCP_OIDC_CLIENT_SECRET", + "Token introspection requires MCP_OIDC_CLIENT_ID and MCP_OIDC_CLIENT_SECRET " + + "(credentials of a client permitted to introspect tokens).", + ); + if (!clientId) { + console.error("[jortt-mcp] Token introspection requires MCP_OIDC_CLIENT_ID."); + process.exit(1); + } + return { + verifier: new IntrospectionVerifier( + issuer, + clientId, + clientSecret, + audience, + policy, + introspectionUrl, + ), + issuer, + }; + } + + if (!audience) { + console.error( + "[jortt-mcp] MCP_AUTH_MODE=oidc requires an audience to validate: " + + "set MCP_RESOURCE_URL (recommended) or MCP_OIDC_AUDIENCE.", + ); + process.exit(1); + } + return { verifier: new JwtVerifier(issuer, audience, policy), issuer }; + } + default: + console.error( + `[jortt-mcp] MCP_TRANSPORT=http requires MCP_AUTH_MODE to be "token" or "oidc" ` + + `(got: ${mode ?? "unset"}). Refusing to serve the Jortt API unauthenticated.`, + ); + process.exit(1); + } +} + +async function main(): Promise { + const clientId = requireEnvOrFile( + "JORTT_CLIENT_ID", + "Set JORTT_CLIENT_ID and JORTT_CLIENT_SECRET (or their *_FILE variants) with an\n" + + "application registered at https://app.jortt.nl/new_profile/api/jortt/oauth_applications/list", ); + const clientSecret = requireEnvOrFile( + "JORTT_CLIENT_SECRET", + "Set JORTT_CLIENT_SECRET or JORTT_CLIENT_SECRET_FILE.", + ); + const scopes = process.env.JORTT_SCOPES; // optional space-separated override + const readOnly = envFlag("JORTT_READ_ONLY"); + + const tokens = new TokenManager(clientId, clientSecret, scopes || undefined); + const client = new JorttClient(tokens); + + let jorttTools = await generateTools(); + if (readOnly) { + jorttTools = jorttTools.filter((t) => t.method === "get"); + } + const toolsByName = new Map( + jorttTools.map((t) => [t.name, t]), + ); + + const transport = process.env.MCP_TRANSPORT || "stdio"; + const toolSummary = `${jorttTools.length} tools${readOnly ? " (read-only)" : ""}`; + + if (transport === "stdio") { + const server = buildMcpServer(jorttTools, toolsByName, client); + await server.connect(new StdioServerTransport()); + console.error(`[jortt-mcp] ready — ${toolSummary} over stdio`); + return; + } + + if (transport !== "http") { + console.error( + `[jortt-mcp] Unknown MCP_TRANSPORT: ${transport} (expected "stdio" or "http")`, + ); + process.exit(1); + } + + const resourceUrl = process.env.MCP_RESOURCE_URL; + const { verifier, issuer } = buildVerifier(resourceUrl); + await startHttpServer({ + host: process.env.MCP_HOST || "127.0.0.1", + port: Number(process.env.MCP_PORT || 3910), + verifier, + resourceUrl, + issuer, + allowedHosts: envList("MCP_ALLOWED_HOSTS"), + allowedOrigins: envList("MCP_ALLOWED_ORIGINS"), + createMcpServer: () => buildMcpServer(jorttTools, toolsByName, client), + }); + console.error(`[jortt-mcp] ready — ${toolSummary} over streamable HTTP`); } main().catch((err) => { diff --git a/src/openapi.json b/src/openapi.json new file mode 100644 index 0000000..9f4aa3b --- /dev/null +++ b/src/openapi.json @@ -0,0 +1 @@ +{"info":{"title":"Jortt API","description":"# Introduction\n\nWelcome to the Jortt API. This API is meant for applications that want to connect to the\n[Jortt](https://www.jortt.nl) application.\n\nThis API is designed around the [REST principles](https://en.wikipedia.org/wiki/Representational_state_transfer).\n\n[OAuth 2.0](https://oauth.net/2) is used for authentication and authorization.\n\nThe Jortt API supports [Open API](https://en.wikipedia.org/wiki/OpenAPI_Specification) version 2.0 (formerly known as\nSwagger) for describing the API interface. Check [openapi.tools](https://openapi.tools) for handy tooling (such a\ngenerating client libraries).\n\nYou can also [download the API specification](https://api.jortt.nl/swagger_doc).\n\n## Test with Postman app\n\nThe **Run in Postman** button imports and opens the Jortt collection of API endpoints directly in your Postman app.\nReady to create your first customer and invoice via the API.\n\n[![Run in Postman](https://run.pstmn.io/button.svg)](https://app.getpostman.com/run-collection/9338c4f4e3adefafa561)\n\nTo get an Access token, **edit** the Jortt API collection, go to the **Authorization** tab and use the **Get New Access Token** button.\n\n# Connecting\n\nConnecting your application with the Jortt API requires you to register your application as a client in our\nauthorization server. We support the following\nOAuth 2.0 grant types:\n\n- [Authorization code](#authorization-code-grant-type) (for third party apps, for example a webshop)\n- [Client credentials](#client-credentials-grant-type) (for your own app)\n\nAfter successful registration you will receive the necessary credentials (client ID and secret) to initiate the\nOAuth 2.0 flow to gain access.\n\nWhen retrieving the initial `access_token` you need to pass the `client_id` and `client_secret` in the\n`Authorization` header.\n\n## Authorization code grant type\n\nThe `authorization_code` grant type should be used by third party applications (for example a webshop) that can be used\nby any Jortt administration. If you are developing an application just for your own administration, you must use the\n[client credentials](#client-credentials-grant-type) grant type.\n\nThe following links describe the `authorization_code` grant type in detail:\n\n- [OAuth.net](https://oauth.net/2/grant-types/authorization-code/)\n- [Article from Digital Ocean](https://www.digitalocean.com/community/tutorials/an-introduction-to-oauth-2#grant-type-authorization-code)\n- [Official RFC](https://tools.ietf.org/html/rfc6749#section-4.1)\n\nFor the `authorization_code` grant type, you don't necessarily need access to a Jortt account (although it may be handy\nfor testing). You can always sign up for a free Jortt account.\n\nIn order to use the `authorization_code` grant type, please send an e-mail to\nsupport@jortt.nl to register your application and provide the following\ninformation:\n\n- Application name\n- Redirect URL\n- Required [scopes](#scopes)\n\n### Getting a Jortt adminstration's (read: user's) consent\n\nYour application first needs to decide which permissions it is requesting, then send the user to a browser to get their\npermission. To begin the authorization flow, the application constructs a URL like the following and open a browser\nto that URL.\n\n\n\n\n\n\n\n\n\n\n```bash\ncurl https://app.jortt.nl/oauth-provider/oauth/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT_URI&response_type=code&scope=YOUR_SCOPES&state=RANDOM_STRING\n```\n\nWhen the user visits this URL, it will present them with a prompt asking if they would like to authorize your\napplication's request.\n\n\n\nIf the user approves the request, Jortt will redirect the browser back to the `redirect_uri` specified by your\napplication, adding a `code` to the query string along with the provided `state` parameter.\n\n\n\n\n\n### Obtaining an access token\n\n\n\n\n\n\n\nFor example:\n\n```bash\ncurl -X POST -u \"YOUR_CLIENT_ID:YOUR_CLIENT_SECRET\" -d \"grant_type=authorization_code&code=YOUR_AUTHORIZATION_CODE&redirect_uri=YOUR_REDIRECT_URI\" https://app.jortt.nl/oauth-provider/oauth/token\n```\n\nWould respond with:\n\n```json\n{\n \"access_token\": \"YOUR_ACCESS_TOKEN\",\n \"token_type\": \"Bearer\",\n \"expires_in\": 7200,\n \"refresh_token\": \"YOUR_REFRESH_TOKEN\",\n \"scope\": \"YOUR_SCOPES\",\n \"created_at\": 1587717832\n}\n```\n\n### Refreshing a token\n\n\n\n\n\nFor example:\n\n```bash\ncurl -X POST -u \"YOUR_CLIENT_ID:YOUR_CLIENT_SECRET\" -d \"grant_type=refresh_token&refresh_token=YOUR_REFRESH_TOKEN\" https://app.jortt.nl/oauth-provider/oauth/token\n```\n\nWould respond with:\n\n```json\n{\n \"access_token\": \"YOUR_NEW_ACCESS_TOKEN\",\n \"token_type\": \"Bearer\",\n \"expires_in\": 7200,\n \"refresh_token\": \"YOUR_NEW_REFRESH_TOKEN\",\n \"scope\": \"YOUR_SCOPES\",\n \"created_at\": 1587717832\n}\n```\n\n## Client credentials grant type\n\nThe `client_credentials` grant type should be used by applications that you've built solely for your own administration\n(the application is bound to the administration that created it). If you are developing an application that should be\naccessible for all Jortt administrations (for example a webshop), you must use the\n[authorization code](#authorization-code-grant-type) grant type.\n\nThe following links describe the `client_credentials` grant type in detail:\n\n- [OAuth.net](https://oauth.net/2/grant-types/client-credentials/)\n- [Article from Digital Ocean](https://www.digitalocean.com/community/tutorials/an-introduction-to-oauth-2#grant-type-client-credentials)\n- [Official RFC](https://tools.ietf.org/html/rfc6749#section-4.4)\n\nYou will need to have access to a Jortt account, and\nregister your application.\n\n### Obtaining an access token\n\n\n\n\n\n\n\nFor example:\n\n```bash\ncurl -X POST -u \"YOUR_CLIENT_ID:YOUR_CLIENT_SECRET\" -d \"grant_type=client_credentials&scope=YOUR_SCOPES\" https://app.jortt.nl/oauth-provider/oauth/token\n```\n\nWould respond with:\n\n```json\n{\n \"access_token\": \"YOUR_ACCESS_TOKEN\",\n \"token_type\": \"Bearer\",\n \"expires_in\": 7200,\n \"scope\": \"YOUR_SCOPES\",\n \"created_at\": 1588331418\n}\n```\n\n# Making requests\n\nWhenever you make an HTTP request to the Jortt API, the response will contain a\n[JSON](https://json.org/) object with (at least) either a `data` key (a success response) or\nan `error` key (an error response).\n\nPlease check the documentation of endpoints for the details of responses.\n\nThe `access_token` must be provided as an HTTP header: `Authorization: Bearer YOUR_ACCESS_TOKEN`.\n\n## Success response\n\nAn HTTP response with status code `200 OK` or `201 Created` indicates the request was successful.\n\n### Request example\n\n```bash\ncurl -X GET https://api.jortt.nl/customers/c4075eb6-2028-457e-817f-a6a8d4703fbb -H \"Authorization: Bearer czZCaGRSa3F0MzpnWDFmQmF0M2JW\"\n```\n\n### Response example\n\n```http\nHTTP/1.1 200 OK\nContent-Type: application/json\n\n{\n \"data\": [\n {\n \"id\": \"c4075eb6-2028-457e-817f-a6a8d4703fbb\",\n ... more properties\n }\n ]\n}\n```\n\n## Error response\n\nAn HTTP response with status code `4xx` or `5xx` indicates there was an error while processing the request.\nThe response body will contain extra information about the [error](#tocSerror).\n\n### Request example\n\n```bash\ncurl -X POST https://api.jortt.nl/customers -H \"Authorization: Bearer czZCaGRSa3F0MzpnWDFmQmF0M2JW\"\n```\n\n### Response example\n\n```http\nHTTP/1.1 422 Unprocessable Entity\nContent-Type: application/json\n\n{\n \"error\": {\n \"code\": 422,\n \"key\": \"invalid_params\",\n \"message\": \"The parameters are invalid (either missing, not of the correct type or incorrect format).\",\n \"details\": [\n {\n \"param\": \"customer_id\",\n \"key\": \"is_missing\",\n \"message\": \"is missing\"\n }\n ]\n }\n}\n```\n\n## Errors\n\nThe following table describes the [errors](#tocSerror) found in responses.\n\n| Code | Key | Description |\n| ----- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |\n| `401` | `access_token.invalid` | The `access_token` is either missing or invalid. |\n| `401` | `access_token.expired` | The `access_token` has expired. Use `refresh_token` to get a new `access_token`. |\n| `401` | `access_token.revoked` | The `access_token` has been revoked. |\n| `401` | `scopes.insufficient` | Insufficient permissions (read: missing scopes) to access resource. Your [application](#connecting) probably needs more [scopes](#scopes). |\n| `401` | `organization.non_existing` | The corresponding organization for your access token does not exist. |\n| `401` | `organization.requires_mkb_plan` | The corresponding organization for your access token does not have a jortt MKB or jortt Plus plan. |\n| `401` | `user.non_existing` | The corresponding user for your access token does not exist. |\n| `401` | `user.invalid_credentials` | The corresponding user for your credentials does not exist. |\n| `401` | `two_factor_code.invalid` | Invalid code supplied. |\n| `401` | `two_factor_code.missing_secret` | Two-step verification code |\n| `404` | `endpoint.not_found` | Invalid or non existing endpoint. |\n| `404` | `resource.not_found` | Requested resource cannot be found. |\n| `405` | `resource.method_not_allowed` | The method is not allowed on this resource. |\n| `409` | `resource.conflict` | A conflict occurred while processing the request. This can happen when two processes are trying to modify the same resource simultaneously. |\n| `422` | `params.invalid` | The parameters are invalid (either missing, not of the correct type or incorrect format). |\n| `422` | `params.invalid_format` | The parameters could not be parsed (as specified by the Content-Type header). Please either specify the correct Content-Type or fix the parameters formatting. |\n| `422` | `params.invalid_encoding` | The parameters contain invalid UTF-8 characters and could not be parsed. |\n| `422` | `operation.invalid` | The operation could not be executed on the resource. |\n| `422` | `operation.year_closed` | The operation could not be executed because it affects a closed financial year: %{closed_year}. To execute the operation, please open the financial year in your jortt administration first. |\n| `429` | `request.throttled` | The request has been throttled, please try again later. |\n| `500` | `server.internal_error` | Internal server error. Sorry, we screwed up :-( Please try again later. |\n| `503` | `server.maintenance` | API is temporarily offline for maintenance. Please try again later. |\n| `503` | `integration.outage` | An integration has an outage. Please try again later. |\n\n\n# Rate limiting\nIn order to ensure fair performance of our API we have rate limits in place. This limit is set to `10` requests per second.\n\n# Pagination\nFetching all objects of a resource can be convenient. At the same time, returning too many objects at once can be unpractical from a performance perspective.\n\nDoing so might be too much work for the Jortt API to generate, or for your system to process.\n\nFor this reason in the case of a request that should return a list of objects, the Jortt API only returns a subset of the requested set of objects. In other words, the Jortt API chops the result of a certain API method call into pages you are able to programmatically scroll through.\n\nThe maximum number of objects returned per page is 100.\n\nIn those cases, a successful response body will contain a `data` key and a `_links` key with extra information to be able to paginate. [links](#tocSlink)\n\nFor example:\n\n```bash\ncurl -X GET https://api.jortt.nl/customers?query=bob -H \"Authorization: Bearer czZCaGRSa3F0MzpnWDFmQmF0M2JW\"\n```\n\nWould respond with:\n\n```http\nHTTP/1.1 200 OK\nContent-Type: application/json\n\n{\n \"data\": [\n {\n \"id\": \"c4075eb6-2028-457e-817f-a6a8d4703fbb\",\n ... more properties\n }\n {\n \"id\": \"d453034d-6a33-44b3-8d5f-f962beeeb956\",\n ... more properties\n }\n ... more objects\n ]\n \"_links\": {\n \"self\": {\n \"href\": \"https://api.jortt.nl/customers?query=bob?page=1\",\n \"type\": \"application/json\"\n },\n \"previous\": null,\n \"next\": {\n \"href\": \"https://api.jortt.nl/customers?query=bob?page=2\",\n \"type\": \"application/json\"\n },\n \"documentation\": {\n \"href\": \"https://developer.jortt.nl/#pagination\",\n \"type\": \"application/json\"\n },\n }\n}\n```\n","termsOfService":"https://www.jortt.nl/over-ons/AlgemeneVoorwaardenJortt.pdf","contact":{"email":"support@jortt.nl"},"version":"1.0.0","x-logo":{"url":"https://app.jortt.nl/img/logo.svg"}},"swagger":"2.0","produces":["application/json"],"securityDefinitions":{"OAuth2 Authorization Code":{"type":"oauth2","flow":"accessCode","authorizationUrl":"https://app.jortt.nl/oauth-provider/oauth/authorize","tokenUrl":"https://app.jortt.nl/oauth-provider/oauth/token","scopes":{"customers:read":"Read customers","customers:write":"Create and update customers","estimates:read":"Read estimates","estimates:write":"Create, update and send estimates","expenses:read":"Read expenses","expenses:write":"Create and update expenses","financing:read":"Read bank accounts and transactions","inbox:write":"Upload and delete receipts","invoices:read":"Read invoices","invoices:write":"Create, update and send invoices","organizations:read":"Read organization data and settings","organizations:write":"Update organization data and settings","payroll:read":"Read payroll data","payroll:write":"Create and update payroll data","reports:read":"Read reports data"}},"OAuth2 Client Credentials":{"type":"oauth2","flow":"application","tokenUrl":"https://app.jortt.nl/oauth-provider/oauth/token","scopes":{"customers:read":"Read customers","customers:write":"Create and update customers","estimates:read":"Read estimates","estimates:write":"Create, update and send estimates","expenses:read":"Read expenses","expenses:write":"Create and update expenses","financing:read":"Read bank accounts and transactions","inbox:write":"Upload and delete receipts","invoices:read":"Read invoices","invoices:write":"Create, update and send invoices","organizations:read":"Read organization data and settings","organizations:write":"Update organization data and settings","payroll:read":"Read payroll data","payroll:write":"Create and update payroll data","reports:read":"Read reports data"}}},"host":"api.jortt.nl","schemes":["https"],"tags":[{"name":"v2","description":"Operations about v2s"},{"name":"estimates","description":"Operations about estimates"},{"name":"inbox","description":"Operations about inboxes"},{"name":"files","description":"Operations about files"},{"name":"customers","description":"Customers"},{"name":"tradenames","description":"Tradenames"},{"name":"labels","description":"Labels"},{"name":"invoices","description":"Invoices"},{"name":"expenses","description":"Expenses"},{"name":"bank-accounts","description":"Banking"},{"name":"ledger_accounts","description":"Ledger Accounts"},{"name":"loonjournaalposten","description":"Loonjournaalposten"},{"name":"projects","description":"Projects"},{"name":"organizations","description":"Organizations"},{"name":"reports","description":"Reports"}],"paths":{"/v3/invoices":{"post":{"summary":"Creates (and optionally sends) an Invoice V3 with invoice-level VAT","description":"When the optional `send_method` is not provided, the Invoice will be created as a Draft.\n\nWhen the optional `send_method` parameter is provided, the Invoice will also be scheduled for sending.\n\nBy polling the [`GET /invoices/{id}`](#get-invoice-by-id) endpoint you can check if the Invoice has\nbeen sent (the returned Invoice's `invoice_status` attribute is then set to `sent` otherwise it will\nbe `draft`).\n\n**V3 Differences**:\n- This endpoint creates invoices with `vat_calculation_method` set to `invoice_level_vat`.\n- Supports all send methods including `peppol`, `email`, and `self`.\n- Note: `peppol` send method is only available in v3. Use v3 if you need to send invoices via peppol.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"name":"CreateInvoiceV3","in":"body","required":true,"schema":{"$ref":"#/definitions/CreateInvoiceV3"}}],"security":[{"OAuth2":["invoices:write"]}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_ResourceCreatedResponse"}}},"tags":["invoices"],"operationId":"Create (and optionally send) an Invoice V3"}},"/v2/invoices/peppol-scheme-catalog":{"get":{"description":"Returns valid peppol scheme options by country code","produces":["application/json"],"security":[{"OAuth2":["invoices:read"]}],"responses":{"200":{"description":"Peppol scheme catalog","schema":{"$ref":"#/definitions/Jortt_V2_Invoicing_Invoices_Responses_GetPeppolSchemeCatalogResponse"}}},"tags":["invoices"],"operationId":"Get peppol scheme catalog"}},"/v2/invoices":{"get":{"summary":"Returns a list of Invoices","description":"If the `query` is null (e.g `GET /invoices`), it will retrieve all Invoices ordered by `created_at`.\nOtherwise if a `query` is passed (e.g `GET /invoices?query=foo`), it will search and list only the Invoices\nmatching the query, ordered by `created_at`.\nIf the `invoice_status` is null (e.g `GET /invoices`), it will retrieve all Invoices ordered by `created_at`.\nOtherwise if a `invoice_status` is passed (e.g `GET /invoices?invoice_status=draft`), it will search and list only the Invoices\nmatching the invoice_status with status draft, ordered by `created_at`.\n","produces":["application/json"],"parameters":[{"in":"query","name":"query","description":"Search query (at least 3 characters)","type":"string","required":false},{"in":"query","name":"invoice_status","description":"invoice_status options [sent draft unpaid late paid]","type":"string","required":false},{"in":"query","name":"page","description":"Page of the response (minimum 1)","type":"integer","format":"int32","required":false}],"security":[{"OAuth2":["invoices:read"]}],"responses":{"200":{"description":"Invoices","schema":{"$ref":"#/definitions/Jortt_V2_Invoicing_Invoices_Responses_ListInvoicesResponse"}}},"tags":["v2"],"operationId":"List Invoices v2"},"post":{"summary":"Creates (and optionally sends) an Invoice V2","description":"**DEPRECATED**: This endpoint is deprecated. Please use the V3 API for new integrations.\n\nWhen the optional `send_method` is not provided, the Invoice will be created as a Draft.\n\nWhen the optional `send_method` parameter is provided, the Invoice will also be scheduled for sending.\n\nBy polling the [`GET /invoices/{id}`](#get-invoice-by-id) endpoint you can check if the Invoice has\nbeen sent (the returned Invoice's `invoice_status` attribute is then set to `sent` otherwise it will\nbe `draft`).\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"name":"CreateInvoiceV2","in":"body","required":true,"schema":{"$ref":"#/definitions/CreateInvoiceV2"}}],"security":[{"OAuth2":["invoices:write"]}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_ResourceCreatedResponse"}}},"tags":["invoices"],"operationId":"Create (and optionally send) an Invoice V2"}},"/v2/invoices/{id}":{"get":{"description":"Returns an Invoice by ID","produces":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID)","type":"string","required":true}],"security":[{"OAuth2":["invoices:read"]}],"responses":{"200":{"description":"Invoice","schema":{"$ref":"#/definitions/Jortt_V2_Invoicing_Invoices_Responses_GetInvoiceResponse"}}},"tags":["v2"],"operationId":"Get Invoice by ID v2"},"put":{"summary":"Edits an Invoice V2","description":"Edits an invoice, provided said invoice has not been finalized.\nNOTE: You may receive unexpected errors if any data on the invoice is invalid. When correcting\ninvalid data only the relevant fields should be filled.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID)","type":"string","required":true},{"name":"EditInvoiceV2","in":"body","required":true,"schema":{"$ref":"#/definitions/EditInvoiceV2"}}],"security":[{"OAuth2":["invoices:write"]}],"responses":{"200":{"description":"No Content"}},"tags":["invoices"],"operationId":"Edit Invoice V2"}},"/v2/invoices/{id}/line_item_suggestions":{"get":{"summary":"Returns a list of suggested line items","description":"Returns a list of suggested line items for a given invoice based on the given query.\nThe invoice must have an associated customer or 404 is returned.\n","produces":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID)","type":"string","required":true},{"in":"query","name":"query","description":"Query string to search for suggestions with","type":"string","required":true}],"security":[{"OAuth2":["invoices:read"]}],"responses":{"200":{"description":"Invoice Line Item Suggestions","schema":{"$ref":"#/definitions/Jortt_V2_Invoicing_Invoices_Responses_GetInvoiceLineItemSuggestionsResponse"}}},"tags":["invoices"],"operationId":"Get line item suggestions"}},"/v1/invoices/{id}":{"get":{"description":"Returns an Invoice by ID","produces":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID)","type":"string","required":true}],"security":[{"OAuth2":["invoices:read"]}],"responses":{"200":{"description":"Invoice","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_GetInvoiceResponse"}}},"tags":["invoices"],"operationId":"Get Invoice by ID"},"put":{"summary":"Edits an Invoice","description":"Edits an invoice, provided said invoice has not been finalized.\nNOTE: This operation will overwrite existing invoice data for all fields, fields that are not supplied will be nulled.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID)","type":"string","required":true},{"name":"EditInvoice","in":"body","required":true,"schema":{"$ref":"#/definitions/EditInvoice"}}],"security":[{"OAuth2":["invoices:write"]}],"responses":{"200":{"description":"No Content"}},"tags":["invoices"],"operationId":"Edit Invoice"},"delete":{"description":"Deletes an Invoice by ID","produces":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID)","type":"string","required":true}],"security":[{"OAuth2":["invoices:write"]}],"responses":{"204":{"description":"No Content"}},"tags":["invoices"],"operationId":"Delete Invoice by ID"}},"/v1/invoices/{id}/set_labels":{"put":{"summary":"Sets the labels for a given invoice","description":"Sets the labels for a given invoice\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"id","type":"integer","format":"int32","required":true},{"name":"SetLabels","in":"body","required":true,"schema":{"$ref":"#/definitions/SetLabels"}}],"security":[{"OAuth2":["invoices:write"]}],"responses":{"204":{"description":"No Content"}},"tags":["invoices"],"operationId":"Set invoice labels"}},"/v1/invoices/{id}/credit":{"post":{"summary":"Creates (and optionally sends) a credit Invoice","description":"When the optional `send_method` is not provided, the Invoice will be created as a Draft.\n\nWhen the optional `send_method` parameter is provided, the Invoice will also be scheduled for sending.\n\nBy polling the [`GET /invoices/{id}`](#get-invoice-by-id) endpoint you can check if the Invoice been sent (the returned Invoice's `invoice_status` attribute is then set to `sent` otherwise it will be `draft`).\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID) of the invoice you want to credit","type":"string","required":true},{"name":"CreateCreditInvoice","in":"body","required":true,"schema":{"$ref":"#/definitions/CreateCreditInvoice"}}],"security":[{"OAuth2":["invoices:write"]}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_ResourceCreatedResponse"}}},"tags":["invoices"],"operationId":"Creates (and optionally sends) a credit Invoice"}},"/v1/invoices":{"get":{"summary":"Returns a list of Invoices","description":"\nIf the `query` is null (e.g `GET /invoices`), it will retrieve all Invoices ordered by `created_at`.\n\nOtherwise if a `query` is passed (e.g `GET /invoices?query=foo`), it will search and list only the Invoices\nmatching the query, ordered by `created_at`.\n\n\nIf the `invoice_status` is null (e.g `GET /invoices`), it will retrieve all Invoices ordered by `created_at`.\n\nOtherwise if a `invoice_status` is passed (e.g `GET /invoices?invoice_status=draft`), it will search and list only the Invoices\nmatching the invoice_status with status draft, ordered by `created_at`.\n","produces":["application/json"],"parameters":[{"in":"query","name":"query","description":"Search query (at least 3 characters)","type":"string","required":false},{"in":"query","name":"invoice_status","description":"invoice_status options [sent draft unpaid late paid]","type":"string","required":false},{"in":"query","name":"page","description":"Page of the response (minimum 1)","type":"integer","format":"int32","required":false}],"security":[{"OAuth2":["invoices:read"]}],"responses":{"200":{"description":"Invoices","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_ListInvoicesResponse"}}},"tags":["invoices"],"operationId":"List Invoices"},"post":{"summary":"Creates (and optionally sends) an Invoice","description":"**DEPRECATED**: This endpoint is deprecated. Please use the V3 API for new integrations.\n\nWhen the optional `send_method` is not provided, the Invoice will be created as a Draft.\n\nWhen the optional `send_method` parameter is provided, the Invoice will also be scheduled for sending.\n\nBy polling the [`GET /invoices/{id}`](#get-invoice-by-id) endpoint you can check if the Invoice has\nbeen sent (the returned Invoice's `invoice_status` attribute is then set to `sent` otherwise it will\nbe `draft`).\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"name":"CreateInvoice","in":"body","required":true,"schema":{"$ref":"#/definitions/CreateInvoice"}}],"security":[{"OAuth2":["invoices:write"]}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_ResourceCreatedResponse"}}},"tags":["invoices"],"operationId":"Create (and optionally send) an Invoice"}},"/v1/invoices/{id}/download":{"get":{"summary":"Returns a URL from which the invoice PDF can be downloaded.","description":"Only returns a URL for sent invoices. Will fail otherwise.\n\nNote: The returned URL expires after 10 minutes.\n","produces":["application/pdf"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID)","type":"string","required":true}],"security":[{"OAuth2":["invoices:read"]}],"responses":{"200":{"description":"URL to download invoice PDF created","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_DownloadInvoiceOrEstimatePdfResponse"}}},"tags":["invoices"],"operationId":"Download Invoice PDF"}},"/v1/invoices/{id}/next_possible_invoice_number":{"get":{"description":"Returns the next possible invoice number","produces":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID)","type":"string","required":true}],"security":[{"OAuth2":["invoices:read"]}],"responses":{"200":{"description":"Next possible invoice number","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_NextPossibleInvoiceNumberResponse"}}},"tags":["invoices"],"operationId":"Get next possible invoice number"}},"/v1/invoices/{id}/line_item_suggestions":{"get":{"summary":"Returns a list of suggested line items","description":"Returns a list of suggested line items for a given invoice based on the given query.\nThe invoice must have an associated customer or 404 is returned.\n","produces":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID)","type":"string","required":true},{"in":"query","name":"query","description":"Query string to search for suggestions with","type":"string","required":true}],"security":[{"OAuth2":["invoices:read"]}],"responses":{"200":{"description":"Invoice Line Item Suggestions","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_GetInvoiceLineItemSuggestionsResponse"}}},"tags":["invoices"],"operationId":"Get line item suggestions"}},"/v1/invoices/{id}/send":{"post":{"summary":"Sends an Invoice","description":" Sends an invoice by the method indicated in the params.\nIf email is selected additional parameters must be provided as indicated.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID)","type":"string","required":true},{"name":"SendInvoice","in":"body","required":true,"schema":{"$ref":"#/definitions/SendInvoice"}}],"security":[{"OAuth2":["invoices:write"]}],"responses":{"200":{"description":"No Content"}},"tags":["invoices"],"operationId":"Send Invoice"}},"/v1/invoices/{id}/send_settings":{"get":{"description":"Returns the current send settings for the invoice","produces":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID)","type":"string","required":true}],"security":[{"OAuth2":["invoices:read"]}],"responses":{"200":{"description":"Invoice","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_SendSettingsInvoiceResponse"}}},"tags":["invoices"],"operationId":"Get send settings"}},"/v1/invoices/{id}/copy":{"post":{"summary":"Copies an Invoice","description":"Creates an unsent copy of a sent invoice.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID)","type":"string","required":true}],"security":[{"OAuth2":["invoices:write"]}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_ResourceCreatedResponse"}}},"tags":["invoices"],"operationId":"Copy an Invoice"}},"/invoices/{id}":{"get":{"description":"Returns an Invoice by ID","produces":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID)","type":"string","required":true}],"security":[{"OAuth2":["invoices:read"]}],"responses":{"200":{"description":"Invoice","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_GetInvoiceResponse"}}},"tags":["invoices"],"operationId":"Get Invoice by ID"},"put":{"summary":"Edits an Invoice","description":"Edits an invoice, provided said invoice has not been finalized.\nNOTE: This operation will overwrite existing invoice data for all fields, fields that are not supplied will be nulled.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID)","type":"string","required":true},{"name":"EditInvoice","in":"body","required":true,"schema":{"$ref":"#/definitions/EditInvoice"}}],"security":[{"OAuth2":["invoices:write"]}],"responses":{"200":{"description":"No Content"}},"tags":["invoices"],"operationId":"Edit Invoice"},"delete":{"description":"Deletes an Invoice by ID","produces":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID)","type":"string","required":true}],"security":[{"OAuth2":["invoices:write"]}],"responses":{"204":{"description":"No Content"}},"tags":["invoices"],"operationId":"Delete Invoice by ID"}},"/invoices/{id}/set_labels":{"put":{"summary":"Sets the labels for a given invoice","description":"Sets the labels for a given invoice\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"id","type":"integer","format":"int32","required":true},{"name":"SetLabels","in":"body","required":true,"schema":{"$ref":"#/definitions/SetLabels"}}],"security":[{"OAuth2":["invoices:write"]}],"responses":{"204":{"description":"No Content"}},"tags":["invoices"],"operationId":"Set invoice labels"}},"/invoices/{id}/credit":{"post":{"summary":"Creates (and optionally sends) a credit Invoice","description":"When the optional `send_method` is not provided, the Invoice will be created as a Draft.\n\nWhen the optional `send_method` parameter is provided, the Invoice will also be scheduled for sending.\n\nBy polling the [`GET /invoices/{id}`](#get-invoice-by-id) endpoint you can check if the Invoice been sent (the returned Invoice's `invoice_status` attribute is then set to `sent` otherwise it will be `draft`).\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID) of the invoice you want to credit","type":"string","required":true},{"name":"CreateCreditInvoice","in":"body","required":true,"schema":{"$ref":"#/definitions/CreateCreditInvoice"}}],"security":[{"OAuth2":["invoices:write"]}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_ResourceCreatedResponse"}}},"tags":["invoices"],"operationId":"Creates (and optionally sends) a credit Invoice"}},"/invoices":{"get":{"summary":"Returns a list of Invoices","description":"\nIf the `query` is null (e.g `GET /invoices`), it will retrieve all Invoices ordered by `created_at`.\n\nOtherwise if a `query` is passed (e.g `GET /invoices?query=foo`), it will search and list only the Invoices\nmatching the query, ordered by `created_at`.\n\n\nIf the `invoice_status` is null (e.g `GET /invoices`), it will retrieve all Invoices ordered by `created_at`.\n\nOtherwise if a `invoice_status` is passed (e.g `GET /invoices?invoice_status=draft`), it will search and list only the Invoices\nmatching the invoice_status with status draft, ordered by `created_at`.\n","produces":["application/json"],"parameters":[{"in":"query","name":"query","description":"Search query (at least 3 characters)","type":"string","required":false},{"in":"query","name":"invoice_status","description":"invoice_status options [sent draft unpaid late paid]","type":"string","required":false},{"in":"query","name":"page","description":"Page of the response (minimum 1)","type":"integer","format":"int32","required":false}],"security":[{"OAuth2":["invoices:read"]}],"responses":{"200":{"description":"Invoices","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_ListInvoicesResponse"}}},"tags":["invoices"],"operationId":"List Invoices"},"post":{"summary":"Creates (and optionally sends) an Invoice","description":"**DEPRECATED**: This endpoint is deprecated. Please use the V3 API for new integrations.\n\nWhen the optional `send_method` is not provided, the Invoice will be created as a Draft.\n\nWhen the optional `send_method` parameter is provided, the Invoice will also be scheduled for sending.\n\nBy polling the [`GET /invoices/{id}`](#get-invoice-by-id) endpoint you can check if the Invoice has\nbeen sent (the returned Invoice's `invoice_status` attribute is then set to `sent` otherwise it will\nbe `draft`).\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"name":"CreateInvoice","in":"body","required":true,"schema":{"$ref":"#/definitions/CreateInvoice"}}],"security":[{"OAuth2":["invoices:write"]}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_ResourceCreatedResponse"}}},"tags":["invoices"],"operationId":"Create (and optionally send) an Invoice"}},"/invoices/{id}/download":{"get":{"summary":"Returns a URL from which the invoice PDF can be downloaded.","description":"Only returns a URL for sent invoices. Will fail otherwise.\n\nNote: The returned URL expires after 10 minutes.\n","produces":["application/pdf"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID)","type":"string","required":true}],"security":[{"OAuth2":["invoices:read"]}],"responses":{"200":{"description":"URL to download invoice PDF created","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_DownloadInvoiceOrEstimatePdfResponse"}}},"tags":["invoices"],"operationId":"Download Invoice PDF"}},"/invoices/{id}/next_possible_invoice_number":{"get":{"description":"Returns the next possible invoice number","produces":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID)","type":"string","required":true}],"security":[{"OAuth2":["invoices:read"]}],"responses":{"200":{"description":"Next possible invoice number","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_NextPossibleInvoiceNumberResponse"}}},"tags":["invoices"],"operationId":"Get next possible invoice number"}},"/invoices/{id}/line_item_suggestions":{"get":{"summary":"Returns a list of suggested line items","description":"Returns a list of suggested line items for a given invoice based on the given query.\nThe invoice must have an associated customer or 404 is returned.\n","produces":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID)","type":"string","required":true},{"in":"query","name":"query","description":"Query string to search for suggestions with","type":"string","required":true}],"security":[{"OAuth2":["invoices:read"]}],"responses":{"200":{"description":"Invoice Line Item Suggestions","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_GetInvoiceLineItemSuggestionsResponse"}}},"tags":["invoices"],"operationId":"Get line item suggestions"}},"/invoices/{id}/send":{"post":{"summary":"Sends an Invoice","description":" Sends an invoice by the method indicated in the params.\nIf email is selected additional parameters must be provided as indicated.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID)","type":"string","required":true},{"name":"SendInvoice","in":"body","required":true,"schema":{"$ref":"#/definitions/SendInvoice"}}],"security":[{"OAuth2":["invoices:write"]}],"responses":{"200":{"description":"No Content"}},"tags":["invoices"],"operationId":"Send Invoice"}},"/invoices/{id}/send_settings":{"get":{"description":"Returns the current send settings for the invoice","produces":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID)","type":"string","required":true}],"security":[{"OAuth2":["invoices:read"]}],"responses":{"200":{"description":"Invoice","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_SendSettingsInvoiceResponse"}}},"tags":["invoices"],"operationId":"Get send settings"}},"/invoices/{id}/copy":{"post":{"summary":"Copies an Invoice","description":"Creates an unsent copy of a sent invoice.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID)","type":"string","required":true}],"security":[{"OAuth2":["invoices:write"]}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_ResourceCreatedResponse"}}},"tags":["invoices"],"operationId":"Copy an Invoice"}},"/v3/expenses":{"get":{"summary":"List Expenses","description":"Returns a paginated list of Expenses for the current organization.\nResults are ordered by expense number by default.\n\nUse `vat_date_from` and `vat_date_till` to filter by VAT date range.\nUse `delivery_date_from` and `delivery_date_till` to filter by delivery period.\nUse `expense_type` to filter by type (cost, income, or balance).\n","produces":["application/json"],"parameters":[{"in":"query","name":"page","description":"Page number (starting at 1)","type":"integer","format":"int32","required":false},{"in":"query","name":"vat_date_from","description":"Filter expenses with vat_date on or after this date","type":"string","format":"date","required":false},{"in":"query","name":"vat_date_till","description":"Filter expenses with vat_date on or before this date","type":"string","format":"date","required":false},{"in":"query","name":"delivery_date_from","description":"Filter expenses with delivery_period on or after this date","type":"string","format":"date","required":false},{"in":"query","name":"delivery_date_till","description":"Filter expenses with delivery_period on or before this date","type":"string","format":"date","required":false},{"in":"query","name":"expense_type","description":"Filter by expense type","type":"string","enum":["cost","income","balance"],"required":false}],"security":[{"OAuth2":["expenses:read"]}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/Jortt_V3_Invoicing_Expenses_Responses_ListExpensesResponse"}}},"tags":["expenses"],"operationId":"List Expenses"},"post":{"summary":"Create an Expense","description":"Creates a new Expense for the current organization.\n\nThe `expense_type` field accepts `cost`, `income`, or `balance`.\nNote: `bedrijfsmiddel` type is not supported via this API.\n\nThe `delivery_period` is required for `cost` and `income` expense types.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"name":"CreateExpense","in":"body","required":true,"schema":{"$ref":"#/definitions/CreateExpense"}}],"security":[{"OAuth2":["expenses:write"]}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_ResourceCreatedResponse"}}},"tags":["expenses"],"operationId":"Create an Expense"}},"/v3/expenses/id/{id}":{"get":{"summary":"Get Expense by ID","description":"Returns a single Expense for the given `id`.","produces":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID)","type":"string","required":true}],"security":[{"OAuth2":["expenses:read"]}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/Jortt_V3_Invoicing_Expenses_Responses_GetExpenseResponse"}},"404":{"description":"Not Found"}},"tags":["expenses"],"operationId":"Get Expense by ID"},"post":{"summary":"Update an Expense","description":"Updates an existing Expense with new expense data.","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID)","type":"string","required":true},{"name":"UpdateExpense","in":"body","required":true,"schema":{"$ref":"#/definitions/UpdateExpense"}}],"security":[{"OAuth2":["expenses:write"]}],"responses":{"200":{"description":"OK"},"404":{"description":"Not Found"}},"tags":["expenses"],"operationId":"Update an Expense"}},"/v3/expenses/id/{id}/receipt":{"post":{"summary":"Attach a Receipt to an Expense","description":"Attaches an existing Receipt to an Expense. Fails if the receipt is already attached to another expense.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"id","description":"Expense identifier (UUID)","type":"string","required":true},{"name":"AttachReceiptToExpense","in":"body","required":true,"schema":{"$ref":"#/definitions/AttachReceiptToExpense"}}],"security":[{"OAuth2":["expenses:write"]}],"responses":{"200":{"description":"OK"},"404":{"description":"Not Found"},"422":{"description":"Unprocessable Entity"}},"tags":["expenses"],"operationId":"Attach Receipt to Expense"}},"/v3/ledger_accounts/expenses/cost":{"get":{"produces":["application/json"],"responses":{"200":{"description":"get Cost(s)"}},"tags":["ledger_accounts"],"operationId":"getV3LedgerAccountsExpensesCost"}},"/v3/ledger_accounts/expenses/income":{"get":{"produces":["application/json"],"responses":{"200":{"description":"get Income(s)"}},"tags":["ledger_accounts"],"operationId":"getV3LedgerAccountsExpensesIncome"}},"/v3/ledger_accounts/expenses/balance":{"get":{"produces":["application/json"],"responses":{"200":{"description":"get Balance(s)"}},"tags":["ledger_accounts"],"operationId":"getV3LedgerAccountsExpensesBalance"}},"/v1/ledger_accounts/invoices":{"get":{"description":"Returns the list of Ledger Accounts that can be used to categorize Line Items\non an Invoice for in your Profit and Loss report.\n","produces":["application/json"],"security":[{"OAuth2":["invoices:write"]}],"responses":{"200":{"description":"List of Ledger Accounts retrieved","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_ListLedgerAccountsResponse"}}},"tags":["ledger_accounts"],"operationId":"List Invoice Ledger Accounts"}},"/v1/ledger_accounts/invoices/default":{"get":{"description":"Returns the default Ledger Account that can be used to categorize Line Items.\n","produces":["application/json"],"security":[{"OAuth2":["invoices:write"]}],"responses":{"200":{"description":"The Ledger Account","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_GetLedgerAccountResponse"}}},"tags":["ledger_accounts"],"operationId":"The default Ledger Account for invoice line items"}},"/ledger_accounts/invoices":{"get":{"description":"Returns the list of Ledger Accounts that can be used to categorize Line Items\non an Invoice for in your Profit and Loss report.\n","produces":["application/json"],"security":[{"OAuth2":["invoices:write"]}],"responses":{"200":{"description":"List of Ledger Accounts retrieved","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_ListLedgerAccountsResponse"}}},"tags":["ledger_accounts"],"operationId":"List Invoice Ledger Accounts"}},"/ledger_accounts/invoices/default":{"get":{"description":"Returns the default Ledger Account that can be used to categorize Line Items.\n","produces":["application/json"],"security":[{"OAuth2":["invoices:write"]}],"responses":{"200":{"description":"The Ledger Account","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_GetLedgerAccountResponse"}}},"tags":["ledger_accounts"],"operationId":"The default Ledger Account for invoice line items"}},"/v3/bank-accounts":{"get":{"description":"List all Bank Accounts","produces":["application/json"],"security":[{"OAuth2":["financing:read"]}],"responses":{"200":{"description":"Bank accounts","schema":{"$ref":"#/definitions/Jortt_V3_Financing_BankAccounts_Responses_ListBankAccountsResponse"}}},"tags":["bank-accounts"],"operationId":"List Bank Accounts V3"}},"/v3/bank-accounts/{id}/bank-transactions":{"get":{"description":"List Bank Transactions for a Bank Account","produces":["application/json"],"parameters":[{"in":"path","name":"id","description":"Bank account resource identifier (UUID)","type":"string","required":true},{"in":"query","name":"page","description":"Page of the response (minimum 1)","type":"integer","format":"int32","required":false}],"security":[{"OAuth2":["financing:read"]}],"responses":{"200":{"description":"Bank transactions","schema":{"$ref":"#/definitions/Jortt_V3_Financing_BankAccounts_Responses_ListBankTransactionsResponse"}},"404":{"description":"Bank account not found"}},"tags":["bank-accounts"],"operationId":"List Bank Transactions V3"}},"/v2/customers/{customer_id}/vat-percentages":{"get":{"description":"Returns vats that are valid on today's date for a Customer by ID","produces":["application/json"],"parameters":[{"in":"path","name":"customer_id","description":"Resource identifier (UUID)","type":"string","required":true}],"security":[{"OAuth2":["customers:read"]}],"responses":{"200":{"description":"Customer vat percentages","schema":{"$ref":"#/definitions/Jortt_V2_Invoicing_Customers_Responses_GetVatPercentagesForCustomerResponse"}}},"tags":["customers"],"operationId":"Get vat percentages for a Customer by ID V2"}},"/v1/customers":{"get":{"summary":"Returns a list of Customers","description":"A Customer can be either a private person or a company.\n\nIf the `query` is null (e.g `GET /customers`), it will retrieve all Customers in alphabetical order of `customer_name`.\n\nOtherwise if a `query` is passed (e.g `GET /customers?query=foo`), it will search and list only the Customers\nmatching the query, ordered alphabetically on `customer_name``.\n","produces":["application/json"],"parameters":[{"in":"query","name":"query","description":"Search query (at least 3 characters)","type":"string","required":false},{"in":"query","name":"page","description":"Page of the response (minimum 1)","type":"integer","format":"int32","required":false}],"security":[{"OAuth2":["customers:read"]}],"responses":{"200":{"description":"Customers","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_ListCustomersResponse"}}},"tags":["customers"],"operationId":"List Customers"},"post":{"summary":"Creates a Customer","description":"A Customer can either be a private person (set `is_private` to `true`) or\na company (set `is_private` to `false`).\n\nThe required attributes are different for a private person than a company. See parameters documentation\nbelow for details.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"name":"CreateCustomer","in":"body","required":true,"schema":{"$ref":"#/definitions/CreateCustomer"}}],"security":[{"OAuth2":["customers:write"]}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_ResourceCreatedResponse"}}},"tags":["customers"],"operationId":"Create Customer"}},"/v1/customers/{customer_id}":{"get":{"description":"Returns a Customer by ID","produces":["application/json"],"parameters":[{"in":"path","name":"customer_id","description":"Resource identifier (UUID)","type":"string","required":true}],"security":[{"OAuth2":["customers:read"]}],"responses":{"200":{"description":"Customer","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_GetCustomerResponse"}}},"tags":["customers"],"operationId":"Get Customer by ID"},"put":{"summary":"Updates a Customer","description":"A Customer can either be a private person (set `is_private` to `true`) or\na company (set `is_private` to `false`).\n\nThe required attributes are different for a private person than a company. See parameters documentation\nbelow for details.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"customer_id","type":"integer","format":"int32","required":true},{"name":"UpdateCustomer","in":"body","required":true,"schema":{"$ref":"#/definitions/UpdateCustomer"}}],"security":[{"OAuth2":["customers:write"]}],"responses":{"204":{"description":"No Content"}},"tags":["customers"],"operationId":"Update Customer"},"delete":{"summary":"Delete a Customer","description":"No required attributes to be passed.\n\nThis call will delete a customer. Then the Customer `deleted` attribute will be true.\n","produces":["application/json"],"parameters":[{"in":"path","name":"customer_id","description":"Resource identifier (UUID)","type":"string","required":true}],"security":[{"OAuth2":["customers:write"]}],"responses":{"204":{"description":"No Content"}},"tags":["customers"],"operationId":"Delete a Customer"}},"/v1/customers/{customer_id}/extra_details":{"get":{"description":"Returns a Customer's details by ID","produces":["application/json"],"parameters":[{"in":"path","name":"customer_id","description":"Resource identifier (UUID)","type":"string","required":true}],"security":[{"OAuth2":["customers:read"]}],"responses":{"200":{"description":"CustomerDetails","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_GetCustomerDetailsResponse"}}},"tags":["customers"],"operationId":"Get Customer's details by ID"}},"/v1/customers/{customer_id}/vat-percentages":{"get":{"description":"Returns vat percentages that are valid on today's date for a Customer by ID","produces":["application/json"],"parameters":[{"in":"path","name":"customer_id","description":"Resource identifier (UUID)","type":"string","required":true}],"security":[{"OAuth2":["customers:read"]}],"responses":{"200":{"description":"Customer vat percentages","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_GetVatPercentagesForCustomerResponse"}}},"tags":["customers"],"operationId":"Get vat percentages for a Customer by ID"}},"/v1/customers/{customer_id}/direct_debit_mandate":{"post":{"summary":"Send direct debit authorization to a Customer","description":"No required attributes to be passed.\n\nThis call will send an e-mail to the customer with a request for an authorization payment,\ntypically 1 or 2 cents, in order to be able to direct debit the customer. Only when the customer\npays the authorization payment the direct debit for this customer is enabled.\nIf so the Customer `mollie_direct_debit_status` will be `mollie_direct_debit_accepted`\nand it will have a `mollie_mandate_id`.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"customer_id","type":"integer","format":"int32","required":true}],"security":[{"OAuth2":["customers:write"]}],"responses":{"204":{"description":"No Content"}},"tags":["customers"],"operationId":"Send direct debit authorization to a Customer"}},"/v1/customers/{customer_id}/set_archived":{"put":{"summary":"Sets the archived status for a customer","description":"This call will set the archived status on a customer. Archived customers act as though deleted, but can be unarchived at a later point\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"customer_id","description":"Resource identifier (UUID)","type":"string","required":true},{"name":"SetCustomerArchived","in":"body","required":true,"schema":{"$ref":"#/definitions/SetCustomerArchived"}}],"security":[{"OAuth2":["customers:write"]}],"responses":{"204":{"description":"No Content"}},"tags":["customers"],"operationId":"Set Customer Archived"}},"/customers":{"get":{"summary":"Returns a list of Customers","description":"A Customer can be either a private person or a company.\n\nIf the `query` is null (e.g `GET /customers`), it will retrieve all Customers in alphabetical order of `customer_name`.\n\nOtherwise if a `query` is passed (e.g `GET /customers?query=foo`), it will search and list only the Customers\nmatching the query, ordered alphabetically on `customer_name``.\n","produces":["application/json"],"parameters":[{"in":"query","name":"query","description":"Search query (at least 3 characters)","type":"string","required":false},{"in":"query","name":"page","description":"Page of the response (minimum 1)","type":"integer","format":"int32","required":false}],"security":[{"OAuth2":["customers:read"]}],"responses":{"200":{"description":"Customers","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_ListCustomersResponse"}}},"tags":["customers"],"operationId":"List Customers"},"post":{"summary":"Creates a Customer","description":"A Customer can either be a private person (set `is_private` to `true`) or\na company (set `is_private` to `false`).\n\nThe required attributes are different for a private person than a company. See parameters documentation\nbelow for details.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"name":"CreateCustomer","in":"body","required":true,"schema":{"$ref":"#/definitions/CreateCustomer"}}],"security":[{"OAuth2":["customers:write"]}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_ResourceCreatedResponse"}}},"tags":["customers"],"operationId":"Create Customer"}},"/customers/{customer_id}":{"get":{"description":"Returns a Customer by ID","produces":["application/json"],"parameters":[{"in":"path","name":"customer_id","description":"Resource identifier (UUID)","type":"string","required":true}],"security":[{"OAuth2":["customers:read"]}],"responses":{"200":{"description":"Customer","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_GetCustomerResponse"}}},"tags":["customers"],"operationId":"Get Customer by ID"},"put":{"summary":"Updates a Customer","description":"A Customer can either be a private person (set `is_private` to `true`) or\na company (set `is_private` to `false`).\n\nThe required attributes are different for a private person than a company. See parameters documentation\nbelow for details.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"customer_id","type":"integer","format":"int32","required":true},{"name":"UpdateCustomer","in":"body","required":true,"schema":{"$ref":"#/definitions/UpdateCustomer"}}],"security":[{"OAuth2":["customers:write"]}],"responses":{"204":{"description":"No Content"}},"tags":["customers"],"operationId":"Update Customer"},"delete":{"summary":"Delete a Customer","description":"No required attributes to be passed.\n\nThis call will delete a customer. Then the Customer `deleted` attribute will be true.\n","produces":["application/json"],"parameters":[{"in":"path","name":"customer_id","description":"Resource identifier (UUID)","type":"string","required":true}],"security":[{"OAuth2":["customers:write"]}],"responses":{"204":{"description":"No Content"}},"tags":["customers"],"operationId":"Delete a Customer"}},"/customers/{customer_id}/extra_details":{"get":{"description":"Returns a Customer's details by ID","produces":["application/json"],"parameters":[{"in":"path","name":"customer_id","description":"Resource identifier (UUID)","type":"string","required":true}],"security":[{"OAuth2":["customers:read"]}],"responses":{"200":{"description":"CustomerDetails","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_GetCustomerDetailsResponse"}}},"tags":["customers"],"operationId":"Get Customer's details by ID"}},"/customers/{customer_id}/vat-percentages":{"get":{"description":"Returns vat percentages that are valid on today's date for a Customer by ID","produces":["application/json"],"parameters":[{"in":"path","name":"customer_id","description":"Resource identifier (UUID)","type":"string","required":true}],"security":[{"OAuth2":["customers:read"]}],"responses":{"200":{"description":"Customer vat percentages","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_GetVatPercentagesForCustomerResponse"}}},"tags":["customers"],"operationId":"Get vat percentages for a Customer by ID"}},"/customers/{customer_id}/direct_debit_mandate":{"post":{"summary":"Send direct debit authorization to a Customer","description":"No required attributes to be passed.\n\nThis call will send an e-mail to the customer with a request for an authorization payment,\ntypically 1 or 2 cents, in order to be able to direct debit the customer. Only when the customer\npays the authorization payment the direct debit for this customer is enabled.\nIf so the Customer `mollie_direct_debit_status` will be `mollie_direct_debit_accepted`\nand it will have a `mollie_mandate_id`.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"customer_id","type":"integer","format":"int32","required":true}],"security":[{"OAuth2":["customers:write"]}],"responses":{"204":{"description":"No Content"}},"tags":["customers"],"operationId":"Send direct debit authorization to a Customer"}},"/customers/{customer_id}/set_archived":{"put":{"summary":"Sets the archived status for a customer","description":"This call will set the archived status on a customer. Archived customers act as though deleted, but can be unarchived at a later point\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"customer_id","description":"Resource identifier (UUID)","type":"string","required":true},{"name":"SetCustomerArchived","in":"body","required":true,"schema":{"$ref":"#/definitions/SetCustomerArchived"}}],"security":[{"OAuth2":["customers:write"]}],"responses":{"204":{"description":"No Content"}},"tags":["customers"],"operationId":"Set Customer Archived"}},"/v2/estimates":{"get":{"summary":"Returns a list of Estimates","description":"If the `query` is null (e.g `GET /estimates`), it will retrieve all Estimates ordered by `created_at`.\nOtherwise if a `query` is passed (e.g `GET /estimates?query=foo`), it will search and list only the Estimates\nmatching the query, ordered by `created_at`.\nOptionally filter by `estimate_status` (lifecycle status): `draft`, `sent`, `expired`, `invoiced`.\nOptionally filter by `acceptance_status`: `accepted`, `rejected`, `signed`.\n","produces":["application/json"],"parameters":[{"in":"query","name":"page","type":"integer","format":"int32","required":false},{"in":"query","name":"query","type":"string","required":false},{"in":"query","name":"estimate_status","type":"string","required":false},{"in":"query","name":"acceptance_status","type":"string","required":false}],"security":[{"OAuth2":["estimates:read"]}],"responses":{"200":{"description":"Estimates","schema":{"$ref":"#/definitions/Jortt_V2_Invoicing_Estimates_Responses_ListEstimatesResponse"}}},"tags":["v2"],"operationId":"List Estimates v2"},"post":{"summary":"Creates (and optionally sends) an Estimate","description":"When the optional `send_method` is not provided, the Estimate will be created as a Draft.\n\nWhen the optional `send_method` parameter is provided, the Estimate will also be scheduled for sending.\n\nWhen sending by email, you can optionally provide `email_address_customer` to override the customer's\ndefault email address.\n\nBy polling the [`GET /estimates/{id}`](#get-estimate-by-id) endpoint you can check if the Estimate has\nbeen sent (the returned Estimate's `estimate_status` attribute is then set to `sent` otherwise it will\nbe `draft`).\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"name":"CreateEstimateV2","in":"body","required":true,"schema":{"$ref":"#/definitions/CreateEstimateV2"}}],"security":[{"OAuth2":["estimates:write"]}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_ResourceCreatedResponse"}}},"tags":["estimates"],"operationId":"Create (and optionally send) an Estimate V2"}},"/v2/estimates/{id}":{"get":{"description":"Returns an Estimate by ID","produces":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID)","type":"string","required":true}],"security":[{"OAuth2":["estimates:read"]}],"responses":{"200":{"description":"Estimate","schema":{"$ref":"#/definitions/Jortt_V2_Invoicing_Estimates_Responses_GetEstimateResponse"}}},"tags":["v2"],"operationId":"Get Estimate by ID v2"},"put":{"summary":"Edits an Estimate","description":"Edits an estimate, provided said estimate has not been accepted, signed, or invoiced.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID)","type":"string","required":true},{"name":"EditEstimateV2","in":"body","required":true,"schema":{"$ref":"#/definitions/EditEstimateV2"}}],"security":[{"OAuth2":["estimates:write"]}],"responses":{"200":{"description":"No Content"}},"tags":["estimates"],"operationId":"Edit Estimate V2"},"delete":{"summary":"Deletes an Estimate","description":"Permanently deletes a draft Estimate.\n","produces":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID)","type":"string","required":true}],"security":[{"OAuth2":["estimates:write"]}],"responses":{"204":{"description":"No Content"}},"tags":["estimates"],"operationId":"Delete Estimate V2"}},"/v2/estimates/{id}/send":{"post":{"summary":"Sends an Estimate","description":"Sends an estimate to the linked customer. The estimate must have a customer, line items,\nand a valid creditor (organization) to be sent.\n\nThe `send_method` parameter determines how the estimate is delivered:\n- `email`: Sends the estimate via email to the customer's email address.\n- `self`: Marks the estimate as sent (for manual delivery / printing).\n\nWhen sending by email, you can optionally provide `email_address_customer` to override the\ncustomer's default email address.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID)","type":"string","required":true},{"name":"SendEstimateV2","in":"body","required":true,"schema":{"$ref":"#/definitions/SendEstimateV2"}}],"security":[{"OAuth2":["estimates:write"]}],"responses":{"200":{"description":"Sent"}},"tags":["estimates"],"operationId":"Send Estimate V2"}},"/v2/estimates/{id}/download":{"get":{"summary":"Returns a URL from which the estimate PDF can be downloaded.","description":"Only returns a URL for sent estimates. Will fail otherwise.\n\nNote: The returned URL expires after 10 minutes.\n","produces":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID)","type":"string","required":true},{"in":"query","name":"version","description":"The version of the estimate to download. Defaults to latest","type":"integer","format":"int32","required":false}],"security":[{"OAuth2":["estimates:read"]}],"responses":{"200":{"description":"URL to download estimate PDF created","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_DownloadInvoiceOrEstimatePdfResponse"}}},"tags":["v2"],"operationId":"Download Estimate PDF v2"}},"/v2/estimates/{id}/accept":{"post":{"summary":"Accepts an Estimate","description":"Marks an estimate as accepted by the customer. The estimate must have been sent first.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID)","type":"string","required":true}],"security":[{"OAuth2":["estimates:write"]}],"responses":{"200":{"description":"Accepted"}},"tags":["estimates"],"operationId":"Accept Estimate V2"}},"/v2/estimates/{id}/reject":{"post":{"summary":"Rejects an Estimate","description":"Marks an estimate as rejected. The estimate must have been sent first.\n\nAn optional `comment` can be provided to explain the reason for rejection.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID)","type":"string","required":true},{"name":"RejectEstimateV2","in":"body","required":true,"schema":{"$ref":"#/definitions/RejectEstimateV2"}}],"security":[{"OAuth2":["estimates:write"]}],"responses":{"200":{"description":"Rejected"}},"tags":["estimates"],"operationId":"Reject Estimate V2"}},"/v2/estimates/{id}/copy":{"post":{"summary":"Creates a copy of an Estimate","description":"Creates a new draft Estimate by copying the line items, discounts, customer,\nintroduction, remarks, and reference of the given Estimate.\n\nReturns the ID of the newly created Estimate.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID)","type":"string","required":true}],"security":[{"OAuth2":["estimates:write"]}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_ResourceCreatedResponse"}}},"tags":["estimates"],"operationId":"Copy Estimate V2"}},"/v2/estimates/{id}/invoice":{"post":{"summary":"Creates an Invoice from an Estimate","description":"Creates a new Invoice based on the line items of the given Estimate and marks the\nEstimate as invoiced.\n\nReturns the ID of the newly created Invoice.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID)","type":"string","required":true}],"security":[{"OAuth2":["estimates:write"]}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_ResourceCreatedResponse"}}},"tags":["estimates"],"operationId":"Create Invoice from Estimate V2"}},"/v2/estimates/{id}/line_item_suggestions":{"get":{"summary":"Returns a list of suggested line items","description":"Returns a list of suggested line items for a given estimate based on the given query.\nThe estimate must have an associated customer or 404 is returned.\n","produces":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID)","type":"string","required":true},{"in":"query","name":"query","description":"Query string to search for suggestions with","type":"string","required":true}],"security":[{"OAuth2":["estimates:read"]}],"responses":{"200":{"description":"Estimate Line Item Suggestions","schema":{"$ref":"#/definitions/Jortt_V2_Invoicing_Estimates_Responses_GetEstimateLineItemSuggestionsResponse"}}},"tags":["estimates"],"operationId":"Get estimate line item suggestions"}},"/v2/estimates/{id}/send_settings":{"get":{"description":"Returns the current send settings for the estimate","produces":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID)","type":"string","required":true}],"security":[{"OAuth2":["estimates:read"]}],"responses":{"200":{"description":"Estimate send settings","schema":{"$ref":"#/definitions/Jortt_V2_Invoicing_Estimates_Responses_SendSettingsEstimateResponse"}}},"tags":["v2"],"operationId":"Get estimate send settings v2"}},"/v1/tradenames":{"get":{"summary":"Returns a list of tradenames","description":"A tradename is the name under which your business trades. The tradename is often the same as the name in your company's deed of incorporation, but it can also be different. A business can, for instance, use different tradenames for different activities.\n\nGET `/tradenames` it will retrieve all Tradenames in alphabetical order of `company_name`.\n","produces":["application/json"],"security":[{"OAuth2":["organizations:read"]}],"responses":{"200":{"description":"tradenames","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_ListTradenamesResponse"}}},"tags":["tradenames"],"operationId":"List tradenames"}},"/tradenames":{"get":{"summary":"Returns a list of tradenames","description":"A tradename is the name under which your business trades. The tradename is often the same as the name in your company's deed of incorporation, but it can also be different. A business can, for instance, use different tradenames for different activities.\n\nGET `/tradenames` it will retrieve all Tradenames in alphabetical order of `company_name`.\n","produces":["application/json"],"security":[{"OAuth2":["organizations:read"]}],"responses":{"200":{"description":"tradenames","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_ListTradenamesResponse"}}},"tags":["tradenames"],"operationId":"List tradenames"}},"/v1/labels":{"get":{"summary":"Returns a list of labels","description":"GET `/labels` will retrieve all Labels in alphabetical order of `description`.\n","produces":["application/json"],"security":[{"OAuth2":["organizations:read"]}],"responses":{"200":{"description":"labels","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_ListLabelsResponse"}}},"tags":["labels"],"operationId":"List labels"},"post":{"summary":"Create a label","description":"POST `/labels` Will create a label and return a representative uuid.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"name":"CreateLabel","in":"body","required":true,"schema":{"$ref":"#/definitions/CreateLabel"}}],"security":[{"OAuth2":["organizations:write"]}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_ResourceCreatedResponse"}}},"tags":["labels"],"operationId":"Create a Label"}},"/labels":{"get":{"summary":"Returns a list of labels","description":"GET `/labels` will retrieve all Labels in alphabetical order of `description`.\n","produces":["application/json"],"security":[{"OAuth2":["organizations:read"]}],"responses":{"200":{"description":"labels","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_ListLabelsResponse"}}},"tags":["labels"],"operationId":"List labels"},"post":{"summary":"Create a label","description":"POST `/labels` Will create a label and return a representative uuid.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"name":"CreateLabel","in":"body","required":true,"schema":{"$ref":"#/definitions/CreateLabel"}}],"security":[{"OAuth2":["organizations:write"]}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_ResourceCreatedResponse"}}},"tags":["labels"],"operationId":"Create a Label"}},"/v1/organizations/me":{"get":{"description":"Get the current Organization","produces":["application/json"],"security":[{"OAuth2":["organizations:read"]}],"responses":{"200":{"description":"Organizations","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_GetOrganizationResponse"}}},"tags":["organizations"],"operationId":"Get the organization associated with the api credentials"}},"/organizations/me":{"get":{"description":"Get the current Organization","produces":["application/json"],"security":[{"OAuth2":["organizations:read"]}],"responses":{"200":{"description":"Organizations","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_GetOrganizationResponse"}}},"tags":["organizations"],"operationId":"Get the organization associated with the api credentials"}},"/v1/loonjournaalposten":{"get":{"summary":"Returns a list of Loonjournaalposten","description":"\nIf the `year` is null (e.g `GET /payroll/loonjournaalposten`), it will retrieve all Loonjournaalposten ordered by `period_date`.\n\nOtherwise if a `year` is passed (e.g `GET /payroll/loonjournaalposten?year=2022`), it will search and list only the Loonjournaalposten\nmatching the year, ordered by `period_date`.\n","produces":["application/json"],"parameters":[{"in":"query","name":"year","description":"The year to get the Loonjournaalposten for","type":"string","required":false}],"security":[{"OAuth2":["payroll:read"]}],"responses":{"200":{"description":"Loonjournaalposten","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_ListLoonjournaalpostenResponse"}}},"tags":["loonjournaalposten"],"operationId":"List Loonjournaalposten"},"post":{"summary":"Create a Loonjournaalpost","description":"POST `/loonjournaalposten` Will create a Loonjournaalpost and return a representative uuid.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"name":"CreateLoonjournaalpost","in":"body","required":true,"schema":{"$ref":"#/definitions/CreateLoonjournaalpost"}}],"security":[{"OAuth2":["payroll:write"]}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_ResourceCreatedResponse"}}},"tags":["loonjournaalposten"],"operationId":"Create a Loonjournaalpost"}},"/v1/loonjournaalposten/{loonjournaalpost_id}":{"put":{"summary":"Update a Loonjournaalpost","description":"Update a Loonjournaalpost by ID\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"loonjournaalpost_id","description":"Resource identifier (UUID)","type":"string","required":true},{"name":"UpdateLoonjournaalpost","in":"body","required":true,"schema":{"$ref":"#/definitions/UpdateLoonjournaalpost"}}],"security":[{"OAuth2":["payroll:write"]}],"responses":{"204":{"description":"No Content"}},"tags":["loonjournaalposten"],"operationId":"Update a Loonjournaalpost"},"delete":{"summary":"Delete a Loonjournaalpost","description":"No required attributes to be passed.\n\nThis call will delete a loonjournaalpost.\n","produces":["application/json"],"parameters":[{"in":"path","name":"loonjournaalpost_id","description":"Resource identifier (UUID)","type":"string","required":true}],"security":[{"OAuth2":["payroll:write"]}],"responses":{"204":{"description":"No Content"}},"tags":["loonjournaalposten"],"operationId":"Delete a Loonjournaalpost"}},"/loonjournaalposten":{"get":{"summary":"Returns a list of Loonjournaalposten","description":"\nIf the `year` is null (e.g `GET /payroll/loonjournaalposten`), it will retrieve all Loonjournaalposten ordered by `period_date`.\n\nOtherwise if a `year` is passed (e.g `GET /payroll/loonjournaalposten?year=2022`), it will search and list only the Loonjournaalposten\nmatching the year, ordered by `period_date`.\n","produces":["application/json"],"parameters":[{"in":"query","name":"year","description":"The year to get the Loonjournaalposten for","type":"string","required":false}],"security":[{"OAuth2":["payroll:read"]}],"responses":{"200":{"description":"Loonjournaalposten","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_ListLoonjournaalpostenResponse"}}},"tags":["loonjournaalposten"],"operationId":"List Loonjournaalposten"},"post":{"summary":"Create a Loonjournaalpost","description":"POST `/loonjournaalposten` Will create a Loonjournaalpost and return a representative uuid.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"name":"CreateLoonjournaalpost","in":"body","required":true,"schema":{"$ref":"#/definitions/CreateLoonjournaalpost"}}],"security":[{"OAuth2":["payroll:write"]}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_ResourceCreatedResponse"}}},"tags":["loonjournaalposten"],"operationId":"Create a Loonjournaalpost"}},"/loonjournaalposten/{loonjournaalpost_id}":{"put":{"summary":"Update a Loonjournaalpost","description":"Update a Loonjournaalpost by ID\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"loonjournaalpost_id","description":"Resource identifier (UUID)","type":"string","required":true},{"name":"UpdateLoonjournaalpost","in":"body","required":true,"schema":{"$ref":"#/definitions/UpdateLoonjournaalpost"}}],"security":[{"OAuth2":["payroll:write"]}],"responses":{"204":{"description":"No Content"}},"tags":["loonjournaalposten"],"operationId":"Update a Loonjournaalpost"},"delete":{"summary":"Delete a Loonjournaalpost","description":"No required attributes to be passed.\n\nThis call will delete a loonjournaalpost.\n","produces":["application/json"],"parameters":[{"in":"path","name":"loonjournaalpost_id","description":"Resource identifier (UUID)","type":"string","required":true}],"security":[{"OAuth2":["payroll:write"]}],"responses":{"204":{"description":"No Content"}},"tags":["loonjournaalposten"],"operationId":"Delete a Loonjournaalpost"}},"/v1/reports/summaries/invoices":{"get":{"summary":"Returns a summary of invoices for the current year","description":"Gets summarized Invoice data intended for dashboard display. Summaries reflect invoices for the current year, grouped by payment status.\n","produces":["application/json"],"security":[{"OAuth2":["reports:read"]}],"responses":{"200":{"description":"Invoices summary","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_Reports_Summaries_GetInvoicesResponse"}}},"tags":["reports"],"operationId":"Dashboard invoices"}},"/v1/reports/summaries/btw":{"get":{"summary":"Returns a list of summarized btw periods","description":"Gets a list summarized BTW period data intended for dashboard display.\n","produces":["application/json"],"security":[{"OAuth2":["reports:read"]}],"responses":{"200":{"description":"Btw summaries","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_Reports_Summaries_GetBtwResponse"}}},"tags":["reports"],"operationId":"Dashboard btw"}},"/v1/reports/summaries/balance":{"get":{"summary":"Returns key organization balances for the current date","description":"Return organization balances for dashboard display. Balances presented are reflective of the current date.\n","produces":["application/json"],"security":[{"OAuth2":["reports:read"]}],"responses":{"200":{"description":"Balances","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_Reports_Summaries_GetBalancesResponse"}}},"tags":["reports"],"operationId":"Dashboard balance"}},"/v1/reports/summaries/profit_and_loss":{"get":{"summary":"Returns a summary of profit and loss for the current year","description":"Gets a summarized report for profit and loss this year up until the current date.\n","produces":["application/json"],"security":[{"OAuth2":["reports:read"]}],"responses":{"200":{"description":"Profit and loss summary","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_Reports_Summaries_GetProfitAndLossReponse"}}},"tags":["reports"],"operationId":"Dashboard profit and loss"}},"/v1/reports/summaries/cash_and_bank":{"get":{"summary":"Returns a summary of bank accounts, cash and liquid assets","description":"Returns summaries of all organization bank accounts, liquid assets and cash balances intended for dashboard display.\n","produces":["application/json"],"security":[{"OAuth2":["reports:read"]}],"responses":{"200":{"description":"Cash and bank summary","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_Reports_Summaries_GetCashAndBankResponse"}}},"tags":["reports"],"operationId":"Dashboard cash and bank"}},"/reports/summaries/invoices":{"get":{"summary":"Returns a summary of invoices for the current year","description":"Gets summarized Invoice data intended for dashboard display. Summaries reflect invoices for the current year, grouped by payment status.\n","produces":["application/json"],"security":[{"OAuth2":["reports:read"]}],"responses":{"200":{"description":"Invoices summary","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_Reports_Summaries_GetInvoicesResponse"}}},"tags":["reports"],"operationId":"Dashboard invoices"}},"/reports/summaries/btw":{"get":{"summary":"Returns a list of summarized btw periods","description":"Gets a list summarized BTW period data intended for dashboard display.\n","produces":["application/json"],"security":[{"OAuth2":["reports:read"]}],"responses":{"200":{"description":"Btw summaries","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_Reports_Summaries_GetBtwResponse"}}},"tags":["reports"],"operationId":"Dashboard btw"}},"/reports/summaries/balance":{"get":{"summary":"Returns key organization balances for the current date","description":"Return organization balances for dashboard display. Balances presented are reflective of the current date.\n","produces":["application/json"],"security":[{"OAuth2":["reports:read"]}],"responses":{"200":{"description":"Balances","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_Reports_Summaries_GetBalancesResponse"}}},"tags":["reports"],"operationId":"Dashboard balance"}},"/reports/summaries/profit_and_loss":{"get":{"summary":"Returns a summary of profit and loss for the current year","description":"Gets a summarized report for profit and loss this year up until the current date.\n","produces":["application/json"],"security":[{"OAuth2":["reports:read"]}],"responses":{"200":{"description":"Profit and loss summary","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_Reports_Summaries_GetProfitAndLossReponse"}}},"tags":["reports"],"operationId":"Dashboard profit and loss"}},"/reports/summaries/cash_and_bank":{"get":{"summary":"Returns a summary of bank accounts, cash and liquid assets","description":"Returns summaries of all organization bank accounts, liquid assets and cash balances intended for dashboard display.\n","produces":["application/json"],"security":[{"OAuth2":["reports:read"]}],"responses":{"200":{"description":"Cash and bank summary","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_Reports_Summaries_GetCashAndBankResponse"}}},"tags":["reports"],"operationId":"Dashboard cash and bank"}},"/v1/inbox/images":{"post":{"summary":"Upload images to jortt","description":"A simple endpoint where one can upload images to jortt, typically used to upload receipts.\nThe response will contain the uuids of the uploaded images in the same order as they were supplied in the request.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"name":"UploadReceipt","in":"body","required":true,"schema":{"$ref":"#/definitions/UploadReceipt"}}],"security":[{"OAuth2":["inbox:write"]}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_ResourcesCreatedResponse"}}},"tags":["inbox"],"operationId":"Upload receipt"}},"/inbox/images":{"post":{"summary":"Upload images to jortt","description":"A simple endpoint where one can upload images to jortt, typically used to upload receipts.\nThe response will contain the uuids of the uploaded images in the same order as they were supplied in the request.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"name":"UploadReceipt","in":"body","required":true,"schema":{"$ref":"#/definitions/UploadReceipt"}}],"security":[{"OAuth2":["inbox:write"]}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_ResourcesCreatedResponse"}}},"tags":["inbox"],"operationId":"Upload receipt"}},"/v1/projects":{"get":{"summary":"Returns a list of Projects","description":"Returns a paginated list of projects ordered by creation date. 10 items are provided per page\n","produces":["application/json"],"parameters":[{"in":"query","name":"page","description":"Page of the response (minimum 1)","type":"integer","format":"int32","required":false}],"security":[{"OAuth2":["invoices:read"]}],"responses":{"200":{"description":"Projects","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_ListProjectsResponse"}}},"tags":["projects"],"operationId":"List Projects"},"post":{"summary":"Creates a Project","description":"Creates a Project\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"name":"CreateProject","in":"body","required":true,"schema":{"$ref":"#/definitions/CreateProject"}}],"security":[{"OAuth2":["invoices:write"]}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_ResourceCreatedResponse"}}},"tags":["projects"],"operationId":"Create Project"}},"/v1/projects/{id}":{"get":{"summary":"Returns a Project","description":"Returns a Projects wrapped in a data object\n","produces":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID) for the owning Project","type":"string","required":true}],"security":[{"OAuth2":["invoices:read"]}],"responses":{"200":{"description":"Project","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_GetProjectResponse"}}},"tags":["projects"],"operationId":"Get Project"},"put":{"summary":"Updates a Project","description":"Updates a project\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID) for the owning Project","type":"string","required":true},{"name":"UpdateProject","in":"body","required":true,"schema":{"$ref":"#/definitions/UpdateProject"}}],"security":[{"OAuth2":["invoices:write"]}],"responses":{"200":{"description":"No Content"}},"tags":["projects"],"operationId":"Update Project"},"delete":{"summary":"Deletes a Project","description":"Deletes a project\n","produces":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID) for the owning Project","type":"string","required":true}],"security":[{"OAuth2":["invoices:write"]}],"responses":{"204":{"description":"No Content"}},"tags":["projects"],"operationId":"Delete Project"}},"/v1/projects/{id}/invoice":{"post":{"summary":"Creates an invoice based on project line items","description":"Creates an invoice based on the given project line items for the customer associated with said project.\nInvoiced line items will be marked as invoiced under the created invoice.\nReturns the resource ID for the newly created invoice.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID) for the owning Project","type":"string","required":true},{"name":"InvoiceProject","in":"body","required":true,"schema":{"$ref":"#/definitions/InvoiceProject"}}],"security":[{"OAuth2":["invoices:write"]}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_ResourceCreatedResponse"}}},"tags":["projects"],"operationId":"Invoice Project"}},"/v1/projects/{id}/line_items":{"get":{"summary":"Returns a list of Project Line Items","description":"Returns a paginated list of line items for the given project id ordered by creation date.\n100 items are provided per page\n","produces":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID)","type":"string","required":true},{"in":"query","name":"page","description":"Page of the response (minimum 1)","type":"integer","format":"int32","required":false},{"in":"query","name":"start_date","description":"date to fetch data from (inclusive)","type":"string","format":"date","required":false},{"in":"query","name":"end_date","description":"date to fetch data until (exclusive)","type":"string","format":"date","required":false},{"in":"query","name":"status_filter","description":"status used to filter project line items","type":"string","enum":["billable","invoiced","concept","nonbillable"],"required":false}],"security":[{"OAuth2":["invoices:read"]}],"responses":{"200":{"description":"Project Line Items","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_ListProjectLineItemsResponse"}}},"tags":["projects"],"operationId":"List Project Line Items"},"post":{"summary":"Creates a Project Line Item","description":"Add a project line item to given project.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID) for the owning Project","type":"string","required":true},{"name":"V1ProjectsIdLineItems","in":"body","required":true,"schema":{"$ref":"#/definitions/V1ProjectsIdLineItems"}}],"security":[{"OAuth2":["invoices:write"]}],"responses":{"201":{"description":"Project Line Item Created"}},"tags":["projects"],"operationId":"Create Project Line item"}},"/v1/projects/{id}/line_items/summary":{"get":{"summary":"Returns a list of Project Line Item summaries","description":"Returns a list of monthly summaries for project line items.\n","produces":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID) for the Project","type":"string","required":true},{"in":"query","name":"period","description":"period of the summaries you wish to fetch. one of [year, month, day]. Defaults to month","type":"string","required":false},{"in":"query","name":"start_date","description":"date to fetch data from (inclusive)","type":"string","format":"date","required":false},{"in":"query","name":"end_date","description":"date to fetch data until (inclusive)","type":"string","format":"date","required":false}],"security":[{"OAuth2":["invoices:read"]}],"responses":{"200":{"description":"Project Line Item Summary","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_GetProjectLineItemsSummaryResponse"}}},"tags":["projects"],"operationId":"Get Project Line Item Summary"}},"/v1/projects/{id}/line_items/{line_item_id}":{"get":{"summary":"Returns a Project Line Item","description":"Returns a given Project Line Item wrapped in a data object.\n","produces":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID) for the owning Project","type":"string","required":true},{"in":"path","name":"line_item_id","description":"ID of the line item to update","type":"integer","format":"int32","required":true}],"security":[{"OAuth2":["invoices:read"]}],"responses":{"200":{"description":"Project Line Item","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_GetProjectLineItemResponse"}}},"tags":["projects"],"operationId":"Get Project Line Item"},"put":{"summary":"Updates a Project Line item","description":"Update the given project line item.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID) for the Project","type":"string","required":true},{"in":"path","name":"line_item_id","description":"ID of the line item to update","type":"integer","format":"int32","required":true},{"name":"V1ProjectsIdLineItems","in":"body","required":true,"schema":{"$ref":"#/definitions/V1ProjectsIdLineItems"}}],"security":[{"OAuth2":["invoices:write"]}],"responses":{"201":{"description":"Project Updated"}},"tags":["projects"],"operationId":"Update Project Line item"},"delete":{"summary":"Deletes a Project Line item","description":"Delete the given project line item.\n","produces":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID) for the owning Project","type":"string","required":true},{"in":"path","name":"line_item_id","description":"ID of the line item to update","type":"integer","format":"int32","required":true}],"security":[{"OAuth2":["invoices:write"]}],"responses":{"204":{"description":"No Content"}},"tags":["projects"],"operationId":"Delete Project Line item"}},"/projects":{"get":{"summary":"Returns a list of Projects","description":"Returns a paginated list of projects ordered by creation date. 10 items are provided per page\n","produces":["application/json"],"parameters":[{"in":"query","name":"page","description":"Page of the response (minimum 1)","type":"integer","format":"int32","required":false}],"security":[{"OAuth2":["invoices:read"]}],"responses":{"200":{"description":"Projects","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_ListProjectsResponse"}}},"tags":["projects"],"operationId":"List Projects"},"post":{"summary":"Creates a Project","description":"Creates a Project\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"name":"CreateProject","in":"body","required":true,"schema":{"$ref":"#/definitions/CreateProject"}}],"security":[{"OAuth2":["invoices:write"]}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_ResourceCreatedResponse"}}},"tags":["projects"],"operationId":"Create Project"}},"/projects/{id}":{"get":{"summary":"Returns a Project","description":"Returns a Projects wrapped in a data object\n","produces":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID) for the owning Project","type":"string","required":true}],"security":[{"OAuth2":["invoices:read"]}],"responses":{"200":{"description":"Project","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_GetProjectResponse"}}},"tags":["projects"],"operationId":"Get Project"},"put":{"summary":"Updates a Project","description":"Updates a project\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID) for the owning Project","type":"string","required":true},{"name":"UpdateProject","in":"body","required":true,"schema":{"$ref":"#/definitions/UpdateProject"}}],"security":[{"OAuth2":["invoices:write"]}],"responses":{"200":{"description":"No Content"}},"tags":["projects"],"operationId":"Update Project"},"delete":{"summary":"Deletes a Project","description":"Deletes a project\n","produces":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID) for the owning Project","type":"string","required":true}],"security":[{"OAuth2":["invoices:write"]}],"responses":{"204":{"description":"No Content"}},"tags":["projects"],"operationId":"Delete Project"}},"/projects/{id}/invoice":{"post":{"summary":"Creates an invoice based on project line items","description":"Creates an invoice based on the given project line items for the customer associated with said project.\nInvoiced line items will be marked as invoiced under the created invoice.\nReturns the resource ID for the newly created invoice.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID) for the owning Project","type":"string","required":true},{"name":"InvoiceProject","in":"body","required":true,"schema":{"$ref":"#/definitions/InvoiceProject"}}],"security":[{"OAuth2":["invoices:write"]}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_ResourceCreatedResponse"}}},"tags":["projects"],"operationId":"Invoice Project"}},"/projects/{id}/line_items":{"get":{"summary":"Returns a list of Project Line Items","description":"Returns a paginated list of line items for the given project id ordered by creation date.\n100 items are provided per page\n","produces":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID)","type":"string","required":true},{"in":"query","name":"page","description":"Page of the response (minimum 1)","type":"integer","format":"int32","required":false},{"in":"query","name":"start_date","description":"date to fetch data from (inclusive)","type":"string","format":"date","required":false},{"in":"query","name":"end_date","description":"date to fetch data until (exclusive)","type":"string","format":"date","required":false},{"in":"query","name":"status_filter","description":"status used to filter project line items","type":"string","enum":["billable","invoiced","concept","nonbillable"],"required":false}],"security":[{"OAuth2":["invoices:read"]}],"responses":{"200":{"description":"Project Line Items","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_ListProjectLineItemsResponse"}}},"tags":["projects"],"operationId":"List Project Line Items"},"post":{"summary":"Creates a Project Line Item","description":"Add a project line item to given project.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID) for the owning Project","type":"string","required":true},{"name":"ProjectsIdLineItems","in":"body","required":true,"schema":{"$ref":"#/definitions/ProjectsIdLineItems"}}],"security":[{"OAuth2":["invoices:write"]}],"responses":{"201":{"description":"Project Line Item Created"}},"tags":["projects"],"operationId":"Create Project Line item"}},"/projects/{id}/line_items/summary":{"get":{"summary":"Returns a list of Project Line Item summaries","description":"Returns a list of monthly summaries for project line items.\n","produces":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID) for the Project","type":"string","required":true},{"in":"query","name":"period","description":"period of the summaries you wish to fetch. one of [year, month, day]. Defaults to month","type":"string","required":false},{"in":"query","name":"start_date","description":"date to fetch data from (inclusive)","type":"string","format":"date","required":false},{"in":"query","name":"end_date","description":"date to fetch data until (inclusive)","type":"string","format":"date","required":false}],"security":[{"OAuth2":["invoices:read"]}],"responses":{"200":{"description":"Project Line Item Summary","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_GetProjectLineItemsSummaryResponse"}}},"tags":["projects"],"operationId":"Get Project Line Item Summary"}},"/projects/{id}/line_items/{line_item_id}":{"get":{"summary":"Returns a Project Line Item","description":"Returns a given Project Line Item wrapped in a data object.\n","produces":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID) for the owning Project","type":"string","required":true},{"in":"path","name":"line_item_id","description":"ID of the line item to update","type":"integer","format":"int32","required":true}],"security":[{"OAuth2":["invoices:read"]}],"responses":{"200":{"description":"Project Line Item","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_GetProjectLineItemResponse"}}},"tags":["projects"],"operationId":"Get Project Line Item"},"put":{"summary":"Updates a Project Line item","description":"Update the given project line item.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID) for the Project","type":"string","required":true},{"in":"path","name":"line_item_id","description":"ID of the line item to update","type":"integer","format":"int32","required":true},{"name":"ProjectsIdLineItems","in":"body","required":true,"schema":{"$ref":"#/definitions/ProjectsIdLineItems"}}],"security":[{"OAuth2":["invoices:write"]}],"responses":{"201":{"description":"Project Updated"}},"tags":["projects"],"operationId":"Update Project Line item"},"delete":{"summary":"Deletes a Project Line item","description":"Delete the given project line item.\n","produces":["application/json"],"parameters":[{"in":"path","name":"id","description":"Resource identifier (UUID) for the owning Project","type":"string","required":true},{"in":"path","name":"line_item_id","description":"ID of the line item to update","type":"integer","format":"int32","required":true}],"security":[{"OAuth2":["invoices:write"]}],"responses":{"204":{"description":"No Content"}},"tags":["projects"],"operationId":"Delete Project Line item"}},"/v1/files/put_url":{"get":{"summary":"Request an upload URL for attachments","description":"Returns an upload URL that can be used to upload attachments to be sent with invoice emails though the `attachment_ids` parameter.\n","produces":["application/json"],"parameters":[{"in":"query","name":"id","description":"Resource identifier (UUID)","type":"string","required":true},{"in":"query","name":"name","description":"The name of the file to be uploaded","type":"string","required":true},{"in":"query","name":"mime_type","description":"The MIME type of the file to be uploaded","type":"string","required":true},{"in":"query","name":"content_length","description":"The content length of the file to be uploaded","type":"integer","format":"int64","required":true}],"security":[{"OAuth2":["organizations:write"]}],"responses":{"200":{"description":"Attachments","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_GetFilePutUrlResponse"}}},"tags":["files"],"operationId":"Upload URL for attachments"}},"/files/put_url":{"get":{"summary":"Request an upload URL for attachments","description":"Returns an upload URL that can be used to upload attachments to be sent with invoice emails though the `attachment_ids` parameter.\n","produces":["application/json"],"parameters":[{"in":"query","name":"id","description":"Resource identifier (UUID)","type":"string","required":true},{"in":"query","name":"name","description":"The name of the file to be uploaded","type":"string","required":true},{"in":"query","name":"mime_type","description":"The MIME type of the file to be uploaded","type":"string","required":true},{"in":"query","name":"content_length","description":"The content length of the file to be uploaded","type":"integer","format":"int64","required":true}],"security":[{"OAuth2":["organizations:write"]}],"responses":{"200":{"description":"Attachments","schema":{"$ref":"#/definitions/Jortt_V1_Entities_Responses_GetFilePutUrlResponse"}}},"tags":["files"],"operationId":"Upload URL for attachments"}}},"definitions":{"Jortt_Shared_Entities_Error":{"type":"object","properties":{"code":{"type":"integer","format":"int32","enum":[401,404,405,409,422,429,500,503],"example":422,"description":"HTTP response status code of the error"},"key":{"type":"string","enum":["access_token.invalid","access_token.expired","access_token.revoked","scopes.insufficient","organization.non_existing","organization.requires_mkb_plan","user.non_existing","user.invalid_credentials","two_factor_code.invalid","two_factor_code.missing_secret","endpoint.not_found","resource.not_found","resource.method_not_allowed","resource.conflict","params.invalid","params.invalid_format","params.invalid_encoding","operation.invalid","operation.year_closed","request.throttled","server.internal_error","server.maintenance","integration.outage"],"example":"params.invalid","description":"A machine readable (and constant) key describing the error"},"message":{"type":"string","example":"The parameters are invalid (either missing, not of the correct type or incorrect format).","description":"A human readable message describing the error"},"details":{"type":"array","items":{"$ref":"#/definitions/Jortt_Shared_Entities_ErrorDetail"},"description":"A list of details for the error (can be empty)"}},"required":["code","key","message","details"]},"Jortt_Shared_Entities_ErrorDetail":{"type":"object","properties":{"key":{"type":"string","example":"is_missing","description":"A machine readable (and constant) key describing the error detail"},"message":{"type":"string","example":"is missing","description":"A human readable message describing the error detail"},"param":{"type":"string","example":"customer_id","description":"The path of the param that is faulty (can be absent)"}},"required":["key","message","param"]},"Jortt_V2_Shared_Entities_Amount":{"type":"object","properties":{"amount":{"type":"string"},"currency":{"type":"string"}},"required":["amount","currency"]},"Jortt_V2_Shared_Entities_Vat":{"type":"object","properties":{"value":{"type":"number","format":"double","example":"0.21","description":"A string representing vat percentage as a decimal"},"category":{"type":"string","example":"exempt","description":"A string describing a vat category. Can be null or one of the following [exempt, margin_goods_high, travel_services_high]. Note that special categories are only available if you have enabled the appropriate setting for your organization"}},"required":["value","category"]},"CreateInvoiceV3":{"type":"object","properties":{"customer_id":{"type":"string","description":"Resource identifier (UUID)","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a"},"invoice_date":{"type":"string","format":"date","description":"Date of the Invoice (determines the VAT period)","example":"2020-02-23"},"tradename_id":{"type":"string","description":"The ID of the Tradename used for this Invoice. It should a resource identifier (UUID). If not set the default Organization will be used.\n","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a"},"label_ids":{"type":"array","description":"An array of label ids for example['e508ba44-f8a9-4aa9-b4e8-8d071a3454c4', 'c2ea9b6d-dc5f-4af0-b1ec-46daa4d7a37b']","items":{"type":"string"}},"delivery_period":{"type":"string","format":"date","description":"Determines the profit and loss period start date of this Invoice. If `delivery_period_end` is not present the period is one month. **Required** if `delivery_period_end` is present.\n","example":"2020-02-01"},"delivery_period_end":{"type":"string","format":"date","description":"Determines the profit and loss period end date of this Invoice","example":"2020-02-01"},"payment_term":{"type":"integer","format":"int32","description":"Optional payment term for the Invoice. Defaults to the following first present value:\n - The `payment_term` configured on the Customer (referenced by the `customer_id` param).\n - The `payment_term` configured on the Organization (referenced by the `access_token`).\n - The global default payment term (30 days).\n","example":14},"net_amounts":{"type":"boolean","description":"Whether or not VAT is included in the `amount_per_unit` in the line item (this is typically used when\ninvoicing a private person rather than a company)\n","example":false},"send_method":{"type":"string","description":"How the Invoice should be sent","enum":["email","peppol","self"],"example":"email"},"introduction":{"type":"string","description":"Comments printed on the Invoice (above the line items)","example":"example"},"remarks":{"type":"string","description":"Remarks printed on the Invoice (under the line items)","example":"example"},"payment_method":{"type":"string","description":"How the Invoice can be paid. Determines the payment instructions printed on the Invoice. Choose from:\n - `already_paid` when the Invoice has already been paid. No payment instructions will be printed on the Invoice.\n - `pay_later` will print payment instructions on the Invoice.\n - `direct_debit` requires a connection with [Mollie](https://www.mollie.com). Only for Jortt Plus users.\n\nThe default is `pay_later`.\n","enum":["pay_later","direct_debit","already_paid"],"example":"pay_later"},"line_items":{"type":"array","description":"The line items of an Invoice","items":{"type":"object","properties":{"description":{"type":"string","description":"Description of the line item","example":"this is a description example"},"quantity":{"$ref":"#/definitions/Jortt_V2_Shared_Entities_Amount","description":"A numeric string representing a quantity. ","example":"22.1"},"amount":{"$ref":"#/definitions/Jortt_V2_Shared_Entities_Amount","description":"A hash representing a monetary amount, contains a string for amount and currency.","example":{"amount":"22.00","currency":"EUR"},"type":"object","properties":{"amount":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["amount","currency"]},"vat":{"$ref":"#/definitions/Jortt_V2_Shared_Entities_Vat","description":"A hash representing a vat category, contains a category string and a percentage integer.","example":{"value":"0.21","category":null},"type":"object","properties":{"value":{"type":"number","format":"double"},"category":{"type":"string"}},"required":["value","category"]},"ledger_account_id":{"type":"string","description":"Resource identifier (UUID) of the Ledger Account for this line_item. Null for estimate line items","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a"}},"required":["description","quantity","amount","vat"]}},"discounts":{"type":"array","description":"Discounts applied to the invoice","items":{"type":"object","properties":{"description":{"type":"string","description":"Description of the discount","example":"this is a description example"},"percentage":{"type":"integer","format":"int64","description":"The discount to apply expressed as a percentage","example":21}},"required":["percentage"]}},"reference":{"type":"string","description":"Custom reference (for example an order ID)","example":"123"},"sold_via_platform":{"type":"boolean","description":"Whether or not an Invoice is marked as having goods that are sold via a platform such as Amazon","example":false},"purchase_reference":{"type":"string","description":"Reference to the purchase order or invoice that this invoice is related to"}},"description":"Creates (and optionally sends) an Invoice V3 with invoice-level VAT"},"Jortt_V1_Entities_Responses_ResourceCreatedResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a","description":"The identifier of the created resource"}},"required":["id"]}},"required":["data"],"description":"Jortt_V1_Entities_Responses_ResourceCreatedResponse model"},"Jortt_V2_Invoicing_Invoices_Responses_GetPeppolSchemeCatalogResponse":{"type":"object","properties":{"data":{"type":"object","description":"Response object containing a hash of peppol scheme options by country code"}},"required":["data"],"description":"Jortt_V2_Invoicing_Invoices_Responses_GetPeppolSchemeCatalogResponse model"},"Jortt_V2_Invoicing_Invoices_Responses_ListInvoicesResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/definitions/Jortt_V2_Invoicing_Entities_Invoice"},"description":"Response object containing a list of Invoices"},"_links":{"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Link"}],"description":"Links to help navigate through the lists of objects."}},"required":["data","_links"],"description":"Jortt_V2_Invoicing_Invoices_Responses_ListInvoicesResponse model"},"Jortt_V2_Invoicing_Entities_Invoice":{"type":"object","properties":{"id":{"type":"string","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a","description":"Resource identifier (UUID)"},"invoice_status":{"type":"string","enum":["draft","sent"],"example":"draft","description":"The status of an Invoice"},"customer_id":{"type":"string","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a","description":"Resource identifier (UUID)"},"tradename_id":{"type":"string","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a","description":"The ID of the Tradename used for this Invoice. It should a resource identifier (UUID). If not set the default Organization will be used.\n"},"invoice_number":{"type":"string","example":"202001-002","description":"The generated unique Invoice number for this Invoice (only set when an Invoice is sent)\n"},"invoice_date":{"type":"string","format":"date","example":"2020-02-23","description":"Date of the Invoice (determines the VAT period)"},"invoice_due_date":{"type":"string","format":"date","example":"2020-03-22","description":"When the Invoice should be paid"},"invoice_delivery_period":{"type":"string","format":"date","example":"2020-02-01","description":"Determines the profit and loss period start date of this Invoice. If `delivery_period_end` is not present the period is one month. **Required** if `delivery_period_end` is present.\n"},"invoice_delivery_period_end":{"type":"string","format":"date","example":"2020-02-01","description":"Determines the profit and loss period end date of this Invoice"},"invoice_date_sent":{"type":"string","format":"date","example":"2020-02-23","description":"When the Invoice was sent"},"invoice_total":{"example":{"amount":"22.00","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V2_Shared_Entities_Amount"}],"description":"A hash representing a monetary amount, contains a string for amount and currency.Total amount of the Invoice excluding VAT"},"invoice_total_incl_vat":{"example":{"amount":"22.00","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V2_Shared_Entities_Amount"}],"description":"A hash representing a monetary amount, contains a string for amount and currency.Total amount of the Invoice including VAT"},"invoice_due_amount":{"example":{"amount":"22.00","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V2_Shared_Entities_Amount"}],"description":"A hash representing a monetary amount, contains a string for amount and currency.Total amount due including VAT"},"send_method":{"type":"string","enum":["email","peppol","self"],"example":"email","description":"How the Invoice should be sent"},"net_amounts":{"type":"boolean","example":false,"description":"Whether or not VAT is included in the `amount_per_unit` in the line item (this is typically used when\ninvoicing a private person rather than a company)\n"},"invoice_marked_free_of_vat":{"type":"boolean","example":false,"description":"Whether or not an Invoice is marked as having no VAT"},"credited_invoice_id":{"type":"string","example":"4c23005c-ccd3-4294-bde6-24c726aa8810","description":"Resource identifier (UUID) of the credited Invoice"},"remarks":{"type":"string","example":"example","description":"Remarks printed on the Invoice (under the line items)"},"introduction":{"type":"string","example":"example","description":"Comments printed on the Invoice (above the line items)"},"number_of_reminders_sent":{"type":"integer","format":"int32","example":0,"description":"Number of reminders sent to the Customer"},"last_reminded_at":{"type":"string","format":"date","example":"2026-07-11","description":"When the last reminder was sent to the Customer"},"payment_method":{"type":"string","enum":["pay_later","direct_debit","already_paid"],"example":"pay_later","description":"How the Invoice can be paid. Determines the payment instructions printed on the Invoice. Choose from:\n - `already_paid` when the Invoice has already been paid. No payment instructions will be printed on the Invoice.\n - `pay_later` will print payment instructions on the Invoice.\n - `direct_debit` requires a connection with [Mollie](https://www.mollie.com). Only for Jortt Plus users.\n\nThe default is `pay_later`.\n"},"customer_company_name":{"type":"string","example":"Jortt","description":"Either a company name (when `is_private` is `false`) or a private person's full name (when `is_private` is `true`)"},"customer_attn":{"type":"string","example":"Finance Department","description":"To the attention of"},"customer_address_street":{"type":"string","example":"Rozengracht 75a","description":"Street and house number of the address of a Customer (required when `is_private` is `false`)"},"customer_address_city":{"type":"string","example":"Amsterdam","description":"City of the address of a Customer (required when `is_private` is `false`)"},"customer_address_postal_code":{"type":"string","example":"1012 AB","description":"Postal code of the address of a Customer (required when `is_private` is `false`)"},"customer_address_country_code":{"type":"string","example":"NL","description":"Country code of a Customer in [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)\nformat (required when `is_private` is `false`, defaults to `NL`)\n"},"customer_address_country_name":{"type":"string","example":"Nederland","description":"Country name of a Customer (applicable when `is_private` is `false`, defaults to `NL`)"},"customer_address_extra_information":{"type":"string","example":"2nd floor","description":"Extra line of Customer address information"},"customer_vat_shifted":{"type":"boolean","example":false,"description":"Whether or not to shift the VAT for a Customer (applicable when `is_private` is `false`)"},"customer_vat_number":{"type":"string","example":"NL000099998B57","description":"The VAT number (BTW-nummer) of a Customer (applicable when `is_private` is `false` and `shift_vat` is\n`true`)\n"},"customer_in_eu":{"type":"boolean","example":true,"description":"Whether or not the Customer is in the EU (at the time of sending the Invoice)"},"customer_reference":{"type":"string","example":"BX123-123","description":"Custom reference (for example the ID of a customer in an external CRM)"},"customer_is_private":{"type":"boolean","example":true,"description":"Whether this Customer is a private person (`true`) or a company (`false`)"},"customer_mail_to":{"type":"string","example":"example@email.com","description":"E-mail address to send the Invoice to"},"customer_mail_cc_addresses":{"type":"array","items":{"type":"string"},"example":["example@email.com","example2@email.com"],"description":"An array of e-mail addresses to CC when the Invoice is sent"},"language":{"type":"string","enum":["nl","de","en","fr","es"],"example":"nl","description":"The language in which the Invoice will be translated"},"line_items":{"type":"array","items":{"$ref":"#/definitions/Jortt_V2_Invoicing_Entities_InvoiceLineItem"},"description":"line items of the invoice"},"discounts":{"type":"array","items":{"$ref":"#/definitions/Jortt_V1_Entities_Discount"},"description":"discounts applied to the invoice"},"reference":{"type":"string","example":"123","description":"Custom reference (for example an order ID)"},"created_at":{"type":"string","format":"date-time","example":"2020-02-23T00:00:00.000+01:00","description":"When the Invoice was created"},"credit_invoice_ids":{"type":"string","example":["e508ba44-f8a9-4aa9-b4e8-8d071a3454c4","c2ea9b6d-dc5f-4af0-b1ec-46daa4d7a37b"],"description":"The ids of the invoices this Invoice is credited by"},"peppol_status":{"type":"string","description":"A string reflecting the Peppol status of the invoice. Null if not sent via Peppol."}},"required":["id","invoice_status","customer_id","tradename_id","invoice_number","invoice_date","invoice_due_date","invoice_delivery_period","invoice_delivery_period_end","invoice_date_sent","invoice_total","invoice_total_incl_vat","invoice_due_amount","send_method","net_amounts","invoice_marked_free_of_vat","credited_invoice_id","remarks","introduction","number_of_reminders_sent","last_reminded_at","payment_method","customer_company_name","customer_attn","customer_address_street","customer_address_city","customer_address_postal_code","customer_address_country_code","customer_address_country_name","customer_address_extra_information","customer_vat_shifted","customer_vat_number","customer_in_eu","customer_reference","customer_is_private","customer_mail_to","customer_mail_cc_addresses","language","line_items","discounts","reference","created_at","credit_invoice_ids","peppol_status"]},"Jortt_V2_Invoicing_Entities_InvoiceLineItem":{"type":"object","properties":{"description":{"type":"string","example":"this is a description example","description":"Description of the line item"},"vat":{"example":{"value":"0.21","category":null},"allOf":[{"$ref":"#/definitions/Jortt_V2_Shared_Entities_Vat"}],"description":"A hash representing a vat category, contains a category string and a percentage integer."},"quantity":{"example":"22.1","allOf":[{"$ref":"#/definitions/Jortt_V2_Shared_Entities_Amount"}],"description":"A numeric string representing a quantity. The number of units being sold."},"amount":{"example":{"amount":"22.00","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V2_Shared_Entities_Amount"}],"description":"A hash representing a monetary amount, contains a string for amount and currency.The amount per unit being sold."},"total_amount_ex_vat":{"example":{"amount":"22.00","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V2_Shared_Entities_Amount"}],"description":"A hash representing a monetary amount, contains a string for amount and currency.The total amount for the line item excluding vat."},"ledger_account_id":{"type":"string","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a","description":"Resource identifier (UUID) of the Ledger Account for this line_item. Null for estimate line items"}},"required":["description","vat","quantity","amount","total_amount_ex_vat","ledger_account_id"]},"Jortt_V1_Entities_Discount":{"type":"object","properties":{"description":{"type":"string","example":"this is a description example","description":"Description of the discount"},"percentage":{"type":"integer","format":"int64","example":21,"description":"The discount to apply expressed as a percentage"}},"required":["description","percentage"]},"Jortt_V1_Entities_Link":{"type":"object","properties":{"previous":{"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Url"}],"description":"A URL object representing the previous set of objects, or `null` if not available."},"next":{"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Url"}],"description":"A URL object representing the next set of objects, or `null` if not available."},"self":{"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Url"}],"description":"A URL object representing the current set of objects."},"documentation":{"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Url"}],"description":"A URL object to the pagination documentation of the api."}},"required":["previous","next","self","documentation"]},"Jortt_V1_Entities_Url":{"type":"object","properties":{"href":{"type":"string","example":"https://api.jortt.nl/customers?page=1","description":"The URL."},"type":{"type":"string","enum":["application/json","text/html"],"description":"The content type of the URL."}},"required":["href","type"]},"Jortt_V2_Invoicing_Invoices_Responses_GetInvoiceResponse":{"type":"object","properties":{"data":{"allOf":[{"$ref":"#/definitions/Jortt_V2_Invoicing_Entities_Invoice"}],"description":"Response object containing a single Invoice"},"possible_actions":{"type":"array","items":{"type":"string"},"description":"A list of strings indicating actions that can be taken with the associated invoice"}},"required":["data","possible_actions"],"description":"Jortt_V2_Invoicing_Invoices_Responses_GetInvoiceResponse model"},"CreateInvoiceV2":{"type":"object","properties":{"customer_id":{"type":"string","description":"Resource identifier (UUID)","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a"},"invoice_date":{"type":"string","format":"date","description":"Date of the Invoice (determines the VAT period)","example":"2020-02-23"},"tradename_id":{"type":"string","description":"The ID of the Tradename used for this Invoice. It should a resource identifier (UUID). If not set the default Organization will be used.\n","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a"},"label_ids":{"type":"array","description":"An array of label ids for example['e508ba44-f8a9-4aa9-b4e8-8d071a3454c4', 'c2ea9b6d-dc5f-4af0-b1ec-46daa4d7a37b']","items":{"type":"string"}},"delivery_period":{"type":"string","format":"date","description":"Determines the profit and loss period start date of this Invoice. If `delivery_period_end` is not present the period is one month. **Required** if `delivery_period_end` is present.\n","example":"2020-02-01"},"delivery_period_end":{"type":"string","format":"date","description":"Determines the profit and loss period end date of this Invoice","example":"2020-02-01"},"payment_term":{"type":"integer","format":"int32","description":"Optional payment term for the Invoice. Defaults to the following first present value:\n - The `payment_term` configured on the Customer (referenced by the `customer_id` param).\n - The `payment_term` configured on the Organization (referenced by the `access_token`).\n - The global default payment term (30 days).\n","example":14},"net_amounts":{"type":"boolean","description":"Whether or not VAT is included in the `amount_per_unit` in the line item (this is typically used when\ninvoicing a private person rather than a company)\n","example":false},"send_method":{"type":"string","description":"How the Invoice should be sent","enum":["email","peppol","self"],"example":"email"},"introduction":{"type":"string","description":"Comments printed on the Invoice (above the line items)","example":"example"},"remarks":{"type":"string","description":"Remarks printed on the Invoice (under the line items)","example":"example"},"payment_method":{"type":"string","description":"How the Invoice can be paid. Determines the payment instructions printed on the Invoice. Choose from:\n - `already_paid` when the Invoice has already been paid. No payment instructions will be printed on the Invoice.\n - `pay_later` will print payment instructions on the Invoice.\n - `direct_debit` requires a connection with [Mollie](https://www.mollie.com). Only for Jortt Plus users.\n\nThe default is `pay_later`.\n","enum":["pay_later","direct_debit","already_paid"],"example":"pay_later"},"line_items":{"type":"array","description":"The line items of an Invoice","items":{"type":"object","properties":{"description":{"type":"string","description":"Description of the line item","example":"this is a description example"},"quantity":{"$ref":"#/definitions/Jortt_V2_Shared_Entities_Amount","description":"A numeric string representing a quantity. ","example":"22.1"},"amount":{"$ref":"#/definitions/Jortt_V2_Shared_Entities_Amount","description":"A hash representing a monetary amount, contains a string for amount and currency.","example":{"amount":"22.00","currency":"EUR"},"type":"object","properties":{"amount":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["amount","currency"]},"vat":{"$ref":"#/definitions/Jortt_V2_Shared_Entities_Vat","description":"A hash representing a vat category, contains a category string and a percentage integer.","example":{"value":"0.21","category":null},"type":"object","properties":{"value":{"type":"number","format":"double"},"category":{"type":"string"}},"required":["value","category"]},"ledger_account_id":{"type":"string","description":"Resource identifier (UUID) of the Ledger Account for this line_item. Null for estimate line items","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a"}},"required":["description","quantity","amount","vat"]}},"discounts":{"type":"array","description":"Discounts applied to the invoice","items":{"type":"object","properties":{"description":{"type":"string","description":"Description of the discount","example":"this is a description example"},"percentage":{"type":"integer","format":"int64","description":"The discount to apply expressed as a percentage","example":21}},"required":["percentage"]}},"reference":{"type":"string","description":"Custom reference (for example an order ID)","example":"123"},"sold_via_platform":{"type":"boolean","description":"Whether or not an Invoice is marked as having goods that are sold via a platform such as Amazon","example":false}},"description":"Creates (and optionally sends) an Invoice V2"},"EditInvoiceV2":{"type":"object","properties":{"customer_id":{"type":"string","description":"Resource identifier (UUID)","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a"},"invoice_date":{"type":"string","format":"date","description":"Date of the Invoice (determines the VAT period)","example":"2020-02-23"},"tradename_id":{"type":"string","description":"The ID of the Tradename used for this Invoice. It should a resource identifier (UUID). If not set the default Organization will be used.\n","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a"},"label_ids":{"type":"array","description":"An array of label ids for example['e508ba44-f8a9-4aa9-b4e8-8d071a3454c4', 'c2ea9b6d-dc5f-4af0-b1ec-46daa4d7a37b']","items":{"type":"string"}},"delivery_period":{"type":"string","format":"date","description":"Determines the profit and loss period start date of this Invoice. If `delivery_period_end` is not present the period is one month. **Required** if `delivery_period_end` is present.\n","example":"2020-02-01"},"delivery_period_end":{"type":"string","format":"date","description":"Determines the profit and loss period end date of this Invoice","example":"2020-02-01"},"payment_term":{"type":"integer","format":"int32","description":"Optional payment term for the Invoice. Defaults to the following first present value:\n - The `payment_term` configured on the Customer (referenced by the `customer_id` param).\n - The `payment_term` configured on the Organization (referenced by the `access_token`).\n - The global default payment term (30 days).\n","example":14},"net_amounts":{"type":"boolean","description":"Whether or not VAT is included in the `amount_per_unit` in the line item (this is typically used when\ninvoicing a private person rather than a company)\n","example":false},"send_method":{"type":"string","description":"How the Invoice should be sent","enum":["email","peppol","self"],"example":"email"},"introduction":{"type":"string","description":"Comments printed on the Invoice (above the line items)","example":"example"},"remarks":{"type":"string","description":"Remarks printed on the Invoice (under the line items)","example":"example"},"payment_method":{"type":"string","description":"How the Invoice can be paid. Determines the payment instructions printed on the Invoice. Choose from:\n - `already_paid` when the Invoice has already been paid. No payment instructions will be printed on the Invoice.\n - `pay_later` will print payment instructions on the Invoice.\n - `direct_debit` requires a connection with [Mollie](https://www.mollie.com). Only for Jortt Plus users.\n\nThe default is `pay_later`.\n","enum":["pay_later","direct_debit","already_paid"],"example":"pay_later"},"line_items":{"type":"array","description":"The line items of an Invoice","items":{"type":"object","properties":{"description":{"type":"string","description":"Description of the line item","example":"this is a description example"},"quantity":{"$ref":"#/definitions/Jortt_V2_Shared_Entities_Amount","description":"A numeric string representing a quantity. ","example":"22.1"},"amount":{"$ref":"#/definitions/Jortt_V2_Shared_Entities_Amount","description":"A hash representing a monetary amount, contains a string for amount and currency.","example":{"amount":"22.00","currency":"EUR"},"type":"object","properties":{"amount":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["amount","currency"]},"vat":{"$ref":"#/definitions/Jortt_V2_Shared_Entities_Vat","description":"A hash representing a vat category, contains a category string and a percentage integer.","example":{"value":"0.21","category":null},"type":"object","properties":{"value":{"type":"number","format":"double"},"category":{"type":"string"}},"required":["value","category"]},"ledger_account_id":{"type":"string","description":"Resource identifier (UUID) of the Ledger Account for this line_item. Null for estimate line items","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a"}}}},"discounts":{"type":"array","description":"Discounts applied to the invoice","items":{"type":"object","properties":{"description":{"type":"string","description":"Description of the discount","example":"this is a description example"},"percentage":{"type":"integer","format":"int64","description":"The discount to apply expressed as a percentage","example":21}},"required":["percentage"]}},"reference":{"type":"string","description":"Custom reference (for example an order ID)","example":"123"}},"description":"Edits an Invoice V2"},"Jortt_V2_Invoicing_Invoices_Responses_GetInvoiceLineItemSuggestionsResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/definitions/Jortt_V2_Invoicing_Entities_InvoiceLineItemSuggestion"},"description":"Response object containing a summary of line items for the given project"}},"required":["data"],"description":"Jortt_V2_Invoicing_Invoices_Responses_GetInvoiceLineItemSuggestionsResponse model"},"Jortt_V2_Invoicing_Entities_InvoiceLineItemSuggestion":{"type":"object","properties":{"description":{"type":"string","example":"this is a description example","description":"Description of the line item"},"vat":{"example":{"value":"0.21","category":null},"allOf":[{"$ref":"#/definitions/Jortt_V2_Shared_Entities_Vat"}],"description":"A hash representing a vat category, contains a category string and a percentage integer."},"quantity":{"example":"22.1","allOf":[{"$ref":"#/definitions/Jortt_V2_Shared_Entities_Amount"}],"description":"A numeric string representing a quantity. The number of units being sold."},"amount":{"example":{"amount":"22.00","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V2_Shared_Entities_Amount"}],"description":"A hash representing a monetary amount, contains a string for amount and currency.The price for each unit being sold."},"ledger_account_id":{"type":"string","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a","description":"Resource identifier (UUID) of the Ledger Account for this line_item. Null for estimate line items"}},"required":["description","vat","quantity","amount","ledger_account_id"]},"Jortt_V1_Entities_Responses_GetInvoiceResponse":{"type":"object","properties":{"data":{"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Invoice"}],"description":"Response object containing a single Invoice"},"possible_actions":{"type":"array","items":{"type":"string"},"description":"A list of strings indicating actions that can be taken with the associated invoice"}},"required":["data","possible_actions"],"description":"Jortt_V1_Entities_Responses_GetInvoiceResponse model"},"Jortt_V1_Entities_Invoice":{"type":"object","properties":{"id":{"type":"string","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a","description":"Resource identifier (UUID)"},"invoice_status":{"type":"string","enum":["draft","sent"],"example":"draft","description":"The status of an Invoice"},"customer_id":{"type":"string","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a","description":"Resource identifier (UUID)"},"tradename_id":{"type":"string","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a","description":"The ID of the Tradename used for this Invoice. It should a resource identifier (UUID). If not set the default Organization will be used.\n"},"invoice_number":{"type":"string","example":"202001-002","description":"The generated unique Invoice number for this Invoice (only set when an Invoice is sent)\n"},"invoice_date":{"type":"string","format":"date","example":"2020-02-23","description":"Date of the Invoice (determines the VAT period)"},"invoice_due_date":{"type":"string","format":"date","example":"2020-03-22","description":"When the Invoice should be paid"},"invoice_delivery_period":{"type":"string","format":"date","example":"2020-02-01","description":"Determines the profit and loss period start date of this Invoice. If `delivery_period_end` is not present the period is one month. **Required** if `delivery_period_end` is present.\n"},"invoice_delivery_period_end":{"type":"string","format":"date","example":"2020-02-01","description":"Determines the profit and loss period end date of this Invoice"},"invoice_date_sent":{"type":"string","format":"date","example":"2020-02-23","description":"When the Invoice was sent"},"invoice_total":{"example":{"value":"10.10","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}],"description":"Total amount of the Invoice excluding VAT"},"invoice_total_incl_vat":{"example":{"value":"10.10","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}],"description":"Total amount of the Invoice including VAT"},"invoice_due_amount":{"example":{"value":"10.10","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}],"description":"Total amount due of the Invoice including VAT"},"send_method":{"type":"string","enum":["email","peppol","self"],"example":"email","description":"How the Invoice should be sent"},"net_amounts":{"type":"boolean","example":false,"description":"Whether or not VAT is included in the `amount_per_unit` in the line item (this is typically used when\ninvoicing a private person rather than a company)\n"},"invoice_marked_free_of_vat":{"type":"boolean","example":false,"description":"Whether or not an Invoice is marked as having no VAT"},"credited_invoice_id":{"type":"string","example":"4c23005c-ccd3-4294-bde6-24c726aa8810","description":"Resource identifier (UUID) of the credited Invoice"},"remarks":{"type":"string","example":"example","description":"Remarks printed on the Invoice (under the line items)"},"introduction":{"type":"string","example":"example","description":"Comments printed on the Invoice (above the line items)"},"number_of_reminders_sent":{"type":"integer","format":"int32","example":0,"description":"Number of reminders sent to the Customer"},"last_reminded_at":{"type":"string","format":"date","example":"2026-07-11","description":"When the last reminder was sent to the Customer"},"payment_method":{"type":"string","enum":["pay_later","direct_debit","already_paid"],"example":"pay_later","description":"How the Invoice can be paid. Determines the payment instructions printed on the Invoice. Choose from:\n - `already_paid` when the Invoice has already been paid. No payment instructions will be printed on the Invoice.\n - `pay_later` will print payment instructions on the Invoice.\n - `direct_debit` requires a connection with [Mollie](https://www.mollie.com). Only for Jortt Plus users.\n\nThe default is `pay_later`.\n"},"customer_company_name":{"type":"string","example":"Jortt","description":"Either a company name (when `is_private` is `false`) or a private person's full name (when `is_private` is `true`)"},"customer_attn":{"type":"string","example":"Finance Department","description":"To the attention of"},"customer_address_street":{"type":"string","example":"Rozengracht 75a","description":"Street and house number of the address of a Customer (required when `is_private` is `false`)"},"customer_address_city":{"type":"string","example":"Amsterdam","description":"City of the address of a Customer (required when `is_private` is `false`)"},"customer_address_postal_code":{"type":"string","example":"1012 AB","description":"Postal code of the address of a Customer (required when `is_private` is `false`)"},"customer_address_country_code":{"type":"string","example":"NL","description":"Country code of a Customer in [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)\nformat (required when `is_private` is `false`, defaults to `NL`)\n"},"customer_address_country_name":{"type":"string","example":"Nederland","description":"Country name of a Customer (applicable when `is_private` is `false`, defaults to `NL`)"},"customer_address_extra_information":{"type":"string","example":"2nd floor","description":"Extra line of Customer address information"},"customer_vat_shifted":{"type":"boolean","example":false,"description":"Whether or not to shift the VAT for a Customer (applicable when `is_private` is `false`)"},"customer_vat_number":{"type":"string","example":"NL000099998B57","description":"The VAT number (BTW-nummer) of a Customer (applicable when `is_private` is `false` and `shift_vat` is\n`true`)\n"},"customer_in_eu":{"type":"boolean","example":true,"description":"Whether or not the Customer is in the EU (at the time of sending the Invoice)"},"customer_reference":{"type":"string","example":"BX123-123","description":"Custom reference (for example the ID of a customer in an external CRM)"},"customer_is_private":{"type":"boolean","example":true,"description":"Whether this Customer is a private person (`true`) or a company (`false`)"},"customer_mail_to":{"type":"string","example":"example@email.com","description":"E-mail address to send the Invoice to"},"customer_mail_cc_addresses":{"type":"array","items":{"type":"string"},"example":["example@email.com","example2@email.com"],"description":"An array of e-mail addresses to CC when the Invoice is sent"},"language":{"type":"string","enum":["nl","de","en","fr","es"],"example":"nl","description":"The language in which the Invoice will be translated"},"line_items":{"type":"array","items":{"$ref":"#/definitions/Jortt_V1_Entities_LineItem"},"description":"line items of the invoice"},"discounts":{"type":"array","items":{"$ref":"#/definitions/Jortt_V1_Entities_Discount"},"description":"discounts applied to the invoice"},"reference":{"type":"string","example":"123","description":"Custom reference (for example an order ID)"},"created_at":{"type":"string","format":"date-time","example":"2020-02-23T00:00:00.000+01:00","description":"When the Invoice was created"},"credit_invoice_ids":{"type":"string","example":["e508ba44-f8a9-4aa9-b4e8-8d071a3454c4","c2ea9b6d-dc5f-4af0-b1ec-46daa4d7a37b"],"description":"The ids of the invoices this Invoice is credited by"},"peppol_status":{"type":"string","description":"A string reflecting the Peppol status of the invoice. Null if not sent via Peppol."}},"required":["id","invoice_status","customer_id","tradename_id","invoice_number","invoice_date","invoice_due_date","invoice_delivery_period","invoice_delivery_period_end","invoice_date_sent","invoice_total","invoice_total_incl_vat","invoice_due_amount","send_method","net_amounts","invoice_marked_free_of_vat","credited_invoice_id","remarks","introduction","number_of_reminders_sent","last_reminded_at","payment_method","customer_company_name","customer_attn","customer_address_street","customer_address_city","customer_address_postal_code","customer_address_country_code","customer_address_country_name","customer_address_extra_information","customer_vat_shifted","customer_vat_number","customer_in_eu","customer_reference","customer_is_private","customer_mail_to","customer_mail_cc_addresses","language","line_items","discounts","reference","created_at","credit_invoice_ids","peppol_status"]},"Jortt_V1_Entities_Amount":{"type":"object","properties":{"value":{"type":"number","format":"double","example":"365.00","description":"Amount per unit of the line item"},"currency":{"type":"string","enum":["EUR"],"example":"EUR","description":"Currency of the line item"}},"required":["value","currency"]},"Jortt_V1_Entities_LineItem":{"type":"object","properties":{"description":{"type":"string","example":"this is a description example","description":"Description of the line item"},"vat_percentage":{"type":"string","example":"21.0","description":"VAT percentage of the line item as a string"},"vat":{"type":"integer","format":"int64","example":21,"description":"(**deprecated** in favour of `vat_percentage`) VAT percentage of the line item"},"units":{"type":"number","format":"double","example":3.14,"description":"(**deprecated** in favour of `number_of_units`) Number of units of the line item"},"number_of_units":{"type":"string","example":"3.14","description":"Number of units of the line item as a string"},"amount_per_unit":{"example":{"value":"10.10","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}],"description":"Amount of the line item"},"total_amount_excl_vat":{"example":{"value":"10.10","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}],"description":"Amount of the line item"},"ledger_account_id":{"type":"string","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a","description":"Resource identifier (UUID) of the Ledger Account for this line_item. Null for estimate line items"}},"required":["description","vat_percentage","vat","units","number_of_units","amount_per_unit","total_amount_excl_vat","ledger_account_id"]},"SetLabels":{"type":"array","items":{"type":"object","properties":{"label_ids":{"type":"array","description":"An array of label ids for example['e508ba44-f8a9-4aa9-b4e8-8d071a3454c4', 'c2ea9b6d-dc5f-4af0-b1ec-46daa4d7a37b']","items":{"type":"string"}}},"required":["label_ids"]},"description":"Sets the labels for a given invoice"},"CreateCreditInvoice":{"type":"object","properties":{"send_method":{"type":"string","description":"How the Invoice should be sent","enum":["email","peppol","self"],"example":"email"}},"description":"Creates (and optionally sends) a credit Invoice"},"Jortt_V1_Entities_Responses_ListInvoicesResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/definitions/Jortt_V1_Entities_Invoice"},"description":"Response object containing a list of Invoices"},"_links":{"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Link"}],"description":"Links to help navigate through the lists of objects."}},"required":["data","_links"],"description":"Jortt_V1_Entities_Responses_ListInvoicesResponse model"},"Jortt_V1_Entities_Responses_DownloadInvoiceOrEstimatePdfResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"download_location":{"type":"string","example":"https://files.jortt.nl/storage/042e407f-78d6-4dea-9ca9-5eca3097c220?type=attachment&filename=1.pdf","description":"The link where you can download the invoice or estimate PDF in a data object."}},"required":["download_location"]}},"required":["data"],"description":"Jortt_V1_Entities_Responses_DownloadInvoiceOrEstimatePdfResponse model"},"Jortt_V1_Entities_Responses_NextPossibleInvoiceNumberResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"next_possible_invoice_number":{"type":"string","example":"202401-001","description":"The next possible invoice number."}},"required":["next_possible_invoice_number"]}},"required":["data"],"description":"Jortt_V1_Entities_Responses_NextPossibleInvoiceNumberResponse model"},"Jortt_V1_Entities_Responses_GetInvoiceLineItemSuggestionsResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/definitions/Jortt_V1_Entities_InvoiceLineItemSuggestion"},"description":"Response object containing a summary of line items for the given project"}},"required":["data"],"description":"Jortt_V1_Entities_Responses_GetInvoiceLineItemSuggestionsResponse model"},"Jortt_V1_Entities_InvoiceLineItemSuggestion":{"type":"object","properties":{"description":{"type":"string","example":"this is a description example","description":"Description of the line item"},"vat":{"type":"integer","format":"int64","example":21,"description":"(**deprecated** in favour of `vat_percentage`) VAT percentage of the line item"},"quantity":{"type":"number","format":"double","example":3.14,"description":"(**deprecated** in favour of `number_of_units`) Number of units of the line item"},"amount":{"example":{"value":"10.10","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}],"description":"Amount of the line item"},"ledger_account_id":{"type":"string","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a","description":"Resource identifier (UUID) of the Ledger Account for this line_item. Null for estimate line items"}},"required":["description","vat","quantity","amount","ledger_account_id"]},"CreateInvoice":{"type":"object","properties":{"customer_id":{"type":"string","description":"Resource identifier (UUID)","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a"},"invoice_date":{"type":"string","format":"date","description":"Date of the Invoice (determines the VAT period)","example":"2020-02-23"},"tradename_id":{"type":"string","description":"The ID of the Tradename used for this Invoice. It should a resource identifier (UUID). If not set the default Organization will be used.\n","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a"},"label_ids":{"type":"array","description":"An array of label ids for example['e508ba44-f8a9-4aa9-b4e8-8d071a3454c4', 'c2ea9b6d-dc5f-4af0-b1ec-46daa4d7a37b']","items":{"type":"string"}},"delivery_period":{"type":"string","format":"date","description":"Determines the profit and loss period start date of this Invoice. If `delivery_period_end` is not present the period is one month. **Required** if `delivery_period_end` is present.\n","example":"2020-02-01"},"delivery_period_end":{"type":"string","format":"date","description":"Determines the profit and loss period end date of this Invoice","example":"2020-02-01"},"payment_term":{"type":"integer","format":"int32","description":"Optional payment term for the Invoice. Defaults to the following first present value:\n - The `payment_term` configured on the Customer (referenced by the `customer_id` param).\n - The `payment_term` configured on the Organization (referenced by the `access_token`).\n - The global default payment term (30 days).\n","example":14},"net_amounts":{"type":"boolean","description":"Whether or not VAT is included in the `amount_per_unit` in the line item (this is typically used when\ninvoicing a private person rather than a company)\n","example":false},"send_method":{"type":"string","description":"How the Invoice should be sent","enum":["email","peppol","self"],"example":"email"},"introduction":{"type":"string","description":"Comments printed on the Invoice (above the line items)","example":"example"},"remarks":{"type":"string","description":"Remarks printed on the Invoice (under the line items)","example":"example"},"payment_method":{"type":"string","description":"How the Invoice can be paid. Determines the payment instructions printed on the Invoice. Choose from:\n - `already_paid` when the Invoice has already been paid. No payment instructions will be printed on the Invoice.\n - `pay_later` will print payment instructions on the Invoice.\n - `direct_debit` requires a connection with [Mollie](https://www.mollie.com). Only for Jortt Plus users.\n\nThe default is `pay_later`.\n","enum":["pay_later","direct_debit","already_paid"],"example":"pay_later"},"line_items":{"type":"array","description":"The line items of an Invoice","items":{"type":"object","properties":{"description":{"type":"string","description":"Description of the line item","example":"this is a description example"},"units":{"type":"number","format":"double","description":"(**deprecated** in favour of `number_of_units`) Number of units of the line item","example":3.14},"number_of_units":{"type":"string","description":"**Required**: Number of units of the line item as a string","example":"3.14"},"amount_per_unit":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount per unit per line item","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"vat":{"type":"integer","format":"int64","description":"(**deprecated** in favour of `vat_percentage`) VAT percentage of the line item","example":21},"vat_percentage":{"type":"string","description":"**Required**: VAT percentage of the line item as a string","example":"21.0"},"ledger_account_id":{"type":"string","description":"Resource identifier (UUID) of the Ledger Account for this line_item. Null for estimate line items","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a"}},"required":["description"]}},"discounts":{"type":"array","description":"Discounts applied to the invoice","items":{"type":"object","properties":{"description":{"type":"string","description":"Description of the discount","example":"this is a description example"},"percentage":{"type":"integer","format":"int64","description":"The discount to apply expressed as a percentage","example":21}},"required":["percentage"]}},"reference":{"type":"string","description":"Custom reference (for example an order ID)","example":"123"},"sold_via_platform":{"type":"boolean","description":"Whether or not an Invoice is marked as having goods that are sold via a platform such as Amazon","example":false}},"description":"Creates (and optionally sends) an Invoice"},"EditInvoice":{"type":"object","properties":{"customer_id":{"type":"string","description":"Resource identifier (UUID)","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a"},"invoice_date":{"type":"string","format":"date","description":"Date of the Invoice (determines the VAT period)","example":"2020-02-23"},"tradename_id":{"type":"string","description":"The ID of the Tradename used for this Invoice. It should a resource identifier (UUID). If not set the default Organization will be used.\n","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a"},"label_ids":{"type":"array","description":"An array of label ids for example['e508ba44-f8a9-4aa9-b4e8-8d071a3454c4', 'c2ea9b6d-dc5f-4af0-b1ec-46daa4d7a37b']","items":{"type":"string"}},"delivery_period":{"type":"string","format":"date","description":"Determines the profit and loss period start date of this Invoice. If `delivery_period_end` is not present the period is one month. **Required** if `delivery_period_end` is present.\n","example":"2020-02-01"},"delivery_period_end":{"type":"string","format":"date","description":"Determines the profit and loss period end date of this Invoice","example":"2020-02-01"},"payment_term":{"type":"integer","format":"int32","description":"Optional payment term for the Invoice. Defaults to the following first present value:\n - The `payment_term` configured on the Customer (referenced by the `customer_id` param).\n - The `payment_term` configured on the Organization (referenced by the `access_token`).\n - The global default payment term (30 days).\n","example":14},"net_amounts":{"type":"boolean","description":"Whether or not VAT is included in the `amount_per_unit` in the line item (this is typically used when\ninvoicing a private person rather than a company)\n","example":false},"send_method":{"type":"string","description":"How the Invoice should be sent","enum":["email","peppol","self"],"example":"email"},"introduction":{"type":"string","description":"Comments printed on the Invoice (above the line items)","example":"example"},"remarks":{"type":"string","description":"Remarks printed on the Invoice (under the line items)","example":"example"},"payment_method":{"type":"string","description":"How the Invoice can be paid. Determines the payment instructions printed on the Invoice. Choose from:\n - `already_paid` when the Invoice has already been paid. No payment instructions will be printed on the Invoice.\n - `pay_later` will print payment instructions on the Invoice.\n - `direct_debit` requires a connection with [Mollie](https://www.mollie.com). Only for Jortt Plus users.\n\nThe default is `pay_later`.\n","enum":["pay_later","direct_debit","already_paid"],"example":"pay_later"},"invoice_marked_free_of_vat":{"type":"boolean","description":"Whether or not an Invoice is marked as having no VAT","example":false},"line_items":{"type":"array","description":"The line items of an Invoice","items":{"type":"object","properties":{"description":{"type":"string","description":"Description of the line item","example":"this is a description example"},"number_of_units":{"type":"string","description":"**Required**: Number of units of the line item as a string","example":"3.14"},"amount_per_unit":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount per unit per line item","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}}},"vat":{"type":"integer","format":"int64","description":"(**deprecated** in favour of `vat_percentage`) VAT percentage of the line item","example":21},"vat_percentage":{"type":"string","description":"**Required**: VAT percentage of the line item as a string","example":"21.0"},"ledger_account_id":{"type":"string","description":"Resource identifier (UUID) of the Ledger Account for this line_item. Null for estimate line items","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a"},"units":{"type":"string"}}}},"discounts":{"type":"array","description":"Discounts applied to the invoice","items":{"type":"object","properties":{"description":{"type":"string","description":"Description of the discount","example":"this is a description example"},"percentage":{"type":"integer","format":"int64","description":"The discount to apply expressed as a percentage","example":21}},"required":["percentage"]}},"reference":{"type":"string","description":"Custom reference (for example an order ID)","example":"123"}},"description":"Edits an Invoice"},"SendInvoice":{"type":"object","properties":{"send_method":{"type":"string","description":"How the Invoice should be sent","enum":["email","peppol","self"],"example":"email"},"email_address_customer":{"type":"string","description":"The email address to send the invoice too. Required if sending by email."},"mail_subject":{"type":"string","description":"Subject of the email that will be sent with the invoice. Required if sending by email."},"mail_body":{"type":"string","description":"Body of the email that will be sent with the invoice. Required if sending by email."},"send_copy_to_me":{"type":"boolean","description":"If true a copy of the invoice email will be sent to the users registered email."},"cc_addresses":{"type":"array","description":"Extra email addresses to CC when sending the invoice by email.","items":{"type":"string"}},"attachment_ids":{"type":"array","description":"The attachments to send with the email","items":{"type":"string"}},"attachment_mime_types":{"type":"array","description":"The MIME types of the attachments to send with the email","items":{"type":"string"}},"peppol_scheme_code":{"type":"string","description":"The Code that described the given Peppol identifier. Required if sending by Peppol."},"peppol_identifier":{"type":"string","description":"The Peppol identifier of the recipient. Required if sending by Peppol."}},"required":["send_method"],"description":"Sends an Invoice"},"Jortt_V1_Entities_Responses_SendSettingsInvoiceResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"default_mail_subject":{"type":"string","description":"Default email subject for this invoice"},"default_mail_body":{"type":"string","description":"Default email body for this invoice."},"supported_attachment_types":{"type":"string","enum":["image/bmp","image/x-windows-bmp","image/gif","image/jpeg","image/jpg","image/pjpeg","image/png","image/x-png","application/pdf","image/tif","image/x-tif","image/tiff","image/x-tiff","application/tif","application/x-tif","application/tiff","application/x-tiff","application/msword","application/vnd.openxmlformats-officedocument.wordprocessingml.document","application/vnd.ms-excel","application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"],"example":"image/jpeg","description":"The MIME type of the attachment"},"default_attachments":{"type":"array","items":{"$ref":"#/definitions/Jortt_V1_Entities_File"},"description":"Response object containing a list of attachments"}},"required":["default_mail_subject","default_mail_body","supported_attachment_types","default_attachments"]}},"required":["data"],"description":"Jortt_V1_Entities_Responses_SendSettingsInvoiceResponse model"},"Jortt_V1_Entities_File":{"type":"object","properties":{"id":{"type":"string","example":"531b4c45-1e15-420a-a5ff-bac58cf00b68","description":"The id of the attachment"},"name":{"type":"string","example":"image.jpeg","description":"The name of the attachment"},"mime_type":{"type":"string","enum":["image/bmp","image/x-windows-bmp","image/gif","image/jpeg","image/jpg","image/pjpeg","image/png","image/x-png","application/pdf","image/tif","image/x-tif","image/tiff","image/x-tiff","application/tif","application/x-tif","application/tiff","application/x-tiff","application/msword","application/vnd.openxmlformats-officedocument.wordprocessingml.document","application/vnd.ms-excel","application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"],"example":"image/jpeg","description":"The MIME type of the attachment"}},"required":["id","name","mime_type"]},"Jortt_V3_Invoicing_Expenses_Responses_ListExpensesResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/definitions/Jortt_V3_Invoicing_Expenses_Responses_Expense"},"description":"Response object containing a list of Expenses"},"_links":{"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Link"}],"description":"Links to help navigate through the lists of objects."}},"required":["data","_links"],"description":"Jortt_V3_Invoicing_Expenses_Responses_ListExpensesResponse model"},"Jortt_V3_Invoicing_Expenses_Responses_Expense":{"type":"object","properties":{"id":{"type":"string","description":"The identifier of the expense"},"expense_number":{"type":"string","description":"The expense number"},"description":{"type":"string","description":"Description of the expense"},"expense_type":{"type":"string","description":"Type of expense"},"ledger_account_id":{"type":"string","description":"Ledger account identifier"},"ledger_account_name":{"type":"string","description":"The display name of the ledger account"},"vat_date":{"type":"string","format":"date","description":"VAT date"},"delivery_period":{"type":"string","format":"date","description":"Delivery period start date"},"delivery_period_end":{"type":"string","format":"date","description":"Delivery period end date"},"vat_type":{"type":"string","description":"VAT type"},"approved_at":{"type":"string","format":"date-time","description":"Timestamp when the expense was approved"},"total_amount_incl_vat":{"example":{"amount":"22.00","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V2_Shared_Entities_Amount"}],"description":"A hash representing a monetary amount, contains a string for amount and currency.Total amount including VAT"},"grand_total_vat_amount":{"example":{"amount":"22.00","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V2_Shared_Entities_Amount"}],"description":"A hash representing a monetary amount, contains a string for amount and currency.Total VAT amount"},"vat_line_items":{"type":"array","items":{"$ref":"#/definitions/Jortt_V3_Invoicing_Expenses_Responses_ExpenseVatLineItem"},"description":"VAT line items"}},"required":["id","expense_number","description","expense_type","ledger_account_id","ledger_account_name","vat_date","delivery_period","delivery_period_end","vat_type","approved_at","total_amount_incl_vat","grand_total_vat_amount","vat_line_items"]},"Jortt_V3_Invoicing_Expenses_Responses_ExpenseVatLineItem":{"type":"object","properties":{"vat":{"example":{"value":"0.21","category":null},"allOf":[{"$ref":"#/definitions/Jortt_V2_Shared_Entities_Vat"}],"description":"A hash representing a vat category, contains a category string and a percentage integer."},"vat_amount":{"example":{"amount":"22.00","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V2_Shared_Entities_Amount"}],"description":"A hash representing a monetary amount, contains a string for amount and currency.VAT amount"}},"required":["vat","vat_amount"]},"Jortt_V3_Invoicing_Expenses_Responses_GetExpenseResponse":{"type":"object","properties":{"data":{"allOf":[{"$ref":"#/definitions/Jortt_V3_Invoicing_Expenses_Responses_Expense"}],"description":"Response object containing a single Expense"}},"required":["data"],"description":"Jortt_V3_Invoicing_Expenses_Responses_GetExpenseResponse model"},"UpdateExpense":{"type":"object","properties":{"approved":{"type":"boolean","description":"Whether the expense is approved"},"description":{"type":"string","description":"Description of the expense"},"ledger_account_id":{"type":"string","description":"Ledger account identifier"},"expense_type":{"type":"string","description":"Type of expense. One of: cost, income, balance","enum":["cost","income","balance"]},"vat_date":{"type":"string","format":"date","description":"VAT date of the expense"},"delivery_period":{"type":"string","format":"date","description":"Delivery period start date (required for cost and income)"},"delivery_period_end":{"type":"string","format":"date","description":"Delivery period end date"},"vat_type":{"type":"string","description":"VAT type of the expense","enum":["btw_type_leverancier_uit_nl","btw_type_leverancier_uit_nl_btw_verlegd","btw_type_leverancier_binnen_eu","btw_type_leverancier_binnen_eu_btw_verlegd","btw_type_leverancier_buiten_eu","btw_type_leverancier_buiten_eu_btw_verlegd","btw_type_particulier_binnen_de_eu_margeregeling_hoog","btw_type_leverancier_reisproduct_reisbureauregeling_hoog"]},"raw_total_amount":{"$ref":"#/definitions/Jortt_V2_Shared_Entities_Amount","description":"A hash representing a monetary amount, contains a string for amount and currency.Total amount including VAT","example":{"amount":"22.00","currency":"EUR"},"type":"object","properties":{"amount":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["amount","currency"]},"vat_line_items":{"type":"array","description":"VAT line items","items":{"type":"object","properties":{"vat":{"$ref":"#/definitions/Jortt_V2_Shared_Entities_Vat","description":"A hash representing a vat category, contains a category string and a percentage integer.","example":{"value":"0.21","category":null},"type":"object","properties":{"value":{"type":"number","format":"double"},"category":{"type":"string"}},"required":["value","category"]},"vat_amount":{"$ref":"#/definitions/Jortt_V2_Shared_Entities_Amount","description":"A hash representing a monetary amount, contains a string for amount and currency.VAT amount","example":{"amount":"22.00","currency":"EUR"},"type":"object","properties":{"amount":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["amount","currency"]}},"required":["vat_amount"]}}},"required":["description","ledger_account_id","expense_type","vat_date","delivery_period","vat_type","raw_total_amount"],"description":"Update an Expense"},"AttachReceiptToExpense":{"type":"object","properties":{"receipt_id":{"type":"string","description":"Receipt identifier (UUID)"}},"required":["receipt_id"],"description":"Attach a Receipt to an Expense"},"CreateExpense":{"type":"object","properties":{"approved":{"type":"boolean","description":"Whether the expense is approved"},"description":{"type":"string","description":"Description of the expense"},"ledger_account_id":{"type":"string","description":"Ledger account identifier"},"expense_type":{"type":"string","description":"Type of expense. One of: cost, income, balance","enum":["cost","income","balance"]},"vat_date":{"type":"string","format":"date","description":"VAT date of the expense"},"delivery_period":{"type":"string","format":"date","description":"Delivery period start date (required for cost and income)"},"delivery_period_end":{"type":"string","format":"date","description":"Delivery period end date"},"vat_type":{"type":"string","description":"VAT type of the expense","enum":["btw_type_leverancier_uit_nl","btw_type_leverancier_uit_nl_btw_verlegd","btw_type_leverancier_binnen_eu","btw_type_leverancier_binnen_eu_btw_verlegd","btw_type_leverancier_buiten_eu","btw_type_leverancier_buiten_eu_btw_verlegd","btw_type_particulier_binnen_de_eu_margeregeling_hoog","btw_type_leverancier_reisproduct_reisbureauregeling_hoog"]},"raw_total_amount":{"$ref":"#/definitions/Jortt_V2_Shared_Entities_Amount","description":"A hash representing a monetary amount, contains a string for amount and currency.Total amount including VAT","example":{"amount":"22.00","currency":"EUR"},"type":"object","properties":{"amount":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["amount","currency"]},"vat_line_items":{"type":"array","description":"VAT line items","items":{"type":"object","properties":{"vat":{"$ref":"#/definitions/Jortt_V2_Shared_Entities_Vat","description":"A hash representing a vat category, contains a category string and a percentage integer.","example":{"value":"0.21","category":null},"type":"object","properties":{"value":{"type":"number","format":"double"},"category":{"type":"string"}},"required":["value","category"]},"vat_amount":{"$ref":"#/definitions/Jortt_V2_Shared_Entities_Amount","description":"A hash representing a monetary amount, contains a string for amount and currency.VAT amount","example":{"amount":"22.00","currency":"EUR"},"type":"object","properties":{"amount":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["amount","currency"]}},"required":["vat_amount"]}}},"required":["description","ledger_account_id","expense_type","vat_date","delivery_period","vat_type","raw_total_amount"],"description":"Create an Expense"},"Jortt_V1_Entities_Responses_ListLedgerAccountsResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/definitions/Jortt_V1_Entities_LedgerAccount"},"description":"Response object containing a list of Ledger Accounts"}},"required":["data"],"description":"Jortt_V1_Entities_Responses_ListLedgerAccountsResponse model"},"Jortt_V1_Entities_LedgerAccount":{"type":"object","properties":{"ledger_account_id":{"type":"string","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a","description":"Resource identifier (UUID)"},"parent_ledger_account_id":{"type":"string","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a","description":"Resource identifier (UUID)"},"name":{"type":"string","example":"Uitbesteed werk","description":"A human readable name describing the Ledger Account"},"selectable":{"type":"boolean","example":true,"description":"Whether you can choose this Ledger Account in Bookings and/or Invoices.\nSince the Ledger is a tree of Ledger Accounts some Ledger Accounts serve as Nodes, to group underlying Ledger Accounts.\nThese Ledger Accounts can not be used in Invoices or Bookings, typically the underlying Ledger Accounts (Leaves) can be used.\n"}},"required":["ledger_account_id","parent_ledger_account_id","name","selectable"]},"Jortt_V1_Entities_Responses_GetLedgerAccountResponse":{"type":"object","properties":{"data":{"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_LedgerAccount"}],"description":"Response object containing a single LedgerAccount"}},"required":["data"],"description":"Jortt_V1_Entities_Responses_GetLedgerAccountResponse model"},"Jortt_V3_Financing_BankAccounts_Responses_ListBankAccountsResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/definitions/Jortt_V3_Financing_BankAccounts_Entities_BankAccount"},"description":"List of bank accounts"}},"required":["data"],"description":"Jortt_V3_Financing_BankAccounts_Responses_ListBankAccountsResponse model"},"Jortt_V3_Financing_BankAccounts_Entities_BankAccount":{"type":"object","properties":{"id":{"type":"string","description":"Resource identifier (UUID)"},"iban":{"type":"string","description":"IBAN of the bank account"},"bank":{"type":"string","description":"Bank name"},"in_the_name_of":{"type":"string","description":"Account holder name"},"bic":{"type":"string","description":"BIC of the bank account"},"archived":{"type":"boolean","description":"Whether the bank account is archived"}},"required":["id","iban","bank","in_the_name_of","bic","archived"]},"Jortt_V3_Financing_BankAccounts_Responses_ListBankTransactionsResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/definitions/Jortt_V3_Financing_BankAccounts_Entities_BankTransaction"},"description":"List of bank transactions"},"_links":{"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Link"}],"description":"Links to help navigate through the list."}},"required":["data","_links"],"description":"Jortt_V3_Financing_BankAccounts_Responses_ListBankTransactionsResponse model"},"Jortt_V3_Financing_BankAccounts_Entities_BankTransaction":{"type":"object","properties":{"id":{"type":"string","description":"Resource identifier (UUID)"},"date":{"type":"string","description":"Transaction date"},"amount":{"type":"string","description":"Transaction amount (2 decimal places)"},"description":{"type":"string","description":"Transaction description"},"contra_account_owner":{"type":"string","description":"Name of the counter party"},"contra_account_iban":{"type":"string","description":"IBAN of the counter party"}},"required":["id","date","amount","description","contra_account_owner","contra_account_iban"]},"Jortt_V2_Invoicing_Customers_Responses_GetVatPercentagesForCustomerResponse":{"type":"object","properties":{"data":{"allOf":[{"$ref":"#/definitions/Jortt_V2_Invoicing_Entities_VatPercentagesForCustomer"}],"description":"Response object containing a list of vats valid for the Customer\n"}},"required":["data"],"description":"Jortt_V2_Invoicing_Customers_Responses_GetVatPercentagesForCustomerResponse model"},"Jortt_V2_Invoicing_Entities_VatPercentagesForCustomer":{"type":"object","properties":{"id":{"type":"string","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a","description":"Resource identifier (UUID)"},"vats":{"type":"array","items":{"$ref":"#/definitions/Jortt_V2_Shared_Entities_Vat"},"description":"list of valid vats"}},"required":["id","vats"]},"Jortt_V1_Entities_Responses_ListCustomersResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/definitions/Jortt_V1_Entities_Customer"},"description":"Response object containing a list of Customers"},"_links":{"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Link"}],"description":"Links to help navigate through the lists of objects."}},"required":["data","_links"],"description":"Jortt_V1_Entities_Responses_ListCustomersResponse model"},"Jortt_V1_Entities_Customer":{"type":"object","properties":{"id":{"type":"string","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a","description":"Resource identifier (UUID)"},"is_private":{"type":"boolean","example":true,"description":"Whether this Customer is a private person (`true`) or a company (`false`)"},"customer_name":{"type":"string","example":"Jortt","description":"Either a company name (when `is_private` is `false`) or a private person's full name (when `is_private` is `true`)"},"address_street":{"type":"string","example":"Rozengracht 75a","description":"Street and house number of the address of a Customer (required when `is_private` is `false`)"},"address_postal_code":{"type":"string","example":"1012 AB","description":"Postal code of the address of a Customer (required when `is_private` is `false`)"},"address_city":{"type":"string","example":"Amsterdam","description":"City of the address of a Customer (required when `is_private` is `false`)"},"address_country_code":{"type":"string","example":"NL","description":"Country code of a Customer in [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)\nformat (required when `is_private` is `false`, defaults to `NL`)\n"},"address_country_name":{"type":"string","example":"Nederland","description":"Country name of a Customer (applicable when `is_private` is `false`, defaults to `NL`)"},"address_extra_information":{"type":"string","example":"2nd floor","description":"Extra line of Customer address information"},"shift_vat":{"type":"boolean","example":false,"description":"Whether or not to shift the VAT for a Customer (applicable when `is_private` is `false`)"},"vat_number":{"type":"string","example":"NL000099998B57","description":"The VAT number (BTW-nummer) of a Customer (applicable when `is_private` is `false` and `shift_vat` is\n`true`)\n"},"coc_number":{"type":"string","example":"41202536","description":"The Chamber of Commerce number (KvK-nummer) of a Customer (applicable when `is_private` is `false`)"},"initials":{"type":"string","example":"FL","description":"Initials of a Customer (applicable when `is_private` is `true`)"},"first_name":{"type":"string","example":"Jane","description":"First name of a Customer (applicable when `is_private` is `true`)"},"last_name":{"type":"string","example":"Doe","description":"Last name of a Customer (applicable when `is_private` is `true`)"},"date_of_birth":{"type":"string","format":"date","example":"1985-04-29","description":"Date of birth of a Customer (applicable when `is_private` is `true`)"},"citizen_service_number":{"type":"string","example":"123456789","description":"Citizen service number (BSN-nummer) of a Customer (applicable when `is_private` is `true`)"},"attn":{"type":"string","example":"Finance Department","description":"To the attention of"},"phonenumber":{"type":"string","example":"+31658798654","description":"A Customer's phone number"},"website":{"type":"string","example":"www.example.com","description":"A Customer's website"},"email":{"type":"string","example":"example@email.com","description":"E-mail address to send the Invoice to"},"cc_emails":{"type":"array","items":{"type":"string"},"example":["example@email.com","example2@email.com"],"description":"An array of e-mail addresses to CC when the Invoice is sent"},"email_salutation":{"type":"string","example":"Geachte mevrouw,","description":"Salutation used in the e-mail (template) when sending an Invoice"},"additional_information":{"type":"string","example":"this is extra info","description":"Extra Customer information"},"payment_term":{"type":"integer","format":"int32","example":30,"description":"Payment term for Invoices (in days, defaults to 30)"},"invoice_language":{"type":"string","enum":["nl","de","en","fr","es"],"example":"nl","description":"The language in which the Invoice will be translated"},"payment_method_invoice":{"type":"string","enum":["pay_later","direct_debit","already_paid"],"example":"pay_later","description":"How the Invoice can be paid. Determines the payment instructions printed on the Invoice. Choose from:\n - `already_paid` when the Invoice has already been paid. No payment instructions will be printed on the Invoice.\n - `pay_later` will print payment instructions on the Invoice.\n - `direct_debit` requires a connection with [Mollie](https://www.mollie.com). Only for Jortt Plus users.\n\nThe default is `pay_later`.\n"},"reference":{"type":"string","example":"BX123-123","description":"Custom reference (for example the ID of a customer in an external CRM)"},"mollie_direct_debit_status":{"type":"string","enum":["mollie_direct_debit_requested","mollie_first_payment_sent","mollie_direct_debit_accepted","mollie_direct_debit_invalid"],"example":"mollie_direct_debit_requested","description":"The status of a mollie direct debit request. Options are:\n - `mollie_direct_debit_requested` when we successfully received your request to ask for a\n direct debit authorization for this customer.\n - `mollie_first_payment_sent` when we have sent the first email to the customer with a request for an\n authorization payment.\n - `mollie_direct_debit_accepted` when the customer has paid the authorization payment. In this case\n the customer will have a `mollie_mandate_id` and `mollie_customer_id`.\n - `mollie_direct_debit_invalid` when the direct debit authorization is invalid.\n"},"mollie_customer_id":{"type":"string","example":"cst_kEn1PlbGa","description":"Unique ID of the customer in Mollie"},"mollie_mandate_id":{"type":"string","example":"mdt_h3gAaD5zP","description":"Unique ID of the mandate of a customer in Mollie"},"default_ledger_account_id":{"type":"string","example":"61a5b600-16bc-013d-3d2d-42742b204134","description":"Resource identifier (UUID) of the ledger account to be set as default for a customer, which will\nautomatically be applied to their invoices.\n"},"archived":{"type":"boolean","example":false,"description":"Whether or not the item has been archived."},"default_label_id":{"type":"string","example":"57aab2d0-16b8-013d-3d2c-42742b204134","description":"Resource identifier (UUID) of the label to be set as default for a customer, which will automatically be\napplied to their invoices & estimates.\n"},"default_discount_description":{"type":"string","example":"this is a description example","description":"Description a default discount to apply to any invoices or estimates created for this customer"},"default_discount_percentage":{"type":"integer","format":"int64","example":21,"description":"The default discount to apply expressed as a percentage"},"peppol_scheme_code":{"type":"integer","format":"int64","example":"KVK","description":"Prefix describing the scheme of the peppol identifier. Used when sending invoices via Peppol"},"peppol_identifier":{"type":"integer","format":"int64","example":"12345677","description":"Code used to identify a Peppol legal entity. Used when sending invoices via Peppol"}},"required":["id","is_private","customer_name","address_street","address_postal_code","address_city","address_country_code","address_country_name","address_extra_information","shift_vat","vat_number","coc_number","initials","first_name","last_name","date_of_birth","citizen_service_number","attn","phonenumber","website","email","cc_emails","email_salutation","additional_information","payment_term","invoice_language","payment_method_invoice","reference","mollie_direct_debit_status","mollie_customer_id","mollie_mandate_id","default_ledger_account_id","archived","default_label_id","default_discount_description","default_discount_percentage","peppol_scheme_code","peppol_identifier"]},"Jortt_V1_Entities_Responses_GetCustomerResponse":{"type":"object","properties":{"data":{"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Customer"}],"description":"Response object containing a single Customer"},"_links":{"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_EntityDetailLink"}],"description":"Links to help navigate through the objects details."}},"required":["data","_links"],"description":"Jortt_V1_Entities_Responses_GetCustomerResponse model"},"Jortt_V1_Entities_EntityDetailLink":{"type":"object","properties":{"extra_details":{"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Url"}],"description":"A URL object representing the details of the object."}},"required":["extra_details"]},"Jortt_V1_Entities_Responses_GetCustomerDetailsResponse":{"type":"object","properties":{"data":{"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_CustomerDetails"}],"description":"Response object containing a single Customer's details"}},"required":["data"],"description":"Jortt_V1_Entities_Responses_GetCustomerDetailsResponse model"},"Jortt_V1_Entities_CustomerDetails":{"type":"object","properties":{"id":{"type":"string","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a","description":"Resource identifier (UUID)"},"total_revenue":{"type":"object","example":{"amount":123.21,"count":2},"description":"Hash of the amount and invoice count of the total revenue for the customer\n"},"total_due":{"type":"object","example":{"amount":123.21,"count":2},"description":"Hash of the amount and invoice count of the total due for the customer\n"},"total_late":{"type":"object","example":{"amount":123.21,"count":2},"description":"Hash of the amount and invoice count of the total late for the customer\n"}},"required":["id","total_revenue","total_due","total_late"]},"Jortt_V1_Entities_Responses_GetVatPercentagesForCustomerResponse":{"type":"object","properties":{"data":{"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_VatPercentagesForCustomer"}],"description":"Response object containing a hash of vat percentages with keys `standard_rate` and `reduced_rate` for the Customer\n"}},"required":["data"],"description":"Jortt_V1_Entities_Responses_GetVatPercentagesForCustomerResponse model"},"Jortt_V1_Entities_VatPercentagesForCustomer":{"type":"object","properties":{"id":{"type":"string","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a","description":"Resource identifier (UUID)"},"vat_percentages":{"example":{"standard_rate":"21.0","reduced_rate":["19.0","12.0"]},"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_VatPercentages"}],"description":"Hash of available vat percentages that are valid on today's date for a Customer"}},"required":["id","vat_percentages"]},"Jortt_V1_Entities_VatPercentages":{"type":"object","properties":{"standard_rate":{"type":"string","example":"21.0","description":"VAT percentage as a string"},"reduced_rate":{"type":"array","items":{"type":"string"},"example":["19.0","12.0"],"description":"array of VAT percentages"}},"required":["standard_rate","reduced_rate"]},"CreateCustomer":{"type":"object","properties":{"is_private":{"type":"boolean","description":"Whether this Customer is a private person (`true`) or a company (`false`)","example":true},"customer_name":{"type":"string","description":"Either a company name (when `is_private` is `false`) or a private person's full name (when `is_private` is `true`)","example":"Jortt"},"address_street":{"type":"string","description":"Street and house number of the address of a Customer (required when `is_private` is `false`)","example":"Rozengracht 75a"},"address_postal_code":{"type":"string","description":"Postal code of the address of a Customer (required when `is_private` is `false`)","example":"1012 AB"},"address_city":{"type":"string","description":"City of the address of a Customer (required when `is_private` is `false`)","example":"Amsterdam"},"address_country_code":{"type":"string","description":"Country code of a Customer in [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)\nformat (required when `is_private` is `false`, defaults to `NL`)\n","example":"NL"},"address_extra_information":{"type":"string","description":"Extra line of Customer address information","example":"2nd floor"},"shift_vat":{"type":"boolean","description":"Whether or not to shift the VAT for a Customer (applicable when `is_private` is `false`)","example":false},"vat_number":{"type":"string","description":"The VAT number (BTW-nummer) of a Customer (applicable when `is_private` is `false` and `shift_vat` is\n`true`)\n","example":"NL000099998B57"},"coc_number":{"type":"string","description":"The Chamber of Commerce number (KvK-nummer) of a Customer (applicable when `is_private` is `false`)","example":"41202536"},"salutation":{"type":"string","description":"The way a Customer is addressed (applicable when `is_private` is `true`)","enum":["sir","madam","family"],"example":"madam"},"initials":{"type":"string","description":"Initials of a Customer (applicable when `is_private` is `true`)","example":"FL"},"first_name":{"type":"string","description":"First name of a Customer (applicable when `is_private` is `true`)","example":"Jane"},"last_name":{"type":"string","description":"Last name of a Customer (applicable when `is_private` is `true`)","example":"Doe"},"date_of_birth":{"type":"string","format":"date","description":"Date of birth of a Customer (applicable when `is_private` is `true`)","example":"1985-04-29"},"citizen_service_number":{"type":"string","description":"Citizen service number (BSN-nummer) of a Customer (applicable when `is_private` is `true`)","example":"123456789"},"attn":{"type":"string","description":"To the attention of","example":"Finance Department"},"phonenumber":{"type":"string","description":"A Customer's phone number","example":"+31658798654"},"website":{"type":"string","description":"A Customer's website","example":"www.example.com"},"email":{"type":"string","description":"E-mail address to send the Invoice to","example":"example@email.com"},"cc_emails":{"type":"array","description":"An array of e-mail addresses to CC when the Invoice is sent","example":["example@email.com","example2@email.com"],"items":{"type":"string"}},"email_salutation":{"type":"string","description":"Salutation used in the e-mail (template) when sending an Invoice","example":"Geachte mevrouw,"},"additional_information":{"type":"string","description":"Extra Customer information","example":"this is extra info"},"payment_term":{"type":"integer","format":"int32","description":"Payment term for Invoices (in days, defaults to 30)","example":30},"invoice_language":{"type":"string","description":"The language in which the Invoice will be translated","enum":["nl","de","en","fr","es"],"example":"nl"},"payment_method_invoice":{"type":"string","description":"How the Invoice can be paid. Determines the payment instructions printed on the Invoice. Choose from:\n - `already_paid` when the Invoice has already been paid. No payment instructions will be printed on the Invoice.\n - `pay_later` will print payment instructions on the Invoice.\n - `direct_debit` requires a connection with [Mollie](https://www.mollie.com). Only for Jortt Plus users.\n\nThe default is `pay_later`.\n","enum":["pay_later","direct_debit","already_paid"],"example":"pay_later"},"reference":{"type":"string","description":"Custom reference (for example the ID of a customer in an external CRM)","example":"BX123-123"},"default_label_id":{"type":"string","description":"Resource identifier (UUID) of the label to be set as default for a customer, which will automatically be\napplied to their invoices & estimates.\n","example":"57aab2d0-16b8-013d-3d2c-42742b204134"},"default_ledger_account_id":{"type":"string","description":"Resource identifier (UUID) of the ledger account to be set as default for a customer, which will\nautomatically be applied to their invoices.\n","example":"61a5b600-16bc-013d-3d2d-42742b204134"},"default_discount_description":{"type":"string","description":"Description a default discount to apply to any invoices or estimates created for this customer","example":"this is a description example"},"default_discount_percentage":{"type":"integer","format":"int64","description":"The default discount to apply expressed as a percentage","example":21},"peppol_scheme_code":{"type":"integer","format":"int64","description":"Prefix describing the scheme of the peppol identifier. Used when sending invoices via Peppol","example":"KVK"},"peppol_identifier":{"type":"integer","format":"int64","description":"Code used to identify a Peppol legal entity. Used when sending invoices via Peppol","example":"12345677"}},"required":["is_private","customer_name"],"description":"Creates a Customer"},"UpdateCustomer":{"type":"object","properties":{"is_private":{"type":"boolean","description":"Whether this Customer is a private person (`true`) or a company (`false`)","example":true},"customer_name":{"type":"string","description":"Either a company name (when `is_private` is `false`) or a private person's full name (when `is_private` is `true`)","example":"Jortt"},"address_street":{"type":"string","description":"Street and house number of the address of a Customer (required when `is_private` is `false`)","example":"Rozengracht 75a"},"address_postal_code":{"type":"string","description":"Postal code of the address of a Customer (required when `is_private` is `false`)","example":"1012 AB"},"address_city":{"type":"string","description":"City of the address of a Customer (required when `is_private` is `false`)","example":"Amsterdam"},"address_country_code":{"type":"string","description":"Country code of a Customer in [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)\nformat (required when `is_private` is `false`, defaults to `NL`)\n","example":"NL"},"address_extra_information":{"type":"string","description":"Extra line of Customer address information","example":"2nd floor"},"shift_vat":{"type":"boolean","description":"Whether or not to shift the VAT for a Customer (applicable when `is_private` is `false`)","example":false},"vat_number":{"type":"string","description":"The VAT number (BTW-nummer) of a Customer (applicable when `is_private` is `false` and `shift_vat` is\n`true`)\n","example":"NL000099998B57"},"coc_number":{"type":"string","description":"The Chamber of Commerce number (KvK-nummer) of a Customer (applicable when `is_private` is `false`)","example":"41202536"},"salutation":{"type":"string","description":"The way a Customer is addressed (applicable when `is_private` is `true`)","enum":["sir","madam","family"],"example":"madam"},"initials":{"type":"string","description":"Initials of a Customer (applicable when `is_private` is `true`)","example":"FL"},"first_name":{"type":"string","description":"First name of a Customer (applicable when `is_private` is `true`)","example":"Jane"},"last_name":{"type":"string","description":"Last name of a Customer (applicable when `is_private` is `true`)","example":"Doe"},"date_of_birth":{"type":"string","format":"date","description":"Date of birth of a Customer (applicable when `is_private` is `true`)","example":"1985-04-29"},"citizen_service_number":{"type":"string","description":"Citizen service number (BSN-nummer) of a Customer (applicable when `is_private` is `true`)","example":"123456789"},"attn":{"type":"string","description":"To the attention of","example":"Finance Department"},"phonenumber":{"type":"string","description":"A Customer's phone number","example":"+31658798654"},"website":{"type":"string","description":"A Customer's website","example":"www.example.com"},"email":{"type":"string","description":"E-mail address to send the Invoice to","example":"example@email.com"},"cc_emails":{"type":"array","description":"An array of e-mail addresses to CC when the Invoice is sent","example":["example@email.com","example2@email.com"],"items":{"type":"string"}},"email_salutation":{"type":"string","description":"Salutation used in the e-mail (template) when sending an Invoice","example":"Geachte mevrouw,"},"additional_information":{"type":"string","description":"Extra Customer information","example":"this is extra info"},"payment_term":{"type":"integer","format":"int32","description":"Payment term for Invoices (in days, defaults to 30)","example":30},"invoice_language":{"type":"string","description":"The language in which the Invoice will be translated","enum":["nl","de","en","fr","es"],"example":"nl"},"payment_method_invoice":{"type":"string","description":"How the Invoice can be paid. Determines the payment instructions printed on the Invoice. Choose from:\n - `already_paid` when the Invoice has already been paid. No payment instructions will be printed on the Invoice.\n - `pay_later` will print payment instructions on the Invoice.\n - `direct_debit` requires a connection with [Mollie](https://www.mollie.com). Only for Jortt Plus users.\n\nThe default is `pay_later`.\n","enum":["pay_later","direct_debit","already_paid"],"example":"pay_later"},"reference":{"type":"string","description":"Custom reference (for example the ID of a customer in an external CRM)","example":"BX123-123"},"default_label_id":{"type":"string","description":"Resource identifier (UUID) of the label to be set as default for a customer, which will automatically be\napplied to their invoices & estimates.\n","example":"57aab2d0-16b8-013d-3d2c-42742b204134"},"default_ledger_account_id":{"type":"string","description":"Resource identifier (UUID) of the ledger account to be set as default for a customer, which will\nautomatically be applied to their invoices.\n","example":"61a5b600-16bc-013d-3d2d-42742b204134"},"default_discount_description":{"type":"string","description":"Description a default discount to apply to any invoices or estimates created for this customer","example":"this is a description example"},"default_discount_percentage":{"type":"integer","format":"int64","description":"The default discount to apply expressed as a percentage","example":21},"peppol_scheme_code":{"type":"integer","format":"int64","description":"Prefix describing the scheme of the peppol identifier. Used when sending invoices via Peppol","example":"KVK"},"peppol_identifier":{"type":"integer","format":"int64","description":"Code used to identify a Peppol legal entity. Used when sending invoices via Peppol","example":"12345677"}},"required":["is_private","customer_name"],"description":"Updates a Customer"},"SetCustomerArchived":{"type":"object","properties":{"archived":{"type":"boolean","description":"Whether or not the item has been archived.","example":false}},"required":["archived"],"description":"Sets the archived status for a customer"},"Jortt_V2_Invoicing_Estimates_Responses_ListEstimatesResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/definitions/Jortt_V2_Invoicing_Entities_Estimate"},"description":"Response object containing a list of Estimates"},"_links":{"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Link"}],"description":"Links to help navigate through the lists of objects."}},"required":["data","_links"],"description":"Jortt_V2_Invoicing_Estimates_Responses_ListEstimatesResponse model"},"Jortt_V2_Invoicing_Entities_Estimate":{"type":"object","properties":{"id":{"type":"string","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a","description":"Resource identifier (UUID)"},"estimate_status":{"type":"string","description":"The lifecycle status of the Estimate. Possible values: `draft`, `sent`, `expired`, `invoiced`."},"acceptance_status":{"type":"string","description":"The acceptance status of the Estimate. Possible values: `pending`, `accepted`, `rejected`, `signed`, or `null`."},"customer_id":{"type":"string","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a","description":"Resource identifier (UUID)"},"tradename_id":{"type":"string","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a","description":"The ID of the Tradename used for this Invoice. It should a resource identifier (UUID). If not set the default Organization will be used.\n"},"estimate_number":{"type":"string","description":"The number assigned to the Estimate."},"valid_until":{"type":"string","format":"date","description":"The date until which the Estimate is valid."},"total":{"example":{"amount":"22.00","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V2_Shared_Entities_Amount"}],"description":"A hash representing a monetary amount, contains a string for amount and currency.Total amount of the Estimate excluding VAT"},"total_incl_vat":{"example":{"amount":"22.00","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V2_Shared_Entities_Amount"}],"description":"A hash representing a monetary amount, contains a string for amount and currency.Total amount of the Estimate including VAT"},"net_amounts":{"type":"boolean","example":false,"description":"Whether or not VAT is included in the `amount_per_unit` in the line item (this is typically used when\ninvoicing a private person rather than a company)\n"},"introduction":{"type":"string","example":"example","description":"Comments printed on the Invoice (above the line items)"},"remarks":{"type":"string","example":"example","description":"Remarks printed on the Invoice (under the line items)"},"reference":{"type":"string","example":"123","description":"Custom reference (for example an order ID)"},"customer_company_name":{"type":"string","example":"Jortt","description":"Either a company name (when `is_private` is `false`) or a private person's full name (when `is_private` is `true`)"},"customer_attn":{"type":"string","example":"Finance Department","description":"To the attention of"},"customer_address_street":{"type":"string","example":"Rozengracht 75a","description":"Street and house number of the address of a Customer (required when `is_private` is `false`)"},"customer_address_city":{"type":"string","example":"Amsterdam","description":"City of the address of a Customer (required when `is_private` is `false`)"},"customer_address_postal_code":{"type":"string","example":"1012 AB","description":"Postal code of the address of a Customer (required when `is_private` is `false`)"},"customer_address_country_code":{"type":"string","example":"NL","description":"Country code of a Customer in [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)\nformat (required when `is_private` is `false`, defaults to `NL`)\n"},"customer_address_country_name":{"type":"string","example":"Nederland","description":"Country name of a Customer (applicable when `is_private` is `false`, defaults to `NL`)"},"customer_address_extra_information":{"type":"string","example":"2nd floor","description":"Extra line of Customer address information"},"customer_vat_shifted":{"type":"boolean","example":false,"description":"Whether or not to shift the VAT for a Customer (applicable when `is_private` is `false`)"},"customer_vat_number":{"type":"string","example":"NL000099998B57","description":"The VAT number (BTW-nummer) of a Customer (applicable when `is_private` is `false` and `shift_vat` is\n`true`)\n"},"language":{"type":"string","enum":["nl","de","en","fr","es"],"example":"nl","description":"The language in which the Invoice will be translated"},"line_items":{"type":"array","items":{"$ref":"#/definitions/Jortt_V2_Invoicing_Entities_EstimateLineItem"},"description":"line items of the estimate"},"discounts":{"type":"array","items":{"$ref":"#/definitions/Jortt_V1_Entities_Discount"},"description":"discounts applied to the estimate"},"sent_or_modified_on":{"type":"string","format":"date","description":"The date the Estimate was last sent or modified."}},"required":["id","estimate_status","acceptance_status","customer_id","tradename_id","estimate_number","valid_until","total","total_incl_vat","net_amounts","introduction","remarks","reference","customer_company_name","customer_attn","customer_address_street","customer_address_city","customer_address_postal_code","customer_address_country_code","customer_address_country_name","customer_address_extra_information","customer_vat_shifted","customer_vat_number","language","line_items","discounts","sent_or_modified_on"]},"Jortt_V2_Invoicing_Entities_EstimateLineItem":{"type":"object","properties":{"description":{"type":"string","example":"this is a description example","description":"Description of the line item"},"vat":{"example":{"value":"0.21","category":null},"allOf":[{"$ref":"#/definitions/Jortt_V2_Shared_Entities_Vat"}],"description":"A hash representing a vat category, contains a category string and a percentage integer."},"quantity":{"example":"22.1","allOf":[{"$ref":"#/definitions/Jortt_V2_Shared_Entities_Amount"}],"description":"A numeric string representing a quantity. The number of units being sold."},"amount":{"example":{"amount":"22.00","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V2_Shared_Entities_Amount"}],"description":"A hash representing a monetary amount, contains a string for amount and currency.The amount per unit being sold."},"total_amount_ex_vat":{"example":{"amount":"22.00","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V2_Shared_Entities_Amount"}],"description":"A hash representing a monetary amount, contains a string for amount and currency.The total amount for the line item excluding vat."}},"required":["description","vat","quantity","amount","total_amount_ex_vat"]},"Jortt_V2_Invoicing_Estimates_Responses_GetEstimateResponse":{"type":"object","properties":{"data":{"allOf":[{"$ref":"#/definitions/Jortt_V2_Invoicing_Entities_Estimate"}],"description":"Response object containing a single Estimate"},"possible_actions":{"type":"array","items":{"type":"string"},"description":"A list of strings indicating actions that can be taken with the associated estimate"}},"required":["data","possible_actions"],"description":"Jortt_V2_Invoicing_Estimates_Responses_GetEstimateResponse model"},"CreateEstimateV2":{"type":"object","properties":{"customer_id":{"type":"string","description":"Resource identifier (UUID)","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a"},"tradename_id":{"type":"string","description":"The ID of the Tradename used for this Invoice. It should a resource identifier (UUID). If not set the default Organization will be used.\n","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a"},"valid_until":{"type":"string","format":"date","description":"The date until which the Estimate is valid."},"net_amounts":{"type":"boolean","description":"Whether or not VAT is included in the `amount_per_unit` in the line item (this is typically used when\ninvoicing a private person rather than a company)\n","example":false},"send_method":{"type":"string","description":"How the Invoice should be sent","enum":["email","peppol","self"],"example":"email"},"email_address_customer":{"type":"string","description":"The email address to send the estimate to. Overrides the customer's default email. Only used when send_method is email."},"introduction":{"type":"string","description":"Comments printed on the Invoice (above the line items)","example":"example"},"remarks":{"type":"string","description":"Remarks printed on the Invoice (under the line items)","example":"example"},"reference":{"type":"string","description":"Custom reference (for example an order ID)","example":"123"},"line_items":{"type":"array","description":"The line items of an Invoice","items":{"type":"object","properties":{"description":{"type":"string","description":"Description of the line item","example":"this is a description example"},"quantity":{"$ref":"#/definitions/Jortt_V2_Shared_Entities_Amount","description":"A numeric string representing a quantity. ","example":"22.1"},"amount":{"$ref":"#/definitions/Jortt_V2_Shared_Entities_Amount","description":"A hash representing a monetary amount, contains a string for amount and currency.","example":{"amount":"22.00","currency":"EUR"},"type":"object","properties":{"amount":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["amount","currency"]},"vat":{"$ref":"#/definitions/Jortt_V2_Shared_Entities_Vat","description":"A hash representing a vat category, contains a category string and a percentage integer.","example":{"value":"0.21","category":null},"type":"object","properties":{"value":{"type":"number","format":"double"},"category":{"type":"string"}},"required":["value","category"]}},"required":["description","quantity","amount","vat"]}},"discounts":{"type":"array","description":"Discounts applied to the invoice","items":{"type":"object","properties":{"description":{"type":"string","description":"Description of the discount","example":"this is a description example"},"percentage":{"type":"integer","format":"int64","description":"The discount to apply expressed as a percentage","example":21}},"required":["percentage"]}}},"description":"Creates (and optionally sends) an Estimate"},"EditEstimateV2":{"type":"object","properties":{"customer_id":{"type":"string","description":"Resource identifier (UUID)","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a"},"tradename_id":{"type":"string","description":"The ID of the Tradename used for this Invoice. It should a resource identifier (UUID). If not set the default Organization will be used.\n","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a"},"valid_until":{"type":"string","format":"date","description":"The date until which the Estimate is valid."},"net_amounts":{"type":"boolean","description":"Whether or not VAT is included in the `amount_per_unit` in the line item (this is typically used when\ninvoicing a private person rather than a company)\n","example":false},"introduction":{"type":"string","description":"Comments printed on the Invoice (above the line items)","example":"example"},"remarks":{"type":"string","description":"Remarks printed on the Invoice (under the line items)","example":"example"},"reference":{"type":"string","description":"Custom reference (for example an order ID)","example":"123"},"line_items":{"type":"array","description":"The line items of an Invoice","items":{"type":"object","properties":{"description":{"type":"string","description":"Description of the line item","example":"this is a description example"},"quantity":{"$ref":"#/definitions/Jortt_V2_Shared_Entities_Amount","description":"A numeric string representing a quantity. ","example":"22.1"},"amount":{"$ref":"#/definitions/Jortt_V2_Shared_Entities_Amount","description":"A hash representing a monetary amount, contains a string for amount and currency.","example":{"amount":"22.00","currency":"EUR"},"type":"object","properties":{"amount":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["amount","currency"]},"vat":{"$ref":"#/definitions/Jortt_V2_Shared_Entities_Vat","description":"A hash representing a vat category, contains a category string and a percentage integer.","example":{"value":"0.21","category":null},"type":"object","properties":{"value":{"type":"number","format":"double"},"category":{"type":"string"}},"required":["value","category"]}}}},"discounts":{"type":"array","description":"Discounts applied to the invoice","items":{"type":"object","properties":{"description":{"type":"string","description":"Description of the discount","example":"this is a description example"},"percentage":{"type":"integer","format":"int64","description":"The discount to apply expressed as a percentage","example":21}},"required":["percentage"]}}},"description":"Edits an Estimate"},"SendEstimateV2":{"type":"object","properties":{"send_method":{"type":"string","description":"How the Invoice should be sent","enum":["email","peppol","self"],"example":"email"},"email_address_customer":{"type":"string","description":"The email address to send the estimate to. Overrides the customer's default email. Only used when send_method is email."}},"required":["send_method"],"description":"Sends an Estimate"},"RejectEstimateV2":{"type":"object","properties":{"comment":{"type":"string","description":"Optional rejection comment"}},"description":"Rejects an Estimate"},"Jortt_V2_Invoicing_Estimates_Responses_GetEstimateLineItemSuggestionsResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/definitions/Jortt_V2_Invoicing_Entities_InvoiceLineItemSuggestion"},"description":"Response object containing a list of suggested line items for the given estimate"}},"required":["data"],"description":"Jortt_V2_Invoicing_Estimates_Responses_GetEstimateLineItemSuggestionsResponse model"},"Jortt_V2_Invoicing_Estimates_Responses_SendSettingsEstimateResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"default_mail_subject":{"type":"string","description":"Default email subject for this estimate"},"default_mail_body":{"type":"string","description":"Default email body for this estimate."},"supported_attachment_types":{"type":"array","items":{"type":"string"},"description":"Supported attachment MIME types"},"default_attachments":{"type":"array","items":{"$ref":"#/definitions/Jortt_V1_Entities_File"},"description":"Response object containing a list of attachments"}},"required":["default_mail_subject","default_mail_body","supported_attachment_types","default_attachments"]}},"required":["data"],"description":"Jortt_V2_Invoicing_Estimates_Responses_SendSettingsEstimateResponse model"},"Jortt_V1_Entities_Responses_ListTradenamesResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/definitions/Jortt_V1_Entities_Tradename"},"description":"Response object containing a list of Tradenames"}},"required":["data"],"description":"Jortt_V1_Entities_Responses_ListTradenamesResponse model"},"Jortt_V1_Entities_Tradename":{"type":"object","properties":{"id":{"type":"string","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a","description":"Resource identifier (UUID)"},"company_name":{"type":"string"},"company_name_line_2":{"type":"string"},"address_street":{"type":"string"},"address_city":{"type":"string"},"address_postal_code":{"type":"string"},"address_country_code":{"type":"string"},"phonenumber":{"type":"string"},"bank_account_in_the_name_of":{"type":"string"},"iban":{"type":"string"},"bic":{"type":"string"},"finance_email":{"type":"string"}},"required":["id","company_name","company_name_line_2","address_street","address_city","address_postal_code","address_country_code","phonenumber","bank_account_in_the_name_of","iban","bic","finance_email"]},"Jortt_V1_Entities_Responses_ListLabelsResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/definitions/Jortt_V1_Entities_Label"},"description":"Response object containing a list of Labels"}},"required":["data"],"description":"Jortt_V1_Entities_Responses_ListLabelsResponse model"},"Jortt_V1_Entities_Label":{"type":"object","properties":{"id":{"type":"string","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a","description":"Resource identifier (UUID)"},"description":{"type":"string","example":"My Label","description":"The description of a Label"},"category":{"type":"string","enum":["user_label","tradename_label","project_label"],"example":"user_label","description":"The category of a Label"}},"required":["id","description","category"]},"CreateLabel":{"type":"object","properties":{"description":{"type":"string","description":"The description of a Label","example":"My Label"}},"required":["description"],"description":"Create a label"},"Jortt_V1_Entities_Responses_GetOrganizationResponse":{"type":"object","properties":{"data":{"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Organization"}],"description":"Response object containing a single Organization"}},"required":["data"],"description":"Jortt_V1_Entities_Responses_GetOrganizationResponse model"},"Jortt_V1_Entities_Organization":{"type":"object","properties":{"id":{"type":"string","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a","description":"Resource identifier (UUID)"},"vat_type":{"type":"string","example":"vat","description":"Company vat type, can be one of [vat, sometimes_vat, never_vat]"},"company_name":{"type":"string","example":"Jortt B.V.","description":"Legal name of the company name"},"company_name_line_2":{"type":"string","example":"IT department","description":"Optional part of the name of the company"},"address_street":{"type":"string","example":"Nieuwezijds Voorburgwal 147","description":"Street and house number of the address"},"address_postal_code":{"type":"string","example":"1012 RJ","description":"Postal code of the address"},"address_city":{"type":"string","example":"Amsterdam","description":"City of the address"},"address_country_code":{"type":"string","example":"NL","description":"Country code of the address in [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)\nformat\n"},"address_country_name":{"type":"string","example":"Nederland","description":"Country name of the address"},"tax_registration_number":{"type":"string","example":"NL999999999B01","description":"Tax registration number (Btw nummer)"},"chamber_of_commerce_number":{"type":"string","example":"12345678","description":"Chamber of commerce number"},"bank_account_iban":{"type":"string","example":"NL10BANK 1234 5678 90","description":"IBAN number of the account"},"bank_account_bban":{"type":"string","example":"123456789","description":"BBAN number of the account (some accounts have no IBAN, typically savings accounts)"},"owners":{"type":"array","items":{"$ref":"#/definitions/Jortt_V1_Entities_OrganizationUser"},"description":"A user that has access to this organization"},"legal_form":{"type":"string","enum":["bv","cooperatie","cv","eenmanszaak","kerkgenootschap","maatschap","nv","stichting","vereniging","vof"],"example":"bv","description":"Legal form of the company"},"website":{"type":"string","example":"http://www.jortt.nl","description":"Website of the organization"},"phonenumber":{"type":"string","example":"+31 20 1234567","description":"Phonennumber of the organization"},"invoice_from_email":{"type":"string","example":"support@jortt.nl","description":"E-mail address used for sending invoices by this Organization"},"one_stop_shop_enabled":{"type":"boolean","example":false,"description":"Does this organization have one-stop-shop (éénloketsysteem) enabled"},"has_mollie":{"type":"boolean","example":false,"description":"Does this organization have a Mollie integration"},"jortt_start_date":{"type":"string","format":"date-time","example":"2026-07-11T13:45:27+02:00","description":"Time this organization was created in Jortt"},"invoice_from_email_confirmed":{"type":"boolean","example":true,"description":"Is the e-mail address used for sending invoices by this Organization confirmed or not"},"data_complete_for_sending_invoices":{"type":"boolean","example":true,"description":"Is the data of the Organization complete for sending invoices, for example company address and such"},"default_vat":{"type":"object","example":{"category":"exempt","value":"0.0"},"description":"Default vat for this organization, contains a value string representing the vat fraction and a category string"},"has_peppol_access":{"type":"boolean","description":"Boolean indicating weather this organization has access to peppol"},"access_to_jortt_zzp":{"type":"boolean","example":false,"description":"Whether the organization has access to Jortt ZZP plan features"},"access_to_jortt_mkb":{"type":"boolean","example":false,"description":"Whether the organization has access to Jortt MKB plan features"},"access_to_jortt_plus":{"type":"boolean","example":false,"description":"Whether the organization has access to Jortt Plus plan features"}},"required":["id","vat_type","company_name","company_name_line_2","address_street","address_postal_code","address_city","address_country_code","address_country_name","tax_registration_number","chamber_of_commerce_number","bank_account_iban","bank_account_bban","owners","legal_form","website","phonenumber","invoice_from_email","one_stop_shop_enabled","has_mollie","jortt_start_date","invoice_from_email_confirmed","data_complete_for_sending_invoices","default_vat","has_peppol_access","access_to_jortt_zzp","access_to_jortt_mkb","access_to_jortt_plus"]},"Jortt_V1_Entities_OrganizationUser":{"type":"object","properties":{"id":{"type":"string","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a","description":"Resource identifier (UUID)"},"full_name":{"type":"string","example":"Jari Litmanen","description":"Name of the user"}},"required":["id","full_name"]},"Jortt_V1_Entities_Responses_ListLoonjournaalpostenResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/definitions/Jortt_V1_Entities_Loonjournaalpost"},"description":"Response object containing a list of Loonjournaalposten"}},"required":["data"],"description":"Jortt_V1_Entities_Responses_ListLoonjournaalpostenResponse model"},"Jortt_V1_Entities_Loonjournaalpost":{"type":"object","properties":{"id":{"type":"string","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a","description":"Resource identifier (UUID)"},"period_date":{"type":"string","format":"date","example":"2023-10-01","description":"Date of the Loonjournaalpost period, must be the first day of the month"},"bruto_loon":{"example":{"value":"10.10","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}],"description":"Amount of bruto_loon"},"werkgeversdeel_sociale_lasten":{"example":{"value":"10.10","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}],"description":"Amount of werkgeversdeel_sociale_lasten"},"werkgeversdeel_pensioenkosten":{"example":{"value":"10.10","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}],"description":"Amount of werkgeversdeel_pensioenkosten"},"reservering_vakantiegeld":{"example":{"value":"10.10","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}],"description":"Amount of reservering_vakantiegeld"},"reservering_eindejaarsuitkering":{"example":{"value":"10.10","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}],"description":"Amount of reservering_eindejaarsuitkering"},"onbelaste_reiskostenvergoedingen":{"example":{"value":"10.10","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}],"description":"Amount of onbelaste_reiskostenvergoedingen"},"onbelaste_vergoedingen_gericht_vrijgesteld":{"example":{"value":"10.10","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}],"description":"Amount of onbelaste_vergoedingen_gericht_vrijgesteld"},"onbelaste_vergoedingen_wkr":{"example":{"value":"10.10","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}],"description":"Amount of onbelaste_vergoedingen_wkr"},"declaraties_algemeen":{"example":{"value":"10.10","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}],"description":"Amount of declaraties_algemeen"},"declaraties_kantoorkosten":{"example":{"value":"10.10","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}],"description":"Amount of declaraties_kantoorkosten"},"declaraties_lunch_en_diner_representatiekosten":{"example":{"value":"10.10","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}],"description":"Amount of declaraties_lunch_en_diner_representatiekosten"},"declaraties_reiskosten":{"example":{"value":"10.10","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}],"description":"Amount of declaraties_reiskosten"},"declaraties_auto_en_transportkosten":{"example":{"value":"10.10","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}],"description":"Amount of declaraties_auto_en_transportkosten"},"declaraties_verkoopkosten":{"example":{"value":"10.10","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}],"description":"Amount of declaraties_verkoopkosten"},"eindheffing_wkr":{"example":{"value":"10.10","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}],"description":"Amount of eindheffing_wkr"},"netto_inhoudingen":{"example":{"value":"10.10","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}],"description":"Amount of netto_inhoudingen"},"afdrachtvermindering_speur_en_ontwikkelingswerk":{"example":{"value":"10.10","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}],"description":"Amount of afdrachtvermindering_speur_en_ontwikkelingswerk"},"te_betalen_nettolonen":{"example":{"value":"10.10","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}],"description":"Amount of te_betalen_nettolonen"},"te_betalen_sociale_lasten_loonbelasting":{"example":{"value":"10.10","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}],"description":"Amount of te_betalen_sociale_lasten_loonbelasting"},"te_betalen_pensioenpremies":{"example":{"value":"10.10","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}],"description":"Amount of te_betalen_pensioenpremies"},"te_betalen_vakantiegeld":{"example":{"value":"10.10","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}],"description":"Amount of te_betalen_vakantiegeld"},"te_betalen_eindejaarsuitkering":{"example":{"value":"10.10","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}],"description":"Amount of te_betalen_eindejaarsuitkering"},"te_betalen_loonbeslag":{"example":{"value":"10.10","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}],"description":"Amount of te_betalen_loonbeslag"}},"required":["id","period_date","bruto_loon","werkgeversdeel_sociale_lasten","werkgeversdeel_pensioenkosten","reservering_vakantiegeld","reservering_eindejaarsuitkering","onbelaste_reiskostenvergoedingen","onbelaste_vergoedingen_gericht_vrijgesteld","onbelaste_vergoedingen_wkr","declaraties_algemeen","declaraties_kantoorkosten","declaraties_lunch_en_diner_representatiekosten","declaraties_reiskosten","declaraties_auto_en_transportkosten","declaraties_verkoopkosten","eindheffing_wkr","netto_inhoudingen","afdrachtvermindering_speur_en_ontwikkelingswerk","te_betalen_nettolonen","te_betalen_sociale_lasten_loonbelasting","te_betalen_pensioenpremies","te_betalen_vakantiegeld","te_betalen_eindejaarsuitkering","te_betalen_loonbeslag"]},"CreateLoonjournaalpost":{"type":"object","properties":{"period_date":{"type":"string","format":"date","description":"Date of the Loonjournaalpost period, must be the first day of the month","example":"2023-10-01"},"bruto_loon":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"werkgeversdeel_sociale_lasten":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"werkgeversdeel_pensioenkosten":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"reservering_vakantiegeld":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"reservering_eindejaarsuitkering":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"onbelaste_reiskostenvergoedingen":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"onbelaste_vergoedingen_gericht_vrijgesteld":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"onbelaste_vergoedingen_wkr":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"declaraties_algemeen":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"declaraties_kantoorkosten":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"declaraties_lunch_en_diner_representatiekosten":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"declaraties_reiskosten":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"declaraties_auto_en_transportkosten":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"declaraties_verkoopkosten":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"eindheffing_wkr":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"netto_inhoudingen":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"afdrachtvermindering_speur_en_ontwikkelingswerk":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"te_betalen_nettolonen":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"te_betalen_sociale_lasten_loonbelasting":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"te_betalen_pensioenpremies":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"te_betalen_vakantiegeld":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"te_betalen_eindejaarsuitkering":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"te_betalen_loonbeslag":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]}},"required":["period_date"],"description":"Create a Loonjournaalpost"},"UpdateLoonjournaalpost":{"type":"object","properties":{"bruto_loon":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"werkgeversdeel_sociale_lasten":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"werkgeversdeel_pensioenkosten":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"reservering_vakantiegeld":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"reservering_eindejaarsuitkering":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"onbelaste_reiskostenvergoedingen":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"onbelaste_vergoedingen_gericht_vrijgesteld":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"onbelaste_vergoedingen_wkr":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"declaraties_algemeen":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"declaraties_kantoorkosten":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"declaraties_lunch_en_diner_representatiekosten":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"declaraties_reiskosten":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"declaraties_auto_en_transportkosten":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"declaraties_verkoopkosten":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"eindheffing_wkr":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"netto_inhoudingen":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"afdrachtvermindering_speur_en_ontwikkelingswerk":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"te_betalen_nettolonen":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"te_betalen_sociale_lasten_loonbelasting":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"te_betalen_pensioenpremies":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"te_betalen_vakantiegeld":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"te_betalen_eindejaarsuitkering":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]},"te_betalen_loonbeslag":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"Amount for interpolated ledger account","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"number","format":"double"},"currency":{"type":"string"}},"required":["value","currency"]}},"description":"Update a Loonjournaalpost"},"Jortt_V1_Entities_Responses_Reports_Summaries_GetInvoicesResponse":{"type":"object","properties":{"data":{"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_InvoicesSummary"}],"description":"Response object containing invoice summaries for the current year, grouped by payment status"}},"required":["data"],"description":"Jortt_V1_Entities_Responses_Reports_Summaries_GetInvoicesResponse model"},"Jortt_V1_Entities_InvoicesSummary":{"type":"object","properties":{"sent":{"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_InvoiceSummary"}],"description":"A Summary of all invoices sent this year."},"due_and_late":{"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_InvoiceSummary"}],"description":"A Summary of all invoices sent this year which have not been paid yet."},"late":{"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_InvoiceSummary"}],"description":"A Summary of all invoices sent this year which have not been paid and are overdue."}},"required":["sent","due_and_late","late"]},"Jortt_V1_Entities_InvoiceSummary":{"type":"object","properties":{"count":{"type":"integer","format":"int32","description":"The number of invoices in this group."},"amount":{"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}],"description":"The sum of amounts for invoices in this group."}},"required":["count","amount"]},"Jortt_V1_Entities_Responses_Reports_Summaries_GetBtwResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/definitions/Jortt_V1_Entities_BtwSummary"},"description":"Response object containing a list of summarized btw data up until the current date."}},"required":["data"],"description":"Jortt_V1_Entities_Responses_Reports_Summaries_GetBtwResponse model"},"Jortt_V1_Entities_BtwSummary":{"type":"object","properties":{"period_indicator":{"type":"string","example":["mrt 2024","Q1 2024","2024"],"description":"String indicating the BTW period, changes based on btw interval as set per organization."},"total_vat":{"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}],"description":"Total vat due for the indicated period."}},"required":["period_indicator","total_vat"]},"Jortt_V1_Entities_Responses_Reports_Summaries_GetBalancesResponse":{"type":"object","properties":{"data":{"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_BalanceSummary"}],"description":"Response object containing a summary of balance data for dashboard display"}},"required":["data"],"description":"Jortt_V1_Entities_Responses_Reports_Summaries_GetBalancesResponse model"},"Jortt_V1_Entities_BalanceSummary":{"type":"object","properties":{"balans_vaste_activa":{"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}],"description":"Balance for fixed assets for the current year."},"balans_vlottende_activa":{"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}],"description":"Balance for current assets for the current year."},"balans_eigen_vermogen":{"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}],"description":"Balance for equity for the current year."},"balans_overige_passiva":{"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}],"description":"Balance for other liabilities for the current year."}},"required":["balans_vaste_activa","balans_vlottende_activa","balans_eigen_vermogen","balans_overige_passiva"]},"Jortt_V1_Entities_Responses_Reports_Summaries_GetProfitAndLossReponse":{"type":"object","properties":{"data":{"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_ProfitAndLossSummary"}],"description":"Response object containing a summary of profit and loss data for dashboard display"}},"required":["data"],"description":"Jortt_V1_Entities_Responses_Reports_Summaries_GetProfitAndLossReponse model"},"Jortt_V1_Entities_ProfitAndLossSummary":{"type":"object","properties":{"total_opbrengsten":{"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}],"description":"Total income for the current year up until the current date."},"total_kosten":{"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}],"description":"Total costs for the current year up until the current date."},"possible_profit":{"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}],"description":"Possible profit for the current year up until the current date."},"end_date":{"type":"string","format":"date","description":"The date up until the profit and loss values were calculated."}},"required":["total_opbrengsten","total_kosten","possible_profit","end_date"]},"Jortt_V1_Entities_Responses_Reports_Summaries_GetCashAndBankResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/definitions/Jortt_V1_Entities_CashAndBankSummary"},"description":"Response object containing a list of summarized cash and bank entities."}},"required":["data"],"description":"Jortt_V1_Entities_Responses_Reports_Summaries_GetCashAndBankResponse model"},"Jortt_V1_Entities_CashAndBankSummary":{"type":"object","properties":{"bank_accounts":{"type":"array","items":{"$ref":"#/definitions/Jortt_V1_Entities_BankAccountSummary"},"description":"A list of summarized bank account information for the organization's bank accounts."},"liquid_assets":{"type":"array","items":{"$ref":"#/definitions/Jortt_V1_Entities_LiquidAssetSummary"},"description":"A list of summarized liquid asset details for the organization's liquid assets."},"kas":{"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_CashSummary"}],"description":"A summary of the organizations cash assets"},"has_mollie":{"type":"boolean","description":"Whether or not the organization has an active mollie integration."}},"required":["bank_accounts","liquid_assets","kas","has_mollie"]},"Jortt_V1_Entities_BankAccountSummary":{"type":"object","properties":{"bank_account_id":{"type":"string","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a","description":"Resource identifier (UUID)"},"archived":{"type":"boolean","example":false,"description":"Whether or not the item has been archived."},"description":{"type":"string","example":"User set description","description":"The name of the bank at which the account is held."},"balance":{"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}],"description":"The balance for this account."},"iban":{"type":"string","example":"NL10BANK 1234 5678 90","description":"IBAN number of the account"},"bank":{"type":"string","example":"Rabobank","description":"The name of the bank at which the account is held."}},"required":["bank_account_id","archived","description","balance","iban","bank"]},"Jortt_V1_Entities_LiquidAssetSummary":{"type":"object","properties":{"archived":{"type":"boolean","example":false,"description":"Whether or not the item has been archived."},"ledger_account_id":{"type":"string","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a","description":"Resource identifier (UUID)"},"balance":{"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}],"description":"The balance for this asset."},"display_number":{"type":"string","example":"credit_card","description":"Type describing the liquid asset. Can be one of the following: savings_account, credit_card, ideal, mollie, paypal, aandelen, other.\n"},"liquid_asset_type":{"type":"string","example":"credit_card","description":"Type describing the liquid asset. Can be one of the following: savings_account, credit_card, ideal, mollie, paypal, aandelen, other.\n"}},"required":["archived","ledger_account_id","balance","display_number","liquid_asset_type"]},"Jortt_V1_Entities_CashSummary":{"type":"object","properties":{"balance":{"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}],"description":"The balance held in cash."}},"required":["balance"]},"UploadReceipt":{"type":"array","items":{"type":"object","properties":{"images":{"type":"array","description":"An array of base64 encoded images","items":{"type":"string"}}},"required":["images"]},"description":"Upload images to jortt"},"Jortt_V1_Entities_Responses_ResourcesCreatedResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"ids":{"type":"array","items":{"type":"string"},"example":"['f8fd3e4e-da1c-43a7-892f-1410ac13e38a']","description":"The identifiers of the created resources"}},"required":["ids"]}},"required":["data"],"description":"Jortt_V1_Entities_Responses_ResourcesCreatedResponse model"},"Jortt_V1_Entities_Responses_ListProjectsResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/definitions/Jortt_V1_Entities_Project"},"description":"Response object containing a list of Projects"},"_links":{"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Link"}],"description":"Links to help navigate through the lists of objects."}},"required":["data","_links"],"description":"Jortt_V1_Entities_Responses_ListProjectsResponse model"},"Jortt_V1_Entities_Project":{"type":"object","properties":{"organization_id":{"type":"string","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a","description":"Resource identifier (UUID)"},"aggregate_id":{"type":"string","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a","description":"Resource identifier (UUID)"},"name":{"type":"string","example":"Jortt Logo Design","description":"The name of the project.\n"},"reference":{"type":"string","example":"123","description":"Custom reference (for example an order ID)"},"customer_id":{"type":"string","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a","description":"Resource identifier (UUID)"},"description":{"type":"string","example":"This project is fun","description":"A string describing the project.\n"},"created_at":{"type":"string","format":"date-time","example":"2020-02-23T00:00:00.000+01:00","description":"When the item was created"},"updated_at":{"type":"string","format":"date-time","example":"2020-02-23T00:00:00.000+01:00","description":"When the item was updated"},"archived":{"type":"boolean","example":false,"description":"Whether or not the item has been archived."},"is_internal":{"type":"boolean","example":false,"description":"Whether or not the project is an internal project. Internal projects are not attributed to customer.\n"},"customer_record":{"$ref":"#/definitions/Jortt_V1_Entities_Customer"},"default_registration_description":{"type":"string","example":"This project is fun","description":"A string that can be used to prefill project registrations.\n"},"default_hourly_rate":{"example":{"value":"10.10","currency":"EUR"},"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}],"description":"An amount that can be used to prefill project registrations."},"minutes_this_month":{"type":"integer","format":"int32","example":20,"description":"The time logged to this project this month in minutes.\n"},"total_minutes":{"type":"integer","format":"int32","example":200,"description":"The total time logged to this project in minutes.\n"},"total_value":{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}},"required":["organization_id","aggregate_id","name","reference","customer_id","description","created_at","updated_at","archived","is_internal","customer_record","default_registration_description","default_hourly_rate","minutes_this_month","total_minutes","total_value"]},"CreateProject":{"type":"object","properties":{"name":{"type":"string","description":"The name of the project.\n","example":"Jortt Logo Design"},"description":{"type":"string","description":"A string describing the project.\n","example":"This project is fun"},"reference":{"type":"string","description":"Custom reference (for example an order ID)","example":"123"},"is_internal":{"type":"boolean","description":"Whether or not the project is an internal project. Internal projects are not attributed to customer.\n","example":false},"default_registration_description":{"type":"string","description":"A string that can be used to prefill project registrations.\n","example":"This project is fun"},"default_hourly_rate":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"can be used to prefill project registrations","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"string"},"currency":{"type":"string"}},"required":["value","currency"]},"customer_id":{"type":"string","description":"Resource identifier (UUID) for the project customer. Required if `is_internal` is false.\n","example":"500fda8f-2c95-46b7-983b-f12df4f75b21"}},"required":["name"],"description":"Creates a Project"},"Jortt_V1_Entities_Responses_GetProjectResponse":{"type":"object","properties":{"data":{"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Project"}],"description":"Response object containing a single Invoice"}},"required":["data"],"description":"Jortt_V1_Entities_Responses_GetProjectResponse model"},"UpdateProject":{"type":"object","properties":{"name":{"type":"string","description":"The name of the project.\n","example":"Jortt Logo Design"},"description":{"type":"string","description":"A string describing the project.\n","example":"This project is fun"},"reference":{"type":"string","description":"Custom reference (for example an order ID)","example":"123"},"is_internal":{"type":"boolean","description":"Whether or not the project is an internal project. Internal projects are not attributed to customer.\n","example":false},"default_registration_description":{"type":"string","description":"A string that can be used to prefill project registrations.\n","example":"This project is fun"},"default_hourly_rate":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"can be used to prefill project registrations","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"string"},"currency":{"type":"string"}},"required":["value","currency"]},"customer_id":{"type":"string","description":"Resource identifier (UUID) for the project customer. Required if `is_internal` is false.\n","example":"500fda8f-2c95-46b7-983b-f12df4f75b21"}},"description":"Updates a Project"},"InvoiceProject":{"type":"array","items":{"type":"object","properties":{"line_item_ids":{"type":"array","description":"An array of line item ids to be invoiced","items":{"type":"string"}},"days":{"type":"array","description":"An array of dates on which all invoiceable line items will be invoiced","items":{"type":"string","format":"date"}},"months":{"type":"array","description":"An array of months for which all invoicable line items will be invoiced","items":{"type":"string","format":"date"}}},"required":["line_item_ids"]},"description":"Creates an invoice based on project line items"},"Jortt_V1_Entities_Responses_ListProjectLineItemsResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/definitions/Jortt_V1_Entities_ProjectLineItem"},"description":"Response object containing a list of Project line items for a given project"},"_links":{"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_Link"}],"description":"Links to help navigate through the lists of objects."}},"required":["data","_links"],"description":"Jortt_V1_Entities_Responses_ListProjectLineItemsResponse model"},"Jortt_V1_Entities_ProjectLineItem":{"type":"object","properties":{"project_id":{"type":"string","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a","description":"Resource identifier (UUID)"},"description":{"type":"string","example":"Work done today","description":"A string describing line item.\n"},"amount":{"$ref":"#/definitions/Jortt_V1_Entities_Amount"},"quantity":{"type":"string","example":"202","description":"The number of items as a string. For time registrations this is hours, for cost registrations it is a simple quantity\n"},"invoice_id":{"type":"string","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a","description":"Resource identifier (UUID)"},"user_id":{"type":"string","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a","description":"Resource identifier (UUID)"},"organization_id":{"type":"string","example":"f8fd3e4e-da1c-43a7-892f-1410ac13e38a","description":"Resource identifier (UUID)"},"line_item_id":{"type":"integer","format":"int32","example":22,"description":"The line item's id.\n"},"status":{"type":"string","example":"billable","description":"String indicating the line item's billing status. Can be one of [billable, invoiced, concept, nonbillable]\n"},"date":{"type":"string","format":"date-time","example":"2020-02-23T00:00:00.000+01:00","description":"The date that the line item was logged on."},"line_item_type":{"type":"string","example":"time_registration","description":"String representing the type of line item this is, can either be time_registration or cost.\n"},"created_at":{"type":"string","format":"date-time","example":"2020-02-23T00:00:00.000+01:00","description":"When the item was created"},"updated_at":{"type":"string","format":"date-time","example":"2020-02-23T00:00:00.000+01:00","description":"When the item was updated"},"total_amount":{"$ref":"#/definitions/Jortt_V1_Entities_Amount"},"user_record":{"$ref":"#/definitions/Jortt_V1_Entities_OrganizationUser"}},"required":["project_id","description","amount","quantity","invoice_id","user_id","organization_id","line_item_id","status","date","line_item_type","created_at","updated_at","total_amount","user_record"]},"V1ProjectsIdLineItems":{"type":"object","properties":{"description":{"type":"string","description":"A string describing line item.\n","example":"Work done today"},"date":{"type":"string","format":"date-time","description":"The date that the line item was logged on.","example":"2020-02-23T00:00:00.000+01:00"},"amount":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"represents cost per quantity (hourly rate if time registration)","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"string"},"currency":{"type":"string"}},"required":["value","currency"]},"status":{"type":"string","description":"String indicating the line item's billing status. Can be one of [billable, invoiced, concept, nonbillable]\n","enum":["billable","invoiced","concept","nonbillable"],"example":"billable"},"line_item_type":{"type":"string","description":"String representing the type of line item this is, can either be time_registration or cost.\n","example":"time_registration"},"quantity":{"type":"object","description":"Quantity for this time registration. Only for cost registrations"},"minutes":{"type":"integer","format":"int32","description":"Minutes for this time registration. Only for time registrations"},"user_id":{"type":"string","description":"ID for the user associated with this line item. Uses token owner if not specified"}},"description":"Updates a Project Line item"},"Jortt_V1_Entities_Responses_GetProjectLineItemsSummaryResponse":{"type":"object","properties":{"data":{"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_ProjectLineItemsSummary"}],"description":"Response object containing a summary of line items for the given project"}},"required":["data"],"description":"Jortt_V1_Entities_Responses_GetProjectLineItemsSummaryResponse model"},"Jortt_V1_Entities_ProjectLineItemsSummary":{"type":"object","properties":{"date":{"type":"string","example":"2024-06","description":"A string indicating the date period for the given summary.\n"},"total_minutes":{"type":"integer","format":"int32","example":202,"description":"The total number of minutes logged in the summary period.\n"},"due_minutes":{"type":"integer","format":"int32","example":202,"description":"The total number of billable minutes logged in the summary period.\n"},"total_amount":{"$ref":"#/definitions/Jortt_V1_Entities_Amount"},"due_amount":{"$ref":"#/definitions/Jortt_V1_Entities_Amount"},"due_costs":{"$ref":"#/definitions/Jortt_V1_Entities_Amount"},"costs_amount":{"$ref":"#/definitions/Jortt_V1_Entities_Amount"}},"required":["date","total_minutes","due_minutes","total_amount","due_amount","due_costs","costs_amount"]},"Jortt_V1_Entities_Responses_GetProjectLineItemResponse":{"type":"object","properties":{"data":{"allOf":[{"$ref":"#/definitions/Jortt_V1_Entities_ProjectLineItem"}],"description":"Response object containing a single project line item"}},"required":["data"],"description":"Jortt_V1_Entities_Responses_GetProjectLineItemResponse model"},"ProjectsIdLineItems":{"type":"object","properties":{"description":{"type":"string","description":"A string describing line item.\n","example":"Work done today"},"date":{"type":"string","format":"date-time","description":"The date that the line item was logged on.","example":"2020-02-23T00:00:00.000+01:00"},"amount":{"$ref":"#/definitions/Jortt_V1_Entities_Amount","description":"represents cost per quantity (hourly rate if time registration)","example":{"value":"10.10","currency":"EUR"},"type":"object","properties":{"value":{"type":"string"},"currency":{"type":"string"}},"required":["value","currency"]},"status":{"type":"string","description":"String indicating the line item's billing status. Can be one of [billable, invoiced, concept, nonbillable]\n","enum":["billable","invoiced","concept","nonbillable"],"example":"billable"},"line_item_type":{"type":"string","description":"String representing the type of line item this is, can either be time_registration or cost.\n","example":"time_registration"},"quantity":{"type":"object","description":"Quantity for this time registration. Only for cost registrations"},"minutes":{"type":"integer","format":"int32","description":"Minutes for this time registration. Only for time registrations"},"user_id":{"type":"string","description":"ID for the user associated with this line item. Uses token owner if not specified"}},"description":"Updates a Project Line item"},"Jortt_V1_Entities_Responses_GetFilePutUrlResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"put_url":{"type":"string","description":"An S3 url to PUT the file to"}},"required":["put_url"]}},"required":["data"],"description":"Jortt_V1_Entities_Responses_GetFilePutUrlResponse model"}}} \ No newline at end of file diff --git a/src/openapi.ts b/src/openapi.ts index 4be3834..82ce508 100644 --- a/src/openapi.ts +++ b/src/openapi.ts @@ -4,9 +4,6 @@ import { dirname, join } from "node:path"; const __dirname = dirname(fileURLToPath(import.meta.url)); -/** Where to fetch the spec from when no bundled copy is present. */ -const DEFAULT_SPEC_URL = "https://api.jortt.nl/swagger_doc"; - /** A single generated MCP tool derived from an OpenAPI operation. */ export interface JorttTool { name: string; @@ -77,32 +74,43 @@ const ACTION_SEGMENTS = new Set([ let cachedSpec: SwaggerSpec | null = null; /** - * Load the OpenAPI spec. Prefers the copy bundled next to the compiled module - * (produced at build time); if that is missing, fetches it live from Jortt so - * the server still works from a bare checkout. Override the source with - * `JORTT_OPENAPI_URL`. + * Load the OpenAPI spec bundled next to the compiled module. The spec is + * committed to the repository and refreshed explicitly with + * `npm run fetch-spec`; the server never fetches it from the network on its + * own unless `JORTT_OPENAPI_URL` is explicitly set (since the spec determines + * which requests the tools can make, loading it from a remote source should + * be a deliberate choice). */ async function loadSpec(): Promise { if (cachedSpec) return cachedSpec; - try { - const raw = await readFile(join(__dirname, "openapi.json"), "utf8"); - cachedSpec = JSON.parse(raw) as SwaggerSpec; + const overrideUrl = process.env.JORTT_OPENAPI_URL; + if (overrideUrl) { + const res = await fetch(overrideUrl, { + headers: { Accept: "application/json" }, + signal: AbortSignal.timeout(30_000), + }); + if (!res.ok) { + throw new Error( + `Could not load the Jortt OpenAPI spec from ${overrideUrl} (HTTP ${res.status}).`, + ); + } + cachedSpec = (await res.json()) as SwaggerSpec; return cachedSpec; - } catch { - // No bundled spec — fall back to fetching it. } - const url = process.env.JORTT_OPENAPI_URL || DEFAULT_SPEC_URL; - const res = await fetch(url, { headers: { Accept: "application/json" } }); - if (!res.ok) { + const specPath = join(__dirname, "openapi.json"); + try { + const raw = await readFile(specPath, "utf8"); + cachedSpec = JSON.parse(raw) as SwaggerSpec; + return cachedSpec; + } catch (err) { + const message = err instanceof Error ? err.message : String(err); throw new Error( - `Could not load the Jortt OpenAPI spec from ${url} (HTTP ${res.status}). ` + - `Run \`npm run build\` to bundle it, or set JORTT_OPENAPI_URL.`, + `Could not read the bundled OpenAPI spec at ${specPath}: ${message}. ` + + `Run \`npm run build\` to bundle it (or set JORTT_OPENAPI_URL to load one remotely).`, ); } - cachedSpec = (await res.json()) as SwaggerSpec; - return cachedSpec; } /** Recursively inline `$ref` references into a plain JSON Schema, guarding cycles. */