Convenciones de la API
Estas reglas valen para todas las operaciones de /api/v1. La
referencia de la API documenta los parámetros, cuerpos y códigos de error de
cada operación.
Lo básico
Sección titulada «Lo básico»- URL base:
https://<tu panel>/api/v1. La API está en la dirección de tu panel (incluida la dirección propia del panel de una organización); no hay un host aparte para la API. - JSON de entrada y de salida (
Content-Type: application/json), UTF-8. Las fechas son RFC 3339 en UTC (2026-10-19T09:12:44Z) y los ids son UUID. - Solo HTTPS. Envía
Authorization: Bearer <clave>(autenticación). - Los miembros desconocidos en el cuerpo de una petición se rechazan (
422), para que una errata no pase inadvertida. - Cada respuesta lleva la cabecera
X-Request-ID(puedes enviar el tuyo, hasta 64 caracteres deA-Z a-z 0-9 . _ -); menciónalo cuando contactes con soporte.
Errores
Sección titulada «Errores»Los errores son «problem details» RFC 9457, con el tipo de
medio application/problem+json:
{ "type": "https://pluspanel.net/problems/domain_taken", "title": "Conflict", "status": 409, "detail": "shop.example.com is already used by another site on this server", "instance": "/api/v1/accounts/5a5a5a5a-1111-4222-8333-444455556666/sites", "code": "domain_taken", "request_id": "4b7c1e2a-9d3f-4e8a-b1c2-0f9e8d7c6b5a", "error": { "code": "domain_taken", "message": "shop.example.com is already used by another site on this server", "request_id": "4b7c1e2a-9d3f-4e8a-b1c2-0f9e8d7c6b5a" }}codees estable y pensado para programas: decide en función de él, no detitleni dedetail(que son para personas, en inglés, y pueden cambiar de redacción).request_idenlaza con nuestros registros.errores el miembro de error original de la API, conservado para que los clientes existentes sigan funcionando. Está obsoleto: el código nuevo debe leer los miembros del nivel superior. Se mantiene durante toda la v1.
Estados habituales: 400 petición mal formada, 401 credenciales ausentes o no válidas, 403 no
permitido (rol o scope), 404 no existe o no es visible para ti, 409 conflicto con el estado actual,
412 precondición fallida, 422 validación fallida, 429 límite de peticiones superado, 5xx error del
servidor (se puede reintentar con seguridad con el mismo Idempotency-Key).
Paginación
Sección titulada «Paginación»Las colecciones que pueden crecer sin límite se paginan con un cursor opaco:
limit: tamaño de página, 1–200 (por defecto 50);cursor: elnext_cursorde la página anterior;- la respuesta es
{"items": [...], "next_cursor": "…"}; en la última páginanext_cursoresnull.
No construyas ni modifiques cursores. Las colecciones pequeñas y acotadas (planes, versiones de PHP) se devuelven completas.
Obtener todo con curl y jq:
cursor=""while :; do page=$(curl -fsS -G "$PLUSPANEL_URL/api/v1/accounts" \ -H "Authorization: Bearer $PLUSPANEL_API_KEY" \ --data-urlencode "limit=200" ${cursor:+--data-urlencode "cursor=$cursor"}) jq -c '.items[]' <<<"$page" cursor=$(jq -r '.next_cursor // empty' <<<"$page") [ -n "$cursor" ] || breakdoneLa CLI hace lo mismo con --all (pluspanel accounts list --all -o json).
Filtros
Sección titulada «Filtros»Los filtros son parámetros de consulta, documentados para cada operación en la referencia. Por ejemplo, el registro de auditoría:
curl -fsS -G "$PLUSPANEL_URL/api/v1/audit" -H "Authorization: Bearer $PLUSPANEL_API_KEY" \ --data-urlencode "object_type=site" \ --data-urlencode "from=2026-10-01T00:00:00Z" --data-urlencode "to=2026-10-19T00:00:00Z"Idempotencia
Sección titulada «Idempotencia»Todo POST que modifica algo acepta la cabecera Idempotency-Key (de 1 a 255 caracteres ASCII
imprimibles; un UUID es una buena opción). Usa una clave nueva para cada operación nueva y la misma clave
cuando la reintentes.
- El resultado de la primera petición se guarda 24 horas, por clave y por credencial (la clave de API o, si no, el usuario).
- Un reintento con la misma clave, el mismo método, la misma ruta, la misma consulta y el mismo cuerpo
repite la
respuesta guardada, con la cabecera
Idempotent-Replayed: true. Nada se ejecuta dos veces. - La misma clave con una petición distinta recibe
422 idempotency_key_reused. - Un reintento mientras la primera petición sigue en curso recibe
409 idempotency_in_progress: espera y vuelve a intentarlo. - Las respuestas que contienen un secreto que se muestra una sola vez (una clave de API nueva, el
secreto de firma de un webhook, un enlace de invitación, códigos de recuperación…) nunca se guardan. Su
repetición recibe
409 idempotency_replay_unavailable: la primera petición tuvo éxito, pero el secreto no se muestra por segunda vez. - Las respuestas
5xx,408y429no se guardan, así que puedes reintentarlas con la misma clave. Tampoco se guarda una respuesta de más de 1 MiB (su repetición recibe409 idempotency_replay_unavailable). - Una clave mal formada recibe
400 idempotency_key_invalid. Los pasos de inicio de sesión y el device flow ignoran la cabecera. La CLI la envía con--idempotency-key.
key=$(uuidgen)for attempt in 1 2 3; do curl -fsS -X POST "$PLUSPANEL_URL/api/v1/accounts/$ACCOUNT_ID/sites" \ -H "Authorization: Bearer $PLUSPANEL_API_KEY" -H "Content-Type: application/json" \ -H "Idempotency-Key: $key" \ -d '{"primary_domain": "shop.example.com", "title": "Shop", "admin_email": "owner@example.com"}' \ && break sleep $((attempt * 5))doneETags y precondiciones
Sección titulada «ETags y precondiciones»- Cada
GETque responde con un cuerpo JSON lleva unETagfuerte. Devuélvelo enIf-None-Matchy un recurso sin cambios responde304 Not Modified, sin cuerpo. - Un
PUToPATCHrespetaIf-Match: si el recurso cambió desde que lo leíste (suGETactual, leído con tu propia credencial y tus permisos, tiene otro ETag), la respuesta es412 precondition_failedy no se cambia nada. Lee de nuevo, vuelve a aplicar tu cambio y reintenta. UnPUToPATCHcuya ruta no tieneGETresponde412cuando se envíaIf-Match. La CLI la envía con--if-match. - Las operaciones que lo admiten están marcadas con
x-pluspanel-etag: trueen la referencia.
etag=$(curl -fsS -D - -o site-security.json "$PLUSPANEL_URL/api/v1/sites/$SITE_ID/security" \ -H "Authorization: Bearer $PLUSPANEL_API_KEY" | awk 'tolower($1)=="etag:" {print $2}' | tr -d '\r')# … editas site-security.json …curl -fsS -X PUT "$PLUSPANEL_URL/api/v1/sites/$SITE_ID/security" \ -H "Authorization: Bearer $PLUSPANEL_API_KEY" -H "Content-Type: application/json" \ -H "If-Match: $etag" --data @site-security.jsonEs concurrencia optimista, no un bloqueo: evita que los clientes bien hechos sobrescriban los cambios de otros, pero dos escritores que compiten en el mismo instante pueden pasar los dos.
Límites de peticiones
Sección titulada «Límites de peticiones»Cada clave de API tiene un presupuesto de peticiones por minuto: 600 por defecto, en todas las
ediciones; a una clave se le puede dar un límite menor. El presupuesto se recarga de forma continua
(token bucket). Cada respuesta a una petición con clave te dice dónde estás (RateLimit-Reset es el
número de segundos hasta que el presupuesto vuelve a estar lleno):
RateLimit-Limit: 600RateLimit-Remaining: 598RateLimit-Reset: 1RateLimit-Policy: 600;w=60Cuando se agota el presupuesto, la respuesta es 429 rate_limited con Retry-After (en segundos).
Espera ese tiempo antes de reintentar y reparte el trabajo masivo en lugar de enviarlo en ráfagas.