Citeri Developers
Ayuda citeri.es

Empezar

Entrar con Citeri

«Entrar con Citeri» permite que tu aplicación actúe sobre un negocio de Citeri con el permiso de quien lo usa: la dueña, una empleada o el acceso compartido del salón. Es OAuth 2.1 con OpenID Connect: código de autorización, PKCE S256 y tokens de refresco con rotación.

Una aplicación nunca puede hacer más que la persona que la autorizó. Si una empleada no puede ver los teléfonos en el panel, tu app tampoco, aunque haya pedido clientas:telefono. Y si el negocio pausa a esa empleada o le quita un permiso, el cambio se aplica a la app al momento.

1. Crea la aplicación#

En el panel de tu negocio, ve a Ajustes → Desarrolladores → Nueva aplicación. Indica el nombre, las URLs de vuelta (redirect_uri, siempre https://; http://localhost solo para pruebas) y los permisos que podrá pedir.

TipoCuándoAutenticación en /oauth/token
ConfidencialTienes un servidor que guarda el secretoclient_secret_basic o client_secret_post (PKCE recomendado)
PúblicaWeb sin servidor, app móvil o de escritorionone + PKCE S256 obligatorio

El client_secret se enseña una sola vez al crearla. Si lo pierdes, genera uno nuevo desde el mismo apartado; el anterior deja de valer al momento.

2. Verificación#

Recién creada, la aplicación funciona solo en tu propio negocio. Para que otros negocios puedan conectarla, pulsa Pedir verificación (hace falta la web de la aplicación y su política de privacidad). Citeri revisa la app y, si la aprueba, aparece con el sello «Verificada por Citeri» en la pantalla de permisos.

Si después cambias el nombre, el logo, las URLs de vuelta o los permisos, la app pierde la verificación y hay que volver a pedirla.

Descubrimiento#

Documento / endpointURL
Servidor de autorización (RFC 8414)https://citeri.es/.well-known/oauth-authorization-server
OpenID Connecthttps://citeri.es/.well-known/openid-configuration
Autorizaciónhttps://citeri.es/oauth/authorize
Tokenhttps://citeri.es/oauth/token
Datos de la persona (userinfo)https://citeri.es/oauth/userinfo
Revocación (RFC 7009)https://citeri.es/oauth/revoke
Claves públicas (JWKS)https://citeri.es/oauth/jwks.json
curl https://citeri.es/.well-known/openid-configuration

Permisos (scopes)#

Pide solo lo que necesites: la persona ve en castellano cada permiso antes de aceptar. Tu app solo puede pedir los que marcaste al crearla.

ScopeQué permite
openidSaber quién eres (identificador de tu cuenta)
profileVer tu nombre y el del negocio
emailVer tu correo electrónico
offline_accessSeguir conectada aunque no estés usando la aplicación
citas:leerVer la agenda y las citas
citas:escribirCrear, cambiar y cancelar citas
clientas:leerVer las fichas de clientas
clientas:escribirCrear y modificar fichas de clientas
clientas:telefonoVer los teléfonos de las clientas sensible
catalogo:leerVer servicios, productos y precios
catalogo:escribirModificar servicios, productos y precios
cobrosCobrar y operar la caja
finanzasVer y gestionar facturas, ventas e importes sensible
informesVer informes y estadísticas
marketingGestionar marketing y mensajes
equipoGestionar el equipo y sus horarios
ajustesCambiar los ajustes del negocio sensible
mcpUsar Citeri desde asistentes de IA (MCP)
citeriAcceso completo al negocio legado

Pedir escritura no da lectura: si tu app crea citas y también las consulta, pide citas:leer citas:escribir. citeri existe solo por compatibilidad con integraciones antiguas; no lo uses en apps nuevas.

3. PKCE#

Genera un code_verifier aleatorio (43–128 caracteres) por cada intento de inicio de sesión y envía su huella SHA-256 en base64url como code_challenge. Solo se admite S256.

CODE_VERIFIER=$(openssl rand -base64 64 | tr -d '=+/\n' | cut -c1-64)
CODE_CHALLENGE=$(printf '%s' "$CODE_VERIFIER" | openssl dgst -sha256 -binary | openssl base64 -A | tr '+/' '-_' | tr -d '=')
STATE=$(openssl rand -hex 16)

4. Envía a la persona a autorizar#

GEThttps://citeri.es/oauth/authorize
https://citeri.es/oauth/authorize?response_type=code
  &client_id=citeri_XXXXXXXXXXXXXXXXXXXXXXXX
  &redirect_uri=https%3A%2F%2Fmiapp.es%2Foauth%2Fcallback
  &scope=openid%20profile%20email%20offline_access%20citas%3Aleer%20citas%3Aescribir
  &state=$STATE
  &code_challenge=$CODE_CHALLENGE
  &code_challenge_method=S256
  &nonce=$(openssl rand -hex 16)

Si la persona ya tiene la sesión del panel abierta, no vuelve a iniciar sesión. Si gestiona varios negocios, elige uno: el token queda ligado a ese negocio. Si ya autorizó antes los mismos permisos, no se le vuelven a preguntar.

Al volver, comprueba que state coincide y recoge el code (vale 10 minutos y un solo uso). Si la persona rechaza, recibes error=access_denied.

5. Cambia el código por tokens#

POSThttps://citeri.es/oauth/token
curl -X POST https://citeri.es/oauth/token \
  -u "citeri_XXXXXXXXXXXXXXXXXXXXXXXX:cs_TU_SECRETO" \
  -d grant_type=authorization_code \
  -d code=EL_CODIGO_RECIBIDO \
  -d redirect_uri=https://miapp.es/oauth/callback \
  -d code_verifier=$CODE_VERIFIER

En una app pública, quita -u y envía -d client_id=…. Respuesta:

{{
  "access_token": "…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "…",
  "scope": "openid profile email offline_access citas:leer citas:escribir",
  "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6…"
}}

El access_token dura 1 hora. El refresh_token (solo si pediste offline_access) dura 90 días. El id_token va firmado con RS256; verifícalo con las claves de /oauth/jwks.json (campo kid) y comprueba iss, aud y nonce.

6. Llama a la API#

curl https://citeri.es/oauth/userinfo \
  -H "Authorization: Bearer $ACCESS_TOKEN"

curl "https://citeri.es/api/mcp" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{{"jsonrpc":"2.0","id":1,"method":"tools/list"}}'

Si el token no cubre lo que pides, la respuesta es 403 con WWW-Authenticate: Bearer error="insufficient_scope". Puede pasar aunque el scope esté concedido, si la persona que autorizó ya no tiene ese permiso en el panel.

7. Renueva el token#

curl -X POST https://citeri.es/oauth/token \
  -u "citeri_XXXXXXXXXXXXXXXXXXXXXXXX:cs_TU_SECRETO" \
  -d grant_type=refresh_token \
  -d refresh_token=$REFRESH_TOKEN

Cada renovación devuelve un refresh_token nuevo y el anterior deja de valer. Guarda siempre el último: si se vuelve a usar uno ya cambiado, Citeri lo trata como un robo y revoca toda la conexión, y la persona tendrá que autorizar otra vez.

8. Desconectar#

curl -X POST https://citeri.es/oauth/revoke \
  -u "citeri_XXXXXXXXXXXXXXXXXXXXXXXX:cs_TU_SECRETO" \
  -d token=$REFRESH_TOKEN

El negocio también puede desconectar tu app cuando quiera desde Ajustes → Aplicaciones conectadas; a partir de ese momento tus tokens devuelven 401.

Errores frecuentes#

ErrorCausa
invalid_requestFalta un parámetro, o redirect_uri no es exactamente una de las registradas
invalid_clientclient_id o secreto incorrectos, o app suspendida
invalid_grantCódigo caducado o ya usado, code_verifier que no cuadra, o refresh revocado
invalid_scopePides un permiso que tu app no tiene marcado
access_deniedLa persona rechazó, o la app no está verificada y el negocio no es el suyo
insufficient_scope (403)El token no cubre esa operación