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

Errors

The error envelope, every status code this API returns, and what to do about each.

Last updated 1 August 2026

Every error, on every endpoint, has the same shape:

{
  "error": "Invalid request parameters",
  "code": "VALIDATION_ERROR",
  "fields": [
    { "path": "statuss", "message": "Not a supported parameter for this endpoint" }
  ]
}

error is a string, not an object. code sits beside it at the top level rather than inside it. That is worth reading twice, because the other arrangement is the more common convention and a client written against it will find undefined where it expected a code.

code is a stable identifier and is what your code branches on. error is a sentence written for a human reading a log: it may be reworded at any time, and it never echoes what you sent.

Three members appear only on the codes that carry them:

MemberAppears on
fieldsVALIDATION_ERROR. One entry per rejected parameter or body field, keyed path and not field
scopeINSUFFICIENT_SCOPE and PLAN_UPGRADE_REQUIRED. The scope that was needed
requiredPlanPLAN_UPGRADE_REQUIRED. The cheapest plan slug that entitles it
ℹ️

Do not parse this shape strictly. A code we add later may bring a member with it, and that is an additive change that ships without notice. Read the members you know and ignore the rest.

Statuses

StatusWhenRetry?
400The request is malformed or a value is invalidNo. Fix the request
401Missing, unknown, revoked or expired keyNo. See authentication
403The key lacks the scope, the plan does not entitle it, or the business lacks the moduleNo
404No such record for this businessNo
405Wrong method for this pathNo
409The current state conflicts with the requestOnly after resolving the conflict
422The request was fine and a business rule refused itOnly if the rule stops applying
429Rate limitedYes, after Retry-After
500Our faultYes, with backoff, if the request is safe to repeat
503The module is temporarily unavailableYes, with backoff

Codes

codeStatusWhat it means and what to do
VALIDATION_ERROR400Read fields. It names every rejected parameter or body field
IDEMPOTENCY_KEY_REQUIRED400A creating POST needs an Idempotency-Key header. See idempotency
UNAUTHORIZED401The credential is missing, malformed or unknown
KEY_REVOKED401Somebody revoked this key. Stop and alert a human
KEY_EXPIRED401The key passed its expiry date
INSUFFICIENT_SCOPE403The key was not granted this scope. The owner can edit the key
PLAN_UPGRADE_REQUIRED403The plan does not entitle this scope. The owner must upgrade
FORBIDDEN403The business does not have the module this endpoint belongs to
PAYMENTS_UNAVAILABLE403The business has no payment provider connected
NOT_FOUND404No such record for this business
METHOD_NOT_ALLOWED405Including OPTIONS, which this API answers with 405 by design
CONFLICT409The slot is taken, or a value that must be unique is not
IDEMPOTENCY_IN_PROGRESS409The same key is being processed right now. Wait and poll
IDEMPOTENCY_KEY_REUSED409Reused with a different body. A client bug, reported rather than hidden
RATE_LIMITED429Over quota. Wait for Retry-After seconds. See rate limits
INVALID_STATUS_TRANSITION422The appointment cannot go from its current status to that one
STAFF_NOT_BOOKABLE422That staff member is not available for online booking
PAYMENTS_UNAVAILABLE422Prepayment is required and the provider is not reachable
PAYMENT_ERROR500Prepayment is required and creating the checkout failed
MODULE_UNAVAILABLE503Temporarily switched off platform-wide. Back off and retry
INTERNAL_ERROR500Ours. Retry an idempotent request with backoff

The business's own booking rules

A business configures rules the widget enforces, and POST /api/v1/appointments enforces the same ones. Each refusal is a 422 carrying the rule's own code, so you can tell them apart:

codeMeans
BOOKING_TOO_SOONInside the notice period this business requires
BOOKING_TOO_FAR_AHEADFurther ahead than this business accepts bookings
BOOKING_REQUIRES_EXISTING_CUSTOMERThis business only takes online bookings from customers on file
BOOKING_PERIOD_RESTRICTEDNot accepting online bookings for that period
BOOKING_SLOT_NOT_CONNECTEDThat start time would leave an unbookable gap in the day

If you are creating an appointment on the business's behalf rather than on a customer's, for example from their own back-office tool, send enforceBookingRules: false in the body and none of these apply. That flag exists because a receptionist taking a booking by phone is not subject to the rules the public widget is.

The rule set is extensible, so a sixth code can appear. When it does, the message field carries a readable sentence naming it.

ℹ️

This list can grow. A new code is an additive change and ships without notice, so branch on the codes you handle and fall through to a generic branch for anything else. Do not write a switch that throws on an unknown code.

Two behaviours worth knowing about

A cross-tenant id is a 404, never a 403

Passing an id that belongs to another business returns 404 NOT_FOUND, exactly as a nonexistent id would. It is deliberate: a 403 would confirm that the record exists somewhere, which is a membership oracle over other businesses' data. You cannot tell the two cases apart, and neither can anyone probing.

fields names parameters you did not send

Query parsing is strict. A parameter this endpoint does not accept is a 400 naming it, rather than being ignored:

{
  "error": "Invalid request parameters",
  "code": "VALIDATION_ERROR",
  "fields": [
    { "path": "statuss", "message": "Not a supported parameter for this endpoint" }
  ]
}

A typo in a filter that is silently dropped gives you a full unfiltered list and no signal, and that is a worse failure than a 400 during development. Request bodies behave the same way.

An out-of-range value reads the same way, with the bound in the message:

{
  "error": "Invalid request parameters",
  "code": "VALIDATION_ERROR",
  "fields": [{ "path": "limit", "message": "Must be at most 100" }]
}

Retrying safely

GET requests are safe to retry freely. Use exponential backoff and honour Retry-After on a 429.

Creating POST requests are safe to retry because they require an Idempotency-Key. Retry with the same key and you either get the original result or a clear conflict, never a duplicate appointment. Retrying with a new key on a request that may have succeeded is how you double-book somebody.

PATCH and the status transition are naturally idempotent: setting a status to cancelled twice is one cancellation.

When you need to see what happened

The business owner has a Logboek screen in the product listing every request made with their keys: method, path, status, error code, duration and timestamp. If your integration is failing and the error is not telling you enough, that log is the shared view of reality between you and them. Webhook deliveries have an equivalent log with the full payload and every retry attempt.

On this page

  • Statuses
  • Codes
  • The business's own booking rules
  • Two behaviours worth knowing about
  • A cross-tenant id is a 404, never a 403
  • fields names parameters you did not send
  • Retrying safely
  • When you need to see what happened
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

······