FOR DEVELOPERS AND AI TOOLS
Make and change QR codes from AI apps and your own code
HueQR has an MCP server, so Claude, ChatGPT and Cursor can create dynamic QR codes, change where they go and read their scans when you ask. It also has a REST API for your own scripts and apps. Both come with Pro and Organisation and work on the same codes you see in the app.
Connect an AI app with MCP
MCP (Model Context Protocol) is how AI apps use other products for you. Add HueQR once, then ask in plain words: "make a QR code for our spring menu and file it under Harbor Yoga", "point the window poster code at the new booking page", "which of my codes got the most scans this month?"
https://hueqr.com/mcp
Claude and ChatGPT
In the app's connector settings, add a custom connector and paste the address above. You are sent to HueQR to sign in and press Allow. That is all: no key to copy. You can remove the app's access anytime in HueQR under API and MCP.
Claude Code
claude mcp add --transport http hueqr https://hueqr.com/mcp
Cursor and other tools with a config file
{
"mcpServers": {
"hueqr": { "url": "https://hueqr.com/mcp" }
}
}A tool that cannot open a sign-in page can send an API key instead, as the header Authorization: Bearer hq_live_...
What the AI app can do
- Create a code, or up to 50 at once, in a saved brand template
- Change where a code goes, rename it, pause it, resume it or delete it
- Set smart rules: "send iPhones to the App Store and Android to Google Play", "show the lunch menu from 12 to 4"
- Show a code's QR image and its scans by day, phone and city
- List and add client workspaces, and list your domains and templates
Every link is checked for safety, exactly as in the app. A read-only connection can look but not change anything.
API keys
The account owner makes keys in the app under API and MCP. A key is shown once, so copy it then. Each key is read and write or read only, and can be limited to one client workspace. Revoking a key stops it at once.
Send the key as a bearer token on every request. Keep keys on your server: anyone holding one can change your codes.
curl https://hueqr.com/api/v1/codes \ -H "Authorization: Bearer hq_live_..."
REST API
The base address is https://hueqr.com/api/v1. Requests and answers are JSON. Lists page with next_cursor. The same reference is available as OpenAPI 3.1.
Create a code
curl -X POST https://hueqr.com/api/v1/codes \
-H "Authorization: Bearer hq_live_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Spring menu",
"destination": "https://example.com/menu",
"placement": "Window poster"
}'{
"id": "4b1f0c1e-...",
"name": "Spring menu",
"type": "website",
"status": "live",
"destination": "https://example.com/menu",
"short_url": "https://hueqr.com/Xk3pQ",
"domain": "hueqr.com",
"placement": "Window poster",
"client_id": null,
"created_at": "2026-11-06T09:30:00Z",
"updated_at": "2026-11-06T09:30:00Z"
}Change where it goes, after printing
curl -X PATCH https://hueqr.com/api/v1/codes/4b1f0c1e-... \
-H "Authorization: Bearer hq_live_..." \
-H "Content-Type: application/json" \
-d '{ "destination": "https://example.com/summer-menu" }'Every endpoint
GET
/codesList codes, newest first
QUERY
- limit
- 1 to 100, default 25.
- cursor
- next_cursor from the last page.
- client_id
- Only one client's codes.
- status
- live, paused or blocked.
- type
- Only one type of code.
- q
- Only names containing this text.
RETURNS { data: Code[], next_cursor }
POST
/codesWRITE KEYCreate a code
BODY
- name
- A name to recognise the code by. Required.
- type
- What it opens: website, vcard, wifi, text, links, email, sms, phone. Default website.
- destination
- Website codes: the link it sends people to.
- placement
- Where it will be printed, to compare placements in analytics.
- client_id
- File it under this client workspace. It then uses the client's domain.
- domain
- A connected domain for the short link. Default: the client's domain, else hueqr.com.
- template_id
- Use a saved brand template's design. Default: plain black.
- design
- Or send a design object (colors, shapes) to style it directly.
- content
- Non-website types: the Wi-Fi details, text, links, email, SMS or phone number.
- utm
- true to add UTM tags to the destination.
- rules
- Website codes: smart rules to set up with the code, in the same shape as PUT /codes/{id}/rules.
RETURNS The new Code, with its short_url.
POST
/codes/bulkWRITE KEYCreate up to 100 codes
BODY
- codes
- A list of codes, each with the same fields as POST /codes.
RETURNS { created, failed, results: [{ index, ok, code | error }] }
GET
/codes/{id}Get one code
RETURNS Code
PATCH
/codes/{id}WRITE KEYChange a code
BODY
- destination
- A new link. Printed codes keep working.
- name
- A new name.
- placement
- A new placement.
- status
- live or paused.
- client_id
- Move it to a client, or null for none.
RETURNS The updated Code.
DELETE
/codes/{id}WRITE KEYDelete a code
RETURNS { id, deleted: true }. Anything printed with it stops working.
GET
/codes/{id}/qrGet the QR image
QUERY
- format
- svg, png, pdf, eps. Default svg.
- size
- PNG width in pixels, 128 to 4096. Default 1024.
RETURNS The image file, in the code's saved design.
GET
/codes/{id}/scansGet scan analytics
QUERY
- days
- 1 to 90, default 30.
RETURNS { scans, unique_scanners, previous_period_scans, by_day, devices, cities }
GET
/codes/{id}/rulesGet smart rules
RETURNS { code_id, default_destination, timezone, rules: [{ id, conditions, destination, enabled, description }] }
PUT
/codes/{id}/rulesWRITE KEYSet smart rules
BODY
- rules
- The full list, in order: the first enabled rule that matches a scan wins. Each is { conditions: [...], destination, enabled }. An empty list removes every rule.
RETURNS The rules as saved. Website codes only.
GET
/placesFind place ids for location rules
QUERY
- q
- The start of a country, region or city name.
RETURNS { data: [{ id, name, in, level }] }
GET
/clientsList client workspaces
RETURNS { data: Client[] }
POST
/clientsWRITE KEYAdd a client workspace
BODY
- name
- The client's name. Required.
- website
- Their website, for brand match.
- domain
- A connected domain for their codes.
RETURNS The new Client.
GET
/domainsList domains
RETURNS { data: [{ hostname, status, default }] }
GET
/templatesList brand templates
RETURNS { data: [{ id, name }] }
GET
/usageGet plan usage
RETURNS { plan, codes, scans_this_month, clients, requests_per_minute }
Smart rules
Smart rules send some scans somewhere else: by device, place, time of day, weekday, phone language, date or how many scans the code has had. They are checked in order and the first one that is on and matches wins. A scan that matches no rule goes to the code's main destination. A code can have up to 20 rules, on website codes only.
PUT /codes/{id}/rules replaces the whole list, so to change one rule, read the list, change it and send it all back. An empty list removes every rule.
curl -X PUT https://hueqr.com/api/v1/codes/4b1f0c1e-.../rules \
-H "Authorization: Bearer hq_live_..." \
-H "Content-Type: application/json" \
-d '{
"rules": [
{
"conditions": [{ "type": "device", "device": "ios" }],
"destination": "https://apps.apple.com/app/id123"
},
{
"conditions": [
{ "type": "weekday", "days": ["sat", "sun"] },
{ "type": "time", "from": "10:00", "to": "15:00" }
],
"destination": "https://example.com/brunch"
}
]
}'Every condition on a rule has to match, and a rule uses each type once at most. Times, weekdays and dates are read in your account's timezone, which the rules come back with.
- device
- { "type": "device", "device": "ios" | "android" | "desktop" }
- location
- { "type": "location", "places": ["US", "CA-ON", "CA-ON/toronto"] } with place ids from the places search. Countries are exact; regions and cities are approximate.
- time
- { "type": "time", "from": "12:00", "to": "16:00" } in the account's timezone. A window may pass midnight.
- weekday
- { "type": "weekday", "days": ["sat", "sun"] }
- language
- { "type": "language", "languages": ["es", "pt"] } matched against the phone's first language.
- dates
- { "type": "dates", "from": "2026-12-01", "to": "2026-12-31" } inclusive; either end may be left out.
- scans
- { "type": "scans", "first": 100 } for the first 100 scans, or { "type": "scans", "after": 100 } from then on.
Location rules use place ids. Find them with GET /places?q=toronto, which answers with ids like CA-ON/toronto. Every rule's link gets the same safety check as the code's own.
Errors and limits
An error is always { "error": { "code", "message" } } with a matching status. The message is written for a person, so it is safe to show.
| Status | Code | What it means |
|---|---|---|
| 400 | invalid_request | A field is missing or has a value that is not allowed. |
| 401 | unauthorized | No key, or a key that is wrong or revoked. |
| 403 | plan_required | The account is not on Pro or Organisation. |
| 403 | read_only_key | A read-only key tried to change something. |
| 404 | not_found | No code, client or template with that id on this account. |
| 422 | invalid_code | The code could not be made as sent. The message says why. |
| 400 | invalid_rules | A smart rule is not complete or not valid. The message names the rule and says why. |
| 422 | link_blocked | The link was flagged as unsafe, so it was refused. |
| 422 | code_limit | The batch would go over the plan's number of codes. |
| 429 | rate_limited | Too many requests in a minute. Wait and try again. |
Each key can make 60 requests a minute on Pro, and 300, 600, 1,200 on Organisation 500, 1,000, 2,500. The API and the MCP server never sit in the path of a scan, so a busy script cannot slow your codes down.
Not in the API yet: PDF codes are made in the app. Tell us what you need at hello@hueqr.com.