/eventsevents:readList events
Parámetros
| limit | query | integer | Page size. |
| starting_after | query | string | Cursor: the next_cursor of the previous page. |
Respuestas: 200 · 400 · 401 · 403 · 429
Una API REST sobre HTTPS con cuerpos JSON. Cada llave pertenece a una cuenta de organizador y solo ve los eventos, registros, check-ins y abstracts de esa cuenta.
https://ntl.events/api/v1Especificación OpenAPI 3.1curl https://ntl.events/api/v1/events \
-H "Authorization: Bearer ntl_live_…"curl https://ntl.events/api/v1/registrations \
-H "Authorization: Bearer ntl_live_…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: import-2027-000123" \
-d '{
"event_id": "8c3f…",
"registration_type_id": "1f0a…",
"buyer": { "name": "Ingrid Halvorsen", "email": "ingrid@example.org" },
"answers": { "organization": "Norwegian Defence Research Establishment" }
}'Manda Authorization: Bearer ntl_live_… (o X-API-Key). Una llave que falta, no es válida, venció o se revocó responde 401; una llave sin el permiso necesario responde 403. Las llaves se guardan como huella SHA-256: una llave perdida no se recupera, se revoca y se crea otra.
| Permiso | Permite |
|---|---|
| events:read | Leer eventos y tipos de registro |
| registrations:read | Leer registros y asistentes |
| registrations:write | Crear (importar) y cancelar registros |
| checkins:read | Leer check-ins |
| checkins:write | Registrar check-ins |
| abstracts:read | Leer abstracts |
| abstracts:write | Decidir abstracts |
| reports:read | Exportar reportes |
Las listas devuelven { object: "list", data, has_more, next_cursor }, de lo más nuevo a lo más viejo. Manda next_cursor como starting_after para la página siguiente. limit va de 1 a 100 (25 por omisión). El cursor es estable aunque entren registros nuevos mientras paginas.
curl "https://ntl.events/api/v1/registrations?event_id=8c3f…&limit=100&starting_after=eyJ0Ijoi…" \
-H "Authorization: Bearer ntl_live_…"Todos los errores tienen la misma forma. type es la familia estable contra la que se programa; code es el caso concreto; request_id le sirve a soporte para encontrar la petición.
HTTP/1.1 409 Conflict
{
"error": {
"type": "conflict_error",
"code": "sold_out",
"message": "Not enough capacity left in this registration type.",
"request_id": "req_3f9c…"
}
}Cada llave permite 120 peticiones por minuto. Toda respuesta trae X-RateLimit-Limit, X-RateLimit-Remaining y X-RateLimit-Reset; un 429 trae Retry-After.
Manda el encabezado Idempotency-Key en los POST. Si la red falla y reintentas con la misma clave, recibes la respuesta original (con Idempotent-Replayed: true) en vez de un registro o check-in duplicado. Reusar la clave con otro cuerpo responde 422. Las claves se guardan 24 horas.
Montos y fechas. Los montos son enteros en unidades menores (centavos) con su moneda ISO 4217. Las fechas van en ISO 8601, UTC.
Registra en el panel una URL HTTPS y los eventos que te interesan. Cada entrega es un POST firmado con el mismo objeto que devuelve la API para ese recurso. Responde 2xx en menos de 10 segundos. Si falla, se reintenta a los 1 min, 5 min, 30 min, 2 h, 6 h, 12 h, 24 h — 8 intentos en total — y cualquier entrega se puede reintentar a mano desde el panel.
registration.created — Registro confirmadoregistration.cancelled — Registro cancelado o reembolsadopayment.completed — Pago completadocheckin.created — Check-in registradoabstract.decided — Decisión sobre un abstractNTL-Signature: t=<segundos unix>,v1=<hex> donde v1 = HMAC-SHA256(tu secreto whsec_, "<t>.<cuerpo crudo>"). Compara en tiempo constante y rechaza si la marca de tiempo difiere más de 5 minutos de tu reloj. Usa el cuerpo crudo, antes de interpretar el JSON.
// Node.js
import crypto from "node:crypto";
export function verifyNtlSignature(secret, header, rawBody, toleranceSeconds = 300) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const t = Number(parts.t);
if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
const expected = Buffer.from(crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex"), "hex");
const received = Buffer.from(parts.v1 ?? "", "hex");
return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}POST /your/endpoint
NTL-Signature: t=1789200000,v1=5f2b…
NTL-Event-Type: registration.created
NTL-Event-Id: evt_9a1c…
{
"id": "evt_9a1c…",
"object": "event",
"type": "registration.created",
"created": 1789200000,
"data": { "object": { "id": "…", "object": "registration", "status": "confirmed", "attendees": [ … ] } }
}La referencia está en inglés y se genera del mismo documento OpenAPI que sirve la API.
/eventsevents:readParámetros
| limit | query | integer | Page size. |
| starting_after | query | string | Cursor: the next_cursor of the previous page. |
Respuestas: 200 · 400 · 401 · 403 · 429
/events/{id}events:readParámetros
| id | path · obligatorio | string (uuid) | Event id. |
Respuestas: 200 · 401 · 403 · 404 · 429
/events/{id}/registration-typesevents:readParámetros
| id | path · obligatorio | string (uuid) | Event id. |
Respuestas: 200 · 401 · 403 · 404 · 429
/registrationsregistrations:readParámetros
| event_id | query | string (uuid) | Only this event. |
| status | query | pending | confirmed | failed | refunded | cancelled | |
| query | string (email) | Buyer email (exact, case-insensitive). | |
| limit | query | integer | Page size. |
| starting_after | query | string | Cursor: the next_cursor of the previous page. |
Respuestas: 200 · 400 · 401 · 403 · 429
/registrationsregistrations:writeCreates a confirmed registration without charging, e.g. to migrate registrations from another platform or add guests. Capacity is reserved atomically: it can never oversell. Paid registrations go through the NTL Events checkout (Stripe). Fires registration.created.
Parámetros
| Idempotency-Key | header | string | Retry-safe key. The first response is replayed for 24 h; reusing the key with a different body returns 422. |
Cuerpo
| event_id | obligatorio | string (uuid) | |
| registration_type_id | obligatorio | string (uuid) | |
| buyer | obligatorio | object | |
| quantity | integer | ||
| attendees | array | One per ticket; defaults to the buyer. | |
| answers | object | ||
| language | en | es |
Respuestas: 201 · 400 · 401 · 403 · 404 · 409 · 422 · 429
/registrations/{id}registrations:readParámetros
| id | path · obligatorio | string (uuid) | Registration id. |
Respuestas: 200 · 401 · 403 · 404 · 429
/registrations/{id}/cancelregistrations:writeVoids the attendees' tickets, releases the capacity and notifies the waitlist. Paid registrations return 409 refund_required: they are refunded from the dashboard so the money goes back through the payment processor. Fires registration.cancelled.
Parámetros
| id | path · obligatorio | string (uuid) | Registration id. |
| Idempotency-Key | header | string | Retry-safe key. The first response is replayed for 24 h; reusing the key with a different body returns 422. |
Respuestas: 200 · 401 · 403 · 404 · 409 · 422 · 429
/check-inscheckins:readParámetros
| event_id | query | string (uuid) | Only this event. |
| limit | query | integer | Page size. |
| starting_after | query | string | Cursor: the next_cursor of the previous page. |
Respuestas: 200 · 400 · 401 · 403 · 429
/check-inscheckins:writeBy qr_code (what a scanner reads) or attendee_id. Same rules as the NTL Events scanner: signed QR, confirmed registration, ticket for this date, and an atomic mark (two scanners cannot admit the same ticket). Fires checkin.created.
Parámetros
| Idempotency-Key | header | string | Retry-safe key. The first response is replayed for 24 h; reusing the key with a different body returns 422. |
Cuerpo
| qr_code | string | Ticket QR payload. | |
| attendee_id | string (uuid) | Attendee id. |
Respuestas: 201 · 400 · 401 · 403 · 404 · 409 · 422 · 429
/abstractsabstracts:readParámetros
| event_id | query | string (uuid) | Only this event. |
| status | query | submitted | in_review | accepted | rejected | |
| limit | query | integer | Page size. |
| starting_after | query | string | Cursor: the next_cursor of the previous page. |
Respuestas: 200 · 400 · 401 · 403 · 429
/abstracts/{id}abstracts:readParámetros
| id | path · obligatorio | string (uuid) | Abstract id. |
Respuestas: 200 · 401 · 403 · 404 · 429
/abstracts/{id}/decisionabstracts:writeThe note is shown to the author. Recorded in the account's access log. Fires abstract.decided.
Parámetros
| id | path · obligatorio | string (uuid) | Abstract id. |
| Idempotency-Key | header | string | Retry-safe key. The first response is replayed for 24 h; reusing the key with a different body returns 422. |
Cuerpo
| decision | obligatorio | accepted | rejected | in_review | |
| note | string | Visible to the author. |
Respuestas: 200 · 400 · 401 · 403 · 404 · 422 · 429
/reports/registrationsreports:readOne row per attendee with the registration data and each form answer as answer:<key>. CSV with BOM (opens in Excel), formula-safe cells.
Parámetros
| event_id | query · obligatorio | string (uuid) | |
| format | query | csv | json |
Respuestas: 200 · 400 · 401 · 403 · 404 · 429
/reports/monthlyreports:readPer event and month: registrations, tickets by type, gross, NTL fee, processor fee, refunds, net and check-ins. Figures the processor could not provide are null with a reason.
Parámetros
| month | query | string | YYYY-MM. Defaults to the current month. |
Respuestas: 200 · 400 · 401 · 403 · 404 · 429
| id | string (uuid) |
| object | "event" |
| slug | string |
| title | string |
| starts_at | string | null (date-time) |
| ends_at | string | null (date-time) |
| venue | string | null |
| city | string | null |
| currency | string |
| status | string |
| visibility | string | null |
| requires_approval | boolean |
| created_at | string | null (date-time) |
| id | string (uuid) |
| object | "registration_type" |
| event_id | string |
| name | string |
| description | string | null |
| price | integer |
| currency | string |
| capacity | integer |
| sold | integer |
| available | integer |
| active | boolean |
| sales_start | string | null (date-time) |
| sales_end | string | null (date-time) |
| id | string (uuid) |
| object | "attendee" |
| name | string | null |
| string | null | |
| qr_code | string |
| seat | string | null |
| checked_in | boolean |
| checked_in_at | string | null (date-time) |
| voided | boolean |
| id | string (uuid) |
| object | "registration" |
| event_id | string |
| status | pending | confirmed | failed | refunded | cancelled |
| registration_type_id | string | null |
| registration_type_name | string | null |
| buyer | object |
| quantity | integer |
| amount_subtotal | integer |
| amount_total | integer |
| currency | string |
| answers | object |
| source | checkout | api |
| created_at | string | null (date-time) |
| confirmed_at | string | null (date-time) |
| cancelled_at | string | null (date-time) |
| attendees | array |
| id | string |
| object | "check_in" |
| attendee_id | string |
| registration_id | string |
| event_id | string |
| attendee | object |
| checked_in_at | string | null (date-time) |
| source | scanner | api |
| id | string (uuid) |
| object | "abstract" |
| event_id | string |
| code | string |
| title | string |
| abstract | string |
| keywords | string | null |
| authors | array |
| contact | object |
| file_name | string | null |
| status | submitted | in_review | accepted | rejected |
| decision_note | string | null |
| decided_at | string | null (date-time) |
| submitted_at | string | null (date-time) |
| id | string |
| object | "event" |
| type | registration.created | registration.cancelled | payment.completed | checkin.created | abstract.decided | ping |
| created | integer |
| data | object |