Mosaqo API for developers
A REST API over the same workspace you use in the app. Everything below works with a single API key and any HTTP client.
What you can do
- Create QR codes from your own records — one at a time, or ten thousand from a CSV.
- Change where a printed code points, in one call, without reprinting anything.
- Download the finished card as PNG, SVG or PDF and attach it to a CRM record.
- Read scans and aggregate analytics, filtered by your workspace's privacy rules.
- Receive signed webhooks when codes are created, published, changed or archived.
Quickstart
Create a key in Bulk & API inside your workspace, tick the qr:write and qr:read scopes, and copy the secret — it is shown once.
Check the key and learn which workspace it opens:
curl https://api.mosaqo.app/v1/public-api/me \
-H "Authorization: Bearer $MOSAQO_KEY"Create a dynamic QR code:
curl -X POST https://api.mosaqo.app/v1/public-api/qr \
-H "Authorization: Bearer $MOSAQO_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Autumn campaign",
"mode": "dynamic",
"contentType": "url",
"content": { "targetUrl": "https://example.com/autumn" }
}'Publish it, which is what makes the redirect resolve, then fetch the image you can print:
curl -X POST https://api.mosaqo.app/v1/public-api/qr/$QR_ID/publish \
-H "Authorization: Bearer $MOSAQO_KEY"
curl https://api.mosaqo.app/v1/public-api/qr/$QR_ID/image?format=png&size=2048 \
-H "Authorization: Bearer $MOSAQO_KEY" -o campaign.pngMonths later, when the campaign moves, retarget the same printed code without reprinting it:
curl -X POST https://api.mosaqo.app/v1/public-api/qr/$QR_ID/destination \
-H "Authorization: Bearer $MOSAQO_KEY" \
-H "Content-Type: application/json" \
-d '{ "url": "https://example.com/winter" }'Authentication
Send your secret either way. Both are supported for the whole of v1.
Authorization: Bearer mosaqo_xxx
X-Mosaqo-API-Key: mosaqo_xxxA key belongs to exactly one workspace, so paths do not need a workspace id: /v1/public-api/qr is enough. The older /v1/public-api/workspaces/{workspaceId}/qr form still works.
| Scope | Grants |
|---|---|
qr:read | List and read QR codes, folders and templates |
qr:write | Create, update, retarget, publish, archive and delete |
exports:read | Download rendered cards |
analytics:read | Aggregate analytics and individual scans |
bulk:write | Create and read bulk jobs |
webhooks:write | Manage webhook subscriptions |
Keys can be revoked, given an expiry date, and restricted to an IP allow-list. A key rejected by the allow-list is told so explicitly, rather than looking invalid.
Pagination
Lists use keyset pagination. Pass pagination.nextCursor back unchanged; rows never repeat or vanish because something was edited mid-sync, which matters for an overnight sync.
curl "https://api.mosaqo.app/v1/public-api/qr?limit=50&cursor=$CURSOR" \
-H "Authorization: Bearer $MOSAQO_KEY"To poll for changes rather than walk everything, add updatedSince=2026-08-05T00:00:00Z.
Errors
Every failure carries a stable code to branch on, a human-readable message, and a requestId worth quoting to support.
{
"error": "This API key does not have the qr:write scope.",
"message": "This API key does not have the qr:write scope.",
"code": "insufficient_scope",
"details": { "required": "qr:write", "granted": ["qr:read"] },
"requestId": "req-42"
}| Status | Codes | What to do |
|---|---|---|
| 401 | invalid_api_key | The key is missing, revoked or expired. Ask the user to reconnect. |
| 403 | insufficient_scope, ip_not_allowed, workspace_mismatch, quota_exceeded | The key is valid but not allowed to do this. Reconnecting will not help. |
| 404 | not_found | No such record in this workspace. |
| 409 | idempotency_conflict, idempotency_in_progress | Reuse of a key with different input, or a first attempt still running. |
| 422 | validation_failed, invalid_cursor, approval_required, contrast_check_failed | Fix the input. Never retry unchanged. |
| 429 | rate_limited | Wait for Retry-After seconds. |
Rate limits
Every response carries your current budget, not just the ones that are rejected:
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 574
X-RateLimit-Reset: 1785942718The limit is an anti-abuse guard, not a plan: it implies no quota, billing or upgrade path.
Idempotency
Idempotency-Key is optional. Send one — any unique string — and retrying the identical request within 24 hours replays the original response instead of acting twice. Reusing the same key with different input returns 409. Without a key the request simply runs, with no replay protection.
Next
- Webhooks — the event catalogue and how to verify a delivery.
- Recipes — Make, Zapier, n8n and CRM patterns.
- API reference — every endpoint, with a live console.