Dev.to AI πŸ€– Ai πŸ‘ 0 πŸ“– 6 min read

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:

  1. connect an MCP client (Claude Code or VS Code) to a public, read-only MCP server that serves the QR Suisse API documentation;
  2. check what that server exposes, with nothing more than curl;
  3. 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_CREATE and QR_READ rights.
  • curl and jq, 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_KEY environment 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 Origin header: 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/usage shows the balance (it needs the QR_CREATE right). 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 RateLimit and RateLimit-Policy headers; on 429, wait for Retry-After.
  • Errors share one shape, { "error": "<code>", "message": "<text>" }. Code against error.
  • 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/docs so 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).

πŸ“° Read the original article on Dev.to AI

Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes β€” full credit and traffic to the original publisher.