{
  "openapi": "3.0.3",
  "info": {
    "title": "Ledenboek API",
    "version": "2.0.0",
    "description": "The Ledenboek API gives programmatic access to an association's members,\ngroups, events and invoices.\n\nEvery request is authenticated with an API key in the `Authorization`\nheader. Keys are created by an administrator under Settings → API keys and\ncarry their own set of permissions (scopes). Keys from a sample\nassociation start with `lb_test_` and never send email or payment\nrequests, so you can try everything safely.\n\nErrors use RFC 9457 (`application/problem+json`). Lists use cursor\npagination. POST requests accept an `Idempotency-Key`.\n\nThis file is the source of truth: the test suite checks every response of\nthe API against it.\n",
    "contact": {
      "name": "Ledenboek",
      "email": "support@ledenboek.nl",
      "url": "https://developers.ledenboek.nl"
    },
    "termsOfService": "https://developers.ledenboek.nl/terms"
  },
  "servers": [
    {
      "url": "https://api.ledenboek.nl/v2",
      "description": "Production (live and test keys)"
    }
  ],
  "security": [
    {
      "BearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Organization",
      "description": "The association that an API key belongs to."
    },
    {
      "name": "Members",
      "description": "The members of the association. Reading needs `leden:lezen`, writing\n`leden:schrijven`. Date of birth and bank details (`date_of_birth`,\n`sepa`, and the date of birth of participants) are only included for\nkeys with `leden:gevoelig`; without it these fields are left out of the\nresponse entirely, and sending them in a request returns\n`insufficient_scope`.\n"
    },
    {
      "name": "Groups",
      "description": "Groups, teams and classes, and who is in them. Needs `groepen:lezen` to\nread and `groepen:schrijven` to add or remove members. Only available\nwhen the association has enabled the groups module.\n"
    },
    {
      "name": "Committees",
      "description": "Committees and their current members. Read-only, with `groepen:lezen`. Only available when the committees module is enabled."
    },
    {
      "name": "Events",
      "description": "Events and their registrations. Needs `evenementen:lezen` to read and\n`evenementen:schrijven` to register someone. Only available when the\nevents module is enabled.\n"
    },
    {
      "name": "Invoices",
      "description": "Invoices to members, with their lines and payments. Needs\n`facturen:lezen` to read and `facturen:schrijven` to create and send.\nAmounts on an existing invoice can never be changed through the API.\nOnly available when the invoicing module is enabled.\n"
    },
    {
      "name": "Payments",
      "description": "Payments received on invoices. Read-only, with `facturen:lezen`."
    }
  ],
  "paths": {
    "/organization": {
      "get": {
        "tags": [
          "Organization"
        ],
        "operationId": "getOrganization",
        "summary": "Retrieve the organization",
        "description": "Returns the association this API key belongs to, the modules it has\nenabled and the scopes of the key. Works with any key, which makes it\nthe natural first request.\n",
        "responses": {
          "200": {
            "description": "The organization.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Organization"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/members": {
      "get": {
        "tags": [
          "Members"
        ],
        "operationId": "listMembers",
        "summary": "List members",
        "description": "Returns the members of the association, oldest first. Combine the\nfilters to narrow the list; use `updated_since` to sync changes\nincrementally.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/StartingAfter"
          },
          {
            "$ref": "#/components/parameters/UpdatedSince"
          },
          {
            "name": "status",
            "in": "query",
            "description": "Only members with this status.",
            "schema": {
              "$ref": "#/components/schemas/MemberStatus"
            }
          },
          {
            "name": "group_id",
            "in": "query",
            "description": "Only members who are currently in this group.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "email",
            "in": "query",
            "description": "Only the member with this email address (case-insensitive, exact match).",
            "schema": {
              "type": "string",
              "format": "email"
            }
          },
          {
            "name": "q",
            "in": "query",
            "description": "Search in first name, last name and initials.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of members.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MemberList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "tags": [
          "Members"
        ],
        "operationId": "createMember",
        "summary": "Create a member",
        "description": "Adds a member. The membership number is assigned automatically unless\nyou send one, and the country defaults to that of the association.\n\nNo email is sent to the new member, just like when a member is added\nin Ledenboek itself. If the association is connected to the\nWatersportverbond, the new member is reported there as usual.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MemberCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The new member.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Member"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/members/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "tags": [
          "Members"
        ],
        "operationId": "getMember",
        "summary": "Retrieve a member",
        "responses": {
          "200": {
            "description": "The member.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Member"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "patch": {
        "tags": [
          "Members"
        ],
        "operationId": "updateMember",
        "summary": "Update a member",
        "description": "Updates only the fields you send. Send `null` to clear a field. To end\na membership, use `POST /members/{id}/cancel`\ninstead of changing the status.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MemberUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated member.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Member"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/members/{id}/cancel": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "tags": [
          "Members"
        ],
        "operationId": "cancelMember",
        "summary": "Cancel a membership",
        "description": "Cancels the membership, the same way as the Cancel button in\nLedenboek. Without `end_date` the end date follows from the notice\nperiod the association has set. With an end date in the past the\nstatus becomes `ended`, otherwise `cancelled`; the member's running\nsubscriptions stop on the end date.\n\nA confirmation email is only sent with `send_confirmation: true`, and\nnever with a test key. A membership that has already ended cannot be\ncancelled again (`membership_already_ended`).\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MemberCancel"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The member, with the new status and end date.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Member"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/groups": {
      "get": {
        "tags": [
          "Groups"
        ],
        "operationId": "listGroups",
        "summary": "List groups",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/StartingAfter"
          },
          {
            "$ref": "#/components/parameters/UpdatedSince"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of groups.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GroupList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/groups/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "tags": [
          "Groups"
        ],
        "operationId": "getGroup",
        "summary": "Retrieve a group",
        "responses": {
          "200": {
            "description": "The group.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Group"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/groups/{id}/members": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "tags": [
          "Groups"
        ],
        "operationId": "listGroupMembers",
        "summary": "List the members of a group",
        "description": "Everyone who is in the group today, plus those who are already signed\nup to join it from a later date. Past members and members whose\nmembership has ended are not included.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/StartingAfter"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of group memberships.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MembershipList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "tags": [
          "Groups"
        ],
        "operationId": "addGroupMember",
        "summary": "Add a member to a group",
        "description": "Adds a member to the group from `valid_from` (today if left out).\nA member who is already in the group, now or from a later date,\nreturns `already_in_group`.\n\nA class that is linked to a product follows who has that subscription\nand cannot be changed here (`group_follows_subscription`); change the\nmember's subscription in Ledenboek instead.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GroupMemberCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The new group membership.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Membership"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/groups/{id}/members/{member_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        },
        {
          "name": "member_id",
          "in": "path",
          "required": true,
          "description": "The id of the member (not of the group membership).",
          "schema": {
            "type": "integer"
          }
        }
      ],
      "delete": {
        "tags": [
          "Groups"
        ],
        "operationId": "removeGroupMember",
        "summary": "Remove a member from a group",
        "description": "Ends the member's place in the group as of yesterday, so the history\nof who was in the group when is kept. A place that had not started\nyet is removed entirely.\n",
        "responses": {
          "204": {
            "description": "The member is no longer in the group."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/committees": {
      "get": {
        "tags": [
          "Committees"
        ],
        "operationId": "listCommittees",
        "summary": "List committees",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/StartingAfter"
          },
          {
            "$ref": "#/components/parameters/UpdatedSince"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of committees, each with its current members.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CommitteeList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/committees/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "tags": [
          "Committees"
        ],
        "operationId": "getCommittee",
        "summary": "Retrieve a committee",
        "responses": {
          "200": {
            "description": "The committee with its current members.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Committee"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/events": {
      "get": {
        "tags": [
          "Events"
        ],
        "operationId": "listEvents",
        "summary": "List events",
        "description": "Returns events in the order they were created. Filter on start time with `from` and `to`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/StartingAfter"
          },
          {
            "$ref": "#/components/parameters/UpdatedSince"
          },
          {
            "name": "from",
            "in": "query",
            "description": "Only events starting at or after this date (`2026-10-01`) or ISO 8601 timestamp.",
            "schema": {
              "type": "string",
              "example": "2026-10-01"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "Only events starting at or before this date (inclusive, until the end of the day) or ISO 8601 timestamp.",
            "schema": {
              "type": "string",
              "example": "2026-12-31"
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "Only events with this status.",
            "schema": {
              "$ref": "#/components/schemas/EventStatus"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of events.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EventList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/events/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "tags": [
          "Events"
        ],
        "operationId": "getEvent",
        "summary": "Retrieve an event",
        "responses": {
          "200": {
            "description": "The event.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Event"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/events/{id}/registrations": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "tags": [
          "Events"
        ],
        "operationId": "listEventRegistrations",
        "summary": "List the registrations of an event",
        "description": "All registrations, including cancelled ones. Check `status` to see who is still coming.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/StartingAfter"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of registrations.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EventRegistrationList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "tags": [
          "Events"
        ],
        "operationId": "createEventRegistration",
        "summary": "Register for an event",
        "description": "Registers a member (`member_id`) or someone from outside the\nassociation (`name`, and preferably `email`). The same rules apply as\non the public registration page: the event must be published, the\ndeadline must not have passed, and there must be enough spots for\n`number_of_people`. A member can be registered only once.\n\nNo confirmation email is sent; the registration is recorded on behalf\nof the association, as when the board adds someone in Ledenboek.\n\nEvents that require payment cannot be booked through the API, because\nthe payment cannot be made here. These return\n`event_requires_payment`; send the participant to the event's `url`\ninstead. Registration does work for paid events where payment is\noptional, where the board assigns the places (payment follows the\nassignment), and for members when the event is free for members.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EventRegistrationCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The new registration.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EventRegistration"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/invoices": {
      "get": {
        "tags": [
          "Invoices"
        ],
        "operationId": "listInvoices",
        "summary": "List invoices",
        "description": "Returns invoices without their lines and payments; retrieve a single\ninvoice for those. Invoices to business relations (sponsors,\nadvertisers) are included with `member_id` null.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/StartingAfter"
          },
          {
            "$ref": "#/components/parameters/UpdatedSince"
          },
          {
            "name": "status",
            "in": "query",
            "description": "Only invoices with this status.",
            "schema": {
              "$ref": "#/components/schemas/InvoiceStatus"
            }
          },
          {
            "name": "member_id",
            "in": "query",
            "description": "Only invoices to this member.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of invoices.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvoiceList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "tags": [
          "Invoices"
        ],
        "operationId": "createInvoice",
        "summary": "Create an invoice",
        "description": "Creates an invoice to a member as a draft. The invoice number, totals\nand VAT are calculated by Ledenboek, exactly as for an invoice made in\nthe app. With `send: true` the invoice is sent straight away, as with\n`POST /invoices/{id}/send`.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InvoiceCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The new invoice.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/invoices/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "tags": [
          "Invoices"
        ],
        "operationId": "getInvoice",
        "summary": "Retrieve an invoice",
        "description": "The invoice with its lines and payments.",
        "responses": {
          "200": {
            "description": "The invoice.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/invoices/{id}/send": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "tags": [
          "Invoices"
        ],
        "operationId": "sendInvoice",
        "summary": "Send an invoice",
        "description": "Emails the invoice to the member, with the PDF and a payment link, from\nthe association's default sender. A draft becomes `sent`; an\noutstanding invoice is sent again. Paid, cancelled and credited\ninvoices cannot be sent (`invoice_not_sendable`), nor can an invoice\nto someone without an email address (`recipient_without_email`).\n\nWith a test key the invoice is marked as sent, but no email goes out.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "The invoice after sending.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/payments": {
      "get": {
        "tags": [
          "Payments"
        ],
        "operationId": "listPayments",
        "summary": "List payments",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/StartingAfter"
          },
          {
            "$ref": "#/components/parameters/UpdatedSince"
          },
          {
            "name": "invoice_id",
            "in": "query",
            "description": "Only payments on this invoice.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of payments.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/payments/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "tags": [
          "Payments"
        ],
        "operationId": "getPayment",
        "summary": "Retrieve a payment",
        "responses": {
          "200": {
            "description": "The payment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payment"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "lb_live_… or lb_test_…",
        "description": "Send the key as `Authorization: Bearer lb_live_...`. A key in the query\nstring is rejected with `api_key_in_url`.\n"
      }
    },
    "headers": {
      "RateLimit-Limit": {
        "description": "Requests allowed per minute for this key (600 for live keys, 120 for test keys).",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimit-Remaining": {
        "description": "Requests left in the current window.",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimit-Reset": {
        "description": "Seconds until the window resets.",
        "schema": {
          "type": "integer"
        }
      },
      "Retry-After": {
        "description": "Seconds to wait before retrying.",
        "schema": {
          "type": "integer"
        }
      }
    },
    "parameters": {
      "Id": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "The id of the record.",
        "schema": {
          "type": "integer",
          "example": 1042
        }
      },
      "Limit": {
        "name": "limit",
        "in": "query",
        "description": "Number of records to return, 1 to 100.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "default": 25
        }
      },
      "StartingAfter": {
        "name": "starting_after",
        "in": "query",
        "description": "Cursor for the next page; pass the `next_cursor` of the previous response.",
        "schema": {
          "type": "string"
        }
      },
      "UpdatedSince": {
        "name": "updated_since",
        "in": "query",
        "description": "Only records changed at or after this ISO 8601 timestamp. Use it to sync incrementally.",
        "schema": {
          "type": "string",
          "format": "date-time"
        }
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "description": "A unique value (for example a UUID) for this request. Sending the same\nrequest again with the same key returns the original response instead\nof creating a duplicate. Keys are kept for 24 hours.\n",
        "schema": {
          "type": "string",
          "maxLength": 255
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "The request is malformed, for example an invalid parameter.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "The API key is missing, invalid, revoked or expired.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "PaymentRequired": {
        "description": "The association has no valid subscription.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "Forbidden": {
        "description": "The API key lacks the required scope.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "NotFound": {
        "description": "The record does not exist, does not belong to this association, or the module is disabled.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "Conflict": {
        "description": "The request conflicts with the current state, for example an event\nthat is full or a member who is already in the group. The problem\n`type` says which. Also returned while a request with the same\nIdempotency-Key is still in progress.\n",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "ValidationFailed": {
        "description": "One or more fields are invalid, or an Idempotency-Key was reused for a different request.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Too many requests. Wait for the number of seconds in `Retry-After`.",
        "headers": {
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      }
    },
    "schemas": {
      "Problem": {
        "type": "object",
        "description": "An error, as described in RFC 9457. `type` is stable; `title` and `detail` follow `Accept-Language` (nl, en, de, fr).",
        "required": [
          "type",
          "title",
          "status"
        ],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri",
            "example": "https://developers.ledenboek.nl/errors/insufficient_scope"
          },
          "title": {
            "type": "string",
            "example": "Insufficient permissions"
          },
          "status": {
            "type": "integer",
            "example": 403
          },
          "detail": {
            "type": "string",
            "example": "This key lacks the permission leden:schrijven."
          },
          "instance": {
            "type": "string",
            "example": "/v2/members"
          },
          "errors": {
            "type": "array",
            "description": "Field errors, only for `validation_failed`.",
            "items": {
              "type": "object",
              "required": [
                "field",
                "code",
                "message"
              ],
              "properties": {
                "field": {
                  "type": "string",
                  "example": "email"
                },
                "code": {
                  "type": "string",
                  "example": "taken"
                },
                "message": {
                  "type": "string",
                  "example": "Email has already been taken"
                }
              }
            }
          }
        }
      },
      "ListMeta": {
        "type": "object",
        "required": [
          "object",
          "has_more",
          "next_cursor"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "list"
            ]
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": "string",
            "nullable": true,
            "description": "Pass as `starting_after` to fetch the next page. Null on the last page."
          }
        }
      },
      "Organization": {
        "type": "object",
        "required": [
          "object",
          "id",
          "name",
          "slug",
          "country",
          "language",
          "website_url",
          "modules",
          "api_key"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "organization"
            ]
          },
          "id": {
            "type": "integer",
            "example": 42
          },
          "name": {
            "type": "string",
            "example": "WSV De Kaag"
          },
          "slug": {
            "type": "string",
            "nullable": true,
            "example": "wsv-de-kaag"
          },
          "country": {
            "type": "string",
            "nullable": true,
            "example": "Nederland"
          },
          "language": {
            "type": "string",
            "nullable": true,
            "example": "nl"
          },
          "website_url": {
            "type": "string",
            "nullable": true,
            "example": "https://www.wsvdekaag.nl"
          },
          "modules": {
            "type": "object",
            "description": "Which modules the association has enabled. Endpoints of a disabled module return `module_disabled`.",
            "required": [
              "groups",
              "committees",
              "participants",
              "events",
              "invoicing"
            ],
            "properties": {
              "groups": {
                "type": "boolean"
              },
              "committees": {
                "type": "boolean"
              },
              "participants": {
                "type": "boolean"
              },
              "events": {
                "type": "boolean"
              },
              "invoicing": {
                "type": "boolean"
              }
            }
          },
          "api_key": {
            "type": "object",
            "required": [
              "name",
              "prefix",
              "environment",
              "scopes"
            ],
            "properties": {
              "name": {
                "type": "string",
                "example": "Website integration"
              },
              "prefix": {
                "type": "string",
                "example": "lb_live_8kQz"
              },
              "environment": {
                "type": "string",
                "enum": [
                  "live",
                  "test"
                ]
              },
              "scopes": {
                "type": "array",
                "items": {
                  "type": "string",
                  "enum": [
                    "leden:lezen",
                    "leden:schrijven",
                    "leden:gevoelig",
                    "groepen:lezen",
                    "groepen:schrijven",
                    "evenementen:lezen",
                    "evenementen:schrijven",
                    "facturen:lezen",
                    "facturen:schrijven"
                  ]
                }
              }
            }
          }
        }
      },
      "Money": {
        "type": "string",
        "pattern": "^-?\\d+\\.\\d{2}$",
        "description": "An amount in euros as a string with two decimals, so it never suffers from floating-point rounding.",
        "example": "25.00"
      },
      "MemberStatus": {
        "type": "string",
        "description": "- `active`: a current member.\n- `prospective`: signed up or on trial, not yet a full member.\n- `cancelled`: has cancelled; remains a member until `membership_end_date`.\n- `inactive`: temporarily inactive.\n- `ended`: the membership has ended.\n- `deceased`: the member has passed away.\n",
        "enum": [
          "active",
          "prospective",
          "cancelled",
          "inactive",
          "ended",
          "deceased"
        ]
      },
      "Address": {
        "type": "object",
        "required": [
          "street",
          "house_number",
          "postal_code",
          "city",
          "country"
        ],
        "properties": {
          "street": {
            "type": "string",
            "nullable": true,
            "example": "Herengracht"
          },
          "house_number": {
            "type": "string",
            "nullable": true,
            "example": "12a"
          },
          "postal_code": {
            "type": "string",
            "nullable": true,
            "example": "1015 BN"
          },
          "city": {
            "type": "string",
            "nullable": true,
            "example": "Amsterdam"
          },
          "country": {
            "type": "string",
            "nullable": true,
            "description": "The country as entered by the association",
            "usually its name.": null,
            "example": "Nederland"
          }
        }
      },
      "Participant": {
        "type": "object",
        "description": "Someone who takes part through this member's account, such as a child or a team. Only present when the association uses participants.",
        "required": [
          "id",
          "name",
          "active"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "example": 311
          },
          "name": {
            "type": "string",
            "example": "Sanne de Vries"
          },
          "active": {
            "type": "boolean"
          },
          "date_of_birth": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "description": "Only with the `leden:gevoelig` scope.",
            "example": "2015-04-02"
          }
        }
      },
      "Relationship": {
        "type": "object",
        "required": [
          "member_id",
          "type",
          "primary_contact"
        ],
        "properties": {
          "member_id": {
            "type": "integer",
            "description": "The related member.",
            "example": 1043
          },
          "type": {
            "type": "string",
            "description": "How this member relates to the other one, for example `parent_of` means this member is the parent of `member_id`.",
            "enum": [
              "parent_of",
              "child_of",
              "spouse_of",
              "partner_of",
              "sibling_of",
              "household_member_of"
            ]
          },
          "primary_contact": {
            "type": "boolean",
            "description": "Whether the related member is the primary contact."
          }
        }
      },
      "Sepa": {
        "type": "object",
        "description": "Bank account and SEPA direct debit mandate. Only with the `leden:gevoelig` scope.",
        "required": [
          "iban",
          "account_holder",
          "mandate_reference",
          "mandate_date"
        ],
        "properties": {
          "iban": {
            "type": "string",
            "nullable": true,
            "example": "NL91ABNA0417164300"
          },
          "account_holder": {
            "type": "string",
            "nullable": true,
            "example": "J. de Vries"
          },
          "mandate_reference": {
            "type": "string",
            "nullable": true,
            "example": "LB-42-000318"
          },
          "mandate_date": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "example": "2025-01-15"
          }
        }
      },
      "Member": {
        "type": "object",
        "required": [
          "object",
          "id",
          "membership_number",
          "first_name",
          "last_name",
          "initials",
          "email",
          "phone",
          "gender",
          "address",
          "status",
          "member_since",
          "membership_end_date",
          "language",
          "custom_fields",
          "relationships",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "member"
            ]
          },
          "id": {
            "type": "integer",
            "example": 1042
          },
          "membership_number": {
            "type": "string",
            "nullable": true,
            "example": "0318"
          },
          "first_name": {
            "type": "string",
            "nullable": true,
            "example": "Jan"
          },
          "last_name": {
            "type": "string",
            "example": "de Vries"
          },
          "initials": {
            "type": "string",
            "nullable": true,
            "example": "J.P."
          },
          "email": {
            "type": "string",
            "nullable": true,
            "example": "jan@example.com"
          },
          "phone": {
            "type": "string",
            "nullable": true,
            "example": "06 12345678"
          },
          "gender": {
            "type": "string",
            "nullable": true,
            "description": "The salutation as stored by the association, such as `dhr.` or `mv.`.",
            "example": "dhr."
          },
          "address": {
            "$ref": "#/components/schemas/Address"
          },
          "status": {
            "$ref": "#/components/schemas/MemberStatus"
          },
          "member_since": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "example": "2019-03-01"
          },
          "membership_end_date": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "description": "The date the membership ends or ended.",
            "example": null
          },
          "language": {
            "type": "string",
            "nullable": true,
            "enum": [
              "nl",
              "en",
              "de",
              "fr"
            ],
            "description": "The language the member receives email in. Null means the association's language.",
            "example": "nl"
          },
          "custom_fields": {
            "type": "object",
            "description": "The association's own member fields, by field name. Every active\nfield is present; a field without a value is null. Checkboxes are\nbooleans, numbers are integers, dates are `YYYY-MM-DD` and\neverything else is a string.\n",
            "additionalProperties": true,
            "example": {
              "rijbewijs": true,
              "boot": "Waterlelie"
            }
          },
          "participants": {
            "type": "array",
            "description": "Only present when the association uses participants.",
            "items": {
              "$ref": "#/components/schemas/Participant"
            }
          },
          "relationships": {
            "type": "array",
            "description": "Family relationships with other members.",
            "items": {
              "$ref": "#/components/schemas/Relationship"
            }
          },
          "date_of_birth": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "description": "Only with the `leden:gevoelig` scope.",
            "example": "1984-07-21"
          },
          "sepa": {
            "$ref": "#/components/schemas/Sepa"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "MemberList": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ListMeta"
          },
          {
            "type": "object",
            "required": [
              "data"
            ],
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Member"
                }
              }
            }
          }
        ]
      },
      "AddressInput": {
        "type": "object",
        "properties": {
          "street": {
            "type": "string",
            "nullable": true
          },
          "house_number": {
            "type": "string",
            "nullable": true
          },
          "postal_code": {
            "type": "string",
            "nullable": true,
            "description": "Validated as a Dutch postal code when the country is the Netherlands or empty."
          },
          "city": {
            "type": "string",
            "nullable": true
          },
          "country": {
            "type": "string",
            "nullable": true,
            "description": "Defaults to the association's country when creating a member."
          }
        }
      },
      "SepaInput": {
        "type": "object",
        "description": "Requires the `leden:gevoelig` scope. A mandate date is required with an IBAN; a mandate reference is generated when left out.",
        "properties": {
          "iban": {
            "type": "string",
            "nullable": true,
            "description": "Spaces are removed and letters capitalised.",
            "example": "NL91 ABNA 0417 1643 00"
          },
          "account_holder": {
            "type": "string",
            "nullable": true
          },
          "mandate_reference": {
            "type": "string",
            "nullable": true
          },
          "mandate_date": {
            "type": "string",
            "format": "date",
            "nullable": true
          }
        }
      },
      "MemberUpdate": {
        "type": "object",
        "description": "Every field is optional. Fields you leave out are not changed; `null` clears a field.",
        "properties": {
          "first_name": {
            "type": "string",
            "nullable": true,
            "description": "Required unless initials are given."
          },
          "last_name": {
            "type": "string"
          },
          "initials": {
            "type": "string",
            "nullable": true
          },
          "email": {
            "type": "string",
            "nullable": true,
            "description": "Must be unique within the association."
          },
          "phone": {
            "type": "string",
            "nullable": true,
            "description": "At least ten digits."
          },
          "gender": {
            "type": "string",
            "nullable": true
          },
          "membership_number": {
            "type": "string",
            "nullable": true,
            "description": "Must be unique within the association."
          },
          "status": {
            "type": "string",
            "description": "Use the cancel endpoint to end a membership.",
            "enum": [
              "active",
              "prospective",
              "inactive"
            ]
          },
          "member_since": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "language": {
            "type": "string",
            "nullable": true,
            "enum": [
              "nl",
              "en",
              "de",
              "fr"
            ]
          },
          "address": {
            "$ref": "#/components/schemas/AddressInput"
          },
          "custom_fields": {
            "type": "object",
            "description": "Values for the association's own member fields, by field name. An unknown field name is an error (`unknown_field`).",
            "additionalProperties": true
          },
          "date_of_birth": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "description": "Requires the `leden:gevoelig` scope."
          },
          "sepa": {
            "$ref": "#/components/schemas/SepaInput"
          }
        }
      },
      "MemberCreate": {
        "allOf": [
          {
            "$ref": "#/components/schemas/MemberUpdate"
          },
          {
            "type": "object",
            "required": [
              "last_name"
            ]
          }
        ],
        "example": {
          "first_name": "Jan",
          "last_name": "de Vries",
          "email": "jan@example.com",
          "address": {
            "street": "Herengracht",
            "house_number": "12a",
            "postal_code": "1015 BN",
            "city": "Amsterdam"
          },
          "member_since": "2026-10-01"
        }
      },
      "MemberCancel": {
        "type": "object",
        "properties": {
          "end_date": {
            "type": "string",
            "format": "date",
            "description": "The last day of the membership. Defaults to the end of the association's notice period.",
            "example": "2026-12-31"
          },
          "reason": {
            "type": "string",
            "description": "Stored with the cancellation.",
            "example": "Moved abroad"
          },
          "send_confirmation": {
            "type": "boolean",
            "default": false,
            "description": "Email the member a confirmation of the cancellation. Never sent with a test key."
          }
        }
      },
      "Group": {
        "type": "object",
        "required": [
          "object",
          "id",
          "name",
          "description",
          "color",
          "location",
          "is_department",
          "department_id",
          "day_of_week",
          "start_time",
          "end_time",
          "min_age",
          "max_age",
          "max_members",
          "member_count",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "group"
            ]
          },
          "id": {
            "type": "integer",
            "example": 77
          },
          "name": {
            "type": "string",
            "example": "Jeugd 8-12"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "color": {
            "type": "string",
            "nullable": true,
            "example": "#3B82F6"
          },
          "location": {
            "type": "string",
            "nullable": true,
            "example": "Sporthal De Brug"
          },
          "is_department": {
            "type": "boolean",
            "description": "Whether this is a department of a multi-sport club",
            "with other groups under it.": null
          },
          "department_id": {
            "type": "integer",
            "nullable": true,
            "description": "The department this group belongs to."
          },
          "day_of_week": {
            "type": "integer",
            "nullable": true,
            "minimum": 1,
            "maximum": 7,
            "description": "The weekly meeting day: 1 is Monday, 7 is Sunday.",
            "example": 3
          },
          "start_time": {
            "type": "string",
            "nullable": true,
            "example": "18:30"
          },
          "end_time": {
            "type": "string",
            "nullable": true,
            "example": "19:30"
          },
          "min_age": {
            "type": "integer",
            "nullable": true,
            "example": 8
          },
          "max_age": {
            "type": "integer",
            "nullable": true,
            "example": 12
          },
          "max_members": {
            "type": "integer",
            "nullable": true,
            "example": 16
          },
          "member_count": {
            "type": "integer",
            "description": "The number of people in the group today.",
            "example": 14
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "GroupList": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ListMeta"
          },
          {
            "type": "object",
            "required": [
              "data"
            ],
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Group"
                }
              }
            }
          }
        ]
      },
      "Membership": {
        "type": "object",
        "description": "A place in a group or committee. Either a member (`member_id` set) or\nsomeone from outside the association (`external: true`). For members\nonly the name is included here; their contact details are on the\nmember and need `leden:lezen`.\n",
        "required": [
          "object",
          "id",
          "member_id",
          "name",
          "external",
          "email",
          "phone",
          "role",
          "valid_from",
          "valid_until",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "group_membership",
              "committee_membership"
            ]
          },
          "id": {
            "type": "integer",
            "example": 5120
          },
          "member_id": {
            "type": "integer",
            "nullable": true,
            "example": 1042
          },
          "name": {
            "type": "string",
            "nullable": true,
            "example": "Jan de Vries"
          },
          "external": {
            "type": "boolean"
          },
          "email": {
            "type": "string",
            "nullable": true,
            "description": "Only for someone from outside the association."
          },
          "phone": {
            "type": "string",
            "nullable": true,
            "description": "Only for someone from outside the association."
          },
          "role": {
            "type": "string",
            "nullable": true,
            "example": "Trainer"
          },
          "valid_from": {
            "type": "string",
            "format": "date",
            "example": "2026-09-01"
          },
          "valid_until": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "MembershipList": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ListMeta"
          },
          {
            "type": "object",
            "required": [
              "data"
            ],
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Membership"
                }
              }
            }
          }
        ]
      },
      "GroupMemberCreate": {
        "type": "object",
        "required": [
          "member_id"
        ],
        "properties": {
          "member_id": {
            "type": "integer",
            "example": 1042
          },
          "role": {
            "type": "string",
            "description": "A role within the group",
            "such as captain or trainer.": null,
            "example": "Aanvoerder"
          },
          "valid_from": {
            "type": "string",
            "format": "date",
            "description": "Defaults to today.",
            "example": "2026-11-01"
          },
          "valid_until": {
            "type": "string",
            "format": "date"
          }
        }
      },
      "Committee": {
        "type": "object",
        "required": [
          "object",
          "id",
          "name",
          "description",
          "members",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "committee"
            ]
          },
          "id": {
            "type": "integer",
            "example": 12
          },
          "name": {
            "type": "string",
            "example": "Kascommissie"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "members": {
            "type": "array",
            "description": "The current members of the committee.",
            "items": {
              "$ref": "#/components/schemas/Membership"
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CommitteeList": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ListMeta"
          },
          {
            "type": "object",
            "required": [
              "data"
            ],
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Committee"
                }
              }
            }
          }
        ]
      },
      "EventStatus": {
        "type": "string",
        "description": "`draft` events are not visible to members yet; `completed` events are in the past and closed.",
        "enum": [
          "draft",
          "published",
          "cancelled",
          "completed"
        ]
      },
      "Event": {
        "type": "object",
        "required": [
          "object",
          "id",
          "title",
          "description",
          "location",
          "location_url",
          "start_time",
          "end_time",
          "status",
          "visibility",
          "url",
          "registration",
          "payment",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "event"
            ]
          },
          "id": {
            "type": "integer",
            "example": 230
          },
          "title": {
            "type": "string",
            "example": "Najaarswedstrijd"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "location": {
            "type": "string",
            "nullable": true,
            "example": "Clubhuis"
          },
          "location_url": {
            "type": "string",
            "nullable": true
          },
          "start_time": {
            "type": "string",
            "format": "date-time"
          },
          "end_time": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "status": {
            "$ref": "#/components/schemas/EventStatus"
          },
          "visibility": {
            "type": "string",
            "description": "Who can see the event in Ledenboek and on the association's website.",
            "enum": [
              "public",
              "members_only",
              "private"
            ]
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The public page of the event",
            "where people can register and pay.": null
          },
          "registration": {
            "type": "object",
            "required": [
              "required",
              "open",
              "deadline",
              "max_participants",
              "spots_available"
            ],
            "properties": {
              "required": {
                "type": "boolean",
                "description": "Whether people need to register."
              },
              "open": {
                "type": "boolean",
                "description": "Whether registration is possible right now: published, before the deadline and not full."
              },
              "deadline": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "max_participants": {
                "type": "integer",
                "nullable": true,
                "example": 40
              },
              "spots_available": {
                "type": "integer",
                "nullable": true,
                "description": "Null when there is no maximum.",
                "example": 12
              }
            }
          },
          "payment": {
            "type": "object",
            "required": [
              "required",
              "optional",
              "free_for_members",
              "amount",
              "currency"
            ],
            "properties": {
              "required": {
                "type": "boolean"
              },
              "optional": {
                "type": "boolean",
                "description": "Whether paying is voluntary",
                "such as a donation.": null
              },
              "free_for_members": {
                "type": "boolean"
              },
              "amount": {
                "type": "string",
                "nullable": true,
                "pattern": "^-?\\d+\\.\\d{2}$",
                "description": "The fixed price as a string with two decimals. Null when the event is free or offers a choice of prices.",
                "example": "15.00"
              },
              "currency": {
                "type": "string",
                "enum": [
                  "EUR"
                ]
              }
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "EventList": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ListMeta"
          },
          {
            "type": "object",
            "required": [
              "data"
            ],
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Event"
                }
              }
            }
          }
        ]
      },
      "EventRegistration": {
        "type": "object",
        "required": [
          "object",
          "id",
          "event_id",
          "member_id",
          "participant_id",
          "name",
          "external",
          "email",
          "phone",
          "number_of_people",
          "notes",
          "status",
          "payment_status",
          "amount_paid",
          "currency",
          "registered_at",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "event_registration"
            ]
          },
          "id": {
            "type": "integer",
            "example": 9001
          },
          "event_id": {
            "type": "integer",
            "example": 230
          },
          "member_id": {
            "type": "integer",
            "nullable": true,
            "example": 1042
          },
          "participant_id": {
            "type": "integer",
            "nullable": true,
            "description": "The participant taking part",
            "when registered through a member's account.": null
          },
          "name": {
            "type": "string",
            "nullable": true,
            "example": "Jan de Vries"
          },
          "external": {
            "type": "boolean",
            "description": "Whether this is someone from outside the association."
          },
          "email": {
            "type": "string",
            "nullable": true,
            "description": "Only for someone from outside the association."
          },
          "phone": {
            "type": "string",
            "nullable": true,
            "description": "Only for someone from outside the association."
          },
          "number_of_people": {
            "type": "integer",
            "example": 2
          },
          "notes": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "registered",
              "confirmed",
              "cancelled",
              "attended"
            ]
          },
          "payment_status": {
            "type": "string",
            "enum": [
              "not_required",
              "pending",
              "paid",
              "failed"
            ]
          },
          "amount_paid": {
            "type": "string",
            "nullable": true,
            "pattern": "^-?\\d+\\.\\d{2}$",
            "description": "The amount paid for this registration, if recorded.",
            "example": "30.00"
          },
          "currency": {
            "type": "string",
            "enum": [
              "EUR"
            ]
          },
          "registered_at": {
            "type": "string",
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "EventRegistrationList": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ListMeta"
          },
          {
            "type": "object",
            "required": [
              "data"
            ],
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/EventRegistration"
                }
              }
            }
          }
        ]
      },
      "EventRegistrationCreate": {
        "type": "object",
        "description": "Send either `member_id`, or `name` (and preferably `email`) for someone from outside the association.",
        "properties": {
          "member_id": {
            "type": "integer",
            "example": 1042
          },
          "name": {
            "type": "string",
            "description": "Required without member_id.",
            "example": "Petra Jansen"
          },
          "email": {
            "type": "string",
            "format": "email",
            "example": "petra@example.com"
          },
          "phone": {
            "type": "string"
          },
          "number_of_people": {
            "type": "integer",
            "minimum": 1,
            "default": 1,
            "example": 2
          },
          "notes": {
            "type": "string",
            "example": "Vegetarian"
          }
        }
      },
      "InvoiceStatus": {
        "type": "string",
        "description": "- `draft`: created, not sent yet.\n- `sent`: sent and awaiting payment.\n- `partial`: partly paid.\n- `overdue`: past the due date and not fully paid.\n- `paid`: fully paid.\n- `cancelled`: cancelled.\n- `credited`: cancelled with a credit note.\n",
        "enum": [
          "draft",
          "sent",
          "paid",
          "partial",
          "overdue",
          "cancelled",
          "credited"
        ]
      },
      "InvoiceLine": {
        "type": "object",
        "required": [
          "description",
          "quantity",
          "unit_price",
          "vat_percentage",
          "vat_amount",
          "total",
          "period_start",
          "period_end"
        ],
        "properties": {
          "description": {
            "type": "string",
            "example": "Contributie 2027"
          },
          "quantity": {
            "type": "integer",
            "example": 1
          },
          "unit_price": {
            "$ref": "#/components/schemas/Money"
          },
          "vat_percentage": {
            "type": "integer",
            "example": 0
          },
          "vat_amount": {
            "$ref": "#/components/schemas/Money"
          },
          "total": {
            "$ref": "#/components/schemas/Money"
          },
          "period_start": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "period_end": {
            "type": "string",
            "format": "date",
            "nullable": true
          }
        }
      },
      "Invoice": {
        "type": "object",
        "required": [
          "object",
          "id",
          "number",
          "status",
          "member_id",
          "recipient_name",
          "invoice_date",
          "due_date",
          "sent_at",
          "paid_at",
          "currency",
          "subtotal",
          "vat_amount",
          "total",
          "amount_paid",
          "amount_due",
          "payment_reference",
          "payment_url",
          "notes",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "invoice"
            ]
          },
          "id": {
            "type": "integer",
            "example": 5531
          },
          "number": {
            "type": "string",
            "example": "2026-0142"
          },
          "status": {
            "$ref": "#/components/schemas/InvoiceStatus"
          },
          "member_id": {
            "type": "integer",
            "nullable": true,
            "description": "Null for an invoice to a business relation.",
            "example": 1042
          },
          "recipient_name": {
            "type": "string",
            "nullable": true,
            "example": "Jan de Vries"
          },
          "invoice_date": {
            "type": "string",
            "format": "date",
            "example": "2026-10-16"
          },
          "due_date": {
            "type": "string",
            "format": "date",
            "example": "2026-11-15"
          },
          "sent_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "paid_at": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "currency": {
            "type": "string",
            "enum": [
              "EUR"
            ]
          },
          "subtotal": {
            "$ref": "#/components/schemas/Money"
          },
          "vat_amount": {
            "$ref": "#/components/schemas/Money"
          },
          "total": {
            "$ref": "#/components/schemas/Money"
          },
          "amount_paid": {
            "$ref": "#/components/schemas/Money"
          },
          "amount_due": {
            "$ref": "#/components/schemas/Money"
          },
          "payment_reference": {
            "type": "string",
            "nullable": true,
            "description": "The reference to quote with a bank transfer."
          },
          "payment_url": {
            "type": "string",
            "format": "uri",
            "nullable": true,
            "description": "The page where the member can pay. Null when nothing is due."
          },
          "notes": {
            "type": "string",
            "nullable": true,
            "description": "Shown on the invoice."
          },
          "lines": {
            "type": "array",
            "description": "Only when retrieving a single invoice.",
            "items": {
              "$ref": "#/components/schemas/InvoiceLine"
            }
          },
          "payments": {
            "type": "array",
            "description": "Only when retrieving a single invoice.",
            "items": {
              "$ref": "#/components/schemas/Payment"
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "InvoiceList": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ListMeta"
          },
          {
            "type": "object",
            "required": [
              "data"
            ],
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Invoice"
                }
              }
            }
          }
        ]
      },
      "InvoiceLineInput": {
        "type": "object",
        "required": [
          "description",
          "unit_price"
        ],
        "properties": {
          "description": {
            "type": "string",
            "example": "Contributie 2027"
          },
          "quantity": {
            "type": "integer",
            "minimum": 1,
            "default": 1
          },
          "unit_price": {
            "type": "string",
            "pattern": "^-?\\d+(\\.\\d{1,2})?$",
            "description": "The price per unit excluding VAT, with a dot and at most two decimals.",
            "example": "45.00"
          },
          "vat_percentage": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100,
            "default": 0,
            "example": 0
          }
        }
      },
      "InvoiceCreate": {
        "type": "object",
        "required": [
          "member_id",
          "lines"
        ],
        "properties": {
          "member_id": {
            "type": "integer",
            "example": 1042
          },
          "lines": {
            "type": "array",
            "minItems": 1,
            "maxItems": 100,
            "items": {
              "$ref": "#/components/schemas/InvoiceLineInput"
            }
          },
          "invoice_date": {
            "type": "string",
            "format": "date",
            "description": "Defaults to today."
          },
          "due_date": {
            "type": "string",
            "format": "date",
            "description": "Defaults to 30 days after the invoice date."
          },
          "notes": {
            "type": "string",
            "description": "Shown on the invoice."
          },
          "send": {
            "type": "boolean",
            "default": false,
            "description": "Send the invoice right away."
          }
        }
      },
      "Payment": {
        "type": "object",
        "required": [
          "object",
          "id",
          "invoice_id",
          "amount",
          "currency",
          "method",
          "status",
          "paid_at",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "payment"
            ]
          },
          "id": {
            "type": "integer",
            "example": 8810
          },
          "invoice_id": {
            "type": "integer",
            "example": 5531
          },
          "amount": {
            "$ref": "#/components/schemas/Money"
          },
          "currency": {
            "type": "string",
            "enum": [
              "EUR"
            ]
          },
          "method": {
            "type": "string",
            "description": "`mollie` is an online payment, `recurring` an automatic Mollie direct debit, `sepa_direct_debit` a direct debit batch from the association's own bank.",
            "enum": [
              "mollie",
              "recurring",
              "bank_transfer",
              "sepa_direct_debit",
              "cash",
              "other"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "completed",
              "failed",
              "refunded"
            ]
          },
          "paid_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PaymentList": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ListMeta"
          },
          {
            "type": "object",
            "required": [
              "data"
            ],
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Payment"
                }
              }
            }
          }
        ]
      }
    }
  }
}