zabazingo · api reference

oktogen-qr v0.2.0
← back to the app

In-house QR platform: static generation (free, untracked) + dynamic tracked codes (/r redirect) + hosted landing pages. Privacy-by-construction analytics. This page is rendered in-house from the live /openapi.json contract — no third-party documentation host is loaded, so nothing about your visit or this API leaves our infrastructure.

authentication

Public endpoints (static QR generation, signup) need no credentials. Every auth endpoint takes a tenant bearer key:

authorization: Bearer <your-tenant-key>

Tenant API key from POST /app/api/signup. The key is shown once at signup — store it like a password. Each key is scoped to a single tenant; every read/write is ownership-gated, so you only ever see your own codes & analytics.

quickstart · curl

Render a styled static QR (free, anonymous — no key):

curl -X POST https://demo.qr.zabazingo.eu/app/api/generate \
  -H 'content-type: application/json' \
  -d '{ "type": "url", "fields": { "url": "https://demo.qr.zabazingo.eu" }, "style": { "moduleShape": "rounded" } }'

Create a dynamic (trackable, editable) code — needs your key:

curl -X POST https://demo.qr.zabazingo.eu/app/api/codes \
  -H 'authorization: Bearer <your-tenant-key>' \
  -H 'content-type: application/json' \
  -d '{ "type": "url", "fields": { "url": "https://example.com" }, "title": "spring" }'

List your codes + totals:

curl https://demo.qr.zabazingo.eu/app/api/codes \
  -H 'authorization: Bearer <your-tenant-key>'

endpoints

Generated from the spec — 21 paths.

GET/healthzpublic
Liveness
responses
200ok
GET/r/{shortId}public
Resolve + track a dynamic code (302 to destination or hosted page)
parameters
shortId pathrequiredstring
responses
302redirect
404unknown
410disabled
429rate limited
GET/openapi.jsonpublic
This contract
responses
200OpenAPI document
GET/app/api/typespublic
Catalog of code types + templates
responses
200types
POST/app/api/generatepublic
Render a static styled QR (free, anonymous)
request body required application/json
{
  type: string,
  fields: object,
  style: object
}
responses
200svg + payload
429rate limited
POST/app/api/exportpublic
Export a QR as svg/pdf/eps
responses
200file
429rate limited
POST/app/api/signuppublic
Create a workspace + tenant API key
request body application/json
{
  name: string,
  email: string
}
responses
200apiKey
429rate limited
GET/app/api/meauth
Current tenant + resolved grants
responses
200me
401invalid key
GET/app/api/codesauth
List my codes + totals
responses
200codes
POST/app/api/codesauth
Create a dynamic (trackable) code
request body application/json
{
  type: string,
  fields: object,
  target: string,
  title: string,
  style: object
}
responses
200created
400target required
402plan limit
403feature not enabled
PATCH/app/api/codes/{id}auth
Edit destination / active
parameters
id pathrequiredstring
responses
200updated
GET/app/api/codes/{id}/qr.svgauth
Download the TRACKABLE (/r) QR for a saved code
parameters
id pathrequiredstring
responses
200svg
GET/app/api/codes/{id}/analyticsauth
Per-code scan analytics + funnel
responses
200analytics
GET/app/api/codes/{id}/scansauth
Per-scan log + durable-device rollup. Returns total, uniqueVisitors, repeatScanners, uniqueDevices, repeatDevices, avgScansPerDevice and a privacy-hashed per-scan events[]. The per-device devices[] list (opaque device id, firstSeen, lastSeen, scanCount) is only present when the operator has granted the tenant the deviceLog visibility. Durable device ids are consent-only (see POST /p/{shortId}/consent).
parameters
id pathrequiredstring
responses
200scan log + device rollup
401not yours
403analytics not enabled
GET/app/api/campaignsauth
Campaigns (group tag) rolled up: per-campaign codes/scans/unique/leads + per-code compare (scans, unique, leads, CTA-rate)
responses
200campaigns
401invalid key
403analytics not enabled
GET/app/api/webhookauth
Get this tenant's outbound webhook config (url; secret is never re-revealed)
responses
200{ url, configured, updatedAt }
401invalid key
PUT/app/api/webhookauth
Set/update the tenant webhook url. A fresh signing secret is minted (returned ONCE) on first set or when rotate=true. Empty url removes the webhook.
On every scan (/r) and lead (POST /api/leads) the service POSTs a signed event to this url with header `x-oqr-signature: <hex hmac-sha256(secret, body)>`. Versioned envelope: `{ v:1, product:"oktogen-qr", event:"qr.scan"|"qr.lead", codeId, shortId, at, country, device, repeat (scan), hasEmail, consent (lead) }`.
request body application/json
{
  url: string <uri>,
  rotate: boolean
}
responses
200{ url, configured, updatedAt, secret? }
400invalid url
401invalid key
POST/app/api/webhook/testauth
Deliver a synthetic signed qr.scan event to the configured webhook (rate-limited 1/10s per tenant)
responses
200delivered
400no url set
401invalid key
429rate limited
POST/app/api/bulkauth
CSV → ZIP of QR svgs + manifest
responses
200zip
POST/app/api/redeemauth
Redeem an upgrade voucher → plan upgrade
request body application/json
{
  code: string
}
responses
200upgraded
400invalid/expired voucher
POST/api/leads/{shortId}public
Consented zero-party lead capture (hosted page)
responses
200stored
429rate limited
POST/api/events/{shortId}public
Anonymous interaction funnel event
responses
200ok
429rate limited
POST/p/{shortId}/consentpublic
Record the scanner's device-recognition consent (Art.13 surface on the hosted /p page). accept:true sets a signed, HttpOnly oqr_consent cookie so subsequent /r scans mint a durable, random, consent-only device id; accept:false clears consent and any durable id. No durable id is ever derived from IP/UA.
parameters
shortId pathrequiredstring
request body application/json
{
  accept: boolean
}
responses
204recorded
404unknown code

webhook signature

When you configure an outbound webhook (PUT /app/api/webhook), every scan and lead is POSTed to your URL with a signature header you must verify before trusting the event:

x-oqr-signature: <hex hmac-sha256(secret, rawBody)>

The signing secret is returned once when the webhook is first set (or rotated). Recompute the HMAC over the exact raw request body and compare in constant time. Versioned envelope:

{
  "v": 1,
  "product": "oktogen-qr",
  "event": "qr.scan",      // or "qr.lead"
  "codeId": "qr_...",
  "shortId": "abc123",
  "at": "2026-06-03T12:00:00.000Z",
  "country": "DE",         // scan only
  "device": "mobile",      // scan only
  "repeat": false,         // scan only
  "hasEmail": true,        // lead only
  "consent": true          // lead only
}

Privacy-by-construction: the payload carries a coarse device/country bucket, never a raw IP or a person identifier.