Changelog
Dated changes to /api/public/v1. Read
Versioning for what we may change without a version
bump and what we may not.
Every entry names the version string reported in X-Novex-API-Version at the
time of the change.
2026-10-05 — OTP codes resent once after a short pause on WhatsApp’s side
Version: 2026-08-04-preview
Added — automatic resend: when a
live OTP send fails with a temporary WhatsApp error (131042, 131000, 130429,
131016), Novex sends the same code once more about 90 seconds later, if the code still
has at least a minute to live. The message reads queued while it waits — a send response
can now return queued for this reason. One credit either way; if the resend fails too,
the message is failed and the credit comes back once. Other errors are never resent.
2026-10-05 — OTP credit returns for codes never confirmed delivered
Version: 2026-08-04-preview
Added — undelivered status: a live OTP
message with no delivered or read report 5 minutes after WhatsApp’s delivery window
closes (15 minutes after the send for login, 20 for pickup and delivery) now moves to
the final status undelivered, and its credit comes back once. This includes unconfirmed
sends, which used to stay charged. A delivery report that arrives later does not change the
status or charge again. A client that switches on status should treat undelivered like
failed.
2026-10-04 — OTP credit returns when WhatsApp reports a failure
Version: 2026-08-04-preview
Changed — OTP billing: a message that
WhatsApp accepted (sent) and later reported failed before delivery now gets its credit
back automatically, where it used to keep it. The credit shows in GET /otp/balance once
the report arrives; the balance in the send response, and an Idempotency-Key replay of
it, still describe the moment of the send. An unconfirmed send stays charged, and a send
WhatsApp refuses on the spot is refunded as before.
2026-10-03 — Test keys begin nvx_test_
Version: 2026-08-04-preview
Changed — test keys: a test key created from
now on begins nvx_test_. Test keys created earlier begin nvx_live_ and keep working,
unchanged. A nvx_test_ key is never accepted as a live key. The 401 that said sandbox
keys were reserved is gone: nvx_test_ keys are now issued.
2026-10-01 — OTP message types
Version: 2026-08-04-preview
Added — type on POST /otp/send: login (the
default, valid 10 minutes), pickup or delivery (valid 90 minutes). One credit per code
whatever the type. Echoed on the send response and on GET /otp/messages/{id}. A send that
leaves type out behaves exactly as before.
Added — error code 409 template_pending on POST /otp/send: the type’s WhatsApp message
is not available yet; nothing was sent or charged. The test key is never refused this way.
2026-09-30 — OTP messaging
Version: 2026-08-04-preview
Added — OTP messaging: POST /otp/send, POST /otp/verify,
GET /otp/messages/{id} and GET /otp/balance, under the new otp.send scope. Send one-time
passcodes over WhatsApp against prepaid credits, check them, and read delivery status.
Added — error codes 400 invalid_phone and 402 insufficient_credits on POST /otp/send,
and 409 not_verifiable on POST /otp/verify.
2026-09-26 — check-in admits confirmed bookings only
Version: 2026-08-04-preview
Changed — POST /registrations/{id}/check-in
now refuses WAITLISTED and NO_SHOW bookings as well as CANCELED and
REJECTED, with the same 409 REGISTRATION_NOT_CHECKABLE. Only a CONFIRMED
booking can be checked in.
Added — error.details.reason on that 409: canceled, rejected,
waitlisted or no_show.
Why. Every Novex check-in now applies one rule. Until this change the other
check-in paths refused nothing, so a cancelled attendee’s badge still got them in
at a kiosk; now the kiosk, the staffed scanner, the offline kiosk and this endpoint
all refuse the same bookings. To admit a waitlisted guest or a late no-show,
change the booking to CONFIRMED in Novex first, then retry with a new
Idempotency-Key.
Affects you if your integration checks in waitlisted or no-show attendees:
those calls now return 409 instead of 200.
2026-09-06 — accreditation decisions
Version: 2026-08-04-preview
Added — GET /registrations/{id}/accreditation.
The accreditation decision for one registration, and whether its credential
currently stands. Read-only; there is no list endpoint and no write.
Added — scope registration.accreditation.read. A new scope, deliberately
not covered by registration.read: a key you already hold gained nothing when
this shipped, and never will. Add it in Settings → API to a key that needs it.
The credential code is never returned by any read. It is a capability —
whoever holds it can present as the accredited person — so it is shown once, to
the organiser, at approval. If you are building a door, read
credential.active instead.
Read decision, not status. decision (APPROVED / DECLINED /
WITHDRAWN / REVOKED / null) is the outcome and its meaning is stable.
status is the raw row state, and a revocation overwrites it to WITHDRAWN —
so on status alone a revoked credential and an applicant’s withdrawal look
identical. This is why both fields exist.
Nothing changed for existing clients
No existing endpoint, field or error code was modified, and no existing key gained a capability.
2026-09-06 — creating registrations
Version: 2026-08-04-preview
Added — POST /registrations. Create
a registration for an event, through the same code path the public registration
form uses: capacity, waitlist, plan limits, duplicate detection and the
organiser’s custom fields all behave identically. Requires the new
registration.write scope and an Idempotency-Key.
Added — the registration.write scope. Existing keys do not gain it. Edit
the key in Settings → API to add it; a key that scans badges at a door should keep
registration.checkin alone and not be able to invent attendees.
Added — 202 Accepted. A create against a full event that has a waitlist
returns 202 with data.status = "WAITLISTED", not 201. If you branch on
“2xx means they have a seat”, change that now.
Added — 409 codes EVENT_FULL, DUPLICATE_REGISTRATION and
PLAN_LIMIT_REACHED, and a 409 code enum on the create operation in the
OpenAPI document. None of them are cleared by retrying with a new
Idempotency-Key.
No change to any existing endpoint.
2026-09-05 — the first write: attendee check-in
Version: 2026-08-04-preview
Added — POST /registrations/{id}/check-in.
Marks an attendee as arrived. No request body; it returns the registration in the
same shape GET /registrations/{id} does, so no second call is needed to see the
result.
This is the first write endpoint on the public API. The rules that come with
writes are on the new Writes page — an Idempotency-Key
is required, writes have their own 120/minute ceiling, and every write is audited.
Added — scope registration.checkin. Narrower than registration.write on
purpose: it permits checking attendees in and nothing else. Existing keys are
unaffected and do not gain it — add it deliberately in Settings → API.
Added — error code 409 REGISTRATION_NOT_CHECKABLE. Returned when the
booking is CANCELED or REJECTED. Unlike the other codes this one describes the
record’s state, not your request, so the same call becomes valid once the
booking is reinstated — retry with a new Idempotency-Key, because the
refusal is remembered against the old one. See Errors.
Documented — the write rate ceiling. 120 requests/minute per key, against 600/minute for reads. One ceiling on every plan that includes API access; this was the ceiling in force from the endpoint’s first day, now written down.
Nothing changed for existing clients
No read endpoint, field, or error code was modified. A client built before this date behaves exactly as it did — the only way to reach the new endpoint is to add the new scope to a key and call it.