Create and re-point QR redirects from a script, a CI job, or an AI agent. A clean REST API, a one-line CLI, an MCP server, and a Claude Code skill - same rules and quotas as the dashboard.
A single POST returns a live hopp.qr-redirects.be link and its PNG/SVG. Reference →
Exposes create_code, point_code, list_codes, get_stats as tools. Setup →
Install the Hopp skill so Claude Code can make and re-point codes for you.
Get the skillPro codes POST a signed payload on each scan - verify X-Hopp-Signature and trigger your automation. Reference →
# create a code - the destination can change later hopp create --url https://example.com/launch \ --name "Launch poster" → https://hopp.qr-redirects.be/NHDGYUJKK https://hopp.qr-redirects.be/c/NHDGYUJKK/qr.png # re-point it after the campaign - no reprint hopp point NHDGYUJKK --url https://example.com/v2 → updated NHDGYUJKK -> https://example.com/v2 # list codes · pull scan stats as JSON hopp list hopp stats NHDGYUJKK --json
POST /v1/codes Authorization: Bearer hopp_live_••• { "url": "https://example.com/launch", "name": "Launch poster" } // 201 Created { "slug": "NHDGYUJKK", "short_url": "https://hopp.qr-redirects.be/NHDGYUJKK", "destination": "https://example.com/launch", "qr_png": "https://…/c/NHDGYUJKK/qr.png", "qr_svg": "https://…/c/NHDGYUJKK/qr.svg" }
Everything the dashboard does, scriptable. The same quotas, safety checks and daily cap apply.
Create a key in Settings → API keys (format hopp_live_…, shown once). Send it as a Bearer token on every request - the key carries your account, plan and quotas.
# base URL https://hopp.qr-redirects.be # every request Authorization: Bearer hopp_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # the CLI / MCP / skill read these env vars export HOPP_API_KEY=hopp_live_••• export HOPP_API=https://hopp.qr-redirects.be # optional, defaults to prod
No key (or a bad one) returns 401 invalid_api_key. Keys never expire until you revoke them in Settings.
JSON over HTTPS. All /v1 endpoints require the Bearer key and act on your codes only.
Create a dynamic QR code. Returns the short link and QR images; you can re-point the destination later without reprinting.
| Field | Type | Notes |
|---|---|---|
url required | string | destination; must be http(s)://… |
name | string | optional label shown in your dashboard |
webhook_url | string | optional · Pro · HTTPS scan webhook (see below) |
curl -s -X POST https://hopp.qr-redirects.be/v1/codes \ -H "Authorization: Bearer $HOPP_API_KEY" -H 'Content-Type: application/json' \ -d '{"url":"https://example.com/launch","name":"Launch poster"}' // 201 Created { "id":…, "slug":"NHDGYUJKK", "short_url":…, "destination":…, "qr_png":…, "qr_svg":… }
List every code you own, newest first.
curl -s https://hopp.qr-redirects.be/v1/codes -H "Authorization: Bearer $HOPP_API_KEY" // 200 OK { "data": [ { "slug", "title", "destination_url", "status", "webhook_url", "short_url", "created_at" } ] }
Re-point the destination and/or set a scan webhook. Send at least one of the two fields.
| Field | Type | Notes |
|---|---|---|
url | string | new destination (counts against your change quota) |
webhook_url | string | null | Pro · set an HTTPS endpoint, or null to clear |
curl -s -X PATCH https://hopp.qr-redirects.be/v1/codes/NHDGYUJKK \ -H "Authorization: Bearer $HOPP_API_KEY" -H 'Content-Type: application/json' \ -d '{"url":"https://example.com/v2"}' // 200 OK { "slug", "destination", "short_url", "webhook_url" } // when a webhook is set, "webhook_secret" is returned ONCE - store it to verify signatures
Total scan count for a code. (Cookieless, bot-filtered - same numbers as the dashboard.)
curl -s https://hopp.qr-redirects.be/v1/codes/NHDGYUJKK/stats -H "Authorization: Bearer $HOPP_API_KEY" // 200 OK { "slug": "NHDGYUJKK", "scans": 1421 }
Top up a prepaid balance once, then place sticker print orders straight from the API - no per-order checkout. Each order is priced (≥50% markup + destination VAT), debited from credits, and sent to our print partner. Top-ups are a VAT-free advance payment; every order gets its own VAT invoice. Two order shapes: a sheet of identical codes, or a series of unique codes (the /series feature) printed across sheets.
Prepaid balance and recent ledger entries.
curl -s https://hopp.qr-redirects.be/v1/credits -H "Authorization: Bearer $HOPP_API_KEY" // 200 OK { "balance_cents": 4250, "currency": "EUR", "recent": [ { "kind", "amount_cents", "balance_after_cents", "created_at" } ] }
Get a Stripe Checkout link to add credits. Body: amount_cents (500-200000, €5-€2000). The balance updates once payment completes.
curl -s -X POST https://hopp.qr-redirects.be/v1/credits/topup \ -H "Authorization: Bearer $HOPP_API_KEY" -H 'Content-Type: application/json' \ -d '{"amount_cents":5000}' // 200 OK { "checkout_url": "https://checkout.stripe.com/…", "amount_cents": 5000 }
Auto top-up from a saved card: when your balance drops below threshold_cents, Hopp charges the card for amount_cents off-session (a VAT-free advance payment) so orders never stall. Save the card once on the billing page (Stripe Checkout), then enable it here or in-app. GET returns the current config.
curl -s -X POST https://hopp.qr-redirects.be/v1/credits/autorecharge \ -H "Authorization: Bearer $HOPP_API_KEY" -H 'Content-Type: application/json' \ -d '{"enabled":true,"threshold_cents":2000,"amount_cents":5000}' // 200 OK { "enabled": true, "threshold_cents": 2000, "amount_cents": 5000, "has_card": true } // 400 { "error": "no_card" } // save a card on the billing page first
Active, priced sheet products (per-sheet counts) so you can reason about sizes. You pass a sticker size in mm; Hopp picks the cheapest fitting sheet for you.
curl -s https://hopp.qr-redirects.be/v1/products -H "Authorization: Bearer $HOPP_API_KEY" // 200 OK { "data": [ { "id", "name", "copies_per_sheet", "base_cost_cents", "currency" } ] }
Price an order without charging or saving anything. type is sheet or series; size is in mm (sheet 20-350, series 15-120). Returns the marked-up, VAT-aware total and whether your credits cover it.
curl -s -X POST https://hopp.qr-redirects.be/v1/orders/quote \ -H "Authorization: Bearer $HOPP_API_KEY" -H 'Content-Type: application/json' \ -d '{"type":"sheet","content":{"kind":"url","url":"https://ex.com"}, "design":{"primary":"#0C2F2B","shape":"square"},"size":{"width_mm":50,"height_mm":50}, "copies":2,"recipient":{"name":"Jane","line1":"1 Main St","city":"Gent","postal":"9000","country":"BE"}}' // 200 OK { "total_cents", "net_cents", "vat_cents", "shipping_cents", "sheets", "per_sheet", "credit_balance_cents", "sufficient" }
Place an order, debit credits, and fulfil straight to print. Requires an Idempotency-Key header - a retry with the same key returns the existing order and never double-charges. A series can pass inline items (or an existing series_id). On a hard fulfilment failure the debit is auto-refunded.
curl -s -X POST https://hopp.qr-redirects.be/v1/orders \ -H "Authorization: Bearer $HOPP_API_KEY" -H "Idempotency-Key: $(uuidgen)" -H 'Content-Type: application/json' \ -d '{"type":"series","items":["A-1","A-2","A-3"],"prefix":"https://ex.com/p", "design":{"primary":"#0C2F2B","shape":"square"},"size":{"width_mm":30,"height_mm":30}, "recipient":{"name":"Jane","line1":"1 Main St","city":"Gent","postal":"9000","country":"BE"}}' // 201 Created { "order_id", "status", "charged_cents", "sheets", "credit_balance_cents", "invoice_url" } // 402 { "error": "insufficient_credits", "balance_cents", "required_cents", "topup_url" }
List your credit-funded orders, or fetch one with live status, tracking and invoice link.
curl -s https://hopp.qr-redirects.be/v1/orders/po_XXXXXXX -H "Authorization: Bearer $HOPP_API_KEY" // 200 OK { "order_id", "status", "type", "sheets", "charged_cents", "tracking", "invoice_url" }
Liveness probe and which integrations are configured. No auth.
// 200 OK { "ok": true, "integrations": { "mailgun":true, "stripe":true } }
Set webhook_url on a code (at create or via PATCH); Hopp POSTs a signed JSON payload to it on every scan. Use it to notify, log, or kick off automation. The signing webhook_secret is returned once when you set the URL.
# delivery from Hopp -> your endpoint POST https://your-app.com/hooks/hopp X-Hopp-Signature: sha256=<hex HMAC-SHA256 of the raw body> { "event":"scan", "slug", "destination", "scanned_at", "referrer", "user_agent" }
// verify before acting (Node) import crypto from 'node:crypto'; const mac = 'sha256=' + crypto.createHmac('sha256', SECRET).update(rawBody).digest('hex'); if (!crypto.timingSafeEqual(Buffer.from(sigHeader), Buffer.from(mac))) return reject;
A tiny Node wrapper over the REST API. Human output by default; add --json for machine output. Reads HOPP_API_KEY (required) and HOPP_API (optional).
# run once with npx, or install globally npx @hopp/cli create --url https://example.com npm i -g @hopp/cli # then just: hopp …
| Command | Does |
|---|---|
hopp create --url <url> [--name <n>] [--json] | create a code → prints short_url + QR image URL |
hopp point <slug> --url <url> [--json] | re-point a code to a new destination |
hopp list [--json] | list your codes (slug · status · destination) |
hopp stats <slug> [--json] | scan count for a code |
hopp credits | prepaid balance + recent ledger |
hopp topup --amount <eur> | print a Stripe Checkout link to add credits |
hopp quote --type sheet --content <url> --w 5 --h 5 --copies 2 --to … --line1 … --city … --postal … --country BE | price a sheet of identical codes (size in cm) |
hopp quote --type series --items a,b,c [--series-id <id>] --prefix <url> --w 3 --h 3 --to … | price a series of unique codes |
hopp order … | same flags as quote; places the order & charges credits (Idempotency-Key auto) |
hopp orders [--json] | list your API (credit-funded) orders |
hopp autorecharge [--on|--off --threshold <eur> --amount <eur>] | show / set auto top-up (save card in-app first) |
A stdio Model Context Protocol server exposing seven tools - create_code, point_code, list_codes, get_stats, get_credits, quote_order, place_order - to any MCP-capable agent. So an agent can buy credits-funded sticker prints on its own.
# run it HOPP_API_KEY=hopp_live_••• node cli/mcp.mjs # wire it into an agent (e.g. Claude Desktop config) { "mcpServers": { "hopp": { "command": "node", "args": ["/path/to/cli/mcp.mjs"], "env": { "HOPP_API_KEY": "hopp_live_•••" } } } }
Install the skill so Claude Code can make, re-point and track codes when you ask in plain language. It drives the same REST API.
mkdir -p ~/.claude/skills/hopp curl -s https://hopp.qr-redirects.be/hopp-skill.md -o ~/.claude/skills/hopp/SKILL.md export HOPP_API_KEY=hopp_live_••• # then just ask: "make a QR for example.com I can re-point later"
| Free | Pro · €9.99/yr | |
|---|---|---|
| Redirects | 1,000 / day (then a short countdown page) | 1,000 / day |
| Destination changes | 1 per code per month | unlimited |
| API · CLI · MCP · skill | included | included |
| Scan webhooks | - | ✓ signed (HMAC-SHA256) |
| Custom domain · AI styling · CSV | - | ✓ |
Codes are host-scoped: a code resolves only on its own domain. Every redirect runs the same safety check as the UI.
Errors are JSON: { "error": "code", "message"?: "…" } with the matching HTTP status.
| Status | error | Meaning |
|---|---|---|
| 401 | invalid_api_key | key missing or invalid |
| 400 | invalid_url | url must be http(s)://… |
| 400 | nothing_to_update | PATCH with neither url nor webhook_url |
| 400 | invalid_webhook_url | webhook_url must be HTTPS (or null to clear) |
| 403 | change_limit | free monthly change used - unlock or upgrade |
| 403 | pro_required | webhooks are a Pro feature |
| 400 | invalid_recipient | order recipient needs name, line1, city, postal & a 2-letter country |
| 400 | idempotency_key_required | POST /v1/orders needs an Idempotency-Key header |
| 400 | invalid_content / empty_series / no_product | order content/size could not be turned into a printable sheet |
| 402 | insufficient_credits | top up - response carries required_cents & topup_url |
| 404 | not_found | no such code or order on your account |
| 502 | fulfilment_failed | print partner rejected the order - credits auto-refunded |
Grab a key, then drive it from the API, the CLI, an MCP agent, or Claude Code.