Reservations

With the reservations API a partner manages bookings for a location of your organization: padel courts, car wash bays, treatment rooms or staff members. You describe what can be booked (resources), how it is booked (services), when it is open (schedules and closures), and then look up free slots and create, change, cancel and check in reservations. Every change can be pushed to your system with webhooks.


Quickstart: your first reservation in 4 calls

  1. Create a resource at one of your locations, for example a padel court: POST /reservations/resources.
  2. Create a service that this resource delivers, for example a 90 minute padel match for up to 4 players: POST /reservations/services with resources set to the court.
  3. Look up free slots: GET /reservations/availability. Without a schedule of its own the court follows the opening hours of the location.
  4. Book a slot: POST /reservations with one of the returned starts_at values.

The token needs reservations.manage for steps 1 and 2, and reservations.get (or reservations.manage) plus reservations.store (or reservations.manage) for steps 3 and 4. See Abilities.

1. Resource

curl -X POST https://api.walletapp.co/reservations/resources \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"location":"0c9e7b1a-4d2f-4a8e-9b3c-5e6f7a8b9c0d","name":"Court 1","type":"court"}'

2. Service

curl -X POST https://api.walletapp.co/reservations/services \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"name":"Padel 90 minutes","duration_minutes":90,"max_party_size":4,
       "resources":["3f6c1a2e-7b8d-4e9f-a0b1-c2d3e4f5a601"]}'

3. Availability

curl -G https://api.walletapp.co/reservations/availability \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json" \
  -d service=8e2d4c6a-1b3f-4a5e-9c7d-0f1e2d3c4b5a \
  -d location=0c9e7b1a-4d2f-4a8e-9b3c-5e6f7a8b9c0d \
  -d from=2026-10-17 \
  -d party_size=4

4. Reservation

curl -X POST https://api.walletapp.co/reservations \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 2b7f4e1c-9a3d-4c5e-8f6a-1d2c3b4a5e6f" \
  -d '{"service":"8e2d4c6a-1b3f-4a5e-9c7d-0f1e2d3c4b5a",
       "location":"0c9e7b1a-4d2f-4a8e-9b3c-5e6f7a8b9c0d",
       "starts_at":"2026-10-17T10:30:00+02:00","party_size":4,
       "customer":{"name":"Alex Jansen","email":"alex@example.com"},
       "external_reference":"club-booking-1042"}'

Abilities

Request a token with the abilities below via POST /oauth/token. A token without the required ability gets an HTTP 403. Everything is scoped to the organizations of the token: a resource, service, location or reservation of another organization answers 404, exactly like one that does not exist.

  • Name
    reservations.get
    Type
    ability
    Description

    Read: list and inspect resources, services, schedules, closures and reservations, and look up availability.

  • Name
    reservations.store
    Type
    ability
    Description

    Book: create reservations, change them with PUT /reservations/:id and cancel them (within the cancellation cut-off). This ability does not include reading: with only reservations.store the GET endpoints answer 403, so combine it with reservations.get if your integration also reads.

  • Name
    reservations.manage
    Type
    ability
    Description

    Everything: all of the above, plus creating, changing and deleting resources, services, schedules and closures, the status endpoints confirm, decline, check-in, no-show and complete, and cancelling after the cut-off with force.

Which endpoint needs which ability:

  • Name
    reservations.get or reservations.manage
    Type
    Description

    GET /reservations/resources, GET /reservations/resources/:id, GET /reservations/resources/:id/schedule, GET /reservations/services, GET /reservations/services/:id, GET /reservations/exceptions, GET /reservations/availability, GET /reservations, GET /reservations/:id.

  • Name
    reservations.store or reservations.manage
    Type
    Description

    POST /reservations, PUT /reservations/:id, POST /reservations/:id/cancel (without force).

  • Name
    reservations.manage
    Type
    Description

    POST, PUT and DELETE on /reservations/resources, /reservations/services and /reservations/exceptions, PUT /reservations/resources/:id/schedule, POST /reservations/:id/confirm, /decline, /check-in, /no-show, /complete, and POST /reservations/:id/cancel with force: true.


Resources

A resource is the thing that is booked: a court, a lane, a wash bay, a room or a staff member. A resource belongs to exactly one location. Its capacity is the number of reservations that may run at the same time on that resource in one slot. It is not the number of seats or players. A padel court or a wash bay takes one booking at a time, so its capacity is 1 (the default), even though four people play on the court. Only use a higher value when the resource really admits several separate bookings at once, for example a sauna that admits 8 independent bookings (capacity 8).

The group size of a reservation is its party_size, limited by the max_party_size of the service (a padel service with max_party_size: 4 lets one booking bring four players to a court with capacity 1). Availability reports remaining per resource as capacity minus the number of overlapping active reservations, regardless of their party size.


Services

A service is what a customer books: "Padel 90 minutes", "Wash program Gold", "Massage 60 minutes". It defines the duration, the buffers before and after (cleaning, changeover), the slot grid, how far ahead and how late one can book, the maximum party size, the cancellation cut-off and whether a new reservation needs to be confirmed first. A service is linked to the resources that can deliver it; a resource can deliver several services.


Schedules and closures

Every resource has a weekly schedule: one or more opening blocks per weekday (1 = Monday … 7 = Sunday), for example 09:00-12:00 and 13:00-22:00. A resource without a schedule of its own follows the opening hours of its location. Replacing the schedule with an empty list switches back to the location hours.

Exceptions (closures) override the schedule for one date, for the whole location or for one resource: closed all day (a holiday, maintenance), or open with different hours. On a date, any closed exception closes the resource; otherwise deviating hours of the resource win over deviating hours of the location, which win over the weekly schedule.


Availability

GET /reservations/availability returns the slots that can be booked for a service at a location. A slot is offered when:

  • it fits in an opening block of the date, including the buffers of the service (the buffer before must start after opening, the end plus the buffer after must be before closing);
  • it is on the grid of the service: slots start at opening time and then every slot_interval_minutes (default: the duration);
  • it starts at least min_notice_minutes from now and at most max_advance_days ahead;
  • the resource still has capacity: overlapping reservations that are pending, confirmed or checked_in (including their buffers) are counted; cancelled, declined, no_show and completed reservations free the slot;
  • the party size is at most max_party_size of the service.

Slots are calculated in real elapsed time, so a day with a daylight saving switch has no missing or doubled slot.


Reservations and their lifecycle

A reservation books one service on one resource for one start time. A new reservation is confirmed right away, or pending when the service has requires_confirmation: true. From there it moves through these statuses:

Status machine

new reservation ──► pending      (service with requires_confirmation)
new reservation ──► confirmed    (all other services)

pending ───confirm───► confirmed ───check-in───► checked_in ───complete───► completed
   │                    │      │
   │ cancel             │      └───no-show───► no_show
   ▼                    │
cancelled ◄───cancel────┘

