QR Chameleon

API Reference

QR Chameleon API: Programmatic Short Links, QR Codes & Analytics

A clean REST API for developers. Create short links and QR codes in bulk, pull click and scan analytics, and build integrations — all with a single Bearer token and generous free-tier rate limits.

Getting started

Authenticate every request with an API key. Generate one in your dashboard under Account → API Access. Keys are shown only once at generation time — store yours in a password manager or environment variable.

Base URL

https://qrchameleon.com/api/v1

Authentication

Pass your key in the Authorization header on every request:

Authorization: Bearer qrc_your_api_key_here

Missing, malformed, or revoked keys return 401 Unauthorized.

Rate limits

Two limits apply to every key: a monthly quota and a per-minute burst cap. Current caps by plan:

PlanMonthly requestsBurst (per minute)
Free 1,250/mo 60/min
Blend 6,500/mo 120/min
Adapt 30,000/mo 300/min
Transform 60,000/mo 600/min

Every response includes these headers so you can self-throttle:

X-RateLimit-Tier:             blend
X-RateLimit-Limit-Minute:     120
X-RateLimit-Remaining-Minute: 118
X-RateLimit-Limit-Month:      7500
X-RateLimit-Remaining-Month:  7491

When either cap is exceeded, requests return 429 Too Many Requests with a Retry-After header (seconds until reset).

Response format

All responses are JSON. Success responses use this envelope:

{
  "success": true,
  "data": { ... }
}

List endpoints include a meta object for pagination:

{
  "success": true,
  "data": [ ... ],
  "meta": { "page": 1, "limit": 20, "total": 150, "total_pages": 8 }
}

Errors

Error responses use status: "fail" for client errors (4xx) and status: "error" for server errors (5xx):

{
  "status": "fail",
  "message": "URL not found"
}
CodeMeaning
400Malformed request body or missing required field
401Missing, invalid, or expired API key
403API key lacks required permission, or your plan doesn't include this endpoint (see plans)
404Resource not found or not owned by this account
429Rate limit exceeded (see Retry-After header)
500Server error — retry with exponential backoff

Pagination

List endpoints accept page and limit query parameters. limit defaults to 20 and caps at 100.

Data retention

Analytics endpoints clamp the requested timeframe to your plan's data-retention window. If you ask for 90d on Free (7-day retention), the response returns 7 days and reports the clamp:

{
  "timeframe_requested": "90d",
  "timeframe_applied":   "7d",
  "retention_limit_days": 7,
  "was_clamped": true,
  ...
}
PlanRetention
Free 0 days
Blend 0 days
Adapt 0 days
Transform 0 days

The timeframe query parameter accepts 7d, 30d, 90d, or all on every analytics endpoint. all resolves to your retention window.

Need deeper history? Upgrade your plan to extend retention up to 1 year.

GET/v1/urls

List the authenticated account's short links.

Query params: page (default 1), limit (default 20, max 100)

curl -H "Authorization: Bearer qrc_..." \
  "https://qrchameleon.com/api/v1/urls?limit=10"
{
  "success": true,
  "data": [
    {
      "id": "Lp8cgZgbkgH9faH1",
      "short_url": "https://qrch.am/my-link",
      "short_code": "my-link",
      "custom_back_half": "my-link",
      "destination_url": "https://example.com/landing",
      "title": "Spring Sale",
      "is_active": true,
      "created_at": "2026-04-01T12:00:00.000Z",
      "expires_at": null,
      "last_clicked_at": "2026-04-22T17:33:12.000Z",
      "click_count": 1420,
      "linked_qr_code_id": null,
      "utm": {
        "source": "email", "medium": "newsletter",
        "campaign": "spring-sale", "term": null, "content": null
      }
    }
  ],
  "meta": { "page": 1, "limit": 10, "total": 42, "total_pages": 5 }
}

GET/v1/urls/:id

Fetch a single short link by ID. Returns 404 if the link doesn't exist or belongs to another account.

curl -H "Authorization: Bearer qrc_..." \
  https://qrchameleon.com/api/v1/urls/Lp8cgZgbkgH9faH1

GET/v1/urls/:id/analytics

Click analytics with country, device, daily, and referrer breakdowns.

Query params: timeframe — one of 7d, 30d (default), 90d, all

{
  "success": true,
  "data": {
    "url_id": "Lp8cgZgbkgH9faH1",
    "timeframe": "30d",
    "total_clicks": 1420,
    "unique_visitors": 1103,
    "by_country": [{"country": "United States", "count": 840}, ...],
    "by_device":  [{"device": "mobile", "count": 920}, ...],
    "by_day":     [{"date": "2026-04-01", "count": 34}, ...],
    "by_referrer":[{"source": "Google", "category": "search_engine", "count": 210}, ...]
  }
}

POST/v1/urls/bulk

Create up to 100 short links in one request. Each item is validated independently and results report per-item status (created, failed, or skipped if plan limit reached).

curl -X POST -H "Authorization: Bearer qrc_..." \
  -H "Content-Type: application/json" \
  -d '{
    "urls": [
      {"destination_url":"https://example.com/a","title":"Variant A","utm_source":"email"},
      {"destination_url":"https://example.com/b","custom_back_half":"my-link"}
    ]
  }' \
  https://qrchameleon.com/api/v1/urls/bulk
{
  "success": true,
  "data": {
    "total": 2,
    "created": 2,
    "failed": 0,
    "skipped": 0,
    "items": [
      {"index": 0, "status": "created", "id": "Lp...", "short_code": "ab12cd", "short_url": "https://qrch.am/ab12cd", "destination_url": "https://example.com/a"},
      {"index": 1, "status": "created", "id": "Lp...", "short_code": "my-link", "short_url": "https://qrch.am/my-link", "destination_url": "https://example.com/b"}
    ]
  }
}

