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 merged last, for 100 percent per-row styling control. See the accepted keys below. 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

styleJson reference

The cell must hold a single JSON object. Only these keys are merged; anything else (including uid, data, password) is ignored with a warning:

KeyMeaning
shapesquare or circle code silhouette
marginQuiet zone in modules (0-40)
width / heightRendered pixel size
returnTypeOutput format for the generated image (png, svg, webp, jpg, pdf)
qrOptionse.g. {"errorCorrectionLevel":"H"}
dotsOptionsDot pattern + color/gradient, e.g. {"type":"rounded","color":"#0f766e"}
cornersSquareOptionsCorner-eye shape + color/gradient
cornersDotOptionsInner eye-dot shape + color/gradient
backgroundOptionsBackground color/gradient/rounding, e.g. {"color":"#f8fafc"}
frameFrame config ({"id":"scanme","text":"SCAN ME", ...})
imageLogo URL overlaid on the code
imageOptionsLogo size/margin/hide-dots, e.g. {"imageSize":0.3,"margin":4}

Example cell (quote it properly in CSV):

{"dotsOptions":{"type":"rounded","color":"#0f766e"},"backgroundOptions":{"color":"#f8fafc"}}

These are the same objects the API’s single-create endpoint accepts, so anything documented for POST /api/qr/codes styling works here. (On the API’s bulk endpoint there is no styleJson column; send these keys directly on the row body instead.)

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.