pending ───decline───► declined
  • Name
    pending
    Type
    status
    Description

    Waiting for confirmation. Can move to confirmed, cancelled or declined. Holds capacity.

  • Name
    confirmed
    Type
    status
    Description

    Booked. Can move to checked_in, no_show or cancelled. Holds capacity.

  • Name
    checked_in
    Type
    status
    Description

    The customer has arrived (sets checked_in_at). Can only move to completed. Holds capacity.

  • Name
    completed
    Type
    status
    Description

    Finished (sets completed_at). Final.

  • Name
    cancelled
    Type
    status
    Description

    Cancelled by the partner or the location (sets cancelled_at and cancel_reason). Final.

  • Name
    no_show
    Type
    status
    Description

    The customer did not come. Final.

  • Name
    declined
    Type
    status
    Description

    The location refused a pending reservation with decline. Frees the slot. Final.

Any other transition, for example checking in a pending reservation or cancelling a checked_in one, is refused with 409 invalid_transition and nothing changes. Only pending and confirmed reservations can be changed with PUT /reservations/:id.


Customers, participants and members

A reservation has one customer (the booker: name plus at least an e-mail address, phone number or member id) and optionally a list of participants, for example the four players of a padel match. There can be at most party_size participants; you may include the customer among them.

Customers and participants are linked to existing members of the organization that owns the service, so loyalty can follow later:

  • with member_id (the member UUID): it must be a member of this organization, otherwise 422 (Unknown member.);
  • with email: when the e-mail address belongs to a member of this organization, the reservation is linked to that member; otherwise it stays unlinked, without an error.

Members of other organizations are never linked and never revealed: an unknown member id and a member of another organization give the exact same 422, and an e-mail address of someone outside your organization simply stays unlinked. A new member is only created when you pass create_member: true and member_opt_in: true (the explicit consent of the customer) together with an e-mail address, and only when that e-mail address is not known anywhere on the platform yet. The response tells you with member_created.


Idempotency

Two mechanisms protect you against double bookings when you retry:

  • external_reference (business idempotency): your own booking id, unique per organization. Creating a reservation with a reference that already exists in the organization returns the existing reservation with HTTP 200 and was_duplicate: true, whatever else is in the request and whatever its status. No second reservation is made and no webhook is sent.
  • Idempotency-Key header (transport idempotency): accepted on every POST and PUT. Repeating the exact same request with the same key replays the first response with the header Idempotent-Replayed: true. See Limits and retries for the rules.

Use external_reference when you have a booking id of your own; it also protects you across different tokens and after the 24 hours an idempotency key is kept.


Errors

Validation errors are a 422 in the usual format (message plus errors per field). Business errors carry a machine readable code:

  • Name
    404
    Type
    not found
    Description

    {"error": true, "message": "Reservation not found"} (or Resource, Service, Location, Exception). Also for anything of another organization.

  • Name
    409 slot_unavailable
    Type
    conflict
    Description

    The slot cannot be booked. reason is not_available (full, closed, off the grid, too soon or too far ahead) or no_resource (no active resource at this location delivers the service, or the requested resource does not).

  • Name
    409 invalid_transition
    Type
    conflict
    Description

    The status change is not allowed. The body contains from, to and allowed (the statuses that are possible from from).

  • Name
    409 not_editable
    Type
    conflict
    Description

    PUT /reservations/:id on a reservation that is not pending or confirmed.

  • Name
    409 has_future_reservations
    Type
    conflict
    Description

    Deleting a resource or service (or moving a resource to another location) while it still has future pending, confirmed or checked_in reservations. Cancel or move them first.

  • Name
    422 cancellation_window_passed
    Type
    unprocessable
    Description

    The cancellation cut-off of the service has passed. The body contains cancellation_cutoff_minutes. Cancel with force: true (needs reservations.manage) to override.

  • Name
    403 force_requires_manage
    Type
    forbidden
    Description

    force: true on a cancellation by a token without reservations.manage.

  • Name
    401
    Type
    unauthorized
    Description

    No valid token, or an organization_id your token has no access to.

409 slot_unavailable

{
  "error": true,
  "code": "slot_unavailable",
  "reason": "not_available",
  "message": "The requested slot is not available."
}

409 invalid_transition

{
  "error": true,
  "code": "invalid_transition",
  "message": "Cannot change a reservation from 'checked_in' to 'cancelled'.",
  "from": "checked_in",
  "to": "cancelled",
  "allowed": ["completed"]
}

Lists and pagination

All list endpoints are paginated with page (default 1) and per_page (default 25, at most 100; more is a 422) and return a meta object. The order is always deterministic. When your token has access to several organizations, all of them are listed; pass organization_id (the organization identifier) to narrow the list to one.

meta

"meta": {
  "current_page": 1,
  "per_page": 25,
  "total": 3,
  "last_page": 1
}

The resource model

  • Name
    id
    Type
    string
    Description

    Unique identifier of the resource (a UUID).

  • Name
    location
    Type
    string
    Description

    Identifier of the location the resource belongs to.

  • Name
    name
    Type
    string
    Description

    Name, for example Court 1.

  • Name
    type
    Type
    string
    Description

    One of lane, bay, court, staff, room, other. Default other.

  • Name
    capacity
    Type
    integer
    Description

    Number of reservations that may run at the same time on this resource in one slot (1 to 10000). Default 1, which is right for a court, lane or wash bay. This is not the number of people: group size is the reservation's party_size, limited by the service's max_party_size.

  • Name
    active
    Type
    boolean
    Description

    Inactive resources are not offered in availability and cannot be booked. Default true.

  • Name
    sort
    Type
    integer
    Description

    Sort order (0 to 1000000). When you book without a resource, the first free resource in sort, then creation order, is chosen. Default 0.

  • Name
    external_id
    Type
    string|null
    Description

    Your own id for the resource (max 255 characters).

  • Name
    services
    Type
    array
    Description

    UUIDs of the services this resource delivers.

  • Name
    created_at
    Type
    string
    Description

    ISO 8601 timestamp.

  • Name
    updated_at
    Type
    string
    Description

    ISO 8601 timestamp.


GET/reservations/resources

List resources

Returns the resources of your organization, ordered by sort and then creation order. Ability reservations.get or reservations.manage.

Query parameters

  • Name
    location
    Type
    string
    Description

    Only resources of this location (identifier). A location of another organization answers 404.

  • Name
    type
    Type
    string
    Description

    Only resources of this type.

  • Name
    active
    Type
    boolean
    Description

    1 for active, 0 for inactive resources.

  • Name
    organization_id
    Type
    string
    Description

    Only this organization (when your token has access to several).

  • Name
    page, per_page
    Type
    integer
    Description

    Pagination, see Lists and pagination.

Request

GET
/reservations/resources
curl -G https://api.walletapp.co/reservations/resources \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json" \
  -d location=0c9e7b1a-4d2f-4a8e-9b3c-5e6f7a8b9c0d

Response

{
  "message": "Request successful",
  "data": [
    {
      "id": "3f6c1a2e-7b8d-4e9f-a0b1-c2d3e4f5a601",
      "location": "0c9e7b1a-4d2f-4a8e-9b3c-5e6f7a8b9c0d",
      "name": "Court 1",
      "type": "court",
      "capacity": 1,
      "active": true,
      "sort": 1,
      "external_id": "court-1",
      "services": ["8e2d4c6a-1b3f-4a5e-9c7d-0f1e2d3c4b5a"],
      "created_at": "2026-10-10T14:00:00+02:00",
      "updated_at": "2026-10-10T14:00:00+02:00"
    }
  ],
  "meta": { "current_page": 1, "per_page": 25, "total": 1, "last_page": 1 }
}

