Developers

Public endpoints: services and availability

These two endpoints need no key. They return what anyone can already see on the business’s booking page, as JSON, so you can build your own treatment list or time picker. Both are rate limited per visitor; see limits.

You identify the business by its address name, the part after /book/ in its booking link. For https://app.aphrabooking.com/book/massage-bonaire that is massage-bonaire.

List services

GET /api/public/services?workspace=<address-name>
Query parameter Required Description
workspace yes The business’s address name. A previous address name or the business’s fixed id also work, so a renamed business does not break your integration.
locale no nl, en, de, es, fr or pt. Names and descriptions come back in that language where the business has a translation. Defaults to the business’s language.
category no Only services in this category (its address name).
curl "https://app.aphrabooking.com/api/public/services?workspace=massage-bonaire&locale=en"
{
  "workspace": {
    "slug": "massage-bonaire",
    "name": "Massage Bonaire",
    "tagline": "Relax by the sea",
    "currency": "USD",
    "brand_color": "#0F766E",
    "accent_color": null,
    "logo_url": "https://..."
  },
  "services": [
    {
      "id": "0d4f3b1e-6a2c-4f1a-9e7d-2b8c5a1f3e90",
      "slug": "deep-tissue",
      "name": "Deep tissue massage",
      "short_description": "Firm pressure for tight muscles.",
      "price_from": 65,
      "currency": "USD",
      "image_url": "https://...",
      "category": { "name": "Massage", "slug": "massage" },
      "booking_url": "https://app.aphrabooking.com/book/massage-bonaire/services/deep-tissue",
      "variants": [
        {
          "id": "9a7e2c41-3b5d-4e6f-8a1b-c2d3e4f5a6b7",
          "label": "60 min",
          "duration_minutes": 60,
          "price": 65,
          "address_required": false
        },
        {
          "id": "4c8d1e2f-7a3b-4c5d-9e6f-0a1b2c3d4e5f",
          "label": "90 min",
          "duration_minutes": 90,
          "price": 90,
          "address_required": false
        }
      ],
      "addons": [
        { "id": "b2e4f6a8-1c3d-4e5f-8a9b-0c1d2e3f4a5b", "name": "Hot stones", "price": 15 }
      ]
    }
  ]
}

Services come in the order the business set for its categories, then alphabetically. booking_url opens the booking page straight at that treatment, which is the simplest way to let a visitor book.

workspace.slug is the business’s current address name. Use it for free times, which do not accept old names.

Field Description
price_from The cheapest variant. null when a service has no active variant; such a service cannot be booked yet.
variants The lengths or versions a customer can choose, in the business’s order. You need a variant’s id to fetch free times and to book. Prices are before any off-peak discount.
variants[].address_required true when the treatment is given at the customer’s place. Send serviceAddress when you book.
addons Extras a customer can add to this service. Send their ids as addonIds when you book.

This response is cached, so a change the business makes can take a few minutes to show up.

Free times

GET /api/availability?workspace=<address-name>&service=<id>&variant=<id>&date=<YYYY-MM-DD>
Query parameter Required Description
workspace yes The business’s current address name, workspace.slug from the service list. Older names do not work here.
service yes Service id.
variant yes Variant id, from the service’s variants.
date yes The day, in the business’s time zone.
curl "https://app.aphrabooking.com/api/availability?workspace=massage-bonaire&service=0d4f...&variant=9a7e...&date=2026-11-20"

A day with free times:

{
  "slots": [
    "2026-11-20T13:00:00.000Z",
    "2026-11-20T13:30:00.000Z",
    "2026-11-20T14:00:00.000Z"
  ]
}

Each slot is a start time in UTC. Pass it unchanged as slotStart when you create a booking.

A day without free times tells you why, and when the next free day is:

{
  "slots": [],
  "openThisDay": false,
  "nextDate": "2026-11-21"
}

openThisDay: true means the business is open but fully booked. nextDate is a suggestion, not a promise: fetch that day to get its times. It is null when no free day turned up in the next 60 days.

Free times are not cached. Every call computes them fresh.

A free time is not a reservation. Someone else can take it before your customer confirms. Handle slot_just_taken and slot_unavailable when you book.