Cómo empezar
- Pide tu clave: el administrador del club la genera desde el panel.
- Guárdala en cuanto la veas. Solo se muestra una vez.
- Llama a
GET /api/partner/v1/clubpara comprobar que funciona.
Autenticación
Cabecera Authorization con la clave como bearer.
Nada de claves en la URL: acabarían en los registros de cualquier proxy
por el que pase la petición.
curl https://mi-padel.com/api/partner/v1/club \
-H "Authorization: Bearer mpk_9f3a1c02_TU_CLAVE"
OAuth 2.0, si lo prefieres
La misma clave sirve como cliente de OAuth
(client_credentials): la cambias por un token de una hora
y es el token el que viaja en las peticiones siguientes. Es lo que ya
sabe hacer cualquier librería de OAuth, así que si usas una no tienes
que programar nada especial.
curl -X POST https://mi-padel.com/api/oauth/token -d grant_type=client_credentials -d client_id=mpk_9f3a1c02 -d client_secret=mpk_9f3a1c02_TU_CLAVE
client_id es el prefijo y client_secret la
clave entera. Con scope puedes pedir menos
permisos de los que tiene la clave; pedir uno que no tenga devuelve
invalid_scope, no un recorte silencioso. Si el club revoca
la clave, sus tokens dejan de valer en la siguiente petición.
Los metadatos de descubrimiento están en oauth-authorization-server y oauth-protected-resource, y todo esto está explicado en auth.md.
La clave tiene esta forma:
mpk_9f3a1c02_dCJ3k... → mpk_9f3a1c02 parte pública, la ves en el panel
dCJ3k... secreto, solo lo tienes tú
Permisos
Cada clave lleva solo los permisos que le des. Da los mínimos: si la integración solo pinta la clasificación, no le des escritura.
| Permiso | Para qué |
|---|---|
players_read | Leer los socios del club |
matches_read | Leer los partidos |
tournaments_read | Leer los torneos |
bookings_read | Leer pistas y reservas |
bookings_write | Crear pistas y reservas, y cancelarlas |
Una clave sin el permiso recibe 403. Una clave inválida,
revocada o caducada recibe 401, siempre con el mismo
mensaje: no distinguimos entre esos casos a propósito.
Endpoints
Base: https://mi-padel.com/api/partner/v1
El club
GET /club
→ { "club": { "id", "slug", "name", "city", "timezone" },
"scopes": ["players_read", ...] }
Útil para comprobar la clave y ver qué permisos tiene. No hace falta ninguno.
Socios
GET /members?limit=50&cursor=<uuid> (players_read)
→ { "items": [{ "playerId", "displayName", "slug",
"level", "role", "status", "joinedAt" }],
"nextCursor": null }
level es el nivel de 0,50 a 7,00, o null
mientras sea provisional. No se devuelven datos de contacto
(ni correo, ni teléfono): esos datos ya los tiene el club por su cuenta,
y sacarlos por aquí convertiría una clave filtrada en una fuga.
Partidos y torneos
GET /matches?from=2026-09-01T00:00:00Z&to=2026-09-30T00:00:00Z (matches_read)
GET /tournaments?from=...&to=... (tournaments_read)
from y to son opcionales: por defecto son los
próximos 30 días. Sin ventana se devolvería el histórico entero del
club, que no le sirve a nadie y es caro para los dos lados.
Pistas y reservas
404: Mi Padel todavía
no reserva pistas. Están documentados aquí para que puedas preparar tu
integración con antelación. Si te interesa,
escríbenos y te avisamos al activarlos.
GET /courts (bookings_read)
POST /courts (bookings_write)
GET /bookings?from=&to=&courtId= (bookings_read)
POST /bookings (bookings_write)
DELETE /bookings/{id} (bookings_write)
Crear una reserva:
POST /bookings
{
"courtId": "…",
"startsAt": "2026-10-01T18:00:00Z",
"endsAt": "2026-10-01T19:30:00Z",
"playerId": "…", // opcional: socio de Mi Padel
"contactName": "Invitado", // opcional: si no hay socio
"externalRef": "reserva-4711",// opcional pero MUY recomendable
"notes": "Pista con luz"
}
Reintentar sin duplicar
Manda siempre externalRef con el identificador de la
reserva en tu sistema. Si repites la petición (un timeout, un reintento
de tu cola), no se crea una segunda reserva: se te devuelve la que ya
existe con un 200 en vez de un 201. Así
reintentar es seguro.
Dos reservas a la vez
El solape lo impide la base de datos, no una comprobación previa: dos
peticiones simultáneas para la misma pista y hora no pueden ganar las
dos. La que pierde recibe 409. Los tramos son
[inicio, fin), así que 10:00–11:00 y 11:00–12:00 conviven
sin problema.
Cancelar (DELETE) marca la reserva como cancelada y libera
el hueco; no borra el registro.
Errores
Todos los errores siguen el formato problem details
(RFC 9457), con Content-Type: application/problem+json:
{ "type": "about:blank", "title": "Conflict", "status": 409,
"detail": "Esa pista ya esta reservada en ese horario" }
| Código | Qué ha pasado |
|---|---|
400 | Datos inválidos (fechas al revés, campos que faltan) |
401 | Clave ausente, inválida, revocada o caducada |
403 | La clave es válida pero no tiene ese permiso |
404 | No existe, o no es de tu club, o la función está apagada |
409 | Conflicto: solape de reserva, nombre de pista repetido |
429 | Demasiadas peticiones: espera y reintenta |
Un 404 en un recurso de otro club es deliberado: no
confirmamos que exista algo que no es tuyo.
Buenas prácticas
- Una clave por integración, con su nombre. Así puedes revocar la de la web sin tumbar la del software de reservas.
- Permisos mínimos. Ampliar es fácil; recuperarse de una fuga, no.
- Revócala en cuanto sospeches. Es inmediato: la siguiente petición ya recibe
401. - Guarda la clave donde guardas las demás credenciales (variables de entorno, gestor de secretos). Nunca en el repositorio.
- Página con cursor (
nextCursor) en vez de pedir límites enormes. - Trata el
429con una espera creciente en vez de reintentar en bucle.
¿Dudas?
Escríbenos desde el formulario de contacto contando qué quieres integrar. Si encuentras un problema de seguridad, está el /.well-known/security.txt.