POST/reservations/resources

Create a resource

Creates a resource at a location. The organization and brand are taken from the location. Ability reservations.manage.

Body

  • Name
    location
    Type
    string
    Description

    Identifier of one of your locations. Another organization's location answers 404.

  • Name
    name
    Type
    string
    Description

    Max 255 characters.

  • Name
    type
    Type
    string
    Description

    lane, bay, court, staff, room or other (default).

  • Name
    capacity
    Type
    integer
    Description

    Concurrent reservations per slot, not seats or players: 1 to 10000, default 1 (a padel court or wash bay). Group size is set per reservation with party_size, up to the service's max_party_size.

  • Name
    active
    Type
    boolean
    Description

    Default true.

  • Name
    sort
    Type
    integer
    Description

    0 to 1000000, default 0.

  • Name
    external_id
    Type
    string
    Description

    Max 255 characters.

  • Name
    services
    Type
    array
    Description

    Up to 200 service UUIDs of the same organization. An unknown or foreign service is a 422 on services.N.

Request

POST
/reservations/resources
curl -X POST https://api.walletapp.co/reservations/resources \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"location":"0c9e7b1a-4d2f-4a8e-9b3c-5e6f7a8b9c0d","name":"Court 1","type":"court",
       "capacity":1,"sort":1,"external_id":"court-1"}'

Response

{
  "message": "Request successful",
  "data": {
    "id": "3f6c1a2e-7b8d-4e9f-a0b1-c2d3e4f5a601",
    "location": "0c9e7b1a-4d2f-4a8e-9b3c-5e6f7a8b9c0d",
    "name": "Court 1",
    "type": "court",
    "capacity": 1,
    "active": true,
    "sort": 1,
    "external_id": "court-1",
    "services": [],
    "created_at": "2026-10-10T14:00:00+02:00",
    "updated_at": "2026-10-10T14:00:00+02:00"
  }
}

GET/reservations/resources/:id

Inspect a resource

Returns one resource (see the resource model). Ability reservations.get or reservations.manage. 404 for an unknown resource or one of another organization.

Request

GET
/reservations/resources/:id
curl https://api.walletapp.co/reservations/resources/3f6c1a2e-7b8d-4e9f-a0b1-c2d3e4f5a601 \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json"

PUT/reservations/resources/:id

Update a resource

Changes only the fields you send; the fields and limits are the same as for creating a resource. Ability reservations.manage.

  • services replaces the full list of linked services ([] unlinks all).
  • location must be a location of the same organization. Moving a resource that still has future reservations to another location answers 409 has_future_reservations.

Request

PUT
/reservations/resources/:id
curl -X PUT https://api.walletapp.co/reservations/resources/3f6c1a2e-7b8d-4e9f-a0b1-c2d3e4f5a601 \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"name":"Court 1 (covered)","services":["8e2d4c6a-1b3f-4a5e-9c7d-0f1e2d3c4b5a"]}'

DELETE/reservations/resources/:id

Delete a resource

Deletes a resource. Past reservations keep referring to it. Ability reservations.manage. A resource with future pending, confirmed or checked_in reservations answers 409 has_future_reservations.

Request

DELETE
/reservations/resources/:id
curl -X DELETE https://api.walletapp.co/reservations/resources/3f6c1a2e-7b8d-4e9f-a0b1-c2d3e4f5a601 \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json"

Response

{
  "message": "Request successful",
  "data": { "id": "3f6c1a2e-7b8d-4e9f-a0b1-c2d3e4f5a601", "deleted": true }
}

409

{
  "error": true,
  "code": "has_future_reservations",
  "message": "This item has future reservations that block it. Cancel or move them first."
}

GET/reservations/resources/:id/schedule

Get a schedule

Returns the weekly schedule of a resource. Ability reservations.get or reservations.manage.

  • Name
    resource
    Type
    string
    Description

    The resource UUID.

  • Name
    source
    Type
    string
    Description

    resource when the resource has its own schedule, location_times when it follows the opening hours of its location.

  • Name
    schedule
    Type
    array
    Description

    Opening blocks ordered by weekday and time: weekday (1 = Monday … 7 = Sunday), opens_at and closes_at (HH:MM, local time).

Request

GET
/reservations/resources/:id/schedule
curl https://api.walletapp.co/reservations/resources/3f6c1a2e-7b8d-4e9f-a0b1-c2d3e4f5a601/schedule \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json"

Response

{
  "message": "Request successful",
  "data": {
    "resource": "3f6c1a2e-7b8d-4e9f-a0b1-c2d3e4f5a601",
    "source": "location_times",
    "schedule": [
      { "weekday": 1, "opens_at": "09:00", "closes_at": "17:00" },
      { "weekday": 6, "opens_at": "10:00", "closes_at": "14:00" }
    ]
  }
}

PUT/reservations/resources/:id/schedule

Replace a schedule

Replaces the whole weekly schedule of a resource. Ability reservations.manage. Send an empty list to go back to the opening hours of the location.

Body

  • Name
    schedule
    Type
    array
    Description

    Up to 70 blocks (the key must be present, an empty list is allowed).

  • Name
    schedule.N.weekday
    Type
    integer
    Description

    1 (Monday) to 7 (Sunday).

  • Name
    schedule.N.opens_at
    Type
    string
    Description

    HH:MM.

  • Name
    schedule.N.closes_at
    Type
    string
    Description

    HH:MM, after opens_at (a block cannot cross midnight).

Several blocks per weekday are allowed (for a lunch break), but they must not overlap; touching blocks (09:00-12:00 and 12:00-14:00) are fine. An invalid schedule is a 422 and keeps the existing schedule.

Request

PUT
/reservations/resources/:id/schedule
curl -X PUT https://api.walletapp.co/reservations/resources/3f6c1a2e-7b8d-4e9f-a0b1-c2d3e4f5a601/schedule \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"schedule":[
        {"weekday":6,"opens_at":"09:00","closes_at":"12:00"},
        {"weekday":6,"opens_at":"13:00","closes_at":"22:00"},
        {"weekday":7,"opens_at":"09:00","closes_at":"18:00"}
      ]}'

Response

{
  "message": "Request successful",
  "data": {
    "resource": "3f6c1a2e-7b8d-4e9f-a0b1-c2d3e4f5a601",
    "source": "resource",
    "schedule": [
      { "weekday": 6, "opens_at": "09:00", "closes_at": "12:00" },
      { "weekday": 6, "opens_at": "13:00", "closes_at": "22:00" },
      { "weekday": 7, "opens_at": "09:00", "closes_at": "18:00" }
    ]
  }
}

