---
title: "Scanlo API and MCP server for QR codes | Scanlo"
type: "page"
canonical: "https://scanlo.io/developers"
description: "Create and update tracked QR codes and hubs, and read their scans, from Claude, ChatGPT or your own code. Remote MCP server and REST API on every plan."
---

# 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.

-   MCP server: `https://mcp.scanlo.io`
-   REST API: `https://api.scanlo.io/v1`
-   OpenAPI: [https://api.scanlo.io/v1/openapi.json](https://api.scanlo.io/v1/openapi.json)

## 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](https://claude.ai/directory/scanlo) 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

| Tool | What it does | Changes data |
| --- | --- | --- |
| 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. | No |
| list\_codes | Every QR code and hub on the account, with its short URL, destination, status and a link to its QR image (SVG). | No |
| 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. | Yes |
| 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). | No |
| 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. | Yes |
| 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. | No |
| 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). | Yes |
| 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. | Yes |
| 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). | 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](/pricing): 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.

| Field | In | Type | Description |
| --- | --- | --- | --- |
| type | body | "review" \| "booking" \| "payment" | What the code is for. |
| name | body | string, up to 80 chars | Internal name, shown only in the dashboard. |
| destination\_url | body | string, up to 2048 chars | Where a scan goes. https:// is added if missing. |
| placement? | body | string, up to 80 chars | Where 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).

| Field | In | Type | Description |
| --- | --- | --- | --- |
| id | path | uuid | Id 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.

| Field | In | Type | Description |
| --- | --- | --- | --- |
| id | path | uuid | Id of the code or hub, from GET /v1/codes. |
| destination\_url? | body | string, up to 2048 chars | New destination for every scan. |
| name? | body | string, up to 80 chars | Internal name, shown only in the dashboard. |
| placement? | body | string, up to 80 chars | Where the code is placed, e.g. "front desk" or "receipt". Placement reports group by it. |
| active? | body | boolean | false 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.

| Field | In | Type | Description |
| --- | --- | --- | --- |
| id | path | uuid | Id of the code or hub, from GET /v1/codes. |
| days? | query | integer, 1 to 90 | How many days back. Default 30. Free plan sees at most 30, Standard 90. |
| include\_bots? | query | boolean | Count 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.

| Field | In | Type | Description |
| --- | --- | --- | --- |
| days? | query | integer, 1 to 90 | How many days back. Default 30. Free plan sees at most 30, Standard 90. |
| include\_bots? | query | boolean | Count 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).

| Field | In | Type | Description |
| --- | --- | --- | --- |
| type | body | "payment" \| "review" \| "tip" \| "donation" | What the hub is for. |
| name | body | string, up to 80 chars | Internal name, shown only in the dashboard. |
| title? | body | string, up to 80 chars | Heading on the hub page, e.g. the business name. |
| placement? | body | string, up to 80 chars | Where the code is placed, e.g. "front desk" or "receipt". Placement reports group by it. |
| links | body | array of object, up to 10 | The buttons, in display order. Free plan: up to 2. |
| links\[\].provider | body | string | Provider 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\[\].label | body | string, up to 80 chars | Button text, e.g. "Pay with Venmo". |
| links\[\].url | body | string, up to 2048 chars | Where 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.

| Field | In | Type | Description |
| --- | --- | --- | --- |
| id | path | uuid | Id of the hub, from GET /v1/codes. |
| links? | body | array of object, up to 10 | The full new set of buttons, in display order. Buttons left out are removed. |
| links\[\].provider | body | string | Provider 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\[\].label | body | string, up to 80 chars | Button text, e.g. "Pay with Venmo". |
| links\[\].url | body | string, up to 2048 chars | Where the button goes: the business's own profile or payment link. |
| links\[\].id? | body | uuid | Id of an existing button to keep. |
| title? | body | string, up to 80 chars | Heading on the hub page. |
| description? | body | string, up to 300 chars | Text under the heading (Standard only). |
| show\_scanlo\_branding? | body | boolean | The "Powered by Scanlo" footer. Only Standard can turn it off. |
| name? | body | string, up to 80 chars | Internal name, shown only in the dashboard. |
| placement? | body | string, up to 80 chars | Where the code is placed, e.g. "front desk" or "receipt". Placement reports group by it. |
| active? | body | boolean | false 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).

| Field | In | Type | Description |
| --- | --- | --- | --- |
| id | path | uuid | Id of the hub, from GET /v1/codes. |
| days? | query | integer, 1 to 90 | How many days back. Default 30. Free plan sees at most 30, Standard 90. |
| include\_bots? | query | boolean | Count 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](/contact).

---

Canonical URL: https://scanlo.io/developers
