Scanlo for AI assistants and developers

Everything you do with QR codes in the Scanlo dashboard, an assistant or a script can do too: create a code or a hub, change where it points, switch it off, and read its scans. One remote MCP server for Claude, ChatGPT and other assistants, and a REST API with the same operations. Both are on every plan, including free.

Connect an assistant

Add the MCP server address, then sign in to Scanlo when the assistant asks. You choose which account it can use on a Scanlo consent screen, and you can take access away at any time under My profile, Connected apps. Disconnecting inside the assistant only makes it forget its token; removing it in Scanlo is what ends its access.

  • Claude (web and desktop): open Scanlo in the connector directory and select Connect.
  • ChatGPT: Plugins, the + button, Add custom MCP server, then paste the same address.
  • Claude Code: claude mcp add --transport http scanlo https://mcp.scanlo.io

Then ask, for example:

  • "How many scans did my codes get this month?"
  • "Make a payment hub with Venmo and Zelle for the front counter."
  • "Point my review code at our new Google review link."
  • "Which of my hub's buttons do people tap?"

Tools

ToolWhat it doesChanges data
get_accountThe signed-in Scanlo account: plan, how many QR codes and hubs it uses and may have, and how far back its scan stats go. Call this first, and to explain why a create was refused.No
list_codesEvery QR code and hub on the account, with its short URL, destination, status and a link to its QR image (SVG).No
create_codeCreate a tracked QR code that sends every scan to one link. Types: review (a Google or other review page), booking (a booking page), payment (one payment link). For several payment or review options behind one code, use create_hub. The destination is checked for safety; plan limits apply.Yes
get_codeOne QR code or hub: where it points, its status, its QR image URL and, for a hub, every button with its id (pass those ids to update_hub to keep each button's tap history).No
update_codeChange a QR code (not a hub; use update_hub): its destination, name or placement, or switch it off and on. A printed code keeps its pixels; only where it leads changes. Switching off shows scanners a "no longer available" page and frees a plan slot. Changes reach the printed code within about a minute.Yes
get_scan_statsScans of one QR code or hub, or of the whole account when code_id is left out: total and per day, plus where they came from (scan or a tap on a website embed). Standard adds countries, devices, referrers, per-placement totals and bot filtering. Hub button taps are in get_hub_stats.No
create_hubCreate a hub: one printed QR code that opens a page of buttons, e.g. Venmo, PayPal and Zelle on a payment hub, or Google and Yelp on a review hub. Types: payment, review, tip, donation. Each button is tracked separately. Hubs have their own plan limit (free: one hub in total).Yes
update_hubChange a hub: its buttons, page title and description, name or placement, the "Powered by Scanlo" footer (Standard only), or switch it off and on. Its type (payment, review, tip, donation) is fixed: for another type, create a new hub. links replaces the whole set: call get_code first and pass each kept button's id, or its tap history starts again. Changes reach the printed code within about a minute.Yes
get_hub_statsFor one hub: how many people scanned it and how many tapped a button, so you can see how many scanners went on to pay or review. Standard adds the taps per button (which payment app wins).No

There is no delete tool. A printed code should not disappear because an assistant misread a request, and a code that has been scanned cannot be deleted at all. Switching a code off is the strongest change an assistant can make.

REST API

Base URL https://api.scanlo.io. JSON in, JSON out. Every endpoint is one of the MCP tools above, with the same arguments and the same answers.

Authentication

The API takes an OAuth 2.1 access token, the same kind an assistant gets. There are no API keys yet. Any standard OAuth client library can get a token:

  1. Point your OAuth library at https://api.scanlo.io/.well-known/oauth-protected-resource. It lists the authorization server, and that server's metadata lists every endpoint below.
  2. Register your app with dynamic client registration (RFC 7591).
  3. Send the user to the authorization endpoint with PKCE (S256). They sign in to Scanlo and allow your app.
  4. Exchange the code at the token endpoint. Access tokens last about an hour; use the refresh token for a new one.
  5. Send it as Authorization: Bearer <token>.

Quick start

curl https://api.scanlo.io/v1/account \
  -H "Authorization: Bearer $SCANLO_TOKEN"
curl -X POST https://api.scanlo.io/v1/codes \
  -H "Authorization: Bearer $SCANLO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "review",
    "name": "Front desk reviews",
    "destination_url": "https://g.page/r/your-place/review",
    "placement": "front desk"
  }'

