QRs.bd logo
QRs.bd
API v1Reference
Base URL
qrs.bd/api/v1
โ† Back to home
QRs.bdโ€บAPI Reference
HomeGet API key โ†’

API Reference

The QRs.bd REST API lets you create, manage, and redirect QR codes and short links programmatically. It is organized around standard HTTP methods and returns JSON for every response.

๐Ÿ”—
Base URL
https://qrs.bd/api/v1
๐Ÿ”„
Protocol
HTTPS only
๐Ÿ“ฆ
Format
JSON (request + response)
๐ŸŒ
Redirect engine
https://r.qrs.bd/<slug>

Authentication

All /api/v1/* endpoints require a Bearer API key in the Authorization header.

http
Authorization: Bearer qrbd_3aFk9mZpQxL2nVrTwJsY8bCeKdHoN1gI
๐Ÿ’ก
Go to Settings โ†’ API Keys in your dashboard to generate a key. Keys begin with qrbd_ and are shown once only on creation.
๐Ÿšจ
Store API keys in environment variables or a secrets manager. Never commit them to source control.

Rate Limits

Rate limits are enforced at the network edge per API key using a sliding window. Every response includes rate-limit headers.

Free
10
req / min
Starter
30
req / min
Pro
100
req / min
Business
500
req / min
http
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1716912060000

{ "error": "rate_limited" }
๐Ÿ’ก
When you receive a 429, wait until the value in X-RateLimit-Reset (Unix ms) before retrying. See the for a retry helper.

Plan Limits & Quotas

Hard quotas are enforced server-side and cannot be bypassed. When a quota is reached, the API returns 403 quota_exceeded.

LimitFreeStarterProBusiness
Monthly Price$0$9$29$79
Dynamic QR Codes215UnlimitedUnlimited
Short Links25500UnlimitedUnlimited
Scans / Month50010,000100,000500,000
Custom Domains0015
API Rate Limit10/min30/min100/min500/min
Team Seats1115

API Keys

These endpoints use your dashboard session token (not an API key) and are intended for managing keys from your own integration or UI.

List API Keys

GET/api/keys

Returns all active (non-revoked) API keys for the authenticated user.

json
{
  "data": [
    {
      "id": "a1b2c3d4-...",
      "name": "Production Key",
      "key_prefix": "qrbd_3aFk9m",
      "created_at": "2026-01-15T10:00:00Z",
      "last_used_at": "2026-05-24T18:42:00Z"
    }
  ]
}

Create API Key

POST/api/keys
FieldTypeRequiredDescription
namestringNoHuman-readable label for this key
workspace_idUUIDNoTarget workspace. Defaults to your oldest active workspace
json
// Request
{ "name": "Production Key" }

// Response 201
{
  "data": {
    "id": "a1b2c3d4-...",
    "name": "Production Key",
    "key_prefix": "qrbd_3aFk9m",
    "created_at": "2026-05-25T12:00:00Z",
    "key": "qrbd_3aFk9mZpQxL2nVrTwJsY8bCeKdHoN1gI"
  }
}
๐Ÿšจ
The key field is returned once only in this response. Copy it immediately โ€” it cannot be retrieved again.

Revoke API Key

DELETE/api/keys/:id
json
{ "data": { "id": "a1b2c3d4-...", "revoked": true } }

QR Codes

All QR code endpoints require Authorization: Bearer qrbd_....

List QR Codes

GET/api/v1/qrcodes

Returns up to 200 QR codes in your workspace, newest first.

json
{
  "data": [
    {
      "id": "d4e5f6a7-...",
      "slug": "summer26",
      "domain": "r.qrs.bd",
      "short_url": "https://r.qrs.bd/summer26",
      "target_url": "https://example.com",
      "title": "Summer Campaign",
      "is_active": true,
      "is_dynamic": true,
      "type": "qr",
      "template_type": "url",
      "content": null,
      "smart_rules": [],
      "expires_at": null,
      "created_at": "2026-01-01T00:00:00Z",
      "updated_at": "2026-01-01T00:00:00Z"
    }
  ]
}

Get QR Code

GET/api/v1/qrcodes/:id

Returns a single QR code. Returns 404 if it doesn't exist or belongs to another workspace.

Create QR Code

POST/api/v1/qrcodes
FieldTypeRequiredDescription
target_urlstringYesDestination URL. Must be http(s)://. Internal IPs are rejected.
titlestringNoHuman-readable label
slugstringNoCustom slug matching ^[a-z0-9_-]{3,32}$. Auto-generated if omitted.
is_dynamicbooleanNotrue (default) โ€” dynamic codes can be updated after creation
template_typestringNourl ยท vcard ยท wifi ยท menu ยท review ยท social ยท app ยท event
contentobjectNoTemplate-specific payload
designobjectNoQR design configuration (colors, dot style, logo, etc.)
smart_rulesarrayNoConditional redirect rules. See Smart Rules.
expires_atISO 8601NoWhen the QR stops redirecting
json
// Request
{
  "target_url": "https://your-destination.com",
  "title": "Summer Campaign",
  "slug": "summer26",
  "is_dynamic": true,
  "expires_at": "2026-12-31T23:59:59Z"
}

// Response 201
{
  "data": {
    "id": "d4e5f6a7-...",
    "slug": "summer26",
    "short_url": "https://r.qrs.bd/summer26",
    "target_url": "https://your-destination.com",
    "is_active": true,
    "is_dynamic": true,
    "type": "qr",
    "template_type": "url",
    "smart_rules": [],
    "expires_at": "2026-12-31T23:59:59Z",
    "created_at": "2026-05-25T12:00:00Z",
    "updated_at": "2026-05-25T12:00:00Z"
  }
}

Update QR Code

PATCH/api/v1/qrcodes/:id

Send only the fields you want to change. Updatable fields: target_url ยท title ยท is_active ยท is_dynamic ยท template_type ยท content ยท design ยท smart_rules ยท expires_at

json
// Request โ€” change destination and pause the QR
{ "target_url": "https://new-destination.com", "is_active": false }

// Response 200 โ€” full updated object

Delete QR Code

DELETE/api/v1/qrcodes/:id

Deactivates the QR code. The short URL stops resolving immediately.

json
{ "data": { "id": "d4e5f6a7-...", "deleted": true } }

Short links are URL-only redirects โ€” same API surface as QR codes but without a QR design layer. Uses the same redirect engine and analytics pipeline.

GET/api/v1/links
GET/api/v1/links/:id
POST/api/v1/links
FieldTypeRequiredDescription
target_urlstringYesDestination URL
titlestringNoHuman-readable label
slugstringNoCustom slug ^[a-z0-9_-]{3,32}$. Auto-generated if omitted.
is_dynamicbooleanNoDefault true. Dynamic links can be updated.
expires_atISO 8601NoExpiry datetime
json
// Request
{
  "target_url": "https://example.com/long/url?utm_source=campaign",
  "title": "Campaign Link",
  "slug": "campaign-q1"
}

// Response 201
{
  "data": {
    "id": "b2c3d4e5-...",
    "slug": "campaign-q1",
    "short_url": "https://r.qrs.bd/campaign-q1",
    "target_url": "https://example.com/long/url?utm_source=campaign",
    "is_active": true,
    "type": "link",
    "created_at": "2026-05-25T12:00:00Z"
  }
}
PATCH/api/v1/links/:id
json
{ "target_url": "https://new-destination.com", "is_active": false }
DELETE/api/v1/links/:id
json
{ "data": { "id": "b2c3d4e5-...", "deleted": true } }

Custom Domains

Use links.yourbrand.com instead of r.qrs.bd. Requires Pro (1 domain) or Business (5 domains).

1
POST /api/v1/domains โ€” domain registered, verification records returned
2
Add the CNAME + TXT records at your DNS registrar
3
GET /api/v1/domains โ€” status auto-updates as DNS propagates
4
Once status = "active" โ€” your domain is live

List Domains

GET/api/v1/domains
json
{
  "data": [
    {
      "id": "c3d4e5f6-...",
      "domain": "links.yourbrand.com",
      "status": "pending",
      "ssl_status": "pending",
      "verification": {
        "cname_target": "cname.qrs.bd",
        "ownership": {
          "type": "TXT",
          "name": "_cf-custom-hostname.links.yourbrand.com",
          "value": "abc123def456..."
        },
        "ssl": [
          { "type": "TXT", "name": "_acme-challenge.links.yourbrand.com", "value": "xyz789..." }
        ]
      },
      "created_at": "2026-05-25T12:00:00Z",
      "verified_at": null
    }
  ]
}

Add Custom Domain

POST/api/v1/domains
FieldTypeRequiredDescription
domainstringYesYour domain name. Normalized automatically โ€” scheme, path, and port are stripped.
โ„น๏ธ
After receiving the response, add the DNS records from verification at your registrar. Propagation typically takes a few minutes to a few hours.

Delete Custom Domain

DELETE/api/v1/domains?id=:id

Pass the domain id as a query parameter. QR codes fall back to r.qrs.bd automatically.

json
{ "data": { "id": "c3d4e5f6-..." } }

Redirect Behavior

When a visitor scans a QR or clicks a short link, they hit the redirect engine at r.qrs.bd/<slug> (or your custom domain). Behavior depends on the plan and QR configuration.

๐Ÿ“ขFree plan
Visitor sees a brief interstitial before redirecting
๐Ÿ”’Password-protected
Visitor sees a password entry screen
๐Ÿ“„Non-URL template (vCard, WiFiโ€ฆ)
Visitor sees the appropriate interactive page
โšกPaid + URL template
Instant 302 redirect to target_url
๐Ÿง Smart rule matches
Instant 302 to the matched rule's target_url
โ›”Expired or over scan quota
Redirected to qrs.bd fallback

Smart Rules

Smart rules redirect visitors to different destinations based on conditions โ€” evaluated at the network edge on every scan. Rules are stored as a JSON array in smart_rules and evaluated top-to-bottom; first match wins.

โ„น๏ธ
Smart rules apply to template_type: "url" entries on Starter plans and above.

Geo โ€” Route by Country

Match or exclude a list of ISO 3166-1 alpha-2 country codes.

json
{
  "type": "geo",
  "condition": "in",
  "values": ["US", "CA", "GB"],
  "target_url": "https://en.example.com"
}

// condition: "in" | "not_in"
// values: 2-letter country codes, e.g. ["US", "CA", "BD"]

OS โ€” Route by Device

Match or exclude by operating system detected from the User-Agent.

json
{
  "type": "os",
  "condition": "is",
  "values": ["iOS", "Android"],
  "target_url": "https://m.example.com"
}

// condition: "is" | "is_not"
// values: "iOS" | "Android" | "Windows" | "Mac" | "Linux"

A/B โ€” Split Traffic

Send a percentage of visitors to a variant URL. Visitors not captured fall through to the next rule or default.

json
{
  "type": "ab",
  "weight": 20,
  "target_url": "https://new.example.com"
}

// weight: 1โ€“99 (percent of traffic)

Time โ€” Route by Time of Day

Match a time window and days of week in any IANA timezone. Wrap-midnight windows are supported.

json
{
  "type": "time",
  "timezone": "America/New_York",
  "startTime": "09:00",
  "endTime": "17:00",
  "activeDays": [1, 2, 3, 4, 5],
  "target_url": "https://example.com/contact-us"
}

// activeDays: 0=Sun 1=Mon 2=Tue 3=Wed 4=Thu 5=Fri 6=Sat
// [] means every day

Combining Rules

Rules are evaluated in array order. The QR code's root target_url is the final fallback if no rule matches.

json
{
  "target_url": "https://default.example.com",
  "smart_rules": [
    { "type": "geo", "condition": "in", "values": ["US", "CA"], "target_url": "https://en.example.com" },
    { "type": "os",  "condition": "is", "values": ["iOS"],      "target_url": "https://apps.apple.com/..." },
    { "type": "ab",  "weight": 10,                              "target_url": "https://experiment.example.com" }
  ]
}

Analytics

Analytics are recorded automatically on every valid scan or click โ€” no API call required. View analytics in your dashboard under each QR code or link.

๐ŸŒ
Location
Country, region, city
๐Ÿ“ฑ
Device type
Desktop ยท Mobile ยท Tablet ยท Bot
๐Ÿ—ฃ๏ธ
Language
Browser language preference
๐Ÿ”’
Privacy
Daily-rotating hashed ID โ€” no raw IPs stored
โ„น๏ธ
Scans from bots and vulnerability scanners are filtered at the edge and do not count against your monthly scan quota.

Error Reference

All errors follow this shape:

json
{ "error": "error_code", "message": "Human-readable detail (optional)" }
CodeStatusDescription
unauthorized401API key missing, invalid, or revoked
rate_limited429Rate limit exceeded โ€” check X-RateLimit-Reset
quota_exceeded403Plan limit reached (QR codes, links, or domains)
not_found404Resource doesn't exist or belongs to another workspace
invalid_request400Validation failed; message field has details
invalid_slug400Custom slug doesn't match ^[a-z0-9_-]{3,32}$
slug_taken409That slug is already in use
invalid_domain400Domain string is not a valid hostname
domain_taken409Domain is already registered
domain_quota_reached403Plan domain limit reached
domain_not_found404Domain doesn't exist in this workspace
missing_domain400domain field is required
missing_id400id query parameter is required
no_workspace400Your account has no active workspace
workspace_not_found404Specified workspace_id doesn't exist or isn't yours
query_failed500Unexpected server error
create_failed500Unexpected error during creation
update_failed500Unexpected error during update
delete_failed500Unexpected error during deletion

Code Examples

typescript
const API_KEY = process.env.QRSBD_API_KEY!;
const BASE = "https://qrs.bd/api/v1";

async function api(path: string, init?: RequestInit) {
  const res = await fetch(`${BASE}${path}`, {
    ...init,
    headers: {
      Authorization: `Bearer ${API_KEY}`,
      "Content-Type": "application/json",
      ...init?.headers,
    },
  });
  const body = await res.json();
  if (!res.ok) throw new Error(`${body.error}: ${body.message ?? ""}`);
  return body;
}

// Create a QR code
const { data: qr } = await api("/qrcodes", {
  method: "POST",
  body: JSON.stringify({
    target_url: "https://example.com",
    title: "My Campaign QR",
    is_dynamic: true,
  }),
});
console.log(`Scan URL: ${qr.short_url}`);

// Update destination
await api(`/qrcodes/${qr.id}`, {
  method: "PATCH",
  body: JSON.stringify({ target_url: "https://new-destination.com" }),
});

// Delete
await api(`/qrcodes/${qr.id}`, { method: "DELETE" });

QR Code with all Smart Rules

typescript
const { data: qr } = await api("/qrcodes", {
  method: "POST",
  body: JSON.stringify({
    target_url: "https://default.example.com",
    title: "Multi-rule Campaign",
    is_dynamic: true,
    smart_rules: [
      // Route US/CA to English site
      { type: "geo", condition: "in",  values: ["US", "CA"], target_url: "https://en.example.com" },
      // iOS โ†’ App Store
      { type: "os",  condition: "is",  values: ["iOS"],      target_url: "https://apps.apple.com/app/id123" },
      // Android โ†’ Play Store
      { type: "os",  condition: "is",  values: ["Android"],  target_url: "https://play.google.com/..." },
      // A/B: 20% see new landing page
      { type: "ab",  weight: 20,                             target_url: "https://new.example.com" },
      // Business hours (Eastern Time)
      {
        type: "time",
        timezone: "America/New_York",
        startTime: "09:00",
        endTime: "17:00",
        activeDays: [1, 2, 3, 4, 5],
        target_url: "https://example.com/chat-now",
      },
    ],
  }),
});
console.log(qr.short_url); // https://r.qrs.bd/...

Handling Rate Limits

typescript
async function apiWithRetry(path: string, init?: RequestInit, retries = 3) {
  for (let i = 0; i < retries; i++) {
    const res = await fetch(`https://qrs.bd/api/v1${path}`, {
      ...init,
      headers: {
        Authorization: `Bearer ${process.env.QRSBD_API_KEY}`,
        "Content-Type": "application/json",
        ...init?.headers,
      },
    });

    if (res.status === 429) {
      const reset = Number(res.headers.get("X-RateLimit-Reset") ?? 0);
      const wait  = Math.max(reset - Date.now(), 1_000);
      console.log(`Rate limited โ€” retrying in ${wait}ms`);
      await new Promise(r => setTimeout(r, wait));
      continue;
    }

    return res.json();
  }
  throw new Error("Max retries exceeded");
}