API for apps
How app tokens use the REST API, which endpoints they can reach and which visibility rules apply
Apps use the same REST API as API keys: https://app.checkcourt.de/api/v1.
All endpoints, parameters and responses are in the
API reference. GET /api/v1/openapi returns the full
OpenAPI specification.
curl "https://app.checkcourt.de/api/v1/availability?date=2026-10-07" \
-H "Authorization: Bearer cca_…"How to get a token is covered in Authentication.
Which endpoints an app token can reach
An endpoint only ever checks its scope, not the type of token. So an app token can call any endpoint whose scope it holds, with these particularities:
Club app token (cca_) | Member token (ccu_) | |
|---|---|---|
| Behaves like | a management key: no person of its own, only the granted scopes | a personal key: acts as the member, with the member's current permissions |
/me | not available (401) | pseudonymous user_id and connected clubs, see Who am I |
/me/tenants | not available | clubs in which the connection is active, only id and name |
/me/bookings | not available, the app has no bookings of its own | read and create the member's own bookings (bookings:read) |
| Edit teams | not possible, this requires a person | if the member leads the team |
| Roles, webhook endpoints, API keys | never | never |
| Accounts: login email, password, delete member | never | never |
Apps can't reach any endpoint whose scope is blocked for apps: everything under
/roles, GET/PUT /members/{id}/roles, and creating, changing, testing and deleting
webhook endpoints. Reading (GET /webhooks) works with webhooks:read.
DELETE /members/{id} and changing the login email via PATCH /members/{id} reject app
tokens with 403, even if they hold members:delete or members:write.
Data and events of other apps
Club apps can attach values to bookings, courts and members, read values of other apps and publish events to them, once the club approved the connection. See Connections between apps.
Your own installation and settings
With the club app token (cca_), your app reads its installation, including the
settings the club filled in using the form generated from your settingsSchema. This
requires no scope.
curl "https://app.checkcourt.de/api/v1/app/installation" \
-H "Authorization: Bearer cca_…"{
"installation_id": "inst_…",
"app_id": "…",
"version": 3,
"status": "active",
"tenant_id": "…",
"scopes": ["bookings:read"],
"settings": { "lockId": "A1", "leadMinutes": 5 },
"installed_at": "2026-10-01T08:00:00.000Z"
}Query this endpoint after app.installed, app.upgraded and app.settings_updated.
The events themselves only contain IDs. Member tokens (ccu_) get 403: member
installations have no settings of their own. If your member app needs the club's
settings, your backend reads them with the club app token.
Who am I
With a member token (ccu_), GET /me tells your app who connected it, without any
personal data:
curl "https://app.checkcourt.de/api/v1/me" \
-H "Authorization: Bearer ccu_…"{
"user_id": "usr_k3j9x2m4q7w8e6r5t4z3u2i1o0",
"tenants": [{ "id": "…", "name": "TC Blau-Weiß" }]
}user_idis a pairwise pseudonym: stable for your app across clubs, reconnects and token rotation, and different for every other app. Use it as the key of the member's account in your app.- It is not the CheckCourt user ID that appears in API objects (for example
bookedBy), inmember.*webhooks and in extension context tokens, and it can't be turned into one. To recognize the member in webhooks and extensions of their own connection, match theinstallation_idfrom the token response (installations) instead. - Name and email are not part of the response. A "Sign in with CheckCourt" flow that can share them with consent is planned.
- API keys and club app tokens get
401.
Notify members
POST /api/v1/app/notifications with notifications:send sends a member a message that
CheckCourt delivers in the app and by email, without your app learning any contact data.
See Notifications.
Member count
GET /members/count returns { "total": 87 } with just members:read. It contains no
names or IDs, so you don't need members:read_confidential (the full directory in
GET /members) only to show how large the club is.
Apps never see more than the user interface
Every app token is subject to the visibility rules that members see in the booking
schedule, even if the app holds a confidential scope such as
bookings:read_confidential. CheckCourt enforces this centrally. The app doesn't need
to do anything for it and can't turn it off.
- Hidden bookers: If a member has hidden their name on a booking, or the booking
category doesn't show names, the name and ID of the booking (and cancelling) person
are
nullin the response. This affects, for example,GET /bookings,GET /courts/{id}and team practices inGET /teams/{id}. TherespectPrivacyparameter has no effect for apps, the rules always apply. - Own bookings: A member app sees its member's bookings in full, just like the member does.
- Hidden roles: Apps never see roles that a club hides in member lists. If an app
filters the member list by a hidden role, the API responds with
404 NOT_FOUND, as if the role didn't exist. - Admin views: Endpoints that are only reachable with an admin scope (for example
open fees with
guest_fees:read) return exactly what the admin interface shows with that permission.
Fetch the current state
Webhooks only contain IDs and times. Fetch details when you need them:
async function onBookingEvent(event) {
const token = await installationToken(event.installation_id);
const res = await fetch(
`https://app.checkcourt.de/api/v1/availability?date=${event.data.date}`,
{ headers: { Authorization: `Bearer ${token}` } },
);
const body = await res.json();
// current occupancy for the day, regardless of the order in which events arrived
}When planning, expect one such call per event, and keep the rate limits in mind.
Pagination
- Members (
GET /members): 1-basedpageparameter, 20 entries per page. - Fees (
GET /guest-fees):pageandpageSize(default 25, at most 100). - Delivery log (
GET /webhooks/{id}/deliveries): cursor. PassnextCursorfrom the previous response ascursoruntil it isnull.limit1 to 100, default 25. - All other lists return the complete set.
Successful responses are always JSON objects with named fields, never bare arrays.
Formats
- Calendar days are
YYYY-MM-DD, timestamps ISO 8601 in UTC. - Booking, lock and availability times come back as
HH:MM:SS, for example"startTime": "18:00:00"inGET /availability,GET /me/bookingsand the booking webhooks. Requests acceptHH:MMandHH:MM:SS. Cut the seconds off yourself if you show times to people.
Errors
Errors always have this format. Evaluate code. message is in German and may change:
{ "error": { "code": "FORBIDDEN", "message": "Fehlende Berechtigung: bookings:read" } }Here the message means "Missing permission: bookings:read".
| Code | HTTP | Typical cause for apps |
|---|---|---|
UNAUTHORIZED | 401 | Token missing, expired or revoked, the installation is paused or removed, the app is suspended, or a refresh token was sent as the bearer token |
FORBIDDEN | 403 | Scope missing, wrong X-Tenant-Id, the member no longer belongs to the club or is awaiting approval |
VALIDATION | 400 | Invalid input, or X-Tenant-Id missing for a member token with several clubs |
NOT_FOUND | 404 | Resource doesn't exist or isn't visible to the app |
BUSINESS_RULE | 422 | A business rule was violated, for example a booking policy |
RATE_LIMITED | 429 | More than 60 requests per minute from your IP address or more than 120 per minute with one token. Retry-After gives the wait time in seconds |
INTERNAL | 500 | Error at CheckCourt. Try again later |
If you get 401 for a fresh token, the club has probably paused or removed the app.
Don't fetch new tokens in a loop. Wait for app.uninstalled or check again later.
Activity log in the club
Everything an app changes appears in the club's activity log: for club apps as „Name der App (App)“, for member apps as „Anna Beispiel über Name der App“ (Anna Beispiel via app name). The club also sees a club app's latest actions directly on its page under Admin → Apps.