API documentation
Everything in the dashboard is available over a REST API and a native MCP server, metered
in the same prepaid credits. Base URL: https://www.taproute.io
Authentication
Create API keys in Dashboard → API. Every request needs a bearer
header — keys start with tr_ and are shown exactly
once at creation.
Authorization: Bearer tr_...
curl https://www.taproute.io/api/v1/links \ -H "Authorization: Bearer tr_your_key_here"
Endpoints
| Method | Path | Body | Returns |
|---|---|---|---|
| POST | /api/v1/links | {url, slug?, rules?} | 201 · {id, slug, shortUrl} |
| GET | /api/v1/links | — | {links: [...]} |
| PATCH | /api/v1/links/:id | {url?, status?, rules?} | {id, slug, url, status} |
| DELETE | /api/v1/links/:id | — | {ok: true} |
| GET | /api/v1/wallet | — | {balance, inGrace, …} |
| GET | /api/v1/analytics?days=7|30|90 | — | {total, timeseries, byCountry, byDevice, topLinks} |
Links
curl -X POST https://www.taproute.io/api/v1/links \
-H "Authorization: Bearer tr_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/menu",
"slug": "summer-menu",
"rules": { "ios": "https://apps.apple.com/app/id123" }
}'
# 201 Created
# { "id": "…", "slug": "summer-menu", "shortUrl": "https://tapr.io/summer-menu" } # Change the destination after printing — the QR keeps working
curl -X PATCH https://www.taproute.io/api/v1/links/LINK_ID \
-H "Authorization: Bearer tr_..." \
-H "Content-Type: application/json" \
-d '{ "url": "https://example.com/winter-menu", "status": "active" }' Wallet
curl https://www.taproute.io/api/v1/wallet -H "Authorization: Bearer tr_..."
# { "balance": 48210, "inGrace": false, "graceExhausted": false, … } Analytics
curl "https://www.taproute.io/api/v1/analytics?days=30" -H "Authorization: Bearer tr_..."
# { "total": 1289, "timeseries": [{ "date": "2026-07-01", "count": 42 }, …],
# "byCountry": […], "byDevice": […], "topLinks": […] } The rules object
Optional per-link routing, evaluated at the edge on every scan. All fields are optional;
anything unmatched falls through to the link's main url.
{
"ios": "https://apps.apple.com/app/id123456",
"android": "https://play.google.com/store/apps/details?id=com.example",
"schedule": [
{ "start": "2026-07-01T08:00:00Z", "end": "2026-07-01T11:00:00Z", "url": "https://example.com/breakfast" },
{ "start": "2026-07-01T11:00:00Z", "url": "https://example.com/lunch" }
]
} ios/android— device-specific destinations (App Store / Play Store deep links).schedule— array of time windows with ISO 8601 datetimes.startandendare each optional (open-ended windows). The first matching window wins.
Webhooks
Add endpoints in Dashboard → API to receive a POST for every scan, delivered from the edge in real time. Each request carries two headers:
| X-Taproute-Event | Event name, currently scan |
| X-Taproute-Signature | t=<ms>,v1=<hex hmac-sha256(secret, t + "." + body)> |
Payload
{
"event": "scan",
"data": {
"linkId": "9f1c…",
"slug": "summer-menu",
"domain": "_",
"country": "DE",
"device": "ios",
"ts": 1751884800000
}
} Verifying signatures (Node)
import { createHmac, timingSafeEqual } from 'node:crypto';
// signature header: t=<ms>,v1=<hex>
export function verifyTaproute(secret, signature, rawBody) {
const parts = Object.fromEntries(signature.split(',').map((p) => p.split('=')));
const expected = createHmac('sha256', secret)
.update(`${parts.t}.${rawBody}`)
.digest('hex');
return timingSafeEqual(Buffer.from(parts.v1, 'hex'), Buffer.from(expected, 'hex'));
} MCP server
A native Model Context Protocol server (Streamable HTTP) lets AI agents — Claude, Cursor, and friends — manage your QR codes directly. Authenticate with the same API keys:
{
"mcpServers": {
"taproute": {
"url": "https://www.taproute.io/mcp",
"headers": { "Authorization": "Bearer tr_..." }
}
}
} | create_link | Create a dynamic QR link, returns the short URL to encode |
| list_links | List all links with status and destinations |
| update_link | Change destination, rules, or status (active | paused) |
| delete_link | Delete a link permanently |
| get_qr | Fetch the QR code for a slug as SVG |
| wallet_balance | Credit balance and grace-period status |
| analytics_summary | Scan analytics for 7 / 30 / 90 days |
Errors
Standard HTTP status codes with a JSON body of the shape { "message": "…" }.
| 400 | Malformed input — missing url, invalid slug, bad JSON |
| 401 | Missing or invalid API key |
| 404 | Link not found (or belongs to another workspace) |
| 409 | Slug already taken |
| 422 | Destination rejected by safety screening (malware / phishing lists) |
| 429 | Link-creation rate limit reached — retry in an hour |