QRMaple API v1
Available on Pro and Premium. Anyone can read this page, but API keys can only be created on those plans.
A REST API for managing your dynamic QR codes and short links programmatically: create, read, update, delete, and pull scan stats.
See plan limits on the pricing page.
Base URL
https://qrmaple.com/api/v1
Authentication
Every request needs an `Authorization: Bearer <token>` header. There is no cookie-session access to this API — only an API key works.
Create a key from the dashboard's "API keys" screen. The full key is only ever shown once, right after creation.
Quickstart
curl -X POST https://qrmaple.com/api/v1/links \
-H "Authorization: Bearer qrm_live_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"destination":"https://example.com/landing","title":"Spring promo"}'
{
"id": "018f3a2e-...-b1c2",
"code": "aB3xQ9z",
"destination": "https://example.com/landing",
"title": "Spring promo",
"domain": null,
"expires_at": null,
"active": true,
"created_at": "2026-09-24T12:00:00.000Z"
}
curl https://qrmaple.com/api/v1/links \
-H "Authorization: Bearer qrm_live_xxxxxxxxxxxxxxxx"
curl https://qrmaple.com/api/v1/links/018f3a2e-...-b1c2 \
-H "Authorization: Bearer qrm_live_xxxxxxxxxxxxxxxx"
curl -X PATCH https://qrmaple.com/api/v1/links/018f3a2e-...-b1c2 \
-H "Authorization: Bearer qrm_live_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"destination":"https://example.com/new-landing"}'
curl "https://qrmaple.com/api/v1/links/018f3a2e-...-b1c2/stats?from=2026-09-01&to=2026-09-30" \
-H "Authorization: Bearer qrm_live_xxxxxxxxxxxxxxxx"
curl -X DELETE https://qrmaple.com/api/v1/links/018f3a2e-...-b1c2 \
-H "Authorization: Bearer qrm_live_xxxxxxxxxxxxxxxx"
Endpoints
| Method | Path | |
|---|---|---|
| GET | /api/v1/links | List links, paginated. |
| POST | /api/v1/links | Create a new dynamic link. |
| GET | /api/v1/links/:id | Get a single link. |
| PATCH | /api/v1/links/:id | Update a link (destination, title, expiry, active). |
| DELETE | /api/v1/links/:id | Delete a link (logical delete — active is set to false). |
| GET | /api/v1/links/:id/stats | Get scan stats for a link, optionally bounded by day. |
Pagination
GET /links accepts `limit` (1-100, default 20) and `cursor` (an opaque value from the previous response's `next_cursor`). `next_cursor` is null on the last page.
curl "https://qrmaple.com/api/v1/links?limit=50" \
-H "Authorization: Bearer qrm_live_xxxxxxxxxxxxxxxx"
# {"data": [...], "next_cursor": "eyJjcmVhdGVkX2F0Ijoi..."} -- pass next_cursor back as ?cursor= for the next page
Errors
Every error response has the shape `{"error": {"code": "...", "message": "..."}}`. The HTTP status always matches the code:
{
"error": {
"code": "not_found",
"message": "link not found"
}
}
| code | |
|---|---|
unauthorized | 401 — missing, invalid, or revoked Bearer token. |
forbidden_scope | 403 — this token isn't an API key (it's a different kind of access token). |
tier_required | 403 — the workspace's plan no longer includes API access (e.g. after a refund/downgrade). |
limit_reached | 402 — a plan limit (e.g. dynamic links) was reached. |
rate_limited | 429 — too many requests; see Rate limits below. |
validation_error | 400 — the request body or query parameters failed validation. |
not_found | 404 — the resource doesn't exist, or belongs to a different workspace. |
conflict | 409 — the requested code is reserved or already in use. |
Rate limits
60 requests per minute, per API key. A 429 response includes a `Retry-After` header (seconds) plus `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` headers.
OpenAPI
Machine-readable spec: openapi.json
limit: 1-100, default 20.