Request body fields:

FieldTypeNotes
destination_urlstring (required)Must be a valid http(s) URL
titlestringHuman-readable label
custom_back_halfstringDesired slug; must be unique across the system
expires_atISO 8601Link stops working after this timestamp
utm_source, utm_medium, utm_campaign, utm_term, utm_contentstringCampaign tracking parameters

QR codes

GET/v1/qr

List the authenticated account's QR codes.

curl -H "Authorization: Bearer qrc_..." \
  "https://qrchameleon.com/api/v1/qr?limit=10"
{
  "success": true,
  "data": [
    {
      "id": "Qp8cgZgbkgH9faH1",
      "name": "Spring Sale Flyer",
      "qr_type": "url",
      "destination_url": "https://example.com/landing",
      "is_active": true,
      "created_at": "2026-04-01T12:00:00.000Z",
      "scan_count": 318,
      "download_count": 2,
      "linked_shortlink_id": "Lp...",
      "has_logo": true,
      "qr_config": { "dotsOptions": {"color": "#123351", "type": "dot"}, ... }
    }
  ],
  "meta": { "page": 1, "limit": 10, "total": 18, "total_pages": 2 }
}

GET/v1/qr/:id

Fetch a single QR code by ID.

GET/v1/qr/:id/analytics

Scan analytics with country, device, and daily breakdowns. Accepts timeframe same as URL analytics.

{
  "success": true,
  "data": {
    "qr_id": "Qp8cgZgbkgH9faH1",
    "timeframe": "30d",
    "total_scans": 318,
    "unique_scanners": 287,
    "by_country": [{"country": "United States", "count": 198}, ...],
    "by_device":  [{"device": "mobile", "count": 310}, ...],
    "by_day":     [{"date": "2026-04-01", "count": 9}, ...]
  }
}

POST/v1/qr/bulk

Create up to 100 QR codes in one request. Same partial-success semantics as /v1/urls/bulk.

curl -X POST -H "Authorization: Bearer qrc_..." \
  -H "Content-Type: application/json" \
  -d '{
    "qr_codes": [
      {"destination_url":"https://example.com/1","name":"Flyer 1"},
      {"destination_url":"https://example.com/2","name":"Flyer 2","qr_config":{"dotsOptions":{"color":"#3eeda2","type":"dot","size":10}}}
    ]
  }' \
  https://qrchameleon.com/api/v1/qr/bulk

Account analytics

Aggregated across everything you own — all tiers, retention-clamped.

GET/v1/analytics/overview

Total clicks and scans across the account for the timeframe, plus inventory counts.

{
  "success": true,
  "data": {
    "timeframe_applied": "30d",
    "retention_limit_days": 30,
    "was_clamped": false,
    "totals": {
      "total_clicks": 1420,
      "total_scans": 318,
      "total_engagements": 1738,
      "unique_click_visitors": 1103,
      "unique_scan_visitors": 287
    },
    "inventory": {
      "total_links": 59, "active_links": 59,
      "total_qr_codes": 19, "active_qr_codes": 19
    }
  }
}

GET/v1/analytics/top-urls

Top short links by clicks in the timeframe. Accepts limit (default 10, max 50).

GET/v1/analytics/top-qr

Top QR codes by scans in the timeframe. Accepts limit (default 10, max 50).

GET/v1/analytics/campaigns

UTM-aggregated campaign performance. Groups by utm_campaign × utm_source × utm_medium and sums clicks per group.

{
  "success": true,
  "data": [
    {
      "utm_campaign": "spring-sale",
      "utm_source": "email",
      "utm_medium": "newsletter",
      "link_count": 4,
      "total_clicks": 320,
      "unique_visitors": 245
    }
  ]
}

Advanced insights

Deeper engagement data — scores, heatmaps, audience cross-tabs, and optimal-posting matrices — unlocked on higher plans. Each endpoint below shows the required tier.

GET/v1/insights/engagement-score/:type/:id

Available on Blend+ plans

Returns a 0–100 engagement score for any single resource (:type = url or qr), blending volume, audience uniqueness, geographic spread, and recency. Useful for ranking your links/QRs programmatically.

{
  "success": true,
  "data": {
    "engagement_score": 76,
    "breakdown": {
      "volume_score": 35, "uniqueness_score": 20,
      "geo_spread_score": 12, "recency_score": 9
    },
    "signals": {
      "total_events": 1420, "unique_visitors": 1103,
      "unique_countries": 14, "hours_since_last_event": 3
    }
  }
}

GET/v1/insights/heatmap

Available on Transform plan

City-level geographic heatmap across the entire account. Returns up to 500 points with lat/lng and engagement count — perfect for plotting on Mapbox, Leaflet, or your own visualization.

GET/v1/insights/audience

Available on Transform plan

Device × referrer cross-tabs, browser × OS combinations, and mobile/tablet/desktop form-factor split. Answers "who's actually engaging?" in one call.

GET/v1/insights/optimal-times

Available on Transform plan

Hour-of-day × day-of-week engagement matrix with the top 5 windows. Feed it into your scheduler to automatically post when your audience is most active.

{
  "success": true,
  "data": {
    "matrix": [
      {"weekday": 3, "weekday_name": "Tue", "hour": 14, "count": 187},
      ...
    ],
    "top_5_windows": [
      {"weekday": 3, "weekday_name": "Tue", "hour": 14, "count": 187}
    ]
  }
}

Weekday values follow MySQL DAYOFWEEK: 1=Sunday … 7=Saturday.

Unlock every insight endpoint

Transform includes the full advanced-insights suite plus 60,000 API requests/mo, unlimited data retention, webhooks, and mobile deep linking.

See all plans →