The Open API allows Third Party applications to access our data in a common and standardized manner. You can use this API to get customers, contracts and other information. We use OpenAPI 3.0. specifications for all our endpoints.
The Open API uses persistent key authentication, separate for every studio in . Keys are generated automatically when a studio owner requests an integration with your App. Upon activation, you will receive an email with OpenAPI studio keys and the base URL. In order to authenticate properly you have to send the OpenAPI key in x-api-key header. We will validate the key and check whether it is valid and matches the current studio.
If you have requested access to the Open API and webhook functionality we will send two API Keys upon activation of your integration. The API Keys are used for inbound requests to our endpoints and outbound requests of our webhook events.
All HEAD requests are used solely to verify the validity of the provided x-api-key. They do not trigger any business logic and do not check the existence of the requested resource. If the API key is valid, the response will be 200 OK. If the API key is invalid or missing, the response will be 401 Unauthorized.
All API requests must be made over HTTPS. The https://<tenant_name>.open-api.perfectgym.com/v1 URL is the request base URL. The complete URL varies depending on the accessed resource.
For example, to get a list of customers from a specific facility, you must make an HTTP GET request to the https://<tenant_name>.open-api.perfectgym.com/v1/customers URL.
The base URL, including the <tenant_name>, is part of the activation email and must always match the corresponding x-api-key. If you have registered webhooks you must look up the base URL by the x-api-key header you receive with every webhook event.
Please take into consideration that all URLs with an image to download will expire after a specified time. The exact expiration time can be found documented in the endpoint response field.
All Open API endpoints have a rate limit. Every endpoint has a separate request counter. The default rate limit is 1000 requests per minute. After exceeding the limit, every request for the next minute will be denied with a 429 response code.
The Open API implements an adaptive query throttling mechanism to ensure system stability during periods of high database load. When the database is under heavy load, certain endpoints may temporarily return a 503 Service Unavailable response. This is a protective measure to maintain overall system performance and data integrity.
If you receive a 503 response, we recommend implementing a retry strategy with exponential backoff. The system will automatically recover once the database load decreases.
Certain GET calls, especially bulk requests, may return a large number of records. To make handling large payloads easier, we provide a way to control the maximum number of responses through data chunking. There are two different ways this is achieved, explained below. But in short: If you use the returned offset value from the response in your next request, you should always get continuous results.
In the first example, theoffset reflects the exact id where the next chunk should start from. To fetch the next chunk use returned offset in succeeding request. Last chunk is indicated by hasNext: false property value. Example response:
{
"result": [
{
"id": 1210206595,
"firstName": "Eduardo",
"lastName": "Thomas"
},
{
"id": 1210206596,
"firstName": "Terrence",
"lastName": "Hernandez"
}
],
"offset": "1210206596",
"hasNext": true
} In this example, the next chunk would be requested by calling GET /endpoint?offset=1210206596
Theoffset parameter can also be calculated as sum of the givensliceSize value and the last given offset, reflecting an actual paginating of the results. For example, if your request contains the offset 0 and a sliceSize of 2, the returned offset will be 2. You may take this value for the next request (offset 2, sliceSize 2) to get the next chunk of results, with a returned offset of 4, and so on. Last chunk is indicated by hasNext: false property value. Example for the first chunk with a sliceSize of 2:
{
"result": [
{
"firstName": "Eduardo",
"lastName": "Thomas"
},
{
"firstName": "Terrence",
"lastName": "Hernandez"
}
],
"offset": "2",
"hasNext": true
}In this example, the next chunk would be requested by calling GET /endpoint?offset=2
The Open API uses HTTP Status codes to reflect a successful or unsuccessful request. 2XX status codes represent a successful request, 4XX/5XX status codes represent an error. If you receive an error status code, check the body for an error code and message. Table of error codes:
| Http Code | Reason |
|---|---|
| 400 | Validation of the request failed, check request with specification. |
| 401 | Authentication failed. Probably wrong or missing OpenAPI key. |
| 403 | Permission denied. The caller has no permission for the requested resource. |
| 404 | Requested entity not found. |
| 429 | Access denied - rate limit is exceeded. |
| 500 | Unexpected system error - internal problem. |
Some endpoints require a multipart/form-data request, meaning that the request body is a combination of json and binary data. In this case, the content type of the request must be set to multipart/form-data and the json data request part must be of type application/json. The binary data must be sent as a separate part of the request. Here is a curl example of a multipart/form-data request (In this example the json data request part is called 'data' and the binary data request part is called document):
curl --location 'https://open-api-demo.open-api.perfectgym.com/v1/memberships/1283046050/self-service/idle-periods' \
--header 'x-api-key: *****************************' \
--header 'Content-Type: multipart/form-data' \
--form 'data="{\"startDate\": \"2024-10-24\",\"temporalUnit\": \"MONTH\",\"termValue\": 1,\"reasonId\": 1}";type=application/json' \
--form 'document=@"/path/to/file´"'The Accept-Language header can be used to define the language that will be used for any (error-) messages in the responses. Currently these languages are supported by the :
cs(Czech)de(German)en(English)es(Spanish)fr(French)hu(Hungarian)it(Italian)nb(Norwegian)nl(Dutch)pl(Polish)ro(Romanian)ru(Russian)sl(Slovenian)sv(Swedish)tr(Turkish)
Any other language not listed above, that are provided by the client using this header, will fall back to en (English). If this header is not provided at all by the client, the language of the studio configured in the will be used. For some of the languages mentioned above the has country specific variants:
de-CH(Switzerland)de-LI(Liechtenstein)en-CA(Canada)en-GB(United Kingdom)en-US(USA)fr-LU(Luxembourg)
Any other country specific variants not listed above, that are provided by the client using this header, will fall back to the language. For example de-LU will result in de (German).
All date and time values in the Open API follow ISO-8601. There are three patterns you will encounter, depending on the field:
| Pattern | Example | Where it's used |
|---|---|---|
| Date only | 2025-12-31 | Contract start/end, cancellation date, date of birth, payment due date, studio opening/closing date, idle-period boundaries |
| Time only | 06:00:00 | Recurring weekly studio opening-hours windows |
| Date-time with zone | 2026-03-24T17:00:00+02:00 | Class slot start date time / class slot end date time, appointment slot start date time / appointment slot end date time, check-in date time / check-out date time, customer creation date time, online offer purchase date time / expiry date time, payment transaction date time, document creation date time, contract reversal date time etc. |
The API returns date-times in ISO-8601 without nanoseconds or time zone name (e.g. [Europe/Berlin]). We do apply summer and winter time changes.
2026-03-24T17:00:00Z (Z is short for +00:00)
2026-03-24T17:00:00+02:00Convert to your local timezone on the client side as needed.
Send full ISO-8601 strings with explicit zone information. Example values that are accepted:
2026-03-24T17:00:00Z
2026-03-24T18:00:00+01:00
2026-03-24T18:00:00+01:00[Europe/Berlin]
2026-03-24T18:00 (will use the default zone [Europe/Berlin])
2026-03-24T18:00:00 (will use the default zone [Europe/Berlin])
2026-03-24T18:00:00.123 (will use the default zone [Europe/Berlin])Fields typed as a calendar date (yyyy-MM-dd, for example startDate, endDate, dateOfBirth) do not carry timezone information on the wire.
These values are returned as plain calendar dates, so every caller receives the same date value regardless of the caller's own timezone.
When such a field is derived from a date-time on our side, the resulting calendar date depends on the business context of that field. In many cases this means the relevant organization unit's timezone is used, but the relevant timezone is determined by the underlying business semantics of the resource or operation, not by the caller's location and not by a single global rule for the entire API. Other fields are stored natively as calendar dates and are returned 1:1 without any timezone-based conversion.
Example: a contract signed at 2026-06-24 06:00 in a Berlin studio has startDate = 2026-06-24. A caller in US Pacific time receives the same 2026-06-24, even though at that instant the wall-clock in San Francisco was still 2026-06-23.
On our side, two storage paths commonly feed these fields:
- columns stored as a calendar date are returned 1:1 in the response,
- columns stored as a full date-time are converted to a calendar date using the timezone relevant to that business context before being returned.
Date-based business logic in the API is evaluated using the timezone that is relevant to the business context of the resource or operation being processed.
In many cases this is the timezone of the organization unit associated with the authenticated application. In other cases it may be the organization unit timezone associated with the customer, contract, appointment, or target facility involved in the operation. The correct timezone therefore depends on the business semantics of the specific workflow, rather than a single universal "API timezone".
This means evaluation does not depend on:
- which timezone your integration server runs in,
- the caller's own local timezone,
- what UTC time it currently is.
Examples:
- A "current member" query is evaluated using the organization-unit timezone relevant to that customer or membership context.
- Contract due dates, idle-period start dates and cancellation deadlines are evaluated using the organization-unit timezone relevant to the contract or customer context.
- Class booking windows (
earliestBookingDateTime,latestBookingDateTime) are evaluated in the timezone relevant to the appointment or facility context.