The service model

  • Name
    id
    Type
    string
    Description

    Unique identifier of the service (a UUID).

  • Name
    name
    Type
    string
    Description

    Name, for example Padel 90 minutes.

  • Name
    description
    Type
    string|null
    Description

    Free text, max 5000 characters.

  • Name
    duration_minutes
    Type
    integer
    Description

    Length of a reservation, 5 to 1440.

  • Name
    buffer_before_minutes
    Type
    integer
    Description

    Time blocked before the start (0 to 1440, default 0).

  • Name
    buffer_after_minutes
    Type
    integer
    Description

    Time blocked after the end (0 to 1440, default 0).

  • Name
    slot_interval_minutes
    Type
    integer|null
    Description

    Step between possible start times (5 to 1440). null (default) uses the duration.

  • Name
    min_notice_minutes
    Type
    integer
    Description

    How long before the start a slot can still be booked (0 to 525600, default 0).

  • Name
    max_advance_days
    Type
    integer
    Description

    How many days ahead can be booked (1 to 730, default 60).

  • Name
    cancellation_cutoff_minutes
    Type
    integer|null
    Description

    Until how many minutes before the start a reservation can be cancelled without force (0 to 525600). null (default) means always.

  • Name
    max_party_size
    Type
    integer
    Description

    Maximum party_size of one reservation (1 to 10000, default 1).

  • Name
    price_cents
    Type
    integer|null
    Description

    Informational price in cents.

  • Name
    currency
    Type
    string
    Description

    ISO 4217 code, stored in upper case (default EUR).

  • Name
    requires_confirmation
    Type
    boolean
    Description

    true: new reservations start as pending and need confirm. Default false.

  • Name
    active
    Type
    boolean
    Description

    An inactive service cannot be booked (422). Default true.

  • Name
    external_id
    Type
    string|null
    Description

    Your own id for the service (max 255 characters).

  • Name
    resources
    Type
    array
    Description

    UUIDs of the resources that deliver this service.

  • Name
    created_at
    Type
    string
    Description

    ISO 8601 timestamp.

  • Name
    updated_at
    Type
    string
    Description

    ISO 8601 timestamp.


GET/reservations/services

List services

Returns the services of your organization in creation order. Ability reservations.get or reservations.manage.

Query parameters

  • Name
    active
    Type
    boolean
    Description

    1 for active, 0 for inactive services.

  • Name
    organization_id
    Type
    string
    Description

    Only this organization.

  • Name
    page, per_page
    Type
    integer
    Description

    Pagination, see Lists and pagination.

Request

GET
/reservations/services
curl -G https://api.walletapp.co/reservations/services \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json" \
  -d active=1

POST/reservations/services

Create a service

Creates a service. Ability reservations.manage.

Body

  • Name
    name
    Type
    string
    Description

    Max 255 characters.

  • Name
    duration_minutes
    Type
    integer
    Description

    5 to 1440.

  • Name
    organization_id
    Type
    string
    Description

    Organization identifier. Required only when your token has access to more than one organization (otherwise 422); an organization outside your token is a 401.

  • Name
    brand_id
    Type
    string
    Description

    Identifier of a brand of the organization (optional, a foreign brand is a 422).

  • Name
    resources
    Type
    array
    Description

    Up to 500 resource UUIDs of the same organization. An unknown or foreign resource is a 422.

  • Name
    description, buffer_before_minutes, buffer_after_minutes, slot_interval_minutes, min_notice_minutes, max_advance_days, cancellation_cutoff_minutes, max_party_size, price_cents, currency, requires_confirmation, active, external_id
    Type
    Description

    Optional, with the limits from the service model.

Request

POST
/reservations/services
curl -X POST https://api.walletapp.co/reservations/services \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Padel 90 minutes",
    "description": "Indoor court, rackets available at the desk.",
    "duration_minutes": 90,
    "buffer_before_minutes": 0,
    "buffer_after_minutes": 0,
    "slot_interval_minutes": 30,
    "min_notice_minutes": 60,
    "max_advance_days": 14,
    "cancellation_cutoff_minutes": 1440,
    "max_party_size": 4,
    "price_cents": 4000,
    "currency": "EUR",
    "requires_confirmation": false,
    "active": true,
    "external_id": "padel-90",
    "resources": [
      "3f6c1a2e-7b8d-4e9f-a0b1-c2d3e4f5a601",
      "3f6c1a2e-7b8d-4e9f-a0b1-c2d3e4f5a602"
    ]
  }'

Response

{
  "message": "Request successful",
  "data": {
    "id": "8e2d4c6a-1b3f-4a5e-9c7d-0f1e2d3c4b5a",
    "name": "Padel 90 minutes",
    "description": "Indoor court, rackets available at the desk.",
    "duration_minutes": 90,
    "buffer_before_minutes": 0,
    "buffer_after_minutes": 0,
    "slot_interval_minutes": 30,
    "min_notice_minutes": 60,
    "max_advance_days": 14,
    "cancellation_cutoff_minutes": 1440,
    "max_party_size": 4,
    "price_cents": 4000,
    "currency": "EUR",
    "requires_confirmation": false,
    "active": true,
    "external_id": "padel-90",
    "resources": [
      "3f6c1a2e-7b8d-4e9f-a0b1-c2d3e4f5a601",
      "3f6c1a2e-7b8d-4e9f-a0b1-c2d3e4f5a602"
    ],
    "created_at": "2026-10-10T14:05:00+02:00",
    "updated_at": "2026-10-10T14:05:00+02:00"
  }
}

GET/reservations/services/:id

Inspect a service

Returns one service (see the service model). Ability reservations.get or reservations.manage. 404 for an unknown service or one of another organization.

Request

GET
/reservations/services/:id
curl https://api.walletapp.co/reservations/services/8e2d4c6a-1b3f-4a5e-9c7d-0f1e2d3c4b5a \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json"

PUT/reservations/services/:id

Update a service

Changes only the fields you send, with the same limits as creating a service. resources replaces the full list of linked resources. Changing the duration or buffers does not move existing reservations; it applies to new bookings and to availability. Ability reservations.manage.

Request

PUT
/reservations/services/:id
curl -X PUT https://api.walletapp.co/reservations/services/8e2d4c6a-1b3f-4a5e-9c7d-0f1e2d3c4b5a \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"price_cents":4400,"cancellation_cutoff_minutes":720}'

DELETE/reservations/services/:id

Delete a service

Deletes a service. Ability reservations.manage. A service with future pending, confirmed or checked_in reservations answers 409 has_future_reservations.

Request

DELETE
/reservations/services/:id
curl -X DELETE https://api.walletapp.co/reservations/services/8e2d4c6a-1b3f-4a5e-9c7d-0f1e2d3c4b5a \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json"

Response

{
  "message": "Request successful",
  "data": { "id": "8e2d4c6a-1b3f-4a5e-9c7d-0f1e2d3c4b5a", "deleted": true }
}

The exception model

  • Name
    id
    Type
    integer
    Description

    Identifier of the exception.

  • Name
    location
    Type
    string
    Description

    Identifier of the location.

  • Name
    resource
    Type
    string|null
    Description

    Resource UUID, or null when the exception applies to the whole location.

  • Name
    date
    Type
    string
    Description

    The date, YYYY-MM-DD (local).

  • Name
    closed
    Type
    boolean
    Description

    true: closed all day. false: open with the hours below instead of the weekly schedule.

  • Name
    opens_at
    Type
    string|null
    Description

    HH:MM, only when closed is false.

  • Name
    closes_at
    Type
    string|null
    Description

    HH:MM, only when closed is false.

  • Name
    reason
    Type
    string|null
    Description

    Free text, for example Christmas.


