Shortnd Docs

QR Codes

Generate, brand, manage, and analyze QR codes — both URL-bound and standalone.

/api/v1/qr covers everything QR: branded QRs for short links, standalone QRs for vCards / Wi-Fi / arbitrary payloads, and per-QR scan analytics.

Scopes: qr:read (list / read / scans), qr:write (create / update / delete).

Every QR is org-scoped via qr_codes.organization_id. URL-bound QRs additionally tie back to the parent URL via urlId.

Create a QR for an existing URL

The most common path: generate a QR that encodes a Shortnd short link. The encoded payload is built from the URL's shortCode + the public host (https://shortnd.com/<shortCode>).

curl -X POST https://shortnd.com/api/v1/qr \
  -H "Authorization: Bearer eyJ..." \
  -H "Content-Type: application/json" \
  -d '{
    "urlId": 12345,
    "size": 512,
    "format": "png",
    "foregroundColor": "#000000",
    "backgroundColor": "#FFFFFF",
    "errorCorrectionLevel": "M"
  }'

The URL must belong to the calling org. Response (truncated):

{
  "success": true,
  "data": {
    "id": "qr_uuid",
    "urlId": "12345",
    "organizationId": "org_uuid",
    "qrData": "https://shortnd.com/abc123",
    "format": "png",
    "size": 512,
    "errorCorrection": "M",
    "foregroundColor": "#000000",
    "backgroundColor": "#FFFFFF",
    "logoUrl": null,
    "downloadCount": 0,
    "scanCount": 0,
    "downloadUrl": "/api/qr/qr_uuid/download",
    "previewUrl": "/qr/qr_uuid",
    "createdAt": "2026-05-08T10:30:00Z"
  }
}

Create a standalone QR

Pass data instead of urlId to encode an arbitrary payload — vCards, Wi-Fi credentials, third-party URLs, raw text. Up to 2 KB.

curl -X POST https://shortnd.com/api/v1/qr \
  -H "Authorization: Bearer eyJ..." \
  -H "Content-Type: application/json" \
  -d '{
    "data": "BEGIN:VCARD\nVERSION:3.0\nFN:Tosh\nTEL:+1-555-0123\nEMAIL:hi@acme.com\nEND:VCARD",
    "size": 512,
    "format": "svg"
  }'

Standalone QRs return urlId: null and are still scoped to your org. They count against the same qr_generated meter as URL-bound QRs.

Customization fields

FieldTypeDefaultNotes
urlIdintegerURL-bound QRs. Mutually exclusive with data.
datastringStandalone payload, ≤ 2 KB.
sizeinteger512Pixel dimensions, 200–2048.
formatenumpngpng / svg / webp.
foregroundColorhex#0000006-digit hex.
backgroundColorhex#FFFFFF6-digit hex.
logoUrlstring (URL)nullEmbedded logo at center. Use a high-EC level when set.
errorCorrectionLevelenumML / M / Q / H. Higher = more redundancy, larger code.
stylePresetstringdefaultTheming preset for qr-code-styling.

List & filter

curl 'https://shortnd.com/api/v1/qr?limit=25&offset=0&urlId=12345' \
  -H "Authorization: Bearer eyJ..."
QueryNotes
limitDefault 25, max 100.
offsetDefault 0.
urlIdFilter to QRs for one URL.
standalonetrue to filter to QRs without a urlId.

Read, update branding, soft-delete

MethodPathScope
GET/api/v1/qr/{id}qr:read
PATCH/api/v1/qr/{id}qr:write
DELETE/api/v1/qr/{id}qr:write

PATCH accepts the customization fields above (size, format, colors, logoUrl, errorCorrectionLevel). The encoded payload is immutable — to retarget a URL-bound QR, create a new one and retire the old one.

DELETE is a soft delete. The row remains for analytics continuity but is filtered out of list responses.

List QRs for a URL

A convenience over GET /api/v1/qr?urlId=… for buyers who already have a URL id:

curl https://shortnd.com/api/v1/urls/12345/qr \
  -H "Authorization: Bearer eyJ..."

Per-QR scan analytics

GET /api/v1/qr/{id}/scans mirrors the per-URL clicks shape so the same renderer in your app can handle both. Bot scans are excluded (is_bot = false).

curl 'https://shortnd.com/api/v1/qr/qr_uuid/scans?since=2026-04-08T00:00:00Z&groupBy=day&limit=10' \
  -H "Authorization: Bearer eyJ..."
QueryDefaultNotes
since30 days agoISO-8601
untilnowISO-8601
groupBydayhour or day
limit10Top-N per breakdown dimension (max 50)

Response shape:

{
  "success": true,
  "data": {
    "qrCodeId": "qr_uuid",
    "urlId": "12345",
    "since": "2026-04-08T00:00:00Z",
    "until": "2026-05-08T10:30:00Z",
    "totals": { "totalScans": 412, "uniqueScans": 388, "uniqueIps": 351 },
    "breakdowns": {
      "country":    [{ "country": "US", "scans": 220 }],
      "device":     [{ "deviceType": "mobile", "scans": 380 }],
      "browser":    [{ "browser": "Safari", "scans": 199 }],
      "scanMethod": [{ "scanMethod": "camera", "scans": 320 }, { "scanMethod": "app_scanner", "scans": 92 }],
      "scannerApp": [{ "scannerApp": "iOS Camera", "scans": 188 }]
    },
    "timeseries": [
      { "bucket": "2026-04-08T00:00:00Z", "scans": 24, "uniques": 22 }
    ]
  }
}

scanMethod values: camera, file_upload, manual_entry, app_scanner. Useful for measuring physical-print-to-scan conversion vs. in-app scans.

Image bytes

Every QR record returns a downloadUrl pointing at /api/qr/{id}/download. That endpoint streams the raw image bytes in the QR's stored format. It sits outside /api/v1 today (legacy, public, scope-free) — treat it as a public CDN-style URL safe to embed anywhere. A future v1 wrapper with scope-checked download is on the roadmap.

The previewUrl (/qr/{id}) renders an HTML preview page suitable for inline previews in marketing flows.

Billing meter

Every successful POST /api/v1/qr emits one qr_generated event. Tier bundles:

TierQR codes / month
Free / Sandbox25
Developer1,000
Scale25,000
EnterpriseUnlimited

Overage: $0.05/QR (Developer) → $0.03/QR (Scale). Branded QRs (logo + custom colors) cost the same as plain — the meter counts records, not rendering complexity.

PATCH and DELETE do not charge — only generation.

QR events on webhooks

Subscribe to qr.created, qr.scan_threshold, and qr.deleted to react to QR lifecycle events. See Webhooks for the signature scheme.