Let your AI agent read the QR Suisse docs over MCP, then create a dynamic QR code with the API
AI coding assistants write better integration code when they can read the real documentation instead of guessing from memory. In this tutorial you will: connect an MCP client (Claude Code or VS Code) to a public, read-
AI coding assistants write better integration code when they can read the real documentation instead of guessing from memory. In this tutorial you will:
- connect an MCP client (Claude Code or VS Code) to a public, read-only MCP server that serves the QR Suisse API documentation;
- check what that server exposes, with nothing more than
curl; - create a dynamic QR code with the QR Suisse REST API, change its destination, and download it as SVG.
QR Suisse is a dynamic QR code generator hosted in Switzerland: the printed code points to a short redirect address, so you can change the destination later without reprinting. (It is not a generator for the Swiss QR-bill payment slip.)
What you need
- For part 1 and 2: nothing. The documentation server needs no account and no key.
- For part 3: a QR Suisse account on a Pro or Entreprise plan (the API is not available on the Free plan), and an API key with the
QR_CREATEandQR_READrights. -
curlandjq, or Node.js 18+ for the JavaScript version.
Part 1: connect your agent to the documentation server
The server lives at:
https://www.qrsuisse.ch/mcp/docs
It speaks MCP over Streamable HTTP, is stateless, and only accepts POST. It has no access to any QR Suisse account: it only searches and returns documentation pages.
Claude Code
claude mcp add --transport http qr-suisse-docs https://www.qrsuisse.ch/mcp/docs
Visual Studio Code: add this to .vscode/mcp.json in your project:
{
"servers": {
"qr-suisse-docs": {
"type": "http",
"url": "https://www.qrsuisse.ch/mcp/docs"
}
}
}
Other MCP clients that support remote HTTP servers work the same way: give them the URL.
The server exposes two tools:
| Tool | Input | What it returns |
|---|---|---|
qr_suisse_search_docs |
query: English keywords, 1 to 200 characters |
Up to 5 matching pages, best match first, each with page, title, url and an excerpt
|
qr_suisse_get_page |
page: an identifier from the search results |
The full Markdown text of the page |
Page identifiers come from a closed list: llms-txt, agents-md, auth-md, pricing-md, and api/<operationId> for each operation of the REST API (for example api/createQrCode).
Now ask your agent something concrete, for example:
Using the qr-suisse-docs tools, find how to create a URL QR code with the QR Suisse API, and write a Node.js script that reads the API key from the
QRSUISSE_API_KEYenvironment variable.
A well-behaved agent will call qr_suisse_search_docs with keywords such as "create QR code", then qr_suisse_get_page with api/createQrCode, and base its code on that page rather than on a guess.
Part 2: look under the hood with curl
You do not need an MCP client to see what the server returns. Every request is a single JSON-RPC message in a POST. Two details matter:
- send
Accept: application/json, text/event-stream(required by the Streamable HTTP transport); - do not send an
Originheader: the server rejects requests from web pages.
List the tools:
curl -sS https://www.qrsuisse.ch/mcp/docs \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | jq '.result.tools[].name'
Search the documentation:
curl -sS https://www.qrsuisse.ch/mcp/docs \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"qr_suisse_search_docs","arguments":{"query":"create dynamic QR code"}}}' \
| jq '.result.structuredContent.results[] | {page, title}'
Read one page:
curl -sS https://www.qrsuisse.ch/mcp/docs \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"qr_suisse_get_page","arguments":{"page":"api/createQrCode"}}}' \
| jq -r '.result.structuredContent.content'
The server allows 60 requests per minute per IP address, which leaves plenty of room for an agent session.
Part 3: create a dynamic QR code with the API
The REST API lives at https://app.qrsuisse.ch/api/v1. Its OpenAPI specification is public at https://www.qrsuisse.ch/openapi.json.
3.1 Create a key and keep it out of the chat
In the QR Suisse dashboard, a member with the "Manager of API keys" permission (or an administrator) creates the key and picks its rights. For this tutorial: QR_CREATE and QR_READ. The full key is shown once.
Give it to your shell without leaving it in your history:
read -rs QRSUISSE_API_KEY && export QRSUISSE_API_KEY
Never paste the key into a conversation with an assistant: the agent should read it from its environment or configuration.
Quick check, with no key needed: the API index.
curl -sS https://app.qrsuisse.ch/api/v1 | jq
3.2 Create the QR code
curl -sS -X POST https://app.qrsuisse.ch/api/v1/qr-codes \
-H "Authorization: Bearer $QRSUISSE_API_KEY" \
-H 'Content-Type: application/json' \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"type":"URL","label":"Spring flyer","destinationUrl":"https://www.example.ch/spring"}' \
| tee qr.json | jq '{id, slug, publicUrl, destinationUrl}'
The response (201) is the QR code. Abridged, with illustrative values:
{
"id": "cm2f8k3qz0001xyz9abcd1234",
"slug": "AbC123_-xy",
"publicUrl": "https://app.qrsuisse.ch/r/AbC123_-xy",
"destinationUrl": "https://www.example.ch/spring"
}
publicUrl is what the image encodes; destinationUrl is where people land.
Why the Idempotency-Key: if the request times out and you send it again with the same value and the same body within 24 hours, you get the same QR code back (header Idempotent-Replayed: true) instead of a duplicate. That matters because API creations count against a monthly quota.
3.3 Download the image
QR_ID=$(jq -r .id qr.json)
curl -sS -H "Authorization: Bearer $QRSUISSE_API_KEY" \
-o qr.svg "https://app.qrsuisse.ch/api/v1/qr-codes/$QR_ID/image?format=svg"
SVG is the format to use for print. For PNG, use format=png&size=512 (128 to 1024 pixels).
3.4 Change the destination after printing
This is the point of a dynamic QR code: the printed image stays the same.
curl -sS -X PATCH "https://app.qrsuisse.ch/api/v1/qr-codes/$QR_ID" \
-H "Authorization: Bearer $QRSUISSE_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"destinationUrl":"https://www.example.ch/summer"}' \
| jq '{publicUrl, destinationUrl}'
This call needs the QR_UPDATE right, so add it to your key if you want to try it. An update does not use any creation from the quota.
The same flow in Node.js
// create-qr.mjs (Node.js 18+, no dependency)
import { randomUUID } from "node:crypto";
import { writeFile } from "node:fs/promises";
const API = "https://app.qrsuisse.ch/api/v1";
const key = process.env.QRSUISSE_API_KEY;
if (!key) throw new Error("Set QRSUISSE_API_KEY in your environment.");
const auth = { Authorization: `Bearer ${key}` };
const created = await fetch(`${API}/qr-codes`, {
method: "POST",
headers: { ...auth, "Content-Type": "application/json", "Idempotency-Key": randomUUID() },
body: JSON.stringify({
type: "URL",
label: "Spring flyer",
destinationUrl: "https://www.example.ch/spring",
}),
});
if (!created.ok) {
const error = await created.json();
// Branch on error.error (stable code), never on error.message.
throw new Error(`${created.status} ${error.error}`);
}
const qr = await created.json();
console.log(qr.publicUrl);
const image = await fetch(`${API}/qr-codes/${qr.id}/image?format=svg`, { headers: auth });
if (!image.ok) throw new Error(`Image: ${image.status}`);
await writeFile("qr.svg", Buffer.from(await image.arrayBuffer()));
Run it with node create-qr.mjs.
Limits worth knowing
At the time of writing (API version 1.3.0, November 2026):
-
Creations per calendar month (UTC): 5 with Pro, 100 with Entreprise, then optional one-off creation packs.
GET /api/v1/usageshows the balance (it needs theQR_CREATEright). Reading, updating and downloading use nothing. -
Rate limits per key: 30 requests per minute overall, 8 creations per minute, and 10 PNG renders per minute per account. Responses carry
RateLimitandRateLimit-Policyheaders; on429, wait forRetry-After. -
Errors share one shape,
{ "error": "<code>", "message": "<text>" }. Code againsterror. - Keys can be rotated or revoked from the dashboard at any time. A key sent over plain HTTP is refused, and should be rotated.
Recap
- Point your agent at
https://www.qrsuisse.ch/mcp/docsso it reads the real documentation, without any account or key. - Keep the API key in the environment, never in the chat.
- Create with an
Idempotency-Key, download as SVG for print, and update the destination whenever the target page changes.
Server documentation and client configurations: QR Suisse MCP server Β· API reference: QR Suisse API documentation
Published by the team behind QR Suisse (Skyday SΓ rl).
Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.