GET/reservations/exceptions

List exceptions

Returns closures and deviating hours, ordered by date. Ability reservations.get or reservations.manage.

Query parameters

  • Name
    location
    Type
    string
    Description

    Only exceptions of this location.

  • Name
    resource
    Type
    string
    Description

    Only exceptions of this resource (location-wide exceptions are not included).

  • Name
    from
    Type
    string
    Description

    YYYY-MM-DD, inclusive.

  • Name
    to
    Type
    string
    Description

    YYYY-MM-DD, inclusive, not before from.

  • Name
    organization_id
    Type
    string
    Description

    Only this organization.

  • Name
    page, per_page
    Type
    integer
    Description

    Pagination, see Lists and pagination.

Request

GET
/reservations/exceptions
curl -G https://api.walletapp.co/reservations/exceptions \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json" \
  -d location=0c9e7b1a-4d2f-4a8e-9b3c-5e6f7a8b9c0d \
  -d from=2026-12-01 -d to=2026-12-31

Response

{
  "message": "Request successful",
  "data": [
    {
      "id": 41,
      "location": "0c9e7b1a-4d2f-4a8e-9b3c-5e6f7a8b9c0d",
      "resource": null,
      "date": "2026-12-25",
      "closed": true,
      "opens_at": null,
      "closes_at": null,
      "reason": "Christmas"
    },
    {
      "id": 42,
      "location": "0c9e7b1a-4d2f-4a8e-9b3c-5e6f7a8b9c0d",
      "resource": null,
      "date": "2026-12-31",
      "closed": false,
      "opens_at": "09:00",
      "closes_at": "16:00",
      "reason": "New Year's Eve"
    }
  ],
  "meta": { "current_page": 1, "per_page": 25, "total": 2, "last_page": 1 }
}

POST/reservations/exceptions

Create an exception

Closes a location or resource on a date, or sets different hours. Ability reservations.manage. There is one exception per location, resource (or none) and date: posting again for the same scope and date replaces it and keeps its id.

Body

  • Name
    location
    Type
    string
    Description

    Identifier of one of your locations.

  • Name
    resource
    Type
    string
    Description

    A resource UUID at that location. Leave out for the whole location.

  • Name
    date
    Type
    string
    Description

    YYYY-MM-DD.

  • Name
    closed
    Type
    boolean
    Description

    Default true. With true, opens_at and closes_at are ignored.

  • Name
    opens_at
    Type
    string
    Description

    HH:MM. Required when closed is false.

  • Name
    closes_at
    Type
    string
    Description

    HH:MM, after opens_at. Required when closed is false.

  • Name
    reason
    Type
    string
    Description

    Max 255 characters.

Existing reservations on that date are not cancelled automatically; an exception only affects availability and new bookings.

Request

POST
/reservations/exceptions
curl -X POST https://api.walletapp.co/reservations/exceptions \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"location":"0c9e7b1a-4d2f-4a8e-9b3c-5e6f7a8b9c0d","date":"2026-12-31",
       "closed":false,"opens_at":"09:00","closes_at":"16:00","reason":"New Year'"'"'s Eve"}'

Response

{
  "message": "Request successful",
  "data": {
    "id": 42,
    "location": "0c9e7b1a-4d2f-4a8e-9b3c-5e6f7a8b9c0d",
    "resource": null,
    "date": "2026-12-31",
    "closed": false,
    "opens_at": "09:00",
    "closes_at": "16:00",
    "reason": "New Year's Eve"
  }
}

DELETE/reservations/exceptions/:id

Delete an exception

Removes an exception; the weekly schedule applies again on that date. Ability reservations.manage.

Request

DELETE
/reservations/exceptions/:id
curl -X DELETE https://api.walletapp.co/reservations/exceptions/42 \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json"

Response

{
  "message": "Request successful",
  "data": { "id": 42, "deleted": true }
}

GET/reservations/availability

Get availability

Returns the bookable slots of a service at a location, see Availability for the rules. Ability reservations.get or reservations.manage.

Query parameters

  • Name
    service
    Type
    string
    Description

    Service UUID.

  • Name
    location
    Type
    string
    Description

    Location identifier, of the same organization as the service.

  • Name
    from
    Type
    string
    Description

    First date, YYYY-MM-DD (local).

  • Name
    to
    Type
    string
    Description

    Last date, YYYY-MM-DD, inclusive, default from. The range may span at most 31 days.

  • Name
    resource
    Type
    string
    Description

    Only slots of this resource.

  • Name
    party_size
    Type
    integer
    Description

    Default 1. Larger than max_party_size of the service is a 422.

Response

  • Name
    from, to
    Type
    string
    Description

    The requested range as instants: the start of from and the start of the day after to, both local.

  • Name
    timezone
    Type
    string
    Description

    Always Europe/Amsterdam.

  • Name
    slots
    Type
    array
    Description

    Ascending by start: starts_at, ends_at (start plus duration, without buffers) and resources, the resources that are free at that time in booking order, each with remaining (how many more reservations fit).

No slots (closed, fully booked or no resource for the service) is an empty slots list, not an error.

Request

GET
/reservations/availability
curl -G https://api.walletapp.co/reservations/availability \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json" \
  -d service=8e2d4c6a-1b3f-4a5e-9c7d-0f1e2d3c4b5a \
  -d location=0c9e7b1a-4d2f-4a8e-9b3c-5e6f7a8b9c0d \
  -d from=2026-10-17 \
  -d party_size=4

Response

{
  "message": "Request successful",
  "data": {
    "service": "8e2d4c6a-1b3f-4a5e-9c7d-0f1e2d3c4b5a",
    "location": "0c9e7b1a-4d2f-4a8e-9b3c-5e6f7a8b9c0d",
    "party_size": 4,
    "timezone": "Europe/Amsterdam",
    "from": "2026-10-17T00:00:00+02:00",
    "to": "2026-10-18T00:00:00+02:00",
    "slots": [
      {
        "starts_at": "2026-10-17T09:00:00+02:00",
        "ends_at": "2026-10-17T10:30:00+02:00",
        "resources": [
          { "id": "3f6c1a2e-7b8d-4e9f-a0b1-c2d3e4f5a601", "remaining": 1 },
          { "id": "3f6c1a2e-7b8d-4e9f-a0b1-c2d3e4f5a602", "remaining": 1 }
        ]
      },
      {
        "starts_at": "2026-10-17T10:30:00+02:00",
        "ends_at": "2026-10-17T12:00:00+02:00",
        "resources": [
          { "id": "3f6c1a2e-7b8d-4e9f-a0b1-c2d3e4f5a602", "remaining": 1 }
        ]
      }
    ]
  }
}

The reservation model

