For developers
Partner API
Show a Padli club's courts and free times on the club's own website, priced, with a link that opens the exact slot in Padli to book.
For developers building a club's own website. With it, the site can show the club's details, courts and free time slots, priced, and send a player to Padli to book the exact slot they picked. The player signs in, pays and gets their confirmation in Padli; the site never handles accounts or payments.
The API is read only.
Getting a key
The club creates the key in the Padli portal: Venue > Your website > Create key. It is shown once; the club sends it to you. If it is lost, the club deletes it and creates another.
When creating it, the club lists the website addresses that may use it, for example
padelpark.mk, dev.padelpark.mk and localhost:3000 for local development. Calls made
from a browser on any other address are refused. A key with no addresses works only from
a server.
The key belongs to one club. Every call answers for that club, so no club id appears in any URL.
Making a call
| Environment | Base URL |
|---|---|
| Production | https://play.padli.app/api/partner/v1 |
| Development | https://dev.play.padli.app/api/partner/v1 |
Send the key in the X-Padli-Key header on every request:
const res = await fetch('https://play.padli.app/api/partner/v1/availability?date=2026-10-20', {
headers: {
'X-Padli-Key': 'padli_pk_…',
'Accept-Language': 'mk', // optional: en, mk, sq, sr or tr
},
})
const day = await res.json()
Browser calls work directly (CORS is answered for the addresses on the key). Never send cookies: the API does not use them.
Accept-Language picks the language of the club's own text where the club has a
translation (today, its description). Everything else in a response is data, not text.
Conventions
- Times are local to the club.
timezonenames the zone (Europe/Skopje). Every slot carries bothstart("18:00") andstarts_at, the same moment as a full ISO 8601 timestamp with its offset ("2026-10-20T18:00:00+02:00"). Usestarts_atfor any date arithmetic. - Prices are decimal strings (
"2400.00") in the currency named once per response. Format them for display yourself; never parse them as binary floats for money math. - Weekdays are lower-case English names:
"monday"…"sunday". - Empty is an empty array, never
null, for every list. - Fields are only ever added within v1. Ignore fields you do not know.
GET /venue
The club.
{
"uuid": "e02b0f0f-c0e2-41f7-84ac-704476251a14",
"name": "Padel Park",
"description": "Four indoor courts by the river.",
"address": "Bul. Partizanski Odredi 62",
"city": "Skopje",
"latitude": 41.99813,
"longitude": 21.42544,
"phone": "+38970123456",
"email": "hi@padelpark.mk",
"website": "https://padelpark.mk",
"timezone": "Europe/Skopje",
"photo_urls": ["https://cdn.padli.app/venues/…/1.jpg"],
"opening_hours": [
{ "day": "monday", "intervals": [{ "open": "07:00", "close": "12:00" }, { "open": "16:00", "close": "23:00" }] },
{ "day": "sunday", "intervals": [{ "open": "09:00", "close": "24:00" }] }
],
"courts": [
{
"uuid": "b897bb46-1f56-4cfc-a041-d910c184454d",
"name": "Court 1",
"sports": ["padel"],
"type": "indoor",
"surface": "blue",
"photo_urls": [],
"durations": [60, 90, 120]
}
],
"club_url": "https://padli.app/club/padel-park-skopje",
"book_url": "https://play.padli.app/venues/e02b0f0f-c0e2-41f7-84ac-704476251a14",
"rating": { "average": 4.6, "count": 38 },
"price_from": "1200.00",
"currency": "MKD",
"amenities": ["parking", "showers", "cafe_bar", "equipment_rental"],
"cancellation_policy": [
{ "minutes_before": 1440, "refund_percent": 100 },
{ "minutes_before": 360, "refund_percent": 50 }
],
"cancellation_grace_minutes": 60,
"open_matches_enabled": true
}
opening_hourslists only the days the club opens; a day may have several intervals."close": "24:00"means open until midnight.courts[].sportsis every sport the court can be booked for, its main one first.durationsare the booking lengths it sells, in minutes.club_urlis the club's public page on padli.app (null until it has one).book_urlopens the club in Padli, ready to pick a time.- Any contact or location field may be
nullwhen the club has not filled it in. ratingis the average of each player's latest rating of the club (1 to 5, one decimal) and how many players gave one. It isnulluntil the club has enough ratings for Padli to show it, so a new club never shows an average of two evenings.price_fromis the club's cheapest court price per hour, as it sells today (a promotion the club is running is included). It is the "from" price on the club's Padli page.price_fromandcurrencyare bothnulluntil the club has prices set.amenitiesare slugs, not text: translate them on your site. Today's slugs areparking,wifi,showers,changing_rooms,lockers,restrooms,cafe_bar,pro_shop,equipment_rental,lightingandaccessibility. New slugs may appear: skip one you do not know. The list is empty when the club has none.cancellation_policyis the club's refund schedule, longest notice first: a player who cancels at leastminutes_beforebefore the start getsrefund_percentof the court price back (Padli's service fee is not refunded). Cancelling later than the last tier refunds nothing, so an empty list means cancellations are not refunded. This is the club's default; a single court can have its own, which Padli shows the player when they book it.cancellation_grace_minutes: a booking cancelled within this many minutes of being made is refunded in full, service fee included, whatever the schedule says, as long as the match is still further off than the window itself.0means the club has this off.open_matches_enabled: whether players booking here can open their booking's free places to other players in Padli.
GET /availability
What can be booked on one day.
| Query | Required | Meaning |
|---|---|---|
date |
no | YYYY-MM-DD. Defaults to the club's today. Must be between today and the last day the club sells. |
sport |
no | A sport, e.g. padel. Only courts for that sport. An unknown sport returns no courts. |
duration |
no | Minutes, e.g. 90. Only that booking length. |
{
"date": "2026-10-20",
"timezone": "Europe/Skopje",
"currency": "MKD",
"courts": [
{
"uuid": "b897bb46-1f56-4cfc-a041-d910c184454d",
"name": "Court 1",
"sport": "padel",
"type": "indoor",
"surface": "blue",
"slots": [
{
"start": "18:00",
"starts_at": "2026-10-20T18:00:00+02:00",
"options": [
{
"duration": 60,
"end": "19:00",
"ends_at": "2026-10-20T19:00:00+02:00",
"price": "1600.00",
"price_standard": null,
"total": "1680.00",
"book_url": "https://play.padli.app/venues/e02b…?court=b897…&date=2026-10-20&duration=60&sport=padel&start=18%3A00"
},
{
"duration": 90,
"end": "19:30",
"ends_at": "2026-10-20T19:30:00+02:00",
"price": "2400.00",
"price_standard": "3000.00",
"total": "2520.00",
"book_url": "https://play.padli.app/venues/e02b…?court=b897…&date=2026-10-20&duration=90&sport=padel&start=18%3A00"
}
]
}
]
}
]
}
- Only bookable time is listed: free, priced and not already started. A court with
nothing left that day is still listed, with
"slots": [], so you can show it as fully booked. - Each slot is a start time; its
optionsare the lengths a booking from that start can have on that court. An option is only listed when the whole length is free. priceis the court price for the whole booking.price_standardis the price before a promotion the club is running, ornullwhen there is none: show it struck through.totalis what the player pays in Padli: the court price plus Padli's service fee.- A court that plays two sports appears once per sport, priced for that sport. Booking either one takes the court.
book_urlopens Padli on that exact slot. If the slot was taken in the meantime, Padli shows the day with what is still free.currencyisnullonly when nothing that day is bookable.
Availability changes every time someone books. Do not cache it for more than a minute or
two; do cache /venue, which changes rarely.
Errors
Errors have a status code and a body like { "success": false, "message": "<code>" }.
Validation errors add errors, keyed by query parameter.
| Status | message |
Meaning |
|---|---|---|
| 401 | partner.key_missing |
No X-Padli-Key header. |
| 401 | partner.key_invalid |
The key is wrong or was deleted. |
| 403 | partner.origin_not_allowed |
Called from a browser on an address the key does not list. Ask the club to add it. |
| 403 | partner.venue_unavailable |
The club is not currently live on Padli. |
| 422 | validation.failed |
A query parameter is wrong; see errors. For date: validation.date (not a valid date), booking.date_in_past, booking.beyond_horizon (the club does not sell that far ahead yet). |
| 429 | Too many requests from one address. Wait for Retry-After seconds. |
Limits are per visitor address (about 20 requests a second, short bursts allowed), so a site's visitors do not share one budget. A site calling from its own server shares one address for all its visitors: cache there.