{
"error": {
"type": "invalid_request_error",
"code": "ticket_type_not_allowed",
"message": "Esta API key no tiene acceso al tipo de entrada indicado.",
"param": "ticket_type",
"request_id": "req_9f2c1a4e7b304d51"
}
}
| Campo | Uso |
|---|---|
type | Categoría general. Sirve para decidir si tiene sentido reintentar. |
code | Estable. Es sobre esto sobre lo que debes ramificar. |
message | Texto para humanos. Puede cambiar de redacción sin previo aviso. |
param | Campo de la petición que provocó el error, cuando aplica. |
request_id | Identificador de la petición. También viaja en X-Request-Id. |
Nunca ramifiques sobre
message. Cambiar una redacción no se considera un cambio
incompatible; cambiar un code, sí.Tipos y códigos HTTP
type | HTTP | ¿Reintentar? |
|---|---|---|
authentication_error | 401 | No. Arregla la clave. |
permission_error | 403 | No. Faltan permisos. |
invalid_request_error | 400 / 422 | No. Corrige la petición. |
not_found_error | 404 | No. |
conflict_error | 409 | Depende. Lee el code. |
rate_limit_error | 429 | Sí, tras retry-after. |
api_error | 500 | Sí, con espera creciente. |
Catálogo de códigos
Autenticación y permisos
| Código | HTTP | Significado |
|---|---|---|
missing_api_key | 401 | Falta la cabecera Authorization o está mal formada |
invalid_api_key | 401 | La clave no existe |
revoked_api_key | 401 | La clave fue revocada |
expired_api_key | 401 | La clave ha caducado |
key_pending_activation | 403 | Pendiente de activación manual |
ip_not_allowed | 403 | Origen no autorizado |
insufficient_scope | 403 | Falta el permiso que exige el endpoint |
Petición
| Código | HTTP | Significado |
|---|---|---|
validation_error | 400 | El cuerpo o los parámetros no cumplen el esquema |
invalid_limit | 400 | limit fuera de rango (1–200) |
invalid_cursor | 400 | starting_after no es un cursor válido |
invalid_date | 400 | Una fecha no es ISO 8601 |
invalid_id | 400 / 422 | Un identificador no tiene el formato esperado |
unknown_endpoint | 404 | La ruta no existe |
empty_batch | 400 | Se envió un lote sin ninguna entrada |
Ámbito de la clave
| Código | HTTP | Significado |
|---|---|---|
ticket_type_not_allowed | 422 | El tipo de entrada está fuera del ámbito de tu clave |
day_not_allowed | 422 | El día está fuera del ámbito de tu clave |
day_required | 422 | Tu clave está limitada a días concretos: indica cuál |
Recursos
| Código | HTTP | Significado |
|---|---|---|
ticket_not_found | 404 | No existe la entrada, o está fuera de tu ámbito |
wristband_not_found | 404 | No existe la pulsera, o está fuera de tu ámbito |
ticket_type_not_found | 422 | El tipo de entrada no existe |
day_not_found | 422 | El día no existe |
zone_not_found | 422 | La zona no existe |
Un recurso fuera del ámbito de tu clave responde
404, no 403. Es deliberado:
distinguirlos confirmaría que ese código de entrada existe, que es justo lo que
buscaría alguien enumerando códigos.Conflictos
| Código | HTTP | Significado |
|---|---|---|
resource_already_exists | 409 | Un código de entrada del lote ya existe |
ticket_already_claimed | 409 | La entrada ya se acreditó |
wristband_in_use | 409 | Esa pulsera ya está asignada a otro asistente |
idempotency_in_flight | 409 | La misma Idempotency-Key se está procesando ahora mismo |
api_key_device_missing | 409 | La clave no tiene dispositivo asociado: pide que la reactiven |
Escrituras
| Código | HTTP | Significado |
|---|---|---|
missing_idempotency_key | 400 | Falta la cabecera Idempotency-Key |
invalid_idempotency_key | 400 | La clave de idempotencia no tiene una longitud razonable |
idempotency_key_reused | 422 | Esa clave ya se usó con un cuerpo distinto |
checkin_failed | 422 | La acreditación no se pudo completar |
topup_failed | 422 | La pulsera no admite recargas (bloqueada, en revisión…) |
Otros
| Código | HTTP | Significado |
|---|---|---|
rate_limit_exceeded | 429 | Cupo agotado. Espera lo que diga retry-after. |
internal_error | 500 | Fallo del servidor. Reintenta y, si persiste, escribe citando el request_id. |
Reintentos recomendados
async function callVentry(path, options = {}, attempt = 0) {
const response = await fetch(`https://api.ventry.es/v1${path}`, {
...options,
headers: { Authorization: `Bearer ${process.env.VENTRY_KEY}`, ...options.headers },
});
if (response.ok) return response.json();
const { error } = await response.json();
// 429: el servidor dice exactamente cuánto esperar.
if (response.status === 429 && attempt < 5) {
const wait = Number(response.headers.get("retry-after") ?? 1) * 1000;
await new Promise((r) => setTimeout(r, wait));
return callVentry(path, options, attempt + 1);
}
// 5xx: espera creciente. Con Idempotency-Key, reintentar es seguro.
if (response.status >= 500 && attempt < 3) {
await new Promise((r) => setTimeout(r, 2 ** attempt * 1000));
return callVentry(path, options, attempt + 1);
}
// El resto son errores tuyos: reintentar da exactamente el mismo resultado.
throw new Error(`${error.code}: ${error.message} (${error.request_id})`);
}
import os, time, requests
BASE = "https://api.ventry.es/v1"
def call_ventry(method, path, attempt=0, **kwargs):
headers = {
"Authorization": f"Bearer {os.environ['VENTRY_KEY']}",
**kwargs.pop("headers", {}),
}
response = requests.request(method, f"{BASE}{path}", headers=headers, timeout=30, **kwargs)
if response.ok:
return response.json()
error = response.json()["error"]
# 429: el servidor dice exactamente cuánto esperar.
if response.status_code == 429 and attempt < 5:
time.sleep(int(response.headers.get("retry-after", 1)))
return call_ventry(method, path, attempt + 1, headers=headers, **kwargs)
# 5xx: espera creciente. Con Idempotency-Key, reintentar es seguro.
if response.status_code >= 500 and attempt < 3:
time.sleep(2 ** attempt)
return call_ventry(method, path, attempt + 1, headers=headers, **kwargs)
# El resto son errores tuyos: reintentar da exactamente el mismo resultado.
raise RuntimeError(f"{error['code']}: {error['message']} ({error['request_id']})")
<?php
function callVentry(string $method, string $path, array $options = [], int $attempt = 0): array
{
$ch = curl_init("https://api.ventry.es/v1$path");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => true,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_HTTPHEADER => array_merge(
["Authorization: Bearer " . getenv("VENTRY_KEY")],
$options["headers"] ?? []
),
] + (isset($options["body"]) ? [CURLOPT_POSTFIELDS => $options["body"]] : []));
$raw = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$rawHeaders = substr($raw, 0, $headerSize);
$body = json_decode(substr($raw, $headerSize), true);
curl_close($ch);
if ($status < 400) {
return $body;
}
// 429: el servidor dice exactamente cuánto esperar.
if ($status === 429 && $attempt < 5) {
preg_match('/retry-after:\s*(\d+)/i', $rawHeaders, $m);
sleep((int) ($m[1] ?? 1));
return callVentry($method, $path, $options, $attempt + 1);
}
// 5xx: espera creciente. Con Idempotency-Key, reintentar es seguro.
if ($status >= 500 && $attempt < 3) {
sleep(2 ** $attempt);
return callVentry($method, $path, $options, $attempt + 1);
}
// El resto son errores tuyos: reintentar da exactamente el mismo resultado.
throw new RuntimeException(sprintf(
"%s: %s (%s)",
$body["error"]["code"],
$body["error"]["message"],
$body["error"]["request_id"]
));
}