GuidesBulk upload format

Bulk QR upload format

The dashboard’s QR codes → Bulk page and the customer API’s POST /api/qr/codes/bulk endpoint create many dynamic QR codes in one run. Both support the full customization surface: every row can carry the complete create-body (styling, presets, brand kits, protection, content payloads).

Dashboard upload (CSV or Excel)

Download a sample CSV template or Excel template from the Bulk page. The templates ship every accepted column plus example rows; the Excel file includes a column-reference sheet. A bare list of URLs (one per line, no header) also works and creates url-type QRs.

Accepted columns:

ColumnWhat it does
nameDashboard label (max 120 chars)
dataTypeurl (default), text, email, phone, sms, geo, wifi, vcard
destinationThe content for url, text, phone, geo rows
presetA builder look: classic, soft, dots, ocean, sunset, contrast-eyes, menu, bizcard, wifi, event, boutique, cafe
brandKitIdApply a saved brand kit by id (bk_...)
primaryColor / backgroundColor#rrggbb overrides (a solid override replaces a base gradient)
frameTextCaption text (max 24 chars) when the effective style has a caption frame
styleJsonAdvanced: a JSON object of raw style options (dotsOptions, frame, shape, width, …) merged last, for 100 percent per-row styling control. Non-style keys are ignored with a warning
passwordRequire a password before the redirect opens
expiresAtISO 8601 date/time; must be in the future (and within the JS date range, i.e. before the year 275760)
maxScansStop redirecting after N scans
wifiSsid, wifiPassword, wifiEncryptionWi-Fi payload (WPA, WEP, nopass)
emailTo, emailSubject, emailBodyEmail payload
smsPhone, smsMessageSMS payload
vcardFirstName, vcardLastName, vcardPhone, vcardEmail, vcardOrganization, vcardWebsiteContact card payload

How styling resolves per row:

  1. The batch style you pick on the page (default look, a preset, a brand kit, or a fully custom design) applies to every row.
  2. A row’s preset or brandKitId (mutually exclusive) replaces the batch style for that row.
  3. primaryColor / backgroundColor / frameText tweak the result.
  4. styleJson merges last and wins on any style-option key it sets. It only touches styling: keys like uid, data, or password in a styleJson cell are ignored (the preview shows a warning), never merged.

Rows that fail validation are listed with reasons and excluded from the batch; everything else submits. Row cap: 500 per upload from the dashboard.

JSON format (API)

A bulk row is exactly the POST /api/qr/codes create body plus a required uid (your row id, echoed back in the result). Anything the single-create endpoint accepts works per row: full styling objects, preset, brandKitId, secure + password + expiry, maxScans, routingRules, campaign, deepLinkConfig, and structured data payloads.

POST /api/qr/codes/bulk
Authorization: Bearer exr_live_...
Content-Type: application/json
 
[
  {
    "uid": "row-1",
    "data": "https://yourbrand.com/spring",
    "name": "Spring poster",
    "preset": "cafe",
    "secure": ["expire"],
    "expiry": 1893456000000
  },
  {
    "uid": "row-2",
    "data": { "ssid": "AcmeGuest", "password": "welcome123", "encryption": "WPA" },
    "dataType": "wifi",
    "brandKitId": "bk_abc123"
  }
]
  • Row cap: 1,000 per call (split larger runs across calls).
  • preset / brandKitId are expanded server-side to the same pixels as the dashboard. A bad preset id or a kit that is not yours fails the whole call synchronously with a 400 naming the row, before any job is queued.
  • Logos in bulk: host the logo at an http(s) URL or apply it via a brand kit. Inline data: URI logos longer than 200,000 characters (roughly 150KB of image data) are rejected per row to keep job payloads bounded.

Response is a bulkId you poll:

GET /api/qr/codes/bulk/{bulkId}

When the status is completed, fetch the result once:

GET /api/qr/codes/bulk/{bulkId}/result

The result is an array with one entry per row: successes carry your uid, the internal id, and the redirect URL; failures carry { "uid", "status": "failed", "message", "code" }. It is a one-shot download, removed after the first successful fetch.