Autenticación

Claves de API, permisos, límites de peticiones, caché, errores e idempotencia en la API pública de Sway.

Cada petición lleva una clave de API como token Bearer:

curl https://www.sway.events/api/v1/artists \
  -H "Authorization: Bearer sway_sk_YOUR_KEY"

La clave identifica a tu crew. No hay ningún parámetro de tenant en toda la API: lo que una clave puede ver lo decide el crew al que pertenece, nunca la petición.


Dos tipos de clave

Clave secretaClave publicable
Prefijosway_sk_sway_pk_
Se ejecuta enTu servidorEl navegador de un visitante
PermisosCualquieraSolo las lecturas públicas: read:profile, read:artists, read:promoters, read:events, read:venues, read:content, read:shop
Dónde respondeEn cualquier sitioSolo en los sitios web listados en la clave (se comprueba contra el Origin del navegador)
Se envía comoAuthorization: Bearer ...Authorization: Bearer ..., o ?key= cuando una cabecera no es práctica

Usa una clave secreta para todo lo que hace tu servidor, y para cualquier cosa que escriba, venda entradas o toque a los miembros. Nunca la envíes a un navegador ni a una app: quien pueda leer tus páginas puede leer la clave.

Usa una clave publicable cuando un navegador tenga que llamar a la API directamente, por ejemplo un sitio estático sin servidor. Solo lee lo que Sway ya muestra públicamente, solo responde en sus propios sitios web, y recibe una cuota más pequeña por dirección de visitante, encima de la propia de la clave.

Una clave secreta enviada como ?key= se rechaza con 400 key_in_query, porque las URL acaban en logs e historiales del navegador. Si pasa, trata la clave como filtrada: rótala.

Ciclo de vida de una clave

Las claves se gestionan en Admin del crew → API (planes Studio y Roster, permiso Gestionar API).

AcciónQué pasa
CrearNombre, tipo, permisos, sitios web o direcciones de la tienda, y una caducidad (nunca, 30, 90 o 365 días). La clave completa se muestra una vez; después solo se ven su inicio y sus cuatro últimos caracteres
EditarCambia los permisos y las listas de direcciones; se aplica al instante
SuspenderEl interruptor de emergencia. Toda petición responde 403 key_suspended hasta que alguien hace clic en Reanudar. No se elimina nada
RotarPOST /v1/keys/rotate con una clave que tenga write:keys devuelve su sustituta: mismo nombre, tipo, permisos y límites. La clave antigua sigue funcionando durante grace_hours (de 1 a 168, 24 por defecto), y luego caduca. Una clave rota una vez, como mucho una vez por hora, y se avisa por email al propietario del crew cada vez
RevocarDefinitivo. Las peticiones responden 401 invalid_key. Cuando la clave se rotó, También revocar las claves que la sustituyeron revoca toda la cadena: úsalo cuando la clave se haya filtrado, ya que quien tenga una clave filtrada con write:keys podría haberla rotado
CaducarPasado expires_at, las peticiones responden 401 invalid_key

Una clave revocada, caducada o desconocida responden todas lo mismo, 401 invalid_key: la API nunca dice a quien llama si una clave que no conoce llegó a existir alguna vez. El panel Actividad de cada clave muestra lo que ha usado de sus límites y sus últimas llamadas.

Si la suscripción del crew caduca, todas las claves responden 403 subscription_required hasta que vuelva a estar activa.


Permisos

Los permisos se eligen al crear la clave y se pueden editar después. Una petición sin el permiso que exige su endpoint responde 403 missing_scope.

PermisoAbrePor defecto
read:profileEl perfil propio del crew: /v1/crew, /v1/crew/pagesSí
read:artistsEl roster, cada artista, sus eventos, calendarios y press kitsSí
read:promotersLos promotores que gestionas, sus eventos, salas y artistasSí
read:eventsEventos, line-ups, días, horarios, tipos de entrada, disponibilidad, comisiones, galería, preventasSí
read:venuesSalas, sus eventos y aforoSí
read:contentNoticias, partners, formularios y press kitsSí
read:ambassadorsLa página pública del programa de embajadores, su clasificación, sus recompensas y misiones, y la página propia de un miembro con sesión iniciadaNo
read:ambassador-membersLos miembros, sus movimientos y las estadísticas del programa. Datos personales: solo el propietario del crewNo
read:exportTodo el catálogo como descargas NDJSONNo
read:shopEl merch de las páginas que gestionas: catálogo, producto, lo que vende un evento; el cálculo y la comprobación de un carritoNo
write:bookingsEnviar solicitudes de booking (POST /v1/booking-requests)No
write:newsletterAñadir altas a la newsletter al CRM de un promotor, y retirar una direcciónNo
write:formsEnviar respuestas a tus formularios públicosNo
write:checkoutAbrir checkouts de entradas de pago, y comprobar cupones y códigos de accesoNo
write:checkout:freeEmitir también pedidos de entradas gratis o con 100% de descuento. Solo el propietario del crewNo
write:shop:checkoutAbrir checkouts de merch para una página que gestionasNo
write:ambassadorsUnirse, iniciar y cerrar sesión, canjear una recompensa, editar sus propios datosNo
write:keysDejar que la clave se rote a sí misma (POST /v1/keys/rotate)No
mcp:accessUsar la clave con el servidor MCPNo

