API · CLI · MCP · SKILL

Let your agents make the codes.

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.

REST API

One call, one code

A single POST returns a live hopp.qr-redirects.be link and its PNG/SVG. Reference →

CLI

hopp create

Auth with one env var, human output by default, --json for machines. Commands →

MCP SERVER

Drop it into an agent

Exposes create_code, point_code, list_codes, get_stats as tools. Setup →

CLAUDE CODE

Use it as a skill

Install the Hopp skill so Claude Code can make and re-point codes for you.

Get the skill
WEBHOOKS · PRO

Get pinged on every scan

Pro codes POST a signed payload on each scan - verify X-Hopp-Signature and trigger your automation. Reference →

Command line
# 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
REST / agent tool call
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"
}
Reference

Full API, CLI & agent reference

Everything the dashboard does, scriptable. The same quotas, safety checks and daily cap apply.

Authentication

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.

REST API

JSON over HTTPS. All /v1 endpoints require the Bearer key and act on your codes only.

POST /v1/codes

Create a dynamic QR code. Returns the short link and QR images; you can re-point the destination later without reprinting.

FieldTypeNotes
url requiredstringdestination; must be http(s)://…
namestringoptional label shown in your dashboard
webhook_urlstringoptional · 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":… }

GET /v1/codes

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" } ] }

PATCH /v1/codes/:slug

Re-point the destination and/or set a scan webhook. Send at least one of the two fields.

FieldTypeNotes
urlstringnew destination (counts against your change quota)
webhook_urlstring | nullPro · 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

GET /v1/codes/:slug/stats

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 }

Prepaid credits & automated print orders

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.

GET /v1/credits

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" } ] }

POST /v1/credits/topup

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 }

GET · POST /v1/credits/autorecharge

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

GET /v1/products

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" } ] }

POST /v1/orders/quote

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" }

POST /v1/orders

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" }

GET /v1/orders · /v1/orders/:id

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" }

GET /api/healthz

Liveness probe and which integrations are configured. No auth.

// 200 OK  { "ok": true, "integrations": { "mailgun":true, "stripe":true } }

Scan webhooks PRO

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;

CLI @hopp/cli

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 …
CommandDoes
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 creditsprepaid 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 BEprice 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)

MCP server

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_•••" } } } }

Claude Code skill

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"

Quotas & limits

 FreePro · €9.99/yr
Redirects1,000 / day (then a short countdown page)1,000 / day
Destination changes1 per code per monthunlimited
API · CLI · MCP · skillincludedincluded
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

Errors are JSON: { "error": "code", "message"?: "…" } with the matching HTTP status.

StatuserrorMeaning
401invalid_api_keykey missing or invalid
400invalid_urlurl must be http(s)://…
400nothing_to_updatePATCH with neither url nor webhook_url
400invalid_webhook_urlwebhook_url must be HTTPS (or null to clear)
403change_limitfree monthly change used - unlock or upgrade
403pro_requiredwebhooks are a Pro feature
400invalid_recipientorder recipient needs name, line1, city, postal & a 2-letter country
400idempotency_key_requiredPOST /v1/orders needs an Idempotency-Key header
400invalid_content / empty_series / no_productorder content/size could not be turned into a printable sheet
402insufficient_creditstop up - response carries required_cents & topup_url
404not_foundno such code or order on your account
502fulfilment_failedprint partner rejected the order - credits auto-refunded

Create a code, re-point it later - that's the whole loop.

Grab a key, then drive it from the API, the CLI, an MCP agent, or Claude Code.