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
๐ฆ 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.
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.
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 code examples 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.
Limit Free Starter Pro Business Monthly Price $0 $9 $29 $79 Dynamic QR Codes 2 15 Unlimited Unlimited Short Links 25 500 Unlimited Unlimited Scans / Month 500 10,000 100,000 500,000 Custom Domains 0 0 1 5 API Rate Limit 10/min 30/min 100/min 500/min Team Seats 1 1 1 5
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.
{
"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
Field Type Required Description namestringNo Human-readable label for this key workspace_idUUIDNo Target workspace. Defaults to your oldest active workspace
// 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
{ "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.
{
"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
Field Type Required Description target_urlstringYes Destination URL. Must be http(s)://. Internal IPs are rejected. titlestringNo Human-readable label slugstringNo Custom slug matching ^[a-z0-9_-]{3,32}$. Auto-generated if omitted. is_dynamicbooleanNo true (default) โ dynamic codes can be updated after creation template_typestringNo url ยท vcard ยท wifi ยท menu ยท review ยท social ยท app ยท event contentobjectNo Template-specific payload designobjectNo QR design configuration (colors, dot style, logo, etc.) smart_rulesarrayNo Conditional redirect rules. See Smart Rules. expires_atISO 8601No When the QR stops redirecting
// 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
// 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.
{ "data": { "id": "d4e5f6a7-...", "deleted": true } } Short Links 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.
List Short Links GET /api/v1/links
Get Short Link GET /api/v1/links/:id
Create Short Link POST /api/v1/links
Field Type Required Description target_urlstringYes Destination URL titlestringNo Human-readable label slugstringNo Custom slug ^[a-z0-9_-]{3,32}$. Auto-generated if omitted. is_dynamicbooleanNo Default true. Dynamic links can be updated. expires_atISO 8601No Expiry datetime
// 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"
}
} Update Short Link PATCH /api/v1/links/:id
{ "target_url": "https://new-destination.com", "is_active": false } Delete Short Link DELETE /api/v1/links/:id
{ "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
{
"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
Field Type Required Description domainstringYes Your 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.
{ "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.
{
"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.
{
"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.
{
"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.
{
"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.
{
"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:
{ "error": "error_code", "message": "Human-readable detail (optional)" } Code Status Description unauthorized401 API key missing, invalid, or revoked rate_limited429 Rate limit exceeded โ check X-RateLimit-Reset quota_exceeded403 Plan limit reached (QR codes, links, or domains) not_found404 Resource doesn't exist or belongs to another workspace invalid_request400 Validation failed; message field has details invalid_slug400 Custom slug doesn't match ^[a-z0-9_-]{3,32}$ slug_taken409 That slug is already in use invalid_domain400 Domain string is not a valid hostname domain_taken409 Domain is already registered domain_quota_reached403 Plan domain limit reached domain_not_found404 Domain doesn't exist in this workspace missing_domain400 domain field is required missing_id400 id query parameter is required no_workspace400 Your account has no active workspace workspace_not_found404 Specified workspace_id doesn't exist or isn't yours query_failed500 Unexpected server error create_failed500 Unexpected error during creation update_failed500 Unexpected error during update delete_failed500 Unexpected error during deletion
Code Examples TypeScript Python Php cURL
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 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 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");
}