API · v1
API documentation
From your first request to statistics: examples ready to adapt.
Authentication
Create a key in your developer space. Send it only from your server in the Authorization header. Never embed it in a public page.
Authorization: Bearer <TOKEN>
Accept: application/json
Content-Type: application/json60 requests per minute, shared across the subscription owner’s keys, including their companies. On a 429 response, respect Retry-After. The key keeps its workspace and its creator’s permissions.
Existing qr:* keys remain compatible. New permissions separate each operation. No key can manage passwords or payments.
Add codes:read to receive full content in a mutation response. Otherwise, only the operation confirmation is returned. Statistics never include private content.
Every creation requires a stable Idempotency-Key. An identical retry returns the same code. Different content with the same key returns 409.
Available operations
| HTTP | Operation | Permission |
|---|---|---|
POST /api/v1/qr-images | QR for your emails and applications | images:render |
GET /api/v1/qr-codes/{id}/png | PNG and JPEG images | codes:export |
GET /api/v1/qr-codes/{id}/jpeg | PNG and JPEG images | codes:export |
POST /api/v1/print-sheets | PDF sheets through the API | prints:export |
GET /api/v1/qr-codes | List my codes | codes:read |
POST /api/v1/qr-codes | Create a code | codes:create |
GET /api/v1/qr-codes/{id} | Read a code | codes:read |
PATCH /api/v1/qr-codes/{id} | Change content and schedules | codes:update |
GET /api/v1/qr-codes/{id}/svg | Export the QR or its SVG composition | codes:export |
GET /api/v1/qr-codes/{id}/statistics | Read its counters | statistics:read |
PATCH /api/v1/qr-codes/{id}/state | Disable or reactivate links | codes:state |
DELETE /api/v1/qr-codes/{id} | Disable its link | codes:state |
Changing disabled also requires codes:state in the general PATCH. Only dynamic links can be disabled or reactivated.
DELETE disables the link; it does not trash the code or free a slot.
Create static or editable QR codes. Only editable content can be updated; DELETE disables an editable link.
History through the API
History returns identifiers, dates, sources and change categories without previous private contents. Permissions are separate and are not added to existing keys.
Use meta.revision from the list to restore. An intervening change returns 409: read history again before retrying. Access, dates and quotas remain enforced.
| HTTP | Permission |
|---|---|
GET /qr-codes/{id}/history | history:read |
GET /qr-codes/{id}/history/{version} | history:read + codes:read |
POST /qr-codes/{id}/history/{version}/restore | history:restore |
cURL
curl --fail-with-body "https://preprod.qrissimo.fr/api/v1/qr-codes/$QR_ID/history" \
-H "Authorization: Bearer $QRISSIMO_TOKEN" -H 'Accept: application/json'
curl --fail-with-body -X POST "https://preprod.qrissimo.fr/api/v1/qr-codes/$QR_ID/history/$VERSION_ID/restore" \
-H "Authorization: Bearer $QRISSIMO_TOKEN" \
-H 'Content-Type: application/json' \
-d "{\"revision\":\"$REVISION\"}"PNG and JPEG images
Keep the drawing, caption and arrow, or choose the QR only. Size refers to the longest side.
Requires codes:export. size: 512, 1024 or 2048 (default 1024), longest side. composition=1 keeps the drawing, caption and arrow; otherwise QR only. transparent=1: complete PNG only, QR margin always opaque. A download is counted after success and rechecking access. Shared limits: 10/minute/user, 20/minute/account holder, 30/minute/IP. JSON errors.
cURL
curl --fail-with-body 'https://preprod.qrissimo.fr/api/v1/qr-codes/42/png?size=1024&composition=1&transparent=1' \
-H "Authorization: Bearer $QRISSIMO_TOKEN" -o qr.png
curl --fail-with-body 'https://preprod.qrissimo.fr/api/v1/qr-codes/42/jpeg?size=2048&composition=1' \
-H "Authorization: Bearer $QRISSIMO_TOKEN" -o qr.jpgCaption and arrow placement
The caption and arrow stay outside the illustration and the QR quiet zone. Text adjusts to avoid splitting ordinary words.
caption_position: auto | top | bottom | left | right · caption_offset: 0..100 · caption_scale: 80..140 · caption_align: left | center | right
JSON
{"destination":"https://example.com/event","composition":true,"template":"plain","caption":"Scan me","arrow":"curve","caption_position":"bottom","caption_offset":50,"caption_scale":120,"caption_align":"center","size":2048}Rotation and layers
Move each element independently within the callout. The QR stays protected.
caption_font: sans | serif | mono | caveat | patrick_hand · caption_rotation: -180..180 · caption_dx / caption_dy / arrow_dx / arrow_dy / logo_dx / logo_dy: -100..100
arrow_scale: 80..140 · logo_scale: 50..150 · logo_mode: header | callout | center · caption_color / arrow_color: #RRGGBB
Layer order: logo-arrow-text | logo-text-arrow | arrow-logo-text | arrow-text-logo | text-logo-arrow | text-arrow-logo
JSON
{"composition":true,"destination":"https://example.com/event","caption":"Scan me","caption_font":"caveat","caption_rotation":-15,"caption_position":"bottom","arrow":"curve","caption_dx":-30,"arrow_dx":40,"composition_layers":"logo-arrow-text","size":2048}QR colours
Markers remain solid. Both colours are checked for contrast before export.
color_mode: solid | linear | radial · gradient_color: #RRGGBB · gradient_angle: 0..359
PNG or JPEG, up to 2 MiB. Choose the header, callout or QR centre. Central placement preserves markers and passes a reading check.
logo_mode: center · logo_dx: 0 · logo_dy: 0 · logo_scale: 50..150
JSON
{"destination":"https://example.com/catalogue","color_mode":"linear","foreground":"#182B2A","background":"#FFFFFF","gradient_color":"#164F72","gradient_angle":135,"size":1024}QR for your emails and applications
POST /qr-images renders an image without creating a library QR. Requires images:render. The HTTPS URL is encoded without being fetched or stored. Only the format, size, actor and successful render count are recorded; no scan statistics or editable redirect. Embed the received bytes as a CID image in your email.
Use composition=1 to choose a template, caption, arrow or transparent background. template, caption, arrow · caption ≤ 30
Transparency applies only to complete PNG designs. The QR background and quiet zone stay opaque to preserve readability.
cURL
curl --fail-with-body -X POST 'https://preprod.qrissimo.fr/api/v1/qr-images' \
-H "Authorization: Bearer $QRISSIMO_TOKEN" -H 'Content-Type: application/json' \
-d '{"destination":"https://example.com/event","format":"png","size":512,"modules":"rounded","finders":"rounded"}' \
-o email-qr.png
curl --fail-with-body -X POST 'https://preprod.qrissimo.fr/api/v1/qr-images' \
-H "Authorization: Bearer $QRISSIMO_TOKEN" -H 'Content-Type: application/json' \
-d '{"destination":"https://example.com/event","composition":true,"template":"vinyl","caption":"Scan me","arrow":"curve","size":1024,"transparent":true}' \
-o event-design.pngJavaScript · Node.js · Nodemailer
const response = await fetch(base + '/qr-images', {
method: 'POST', headers,
body: JSON.stringify({ destination: 'https://example.com/event', size: 512 }),
});
if (!response.ok) throw new Error('QRissimo ' + response.status + ': ' + await response.text());
const qrImage = Buffer.from(await response.arrayBuffer());
const email = {
html: '<p>Your event</p><img src="cid:event-qr" width="176" height="176" alt="Event QR">',
attachments: [{ filename: 'event-qr.png', content: qrImage,
contentType: 'image/png', cid: 'event-qr' }],
};
// Add this content to your server-side Nodemailer message.Python · requests · EmailMessage
from email.message import EmailMessage
response = session.post(f'{base}/qr-images', timeout=15, json={
'destination': 'https://example.com/event', 'size': 512,
})
response.raise_for_status()
message = EmailMessage()
message.set_content('Your event: https://example.com/event')
message.add_alternative('<img src="cid:event-qr" width="176" height="176" alt="Event QR">', subtype='html')
message.get_payload()[-1].add_related(response.content,
maintype='image', subtype='png', cid='<event-qr>')
# Send message through your application's own SMTP configuration.Available contents
Eight static or editable content types, plus imported raw content for static QR only. Fields are described in OpenAPI. Types stay permanent. Wi-Fi passwords and imported content are never returned in JSON.
- Website ·
url - Text ·
text - Wi-Fi ·
wifi - Business card ·
vcard - Email ·
email - Phone ·
phone - SMS ·
sms - WhatsApp ·
whatsapp - Imported QR ·
raw - Hosted pages ·
page
cURL · Wi-Fi
curl -X POST 'https://preprod.qrissimo.fr/api/v1/qr-codes' \
-H "Authorization: Bearer $QRISSIMO_TOKEN" \
-H 'Content-Type: application/json' -H 'Accept: application/json' \
-H 'Idempotency-Key: guest-wifi-1' \
-d '{"label":"Guest network","content_type":"wifi","kind":"static","wifi_ssid":"Guest","wifi_security":"nopass"}'JavaScript · vCard
const response = await fetch(base + '/qr-codes', {
method: 'POST', headers: { ...headers, 'Idempotency-Key': 'contact-1' },
body: JSON.stringify({ label: 'Contact', content_type: 'vcard',
contact_first: 'Camille', contact_phone: '+33612345678' }),
});
if (!response.ok) throw new Error('QRissimo ' + response.status + ': ' + await response.text());
const { data } = await response.json();
console.log(data.short_url);Python · SMS
response = session.post(f'{base}/qr-codes', timeout=15,
headers={'Idempotency-Key': 'sms-1'},
json={'label': 'Message', 'content_type': 'sms', 'kind': 'static',
'phone': '+33612345678', 'message': 'Bonjour'})
response.raise_for_status()
print(response.json()['data']['id'])API customization
Change only the desired fields. The encoded content is preserved; the server adapts the matrix to the design. Colors are checked before saving.
GET /api/v1/catalog?locale=fr · catalog:read
PATCH /api/v1/qr-codes/{id}/appearance · codes:style
cURL
curl 'https://preprod.qrissimo.fr/api/v1/catalog?locale=fr' \
-H "Authorization: Bearer $QRISSIMO_TOKEN" -H 'Accept: application/json'
curl -X PATCH 'https://preprod.qrissimo.fr/api/v1/qr-codes/42/appearance' \
-H "Authorization: Bearer $QRISSIMO_TOKEN" \
-H 'Content-Type: application/json' -H 'Accept: application/json' \
-d '{"template":"vinyl","caption":"Scannez-moi !","arrow":"curve","modules":"rounded","finders":"circle","foreground":"#182B2A","background":"#FFFFFF"}'JavaScript
const response = await fetch(base + '/qr-codes/42/appearance', {
method: 'PATCH', headers,
body: JSON.stringify({ template: 'car', arrow: 'curve' }),
});
if (!response.ok) throw new Error('QRissimo ' + response.status + ': ' + await response.text());
const { data } = await response.json();Python
response = session.patch(f'{base}/qr-codes/42/appearance', timeout=15,
json={'template': 'vinyl', 'arrow': 'wave'})
response.raise_for_status()
print(response.json()['data']['style'])Compositions and silhouettes
Choose a template identifier when creating a code. caption accepts up to 30 characters; arrow accepts none, curve, wave or loop. Export returns the QR alone by default. Add composition=1 for the saved silhouette, caption and arrow.
Graphic designs · 24
- Essential ·
plain - Signature ·
card - Badge ·
round - Seal ·
seal - Viewfinder ·
corners - Bracket ·
bracket - Bubble ·
bubble - Tag ·
tag - Ticket ·
ticket - Band ·
band - Snapshot ·
polaroid - Heart ·
heart - Arch ·
arch - Fold ·
fold - Capsule ·
capsule - Hexagon ·
hexagon - Orbit ·
orbit - Echo ·
ripple - Editorial ·
editorial - Rails ·
rails - Ribbon ·
ribbon - Pennant ·
pennant - Markers ·
grid - Rosette ·
rosette
Transport · 8
- Car ·
car - Van ·
van - Bus ·
bus - Cargo bike ·
bicycle - Rocket ·
rocket - Airplane ·
plane - Boat ·
boat - Train ·
train
Nature and animals · 10
- Cat ·
cat - Dog ·
dog - Panda ·
bear - Fox ·
fox - Rabbit ·
rabbit - Owl ·
owl - Butterfly ·
butterfly - Flower ·
flower - Leaf ·
leaf - Tree ·
tree
Everyday objects · 10
- House ·
house - Camera ·
camera - Phone ·
phone - Laptop ·
laptop - Cup ·
cup - Book ·
book - Light bulb ·
bulb - Gift ·
gift - Bottle ·
bottle - Padlock ·
lock
Leisure and creativity · 8
- Game controller ·
gamepad - Football ·
football - Basketball ·
basketball - Music notes ·
music - Vinyl record ·
vinyl - Clapperboard ·
cinema - Palette ·
palette - Crown ·
crown
Complete examples
cURL
curl -X POST 'https://preprod.qrissimo.fr/api/v1/qr-codes' \
-H "Authorization: Bearer $QRISSIMO_TOKEN" \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: panneau-123' \
-d '{"label":"Panneau 123","destination":"https://example.com/panneau/123"}'
curl 'https://preprod.qrissimo.fr/api/v1/qr-codes/42/svg' \
-H "Authorization: Bearer $QRISSIMO_TOKEN" -o panneau.svg
curl -X POST 'https://preprod.qrissimo.fr/api/v1/qr-codes' \
-H "Authorization: Bearer $QRISSIMO_TOKEN" \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: voiture-456' \
-d '{"label":"Voiture","destination":"https://example.com/auto","template":"car","caption":"Scannez-moi !","arrow":"curve"}'
curl 'https://preprod.qrissimo.fr/api/v1/qr-codes/43/svg?composition=1' \
-H "Authorization: Bearer $QRISSIMO_TOKEN" -o voiture.svg
curl -X PATCH 'https://preprod.qrissimo.fr/api/v1/qr-codes/42' \
-H "Authorization: Bearer $QRISSIMO_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"destination":"https://example.com/panneau/456"}'
curl -X PATCH 'https://preprod.qrissimo.fr/api/v1/qr-codes/42/state' \
-H "Authorization: Bearer $QRISSIMO_TOKEN" \
-H 'Content-Type: application/json' -H 'Accept: application/json' \
-d '{"disabled":false}'JavaScript · Node.js
const base = 'https://preprod.qrissimo.fr/api/v1';
const headers = {
Authorization: `Bearer ${process.env.QRISSIMO_TOKEN}`,
Accept: 'application/json',
'Content-Type': 'application/json',
};
const response = await fetch(`${base}/qr-codes`, {
method: 'POST',
headers: { ...headers, 'Idempotency-Key': 'panneau-123' },
body: JSON.stringify({ label: 'Panneau 123', destination: 'https://example.com/panneau/123' }),
});
if (!response.ok) throw new Error(`QRissimo ${response.status}: ${await response.text()}`);
const { data } = await response.json();
const stats = await fetch(`${base}/qr-codes/${data.id}/statistics`, { headers });
if (!stats.ok) throw new Error(`QRissimo ${stats.status}`);
console.log(await stats.json());Python · requests
import os
import requests
base = 'https://preprod.qrissimo.fr/api/v1'
session = requests.Session()
session.headers.update({
'Authorization': f'Bearer {os.environ["QRISSIMO_TOKEN"]}',
'Accept': 'application/json',
})
response = session.post(f'{base}/qr-codes', timeout=15,
headers={'Idempotency-Key': 'panneau-123'},
json={'label': 'Panneau 123', 'destination': 'https://example.com/panneau/123'})
response.raise_for_status()
code = response.json()['data']
svg = session.get(f'{base}/qr-codes/{code["id"]}/svg', timeout=15)
svg.raise_for_status()
with open('panneau.svg', 'wb') as output:
output.write(svg.content)Campaigns, periods and scheduling
Statistics accept from and to (YYYY-MM-DD), timezone, campaign_id, code_id and q. The period includes both dates and at most 366 days. Schedule values are local times YYYY-MM-DDTHH:mm in the supplied time zone; null removes a limit. Responses use UTC. Missing or repeated clock-change times are rejected.
An opening is a visit to a QRissimo link, from a scan or a click. Repeat and automated visits may be counted; these are not unique visitors. Static QR codes cannot measure openings. History is kept for QR codes in the trash.
| HTTP | Operation | Permission |
|---|---|---|
GET /api/v1/statistics | Statistics | statistics:read |
GET /api/v1/statistics.csv | Export CSV | statistics:read |
GET /api/v1/campaigns | Campaigns | campaigns:read |
POST /api/v1/campaigns | Create a campaign | campaigns:write |
GET /api/v1/campaigns/{campaign} | Choose QR codes | campaigns:read |
PATCH /api/v1/campaigns/{campaign} | Save name | campaigns:write |
POST /api/v1/campaigns/{campaign}/codes | Choose QR codes | campaigns:write |
DELETE /api/v1/campaigns/{campaign} | Delete this campaign | campaigns:write |
cURL
curl 'https://preprod.qrissimo.fr/api/v1/statistics?from=2026-10-01&to=2026-10-31&timezone=Europe%2FParis' \
-H "Authorization: Bearer $QRISSIMO_TOKEN" -H 'Accept: application/json'
curl -X POST 'https://preprod.qrissimo.fr/api/v1/campaigns' \
-H "Authorization: Bearer $QRISSIMO_TOKEN" -H 'Content-Type: application/json' \
-d '{"name":"Collection automne"}'
curl -X POST 'https://preprod.qrissimo.fr/api/v1/campaigns/7/codes' \
-H "Authorization: Bearer $QRISSIMO_TOKEN" -H 'Content-Type: application/json' \
-d '{"action":"add","codes":[42,43]}'
curl -X PATCH 'https://preprod.qrissimo.fr/api/v1/qr-codes/42' \
-H "Authorization: Bearer $QRISSIMO_TOKEN" -H 'Content-Type: application/json' \
-d '{"starts_at":"2026-11-01T09:00","ends_at":"2026-11-30T18:00","timezone":"Europe/Paris","fallback_url":"https://example.com/fin"}'JavaScript · Node.js
const params = new URLSearchParams({
from: '2026-10-01', to: '2026-10-31',
timezone: 'Europe/Paris', campaign_id: '7',
});
const response = await fetch(base + '/statistics?' + params, { headers });
if (!response.ok) throw new Error('QRissimo ' + response.status + ': ' + await response.text());
const { data } = await response.json();
console.log(data.summary, data.daily);Python · requests
response = session.get(f'{base}/statistics', timeout=15, params={
'from': '2026-10-01', 'to': '2026-10-31',
'timezone': 'Europe/Paris', 'campaign_id': 7,
})
response.raise_for_status()
report = response.json()['data']
print(report['summary'], report['daily'])Bulk creation through the API
Import a CSV/XLSX file or a JSON list of rows. No QR codes are created before confirmation. Limits: 100 rows, 32 columns, 1 MB, five active drafts per workspace; drafts expire after 24 hours.
Batch and API access are required. Explicit scopes batches:read, batches:write and batches:export; legacy keys do not receive them automatically. Confirmation also requires codes:create; ZIP export also requires codes:export. Write-only keys receive a summary without row contents.
Idempotency-Key is required for import. Identical retries return the same batch; different data returns 409. Column indices start at zero. Use revision=null for the first mapping, then the latest revision. Confirm exclude_invalid=true only after reviewing errors. Repeated confirmation never duplicates or restores trashed QR codes.
| HTTP | Permission |
|---|---|
GET /api/v1/batches | batches:read |
POST /api/v1/batches | batches:write |
GET /api/v1/batches/{id} | batches:read |
PATCH /api/v1/batches/{id}/mapping | batches:write |
POST /api/v1/batches/{id}/confirm | batches:write + codes:create |
DELETE /api/v1/batches/{id} | batches:write |
GET /api/v1/batches/{id}/manifest.csv | batches:read |
GET /api/v1/batches/{id}/svg.zip | batches:export + codes:export |
cURL · JSON
curl 'https://preprod.qrissimo.fr/api/v1/batches' \
-H "Authorization: Bearer $QRISSIMO_TOKEN" \
-H 'Content-Type: application/json' -H 'Accept: application/json' \
-H 'Idempotency-Key: menus-2026-10' \
-d '{"kind":"static","template":"vinyl","caption":"Scannez-moi","arrow":"curve","rows":[{"label":"Menu","destination":"https://example.com/menu","reference":"00042"}]}'
curl "https://preprod.qrissimo.fr/api/v1/batches/$BATCH_ID/confirm" \
-H "Authorization: Bearer $QRISSIMO_TOKEN" \
-H 'Content-Type: application/json' -H 'Accept: application/json' \
-d "{\"revision\":\"$REVISION\",\"exclude_invalid\":false}"
curl "https://preprod.qrissimo.fr/api/v1/batches/$BATCH_ID/svg.zip" \
-H "Authorization: Bearer $QRISSIMO_TOKEN" \
--fail-with-body -o lot.zipcURL · CSV/XLSX
curl 'https://preprod.qrissimo.fr/api/v1/batches' \
-H "Authorization: Bearer $QRISSIMO_TOKEN" -H 'Accept: application/json' \
-H 'Idempotency-Key: fichier-menus-2026-10' -F 'file=@menus.csv'
curl -X PATCH "https://preprod.qrissimo.fr/api/v1/batches/$BATCH_ID/mapping" \
-H "Authorization: Bearer $QRISSIMO_TOKEN" \
-H 'Content-Type: application/json' -H 'Accept: application/json' \
-d '{"revision":null,"mapping":{"label":0,"destination":1,"reference":2},"kind":"static","template":"car"}'JavaScript · Node.js
const prepared = await fetch(base + '/batches', {
method: 'POST', headers: { ...headers, 'Idempotency-Key': 'menus-2026-10' },
body: JSON.stringify({ kind: 'static', template: 'vinyl', rows: [
{ label: 'Menu', destination: 'https://example.com/menu', reference: '00042' },
] }),
});
if (!prepared.ok) throw new Error('QRissimo ' + prepared.status + ': ' + await prepared.text());
const { data: batch } = await prepared.json();
if (batch.summary.invalid) throw new Error('Review invalid rows before confirming');
const confirmed = await fetch(base + '/batches/' + batch.id + '/confirm', {
method: 'POST', headers, body: JSON.stringify({ revision: batch.revision }),
});
if (!confirmed.ok) throw new Error('QRissimo ' + confirmed.status + ': ' + await confirmed.text());
const result = (await confirmed.json()).data;
console.log(result.summary.created);Python · requests
prepared = session.post(f'{base}/batches', timeout=30,
headers={'Idempotency-Key': 'menus-2026-10'}, json={
'kind': 'static', 'template': 'vinyl', 'rows': [
{'label': 'Menu', 'destination': 'https://example.com/menu', 'reference': '00042'}]})
prepared.raise_for_status()
batch = prepared.json()['data']
if batch['summary']['invalid']:
raise ValueError('Review invalid rows before confirming')
confirmed = session.post(f'{base}/batches/{batch["id"]}/confirm', timeout=60,
json={'revision': batch['revision']})
confirmed.raise_for_status()
print(confirmed.json()['data']['summary'])The ZIP contains complete SVGs of QR codes still in the collection; each exported QR adds a download. The CSV summary does not count as a QR download. Use row code IDs for individual PNG/JPEG exports or PDF sheets. Respect Retry-After on 429.
PDF sheets through the API
Generate an A4 sheet with 2, 6, 12 or 24 QR codes per page, using the saved designs and arrows. Select up to 48 creations from the same workspace. Names are optional; identifiers determine ascending order. Each exported QR is counted.
The prints:export permission is required. Legacy qr:export keys do not automatically receive this new permission. Foreign selections, trashed codes and QR codes too dense for the chosen layout are rejected.
cURL
curl -X POST 'https://preprod.qrissimo.fr/api/v1/print-sheets' \
-H "Authorization: Bearer $QRISSIMO_TOKEN" \
-H 'Content-Type: application/json' -H 'Accept: application/pdf' \
-d '{"codes":[42,43],"layout":12,"labels":true}' \
--fail-with-body -o planche.pdfJavaScript · Node.js
import { writeFile } from 'node:fs/promises';
const sheet = await fetch(base + '/print-sheets', {
method: 'POST', headers: { ...headers, Accept: 'application/pdf' },
body: JSON.stringify({ codes: [42, 43], layout: 12, labels: true }),
});
if (!sheet.ok) throw new Error('QRissimo ' + sheet.status + ': ' + await sheet.text());
await writeFile('planche.pdf', Buffer.from(await sheet.arrayBuffer()));Python · requests
sheet = session.post(f'{base}/print-sheets', timeout=60,
headers={'Accept': 'application/pdf'},
json={'codes': [42, 43], 'layout': 12, 'labels': True})
sheet.raise_for_status()
with open('planche.pdf', 'wb') as output:
output.write(sheet.content)Errors and limits
200 · 201 · 204
401 · 403 · 404 · 409 · 410 · 422 · 429
{ "message": "…", "errors": { "destination": ["…"] } }
Retry-After: 60Reusable styles
Explicit kits:read and kits:write permissions. A write-only key receives the identifier and revision without the style. Copy the seven style fields into your creations or batches.
Per workspace: 100 active styles, 250 retained including archives.
| HTTP | Permission |
|---|---|
GET /style-kits | kits:read |
GET /style-kits/{id} | kits:read |
POST /style-kits | kits:write |
PUT /style-kits/{id} | kits:write |
POST /style-kits/{id}/archive | kits:write |
POST /style-kits/{id}/restore | kits:write |
cURL
curl --fail-with-body -X POST 'https://preprod.qrissimo.fr/api/v1/style-kits' \
-H "Authorization: Bearer $QRISSIMO_TOKEN" -H 'Content-Type: application/json' \
-H "Idempotency-Key: $REQUEST_UUID" \
-d '{"name":"Studio","modules":"rounded","finders":"rounded","foreground":"#182B2A","background":"#FFFFFF","template":"card","caption":"Scan","arrow":"curve"}'
curl --fail-with-body 'https://preprod.qrissimo.fr/api/v1/style-kits?archived=1' \
-H "Authorization: Bearer $QRISSIMO_TOKEN" -H 'Accept: application/json'Hosted pages through the API
Create a dynamic QR with content_type=page, page_type and codes:create + pages:write permissions. page_id identifies the draft. Save each language, check the private preview, then publish explicitly. Exactly one QR slot is used.
pages:read reads the draft and preview token; pages:write edits the draft; pages:publish publishes or withdraws the page. Write-only keys never receive private contents.
40 blocks per language, 128 KiB per request and draft, 30 retained versions. Plain text; HTTP(S) links without credentials. Readable accent on white. Other languages remain private until publication.
| HTTP | Permission |
|---|---|
GET /pages | pages:read |
GET /pages/{id} | pages:read |
PUT /pages/{id} | pages:write |
GET /pages/{id}/preview?revision={revision}&lang=fr | pages:read |
POST /pages/{id}/publish | pages:publish |
POST /pages/{id}/unpublish | pages:publish |
DELETE /pages/{id}/locales/{locale} | pages:write |
GET /pages/{id}/history | pages:read |
POST /pages/{id}/history/{version}/restore | pages:write |
cURL
curl --fail-with-body -X POST 'https://preprod.qrissimo.fr/api/v1/qr-codes' \
-H "Authorization: Bearer $QRISSIMO_TOKEN" -H 'Content-Type: application/json' \
-H 'Idempotency-Key: page-menu-2026-10' \
-d '{"label":"Menu","kind":"dynamic","content_type":"page","page_type":"menu","modules":"rounded","finders":"rounded","foreground":"#182B2A","background":"#FFFFFF"}'
curl --fail-with-body -X PUT "https://preprod.qrissimo.fr/api/v1/pages/$PAGE_ID" \
-H "Authorization: Bearer $QRISSIMO_TOKEN" -H 'Content-Type: application/json' \
--data-binary @draft.json
curl --fail-with-body "https://preprod.qrissimo.fr/api/v1/pages/$PAGE_ID/preview?revision=$REVISION&lang=fr" \
-H "Authorization: Bearer $QRISSIMO_TOKEN" -o private-preview.html
curl --fail-with-body -X POST "https://preprod.qrissimo.fr/api/v1/pages/$PAGE_ID/publish" \
-H "Authorization: Bearer $QRISSIMO_TOKEN" -H 'Content-Type: application/json' \
-d "{\"revision\":\"$REVISION\",\"preview_token\":\"$PREVIEW_TOKEN\",\"confirmed\":true}"JSON · draft.json
{
"revision": "CURRENT_REVISION_UUID",
"locale": "fr", "default_locale": "fr",
"brand_name": "Le Jardin", "accent": "#182B2A",
"title": "Notre menu", "intro": "Une cuisine de saison.",
"blocks": [{"type":"menu_item", "title":"Le plat du jour",
"body":"Description du restaurant", "price":"Prix fourni par le restaurant",
"allergens":"Allergènes renseignés par le restaurant"}]
}JavaScript · Node.js
const read = await fetch(base + '/pages/' + pageId, { headers });
if (!read.ok) throw new Error('QRissimo ' + read.status);
const current = (await read.json()).data;
const saved = await fetch(base + '/pages/' + pageId, {
method: 'PUT', headers,
body: JSON.stringify({ revision: current.revision, locale: 'en',
default_locale: current.draft.default_locale,
brand_name: current.draft.brand.name, accent: current.draft.brand.accent,
title: 'Welcome', intro: 'Your company content.', blocks: [] }),
});
if (!saved.ok) throw new Error('QRissimo ' + saved.status + ': ' + await saved.text());
const next = (await saved.json()).data;
// Explicitly review the saved draft before publishing all variants.
console.log(base + '/pages/' + pageId + '/preview?revision=' + next.revision);Python · requests
response = session.get(f'{base}/pages/{page_id}', timeout=15)
response.raise_for_status()
current = response.json()['data']
# Review every draft variant before explicit publication.
publication = session.post(f'{base}/pages/{page_id}/publish', timeout=30,
json={'revision': current['revision'],
'preview_token': current['preview_token'], 'confirmed': True})
publication.raise_for_status()A public click is an observed action, not a sale. Statistics expose page_clicks, page_bot_actions and period_page_clicks; detected bot clicks are excluded from page_clicks.
Photos and documents
Upload a file, then choose it in a draft section. It is public only when the page using it is published. 20 files and 100 MiB per page; 10 MiB per file, images up to 12 megapixels.
PDFs are reconstructed: links, annotations, forms and signatures are not retained. Up to 50 pages. Some protected or compressed PDFs are unsupported. Add a readable summary to the page.
Upload the file to this page library first.
| HTTP | Permission |
|---|---|
GET /pages/{id}/media | pages:read |
GET /pages/{id}/media/{asset} | pages:read |
POST /pages/{id}/media | pages:write |
DELETE /pages/{id}/media/{asset} | pages:write |
cURL · multipart
curl --fail-with-body -X POST "https://preprod.qrissimo.fr/api/v1/pages/$PAGE_ID/media" \
-H "Authorization: Bearer $QRISSIMO_TOKEN" -H 'Accept: application/json' \
-F 'file=@guide.pdf' -F 'name=Guide' \
-F "revision=$REVISION" -F "request_id=$REQUEST_UUID"
curl --fail-with-body "https://preprod.qrissimo.fr/api/v1/pages/$PAGE_ID/media/$ASSET_ID" \
-H "Authorization: Bearer $QRISSIMO_TOKEN" -o guide.pdfJSON · PDF document
{"type":"document", "asset_id":"ASSET_UUID", "title":"Guide",
"body":"An accessible summary for this document."}
{"type":"image", "asset_id":"ASSET_UUID", "title":"",
"alt":"A useful description of the photo.", "body":"Optional caption."}0 observed public document downloads. These requests do not necessarily represent distinct visitors. page_file_downloads · period_page_file_downloads
Feedback & requests
Read and manage the inbox with dedicated permissions. Historical keys do not receive these permissions.
Responses are automatically deleted after 180 days. They are private; no newsletter subscription.
| HTTP | Permission |
|---|---|
GET /feedback | feedback:read |
GET /feedback/{entry} | feedback:read |
PATCH /feedback/{entry} | feedback:write |
DELETE /feedback/{entry} | feedback:write |
GET /feedback/preferences | feedback:notify |
PUT /feedback/preferences | feedback:notify |
POST /feedback/{entry}/replies | feedback:reply |
{
"type": "form",
"title": "Response",
"purpose": "feedback",
"scale": "half",
"contact": "optional",
"privacy": "Explain who receives responses, why, and how people can exercise their rights. Registration is not marketing consent."
}
curl "$QRISSIMO_URL/api/v1/feedback?status=new" \
-H "Authorization: Bearer $QRISSIMO_API_KEY"
curl -X PATCH "$QRISSIMO_URL/api/v1/feedback/$ENTRY_ID" \
-H "Authorization: Bearer $QRISSIMO_API_KEY" \
-H "Content-Type: application/json" \
--data '{"revision":"REVISION_UUID","status":"resolved"}'
const inbox = await fetch(`${base}/api/v1/feedback?status=new`, {
headers: { Authorization: `Bearer ${key}` }
}).then(r => { if (!r.ok) throw new Error(String(r.status)); return r.json(); });
import requests
response = requests.get(base + "/api/v1/feedback", params={"status": "new"},
headers={"Authorization": "Bearer " + key}, timeout=15)
response.raise_for_status()
inbox = response.json()Reply by email
The sender is verified. Your verified account address is used as the reply address. Never share sensitive information with an unverified address.
Replies are limited to 3 per address per day, 50 per workspace and 10 per request. Email limits are separate from your QR code quotas.
An incident prevents us from confirming transmission. No automatic resend, to avoid duplicates. “Accepted” does not guarantee inbox delivery.
Summaries are sent at 8 am, on Mondays for weekly summaries. They count new responses since activation. Instant notifications are limited to 20 a day; all responses remain in your inbox.
curl -X POST "$QRISSIMO_URL/api/v1/feedback/$ENTRY_ID/replies" \
-H "Authorization: Bearer $QRISSIMO_API_KEY" \
-H "Content-Type: application/json" \
--data '{"revision":"REVISION_UUID","request_id":"NEW_UUID","message":"Thank you for your feedback.","confirmed":true}'
const result = await fetch(`${base}/api/v1/feedback/${entryId}/replies`, {
method: 'POST', headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
body: JSON.stringify({revision, request_id: crypto.randomUUID(), message: 'Thank you.', confirmed: true})
});
if (!result.ok) throw new Error(String(result.status));
const delivery = await result.json();
import uuid
response = requests.post(base + '/api/v1/feedback/' + entry_id + '/replies',
headers={'Authorization': 'Bearer ' + key}, timeout=15,
json={'revision': revision, 'request_id': str(uuid.uuid4()), 'message': 'Thank you.', 'confirmed': True})
response.raise_for_status()Restaurants: menus, requests and preparation
First create a Menu page. Save a menu, check its preview and explicitly publish it. Every request stays pending until the restaurant confirms. The server checks prices, options, availability and permissions.
Counts cover retained requests: receipt on submission date, confirmation on the manual decision date. A confirmation remains counted after cancellation and is not a payment. Deleted or expired requests are excluded.
| HTTP | Permission |
|---|---|
GET /restaurants | restaurants:read |
GET /restaurants/{page} | restaurants:read |
PUT /restaurants/{page} | restaurants:write |
GET /restaurants/{page}/preview?revision=UUID | restaurants:read |
POST /restaurants/{page}/publish | restaurants:publish |
POST /restaurants/{page}/withdraw | restaurants:publish |
GET /restaurants/{page}/preparation?slot_id=UUID | restaurants:requests_read |
GET /restaurants/{page}/preparation.pdf?slot_id=UUID | restaurants:export |
GET /restaurant-requests | restaurants:requests_read |
POST /restaurant-requests | restaurants:requests_create |
GET /restaurant-requests/{id} | restaurants:requests_read |
POST /restaurant-requests/{id}/decision | restaurants:requests_write |
DELETE /restaurant-requests/{id} | restaurants:requests_write |
cURL · The menu
curl -X PUT "$QRISSIMO_URL/api/v1/restaurants/$PAGE_ID" \
-H "Authorization: Bearer $QRISSIMO_TOKEN" -H 'Content-Type: application/json' \
--data @menu-fr.json
curl "$QRISSIMO_URL/api/v1/restaurants/$PAGE_ID/preview?revision=$REVISION" \
-H "Authorization: Bearer $QRISSIMO_TOKEN"
curl -X POST "$QRISSIMO_URL/api/v1/restaurants/$PAGE_ID/publish" \
-H "Authorization: Bearer $QRISSIMO_TOKEN" -H 'Content-Type: application/json' \
--data '{"revision":"REVISION_UUID","preview_token":"TOKEN_FROM_PREVIEW","confirmed":true,"notification_mode":"me"}'
{
"revision": "CURRENT_REVISION_OR_NULL",
"locale": "fr",
"currency": "EUR",
"timezone": "Europe\/Paris",
"require_preorder": false,
"lead_minutes": 60,
"cancel_hours": 24,
"max_guests": 8,
"privacy": "Explain who receives responses, why, and how people can exercise their rights. Registration is not marketing consent.",
"terms": "The restaurant will confirm your request. Seats and dishes are reserved only after approval.",
"items": [
{
"id": "ITEM_UUID",
"name": "Dish or set menu",
"price": "12.50",
"available": true,
"max_quantity": 8,
"stock": 20,
"options": [
{
"id": "OPTION_UUID",
"label": "Option name",
"price_cents": 230
}
]
}
],
"slots": [
{
"id": "SLOT_UUID",
"date": "2026-11-01",
"time": "19:30",
"duration_minutes": 120,
"capacity": 32,
"enabled": true
}
]
}JavaScript · Reservation request
const result = await fetch(base + '/restaurant-requests', {
method: 'POST', headers,
body: JSON.stringify({
menu_id: menuId, menu_revision: publishedRevision,
request_id: crypto.randomUUID(), locale: 'fr',
selection: [{ id: itemId, quantity: 2, options: [optionId] }],
slot_id: slotId, guests: 2, name: 'Example', email: 'guest@example.com',
email_followup: false, confirmed: true,
}),
});
if (!result.ok) throw new Error(await result.text());
const { data } = await result.json(); // pending; id/status/revision onlyPython · Confirm reservation
response = session.post(base + '/restaurant-requests/' + request_id + '/decision',
timeout=15, json={
'revision': current_revision, 'action': 'confirm', 'confirmed': True,
})
response.raise_for_status()
# 409: reload the request and quote. Never force confirmation or change the revision blindly.
# 422: capacity, stock or another field must be reviewed.cURL · Download preparation PDF
curl --fail-with-body "$QRISSIMO_URL/api/v1/restaurants/$PAGE_ID/preparation.pdf?slot_id=$SLOT_ID" \
-H "Authorization: Bearer $QRISSIMO_TOKEN" -o restaurant-service.pdf
# Independent scope: restaurants:export. 409 means the preparation changed during rendering.Tickets and coupons: setup, checks and single use
Create a dynamic page/pass QR, publish with pages:publish, then configure its entitlement. Each ticket uses one QR slot. GET never redeems an entitlement. This module does not trigger payments or ticket delivery.
GET /api/v1/passes?per_page=20
GET /api/v1/codes/{code}/pass
PUT /api/v1/codes/{code}/pass
POST /api/v1/codes/{code}/pass/revoke
POST /api/v1/codes/{code}/pass/redeemRead the entitlement, then confirm using its revision and page_version, a stable UUID request_id and confirmed=true. An exact retry returns the receipt without another use. A different ID after redemption returns 409; an unavailable entitlement returns 410. Never expose the API key publicly.
curl -X POST "$BASE/codes/$CODE/pass/redeem" \
-H "Authorization: Bearer $QRISSIMO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"revision":"PASS_REVISION_UUID","page_version":"PUBLISHED_VERSION_UUID","request_id":"STABLE_REQUEST_UUID","confirmed":true}'Product formats and GS1
Create a Data Matrix or GS1 Digital Link QR using your own identifiers.
The identifiers remain yours. Format validation does not prove assignment by GS1. Static codes, without statistics or a new QRissimo redirect.
POST /api/v1/symbols/render symbols:render
{"type":"datamatrix", "text":"PUMP-001", "format":"svg"}
{"type":"gs1_datamatrix", "gtin":"09520123456788", "lot":"ABC1",
"serial":"12345", "expiry":"2027-01-31", "format":"png", "size":512}
{"type":"digital_link", "gtin":"09520123456788", "base_url":"https://id.example.org"}
curl --fail-with-body "$BASE/symbols/render" \
-H "Authorization: Bearer $QRISSIMO_TOKEN" -H "Content-Type: application/json" \
-d '{"type":"datamatrix","text":"PUMP-001","format":"png"}' -o product.pngEnter an HTTPS origin without path or parameters. The resolver must already exist: this tool does not create a redirect.
Use 1 to 256 printable ASCII characters. Use a complete date between 2000 and 2049.
NFC integration
Export the current address or prepare local writing. The server does not write a tag remotely, and a browser read-back is not a certificate.
GET /api/v1/codes/{code}/nfc nfc:read
POST /api/v1/codes/{code}/nfc/prepare nfc:prepare
GET /api/v1/codes/{code}/nfc/export.ndef nfc:export
GET /api/v1/codes/{code}/nfc/export.json nfc:export
curl --fail-with-body "$BASE/codes/$CODE/nfc" \
-H "Authorization: Bearer $QRISSIMO_TOKEN"
{"revision":"64_HEX_CHARACTERS"}
// Web NFC: explicit user click, HTTPS, compatible Android browser
await new NDEFReader().write({records: [
{recordType: "url", data: current.url}
]}, {overwrite: false, signal: controller.signal});The NDEF file contains one URL record. The JSON contains Web NFC data. Tag capacity must also accommodate its memory format.
QR/NFC inventory API
Read objects, configure their references and record interventions. Internal reports require a separate permission. Keep keys on your server.
GET /api/v1/assets?kind=equipment&per_page=20
GET /api/v1/assets.csv?kind=equipment
GET /api/v1/codes/{code}/asset
PUT /api/v1/codes/{code}/asset
GET /api/v1/codes/{code}/asset/checks?page=1
POST /api/v1/codes/{code}/asset/checksFor an intervention, retain the same request_id when retrying. An identical retry returns the original receipt; a different payload or stale revision is refused. Scanning never creates an intervention.
curl --fail-with-body -X POST "$BASE/codes/$CODE/asset/checks" \
-H "Authorization: Bearer $QRISSIMO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"revision":"ASSET_REVISION_UUID","request_id":"STABLE_REQUEST_UUID","kind":"maintenance","note":"Filter checked","confirmed":true}'
// Node.js · assets:checks
const receipt = await fetch(`${base}/codes/${code}/asset/checks`, {
method: "POST",
headers: {Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json"},
body: JSON.stringify({revision, request_id: requestId, kind: "inspection",
note: "Checked connections", confirmed: true, next_due: "2040-01-01T12:00:00Z"})
});
if (!receipt.ok) throw new Error(`QRissimo ${receipt.status}`);
const result = await receipt.json();
# Python · assets:history
import requests
response = requests.get(f"{base}/codes/{code}/asset/checks", timeout=15,
headers={"Authorization": f"Bearer {api_key}"}, params={"page": 1})
response.raise_for_status()
history = response.json()Price and stock API
Authorized source per product. Integer amounts in currency minor units, integer stock or unknown. A scan does not confirm a sale.
GET /api/v1/commerce/feeds?q=Alba&per_page=20
GET /api/v1/commerce/feeds.csv?q=Alba
GET /api/v1/codes/{code}/commerce
PUT /api/v1/codes/{code}/commerce
POST /api/v1/codes/{code}/commerce
POST /api/v1/commerce/updatesKeep the same request_id and payload when retrying. The last 64 receipts remain replayable for up to seven days. Beyond that, older sequences are refused. A batch is atomic.
curl --fail-with-body -X POST "$BASE/codes/$CODE/commerce" \
-H "Authorization: Bearer $QRISSIMO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"revision":"CURRENT_POLICY_UUID","request_id":"STABLE_REQUEST_UUID","sequence":1,"sku":"SKU-001","price_minor":2090,"currency":"EUR","tax_mode":"included","stock_quantity":7,"observed_at":"2026-10-13T12:00:00Z"}'
// Node.js · commerce:write. Keep the same UUID and payload when retrying.
const response = await fetch(`${base}/commerce/updates`, {
method: "POST", headers: {Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json"},
body: JSON.stringify({items: observations})
});
if (!response.ok) throw new Error(`QRissimo ${response.status}`);
const {data: receipts} = await response.json();
# Python · commerce:read
import requests
response = requests.get(f"{base}/codes/{code}/commerce", timeout=15,
headers={"Authorization": f"Bearer {api_key}"})
response.raise_for_status()
feed = response.json()["data"]Monitoring API
Periodic HEAD checks, without redirects or downloaded bodies. Three failures to confirm, two successes to recover. Timing depends on the budget and task service.
GET /api/v1/monitoring?q=Catalogue&per_page=20
GET /api/v1/codes/{code}/monitoring
PUT /api/v1/codes/{code}/monitoring
POST /api/v1/codes/{code}/monitoring/check
PUT /api/v1/codes/{code}/monitoring/alerts
POST /api/v1/codes/{code}/monitoring/incidents/{incident}/acknowledgecurl --fail-with-body -X PUT "$BASE/codes/$CODE/monitoring" \
-H "Authorization: Bearer $QRISSIMO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"revision":null,"enabled":true,"interval_minutes":60}'
// Node.js · monitoring:check. Due checks only; never a force refresh.
const response = await fetch(`${base}/codes/${code}/monitoring/check`, {
method: "POST",
headers: {Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json"},
body: JSON.stringify({revision: currentMonitorRevision})
});
if (!response.ok) throw new Error(`QRissimo ${response.status}`);
const {data: {queued}} = await response.json();
# Python · monitoring:read
import requests
response = requests.get(f"{base}/codes/{code}/monitoring", timeout=15,
headers={"Authorization": f"Bearer {api_key}"})
response.raise_for_status()
for target in response.json()["data"]["targets"]:
print(target["host"], target["status"], target["checked_at"])This is a personal choice. Messages go only to your verified account address while you have access to this QR.
At most 20 emails per person per day, 100 per payer and 500 across the service. One alert per incident; uncertain deliveries are never resent automatically.
Batch publishing
Publish several page drafts together after checking each page.
If content or access changes, no page in the batch is published. Your QR links stay the same.
The preview is valid for 30 minutes. Discard it without changing your pages.
GET /api/v1/page-publications page_publications:read
POST /api/v1/page-publications page_publications:write + pages:read
GET /api/v1/page-publications/{operation} page_publications:read
GET /api/v1/page-publications/{operation}/pages/{page}/preview
POST /api/v1/page-publications/{operation}/decision
publish: page_publications:write + pages:publish
cancel: page_publications:write
cURL
curl --fail-with-body -X POST "$BASE/page-publications" \
-H "Authorization: Bearer $QRISSIMO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"request_id":"aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa","campaign_id":42}'
curl --fail-with-body -X POST "$BASE/page-publications/$OPERATION/decision" \
-H "Authorization: Bearer $QRISSIMO_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"revision\":\"$REVISION\",\"action\":\"publish\",\"confirmed\":true}"JavaScript
const response = await fetch(`${base}/page-publications`, {
method: "POST", headers: {Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json"},
body: JSON.stringify({request_id: crypto.randomUUID(), pages: pageIds})
});
if (!response.ok) throw new Error(`QRissimo ${response.status}`);
const {data: proposal} = await response.json();
// Show proposal.items and each private preview before an explicit decision.Python
import requests, uuid
response = requests.post(f"{base}/page-publications", timeout=30,
headers={"Authorization": f"Bearer {api_key}"},
json={"request_id": str(uuid.uuid4()), "pages": page_ids})
response.raise_for_status()
proposal = response.json()["data"]
# Keep request_id for retries, then show previews before confirming.From 1 to 50 pages per operation.
Total content exceeds 2 MiB. Prepare smaller batches.
Import pages
Turn a CSV or Excel file into page drafts, then check them before publishing.
Each row creates a new draft in the chosen language. Add sections, photos and other languages in the editor. No page is published automatically.
Up to 50 pages, 1 MiB file. Titles: 120 characters; introductions: 800. Excel formulas are rejected.
POST /api/v1/page-imports pages:write + batches:write
GET /api/v1/page-imports/{batch} pages:read + batches:read
POST /api/v1/page-imports/{batch}/confirm pages:write + batches:write + codes:create
cURL
curl --fail-with-body -X POST "$BASE/page-imports" \
-H "Authorization: Bearer $QRISSIMO_API_KEY" -H "Content-Type: application/json" \
-d '{"request_id":"aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa","page_type":"guide","locale":"fr","rows":[{"label":"Room guide","title":"Welcome","intro":"Useful information for your stay."}]}'
curl --fail-with-body -X POST "$BASE/page-imports/$BATCH/confirm" \
-H "Authorization: Bearer $QRISSIMO_API_KEY" -H "Content-Type: application/json" \
-d "{\"revision\":\"$REVISION\",\"confirmed\":true}"JavaScript
const prepared = await fetch(`${base}/page-imports`, {
method: "POST", headers: {Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json"},
body: JSON.stringify({request_id: crypto.randomUUID(), page_type: "guide", locale: "en",
rows: [{label: "Room guide", title: "Welcome", intro: "Useful information for your stay."}]})
});
if (!prepared.ok) throw new Error(`QRissimo ${prepared.status}`);
const {data: batch} = await prepared.json();
// Read/check the import before confirming draft creation. Publishing is a separate decision.Python
import requests, uuid
with open("pages.csv", "rb") as source:
response = requests.post(f"{base}/page-imports", timeout=30,
headers={"Authorization": f"Bearer {api_key}"},
data={"request_id": str(uuid.uuid4()), "page_type": "guide", "locale": "en"},
files={"file": ("pages.csv", source, "text/csv")})
response.raise_for_status()
batch = response.json()["data"]The QR codes use their new QRissimo addresses. Complete each page, then prepare a batch preview to publish.
{"request_id":"bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb","batch_uuid":"IMPORTED_BATCH_UUID"}
POST /api/v1/page-publications
page_publications:write + pages:read + batches:read