Start building
REST APIs for events, attendees, transactional comms, team admin, white-label account management, and device profiles. OAuth-authenticated, all served from api.qflowhub.io.
Qflow's developer surface is six REST APIs — each on its own page in these docs. They share an authentication model, an APIM-fronted gateway, and consistent operational conventions (idempotency, correlation IDs, error envelope).
What's here
Events
Event CRUD plus the tally-counter ledger that lives on each event.
Guests
The bulk of the surface — attendee CRUD, bulk reads, atomic create-and-send, door admission.
Team
Team-member admin for who can operate which events.
Users
Provision and manage end-user accounts under your tenant — the white-label / multi-tenant surface, with iFrame SSO.
Comms
Transactional email: templates, sending, webhooks, custom sending domain.
Profiles
Reusable bundles of restrictions, feature toggles and tag defaults — assign one to a team member, several, or a whole event's team to shape the check-in app.
The per-API split lets each surface own its sidebar and Reference tab without 50+ operations fighting for attention.
Pick your path
Direct customer
You're adding event management to your existing product. Operate any Qflow account you've been given access to — not just your own — though you can't create new accounts yourself. Best for smaller integrators, and for anyone running their own events under a single account. Live in the Events + Guests + Comms APIs.
White-label partner
You create and manage sub-accounts beneath your own — a ticketing company, for example, can provision a Qflow account per client and run their events, guests, and comms. Reselling is supported but not required. Onboard quickly with iFrame SSO, or build your own dashboard on the Users API — branded at your subdomain, with custom iOS and Android apps.
How to read these docs
Every API page has two surfaces:
- Guide — narrative articles, walkthroughs, and curl examples with one-click "Try in Qflow" buttons.
- Reference — the full operation list rendered from the OpenAPI spec, with interactive Test Request popups.
The two are linked: every curl in a Guide article auto-injects a "Try it" pill that jumps to the matching operation in Reference, and every operation card carries a "Guide: {Article} →" back-link to the prose that explains it.
Quick start — 5-minute end-to-end
The core flow is authenticate → create an event → add a guest. Sign in to the Qflow check-in app with the same credentials and the guest appears on the event, ready to scan. Emailing them an invitation / ticket is optional — it is the final step, and may be skipped.
You'll need your client credentials (client_id + client_secret), your subscription key (both in your welcome email), and a Qflow username + password.
1. Get a bearer token
OAuth password flow against identity.qflowhub.io. (For refresh tokens and other flows, see Authentication.)
curl -X POST 'https://identity.qflowhub.io/core/connect/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=password' \
-d 'client_id=<your-client-id>' \
-d 'client_secret=<your-client-secret>' \
-d 'username=<email>' \
-d 'password=<password>' \
-d 'scope=qflowapi+openid+offline_access'
Response gives you an access_token. Every subsequent call needs both:
Authorization: Bearer <access_token>
Ocp-Apim-Subscription-Key: <subscription_key>
2. Create your first event
curl -X POST 'https://api.qflowhub.io/checkin/v1/api/event' \
-H 'Authorization: Bearer <access_token>' \
-H 'Ocp-Apim-Subscription-Key: <subscription_key>' \
-H 'Content-Type: application/json' \
-d '{
"title": "My first event",
"startTime": "2026-06-01T19:00:00",
"endTime": "2026-06-01T23:00:00",
"timeZoneId": "Europe/London"
}'
Save the returned id — that's your eventId.
3. Add a guest
curl -X POST 'https://api.qflowhub.io/checkin/v1/api/guest' \
-H 'Authorization: Bearer <access_token>' \
-H 'Ocp-Apim-Subscription-Key: <subscription_key>' \
-H 'Content-Type: application/json' \
-d '{
"id": "<a fresh GUID you generate>",
"firstName": "Joe",
"lastName": "Bloggs",
"email": "joe@example.com",
"eventId": "<eventId from step 2>",
"correlationId": "first-test"
}'
That completes the core flow. Open the Qflow check-in app (iOS or Android), sign in with the same username and password, and select your event — Joe appears on the list, ready to scan. Records created over the API are reflected on the device in real time.
To control what the operator can do on that device — lock settings, preset scan behaviour, cap or PIN-protect actions — assign them a Profile. It's the configuration layer on top of Team access.
Optional — email an invitation / ticket
To send the guest an invitation / ticket, create a template first, then include its commsTemplateId on the guest call. POST /api/guest then creates the attendee and queues the email atomically — a single round-trip with no race condition. (To invite a guest who already exists, send via the Comms API — see Sending — atomic vs two-step.)
# Create the template — the email Joe receives.
# Supports merge tokens like {{firstname}} and {{barcode_qr}}, plus optional PDF attachments.
curl -X POST 'https://api.qflowhub.io/comms/v1/template' \
-H 'Authorization: Bearer <access_token>' \
-H 'Ocp-Apim-Subscription-Key: <subscription_key>' \
-H 'Content-Type: application/json' \
-d '{
"eventId": "<eventId from step 2>",
"name": "Welcome ticket",
"subject": "Your ticket — {{firstname}}",
"html": "<h1>Hi {{firstname}}!</h1><p>Show this at the door:</p><p><img src=\"{{barcode_qr}}\" /></p>"
}'
Then add "commsTemplateId": "<that id>" to your step-3 guest call. The guest is created and the email is queued for delivery. If you have registered a comms webhook, comms.sent arrives within a second, followed by comms.delivered once the recipient's mail server confirms receipt. See Comms templates for the full template feature set.
Ready to build?
- Read Authentication for the full auth model (refresh tokens, scopes, and the two-header model).
- Read Conventions for idempotency, correlation IDs, and the error envelope.
- Then proceed to whichever API matches your use case.
Need access enabled, or have a question? Email support@qflowhub.io and a member of the team will help.
Authentication
OAuth 2 bearer + APIM subscription key — the same two headers every API call carries.
Two layers, two headers
Every Qflow API call carries two authentication headers:
Authorization: Bearer {token}— OAuth bearer issued byidentity.qflowhub.io. Identifies the user and scopes the call.Ocp-Apim-Subscription-Key: {key}— Azure API Management subscription key. Gates the gateway itself.
Both are required on every request. Missing either returns 401 from APIM before the call reaches the origin.
Acquiring a bearer
Two grant types are supported depending on your integration shape:
- Authorization code — interactive flows where a human logs in via a browser. This is what the docs' "Authorize" button uses. Best for first-party UIs.
- Password grant (resource owner) — server-to-server flows where you hold the user's credentials.
Your client_id and client_secret arrive in your welcome email when access is provisioned. The token endpoint is https://identity.qflowhub.io/core/connect/token. Scopes you'll typically request: qflowapi openid offline_access — the first scopes the API call, the latter two enable refresh tokens.
Subscription key
Your subscription key arrives in your welcome email, alongside your client credentials. It's product-scoped — one key works for every API in the Qflow product (Comms, Check-in, Users, etc). Rotate it periodically; the docs' Test Request popup is pre-filled with a sandbox key so you can experiment without managing it.
Troubleshooting
| Symptom | Likely cause |
|---|---|
401 Unauthorized from APIM |
Missing or invalid Ocp-Apim-Subscription-Key. |
401 Unauthorized from origin |
Bearer token expired, malformed, or issued for the wrong audience. |
403 Forbidden |
Your account is authenticated but not authorised for that API or action. Some APIs require additional enablement or identity verification before use — see that API's Guide for the specific gate. |
429 Too Many Requests |
Rate limit exceeded. Back off and retry after a short delay. |
Conventions
Idempotency, correlation IDs, content types, and the other conventions every endpoint follows.
Idempotency
Many write endpoints accept a caller-supplied id (UUID), which becomes the record's permanent identifier. If you omit it, Qflow generates one server-side and returns it in the response.
Where idempotency is supported — notably guest creation — retrying with the same id returns the existing record instead of creating a duplicate, making the write safe to retry after a transient network failure. Not every endpoint deduplicates on the supplied id, so consult the relevant API Guide before relying on retry safety.
Common pattern:
const id = crypto.randomUUID(); // or any UUIDv4
// ... attempt create; on failure, retry with the same id
Correlation IDs
Pass an opaque correlationId on writes that trigger async side-effects (comms sends, exports, etc). Qflow:
- Persists it on the record.
- Echoes it in any webhook event that fires for that record.
This lets you correlate webhook payloads with your own request log without having to keep a mapping table of Qflow IDs.
Webhooks
Qflow has two independent webhook channels — use either or both:
- Comms (transactional email) — delivery-lifecycle events (
comms.sent,comms.delivered,comms.bounced, …), HMAC-signed with a{ id, type, createdAt, data }envelope. Configured via the Comms API. - Check-in (arrivals) — fires when an attendee is admitted (API, dashboard, or scanner). A flat, legacy payload, not signed, configured in the dashboard. See Check-in webhook.
They're separate systems with different payload shapes — verify HMAC on the Comms channel; treat check-in as the legacy notification feed.
Content types
Send and expect application/json: set Content-Type: application/json on requests; responses are JSON. The one exception is the OAuth /connect/token endpoint, which follows OAuth's standard application/x-www-form-urlencoded.
Date and time
Timestamps are ISO 8601. Send UTC with a trailing Z (2026-05-19T14:30:00Z) and it is stored as given. Alternatively, send a zoneless local timestamp (2026-05-19T14:30:00) together with an IANA timeZoneId (for example Europe/London), and Qflow interprets the value in that zone. Responses are ISO 8601.
Empty responses
204 No Content is the standard success response for DELETE operations and side-effect-only writes that have nothing to return. These responses carry no body; do not attempt to parse one.
Bulk operations
Where the surface offers both single (/api/guest) and bulk (/api/guests) shapes, prefer bulk for any operation hitting >5 records. Lower latency, fewer gateway calls, single transaction at the backend.
Errors
The HTTP status-code shape, the JSON error envelope, and retry guidance.
The error envelope
Every error response carries a JSON body. There are two shapes, depending on where the error originates:
APIM gateway errors — pre-origin failures (auth, subscription, rate limit):
{
"statusCode": 401,
"message": "Access denied due to invalid subscription key."
}
Origin errors — failures inside the API itself (validation, business logic, per-API gates):
{
"error": "validity_expired",
"message": "This ticket is outside its valid time window."
}
The error field is a machine-readable code. The message is human-readable. Branch on error, not on message — message text can change between releases; codes are stable.
Status code semantics
| Status | When |
|---|---|
200 |
Success with a body. |
204 |
Success, no body (DELETE, side-effect-only writes). |
400 |
Bad request — validation failed, missing required field, malformed JSON. |
401 |
Auth failure — missing/invalid bearer or subscription key. |
403 |
Forbidden — authenticated but not authorised for this operation. Read error for the specific reason. Some APIs apply additional gates (allow-listing, identity verification) — see that API's Guide. |
404 |
Not found — the resource id doesn't exist, OR you don't have access to see it (Qflow doesn't leak existence). |
409 |
Conflict — usually means you supplied an id that already exists for a different record. |
429 |
Rate-limited at the APIM gateway. Honour Retry-After. |
5xx |
Server error. Retry with backoff for 502 / 503 / 504 (gateway-side); 500 from origin is more often a real bug — report it. |
Per-API access gates
Beyond authentication, some APIs require explicit enablement or identity verification before certain operations succeed. These surface as a 403 whose error code names the gate, and they are documented in the Guide for the API that enforces them — for example, the Comms API's allow-list and client-verification requirements. Treat such a 403 as "consult the relevant API's Guide for what's required", not as a transient failure to retry.
Retry guidance
- Idempotent writes — endpoints that deduplicate on a caller-supplied
id, such as guest creation — are always safe to retry. - Other writes — retry only on transport-level failures (
5xx, network timeout). On400-class errors the call is rejected at the gateway or validator and has no side-effects; on a partial200, retrying risks duplicating work, so do not retry blindly. - Use exponential backoff for
429and5xx. The APIM rate limits are designed to absorb burst traffic; sustained retries inside a burst window won't help.