This is the shape of every reservation in API responses. Webhook payloads carry only a subset (ids and status, no personal data).

  • Name
    id
    Type
    string
    Description

    Unique identifier of the reservation (a UUID).

  • Name
    status
    Type
    string
    Description

    pending, confirmed, checked_in, completed, cancelled, no_show or declined. See the lifecycle.

  • Name
    service
    Type
    string
    Description

    Service UUID.

  • Name
    resource
    Type
    string
    Description

    UUID of the booked resource.

  • Name
    location
    Type
    string
    Description

    Location identifier.

  • Name
    starts_at
    Type
    string
    Description

    Start, ISO 8601 with the Europe/Amsterdam offset.

  • Name
    ends_at
    Type
    string
    Description

    End (start plus the service duration, without buffers).

  • Name
    party_size
    Type
    integer
    Description

    Number of people.

  • Name
    customer
    Type
    object
    Description

    name, email (lower case), phone and member_id (the UUID of the linked member, or null).

  • Name
    participants
    Type
    array
    Description

    Each with name, email (lower case) and member_id (or null).

  • Name
    source
    Type
    string
    Description

    Where the reservation came from: api for this API.

  • Name
    external_reference
    Type
    string|null
    Description

    Your own booking id, unique per organization.

  • Name
    notes
    Type
    string|null
    Description

    Free text.

  • Name
    metadata
    Type
    object|null
    Description

    Your own key-value data, returned as sent.

  • Name
    payment_status
    Type
    string
    Description

    none (default), pending, paid or refunded. Informational: no payment is taken by this API.

  • Name
    amount_cents
    Type
    integer|null
    Description

    Informational amount in cents.

  • Name
    cancelled_at
    Type
    string|null
    Description

    When the reservation was cancelled.

  • Name
    cancel_reason
    Type
    string|null
    Description

    The reason given when cancelling.

  • Name
    checked_in_at
    Type
    string|null
    Description

    When the customer was checked in.

  • Name
    completed_at
    Type
    string|null
    Description

    When the reservation was completed.

  • Name
    created_at
    Type
    string
    Description

    ISO 8601 timestamp.

  • Name
    updated_at
    Type
    string
    Description

    ISO 8601 timestamp.


POST/reservations

Create a reservation

Books a slot. The slot is checked and claimed under a lock, so two partners booking the last free court at the same moment can never both succeed: the second one gets 409 slot_unavailable. Ability reservations.store or reservations.manage.

Body

  • Name
    service
    Type
    string
    Description

    Service UUID. An inactive service is a 422.

  • Name
    location
    Type
    string
    Description

    Location identifier, of the same organization as the service.

  • Name
    starts_at
    Type
    string
    Description

    The start, exactly one of the starts_at values from availability. Send it with an offset (2026-10-17T10:30:00+02:00); without an offset it is read as Europe/Amsterdam local time.

  • Name
    resource
    Type
    string
    Description

    Book this resource. Without it, the first free resource that delivers the service (by sort) is chosen.

  • Name
    party_size
    Type
    integer
    Description

    Default 1, at most max_party_size of the service.

  • Name
    customer.name
    Type
    string
    Description

    Max 255 characters.

  • Name
    customer.email
    Type
    string
    Description

    Max 191 characters. One of customer.email, customer.phone or customer.member_id is required.

  • Name
    customer.phone
    Type
    string
    Description

    Max 64 characters.

  • Name
    customer.member_id
    Type
    string
    Description

    UUID of a member of the organization. Unknown or foreign: 422.

  • Name
    customer.create_member
    Type
    boolean
    Description

    Create a member for a customer who is not one yet. Requires customer.member_opt_in: true and customer.email. See Customers, participants and members.

  • Name
    customer.member_opt_in
    Type
    boolean
    Description

    The customer explicitly agreed to become a member.

  • Name
    participants
    Type
    array
    Description

    At most party_size (and at most 100) entries, each with optional name, email and member_id, linked to members like the customer.

  • Name
    external_reference
    Type
    string
    Description

    Your booking id, max 191 characters. See Idempotency.

  • Name
    notes
    Type
    string
    Description

    Max 5000 characters.

  • Name
    metadata
    Type
    object
    Description

    Your own key-value data.

  • Name
    payment_status
    Type
    string
    Description

    none (default), pending, paid or refunded.

  • Name
    amount_cents
    Type
    integer
    Description

    0 or more.

Response

201 with the reservation plus was_duplicate (false) and member_created. When the external_reference already exists: 200 with the existing reservation and was_duplicate: true.

Errors

404 for an unknown or foreign service, location or resource; 409 slot_unavailable when the slot cannot be booked; 422 for validation errors (too many participants, party size, unknown member, inactive service).

Request

POST
/reservations
curl -X POST https://api.walletapp.co/reservations \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 2b7f4e1c-9a3d-4c5e-8f6a-1d2c3b4a5e6f" \
  -d '{
    "service": "8e2d4c6a-1b3f-4a5e-9c7d-0f1e2d3c4b5a",
    "location": "0c9e7b1a-4d2f-4a8e-9b3c-5e6f7a8b9c0d",
    "starts_at": "2026-10-17T10:30:00+02:00",
    "party_size": 4,
    "customer": {
      "name": "Alex Jansen",
      "email": "Alex@Example.com",
      "phone": "+31600000000"
    },
    "participants": [
      { "name": "Alex Jansen", "email": "alex@example.com" },
      { "name": "Sam de Boer", "member_id": "9a1c2d3e-4f50-4a6b-8c7d-0e1f2a3b4c5d" },
      { "name": "Robin Smit", "email": "robin@example.org" },
      { "name": "Kim Visser" }
    ],
    "external_reference": "club-booking-1042",
    "notes": "Bring extra balls",
    "metadata": { "court_lights": true },
    "payment_status": "paid",
    "amount_cents": 4000
  }'

Response (201)

{
  "message": "Request successful",
  "data": {
    "id": "d4c3b2a1-9e8f-4d7c-b6a5-0f9e8d7c6b5a",
    "status": "confirmed",
    "service": "8e2d4c6a-1b3f-4a5e-9c7d-0f1e2d3c4b5a",
    "resource": "3f6c1a2e-7b8d-4e9f-a0b1-c2d3e4f5a602",
    "location": "0c9e7b1a-4d2f-4a8e-9b3c-5e6f7a8b9c0d",
    "starts_at": "2026-10-17T10:30:00+02:00",
    "ends_at": "2026-10-17T12:00:00+02:00",
    "party_size": 4,
    "customer": {
      "name": "Alex Jansen",
      "email": "alex@example.com",
      "phone": "+31600000000",
      "member_id": null
    },
    "participants": [
      { "name": "Alex Jansen", "email": "alex@example.com", "member_id": null },
      { "name": "Sam de Boer", "email": null, "member_id": "9a1c2d3e-4f50-4a6b-8c7d-0e1f2a3b4c5d" },
      { "name": "Robin Smit", "email": "robin@example.org", "member_id": "4e5f6a7b-8c9d-4e0f-a1b2-c3d4e5f6a7b8" },
      { "name": "Kim Visser", "email": null, "member_id": null }
    ],
    "source": "api",
    "external_reference": "club-booking-1042",
    "notes": "Bring extra balls",
    "metadata": { "court_lights": true },
    "payment_status": "paid",
    "amount_cents": 4000,
    "cancelled_at": null,
    "cancel_reason": null,
    "checked_in_at": null,
    "completed_at": null,
    "created_at": "2026-10-10T15:12:00+02:00",
    "updated_at": "2026-10-10T15:12:00+02:00",
    "was_duplicate": false,
    "member_created": false
  }
}

