Build on TangoQR
Create codes, repoint them, and catch every scan. One REST API. Your servers, your workflow.
Authentication
The API is a Pro feature. Mint a key in your dashboard, then send it as a Bearer token. You see the key once, so store it somewhere safe. Keys carry read and write scopes; mint a read-only key for anything that only needs to look.
curl https://tangoqr.com/api/public/v1/codes \ -H "Authorization: Bearer tq_live_your_key_here"
Base URL: https://tangoqr.com/api/public/v1
Errors
Every error returns the same envelope with a stable type you can branch on.
{ "error": { "type": "plan_required", "message": "The TangoQR API requires a Pro plan." } }
| Status | type | When |
|---|---|---|
| 401 | unauthorized | Missing or invalid key. |
| 403 | plan_required | The account is not on Pro. |
| 403 | insufficient_scope | The key lacks the scope for that action. |
| 404 | not_found | No such resource on your account. |
| 422 | invalid_request | The request failed validation. |
| 429 | (none) | Rate limited. Honor the Retry-After header. |
Rate limits
120 requests per minute and 5,000 per hour, per key. Over the limit you get a 429 with a Retry-After header. Scans on your printed codes are never rate limited, so your audience always gets through.
Codes
A code is a redirect you control. Print the image once, change where it points whenever you want.
Create a code
curl -X POST https://tangoqr.com/api/public/v1/codes \
-H "Authorization: Bearer tq_live_…" \
-H "Content-Type: application/json" \
-d '{"destination_url":"https://example.com/menu","label":"Spring menu"}'
{
"token": "aBc123_-",
"short_url": "https://tgo.sh/aBc123_-",
"destination_url": "https://example.com/menu",
"label": "Spring menu",
"scan_count": 0,
"frame_style": null,
"module_shape": null,
"eye_shape": null,
"colors": { "foreground": null, "background": null },
"image": {
"svg": "https://tangoqr.com/r/aBc123_-.svg",
"png": "https://tangoqr.com/r/aBc123_-.png"
},
"disabled": false,
"created_at": "2026-06-08T19:48:18Z",
"updated_at": "2026-06-08T19:48:18Z"
}
Optional fields: label, foreground_color, background_color, frame_style (one of none, scan_me, rounded), module_shape (square, rounded, dot), and eye_shape (square, rounded, leaf, circle). Shapes apply to the SVG image; the PNG renders square. The short_url is what the QR encodes. The image URLs are cached and safe to hotlink.
Repoint a code
This is the whole point. Send a new destination; every printed code follows instantly.
curl -X PATCH https://tangoqr.com/api/public/v1/codes/aBc123_- \
-H "Authorization: Bearer tq_live_…" \
-H "Content-Type: application/json" \
-d '{"destination_url":"https://example.com/summer-menu"}'
List, fetch, and image
GET /codes?page=1&per=25 # paginated, newest first GET /codes/aBc123_- # one code GET /codes/aBc123_-/image.png # 302 to the rendered image (.svg or .png)
Disable and enable
Need to pause a code? Disable it and the redirect returns 410 until you turn it back on. There is no hard delete on purpose. Deleting a token would break every code you already printed, so disable is the reversible way to take one offline.
POST /codes/aBc123_-/disable POST /codes/aBc123_-/enable
Webhooks
Register an https endpoint in your dashboard and we POST a scan.recorded event on every scan of your codes. You get the signing secret once. For privacy we send the country and a coarse device class, never the raw IP or user agent.
{
"id": "evt_84217",
"type": "scan.recorded",
"created": "2026-06-08T14:30:45Z",
"data": {
"code": {
"token": "aBc123_-",
"short_url": "https://tgo.sh/aBc123_-",
"destination_url": "https://example.com/summer-menu",
"label": "Spring menu"
},
"scan": {
"scanned_at": "2026-06-08T14:30:45Z",
"country_code": "US",
"device_class": "mobile"
}
}
}
Verify the signature
Each delivery carries a Tango-Signature: t=<unix>,v1=<hex> header. Recompute the HMAC-SHA256 of "<t>.<raw body>" with your secret and compare. Reject anything older than a few minutes to block replays.
// Node.js (Express)
const crypto = require("crypto")
function verify(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map((kv) => kv.split("=")))
const expected = crypto
.createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`)
.digest("hex")
const a = Buffer.from(expected), b = Buffer.from(parts.v1 || "")
// Compare lengths first: timingSafeEqual throws on a length mismatch.
const ok = a.length === b.length && crypto.timingSafeEqual(a, b)
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300
return ok && fresh
}
Retries
Return any 2xx to acknowledge. Non-2xx and timeouts are retried with backoff. An endpoint that keeps failing is disabled automatically, and you can re-enable it from the dashboard once it is healthy.
MCP (for AI agents)
TangoQR speaks the Model Context Protocol, so assistants like Claude can create codes, repoint them, and read scan analytics for you. Two endpoints, both streamable HTTP. Paste the URL into your MCP client.
Pro server (your account)
https://tangoqr.com/mcp exposes the seven code-management and analytics tools, scoped to your account. Pro only. Connect with OAuth: your client registers itself, you approve it once on a TangoQR consent screen, and it gets a token. Clients that prefer a static credential can send a Pro tq_live_ API key as the Bearer token instead.
{
"mcpServers": {
"tangoqr": { "url": "https://tangoqr.com/mcp" }
}
}
Tools: create_qr_code, list_qr_codes, get_qr_code, update_qr_code, disable_qr_code, enable_qr_code, get_scan_analytics.
Free tools (no account)
https://tangoqr.com/mcp/free is anonymous, no key. The same utilities as our free tools page: generate a QR (URL, WiFi, vCard), calculate print size, and decode or inspect an existing QR image. These make static codes; for codes you can change later and track, use the Pro server.
Versioning
The version is in the path (/api/public/v1). We add fields without bumping it, so ignore fields you do not recognize. Breaking changes ship under a new version with notice.
Questions? Contact us.