Ir al contenido

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.

  • 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 de A-Z a-z 0-9 . _ -); menciónalo cuando contactes con soporte.

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"
}
}
  • code es estable y pensado para programas: decide en función de él, no de title ni de detail (que son para personas, en inglés, y pueden cambiar de redacción).
  • request_id enlaza con nuestros registros.
  • error es 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).

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: el next_cursor de la página anterior;
  • la respuesta es {"items": [...], "next_cursor": "…"}; en la última página next_cursor es null.

No construyas ni modifiques cursores. Las colecciones pequeñas y acotadas (planes, versiones de PHP) se devuelven completas.

Obtener todo con curl y jq:

Ventana de terminal
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" ] || break
done

La CLI hace lo mismo con --all (pluspanel accounts list --all -o json).

Los filtros son parámetros de consulta, documentados para cada operación en la referencia. Por ejemplo, el registro de auditoría:

Ventana de terminal
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"

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, 408 y 429 no se guardan, así que puedes reintentarlas con la misma clave. Tampoco se guarda una respuesta de más de 1 MiB (su repetición recibe 409 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.
Ventana de terminal
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))
done
  • Cada GET que responde con un cuerpo JSON lleva un ETag fuerte. Devuélvelo en If-None-Match y un recurso sin cambios responde 304 Not Modified, sin cuerpo.
  • Un PUT o PATCH respeta If-Match: si el recurso cambió desde que lo leíste (su GET actual, leído con tu propia credencial y tus permisos, tiene otro ETag), la respuesta es 412 precondition_failed y no se cambia nada. Lee de nuevo, vuelve a aplicar tu cambio y reintenta. Un PUT o PATCH cuya ruta no tiene GET responde 412 cuando se envía If-Match. La CLI la envía con --if-match.
  • Las operaciones que lo admiten están marcadas con x-pluspanel-etag: true en la referencia.
Ventana de terminal
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.json

Es 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.

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: 600
RateLimit-Remaining: 598
RateLimit-Reset: 1
RateLimit-Policy: 600;w=60

Cuando 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.