Duplicate (200)

{
  "message": "Request successful",
  "data": {
    "id": "d4c3b2a1-9e8f-4d7c-b6a5-0f9e8d7c6b5a",
    "status": "confirmed",
    "external_reference": "club-booking-1042",
    "...": "the existing reservation",
    "was_duplicate": true,
    "member_created": false
  }
}

GET/reservations/:id

Inspect a reservation

Returns one reservation (see the reservation model). Ability reservations.get or reservations.manage. 404 for an unknown reservation or one of another organization.

Request

GET
/reservations/:id
curl https://api.walletapp.co/reservations/d4c3b2a1-9e8f-4d7c-b6a5-0f9e8d7c6b5a \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json"

GET/reservations

List reservations

Returns reservations ordered by starts_at (then creation order). Ability reservations.get or reservations.manage.

Query parameters

  • Name
    from
    Type
    string
    Description

    YYYY-MM-DD: reservations starting on or after this local date.

  • Name
    to
    Type
    string
    Description

    YYYY-MM-DD: reservations starting on or before this local date (inclusive), not before from.

  • Name
    status
    Type
    string
    Description

    One status or a comma separated list, for example pending,confirmed. An unknown status is a 422.

  • Name
    location
    Type
    string
    Description

    Location identifier (foreign: 404).

  • Name
    service
    Type
    string
    Description

    Service UUID (foreign: 404).

  • Name
    resource
    Type
    string
    Description

    Resource UUID (foreign: 404).

  • Name
    member_id
    Type
    string
    Description

    Reservations whose customer is this member. An unknown member simply returns an empty list.

  • Name
    customer_email
    Type
    string
    Description

    Exact customer e-mail address (case insensitive).

  • Name
    external_reference
    Type
    string
    Description

    Your booking id.

  • Name
    order
    Type
    string
    Description

    asc (default) or desc.

  • Name
    organization_id
    Type
    string
    Description

    Only this organization.

  • Name
    page, per_page
    Type
    integer
    Description

    Pagination, see Lists and pagination.

Request

GET
/reservations
curl -G https://api.walletapp.co/reservations \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json" \
  -d from=2026-10-17 -d to=2026-10-17 \
  -d status=pending,confirmed \
  -d per_page=50

Response

{
  "message": "Request successful",
  "data": [
    {
      "id": "d4c3b2a1-9e8f-4d7c-b6a5-0f9e8d7c6b5a",
      "status": "confirmed",
      "service": "8e2d4c6a-1b3f-4a5e-9c7d-0f1e2d3c4b5a",
      "resource": "3f6c1a2e-7b8d-4e9f-a0b1-c2d3e4f5a602",
      "location": "0c9e7b1a-4d2f-4a8e-9b3c-5e6f7a8b9c0d",
      "starts_at": "2026-10-17T10:30:00+02:00",
      "ends_at": "2026-10-17T12:00:00+02:00",
      "party_size": 4,
      "customer": { "name": "Alex Jansen", "email": "alex@example.com", "phone": "+31600000000", "member_id": null },
      "participants": [],
      "source": "api",
      "external_reference": "club-booking-1042",
      "notes": null,
      "metadata": null,
      "payment_status": "paid",
      "amount_cents": 4000,
      "cancelled_at": null,
      "cancel_reason": null,
      "checked_in_at": null,
      "completed_at": null,
      "created_at": "2026-10-10T15:12:00+02:00",
      "updated_at": "2026-10-10T15:12:00+02:00"
    }
  ],
  "meta": { "current_page": 1, "per_page": 50, "total": 1, "last_page": 1 }
}

PUT/reservations/:id

Update a reservation

Reschedules or edits a pending or confirmed reservation; send only what changes. Ability reservations.store or reservations.manage. Any other status answers 409 not_editable.

Body

  • Name
    starts_at
    Type
    string
    Description

    New start (a slot on the grid, see Create a reservation). The new time is checked under a lock; the reservation's own old interval does not count as taken, so moving 30 minutes within an overlapping window works.

  • Name
    resource
    Type
    string
    Description

    Move to this resource. Without it the reservation stays on its current resource; when that resource is taken at the new time you get 409 slot_unavailable, even if another resource is free.

  • Name
    party_size
    Type
    integer
    Description

    At most max_party_size, and not below the number of participants.

  • Name
    customer
    Type
    object
    Description

    name, email, phone, member_id. A member_id (or an e-mail address of a member) links the reservation to that member; an existing link is never removed through this call.

  • Name
    participants
    Type
    array
    Description

    Replaces the full list (max party_size).

  • Name
    notes, metadata, payment_status, amount_cents
    Type
    Description

    As in Create a reservation.

A request that changes nothing returns the reservation unchanged and sends no webhook. 404 for an unknown reservation or resource, 409 slot_unavailable when the new slot is taken (the reservation keeps its old time), 422 for validation errors.

Request

PUT
/reservations/:id
curl -X PUT https://api.walletapp.co/reservations/d4c3b2a1-9e8f-4d7c-b6a5-0f9e8d7c6b5a \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"starts_at":"2026-10-17T09:00:00+02:00","notes":"Moved to the morning"}'

Response

{
  "message": "Request successful",
  "data": {
    "id": "d4c3b2a1-9e8f-4d7c-b6a5-0f9e8d7c6b5a",
    "status": "confirmed",
    "resource": "3f6c1a2e-7b8d-4e9f-a0b1-c2d3e4f5a602",
    "starts_at": "2026-10-17T09:00:00+02:00",
    "ends_at": "2026-10-17T10:30:00+02:00",
    "notes": "Moved to the morning",
    "...": "the other fields of the reservation"
  }
}

409 not_editable

{
  "error": true,
  "code": "not_editable",
  "message": "Only pending or confirmed reservations can be changed (status is checked_in)."
}

POST/reservations/:id/cancel

Cancel a reservation

Cancels a pending or confirmed reservation; the slot becomes free right away. Ability reservations.store or reservations.manage.

Body

  • Name
    reason
    Type
    string
    Description

    Max 255 characters, returned as cancel_reason.

  • Name
    force
    Type
    boolean
    Description

    Cancel even when the cancellation cut-off has passed. Needs reservations.manage (otherwise 403 force_requires_manage). A forced cancellation is recorded as such.

When the service has a cancellation_cutoff_minutes and the start is closer than that, the call answers 422 cancellation_window_passed. Cancelling a reservation in any other status is 409 invalid_transition.

Request

POST
/reservations/:id/cancel
curl -X POST https://api.walletapp.co/reservations/d4c3b2a1-9e8f-4d7c-b6a5-0f9e8d7c6b5a/cancel \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"reason":"Rain"}'

Response

{
  "message": "Request successful",
  "data": {
    "id": "d4c3b2a1-9e8f-4d7c-b6a5-0f9e8d7c6b5a",
    "status": "cancelled",
    "cancelled_at": "2026-10-16T08:00:00+02:00",
    "cancel_reason": "Rain",
    "...": "the other fields of the reservation"
  }
}

422 cancellation_window_passed

