{
  "openapi": "3.1.0",
  "info": {
    "title": "Envelopify API",
    "version": "1.0.0",
    "description": "Envelopify sends real paper letters with real postage stamps from your software, in Belgium. One API call per letter: we print, envelope, stamp and hand the letter to bpost. Standard: handed to bpost within 3 working days. Express: the same working day when received before 12:00 (Europe/Brussels). Flex: within 5 working days. bpost delivers within 2 to 3 working days after hand-over. Amounts in euro cents excl. VAT. Error messages are Dutch."
  },
  "servers": [
    {
      "url": "https://envelopify.lovable.app"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key starting with test_ or live_"
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "sender_not_verified",
                  "weight_class_unavailable",
                  "service_unavailable",
                  "invalid_post_date",
                  "address_incomplete",
                  "insufficient_balance",
                  "pdf_invalid",
                  "too_many_sheets",
                  "express_limit",
                  "country_not_supported",
                  "express_not_available",
                  "daily_limit",
                  "template_invalid",
                  "invalid_request",
                  "unauthorized",
                  "not_found",
                  "cutoff_passed",
                  "not_cancellable",
                  "conflict",
                  "rate_limited",
                  "server_error"
                ]
              },
              "message": {
                "type": "string"
              },
              "letter_id": {
                "type": "string"
              }
            }
          }
        }
      }
    }
  },
  "paths": {
    "/api/v1/flow-events": {
      "post": {
        "operationId": "flowEvent",
        "summary": "Queue a flow event",
        "description": "For an active reminder flow and saved contact. Queues a one-time plan, not an immediate letter. Daily processing uses shared pricing and stop checks; insufficient credit retains the queue entry. event_key deduplicates per flow and key mode. Daily and recipient cooldown limits apply; test plans are admin-executed only and never send or charge.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "flow_id": {
                    "type": "string",
                    "description": "Active flow id."
                  },
                  "contact_id": {
                    "type": "string",
                    "description": "Own saved contact id."
                  },
                  "event_key": {
                    "type": "string",
                    "description": "Unique event reference, 1 to 120 characters."
                  },
                  "order_cents": {
                    "type": "integer",
                    "description": "Amount for condition, default 0."
                  },
                  "variables": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    },
                    "description": "Required template fields only."
                  }
                },
                "required": [
                  "flow_id",
                  "contact_id",
                  "event_key"
                ]
              },
              "example": {
                "flow_id": "b1d1a7f7-3db2-43df-a896-bc9dc10c6029",
                "contact_id": "b1d1a7f7-3db2-43df-a896-bc9dc10c6029",
                "event_key": "herinnering-104",
                "variables": {}
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "status": "queued",
                  "test": true
                },
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/letters": {
      "post": {
        "operationId": "briefVersturen",
        "summary": "Send a letter",
        "description": "Creates a letter. Provide exactly one source: a PDF (`pdf_base64` or `pdf_url`) or a template (`template_id` or `template_code`) with `variables`. PDFs follow the account default: a free address sheet or a white address box on page one using `options.address_on_first_page`. Small is up to 50 g, medium up to 100 g and large up to 350 g. Medium and large require a confirmed and enabled surcharge. The address sheet counts for weight, not the base price. Returns `201` with id, status, price with breakdown (`price_breakdown`), sheet count (address sheet included), latest post date for the chosen speed and `test`. With a test key the status is `received` and `post_date` is null. A rejected letter returns `422` with an error code and `letter_id`. Optional header `Idempotency-Key`.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "options": {
                    "type": "object",
                    "properties": {
                      "address_on_first_page": {
                        "type": "boolean",
                        "description": "For uploaded PDFs: fixed opaque white address window on page one, without an extra address sheet. Covered content is erased. Defaults to account preference (initially separate sheet)."
                      },
                      "corrections": {
                        "type": "object",
                        "additionalProperties": {
                          "type": "string"
                        },
                        "description": "PDF corrections: region {left,top,width,height} in mm on A4, window left/right/both, first_page, white_margins, flatten and a4_portrait. Omit to use the saved account rule. The old address region is whitened. The original is retained."
                      },
                      "flat": {
                        "type": "boolean",
                        "description": "Unfolded in a large envelope, packed by hand. Only when activated. Surcharge from /price."
                      },
                      "registered": {
                        "type": "boolean",
                        "description": "Registered paper mail, BE and standard/express only, when activated. Tracking: registered_tracking, registered_handed_in_at and temporary registered_receipt_url."
                      },
                      "color": {
                        "type": "boolean",
                        "description": "Colour print. Default black and white. Surcharge per sheet via /price."
                      },
                      "duplex": {
                        "type": "boolean",
                        "description": "Double-sided: two pages per sheet. Default single-sided."
                      },
                      "qr_tracking": {
                        "type": "boolean",
                        "description": "Dynamic QR code with scan tracking, +€0,05 per letter."
                      },
                      "speed": {
                        "type": "string",
                        "description": "Speed: `standard` (handed to bpost within 3 working days), `express` (same working day when received before 12:00, +€0,15 per letter, minimum €15,00 per day) or `flex` (within 5 working days, €-0,05 per letter). Default `standard`."
                      },
                      "qr_target_url": {
                        "type": "string",
                        "description": "Required with qr_tracking. https URL the QR code redirects to. Can be changed later in the portal."
                      }
                    }
                  },
                  "recipient": {
                    "type": "object",
                    "properties": {
                      "language": {
                        "type": "string",
                        "description": "Optional: nl, fr, en or de. Selects a saved template language variant, otherwise the base text. Defaults by Belgian postcode."
                      },
                      "company": {
                        "type": "string",
                        "description": "Optional. Company name."
                      },
                      "name": {
                        "type": "string",
                        "description": "Recipient name. Name or company is required."
                      },
                      "street": {
                        "type": "string",
                        "description": "Street."
                      },
                      "number": {
                        "type": "string",
                        "description": "House number."
                      },
                      "box": {
                        "type": "string",
                        "description": "Optional. Box number."
                      },
                      "postal_code": {
                        "type": "string",
                        "description": "Postal code. 4 digits in Belgium."
                      },
                      "city": {
                        "type": "string",
                        "description": "City."
                      },
                      "country": {
                        "type": "string",
                        "description": "ISO country code (2 letters), default BE. All other EU countries too (e.g. NL, FR, DE), +€1,50 per letter, delivery 5 to 10 working days after dispatch, no express."
                      }
                    },
                    "required": [
                      "name",
                      "street",
                      "number",
                      "postal_code",
                      "city"
                    ]
                  },
                  "pdf_base64": {
                    "type": "string",
                    "description": "PDF as base64, A4, max 20 MB."
                  },
                  "pdf_url": {
                    "type": "string",
                    "description": "Direct public HTTPS URL of your PDF, max 20 MB. No redirects, credentials, private networks or nonstandard ports."
                  },
                  "template_id": {
                    "type": "string",
                    "description": "Template id, instead of a PDF."
                  },
                  "template_code": {
                    "type": "string",
                    "description": "Or the template code, e.g. herinnering."
                  },
                  "variables": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    },
                    "description": "Values for the template fields, e.g. { \"voornaam\": \"An\" }."
                  },
                  "enclosure_batch_id": {
                    "type": "string",
                    "description": "Received material id from the portal. One copy per letter after activation and receipt inspection. Pre-production cancellation restores stock."
                  },
                  "reference": {
                    "type": "string",
                    "description": "Optional. Your own reference, e.g. invoice number."
                  },
                  "post_on": {
                    "type": "string",
                    "description": "Optional hand-over day (YYYY-MM-DD), a weekday up to 90 calendar days ahead. Not a delivery date. Credit is reserved by charging on creation and refunded if cancelled before production."
                  }
                }
              },
              "example": {
                "recipient": {
                  "name": "An Peeters",
                  "street": "Meir",
                  "number": "12",
                  "postal_code": "2000",
                  "city": "Antwerpen"
                },
                "pdf_url": "https://envelopify.lovable.app/voorbeeld.pdf",
                "options": {
                  "color": false
                },
                "reference": "factuur-2026-104"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Existing letter for this Idempotency-Key",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "201": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "id": "b1d1a7f7-3db2-43df-a896-bc9dc10c6029",
                  "status": "scheduled",
                  "price_cents": 129,
                  "price_breakdown": {
                    "unit_price_cents": 129,
                    "tier": 1,
                    "tier_fixed": false,
                    "volume_30d": 120,
                    "letters_to_next_tier": 379,
                    "pages": 1,
                    "sheets": 1,
                    "address_sheet": true,
                    "total_sheets": 2,
                    "color": false,
                    "duplex": false,
                    "speed": "standard",
                    "destination": "BE",
                    "options": [],
                    "total_cents": 129,
                    "on_request": false,
                    "currency": "EUR",
                    "vat_included": false
                  },
                  "sheet_count": 2,
                  "post_date": "2026-11-13",
                  "free_trial": false,
                  "test": false
                },
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "lijst",
        "summary": "List letters",
        "description": "Returns your most recent letters, newest first, in `data`. When there are more, pass `next_cursor` back as `cursor`.",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Filter by status, e.g. posted."
          },
          {
            "name": "reference",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Filter by reference."
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "From date, e.g. 2026-11-01."
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Up to and including date."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Default 25, max 100."
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Value of next_cursor."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "data": [
                    {
                      "id": "b1d1a7f7-3db2-43df-a896-bc9dc10c6029",
                      "status": "posted",
                      "reference": "factuur-2026-104"
                    }
                  ],
                  "next_cursor": "MjAyNi0xMS0xMlQxMDo..."
                },
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/letters/{id}": {
      "get": {
        "operationId": "status",
        "summary": "Get a letter",
        "description": "Returns all letter fields, timestamps, the price breakdown, the number of QR scans (`scan_count`) and, once posted, a temporary link to the batch photo (`batch_photo_url`, valid 1 hour).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "id": "b1d1a7f7-3db2-43df-a896-bc9dc10c6029",
                  "tracking_code": "B1D1A7F7",
                  "reference": "factuur-2026-104",
                  "status": "posted",
                  "test": false,
                  "reject_reason": null,
                  "return_reason": null,
                  "recipient": {
                    "company": null,
                    "name": "An Peeters",
                    "street": "Meir",
                    "number": "12",
                    "box": null,
                    "postal_code": "2000",
                    "city": "Antwerpen",
                    "country": "BE"
                  },
                  "source": "pdf",
                  "color": false,
                  "duplex": false,
                  "qr_tracking": false,
                  "sheet_count": 2,
                  "price_cents": 129,
                  "price_breakdown": {
                    "unit_price_cents": 129,
                    "tier": 1,
                    "tier_fixed": false,
                    "volume_30d": 120,
                    "letters_to_next_tier": 379,
                    "pages": 1,
                    "sheets": 1,
                    "address_sheet": true,
                    "total_sheets": 2,
                    "color": false,
                    "duplex": false,
                    "speed": "standard",
                    "destination": "BE",
                    "options": [],
                    "total_cents": 129,
                    "on_request": false,
                    "currency": "EUR",
                    "vat_included": false
                  },
                  "speed": "standard",
                  "post_deadline": "2026-11-13",
                  "created_at": "2026-11-12T10:02:11Z",
                  "scheduled_at": "2026-11-12T10:02:11Z",
                  "printed_at": "2026-11-13T09:40:00Z",
                  "posted_at": "2026-11-13T16:02:11Z",
                  "returned_at": null,
                  "post_date": "2026-11-13",
                  "scan_count": 0,
                  "batch_photo_url": null
                },
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "annuleren",
        "summary": "Cancel a letter",
        "description": "Cancels a letter while the batch of that day is still open. The amount is credited back to your Envelopify balance, not paid out and the letter is returned with status `rejected`. Once the batch is closed you get `409 cutoff_passed`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/letters/{id}/pdf": {
      "get": {
        "operationId": "pdf",
        "summary": "Download the PDF",
        "description": "Returns the final PDF exactly as we print it (`application/pdf`), including the address sheet. Only for your own letters. A rejected letter has no PDF and returns `404`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "PDF",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/price": {
      "post": {
        "operationId": "prijs",
        "summary": "Price quote",
        "description": "Calculates the price of a letter with your current tier, without creating or charging anything. Same options as a letter, but with the number of printed pages instead of a PDF. `address_sheet` (default true) counts the free address sheet towards the sheet limit, like an uploaded PDF; set it to false for a template letter. Unavailable weight bands return `422 weight_class_unavailable`. Use `options.address_on_first_page` to exclude the extra address sheet.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "options": {
                    "type": "object",
                    "properties": {
                      "address_on_first_page": {
                        "type": "boolean",
                        "description": "For uploaded PDFs: fixed opaque white address window on page one, without an extra address sheet. Covered content is erased. Defaults to account preference (initially separate sheet)."
                      },
                      "flat": {
                        "type": "boolean",
                        "description": "Unfolded in a large envelope, packed by hand. Only when activated. Surcharge from /price."
                      },
                      "registered": {
                        "type": "boolean",
                        "description": "Registered paper mail, BE and standard/express only, when activated. Tracking: registered_tracking, registered_handed_in_at and temporary registered_receipt_url."
                      },
                      "country": {
                        "type": "string",
                        "description": "Destination country, default BE. Another EU country adds the EU surcharge."
                      },
                      "color": {
                        "type": "boolean",
                        "description": "Colour, surcharge per sheet via /price."
                      },
                      "duplex": {
                        "type": "boolean",
                        "description": "Double-sided."
                      },
                      "qr_tracking": {
                        "type": "boolean",
                        "description": "QR code with scan tracking."
                      },
                      "speed": {
                        "type": "string",
                        "description": "standard, express or flex."
                      }
                    }
                  },
                  "enclosure_batch_id": {
                    "type": "string",
                    "description": "Received material id from the portal. One copy per letter after activation and receipt inspection. Pre-production cancellation restores stock."
                  },
                  "pages": {
                    "type": "integer",
                    "description": "Number of printed pages (address sheet not included)."
                  },
                  "address_sheet": {
                    "type": "boolean",
                    "description": "Count the free address sheet towards the sheet limit. Default true."
                  }
                },
                "required": [
                  "pages"
                ]
              },
              "example": {
                "pages": 3,
                "address_sheet": true,
                "options": {
                  "color": false,
                  "duplex": false,
                  "qr_tracking": false,
                  "speed": "standard"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "price_breakdown": {
                    "unit_price_cents": 129,
                    "tier": 1,
                    "tier_fixed": false,
                    "volume_30d": 120,
                    "letters_to_next_tier": 379,
                    "pages": 3,
                    "sheets": 3,
                    "address_sheet": true,
                    "total_sheets": 4,
                    "color": false,
                    "duplex": false,
                    "speed": "standard",
                    "destination": "BE",
                    "options": [
                      {
                        "code": "extra_sheets",
                        "label": "Extra vellen (2)",
                        "amount_cents": 20
                      }
                    ],
                    "total_cents": 149,
                    "on_request": false,
                    "currency": "EUR",
                    "vat_included": false
                  }
                },
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/templates": {
      "get": {
        "operationId": "sjablonen",
        "summary": "List templates",
        "description": "Returns your templates in `data`.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "data": [
                    {
                      "id": "6c1f...",
                      "name": "Herinnering",
                      "code": "herinnering",
                      "body": "Beste {{voornaam}}, ...",
                      "created_at": "2026-11-12T10:00:00Z"
                    }
                  ]
                },
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "sjabloonMaken",
        "summary": "Create a template",
        "description": "Creates a template. Fields go between double curly braces, e.g. `{{voornaam}}`. An existing code returns `409 conflict`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Name."
                  },
                  "code": {
                    "type": "string",
                    "description": "Unique code: letters, digits, - and _."
                  },
                  "body": {
                    "type": "string",
                    "description": "Text with fields."
                  }
                },
                "required": [
                  "name",
                  "code",
                  "body"
                ]
              },
              "example": {
                "name": "Herinnering",
                "code": "herinnering",
                "body": "Beste {{voornaam}},\n\nWe ontvingen nog geen betaling van {{bedrag}}."
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/balance": {
      "get": {
        "operationId": "tegoed",
        "summary": "Balance and prices",
        "description": "Returns your credit, free trial letters left (`free_letters_remaining`), current tier, number of real letters in the past 30 days, letters to the next discount and the price list in cents, excl. VAT: price per letter per tier, option surcharges, speeds and the express rules.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "credit_cents": 4950,
                  "free_letters_remaining": 3,
                  "company_verified": true,
                  "tier": 1,
                  "tier_fixed": false,
                  "volume_30d": 120,
                  "letters_to_next_tier": 380,
                  "unit_price_cents": 129,
                  "tiers": [
                    {
                      "tier": 1,
                      "from": 0,
                      "unit_price_cents": 129
                    },
                    {
                      "tier": 2,
                      "from": 500,
                      "unit_price_cents": 109
                    },
                    {
                      "tier": 3,
                      "from": 5000,
                      "unit_price_cents": 99
                    }
                  ],
                  "options": {
                    "extra_sheet_cents": 10,
                    "qr_tracking_cents": 5
                  },
                  "speeds": [
                    {
                      "speed": "standard",
                      "name": "Standaard",
                      "amount_cents": 0,
                      "working_days": 3
                    },
                    {
                      "speed": "express",
                      "name": "Express",
                      "amount_cents": 15,
                      "working_days": 0
                    },
                    {
                      "speed": "flex",
                      "name": "Flex",
                      "amount_cents": -5,
                      "working_days": 5
                    }
                  ],
                  "express": {
                    "minimum_cents": 1500,
                    "max_letters_per_day": 1000,
                    "before_hour": 12
                  },
                  "heavy_letter": {
                    "from_grams": 50,
                    "amount_cents": 60
                  },
                  "eu": {
                    "surcharge_cents": 150,
                    "countries": [
                      "AT",
                      "DE",
                      "FR",
                      "NL",
                      "..."
                    ],
                    "delivery": "5 tot 10 werkdagen na verzending",
                    "express": false
                  },
                  "daily_letter_max": 1000,
                  "max_sheets": 8,
                  "currency": "EUR",
                  "vat_included": false
                },
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/account": {
      "get": {
        "operationId": "account",
        "summary": "Your account and company check",
        "description": "Returns your own account details. `company_verified` is `true` when your VAT number has been checked with the EU VAT check (VIES) or manually by us. `verification_status` is `verified`, `none`, `pending` (check will follow later) or `invalid`. For a verified company, `official_name` and `official_address` come from the official register. `GET /balance` also includes `company_verified`. You cannot change the status through the API: it only changes after a real check in the portal.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "customer_id": "4f6c2a1e-...",
                  "company": "Jouw Bedrijf",
                  "email": "post@jouwbedrijf.be",
                  "company_verified": true,
                  "verification_status": "verified",
                  "vat_number": "BE0123456749",
                  "official_name": "Jouw Bedrijf BV",
                  "official_address": "Kerkstraat 1\n2000 Antwerpen",
                  "checked_at": "2026-11-12T10:00:00Z",
                  "free_letters_remaining": 3
                },
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/webhooks": {
      "get": {
        "operationId": "webhooksLijst",
        "summary": "List webhooks",
        "description": "Lists your registered URLs and notification_groups. null means all existing events; an array limits notifications to selected groups.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "data": [
                    {
                      "id": "a3c2...",
                      "url": "https://jouwsite.be/hooks/envelopify",
                      "active": true,
                      "created_at": "2026-11-12T10:00:00Z"
                    }
                  ]
                },
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "webhookMaken",
        "summary": "Create a webhook",
        "description": "Registers an https URL. The response contains the `secret` once; use it to verify signatures.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "description": "https URL that receives our POST."
                  },
                  "notification_groups": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "problems",
                        "sent",
                        "undeliverable",
                        "delivered"
                      ]
                    },
                    "maxItems": 4,
                    "description": "Optional: problems (review/rejected), sent (posted/registered_handed_in), undeliverable (returned), delivered (coming soon, no events yet). Omit to preserve all existing events; [] receives nothing. Editable per URL in the portal."
                  }
                },
                "required": [
                  "url"
                ]
              },
              "example": {
                "url": "https://jouwsite.be/hooks/envelopify"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "id": "a3c2...",
                  "url": "https://jouwsite.be/hooks/envelopify",
                  "active": true,
                  "created_at": "2026-11-12T10:00:00Z",
                  "secret": "whsec_..."
                },
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/webhooks/{id}": {
      "delete": {
        "operationId": "webhookVerwijderen",
        "summary": "Delete a webhook",
        "description": "Deletes a webhook.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "id": "a3c2...",
                  "deleted": true
                },
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  }
}