# auth.md — Mi Padel

Cómo obtiene credenciales un programa (o un agente) para hablar con Mi Padel,
qué puede hacer con ellas y qué no.

Última revisión: 2026-09-11.

## Resumen en tres líneas

- La credencial para máquinas es la **clave de API de club**. Se puede usar
  directamente o cambiarla por un token de OAuth (`client_credentials`).
- **No hay registro automático.** La crea una persona: el administrador del club,
  desde su panel. No existe ningún endpoint que emita credenciales solo.
- Un agente **no puede actuar en nombre de un jugador**: no hay delegación de
  identidad. Ver [Lo que no existe](#lo-que-no-existe).

## Para quién es esto

Para la web de un club, su software de reservas o cualquier integración que
trabaje **en nombre de un club** que tiene el Plan Club activo.

No es para integraciones que quieran leer o escribir datos de un jugador
concreto: eso no se ofrece.

## Registro: cómo se consigue la credencial

El registro existe, pero lo aprueba una persona. Es deliberado.

**Endpoint de registro (consola, operada por una persona):**
`https://mi-padel.com/clubs/{slug}/panel` — donde `{slug}` es el identificador
público del club, el mismo que sale en su URL.

Pasos:

1. El club contrata el Plan Club ([precios](https://mi-padel.com/precios)).
2. Un administrador del club abre ese panel y crea una clave, poniéndole nombre
   («web del club», «software de reservas») y marcando los permisos.
3. La clave se enseña **una sola vez**. Después solo queda visible su prefijo
   (`mpk_9f3a1c02`), que sirve para identificarla, no para usarla.

No hay `POST /register` ni registro dinámico de clientes (RFC 7591). Emitir
credenciales sin una persona detrás significaría que quien encuentre la URL
puede pedir acceso a los datos de socios de un club, y ese riesgo no compensa el
ahorro de tres clics una vez en la vida de una integración.

¿Necesitas una integración y no eres el club? Escribe al club. ¿Eres el club y
algo no encaja? [Contacto](https://mi-padel.com/contacto).

### Lo mismo, para máquinas

```json
{
  "agent_auth": {
    "skill": "https://mi-padel.com/auth.md",
    "register_uri": "https://mi-padel.com/clubs/{slug}/panel",
    "audience": "Integraciones que actúan en nombre de un club (su web, su software de reservas).",
    "registration_methods": [
      {
        "method": "operator_provisioned",
        "human_approval_required": true,
        "register_uri": "https://mi-padel.com/clubs/{slug}/panel",
        "credential_type": "api_key",
        "credential_format": "mpk_<prefijo>_<secreto>",
        "grant_types_supported": ["client_credentials"],
        "token_endpoint": "https://mi-padel.com/api/oauth/token",
        "token_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post"],
        "authorization_server_metadata": "https://mi-padel.com/.well-known/oauth-authorization-server",
        "protected_resource_metadata": "https://mi-padel.com/.well-known/oauth-protected-resource",
        "bearer_methods_supported": ["header"],
        "authorization_header": "Authorization: Bearer mpk_<prefijo>_<secreto>",
        "scopes_supported": [
          "players_read",
          "matches_read",
          "tournaments_read",
          "bookings_read",
          "bookings_write"
        ],
        "revocation": {
          "self_service": true,
          "uri": "https://mi-padel.com/clubs/{slug}/panel",
          "effect": "inmediato"
        },
        "resource": "https://mi-padel.com",
        "api_base_url": "https://mi-padel.com/api/partner/v1",
        "service_desc": "https://mi-padel.com/openapi-clubes.json",
        "service_doc": "https://mi-padel.com/api-clubes"
      }
    ],
    "oauth": {
      "supported": true,
      "issuer": "https://mi-padel.com",
      "grant_types_supported": ["client_credentials"],
      "user_delegation_supported": false,
      "dynamic_client_registration_supported": false
    }
  }
}
```

## Cómo se usa

Hay dos formas. Las dos llegan al mismo sitio y respetan los mismos permisos.

### 1. La clave directa

```
Authorization: Bearer mpk_9f3a1c02_TU_CLAVE
```

Va bien para un script o una integración pequeña.

### 2. OAuth 2.0 `client_credentials` (recomendado)

Cambias la clave por un token de una hora y es el token el que viaja en las
peticiones siguientes. Es lo que sabe hacer cualquier librería de OAuth, así que
si usas una no tienes que programar nada especial.

```
POST https://mi-padel.com/api/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&client_id=mpk_9f3a1c02&client_secret=mpk_9f3a1c02_TU_CLAVE
```

También se aceptan las credenciales como `Authorization: Basic` (usuario = el
prefijo, contraseña = la clave entera). Respuesta:

```json
{
  "access_token": "eyJhbGciOi...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "players_read matches_read"
}
```

Y después, en cada llamada: `Authorization: Bearer eyJhbGciOi...`.

Con `scope` puedes pedir **menos** permisos de los que tiene la clave, que es
buena idea si la integración solo lee. Pedir uno que la clave no tenga devuelve
`invalid_scope`: no se recorta en silencio, porque una integración que cree
tener escritura y no la tenga falla más tarde y peor.

**El token no sobrevive a la revocación.** Si el club revoca la clave, sus
tokens dejan de valer en la siguiente petición aunque les quede vida.

Descubrimiento automático, si tu librería lo usa:

- `https://mi-padel.com/.well-known/oauth-authorization-server` (RFC 8414)
- `https://mi-padel.com/.well-known/oauth-protected-resource` (RFC 9728)

El identificador del recurso —y el `aud` de los tokens— es `https://mi-padel.com`.

### Vale para las dos formas

- Formato de la clave: `mpk_<prefijo>_<secreto>`. El prefijo es público; el
  secreto no se guarda en claro en ningún sitio, ni siquiera en nuestra base de
  datos (solo su SHA-256).
- **La clave va en tu servidor, nunca en el navegador.** Puesta en el JavaScript
  de la web del club, cualquiera que abra el inspector se lleva los datos de
  todos los socios. Si necesitas enseñar datos en una página pública, llama a la
  API desde tu backend y sirve tú el resultado. Lo mismo para el token: sale de
  la clave, así que sale de tu servidor.
- Una clave —y cualquier token suyo— solo ve los datos de su club. No hay forma
  de que alcance otro.

## Permisos

Cada clave lleva únicamente los que se le marcaron. 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 las reservas |
| `bookings_write` | Crear y cancelar reservas |

Los dos de reservas existen pero la función está apagada todavía: hoy esos
endpoints responden 404 y no aparecen en la especificación.

## Caducidad y revocación

- Una clave puede tener fecha de caducidad; también puede no tenerla.
- El club la revoca desde el mismo panel, con efecto inmediato. Revocar no borra
  el registro: queda el rastro de que existió y de cuándo se usó por última vez.
- El panel muestra el último uso de cada clave, para ver de un vistazo cuáles
  siguen vivas.
- Si sospechas que una clave se ha visto, revócala y crea otra. No hay rotación
  automática: son dos clics y preferimos que sea una decisión consciente.

## Qué pasa cuando algo falla

- `401` — falta la clave, está mal formada, no existe, está revocada o caducó.
  El mensaje es el mismo en todos los casos a propósito: distinguirlos le diría
  a quien prueba claves cuál de ellas existe.
- `403` — la clave es válida pero no tiene el permiso que pide ese endpoint.
- `404` — el endpoint está apagado (reservas) o el recurso no es de tu club.

## Lo que no existe

Se dice aquí para que nadie lo busque:

- **Del OAuth solo está `client_credentials`.** No hay `authorization_endpoint`,
  ni pantalla de consentimiento, ni `refresh_token`: no hacen falta cuando el
  token representa a un club y no a una persona. Tampoco se anuncian en los
  metadatos, porque anunciar un endpoint que devuelve 404 manda a quien integra
  a implementar un flujo que no puede terminar.
- **No hay registro dinámico de clientes** (RFC 7591) ni asertos de identidad de
  ningún tipo: nada con lo que un agente pueda demostrar de parte de quién viene,
  ni con correo verificado ni de forma anónima. Ninguno de los formatos que
  publican otros servicios para eso está soportado aquí.
- **Un agente no puede actuar en nombre de un jugador.** Apuntarse a un partido,
  meter un resultado o confirmarlo son acciones que hace la persona desde su
  sesión. El nivel de cada jugador sale de esos resultados, así que abrirlos a
  credenciales de máquina sin un modelo de delegación pensado es la vía rápida a
  una clasificación que no se parece a lo que pasó en la pista.

Si en algún momento hace falta lo contrario, se diseñará entonces y se publicará
aquí. Mientras tanto, este documento describe lo que hay.

## Dónde seguir

- [Documentación de la API de clubes](https://mi-padel.com/api-clubes)
- [Especificación OpenAPI](https://mi-padel.com/openapi-clubes.json)
- [Catálogo de APIs](https://mi-padel.com/.well-known/api-catalog) (RFC 9727)
- [Estado del servicio](https://mi-padel.com/api/health)
- [Seguridad](https://mi-padel.com/.well-known/security.txt) — para avisar de un fallo
