Skip to content

QR code API and MCP server

Create, change and measure dynamic QR codes from your own code, or let an AI assistant do it for you. Both are free for members while Pro is being built.

What the API does

A dynamic QR code holds a short link, neoreader.com/q/SLUG, that redirects to a destination you can change at any time. The API lets you do everything the dashboard does with those codes:

Scans are counted without storing IP addresses. The API returns JSON and works with any language that can send an HTTPS request. It does not draw QR images; use the QR code generator to design and download the image for a code's short URL.

Get a token

  1. Sign in at /login with Google or an emailed link.
  2. Open Account, then API Tokens, and create a token with a name you will recognise.
  3. Copy it straight away. It is shown once, and you can revoke it at any time.

Send the token in an Authorization header on every request. Treat it like a password: keep it out of public repositories and browser code.

The base URL is https://neoreader.com/api/v1. Every request needs Accept: application/json. In the examples, replace YOUR_TOKEN with your token.

curl https://neoreader.com/api/v1/me \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Accept: application/json"

Endpoints

GET /me

Your name, email, plan, how many codes you have and how many you may have.

curl https://neoreader.com/api/v1/me \
  -H "Authorization: Bearer YOUR_TOKEN" -H "Accept: application/json"
{
  "data": {
    "name": "Jane Doe",
    "email": "[email protected]",
    "plan": "free",
    "codes_count": 12,
    "code_limit": 250
  }
}

GET /codes

A page of your codes, newest first. Optional query parameters: q searches title, slug and destination; folder matches a folder name; status is one of active, paused, blocked, expired, not_started or limit; per_page is 1 to 100 (default 25); page picks the page.

curl "https://neoreader.com/api/v1/codes?status=active&folder=Restaurant&per_page=10" \
  -H "Authorization: Bearer YOUR_TOKEN" -H "Accept: application/json"
{
  "data": [ { "slug": "k7Xp2m", "short_url": "https://neoreader.com/q/k7Xp2m", "title": "Spring menu", "status": "active", "...": "..." } ],
  "links": { "first": "...", "last": "...", "prev": null, "next": null },
  "meta": { "current_page": 1, "per_page": 10, "total": 1, "last_page": 1 }
}

POST /codes

Creates a code and returns it with status 201. title and destination are required. The destination must be a full https:// or http:// address, or a mailto: or tel: link; links back to another NeoReader short link and private network addresses are refused.

curl -X POST https://neoreader.com/api/v1/codes \
  -H "Authorization: Bearer YOUR_TOKEN" -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"title":"Spring menu","destination":"https://example.com/menu","folder":"Restaurant","utm":{"utm_source":"qr","utm_medium":"print"}}'
{
  "data": {
    "slug": "k7Xp2m",
    "short_url": "https://neoreader.com/q/k7Xp2m",
    "title": "Spring menu",
    "destination": "https://example.com/menu",
    "folder": "Restaurant",
    "status": "active",
    "is_active": true,
    "starts_at": null,
    "expires_at": null,
    "scan_limit": null,
    "has_password": false,
    "utm": { "utm_source": "qr", "utm_medium": "print", "utm_campaign": null },
    "scans_count": 0,
    "last_scanned_at": null,
    "created_at": "2026-11-03T10:15:00+00:00",
    "updated_at": "2026-11-03T10:15:00+00:00"
  }
}

Code fields

FieldTypeNotes
titlestringUp to 120 characters. Only you see it.
destinationstringUp to 2,000 characters. Change it any time.
folderstring or nullUp to 60 characters.
is_activebooleanfalse pauses the code. A blocked code cannot be turned back on.
starts_at, expires_atdate and time or nullISO 8601, for example 2026-12-31T23:59:00Z. The expiry must be after the start.
scan_limitinteger or nullThe code stops redirecting after this many scans.
passwordstring or nullWrite-only, 4 to 100 characters. Responses show has_password instead. Send null to remove it.
utmobject or nullutm_source, utm_medium and utm_campaign, added to the destination on every scan.

Responses also include slug, short_url, status, scans_count, last_scanned_at, created_at and updated_at. status is one of active, paused, blocked, not_started, expired or limit.

GET /codes/{slug}

One code. A slug that is not yours returns 404.

curl https://neoreader.com/api/v1/codes/k7Xp2m \
  -H "Authorization: Bearer YOUR_TOKEN" -H "Accept: application/json"
{
  "data": {
    "slug": "k7Xp2m",
    "short_url": "https://neoreader.com/q/k7Xp2m",
    "title": "Spring menu",
    "destination": "https://example.com/menu",
    "folder": "Restaurant",
    "status": "active",
    "is_active": true,
    "starts_at": null,
    "expires_at": null,
    "scan_limit": null,
    "has_password": false,
    "utm": { "utm_source": "qr", "utm_medium": "print", "utm_campaign": null },
    "scans_count": 0,
    "last_scanned_at": null,
    "created_at": "2026-11-03T10:15:00+00:00",
    "updated_at": "2026-11-03T10:15:00+00:00"
  }
}

