openapi: 3.0.3
info:
  title: VoordeMensen API
  description: |
    Public and Peppered API endpoints for the VoordeMensen ticketing
    platform. See [help.voordemensen.nl/api](https://help.voordemensen.nl/api)
    for the narrative documentation.
  version: "1.0.0"
  contact:
    name: VoordeMensen support
    url: https://voordemensen.nl
servers:
  - url: https://api.voordemensen.nl/v1/{client_name}
    description: Production API
    variables:
      client_name:
        default: demo
        description: Your client short-name (e.g. demo, fringe)

tags:
  - name: Public
    description: Open endpoints — no authentication
  - name: Peppered Customers
    description: Customer management (authenticated)
  - name: Peppered Events
    description: Events & productions (authenticated)
  - name: Peppered Cart
    description: Full cart lifecycle (authenticated)
  - name: Peppered Orders
    description: Order read + list (authenticated)
  - name: Peppered Seating
    description: Halls & seating plans (authenticated)
  - name: Peppered Payment
    description: Payment methods (authenticated)
  - name: Peppered Exports
    description: Bulk exports for BI / CRM sync (authenticated)

security:
  - apiKey: []

components:
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: Authorization
      description: |
        Peppered API key. Pass as the raw value of the Authorization header
        (no `Bearer` or other prefix). Request the key from support.
        Basic auth with `base64(key:)` is also accepted by the server.

  schemas:
    Event:
      type: object
      properties:
        event_id: { type: integer, example: 94 }
        event_name: { type: string, example: "Demo Event" }
        event_text: { type: string }
        event_short_text: { type: string }
        event_image: { type: string, nullable: true }
        sub_events:
          type: array
          items:
            $ref: "#/components/schemas/SubEvent"

    SubEvent:
      type: object
      properties:
        event_id: { type: integer, example: 95 }
        event_main_id: { type: integer, example: 94 }
        event_name: { type: string }
        event_date: { type: string, format: date, example: "2026-04-01" }
        event_time: { type: string, example: "20:30:00" }
        event_end: { type: string, example: "22:30:00" }
        event_view_end: { type: string, nullable: true }
        event_rep: { type: string, example: "sub" }
        event_free: { type: integer, description: "Max tickets per order (censored)" }
        event_status: { type: string, example: "pub" }
        location_id: { type: integer }
        location_name: { type: string }

    TicketType:
      type: object
      properties:
        discount_id: { type: string }
        discount_name: { type: string }
        base_price: { type: string }
        discount_type: { type: string, enum: ["€", "%", "="] }
        discount_value: { type: string }
        discounted_price: { type: string }

    CartItem:
      type: object
      properties:
        seat_id: { type: string }
        seat_row_nr: { type: string }
        seat_nr: { type: string }
        event_id: { type: integer }
        event_name: { type: string }
        location_name: { type: string }
        event_date: { type: string }
        event_time: { type: string }
        numberoftickets: { type: integer }
        seat_price: { type: string }
        discount_id: { type: string }
        discount_name: { type: string }

    OrderCreateRequest:
      type: object
      required: [cart_id, payment_id, redirectURL, email, firstname, lastname]
      properties:
        cart_id: { type: string }
        payment_id: { type: integer }
        redirectURL: { type: string, format: uri }
        email: { type: string, format: email }
        firstname: { type: string }
        lastname: { type: string }
        phone: { type: string }
        address: { type: string }
        city: { type: string }
        zip: { type: string }
        avg_optin: { type: boolean }
        avg_mailinglist: { type: boolean }

    OrderCreateResponse:
      type: array
      items:
        type: object
        properties:
          url: { type: string, format: uri }
          order_key: { type: string }

    OrderStatusResponse:
      type: object
      properties:
        order_payment_status:
          type: string
          enum: [paid, pending, cancelled, partial, none]
        order_tickets_nr: { type: integer }
        order_tickets_url: { type: string, format: uri }
        order_receipt_url: { type: string, format: uri }
        seats:
          type: array
          items:
            type: object
            properties:
              seat_id: { type: integer }
              event_name: { type: string }
              event_type: { type: string, nullable: true }

    User:
      type: object
      properties:
        customerId: { type: integer }
        customerExternalId: { type: string, nullable: true }
        email: { type: string, format: email }
        customerSalutation: { type: string }
        customerFirstName: { type: string }
        customerLastName: { type: string }
        customerPreposition: { type: string }
        customerAddress: { type: string }
        customerHouseNr: { type: string }
        customerZip: { type: string }
        customerCity: { type: string }
        customerCountry: { type: string }
        customerPhone: { type: string }
        customerCredit: { type: string }
        lastChanged: { type: string, format: date-time }
        customerTags:
          type: array
          items: { type: string }

    UserCreateRequest:
      type: object
      required: [email, customerFirstName, customerLastName]
      properties:
        email: { type: string, format: email }
        customerSalutation: { type: string, maxLength: 1 }
        customerFirstName: { type: string, maxLength: 255 }
        customerLastName: { type: string, maxLength: 255 }
        customerPreposition: { type: string, maxLength: 255 }
        customerAddress: { type: string, maxLength: 255 }
        customerHouseNr: { type: string, maxLength: 20 }
        customerZip: { type: string, maxLength: 10 }
        customerCity: { type: string, maxLength: 255 }
        customerCountry: { type: string, maxLength: 255 }
        customerPhone: { type: string }
        customerCredit: { type: string }

    PepperedEvent:
      type: object
      properties:
        eventId: { type: integer }
        productionId: { type: integer }
        eventName: { type: string }
        eventType: { type: string, nullable: true }
        eventTags: { type: string, nullable: true }
        start: { type: string, format: date-time }
        end: { type: string, format: date-time, nullable: true }
        hallId: { type: integer }
        prices:
          type: array
          items: { type: object }

    PepperedOrder:
      type: object
      properties:
        orderId: { type: integer }
        orderKey: { type: string }
        customerId: { type: integer, nullable: true }
        orderStatus: { type: string, enum: [ord, cancel, reemit, reissue, res, pros, trash] }
        orderPaymentStatus: { type: string, enum: [none, pending, paid, partial, cancelled] }
        orderDate: { type: string, format: date-time }
        timeOut: { type: string, format: date-time, nullable: true }
        amount: { type: number }
        orderDonation: { type: number }
        lastChanged: { type: string, format: date-time }
        handlingTextPayment: { type: string }
        discountName: { type: string, nullable: true }

    PepperedCart:
      type: object
      properties:
        cartId: { type: string }
        timeOut: { type: string, format: date-time }
        numberOfItems: { type: integer }
        amount: { type: number }
        cartItems:
          type: array
          items:
            type: object
            properties:
              itemType: { type: string }
              itemId: { type: string }
              eventId: { type: integer, nullable: true }
              priceId: { type: string }
              priceName: { type: string }
              description: { type: string }
              amount: { type: number }

    Hall:
      type: object
      properties:
        hallId: { type: integer }
        hallName: { type: string }
        hallAddress: { type: string }
        hallZip: { type: string }
        hallCity: { type: string }
        hallPhone: { type: string }
        hallUrl: { type: string }

    PaymentMethod:
      type: object
      properties:
        paymentMethodId: { type: integer }
        name: { type: string }
        fee: { type: number }

paths:
  # ──────────── PUBLIC ────────────
  /events:
    get:
      tags: [Public]
      summary: List all events
      security: []
      responses:
        "200":
          description: Events retrieved
          content:
            application/json:
              schema:
                type: array
                items: { $ref: "#/components/schemas/Event" }

  /events/{event_id}:
    get:
      tags: [Public]
      summary: Get a specific event (or 'upcoming')
      security: []
      parameters:
        - in: path
          name: event_id
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Event retrieved
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Event" }
        "404":
          description: Event not found

  /tickettypes/{event_id}:
    get:
      tags: [Public]
      summary: List ticket types for a sub-event
      security: []
      parameters:
        - in: path
          name: event_id
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Ticket types
          content:
            application/json:
              schema:
                type: array
                items: { $ref: "#/components/schemas/TicketType" }
        "404":
          description: Sub-event not found

  /cart/:
    post:
      tags: [Public]
      summary: Create a cart
      security: []
      responses:
        "201":
          description: Cart created
          content:
            application/json:
              schema:
                type: object
                properties:
                  cart_id: { type: string }

  /cart/{cart_id}:
    get:
      tags: [Public]
      summary: Get cart contents
      security: []
      parameters:
        - in: path
          name: cart_id
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Cart contents
          content:
            application/json:
              schema:
                type: array
                items: { $ref: "#/components/schemas/CartItem" }
    post:
      tags: [Public]
      summary: Add a seat to the cart
      security: []
      parameters:
        - in: path
          name: cart_id
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [event_id, discount_id, numberoftickets]
              properties:
                event_id: { type: integer }
                discount_id: { type: string }
                numberoftickets: { type: integer }
      responses:
        "200":
          description: Seat added

  /cart/{cart_id}/{seat_id}:
    delete:
      tags: [Public]
      summary: Remove a seat from the cart
      security: []
      parameters:
        - in: path
          name: cart_id
          required: true
          schema: { type: string }
        - in: path
          name: seat_id
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Seat removed

  /order/create:
    post:
      tags: [Public]
      summary: Create an order + payment URL
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/OrderCreateRequest" }
      responses:
        "200":
          description: Order created
          content:
            application/json:
              schema: { $ref: "#/components/schemas/OrderCreateResponse" }

  /order/{order_key}:
    get:
      tags: [Public]
      summary: Get order / payment status
      security: []
      parameters:
        - in: path
          name: order_key
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Order status
          content:
            application/json:
              schema: { $ref: "#/components/schemas/OrderStatusResponse" }

  # ──────────── PEPPERED ────────────
  /peppered/customer:
    post:
      tags: [Peppered Customers]
      summary: Create customer
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/UserCreateRequest" }
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema: { $ref: "#/components/schemas/User" }

  /peppered/customer/findByEmail:
    post:
      tags: [Peppered Customers]
      summary: Find customer by email
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email]
              properties:
                email: { type: string, format: email }
      responses:
        "200":
          description: Customer found
          content:
            application/json:
              schema: { $ref: "#/components/schemas/User" }
        "404":
          description: Not found

  /peppered/customer/{customerId}:
    get:
      tags: [Peppered Customers]
      summary: Get customer
      parameters:
        - in: path
          name: customerId
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: Customer
          content:
            application/json:
              schema: { $ref: "#/components/schemas/User" }
    put:
      tags: [Peppered Customers]
      summary: Update customer
      parameters:
        - in: path
          name: customerId
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/UserCreateRequest" }
      responses:
        "200":
          description: Updated

  /peppered/customer/{customerId}/clienttypes:
    get:
      tags: [Peppered Customers]
      summary: Get customer tags
      parameters:
        - in: path
          name: customerId
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: Tags
          content:
            application/json:
              schema:
                type: array
                items: { type: string }
    post:
      tags: [Peppered Customers]
      summary: Set customer tags
      parameters:
        - in: path
          name: customerId
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                clienttypes:
                  type: array
                  items: { type: string }
      responses:
        "200":
          description: Updated

  /peppered/event:
    get:
      tags: [Peppered Events]
      summary: List all events
      parameters:
        - in: query
          name: startDate
          schema: { type: string, format: date }
        - in: query
          name: endDate
          schema: { type: string, format: date }
      responses:
        "200":
          description: Events
          content:
            application/json:
              schema:
                type: array
                items: { $ref: "#/components/schemas/PepperedEvent" }

  /peppered/event/{eventId}:
    get:
      tags: [Peppered Events]
      summary: Get event with availability
      parameters:
        - in: path
          name: eventId
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: Event
          content:
            application/json:
              schema: { $ref: "#/components/schemas/PepperedEvent" }

  /peppered/production/{productionId}:
    get:
      tags: [Peppered Events]
      summary: Get production
      parameters:
        - in: path
          name: productionId
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: Production

  /peppered/cart/{cartId}/summary:
    get:
      tags: [Peppered Cart]
      summary: Cart summary
      parameters:
        - in: path
          name: cartId
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Summary

  /peppered/cart/{cartId}:
    get:
      tags: [Peppered Cart]
      summary: Get cart
      parameters:
        - in: path
          name: cartId
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Cart
          content:
            application/json:
              schema: { $ref: "#/components/schemas/PepperedCart" }
    post:
      tags: [Peppered Cart]
      summary: Add items
      parameters:
        - in: path
          name: cartId
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: array
              items:
                type: object
      responses:
        "200":
          description: Updated
    delete:
      tags: [Peppered Cart]
      summary: Delete cart
      parameters:
        - in: path
          name: cartId
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Deleted

  /peppered/cart/{cartId}/{itemId}:
    delete:
      tags: [Peppered Cart]
      summary: Remove cart item
      parameters:
        - in: path
          name: cartId
          required: true
          schema: { type: string }
        - in: path
          name: itemId
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Removed

  /peppered/cart/{cartId}/checkout:
    post:
      tags: [Peppered Cart]
      summary: Checkout cart
      parameters:
        - in: path
          name: cartId
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [customerId, paymentMethodId]
              properties:
                customerId: { type: integer }
                paymentMethodId: { type: integer }
                redirectURL: { type: string, format: uri }
      responses:
        "200":
          description: Order created

  /peppered/order:
    get:
      tags: [Peppered Orders]
      summary: List orders
      parameters:
        - in: query
          name: changedSince
          schema: { type: string, format: date }
      responses:
        "200":
          description: Orders
          content:
            application/json:
              schema:
                type: array
                items: { $ref: "#/components/schemas/PepperedOrder" }

  /peppered/order/{orderId}:
    get:
      tags: [Peppered Orders]
      summary: Get order
      parameters:
        - in: path
          name: orderId
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: Order
          content:
            application/json:
              schema: { $ref: "#/components/schemas/PepperedOrder" }

  /peppered/hall:
    get:
      tags: [Peppered Seating]
      summary: List halls
      responses:
        "200":
          description: Halls
          content:
            application/json:
              schema:
                type: array
                items: { $ref: "#/components/schemas/Hall" }

  /peppered/hall/{hallId}:
    get:
      tags: [Peppered Seating]
      summary: Get hall
      parameters:
        - in: path
          name: hallId
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: Hall
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Hall" }

  /peppered/seating/{eventId}:
    get:
      tags: [Peppered Seating]
      summary: Get seating plan
      parameters:
        - in: path
          name: eventId
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: Seat plan

  /peppered/paymentmethods:
    get:
      tags: [Peppered Payment]
      summary: List payment methods
      responses:
        "200":
          description: Methods
          content:
            application/json:
              schema:
                type: array
                items: { $ref: "#/components/schemas/PaymentMethod" }

  /peppered/export/clienttypes:
    get:
      tags: [Peppered Exports]
      summary: Export all client-type names
      responses:
        "200":
          description: Tags
          content:
            application/json:
              schema:
                type: array
                items: { type: string }

  /peppered/export/customers:
    post:
      tags: [Peppered Exports]
      summary: Export customers
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                changedSince: { type: string, format: date }
      responses:
        "200":
          description: Customers
          content:
            application/json:
              schema:
                type: array
                items: { $ref: "#/components/schemas/User" }

  /peppered/export/orders:
    post:
      tags: [Peppered Exports]
      summary: Export orders (with seats + user inline)
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                changedSince: { type: string, format: date }
      responses:
        "200":
          description: Orders
          content:
            application/json:
              schema:
                type: array
                items:
                  allOf:
                    - $ref: "#/components/schemas/PepperedOrder"

  /peppered/export/waitinglist:
    post:
      tags: [Peppered Exports]
      summary: Export waiting list
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                changedSince: { type: string, format: date }
      responses:
        "200":
          description: Waiting list entries