The answer carries the short URL, a link to the QR image as SVG and the code's page in the dashboard, where the print files are.

Limits

  • The account's plan applies to everything, however it is created. Free: 2 QR codes, 1 hub with up to 2 buttons, 30 days of scans. Standard: 20 codes, 10 hubs with up to 10 buttons, 90 days, with countries, devices and bot filtering.
  • Switching a code off frees its slot. It keeps its scan history, and its printed code shows a "no longer available" page until it is switched back on.
  • Every destination is checked against Google Safe Browsing, on create and on every change.
  • A change reaches the printed code within about a minute.

Errors

An error is a status code and a body of { "error": "..." }, written so you can show it to the person as is.

400
Invalid arguments, or a destination that failed validation or Safe Browsing.
401
Missing, expired or revoked token.
403
A plan limit, a frozen or disabled code, or a Standard-only setting.
404
No code or hub with that id on this account.
429
More than 10 codes and hubs created in the last hour.

Endpoint reference

GET /v1/account

Get account. MCP tool get_account.

The signed-in Scanlo account: plan, how many QR codes and hubs it uses and may have, and how far back its scan stats go. Call this first, and to explain why a create was refused.

GET /v1/codes

List QR codes. MCP tool list_codes.

Every QR code and hub on the account, with its short URL, destination, status and a link to its QR image (SVG).

POST /v1/codes

Create QR code. MCP tool create_code.

Create a tracked QR code that sends every scan to one link. Types: review (a Google or other review page), booking (a booking page), payment (one payment link). For several payment or review options behind one code, use create_hub. The destination is checked for safety; plan limits apply.

FieldInTypeDescription
typebody"review" | "booking" | "payment"What the code is for.
namebodystring, up to 80 charsInternal name, shown only in the dashboard.
destination_urlbodystring, up to 2048 charsWhere a scan goes. https:// is added if missing.
placement?bodystring, up to 80 charsWhere the code is placed, e.g. "front desk" or "receipt". Placement reports group by it.

GET /v1/codes/{id}

Get QR code. MCP tool get_code.