GET /v1/me, /v1/usage, /v1/audit-log y /v1/genres no necesitan ningún permiso: cualquier clave válida puede llamarlos.

Los permisos "solo el propietario del crew" solo puede concederlos el propietario del crew; un miembro con el permiso Gestionar API puede quitarlos pero no añadirlos. GET /v1/scopes devuelve esta lista en JSON.

Sitios web y direcciones de la tienda

En una clave viven dos listas de direcciones, las dos se configuran en Admin del crew → API:

  • Sitios web (claves publicables): los únicos orígenes de navegador en los que responde la clave. Una petición desde otro sitio responde 403 origin_not_allowed.
  • Direcciones de la tienda (claves con un permiso de embajadores): a dónde puede devolver Sway a un miembro tras un enlace de acceso por email. Tu web pasa la dirección de vuelta con la petición; Sway solo la acepta si su origen está en la lista, así el enlace nunca se puede desviar a otro sitio. Una clave con un permiso de embajadores y la lista vacía sigue sirviendo páginas, pero nunca podrá volver a iniciar la sesión de un miembro.

Solo se aceptan orígenes https, más http en localhost para el desarrollo. Las rutas se descartan: solo se guarda y compara el origen.


Límites de peticiones

La API está pensada para que una web de socio con mucho tráfico siga mostrando sus datos. Dos ideas lo sostienen:

  • Una respuesta en caché es gratis. Las lecturas se guardan en caché por crew. Repetir una petición que la caché ya tiene no cuesta nada, y un 304 Not Modified tampoco.
  • Se cuenta el trabajo, no las peticiones. Solo una petición que tiene que leer la base de datos gasta el presupuesto de trabajo de la clave, y lo gasta en proporción a lo que lee.
PlanTrabajo por minutoTrabajo por día
Studio12020.000
Roster300100.000

Lo que cuesta una petición sin caché:

PeticiónUnidades de trabajo
Un detalle (/v1/events/{id})1
Una página de lista1 por cada bloque empezado de 25 elementos (limit=100 cuesta 4)
Cada valor de expand+1
Una consulta por lotes (ids=)1 por cada bloque empezado de 10 ids

Cuando se acaba el presupuesto, una lectura que la caché ya tiene se sigue respondiendo, desde la última copia, con Cache-Status: sway; hit; ...; detail=rate-limited. Solo una lectura sin ninguna copia responde 429 rate_limited, con Retry-After.

Otras cuotas, por clave salvo que se diga lo contrario:

CuotaValor
Peticiones, en caché o no2.400 por minuto en Studio, 6.000 en Roster (o 20 veces el trabajo por minuto de la clave, lo que sea mayor)
Datos de respuesta5 GB al día en Studio, 20 GB en Roster
Todas las claves del crew juntas600 de trabajo por minuto y 120.000 al día en Studio; 1.500 y 500.000 en Roster. Otras tantas escrituras
Escrituras (POST), salvo el checkoutTantas como el trabajo por minuto de la clave, nunca menos de 60
Sesiones de checkout600 por minuto, para la clave y para todas las claves del crew juntas, aparte de las demás escrituras
Comprobaciones de cupón y código de acceso300 por minuto, aparte de las escrituras
Mensajes MCP120 por minuto
Clave publicable, por dirección de visitante60 de trabajo y 600 peticiones por minuto

Toda respuesta autenticada informa del presupuesto de trabajo de la clave:

RateLimit-Policy: "burst";q=120;w=60, "daily";q=20000;w=86400
RateLimit: "burst";r=118;t=42, "daily";r=19873;t=51230
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 118
X-RateLimit-Reset: 1759525489

RateLimit-Policy y RateLimit siguen el draft de la IETF; las cabeceras X-RateLimit-* muestran la más estricta de las dos ventanas y se mantienen para los clientes existentes. Ante un 429, espera los segundos de Retry-After; una clave que sigue chocando con el límite recibe esperas más largas. Una clave que recorre ids con un patrón inusual queda limitada a respuestas en caché durante un tiempo.

Con If-None-Match y la caché, una web de socio que sirve a miles de visitantes al día se queda muy por debajo de estas cifras. La guía de integración muestra el patrón. Si necesitas más, contáctanos: los límites se ajustan por clave.

Caché

Cada lectura cacheable lleva su tiempo de vida y un ETag:

Cache-Control: private, max-age=60, stale-while-revalidate=120, stale-if-error=86400
ETag: "4f9a1c7e2b8d5f3a"
Cache-Status: sway; hit; ttl=42

