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

Permissions

Every scope, what it reaches, what it costs, and the things no key can ever do.

Last updated 1 August 2026

A key holds a set of scopes. A scope is resource:action, and a request needs the scope its endpoint declares. There are two actions, read and write, plus one exception described below. There is no admin scope and there never will be.

This page exists so you can see the ceiling before you write code rather than discovering it from a 403 halfway through a build. It is generated from the same two records the request guard reads on every request, so it cannot fall out of step with what actually happens.

Two independent gates

A request passes only if both hold:

  1. The key was granted the scope. Fails with 403 INSUFFICIENT_SCOPE. Fixed by editing the key, which the business owner can do without involving anyone else.
  2. The plan entitles the scope. Fails with 403 PLAN_UPGRADE_REQUIRED. Fixed by changing plan, which is a conversation about money.

They are checked in that order and reported separately, because they send you to different people. GET /api/v1/me returns both sets so you never have to find out the hard way.

Scopes are not hierarchical. Holding appointments:write does not imply appointments:read; if you need both, ask for both. Implied scopes were considered and rejected, because a permission list you have to reason about is a permission list nobody audits.

Every scope

ScopeEntitled byRiskReaches
appointments
appointments:readGroei
feature_public_api
low
  • GET /api/v1/appointments
  • GET /api/v1/appointments/{id}
appointments:writewritesPro
feature_public_api_write
high
  • POST /api/v1/appointments
  • PATCH /api/v1/appointments/{id}
  • POST /api/v1/appointments/{id}/status
customers
customers:readGroei
feature_public_api
medium
  • GET /api/v1/customers
  • GET /api/v1/customers/{id}
customers:writewritesPro
feature_public_api_write
high
  • POST /api/v1/customers
  • PATCH /api/v1/customers/{id}
services
services:readGroei
feature_public_api
low
  • GET /api/v1/services
  • GET /api/v1/services/{id}
  • GET /api/v1/service-categories
staff
staff:readGroei
feature_public_api
low
  • GET /api/v1/staff
  • GET /api/v1/staff/{id}
availability
availability:readGroei
feature_public_api
low
  • GET /api/v1/availability/slots
  • GET /api/v1/availability/dates
products
products:readGroei
feature_public_api
low
  • GET /api/v1/products
  • GET /api/v1/product-categories
payments
payments:readGroei
feature_public_api
medium
  • GET /api/v1/payments
  • GET /api/v1/payments/{id}
webhooks
webhooks:managewritesPro
feature_webhooks
high
  • GET /api/v1/webhooks
  • POST /api/v1/webhooks
  • PATCH /api/v1/webhooks/{id}
  • DELETE /api/v1/webhooks/{id}
  • GET /api/v1/webhooks/{id}/deliveries
  • POST /api/v1/webhooks/{id}/deliveries/{deliveryId}/replay
ℹ️

The Entitled by column is the authoritative one. It is derived from the same record the request guard consults, so it is always right. The plan name beside it is a mirror of our pricing table and would lag a repricing by however long it takes us to notice, which is why the machine readable extension in the OpenAPI document is x-stippa-required-feature and not just x-stippa-required-plan.

webhooks:manage is one scope covering both directions

Every other scope splits reading from writing. This one does not: it lists, creates, updates and deletes endpoints under a single right, because subscribe and unsubscribe are useless apart. A REST-hook consumer that can subscribe but not clean up after itself is a consumer that leaks subscriptions forever.

That makes it a mutating scope whose name does not end in :write, which is worth knowing if you are generating anything from the scope list. Read the endpoints, not the name.

Its risk rating is high for a reason worth stating plainly: a leaked key holding it can point a new endpoint at any address and stream every future appointment, customer and payment, including names, emails and phone numbers, to that address silently until somebody opens the Webhooks screen.

What no key can ever do

Each of the following is a decision rather than an omission. There is no scope for them, no plan that unlocks them, and no roadmap item to add one.

Creating, listing, rotating or revoking API keys

The escalation path that turns a leaked key into a permanent takeover. A key must never be able to widen itself, extend its own life, or mint a sibling, so key management is session-only and owner/admin-only.

Billing and subscriptions

Owner-only even in the dashboard. Money and plan changes need a human who can see the price.

Team members and invitations

Adding an org member grants access to everything. An integration that can invite users is an integration that can grant itself a session.

Modules

A key that can enable modules can enable the module that grants itself more surface, and can silently change what the business is billed for.

Business settings: slug, profile, payment provider, retention policy

These mutations are audited under the security category precisely because they change the security surface or a delivery target. Changing the slug breaks every public link; changing the payment provider redirects money.

Custom domains

Adding one calls our hosting provider. An API-reachable path to it is an API-reachable path to our hosting account.

Two-factor enrolment and recovery codes

A credential that can disable the second factor protecting the account it belongs to is not a second factor.

Refunds and marking a payment paid by hand

Both move real money and are built to run from an authenticated route with a person behind it. This is also why payments are read-only and always will be.

Customer deletion and GDPR anonymisation

Erasure is irreversible and legally consequential, so it keeps its existing audited human path.

Audit logs

Reading the audit trail through the same credential whose actions it records is a poor property. The dashboard also filters the trail by the reader role, and a key has no role to filter by.

The platform administration surface

Not tenant-facing at all. It operates across every business on the platform, so no per-business credential could be scoped to a meaningful subset of it.

Impersonation

The request guard never reads the impersonation cookie. Combining a machine credential with impersonation would produce an actor nobody can attribute.

The pattern behind most of these is the same: a credential must not be able to widen itself, outlive itself, move money, or grant a session. Everything in that class stays behind a logged-in human.

Roles do not apply

An API key has no role. Owner, admin and employee are properties of a person's session, and there is no person here. A key's rights are exactly its scopes intersected with what the plan entitles. The operations reserved for an owner in the dashboard are on the deny list under What no key can ever do: no scope reaches them, so there is no role to ask for.

For the same reason a key never carries an impersonation. The request guard does not read the impersonation cookie at all, so there is no combination of session state and credential that produces an actor nobody can attribute.

On this page

  • Two independent gates
  • Every scope
  • webhooks:manage is one scope covering both directions
  • What no key can ever do
  • Roles do not apply
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

······