Skip to content
StippaStippa
FeaturesPricingDemoFAQContact
Log inGet started

Getting started

  • Introduction
  • Authentication
  • Permissions

Core concepts

  • Errors
  • Pagination
  • Idempotency
  • Rate limits

Resources

  • Appointments
  • Customers
  • Services
  • Staff
  • Availability
  • Products
  • Payments
  • Webhooks

Recipes

  • Recipe: book from your own website
  • Recipe: sync the diary into a spreadsheet or BI tool
  • Recipe: a Slack message on every new booking

Reference

  • Changelog
  • OpenAPI 3.1 document

Services

What the business sells time for, the category tree it sits in, and why the detail differs from the list.

Last updated 1 August 2026

A service is something the business sells time for: a haircut, a colour treatment, a consultation. It carries the duration a booking of it occupies and the price it costs, and it is the first thing you read in any booking flow because everything else needs its id.

Read-only over this API, permanently. A service is priced and configured by the business, and a leaked key that could reprice a salon's whole menu is a much worse outcome than one that can read it.

MethodEndpointScopePlan
GET/api/v1/services

List services

Query: limitcursorcategoryIdisActive

services:readGroei
GET/api/v1/services/{id}

Retrieve a service

services:readGroei
GET/api/v1/service-categories

List service categories

Query: limitcursor

services:readGroei

What you get back

{
  "id": "cb13e0f4-d4fc-406e-9dae-caa0a1d4ab2f",
  "name": "Snor + baard",
  "description": null,
  "durationMinutes": 20,
  "priceCents": 1500,
  "isActive": true,
  "categoryId": "d86bca73-2bdd-43e8-acc8-04be8815fc7f",
  "paymentRequired": false,
  "depositCents": null,
  "imageUrl": null,
  "segments": [],
  "createdAt": "2026-07-29T17:57:46.236Z",
  "updatedAt": "2026-07-29T17:57:46.236Z"
}

Money is in cents, as an integer, always. 1500 is fifteen euros. There are no decimal amounts anywhere on this API, in either direction, because a price that round-trips through a float is a price that eventually charges somebody 14.999999 euros. currency on the resources that carry it is ISO 4217 in upper case.

durationMinutes is what a booking of this service occupies, and it is the reason POST /api/v1/appointments has no endTime field: the end is derived from this value at write time, so the two cannot disagree.

Inactive, deleted, and the difference

Three states, and only two of them are visible to you.

Inactive (isActive: false) means the business has taken the service off the menu without throwing it away. Those rows are in the list by default: isActive is a filter you apply, not one applied for you, so narrow it yourself when you are building a picker.

# What a customer may book now
curl "https://stippa.nl/api/v1/services?isActive=true&limit=100" \
  -H "Authorization: Bearer $STIPPA_KEY"

Deleted services are never returned at all, under any filter, and there is no parameter that reveals them. The gap this creates is worth knowing about because you cannot see it from a response: on the business we tested against, the services table held 43 rows and this endpoint returned 20. The other 23 were deleted, and nothing in the payload says so. hasMore was false and the page was complete, because "everything" means everything you may see.

That is only a problem if you are reconciling counts against something else. It does not break appointment resolution, because an appointment embeds its service rather than referencing it by id alone: an appointment on a service the business has since deleted still carries that service's name, duration and price in its own payload. Building a service map from this endpoint and looking ids up in it will still miss those, so read the appointment's embedded copy rather than your map.

An inactive service is not bookable, and GET /api/v1/availability/slots returns nothing for one.

Categories are a flat list you resolve yourself

categoryId is not expanded, because a category has no bearing on booking and expanding it on every row of a hundred-service menu is bytes nobody reads. GET /api/v1/service-categories returns the whole tree in one page:

{
  "id": "fc0f829e-25c2-45ca-aca5-2d4ab92dccdc",
  "name": "Verzorging",
  "parentId": null,
  "position": 3,
  "createdAt": "2026-07-29T17:57:46.232Z",
  "updatedAt": "2026-07-29T17:57:46.232Z"
}

Fetch it once, build a map, and resolve locally. parentId supports nesting and is usually null; position is the order the business arranged them in and is what you sort a menu by, not name.

💡

Services, categories and staff change a few times a year. Cache them for as long as your product can tolerate being wrong about a menu, and spend your rate-limit budget on availability instead, which changes constantly. See rate limits.

The detail endpoint returns more than the list

GET /api/v1/services/{id} is a different shape from a row of GET /api/v1/services: it adds the service's time-based pricing rules, which the collection omits.

That is the one place on this API where the item is not the list row, and it is a size decision. A business can price a service differently by day and time, so the rules are an array per service, and carrying them on every row of a list request would make the common case pay for a feature most businesses do not use. Fetch the detail when you need to quote a price for a specific time; use the list's priceCents for a menu.

priceCents on the list is the standard price. A booking's real price is snapshotted onto the appointment when it is created, which is what basePriceCents and totalPriceCents are on the appointments resource. Do not compute a total from a service price after the fact: the service may have been repriced since.

Segmented services

segments on a service is the template that produces the segments on an appointment. It is empty for most services and non-empty for the ones with a processing pause in the middle, like a colour treatment where the customer waits thirty minutes between two worked periods.

If it is non-empty, durationMinutes covers the whole span including the gap, and the staff member is genuinely free during that gap. Anything that draws a diary should read the appointment's own segments rather than assuming a solid block.

On this page

  • What you get back
  • Inactive, deleted, and the difference
  • Categories are a flat list you resolve yourself
  • The detail endpoint returns more than the list
  • Segmented services
StippaStippa

Appointment scheduling, without the hassle.

For whom

  • For salons
  • Beauty salons
  • Physiotherapists
  • Coaches
  • Personal trainers

Features

  • Booking widget
  • Online payments
  • Reminders
  • No-show prevention
  • Online calendar
  • Client management

Product

  • Features
  • Pricing
  • FAQ
  • Contact
  • System status

Legal

  • Privacy policy
  • Terms of service
  • Data processing agreement

© 2026 Stippa. All rights reserved.

De Rechter Software · Molenwater 20, 4511 BN Breskens · KvK 98466402 · btw NL005332100B80

······