Reference

API documentation

A REST API for reading your queues and bookings, adding people to a queue from your own website or till system, and receiving webhooks when anything changes.

Available on the Business plan. Create a key in Account settings → Integrations.

Authentication

Every request needs an API key in the Authorization header. Keys start qj_live_ and are shown once when created — we store only a hash, so a lost key must be revoked and replaced rather than recovered.

curl https://queuejoin.com/api/v1/me \
-H "Authorization: Bearer qj_live_xxxxxxxxxxxx"

Keys carry scopes:

  • readRead queues, bookings, customers and locations
  • writeCreate and update queue entries and bookings

Treat a key like a password. It authenticates as your whole organisation, so never put one in browser JavaScript, a mobile app, or a public repository.

Conventions

  • Base URL: https://queuejoin.com/api/v1. All requests must use HTTPS.
  • Successful responses return { "data": … }. Lists also return meta with the limit and offset used.
  • Errors return { "error": { "code", "message" } }. Branch on code, which is stable; message may be reworded.
  • Lists accept limit (default 50, max 200) and offset.
  • Timestamps are ISO 8601 in UTC. Locations return their own timezone so you can render local times correctly.
{
"error": {
  "code": "insufficient_scope",
  "message": "This key does not have the \"write\" scope.",
  "required_scope": "write"
}
}

Endpoints

GET /v1/me

Confirms which organisation a key belongs to and what it can do. The quickest way to check a key works.

GET /v1/locations

Your locations, with addresses, coordinates and timezones.

GET /v1/queues

Your queues and how many people are waiting in each. Filter with location_id.

curl "https://queuejoin.com/api/v1/queues?location_id=LOCATION_ID" \
-H "Authorization: Bearer $QJ_KEY"

GET /v1/queue-entries

Visits, newest first. Filter with status (waiting, called, served, no_show, cancelled), location_id or queue_id.

POST /v1/queue-entries

Add somebody to a queue — from your own booking page, your till, or a kiosk. Requires the write scope. They are notified exactly as if they had scanned your QR code.

curl -X POST https://queuejoin.com/api/v1/queue-entries \
-H "Authorization: Bearer $QJ_KEY" \
-H "Content-Type: application/json" \
-d '{
  "name": "Sam Okafor",
  "mobile_number": "+447700900123",
  "location_id": "LOCATION_ID",
  "party_size": 2
}'

queue_id, service_id and notesare optional; without a queue we use the location's default. Returns 201 with the new entry, or 402if the plan's visit limit is reached.

GET /v1/queue-entries/{id}

A single visit.

PATCH /v1/queue-entries/{id}

Move a visit on: called, served, no_show or cancelled. Requires the write scope. Sends the same notifications as the dashboard, so your customer hears about it either way.

curl -X PATCH https://queuejoin.com/api/v1/queue-entries/ENTRY_ID \
-H "Authorization: Bearer $QJ_KEY" \
-H "Content-Type: application/json" \
-d '{"status": "called"}'

Returns 409 if the visit is already served, cancelled or marked a no-show — closed visits are not reopened.

GET /v1/appointments

Bookings. Filter with from and to (ISO dates) and location_id.

GET /v1/customers

Your customer directory. Filter with search, or with marketing_consent=true to get only those who have opted in and not since opted out.

Webhooks

Register an HTTPS endpoint in Account settings → Integrations and we will POST to it when something happens — whether it happened through this API or because a member of staff pressed a button.

  • appointment.cancelledA booking was cancelled
  • appointment.createdA booking was made
  • appointment.updatedA booking was rescheduled or changed status
  • queue_entry.calledA visitor was called forward
  • queue_entry.cancelledA visitor left the queue
  • queue_entry.createdA visitor joined a queue
  • queue_entry.no_showA visitor did not arrive
  • queue_entry.servedA visitor was served
POST https://your-app.example.com/hooks/queuejoin
Queuejoin-Signature: t=1756377600,v1=6f2a…

{
"id": "evt_…",
"type": "queue_entry.called",
"created_at": "2026-08-28T10:00:00.000Z",
"data": {
  "id": "ENTRY_ID",
  "customer_id": "CUSTOMER_ID",
  "location_id": "LOCATION_ID",
  "queue_id": "QUEUE_ID",
  "status": "called"
}
}

Verifying a webhook

Always verify the signature before acting on a webhook — otherwise anyone who learns your URL can tell your system a customer was served. The header contains a timestamp and an HMAC-SHA256 of <timestamp>.<raw body>, keyed with the signing secret shown when you created the endpoint.

import crypto from "node:crypto";

function verify(rawBody, header, secret) {
const parts = Object.fromEntries(
  header.split(",").map((p) => p.split("=")),
);
const expected = crypto
  .createHmac("sha256", secret)
  .update(`${parts.t}.${rawBody}`)
  .digest("hex");

// Constant-time compare, and reject anything older than five minutes so a
// captured request cannot be replayed later.
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
return fresh && crypto.timingSafeEqual(
  Buffer.from(parts.v1), Buffer.from(expected),
);
}

Use the raw request body, before any JSON parsing — re-serialising changes the bytes and the signature will not match.

Delivery and retries

  • Reply 2xx to acknowledge. Anything else is treated as a failure.
  • Reply quickly and do the work afterwards — we time out after 10 seconds.
  • Failures are retried six times over about 90 minutes with increasing gaps, then given up on. Recent attempts are listed in Account settings → Integrations.
  • Delivery is at-least-once: the same event may arrive twice. Use the event id to make your handling idempotent.
  • Disabling an endpoint stops queued events being delivered to it.

Limits and support

Be reasonable with request volume; we may throttle keys that place unusual load on the service. Creating a visit counts towards your plan's visit allowance, exactly as it would from the dashboard.

Questions, or something missing? hello@queuejoin.com. Use of the API is governed by our terms.