Devuelve el ETag como If-None-Match: si no ha cambiado nada, la respuesta es 304 Not Modified, sin cuerpo y sin coste. Cache-Status dice de dónde viene una respuesta: hit (la caché), fwd=miss (lectura nueva), fwd=stale (renovada después de agotarse su tiempo de vida). Los tiempos de vida van de 30 segundos (tipos de entrada, disponibilidad) a una hora (géneros); la referencia de endpoints da el de cada uno. Las respuestas que llevan datos a nivel de clave o de miembro nunca se guardan en caché (Cache-Control: private, no-store).

Para saltarte la copia de Sway, envía Cache-Control: no-cache con una clave secreta: la petición se lee fresca (Cache-Status: sway; fwd=request; stored) y cuesta trabajo como cualquier petición sin caché. Esa es la petición que hay que hacer cuando un webhook dice que algo ha cambiado, ya que la copia puede ser anterior al cambio. Cuando se te acaba el presupuesto de trabajo, la copia te llega de todos modos, con detail=rate-limited. El Cache-Control de una clave publicable se ignora.


Errores

Todo error es un cuerpo RFC 9457 application/problem+json con un code estable sobre el que decidir, y el id de la petición:

{
  "type": "https://www.sway.events/en/docs/api/errors#missing_scope",
  "title": "Missing Scope",
  "status": 403,
  "detail": "This API key does not have the required scope.",
  "instance": "/api/v1/events/4521",
  "code": "missing_scope",
  "request_id": "0b7c2e4a-9f1d-4a3b-8c6e-5d2f1a9b7e3c"
}
codeEstadoSignificado
invalid_key401La clave está mal formada, es desconocida, fue revocada o caducó
key_in_query400Se envió una clave secreta como ?key=. Rótala
key_suspended403La clave está suspendida; un admin del crew puede reanudarla
subscription_required403El plan del crew ya no incluye la API
missing_scope403A la clave le falta el permiso del endpoint, o una clave publicable llamó a un endpoint que exige una clave secreta
origin_not_allowed403Se usó una clave publicable desde un sitio web que no está en su lista, o un navegador llamó al servidor MCP
not_found404El recurso no existe o está fuera de las páginas de tu crew
conflict409La misma Idempotency-Key todavía se está procesando, o la acción ya se hizo (una clave rota una vez)
validation_error400 / 422Un parámetro de consulta (400) o el cuerpo de la petición (422) no es válido
invalid_cursor400El cursor de paginación está mal formado o caducado
idempotency_mismatch422Esta Idempotency-Key se usó con otro cuerpo
password_required401Un press kit protegido necesita Presskit-Password
rate_limited429Se alcanzó un límite; mira Retry-After
internal_error500Algo falló de nuestro lado; se puede reintentar sin riesgo
upstream_error502Falló un servicio del que depende Sway (pagos, email); se puede reintentar sin riesgo

validation_error lleva además un array errors de { "param", "message" }. Los endpoints añaden sus propios códigos donde los necesitan (un tipo de entrada agotado, un código que no aplica); la referencia de endpoints los lista.

404 not_found vale tanto para "no existe" como para "no es tuyo": la API nunca confirma que exista un dato fuera de tu crew.

Toda respuesta, de éxito o de error, lleva X-Request-Id. Cítalo cuando nos contactes: encuentra la petición en nuestros logs. Puedes enviar tu propio X-Request-Id (de 8 a 128 letras, dígitos, ., _, : o -) para enlazar nuestros logs con los tuyos; cualquier otra cosa se sustituye por un id nuevo.


Idempotencia

Un reintento de red nunca debe reservar, dar de alta o reembolsar dos veces. Envía una cabecera Idempotency-Key en un POST, un valor único por operación (un UUID es ideal, de 1 a 255 caracteres ASCII visibles):

curl -X POST https://www.sway.events/api/v1/booking-requests \
  -H "Authorization: Bearer sway_sk_YOUR_KEY" \
  -H "Idempotency-Key: 5b8f3c1e-2a7d-4e9b-8c6f-1d3a5e7b9c2f" \
  -H "Content-Type: application/json" \
  -d '{ "artist_ids": [501], "event_name": "Warehouse Series #4", "contact_email": "[email protected]" }'
  • El mismo valor de Idempotency-Key con el mismo cuerpo, dentro de las 24 horas, recibe de vuelta la primera respuesta, sin repetir el trabajo.
  • El mismo valor con otro cuerpo responde 422 idempotency_mismatch.
  • Un reintento que llega mientras la primera petición todavía se está procesando responde 409 conflict: espera y reintenta.

Funciona en todos los POST salvo abrir un checkout, que se protege de duplicados por su cuenta, y las comprobaciones de cupón y código de acceso, que no cambian nada. Una respuesta que lleva un secreto (una clave rotada) nunca se guarda para repetirla.