One QR code or hub: where it points, its status, its QR image URL and, for a hub, every button with its id (pass those ids to update_hub to keep each button's tap history).

FieldInTypeDescription
idpathuuidId of the code or hub, from GET /v1/codes.

PATCH /v1/codes/{id}

Update QR code. MCP tool update_code.

Change a QR code (not a hub; use update_hub): its destination, name or placement, or switch it off and on. A printed code keeps its pixels; only where it leads changes. Switching off shows scanners a "no longer available" page and frees a plan slot. Changes reach the printed code within about a minute.

FieldInTypeDescription
idpathuuidId of the code or hub, from GET /v1/codes.
destination_url?bodystring, up to 2048 charsNew destination for every scan.
name?bodystring, up to 80 charsInternal name, shown only in the dashboard.
placement?bodystring, up to 80 charsWhere the code is placed, e.g. "front desk" or "receipt". Placement reports group by it.
active?bodybooleanfalse switches the code off; true switches it back on (needs a free slot).

GET /v1/codes/{id}/stats

Get scan stats. MCP tool get_scan_stats.

Scans of one QR code or hub, or of the whole account when code_id is left out: total and per day, plus where they came from (scan or a tap on a website embed). Standard adds countries, devices, referrers, per-placement totals and bot filtering. Hub button taps are in get_hub_stats.

FieldInTypeDescription
idpathuuidId of the code or hub, from GET /v1/codes.
days?queryinteger, 1 to 90How many days back. Default 30. Free plan sees at most 30, Standard 90.
include_bots?querybooleanCount link previews and scanners too. Default true. Only Standard can exclude them.

GET /v1/stats

Get scan stats. MCP tool get_scan_stats.

Scans of one QR code or hub, or of the whole account when code_id is left out: total and per day, plus where they came from (scan or a tap on a website embed). Standard adds countries, devices, referrers, per-placement totals and bot filtering. Hub button taps are in get_hub_stats.

FieldInTypeDescription
days?queryinteger, 1 to 90How many days back. Default 30. Free plan sees at most 30, Standard 90.
include_bots?querybooleanCount link previews and scanners too. Default true. Only Standard can exclude them.

POST /v1/hubs

Create hub. MCP tool create_hub.

Create a hub: one printed QR code that opens a page of buttons, e.g. Venmo, PayPal and Zelle on a payment hub, or Google and Yelp on a review hub. Types: payment, review, tip, donation. Each button is tracked separately. Hubs have their own plan limit (free: one hub in total).

FieldInTypeDescription
typebody"payment" | "review" | "tip" | "donation"What the hub is for.
namebodystring, up to 80 charsInternal name, shown only in the dashboard.
title?bodystring, up to 80 charsHeading on the hub page, e.g. the business name.
placement?bodystring, up to 80 charsWhere the code is placed, e.g. "front desk" or "receipt". Placement reports group by it.
linksbodyarray of object, up to 10The buttons, in display order. Free plan: up to 2.
links[].providerbodystringProvider key. Allowed per hub type: payment: paypal, venmo, cashapp, zelle, stripe, square, gofundme; review: google, facebook, trustpilot, yelp, bbb, tripadvisor, angi, g2, capterra; tip: paypal, venmo, cashapp, zelle, stripe, square, gofundme; donation: paypal, venmo, cashapp, zelle, stripe, square, gofundme. "custom" (any link) needs the Standard plan.
links[].labelbodystring, up to 80 charsButton text, e.g. "Pay with Venmo".
links[].urlbodystring, up to 2048 charsWhere the button goes: the business's own profile or payment link.

PATCH /v1/hubs/{id}

Update hub. MCP tool update_hub.

Change a hub: its buttons, page title and description, name or placement, the "Powered by Scanlo" footer (Standard only), or switch it off and on. Its type (payment, review, tip, donation) is fixed: for another type, create a new hub. links replaces the whole set: call get_code first and pass each kept button's id, or its tap history starts again. Changes reach the printed code within about a minute.

FieldInTypeDescription
idpathuuidId of the hub, from GET /v1/codes.
links?bodyarray of object, up to 10The full new set of buttons, in display order. Buttons left out are removed.
links[].providerbodystringProvider key. Allowed per hub type: payment: paypal, venmo, cashapp, zelle, stripe, square, gofundme; review: google, facebook, trustpilot, yelp, bbb, tripadvisor, angi, g2, capterra; tip: paypal, venmo, cashapp, zelle, stripe, square, gofundme; donation: paypal, venmo, cashapp, zelle, stripe, square, gofundme. "custom" (any link) needs the Standard plan.
links[].labelbodystring, up to 80 charsButton text, e.g. "Pay with Venmo".
links[].urlbodystring, up to 2048 charsWhere the button goes: the business's own profile or payment link.
links[].id?bodyuuidId of an existing button to keep.
title?bodystring, up to 80 charsHeading on the hub page.
description?bodystring, up to 300 charsText under the heading (Standard only).
show_scanlo_branding?bodybooleanThe "Powered by Scanlo" footer. Only Standard can turn it off.
name?bodystring, up to 80 charsInternal name, shown only in the dashboard.
placement?bodystring, up to 80 charsWhere the code is placed, e.g. "front desk" or "receipt". Placement reports group by it.
active?bodybooleanfalse switches the hub off; true switches it back on (needs a free slot).

GET /v1/hubs/{id}/stats

Get hub stats. MCP tool get_hub_stats.

For one hub: how many people scanned it and how many tapped a button, so you can see how many scanners went on to pay or review. Standard adds the taps per button (which payment app wins).

FieldInTypeDescription
idpathuuidId of the hub, from GET /v1/codes.
days?queryinteger, 1 to 90How many days back. Default 30. Free plan sees at most 30, Standard 90.
include_bots?querybooleanCount link previews and scanners too. Default true. Only Standard can exclude them.

A field ending in ? is optional. Questions or a missing operation: contact us.