{
  "openapi": "3.1.0",
  "info": {
    "title": "nl.legal Incasso API",
    "version": "1.0.0",
    "summary": "Incasso-opdrachten geautomatiseerd indienen bij nl.legal",
    "description": "Met de nl.legal Incasso API dient u incasso-opdrachten rechtstreeks vanuit uw eigen software in. U levert de debiteur(en), facturen en optioneel PDF-bijlagen aan; wij maken direct een dossier aan in ons dossiersysteem en starten het traject.\n\n**Belangrijk om te weten**\n- Alle bedragen zijn **gehele getallen in eurocenten** (`amount_cents`): € 1.250,00 = `125000`. Zo zijn afrondingsfouten uitgesloten.\n- De opdrachtgever wordt bepaald door uw API-key; u stuurt deze nooit mee.\n- Eén opdracht = één vordering = één dossier. Meerdere debiteuren in één opdracht betekent dat zij **samen** voor dezelfde vordering worden aangesproken (bijv. twee contractspartijen).\n- Vervolgfacturen met hetzelfde `customer_number` voor een debiteur met een al openstaand dossier worden conform de Wet Incassokosten (WIK) aan dat dossier toegevoegd in plaats van een nieuw dossier te openen.\n- Met een test-key (`nll_test_...`) wordt uw aanvraag volledig gevalideerd (inclusief PDF-controle en AI-factuurcontrole) maar wordt er géén dossier aangemaakt.\n- Meegestuurde PDF's worden door AI vergeleken met uw opgave; afwijkingen komen als niet-blokkerende `warnings` terug. **Uw opgave blijft altijd leidend.**\n\nEen API-key vraagt u aan via incasso@nl.legal of uw accountmanager.",
    "termsOfService": "https://nl.legal/algemene-voorwaarden",
    "contact": {
      "name": "nl.legal API support",
      "email": "incasso@nl.legal",
      "url": "https://nl.legal/api"
    }
  },
  "servers": [
    { "url": "https://nl.legal/api/v1", "description": "Productie (live- en test-keys)" }
  ],
  "externalDocs": {
    "description": "Handleiding en voorbeelden",
    "url": "https://nl.legal/api"
  },
  "security": [{ "bearerAuth": [] }],
  "paths": {
    "/ping": {
      "get": {
        "operationId": "ping",
        "summary": "Verbindingstest",
        "description": "Controleer of de API bereikbaar is en (optioneel) of uw API-key geldig is. Zonder Authorization-header krijgt u `authenticated: false` terug.",
        "security": [{ "bearerAuth": [] }, {}],
        "tags": ["Systeem"],
        "responses": {
          "200": {
            "description": "API is bereikbaar",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": { "type": "string", "const": "ok" },
                    "time": { "type": "string", "format": "date-time" },
                    "authenticated": { "type": "boolean" },
                    "client": {
                      "type": "object",
                      "description": "Alleen aanwezig bij een geldige API-key.",
                      "properties": {
                        "name": { "type": "string" },
                        "mode": { "type": "string", "enum": ["live", "test"] },
                        "creditor": { "type": "string", "description": "Naam van de gekoppelde opdrachtgever." }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/orders": {
      "get": {
        "operationId": "listOrders",
        "summary": "Opdrachten lijsten of op IN-nummer zoeken",
        "description": "Geeft uw eerder ingediende opdrachten terug, nieuwste eerst, met cursorpaginatie. Handig voor reconciliatie. Filter onder andere op `case_number` om op het dossiernummer (IN-nummer) te zoeken.\n\nWelke opdrachten u ziet hangt af van de instelling van uw key: óf alleen wat met deze key is ingediend, óf alle API-opdrachten van uw organisatie. U ziet nooit opdrachten van een andere opdrachtgever.",
        "tags": ["Opdrachten"],
        "parameters": [
          { "name": "case_number", "in": "query", "required": false, "schema": { "type": "string" }, "description": "Filter op dossiernummer (IN-nummer), bijv. IN211543." },
          { "name": "reference", "in": "query", "required": false, "schema": { "type": "string" }, "description": "Filter op uw eigen referentie." },
          { "name": "customer_number", "in": "query", "required": false, "schema": { "type": "string" }, "description": "Filter op het klantnummer van een debiteur." },
          { "name": "type", "in": "query", "required": false, "schema": { "type": "string", "enum": ["pre-incasso", "incasso"] } },
          { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 25 } },
          { "name": "cursor", "in": "query", "required": false, "schema": { "type": "string" }, "description": "De `next_cursor` uit de vorige pagina." }
        ],
        "responses": {
          "200": {
            "description": "Gepagineerde lijst met opdrachten.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OrderList" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "post": {
        "operationId": "createOrder",
        "summary": "Incasso-opdracht indienen",
        "description": "Dient een incasso-opdracht in. De verwerking is synchroon: u krijgt direct het dossiernummer (of een foutmelding) terug. Reken op 5 tot 15 seconden bij opdrachten met bijlagen.\n\n**Consumenten-incasso en de WIK-aanmaning:** dient u een `incasso` (geen `pre-incasso`) in tegen een particuliere debiteur (`type: individual`), dan is de WIK 14-dagenbrief wettelijk verplicht naast de factuur (art. 6:96 BW). Stuur die aanmaning als PDF mee en label de bijlage met `document_type: \"wik_letter\"`. Ontbreekt de aanmaning, dan weigeren wij de opdracht met code `wik_letter_required`. Heeft de consument de 14-dagentermijn nog niet gehad, dien dan in als `pre-incasso`; dan versturen wij de aanmaning.\n\nGebruik de optionele `Idempotency-Key` header om te voorkomen dat een netwerk-retry dezelfde opdracht twee keer indient: bij een herhaald verzoek met dezelfde key en dezelfde inhoud krijgt u exact dezelfde response terug (met header `Idempotency-Replayed: true`).\n\n**Rate limiting:** standaard 30 verzoeken per minuut en 500 per dag per key. Elke response op dit endpoint bevat de headers `RateLimit-Limit`, `RateLimit-Remaining` en `RateLimit-Reset` (seconden tot het minuutvenster reset). Bij overschrijding volgt een 429 met `Retry-After`.",
        "tags": ["Opdrachten"],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": { "type": "string", "maxLength": 255 },
            "description": "Unieke sleutel per opdracht (bijv. een UUID). Beschermt tegen dubbele indiening bij netwerk-retries."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/CreateOrder" },
              "example": {
                "type": "incasso",
                "reference": "ORDER-2026-0042",
                "description": "Onbetaalde facturen voor webdesign en hosting",
                "debtors": [
                  {
                    "type": "business",
                    "company_name": "Voorbeeld Webshop B.V.",
                    "customer_number": "KLANT-1234",
                    "email": "administratie@voorbeeldwebshop.nl",
                    "phone": "0612345678",
                    "address": {
                      "street": "Hoofdstraat",
                      "house_number": "12",
                      "house_number_suffix": "a",
                      "postal_code": "1234 AB",
                      "city": "Amsterdam",
                      "country": "NL"
                    }
                  }
                ],
                "invoices": [
                  {
                    "invoice_number": "F2026-0815",
                    "invoice_date": "2026-04-01",
                    "due_date": "2026-04-15",
                    "amount_cents": 125000,
                    "description": "Webdesign april 2026"
                  },
                  {
                    "invoice_number": "F2026-0901",
                    "invoice_date": "2026-05-01",
                    "payment_term_days": 14,
                    "amount_cents": 50000,
                    "description": "Hosting Q2 2026"
                  }
                ],
                "attachments": [
                  { "filename": "F2026-0815.pdf", "content_base64": "JVBERi0xLjQK..." }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Opdracht verwerkt. Bij een live-key is het dossier aangemaakt (of toegevoegd aan een bestaand open dossier, zie `case.new` en de warning `attached_to_existing_case`).",
            "headers": {
              "RateLimit-Limit": { "schema": { "type": "integer" }, "description": "Uw limiet per minuut." },
              "RateLimit-Remaining": { "schema": { "type": "integer" }, "description": "Resterende verzoeken in het huidige minuutvenster." },
              "RateLimit-Reset": { "schema": { "type": "integer" }, "description": "Seconden tot het minuutvenster reset." },
              "Idempotency-Replayed": { "schema": { "type": "boolean" }, "description": "Aanwezig en `true` als dit een herhaalde response op dezelfde Idempotency-Key is." }
            },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Order" } } }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "409": {
            "description": "Idempotency-Key is eerder gebruikt voor een andere aanvraag.",
            "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
          },
          "413": {
            "description": "De totale aanvraag (de volledige base64-gecodeerde JSON) is groter dan 40 MB. Let op: base64 maakt binaire bestanden circa 33% groter, dus 25 MB aan PDF's wordt ongeveer 33 MB in de aanvraag.",
            "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
          },
          "415": {
            "description": "Content-Type is geen application/json.",
            "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
          },
          "422": {
            "description": "Validatiefout: één of meer velden of bijlagen zijn ongeldig, OF de verplichte WIK 14-dagenbrief ontbreekt bij een consumenten-incasso (code `wik_letter_required`). Er is géén dossier aangemaakt.",
            "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
          },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "502": {
            "description": "Het dossiersysteem kon de opdracht niet verwerken. Probeer het later opnieuw met dezelfde Idempotency-Key.",
            "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
          }
        }
      }
    },
    "/orders/{orderId}": {
      "get": {
        "operationId": "getOrder",
        "summary": "Status van een opdracht opvragen",
        "description": "Vraagt de actuele status van een eerder ingediende opdracht op, inclusief de live dossierstatus en het openstaande saldo.",
        "tags": ["Opdrachten"],
        "parameters": [
          {
            "name": "orderId",
            "in": "path",
            "required": true,
            "schema": { "type": "string", "pattern": "^ord_[a-f0-9]{16}$" },
            "description": "Het order-ID uit de response van het indienen (bijv. `ord_b2b332c6a34ebb12`)."
          }
        ],
        "responses": {
          "200": {
            "description": "Actuele status van de opdracht.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OrderStatus" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": {
            "description": "Geen opdracht met dit ID voor uw account.",
            "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
          },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/orders/{orderId}/hold": {
      "post": {
        "operationId": "holdOrder",
        "summary": "Pauze aanvragen",
        "description": "Verzoekt om het dossier te pauzeren (bijv. de debiteur belt of betwist). De API wijzigt zelf niets aan het dossier: er wordt een taak met instructies in de agenda van het dossier gezet, die een behandelaar verwerkt. Antwoordt met 202 Accepted.",
        "tags": ["Stuuracties"],
        "parameters": [{ "name": "orderId", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^ord_[a-f0-9]{16}$" } }],
        "requestBody": { "required": false, "content": { "application/json": { "schema": { "type": "object", "properties": { "reason": { "type": "string", "maxLength": 1000, "description": "Toelichting voor de behandelaar." } } } } } },
        "responses": {
          "202": { "description": "Verzoek geregistreerd als taak op het dossier.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OrderAction" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "description": "Onbekende opdracht voor uw account.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } } },
          "409": { "description": "Opdracht heeft geen dossier (testmodus).", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } } },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/orders/{orderId}/withdraw": {
      "post": {
        "operationId": "withdrawOrder",
        "summary": "Intrekking aanvragen",
        "description": "Verzoekt om het dossier in te trekken. Let op: intrekken kan no-cure-no-pay-kosten raken. De API plaatst een taak met instructies op het dossier; een behandelaar beoordeelt en handelt af. Antwoordt met 202 Accepted.",
        "tags": ["Stuuracties"],
        "parameters": [{ "name": "orderId", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^ord_[a-f0-9]{16}$" } }],
        "requestBody": { "required": false, "content": { "application/json": { "schema": { "type": "object", "properties": { "reason": { "type": "string", "maxLength": 1000 } } } } } },
        "responses": {
          "202": { "description": "Verzoek geregistreerd als taak op het dossier.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OrderAction" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "description": "Onbekende opdracht voor uw account.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } } },
          "409": { "description": "Opdracht heeft geen dossier (testmodus).", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } } },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/orders/{orderId}/payments": {
      "post": {
        "operationId": "reportPayment",
        "summary": "Directe betaling melden",
        "description": "Meldt dat de debiteur RECHTSTREEKS aan u (de opdrachtgever) heeft betaald. De API plaatst een taak met het bedrag en de datum op het dossier; een behandelaar boekt de betaling en het saldo + de incassokosten worden herberekend. Antwoordt met 202 Accepted.",
        "tags": ["Stuuracties"],
        "parameters": [{ "name": "orderId", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^ord_[a-f0-9]{16}$" } }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": {
            "type": "object",
            "required": ["amount_cents"],
            "properties": {
              "amount_cents": { "type": "integer", "minimum": 1, "description": "Het ontvangen bedrag in eurocenten." },
              "payment_date": { "type": "string", "format": "date", "description": "Datum van ontvangst (YYYY-MM-DD). Standaard vandaag." },
              "reason": { "type": "string", "maxLength": 1000, "description": "Toelichting (bijv. betaalwijze, kenmerk)." }
            }
          } } }
        },
        "responses": {
          "202": { "description": "Betaalmelding geregistreerd als taak op het dossier.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OrderAction" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "description": "Onbekende opdracht voor uw account.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } } },
          "409": { "description": "Opdracht heeft geen dossier (testmodus).", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } } },
          "422": { "description": "Ongeldig of ontbrekend bedrag.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } } },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Uw API-key: `Authorization: Bearer nll_live_...` (productie) of `Authorization: Bearer nll_test_...` (testmodus, maakt geen dossiers aan)."
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Ongeldige of lege JSON.",
        "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
      },
      "Unauthorized": {
        "description": "Ontbrekende of ongeldige API-key.",
        "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
      },
      "RateLimited": {
        "description": "Rate limit overschreden. Zie de `Retry-After` header.",
        "headers": {
          "Retry-After": { "schema": { "type": "integer" }, "description": "Aantal seconden tot een nieuwe poging zinvol is." },
          "RateLimit-Limit": { "schema": { "type": "integer" }, "description": "Uw limiet per minuut." },
          "RateLimit-Remaining": { "schema": { "type": "integer" }, "description": "Resterende verzoeken in het huidige venster." },
          "RateLimit-Reset": { "schema": { "type": "integer" }, "description": "Seconden tot het minuutvenster reset." }
        },
        "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
      }
    },
    "schemas": {
      "CreateOrder": {
        "type": "object",
        "required": ["type", "debtors", "invoices"],
        "properties": {
          "type": {
            "type": "string",
            "enum": ["pre-incasso", "incasso"],
            "description": "`pre-incasso`: wij sturen eerst een (kosteloze) WIK-aanmaning namens u. `incasso`: het volledige minnelijke incassotraject start direct."
          },
          "reference": {
            "type": ["string", "null"],
            "maxLength": 100,
            "description": "Uw eigen kenmerk voor deze opdracht (komt terug in alle responses en in het dossier)."
          },
          "title": {
            "type": ["string", "null"],
            "maxLength": 200,
            "description": "De titel/omschrijving van de vordering, idealiter in de vorm 'overeenkomst van opdracht voor ...'. Geeft u deze niet op, dan genereert onze AI er een op basis van uw omschrijving en de facturen."
          },
          "description": {
            "type": ["string", "null"],
            "maxLength": 2000,
            "description": "Vrije omschrijving van de vordering (bijv. de geleverde dienst). Helpt onze behandelaars en de AI-titelherkenning."
          },
          "metadata": {
            "type": "object",
            "additionalProperties": { "type": "string" },
            "description": "Vrij invulbare sleutel/waarde-paren (max. 30 sleutels, sleutel max. 40 tekens, waarde max. 500 tekens). Wordt opgeslagen, in alle responses teruggegeven en als notitie in het dossier gezet. Handig om uw eigen identifiers mee te geven.",
            "example": { "po_nummer": "PO-2026-77", "kostenplaats": "sales" }
          },
          "udf_fields": {
            "type": "object",
            "additionalProperties": { "type": "string" },
            "description": "Optionele maatwerkvelden (UDF / flexfields) die als veld op het dossier worden gezet. U mag meerdere velden tegelijk meegeven als sleutel/waarde-paren (max. 25). Welke veldnamen voor uw account beschikbaar zijn, krijgt u van ons; gebruik exact die sleutels. Een onbekende sleutel wordt overgeslagen met de waarschuwing `udf_field_unknown`. Waarden zijn tekst, een getal, een boolean of een datum (YYYY-MM-DD); het juiste type wordt bepaald door de velddefinitie.",
            "example": { "uw_veldnaam_1": "waarde", "uw_veldnaam_2": "2026-06-01" }
          },
          "debtors": {
            "type": "array",
            "minItems": 1,
            "maxItems": 5,
            "items": { "$ref": "#/components/schemas/Debtor" },
            "description": "De debiteur(en). Meerdere debiteuren = zelfde vordering, samen aangesproken (bijv. beide contractspartijen). Eén opdracht kan nooit losse vorderingen op verschillende debiteuren combineren."
          },
          "invoices": {
            "type": "array",
            "minItems": 1,
            "maxItems": 50,
            "items": { "$ref": "#/components/schemas/Invoice" }
          },
          "attachments": {
            "type": "array",
            "maxItems": 10,
            "items": { "$ref": "#/components/schemas/Attachment" },
            "description": "PDF-facturen als base64. Limieten op de binaire (gedecodeerde) inhoud: max. 10 MB per bestand en 25 MB totaal. Daarnaast geldt een limiet van 40 MB op de volledige base64-gecodeerde aanvraag (base64 is ca. 33% groter dan binair). Bestanden worden gevalideerd (PDF-structuur, geen wachtwoordbeveiliging) en aan het dossier toegevoegd."
          }
        }
      },
      "Debtor": {
        "type": "object",
        "required": ["type", "address"],
        "properties": {
          "type": { "type": "string", "enum": ["business", "individual"], "description": "`business` = bedrijf, `individual` = particulier." },
          "company_name": { "type": "string", "maxLength": 200, "description": "Verplicht bij type `business`." },
          "first_name": { "type": ["string", "null"], "maxLength": 100, "description": "Voornaam (alleen bij `individual`)." },
          "middle_name": { "type": ["string", "null"], "maxLength": 50, "description": "Tussenvoegsel (alleen bij `individual`)." },
          "last_name": { "type": "string", "maxLength": 100, "description": "Verplicht bij type `individual`." },
          "customer_number": {
            "type": ["string", "null"],
            "maxLength": 50,
            "pattern": "^[A-Za-z0-9._/\\- ]+$",
            "description": "Uw unieke klantnummer voor deze debiteur. Sterk aanbevolen: bij een vervolgfactuur met hetzelfde klantnummer voegen wij de vordering conform de WIK toe aan een eventueel al openstaand dossier."
          },
          "email": { "type": ["string", "null"], "format": "email" },
          "phone": { "type": ["string", "null"], "maxLength": 30 },
          "address": { "$ref": "#/components/schemas/Address" }
        }
      },
      "Address": {
        "type": "object",
        "required": ["street", "house_number", "postal_code", "city"],
        "properties": {
          "street": { "type": "string", "maxLength": 150 },
          "house_number": { "type": "string", "maxLength": 10 },
          "house_number_suffix": { "type": ["string", "null"], "maxLength": 10 },
          "postal_code": { "type": "string", "maxLength": 12, "description": "Voor Nederland gevalideerd op formaat 1234 AB." },
          "city": { "type": "string", "maxLength": 100 },
          "country": { "type": "string", "default": "NL", "minLength": 2, "maxLength": 2, "description": "ISO-3166 tweelettercode." }
        }
      },
      "Invoice": {
        "type": "object",
        "required": ["invoice_number", "invoice_date", "amount_cents"],
        "properties": {
          "invoice_number": { "type": "string", "maxLength": 50 },
          "invoice_date": { "type": "string", "format": "date", "description": "YYYY-MM-DD, niet in de toekomst." },
          "due_date": { "type": ["string", "null"], "format": "date", "description": "Vervaldatum. Geef óf `due_date` óf `payment_term_days` op." },
          "payment_term_days": { "type": ["integer", "null"], "minimum": 0, "maximum": 365, "description": "Betaaltermijn in dagen na factuurdatum; wij berekenen dan de vervaldatum." },
          "amount_cents": { "type": "integer", "minimum": 1, "maximum": 100000000000, "description": "Openstaand bedrag incl. btw in EUROCENTEN: € 1.250,00 = 125000. Boven dit maximum (zeer grote vorderingen) krijgt u code `amount_too_high`; neem dan contact op." },
          "description": { "type": ["string", "null"], "maxLength": 500, "description": "Optionele omschrijving van de factuur." }
        }
      },
      "Attachment": {
        "type": "object",
        "required": ["filename", "content_base64"],
        "properties": {
          "filename": { "type": "string", "maxLength": 120, "description": "Bestandsnaam (bij voorkeur eindigend op .pdf). De validatie kijkt naar de daadwerkelijke bestandsinhoud, niet naar de extensie; de opgeslagen naam wordt zo nodig met .pdf genormaliseerd." },
          "content_base64": { "type": "string", "contentEncoding": "base64", "description": "De PDF als base64-string (zonder data:-prefix)." },
          "document_type": { "type": ["string", "null"], "enum": ["invoice", "wik_letter", "other", null], "description": "Type document. Label de WIK 14-dagenbrief als `wik_letter`: bij een consumenten-incasso is die verplicht (zie de beschrijving van het indien-endpoint)." }
        }
      },
      "Order": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "description": "Uniek order-ID (`ord_...`). Bewaar dit voor statusopvragingen." },
          "object": { "type": "string", "const": "order" },
          "mode": { "type": "string", "enum": ["live", "test"] },
          "type": { "type": "string", "enum": ["pre-incasso", "incasso"] },
          "reference": { "type": ["string", "null"] },
          "case": {
            "type": "object",
            "properties": {
              "case_number": { "type": ["string", "null"], "description": "Het dossiernummer (bijv. IN211543). `null` in testmodus." },
              "new": { "type": "boolean", "description": "`false` als de facturen conform de WIK aan een bestaand open dossier zijn toegevoegd." }
            }
          },
          "debtors": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": { "type": "string" },
                "customer_number": { "type": ["string", "null"] }
              }
            }
          },
          "invoice_count": { "type": "integer" },
          "total_amount_cents": { "type": "integer", "description": "De aangeleverde hoofdsom (som van de facturen) in eurocenten, exclusief incassokosten en rente." },
          "metadata": { "type": "object", "additionalProperties": { "type": "string" }, "description": "De door u meegegeven metadata, ongewijzigd teruggegeven." },
          "udf_fields": { "type": "object", "additionalProperties": { "type": "string" }, "description": "De door u meegegeven maatwerkvelden, ongewijzigd teruggegeven." },
          "warnings": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/Warning" },
            "description": "Niet-blokkerende signaleringen. De opdracht is gewoon verwerkt; u bepaalt zelf wat u met deze meldingen doet."
          },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "OrderList": {
        "type": "object",
        "properties": {
          "object": { "type": "string", "const": "list" },
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": { "type": "string" },
                "object": { "type": "string", "const": "order" },
                "mode": { "type": "string", "enum": ["live", "test"] },
                "type": { "type": "string", "enum": ["pre-incasso", "incasso"] },
                "reference": { "type": ["string", "null"] },
                "case": {
                  "type": "object",
                  "properties": {
                    "case_number": { "type": ["string", "null"] },
                    "new": { "type": "boolean" }
                  }
                },
                "debtors_summary": { "type": "string", "description": "Komma-gescheiden debiteurnamen." },
                "invoice_count": { "type": "integer" },
                "total_amount_cents": { "type": "integer", "description": "De aangeleverde hoofdsom in eurocenten." },
                "created_at": { "type": "string", "format": "date-time" }
              }
            }
          },
          "has_more": { "type": "boolean" },
          "next_cursor": { "type": ["string", "null"], "description": "Geef mee als `cursor` om de volgende pagina op te halen. `null` als er geen volgende pagina is." }
        }
      },
      "OrderStatus": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "object": { "type": "string", "const": "order" },
          "mode": { "type": "string", "enum": ["live", "test"] },
          "type": { "type": "string", "enum": ["pre-incasso", "incasso"] },
          "reference": { "type": ["string", "null"] },
          "case": {
            "type": "object",
            "properties": {
              "case_number": { "type": ["string", "null"] },
              "new": { "type": "boolean", "description": "Weerspiegelt het indienmoment: of er toen een nieuw dossier is aangemaakt (false = toegevoegd aan een bestaand open dossier)." },
              "status": { "type": ["string", "null"], "description": "Actuele dossierstatus, bijv. Nieuw, In behandeling, Gesloten." },
              "open": { "type": "boolean", "description": "Of het dossier nog openstaat." },
              "balance_due_cents": { "type": "integer", "description": "Door CaseControl berekend openstaand saldo in eurocenten, inclusief incassokosten en rente. Alleen aanwezig zodra het dossier in behandeling is: op een nieuw dossier zijn kosten en rente nog niet vastgesteld, dus dan geven wij dit veld bewust niet terug. Gebruik tot die tijd total_amount_cents (de aangeleverde hoofdsom)." }
            }
          },
          "invoice_count": { "type": "integer" },
          "total_amount_cents": { "type": "integer", "description": "De aangeleverde hoofdsom (som van de facturen) in eurocenten, exclusief incassokosten en rente. Altijd aanwezig. Het actuele saldo inclusief kosten en rente staat in case.balance_due_cents (alleen zodra het dossier in behandeling is)." },
          "warnings": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/Warning" },
            "description": "De niet-blokkerende signaleringen van het indienmoment, opnieuw teruggegeven zodat u ze ook later kunt ophalen."
          },
          "actions": {
            "type": "array",
            "description": "Eerder gemelde stuuracties (pauze/intrekking/betaling), nieuwste eerst.",
            "items": {
              "type": "object",
              "properties": {
                "action": { "type": "string", "enum": ["hold", "withdraw", "payments"] },
                "status": { "type": "string" },
                "reason": { "type": ["string", "null"] },
                "amount_cents": { "type": "integer" },
                "created_at": { "type": "string", "format": "date-time" }
              }
            }
          },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "WebhookEvent": {
        "type": "object",
        "description": "Een event dat nl.legal naar uw webhook-endpoint stuurt.",
        "properties": {
          "id": { "type": "string", "description": "Uniek event-ID (evt_...)." },
          "object": { "type": "string", "const": "event" },
          "type": { "type": "string", "enum": ["payment.received", "correspondence.sent", "invoice.updated", "case.updated", "case.closed", "ping"], "description": "Het type gebeurtenis." },
          "created_at": { "type": "string", "format": "date-time" },
          "data": {
            "type": "object",
            "properties": {
              "order_id": { "type": ["string", "null"], "description": "Het order-ID (ord_...) van de oorspronkelijke opdracht." },
              "case_number": { "type": ["string", "null"], "description": "Het dossiernummer (IN-nummer)." },
              "reference": { "type": ["string", "null"], "description": "Uw eigen referentie." },
              "case_event": { "type": ["string", "null"], "description": "De onderliggende CaseControl-gebeurtenis, bijv. case_payments." }
            }
          }
        }
      },
      "OrderAction": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "description": "Het order-ID waarop de actie betrekking heeft." },
          "object": { "type": "string", "const": "order_action" },
          "action": { "type": "string", "enum": ["hold", "withdraw", "payments"] },
          "status": { "type": "string", "const": "requested", "description": "De actie is als taak geregistreerd; een behandelaar verwerkt deze." },
          "case": { "type": "object", "properties": { "case_number": { "type": ["string", "null"] } } },
          "amount_cents": { "type": "integer", "description": "Alleen bij een betaalmelding." },
          "payment_date": { "type": "string", "format": "date", "description": "Alleen bij een betaalmelding." },
          "message": { "type": "string" },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "Warning": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "description": "Machine-leesbare code.",
            "enum": ["ai_invoice_mismatch", "ai_document_mismatch", "attached_to_existing_case", "multiple_open_cases", "attachment_upload_failed", "claim_attach_failed", "debtor_address_failed", "unknown_field", "udf_field_unknown", "wik_letter_detected", "wik_letter_incomplete", "test_mode"]
          },
          "message": { "type": "string", "description": "Toelichting in het Nederlands." },
          "invoice_number": { "type": ["string", "null"], "description": "Aanwezig als de warning één specifieke factuur betreft." }
        }
      },
      "Problem": {
        "type": "object",
        "description": "Foutformaat volgens RFC 9457 (application/problem+json).",
        "properties": {
          "type": { "type": "string", "format": "uri" },
          "title": { "type": "string" },
          "status": { "type": "integer" },
          "code": { "type": "string", "description": "Machine-leesbare foutcode, bijv. `validation_error`, `rate_limited`, `unauthorized`." },
          "detail": { "type": "string" },
          "errors": {
            "type": "array",
            "description": "Bij validatiefouten (422): per veld de fout.",
            "items": {
              "type": "object",
              "properties": {
                "field": { "type": "string", "description": "Pad naar het veld, bijv. `invoices[0].amount_cents`." },
                "code": { "type": "string" },
                "message": { "type": "string" }
              }
            }
          }
        }
      }
    }
  },
  "webhooks": {
    "event": {
      "post": {
        "summary": "nl.legal stuurt een event naar uw endpoint",
        "description": "Wanneer er iets verandert aan een van uw dossiers (betaling ontvangen, correspondentie verstuurd, dossier bijgewerkt of gesloten), sturen wij een POST naar het webhook-endpoint dat u bij ons heeft laten instellen.\n\n**Verificatie:** elke aflevering bevat een `X-NLLegal-Signature` (HMAC-SHA256, hex), een `X-NLLegal-Timestamp` en een `X-NLLegal-Event` header. Bereken `HMAC_SHA256(timestamp + \".\" + ruwe_body, uw_signing_secret)` en vergelijk timing-safe met de signature-header. Wijs af als ze niet overeenkomen of als de timestamp te oud is.\n\nAntwoord binnen enkele seconden met een 2xx-status. Bij een andere status of een time-out proberen wij het opnieuw met oplopende tussenpozen (tot 6 pogingen).",
        "requestBody": {
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebhookEvent" } } }
        },
        "responses": { "200": { "description": "Ontvangen en verwerkt door uw systeem." } }
      }
    }
  },
  "tags": [
    { "name": "Opdrachten", "description": "Incasso-opdrachten indienen en volgen" },
    { "name": "Stuuracties", "description": "Pauze, intrekking en betaalmeldingen op een lopend dossier" },
    { "name": "Systeem", "description": "Verbinding en beschikbaarheid" }
  ]
}