{
  "error": true,
  "code": "cancellation_window_passed",
  "message": "This reservation can no longer be cancelled (cut-off is 1440 minutes before the start). Use force:true with reservations.manage to override.",
  "cancellation_cutoff_minutes": 1440
}

POST/reservations/:id/confirm

Confirm, check in, no-show and complete

Four status endpoints for the location, without a body. Ability reservations.manage. Each returns the updated reservation, or 409 invalid_transition when the current status does not allow it.

  • Name
    POST /reservations/:id/confirm
    Type
    Description

    pending to confirmed.

  • Name
    POST /reservations/:id/check-in
    Type
    Description

    confirmed to checked_in, sets checked_in_at.

  • Name
    POST /reservations/:id/no-show
    Type
    Description

    confirmed to no_show.

  • Name
    POST /reservations/:id/complete
    Type
    Description

    checked_in to completed, sets completed_at.

Request

POST
/reservations/:id/check-in
curl -X POST https://api.walletapp.co/reservations/d4c3b2a1-9e8f-4d7c-b6a5-0f9e8d7c6b5a/check-in \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json"

Response

{
  "message": "Request successful",
  "data": {
    "id": "d4c3b2a1-9e8f-4d7c-b6a5-0f9e8d7c6b5a",
    "status": "checked_in",
    "checked_in_at": "2026-10-17T08:55:00+02:00",
    "...": "the other fields of the reservation"
  }
}

POST/reservations/:id/decline

Decline a pending reservation

Refuses a reservation that is waiting for confirmation (pending to declined); the slot becomes free right away. Ability reservations.manage. Optional body field reason (string, at most 255 characters), kept in the reservation's history. Returns the updated reservation. Any other status answers 409 invalid_transition and nothing changes; 404 for an unknown reservation or one of another organization. A decline sends a reservation.updated webhook with status: declined and previous_status: pending.

Request

POST
/reservations/:id/decline
curl -X POST https://api.walletapp.co/reservations/d4c3b2a1-9e8f-4d7c-b6a5-0f9e8d7c6b5a/decline \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json" \
  -d reason="Court under maintenance"

Response

{
  "message": "Request successful",
  "data": {
    "id": "d4c3b2a1-9e8f-4d7c-b6a5-0f9e8d7c6b5a",
    "status": "declined",
    "...": "the other fields of the reservation"
  }
}

Webhooks

Subscribe to the events below with POST /webhook-subscriptions. Deliveries use the same envelope, signature and retries as all webhooks. A webhook is only sent after the change is committed: a failed, rolled back, duplicate (was_duplicate) or no-op request sends nothing.

  • Name
    reservation.created
    Type
    Description

    A reservation was created.

  • Name
    reservation.updated
    Type
    Description

    A reservation was rescheduled or edited (PUT), or confirmed, declined, checked in, marked as no-show or completed. A decline is a reservation.updated with status: declined.

  • Name
    reservation.cancelled
    Type
    Description

    A reservation was cancelled.

data carries ids and status only, no personal data, like every other Walnut webhook: never a customer or participant name, e-mail address, phone number, notes, metadata or cancel reason. Fetch the details with GET /reservations/:id when you need them.

  • Name
    id
    Type
    string
    Description

    The reservation UUID.

  • Name
    status
    Type
    string
    Description

    The status after the change.

  • Name
    service, resource, location
    Type
    string
    Description

    The service UUID, the resource UUID and the location identifier.

  • Name
    starts_at, ends_at
    Type
    string
    Description

    The booked interval (ISO 8601).

  • Name
    party_size
    Type
    integer
    Description

    Number of people.

  • Name
    external_reference
    Type
    string
    Description

    Your own booking id, if you sent one.

  • Name
    member_id
    Type
    string
    Description

    Member UUID of the customer (the booker), or null when not linked to a member.

  • Name
    participant_member_ids
    Type
    array
    Description

    Member UUIDs of the participants that are linked to a member.

  • Name
    cancelled_at, checked_in_at, completed_at
    Type
    string
    Description

    Only present once set.

  • Name
    event_id
    Type
    integer
    Description

    Id of the reservation change that caused the webhook. This is not the delivery id: de-duplicate deliveries on the envelope id (WLLT-Message-Id).

  • Name
    previous_status
    Type
    string
    Description

    The status before the change. For reservation.created and for an edit it equals status.

  • Name
    changed
    Type
    array
    Description

    Only for an edit with PUT: the names of the fields that changed (not their values), from starts_at, resource, party_size, customer.name, customer.email, customer.phone, customer.member_id, participants, notes, metadata, payment_status, amount_cents.

  • Name
    previous_starts_at
    Type
    string
    Description

    Only when the start changed: the old start.

  • Name
    previous_resource
    Type
    string
    Description

    Only when the resource changed: the old resource UUID.

reservation.updated (reschedule)

{
  "id": "5e4d3c2b-1a09-4f8e-9d7c-6b5a4f3e2d1c",
  "type": "reservation.updated",
  "created_at": "2026-10-11T09:20:00+02:00",
  "organization_id": "5b2f0c3e-1111-4222-8333-444455556666",
  "data": {
    "id": "d4c3b2a1-9e8f-4d7c-b6a5-0f9e8d7c6b5a",
    "status": "confirmed",
    "service": "8e2d4c6a-1b3f-4a5e-9c7d-0f1e2d3c4b5a",
    "resource": "3f6c1a2e-7b8d-4e9f-a0b1-c2d3e4f5a602",
    "location": "0c9e7b1a-4d2f-4a8e-9b3c-5e6f7a8b9c0d",
    "starts_at": "2026-10-17T09:00:00+02:00",
    "ends_at": "2026-10-17T10:30:00+02:00",
    "party_size": 4,
    "external_reference": "club-booking-1042",
    "member_id": null,
    "participant_member_ids": [
      "9a1c2d3e-4f50-4a6b-8c7d-0e1f2a3b4c5d",
      "4e5f6a7b-8c9d-4e0f-a1b2-c3d4e5f6a7b8"
    ],
    "event_id": 1873,
    "previous_status": "confirmed",
    "changed": ["starts_at", "notes"],
    "previous_starts_at": "2026-10-17T10:30:00+02:00"
  }
}

reservation.cancelled

{
  "id": "6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d",
  "type": "reservation.cancelled",
  "created_at": "2026-10-16T08:00:00+02:00",
  "organization_id": "5b2f0c3e-1111-4222-8333-444455556666",
  "data": {
    "id": "d4c3b2a1-9e8f-4d7c-b6a5-0f9e8d7c6b5a",
    "status": "cancelled",
    "service": "8e2d4c6a-1b3f-4a5e-9c7d-0f1e2d3c4b5a",
    "resource": "3f6c1a2e-7b8d-4e9f-a0b1-c2d3e4f5a602",
    "location": "0c9e7b1a-4d2f-4a8e-9b3c-5e6f7a8b9c0d",
    "starts_at": "2026-10-17T10:30:00+02:00",
    "ends_at": "2026-10-17T12:00:00+02:00",
    "party_size": 4,
    "external_reference": "club-booking-1042",
    "member_id": null,
    "participant_member_ids": [],
    "cancelled_at": "2026-10-16T08:00:00+02:00",
    "event_id": 1902,
    "previous_status": "confirmed"
  }
}