# QR codes

List the saved QR codes of an organization, read and compare their scan statistics, point a printed code at another survey, switch it off or delete it.

A saved QR code is the printed code people scan: it has a short link of its own, counts its scans and can be pointed
at another survey later without reprinting. Reading needs a key with `qr:read`; creating, changing and deleting needs
`qr:write`. [Distributing a survey](https://simplefeedback.de/developer/docs/distributing-a-survey.md#create-a-saved-qr-code) shows how to create one with
`POST /v1/surveys/{id}/qr-codes`; the MCP tool is `create_qr_code`. `GET /v1/surveys/{id}/qr` only renders an image
of a survey's link and saves nothing. The shell variables `SF_API` and `SF_KEY` are set up as in the
[Quickstart](https://simplefeedback.de/developer/docs/quickstart.md).

## List the codes

```bash
curl "$SF_API/v1/qr-codes?surveyId=$SURVEY_ID" \
  -H "Authorization: Bearer $SF_KEY"
```

Filter with `venueId` or `surveyId`, page with `limit` and `offset` as on every list. Each row looks like this:

```json
{
  "id": "6f0c2d1e-…",
  "slug": "front-desk",
  "name": "Front desk",
  "type": "survey",
  "surveyId": "a41b9c70-…",
  "venueId": "0e7d55f2-…",
  "active": true,
  "publicUrl": "https://simplefeedback.de/q/front-desk",
  "scanCount": 312,
  "lastScannedAt": "2026-10-13T16:02:11.000Z",
  "createdAt": "2026-09-01T08:30:00.000Z"
}
```

`type` is `survey` for a code that opens a survey, or `redirect` or `menu` for codes made in the dashboard that lead
somewhere else; those have `surveyId: null`. `GET /v1/qr-codes/{id}?includeImage=true` returns one code with the
PNG as a data URL in `qrDataUrl`.

## Read the scans

```bash
curl "$SF_API/v1/qr-codes/$QR_ID/stats?periodDays=30" \
  -H "Authorization: Bearer $SF_KEY"
```

`periodDays` is 7 (the default), 30 or 90 whole days on the Europe/Berlin calendar, today included. The answer gives
the scans and unique devices of that period next to the period before, the scans per day and per platform (`ios`, `android`, `desktop`, `other`), and for survey codes
how many scans ended in a submitted response, as `survey.completed` and `survey.completionRate`.

## Compare all codes

```bash
curl "$SF_API/v1/qr-codes/stats?periodDays=30&type=survey" \
  -H "Authorization: Bearer $SF_KEY"
```

One call covers every code the key can see, for questions like "which code brings the most responses" or "which
codes stopped being scanned". `periodDays` is 7, 30 (the default) or 90; narrow with `venueId` or `type`. Dates and
hours are on the Europe/Berlin calendar. The answer has:

- the totals of the period next to the period before: `scans`, `uniqueDevices` (distinct devices per day, added up)
  and `conversionRate`, the share of survey-code scans that ended in a response to that code's survey (0 to 1, `null`
  without survey scans)
- `codes`, one row per code, most scanned first, with its own `scans`, `scansPreviousPeriod` and `conversionRate`
- `daily` scans, `scansByWeekdayAndHour` (`[weekday][hour]`, Monday is 0), `devices` and `topCities`
- `hints`, at most one per kind: `dormant` (scanned before, not in this period), `rising` (the largest growth) and
  `lowConversion` (a survey code answered far less often than the others)

The comparison needs a plan with graphical statistics; on other plans it answers 403 `FEATURE_NOT_IN_TIER`. The MCP
tool is `get_qr_codes_stats`.

## Point a printed code at another survey

```bash
curl -X PATCH "$SF_API/v1/qr-codes/$QR_ID" \
  -H "Authorization: Bearer $SF_KEY" \
  -H "Content-Type: application/json" \
  -d '{"surveyId": "'"$NEW_SURVEY_ID"'"}'
```

From the next scan on, the code opens the new survey. Its slug, short link and scan history stay, so the printed
copies keep working. Only the fields you send change: `name` renames it, `active: false` switches it off and
`active: true` switches it back on. A `redirect` or `menu` code refuses `surveyId` with
`400 QR_CODE_NOT_A_SURVEY_CODE`.

## Delete a code

```bash
curl -X DELETE "$SF_API/v1/qr-codes/$QR_ID" \
  -H "Authorization: Bearer $SF_KEY"
```

It answers `204`. The code and its scan statistics are gone for good and printed copies stop working. To take a code
out of use and keep its history, send `PATCH` with `active: false` instead.
