What you can build
The API is meant for club admins who know a little programming and want to save themselves recurring work. Some ideas:
- Show today's free courts or the next club events on your club website.
- Export the member list to a spreadsheet or your accounting tool every night.
- Create the court blockings for the whole team championship from a list of dates.
- Post the bookings of the day to a display in the clubhouse.
- Publish a news item automatically when your league results come in.
There is no separate "public API" with fewer features: a key uses the same endpoints as the Clubnetz app itself.
Create an API key
- Log in to Clubnetz as an admin of your club.
- Open Admin → API keys in the menu.
- Choose New key, give it a name that says what it is for, and pick how long it is valid.
- Decide what it may do: Read only for exports and displays, Read and write if it should change something.
- Copy the key. It starts with
cnz_and is shown only once.
Use one key per script or tool. Then you can see which one was used last and revoke a single key without breaking the others.
Your first request
Send the key in the Authorization header. This lists the courts of your club:
curl -H "Authorization: Bearer cnz_YOUR_KEY" \
https://my.clubnetz.app/api/Clubs/12/Courts
The answer is JSON:
{
"courts": [
{ "courtId": 1, "name": "Court 1", "alias": "1", "sortOrder": 1, "inactive": null },
{ "courtId": 2, "name": "Court 2", "alias": "2", "sortOrder": 2, "inactive": null }
]
}
The address of your club
Every address starts with https://my.clubnetz.app/api/Clubs/clubId/. The number of your club is in the address bar when you use the app (/clubs/12/my-club), and the API keys page shows a ready-made example with it.
If your club runs its own installation of Clubnetz, replace my.clubnetz.app with your own address.
How a key works
- It acts as you
- A key can do what the admin who created it can do in the club. Changes made with it are recorded as made by that person.
- It only works for one club
- A key is bound to the club it was created in. It can't be used for another club, your personal profile or anything outside
/api/Clubs/clubId/. - Read-only keys only read
- A read-only key may send
GETrequests. Everything else is answered with403. - It stops when you stop being admin
- If the person who created the key leaves the club or is no longer an admin, the key is rejected. Another admin then creates a new one.
- It can't manage keys or the Stripe account
- Keys are only created and revoked in the app by a logged-in admin, never with a key. The same goes for connecting and disconnecting the club's Stripe account.
- Every admin can revoke it
- Revoking takes effect immediately and can't be undone.
Data formats
| What | Format | Example |
|---|---|---|
| Requests and answers | JSON with camelCase names. Send Content-Type: application/json with a body. | { "courtId": 1 } |
| A day | Year, month, day | 2027-06-12 |
| A point in time | ISO 8601 with offset; answers are in UTC | 2027-06-12T16:00:00+00:00 |
| A time of day | Hours, minutes, seconds in the club's local time | 18:00:00 |
| Fixed choices | Text, not numbers | "Social", "Admin" |
| Files | Excel exports and attachments are returned as a download |
For bookings, pass the name of your time zone as timeZoneInfoId, for example Europe/Vienna.
Examples
The bookings of a week
curl -H "Authorization: Bearer cnz_YOUR_KEY" \
"https://my.clubnetz.app/api/Clubs/12/Bookings?dayFrom=2027-06-07&dayTo=2027-06-13"
Each booking has its court, its time (interval.from and interval.to), the play mode and the players.
The member list, page by page
curl -H "Authorization: Bearer cnz_YOUR_KEY" \
"https://my.clubnetz.app/api/Clubs/12/Members?Page=1&PageSize=50&EnablePaging=true"
The answer contains total and the members of the page in entities. You can filter with SearchTerm, Roles, InSeasonIds and more, see the reference below.
Create a club event
This needs a key with Read and write. The answer is the id of the new event.
curl -X POST \
-H "Authorization: Bearer cnz_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Summer party",
"category": "Social",
"startDate": "2027-06-12",
"startTime": "18:00:00",
"location": "Clubhouse",
"registrationEnabled": true,
"notifyMembers": false
}' \
https://my.clubnetz.app/api/Clubs/12/ClubEvents
With "notifyMembers": true all members get a push notification and an email about the new event. Leave it off while you are testing.
Python: export all members to a CSV file
import csv, os, requests
BASE = "https://my.clubnetz.app/api/Clubs/12"
HEADERS = {"Authorization": f"Bearer {os.environ['CLUBNETZ_API_KEY']}"}
members, page = [], 1
while True:
response = requests.get(f"{BASE}/Members", headers=HEADERS,
params={"Page": page, "PageSize": 100, "EnablePaging": "true"})
response.raise_for_status()
data = response.json()
members += data["entities"]
if len(members) >= data["total"] or not data["entities"]:
break
page += 1
with open("members.csv", "w", newline="", encoding="utf-8") as file:
writer = csv.writer(file)
writer.writerow(["First name", "Last name", "Email", "Roles"])
for m in members:
writer.writerow([m["firstName"], m["lastName"], m["email"], ", ".join(m["roles"])])
JavaScript (Node.js): the next club events
const base = "https://my.clubnetz.app/api/Clubs/12";
const headers = { Authorization: `Bearer ${process.env.CLUBNETZ_API_KEY}` };
const today = new Date().toISOString().slice(0, 10);
const inAMonth = new Date(Date.now() + 30 * 86400000).toISOString().slice(0, 10);
const response = await fetch(`${base}/ClubEvents?from=${today}&to=${inAMonth}`, { headers });
if (!response.ok) throw new Error(`Clubnetz answered ${response.status}`);
const { events } = await response.json();
for (const event of events) {
console.log(event.startDate, event.startTime ?? "all day", event.title);
}
Run such scripts on a server or your own computer. If you want to show something on your club website, let your server fetch the data and pass on only what visitors should see. Never put the key into the page itself.
Errors and limits
| Status | Meaning | What to do |
|---|---|---|
200 | It worked. | |
401 | The key is missing, wrong, expired, revoked, belongs to another club, or its creator is no longer an admin. | Check the key and the club number in the address. |
403 | The key is read-only (ApiKeyReadOnly), or you are not allowed to do this. | Use a key with read and write access. |
404 | What you asked for does not exist in your club. | Check the id. |
412 | The request breaks a rule of the club or contains invalid data. | Read errorCode and message in the answer. |
429 | Too many requests. | Wait for the number of seconds in the Retry-After header. |
An error that concerns your input looks like this:
{
"message": "A title is required.",
"errorCode": "ClubEventTitleRequired",
"errorDetail": null
}
Use errorCode in your code; the text of message may change.
Limit
Each key may send 120 requests per minute. That is plenty for exports and displays. If you load a lot of data, ask for larger pages instead of sending many small requests.
Keeping keys safe
- Treat a key like a password. Whoever has it can read the data of your members.
- Store it in an environment variable or a secrets store, not in the code and never in a public repository.
- Never put it into a website or a mobile app, where everybody can read it. The API also does not answer requests that a browser sends from another website.
- Prefer read-only keys and give keys an expiry date.
- If a key may have leaked, revoke it right away under Admin → API keys and create a new one.
Changes to the API
The app and the API are released together, so an endpoint can change with a new version of Clubnetz. Every answer carries the running version in the X-App-Version header. If your script matters to you, log that header and check your script after an update. The reference below always describes the version that is running right now.
Endpoint reference
All endpoints a key can call, loaded live from the OpenAPI document of Clubnetz. Paths are shown without the leading /api/Clubs/clubId.
You can import this file into tools such as Postman, Insomnia or Bruno, or generate a client for your programming language from it.