CheckCourt
App platform

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 likea management key: no person of its own, only the granted scopesa personal key: acts as the member, with the member's current permissions
/menot available (401)pseudonymous user_id and connected clubs, see Who am I
/me/tenantsnot availableclubs in which the connection is active, only id and name
/me/bookingsnot available, the app has no bookings of its ownread and create the member's own bookings (bookings:read)
Edit teamsnot possible, this requires a personif the member leads the team
Roles, webhook endpoints, API keysnevernever
Accounts: login email, password, delete membernevernever

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_id is 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), in member.* 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 the installation_id from 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 null in the response. This affects, for example, GET /bookings, GET /courts/{id} and team practices in GET /teams/{id}. The respectPrivacy parameter 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-based page parameter, 20 entries per page.
  • Fees (GET /guest-fees): page and pageSize (default 25, at most 100).
  • Delivery log (GET /webhooks/{id}/deliveries): cursor. Pass nextCursor from the previous response as cursor until it is null. limit 1 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" in GET /availability, GET /me/bookings and the booking webhooks. Requests accept HH:MM and HH: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".

CodeHTTPTypical cause for apps
UNAUTHORIZED401Token missing, expired or revoked, the installation is paused or removed, the app is suspended, or a refresh token was sent as the bearer token
FORBIDDEN403Scope missing, wrong X-Tenant-Id, the member no longer belongs to the club or is awaiting approval
VALIDATION400Invalid input, or X-Tenant-Id missing for a member token with several clubs
NOT_FOUND404Resource doesn't exist or isn't visible to the app
BUSINESS_RULE422A business rule was violated, for example a booking policy
RATE_LIMITED429More 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
INTERNAL500Error 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.

On this page