PATCH /codes/{slug}

Changes the fields you send and leaves the rest alone. The slug and short URL never change.

curl -X PATCH https://neoreader.com/api/v1/codes/k7Xp2m \
  -H "Authorization: Bearer YOUR_TOKEN" -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"destination":"https://example.com/summer-menu","expires_at":"2026-12-31T23:59:00Z"}'
{ "data": { "slug": "k7Xp2m", "destination": "https://example.com/summer-menu", "expires_at": "2026-12-31T23:59:00+00:00", "...": "..." } }

DELETE /codes/{slug}

Deletes the code and its scan history for good. Returns 204 with no body. The printed code stops working, so pause it instead if you may need it again.

curl -X DELETE https://neoreader.com/api/v1/codes/k7Xp2m \
  -H "Authorization: Bearer YOUR_TOKEN" -H "Accept: application/json"
HTTP/1.1 204 No Content

GET /codes/{slug}/stats

Scan statistics for the last days days (1 to 365, default 30, today included). unique counts one visitor once per day.

curl "https://neoreader.com/api/v1/codes/k7Xp2m/stats?days=7" \
  -H "Authorization: Bearer YOUR_TOKEN" -H "Accept: application/json"
{
  "data": {
    "slug": "k7Xp2m",
    "days": 7,
    "from": "2026-10-28",
    "to": "2026-11-03",
    "totals": { "scans": 42, "unique": 31 },
    "all_time_scans": 120,
    "daily": [ { "date": "2026-10-28", "scans": 5, "unique": 4 }, "..." ],
    "countries": [ { "country": "US", "scans": 30 }, { "country": "GB", "scans": 8 } ],
    "devices": [ { "device": "mobile", "scans": 39 } ],
    "os": [ { "os": "iOS", "scans": 25 } ],
    "browsers": [ { "browser": "Safari", "scans": 24 } ]
  }
}

Rate limits

Each token may make 120 requests per minute. Requests without a valid token share a lower limit per IP address. Over the limit you get 429 Too Many Attempts with a Retry-After header giving the seconds to wait. Each account may hold up to 250 dynamic codes.

Errors

Errors are JSON with a message. Validation errors also list each field under errors.

StatusMeaning
401The token is missing, wrong or revoked.
403The account is disabled.
404No such code on your account.
422A field is invalid, you reached your code limit, or you tried to turn a blocked code back on.
429Too many requests. Wait for the seconds in Retry-After.
{
  "message": "Enter a full web address starting with https://",
  "errors": { "destination": ["Enter a full web address starting with https://"] }
}

MCP server

NeoReader also runs a hosted Model Context Protocol server, so an AI assistant can manage your codes in plain language: “Point my menu code at the new PDF”, “Which of my codes got the most scans this month?” It uses the same tokens, limits and ownership rules as the API.

The server is called NeoReader QR and lives at https://neoreader.com/mcp (streamable HTTP). Authenticate with the header Authorization: Bearer YOUR_TOKEN.

ToolWhat it does
list_codesLists your codes, with optional search, folder and status filters.
create_codeCreates a code from a title and a destination.
update_codeChanges a code's destination, title, folder, on/off state or expiry.
get_code_statsScan totals and breakdowns for the last N days.
pause_codeStops a code redirecting without deleting it.
delete_codeDeletes a code for good. Marked as destructive, so most assistants ask you first.

Claude Code

claude mcp add --transport http neoreader https://neoreader.com/mcp --header "Authorization: Bearer YOUR_TOKEN"

Claude Desktop

Add this to claude_desktop_config.json (Settings, Developer, Edit Config), then restart the app. It uses the mcp-remote bridge, which needs Node.js.

{
  "mcpServers": {
    "neoreader": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://neoreader.com/mcp", "--header", "Authorization: Bearer YOUR_TOKEN"]
    }
  }
}

Cursor

Add this to ~/.cursor/mcp.json, or to .cursor/mcp.json inside a project.

{
  "mcpServers": {
    "neoreader": {
      "url": "https://neoreader.com/mcp",
      "headers": { "Authorization": "Bearer YOUR_TOKEN" }
    }
  }
}

Free for members

The API and the MCP server are free for every member while Pro is being built. Questions or something missing? Get in touch.