OpenQR.io (1.1.0)

Download OpenAPI specification:

Create, design and manage QR codes from your own code, or connect an AI assistant to your OpenQR account.

Introduction

OpenQR.io lets you create, update and organise static and dynamic QR codes outside the dashboard. You can work with your account in two ways:

For Start here
REST API Your own apps, scripts and integrations Authentication
MCP server AI assistants such as Claude and ChatGPT MCP overview

Both work with the same account, folders and QR codes as the dashboard.

Plan limits

Every active QR code counts towards your plan limit, whether it is static or dynamic, and however it was created. Archiving a code frees its slot. GET /users shows your limit and how many codes you can still create.

MCP overview

Connect Claude, ChatGPT or another AI assistant to your OpenQR.io account, and manage QR codes by asking in plain words: "Create a dynamic QR code for our spring menu in the Restaurant folder", or "How many scans did the flyer code get last month?"

One URL works for every assistant:

https://mcp.openqr.io/mcp

Any registered OpenQR user can connect, on any plan. Your plan limits apply just as they do in the dashboard.

What an assistant can do

  • List, find and open your QR codes, and filter them by folder or archived status.
  • Create static and dynamic QR codes, and change their name, folder, content and colors and shapes.
  • Preview a code and see exactly what it encodes.
  • Read scan statistics for dynamic codes: all-time scans, plus daily, country, city and OS breakdowns for a month.
  • Archive codes, and list, create and rename folders.
  • Check how many codes your plan still allows.

The full list is under MCP tools.

Changes to dynamic codes affect every printed copy. Changing a dynamic code's content or archiving it changes what every printed copy of it does. Assistants are told to confirm with you before doing either.

Security and permissions

  • You approve every connection. You sign in to OpenQR, and a consent screen shows the app and what it will be able to do. Nothing is shared until you click Allow.
  • Only QR codes and folders. A connected assistant works with your QR codes and folders as you. It can't access billing, API keys or account settings.
  • Access expires when unused. The connection stays signed in while you use it. If it isn't used for 60 days, it expires and you connect again.
  • Disconnect at any time under Integrations in the dashboard.
  • Approved apps only. The hosted apps that can connect are Claude and ChatGPT. Desktop apps on your own computer, such as Claude Code, Cursor and VS Code, can also connect. To ask for another hosted app, email [email protected].

Limits

  • MCP requests are limited to 120 per minute per account, separately from the REST API.

  • Assistants can create these types:

    Kind Types
    Dynamic URL, text, call, SMS, email, Wi-Fi, location
    Static URL, text, call, SMS, email, location, event, Wi-Fi, vCard

    Landing-page types (such as dynamic vCard, event, links and social) and PDF codes are created in the dashboard.

  • Logos, frames and design templates are set in the dashboard.

Connect Claude

Add OpenQR to Claude as a custom connector. It works on claude.ai, in the Claude desktop app and on mobile, once it has been added.

Pro and Max plans

  1. In Claude, open Customize → Connectors.
  2. Click + Add and choose Add custom connector.
  3. Enter a name, for example OpenQR, and the URL https://mcp.openqr.io/mcp. Leave the authentication settings as Claude detects them (OAuth).
  4. Click Add, then Connect.
  5. Sign in to OpenQR in the window that opens, check the consent screen and click Allow.

To use it, turn on OpenQR from the + menu in a chat, then ask, for example: "List my OpenQR folders."

Team and Enterprise plans

An owner adds the connector once under Organization settings → Connectors, using the same URL. Each member then opens Customize → Connectors, finds OpenQR and clicks Connect to sign in with their own OpenQR account.

Claude Code

See Desktop apps.

Claude's menus change from time to time. If these steps don't match what you see, follow Anthropic's guide: Get started with custom connectors using remote MCP.

Connect ChatGPT

ChatGPT connects to MCP servers as plugins, on the web at chatgpt.com. You no longer need to turn on developer mode first.

  1. In ChatGPT, click Plugins in the left sidebar.
  2. Click + (Add) at the top, then Add custom MCP server.
  3. Enter a name, such as OpenQR, and under Connection enter the server URL https://mcp.openqr.io/mcp.
  4. Choose OAuth authentication, review the risk warning and select I understand and want to continue.
  5. Select Create as a plugin, then install the plugin.
  6. Sign in to OpenQR in the window that opens, check the consent screen and click Allow.
  7. In a chat, type @, pick OpenQR and ask, for example: "How many QR codes can I still create?"

ChatGPT asks you to confirm each action that changes your account, such as creating or archiving a code, before it runs.

On Business, Enterprise and Edu workspaces, adding a custom MCP server needs the Create plugins with MCPs permission. On Business workspaces only admins and owners have it by default, so ask an admin to add OpenQR if you don't see Add custom MCP server.

ChatGPT's menus change often. If these steps don't match what you see, follow OpenAI's guide: Add custom MCP server.

Desktop apps

Desktop apps and code editors connect with the same URL. Each one opens your browser so you can sign in to OpenQR and click Allow.

Claude Code

claude mcp add --transport http openqr https://mcp.openqr.io/mcp

Then run /mcp in Claude Code, pick openqr and sign in.

Cursor

Add OpenQR to ~/.cursor/mcp.json (all projects) or .cursor/mcp.json (one project):

{
  "mcpServers": {
    "openqr": {
      "url": "https://mcp.openqr.io/mcp"
    }
  }
}

Cursor shows a Needs login prompt next to the server. Click it to sign in.

VS Code

Run MCP: Add Server from the Command Palette, choose HTTP, and enter https://mcp.openqr.io/mcp. Or add it to .vscode/mcp.json:

{
  "servers": {
    "openqr": {
      "type": "http",
      "url": "https://mcp.openqr.io/mcp"
    }
  }
}

VS Code asks you to sign in the first time the server starts.

Claude desktop app

Connectors added on claude.ai are also available in the desktop app. See Connect Claude.

Other MCP clients

Any MCP client that supports the Streamable HTTP transport can connect to:

https://mcp.openqr.io/mcp

OAuth

The server follows the MCP authorization spec:

  • OAuth 2.1 with the authorization code grant and refresh tokens.
  • Dynamic client registration at /oauth/register, so no client ID needs to be issued in advance.
  • PKCE is required, with S256 only.
  • Scope: mcp:use. It is also the default when a client sends no scope.
  • Discovery: https://mcp.openqr.io/.well-known/oauth-protected-resource and https://mcp.openqr.io/.well-known/oauth-authorization-server.
  • Tokens: access tokens last 1 hour. Refresh tokens last 60 days.

Allowed redirect URIs:

Client Redirect URI
Apps on the user's computer http://localhost, http://127.0.0.1 or http://[::1] (any port), or the cursor:// and vscode:// schemes
Hosted apps Only Claude (claude.ai, claude.com) and ChatGPT (chatgpt.com)

Authorization from any other host is refused with "This app is not supported yet". To ask for your hosted app to be added, email [email protected].

API key instead of OAuth

Clients that can't sign in with OAuth can use an API key from the dashboard's API Keys page (Authentication). Send it in either header:

Authorization: Bearer YOUR_API_KEY
X-API-Key: YOUR_API_KEY

An API key gives full API access to your account and doesn't expire on its own, so prefer OAuth when the client supports it. Revoke the key in the dashboard to cut off access.

MCP tools

The OpenQR MCP server provides these tools. Assistants choose a tool from what you ask, so you don't need to name them.

Tool What it does Changes data
list_qr_codes Lists QR codes, newest first, 20 per page. Filter by folder, or show archived codes. No
get_qr_code One QR code with its type, folder and image URL. No
list_qr_types The supported types and the data fields each one needs. No
create_qr_code Creates a QR code, optionally in a folder. Counts towards your plan limit. Yes
update_qr_code Renames a code, moves it to another folder, changes its content, or restyles it (colors, module, finder, finder dot). Yes
archive_qr_code Archives a code and frees its plan slot. Restore it from the dashboard. Yes
preview_qr_code A link to the SVG image, and the exact content the code encodes. No
get_qr_code_stats Scans for a dynamic code: the all-time total, plus daily, country, city and OS breakdowns for one month. No
list_folders Lists folders, 20 per page. No
create_folder Creates a folder. The name must be 5–255 characters. Yes
rename_folder Renames a folder. Yes
get_plan_usage Your plan, codes used, the limit and codes remaining. No

Tools that read data are marked read-only, so clients can run them without asking. Clients usually ask you before running a tool that changes data.

MCP troubleshooting

What you see Why What to do
"This app is not supported yet" The assistant isn't on the list of approved hosted apps. Email [email protected] to ask for it to be added.
"This connection request has expired" Sign-in took more than 30 minutes, or Allow was clicked twice. Go back to the assistant and connect again.
A plan limit message when creating a code Every active code counts, static or dynamic. Archive codes you no longer need, or upgrade your plan.
"Another QR code is still being created" Several codes were being created at the same time. Try again in a moment.
The assistant asks you to reconnect The connection was removed under Integrations, or wasn't used for 60 days. Connect again.
Too many requests (429) More than 120 requests in a minute. Wait a minute, then continue.
The assistant can't create a dynamic vCard, dynamic event, links, social or PDF code Those types use a landing page or an uploaded file. Create them in the dashboard.

Still stuck? Email [email protected] with the assistant's name and the message you saw.

Authentication

Every request needs an API key, sent as a bearer token:

Authorization: Bearer YOUR_API_KEY

Get an API key

  1. Sign up or sign in at dashboard.openqr.io.
  2. Open the user menu in the top right corner and choose API Keys.
  3. Create a key. You can create several, for example one per integration, and revoke each one separately.

OpenQR.io API Keys

Copy the key when it is created. OpenQR does not store it and cannot show it again. Treat it like a password: keep it on your server and never put it in front-end code or a public repository.

Authentication errors

Status Meaning
401 The key is missing, wrong or revoked.
403 The key is valid but has no API access, or the item belongs to another account.

The same key also works with the MCP server for AI clients that cannot sign in with OAuth.

Requests and pagination

The OpenQR API is a REST API: every request and response body is JSON, and results are reported with standard HTTP status codes.

https://api.openqr.io/api/v1

Request basics

  • Send Accept: application/json with every request. Without it, errors are not returned as JSON.
  • Send request bodies as JSON with Content-Type: application/json. The one exception is file uploads, which use multipart/form-data.
  • Updates use POST to the item's URL, for example POST /qr-codes/{qr_code_id}.

Pagination

Lists return 20 items per page.

  • QR codes and folders use cursor pagination. To get the next page, pass meta.next_cursor as the cursor query parameter, or follow links.next. When next_cursor is null, you are on the last page.
  • Files use page numbers. Pass page=2, page=3 and so on, or follow links.next.

Errors and rate limits

Errors return an HTTP status code and a JSON body with a message:

{
  "message": "The name field is required."
}
Status When
400 Validation failed. message describes the first invalid field.
401 The API key is missing, wrong or revoked.
403 Your plan limit is reached, the key has no API access, or the item belongs to another account.
404 The item does not exist.
409 Another QR code is still being created in your account. Retry after a moment.
429 Too many requests. Wait before retrying.
500 Something went wrong on our side. Retry later, and contact [email protected] if it keeps happening.

Rate limits

Limits are counted per account, per minute.

Requests Limit
All API requests (your dashboard session counts too) 100 per minute
Create, preview, preview and save, and update QR code, combined 60 per minute
MCP server requests (counted separately) 120 per minute

When you go over a limit you get 429. Wait until the minute has passed, then retry.

Plan limit

Every active QR code counts towards your plan limit, whether it is static or dynamic. When the limit is reached, creating a code returns 403. Archive codes you no longer need, or upgrade your plan in the dashboard. GET /users shows qr_codes_limit and qr_codes_remaining.

Folders

Organise your QR codes in folders. A QR code can be in one folder at a time.

Create folder

Create a folder to organise your QR codes.

Authorizations:
api_key
Request Body schema: application/json
required

Folder to save

name
required
string [ 5 .. 255 ] characters

Folder name.

Responses

Request samples

Content type
application/json
{
  • "name": "Marketing"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

List folders

List your folders, 20 per page.

Authorizations:
api_key
query Parameters
cursor
string

Cursor for the page to fetch, from meta.next_cursor of the previous page.

Responses

Request samples

use GuzzleHttp\Client;

$client = new Client();

$response = $client->request('GET', 'https://api.openqr.io/api/v1/folders',
[
    'headers' => [
        'Authorization' => 'Bearer api_key',
        'Accept' => 'application/json',
    ]
]);

echo $response->getBody()->getContents();

Response samples

Content type
application/json
{}

Rename folder

Change a folder's name.

Authorizations:
api_key
path Parameters
folder_id
required
integer

Folder ID.

Request Body schema: application/json
required

Folder to save

name
required
string [ 5 .. 255 ] characters

Folder name.

Responses

Request samples

Content type
application/json
{
  • "name": "Marketing"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Delete folder

Delete a folder. The QR codes in it are kept and moved out of the folder.

Authorizations:
api_key
path Parameters
folder_id
required
integer

Folder ID.

Responses

Response samples

Content type
application/json
{
  • "status": "success",
  • "message": "Your folder was removed"
}

QR Codes

Create, preview, update and archive static and dynamic QR codes, and design how they look.

Create QR code

Create a QR code. Every active code counts towards your plan limit; when the limit is reached you get 403.

To show the user the image before saving, use Preview QR code and then Save previewed QR code.

Authorizations:
api_key
Request Body schema: application/json
required

QR code to create

name
required
string [ 5 .. 255 ] characters

QR code name, shown in the dashboard.

type
required
string (QRCodeType)
Enum: "url" "url_static" "text" "text_static" "call" "call_static" "sms" "sms_static" "email" "email_static" "wifi" "wifi_static" "geo" "geo_static" "event" "event_static" "vcard" "vcard_static"

QR code type. Types ending in _static encode their content directly in the image. The others are dynamic: the image holds a short link, so you can change the content later and see scan statistics.

Dynamic vcard and event codes open a landing page. Build the page in the dashboard, then pass its ID as qr_page_id. The pdf, links, social, app and gs1_digital_link types are managed in the dashboard.

required
URL (url, url_static) (object) or Text (text, text_static) (object) or Call (call, call_static) (object) or SMS (sms, sms_static) (object) or Email (email, email_static) (object) or Wi-Fi (wifi, wifi_static) (object) or Location (geo, geo_static) (object) or Event (event_static) (object) or vCard (vcard_static) (object) or Landing page (event, vcard) (object)

Content of the code. The fields depend on type.

qr_code_folder_id
integer or null

ID of one of your folders to put the code in.

domain_id
integer or null

ID of one of your custom domains, used for a dynamic code's short link. Leave it out to use the default domain.

qr_page_id
integer or null

ID of a landing page built in the dashboard. Required for dynamic event and vcard codes; the page's type must match.

object (QRCodeDesign)

How the QR code looks. Every field is optional. Colors are hex values such as #000000, #000 or #000000FF.

Responses

Request samples

Content type
application/json
Example
{
  • "name": "Spring menu",
  • "type": "url",
  • "qr_code_folder_id": 6,
  • "data": {},
  • "design": {
    }
}

Response samples

Content type
application/json
{}

List QR codes

List your QR codes, newest first, 20 per page.

Authorizations:
api_key
query Parameters
status
string
Default: "public"
Enum: "public" "archived"

public (default) for active codes, or archived.

folder_id
integer

Only return codes in this folder.

cursor
string

Cursor for the page to fetch, from meta.next_cursor of the previous page.

Responses

Request samples

use GuzzleHttp\Client;

$client = new Client();

$response = $client->request('GET', 'https://api.openqr.io/api/v1/qr-codes',
[
    'headers' => [
        'Authorization' => 'Bearer api_key',
        'Accept' => 'application/json',
    ]
]);

echo $response->getBody()->getContents();

Response samples

Content type
application/json
{}

Preview QR code

Get the SVG image of a QR code without saving it. Takes the same body as Create QR code.

A dynamic code's image contains its future short link. The ID behind that link is kept for your account and the code's type, so repeated previews show the same image. To save the code with exactly that link, use Save previewed QR code.

Authorizations:
api_key
Request Body schema: application/json
required

QR code to create

name
required
string [ 5 .. 255 ] characters

QR code name, shown in the dashboard.

type
required
string (QRCodeType)
Enum: "url" "url_static" "text" "text_static" "call" "call_static" "sms" "sms_static" "email" "email_static" "wifi" "wifi_static" "geo" "geo_static" "event" "event_static" "vcard" "vcard_static"

QR code type. Types ending in _static encode their content directly in the image. The others are dynamic: the image holds a short link, so you can change the content later and see scan statistics.

Dynamic vcard and event codes open a landing page. Build the page in the dashboard, then pass its ID as qr_page_id. The pdf, links, social, app and gs1_digital_link types are managed in the dashboard.

required
URL (url, url_static) (object) or Text (text, text_static) (object) or Call (call, call_static) (object) or SMS (sms, sms_static) (object) or Email (email, email_static) (object) or Wi-Fi (wifi, wifi_static) (object) or Location (geo, geo_static) (object) or Event (event_static) (object) or vCard (vcard_static) (object) or Landing page (event, vcard) (object)

Content of the code. The fields depend on type.

qr_code_folder_id
integer or null

ID of one of your folders to put the code in.

domain_id
integer or null

ID of one of your custom domains, used for a dynamic code's short link. Leave it out to use the default domain.

qr_page_id
integer or null

ID of a landing page built in the dashboard. Required for dynamic event and vcard codes; the page's type must match.

object (QRCodeDesign)

How the QR code looks. Every field is optional. Colors are hex values such as #000000, #000 or #000000FF.

Responses

Request samples

Content type
application/json
Example
{
  • "name": "Spring menu",
  • "type": "url",
  • "qr_code_folder_id": 6,
  • "data": {},
  • "design": {
    }
}

Response samples

Content type
application/json
{
  • "message": "string"
}

Save previewed QR code

Save a QR code you previewed with Preview QR code, keeping the ID and short link shown in the preview, so an image you already showed or printed stays valid. Takes the same body as Create QR code, and counts towards your plan limit the same way.

Authorizations:
api_key
Request Body schema: application/json
required

QR code to create

name
required
string [ 5 .. 255 ] characters

QR code name, shown in the dashboard.

type
required
string (QRCodeType)
Enum: "url" "url_static" "text" "text_static" "call" "call_static" "sms" "sms_static" "email" "email_static" "wifi" "wifi_static" "geo" "geo_static" "event" "event_static" "vcard" "vcard_static"

QR code type. Types ending in _static encode their content directly in the image. The others are dynamic: the image holds a short link, so you can change the content later and see scan statistics.

Dynamic vcard and event codes open a landing page. Build the page in the dashboard, then pass its ID as qr_page_id. The pdf, links, social, app and gs1_digital_link types are managed in the dashboard.

required
URL (url, url_static) (object) or Text (text, text_static) (object) or Call (call, call_static) (object) or SMS (sms, sms_static) (object) or Email (email, email_static) (object) or Wi-Fi (wifi, wifi_static) (object) or Location (geo, geo_static) (object) or Event (event_static) (object) or vCard (vcard_static) (object) or Landing page (event, vcard) (object)

Content of the code. The fields depend on type.

qr_code_folder_id
integer or null

ID of one of your folders to put the code in.

domain_id
integer or null

ID of one of your custom domains, used for a dynamic code's short link. Leave it out to use the default domain.

qr_page_id
integer or null

ID of a landing page built in the dashboard. Required for dynamic event and vcard codes; the page's type must match.

object (QRCodeDesign)

How the QR code looks. Every field is optional. Colors are hex values such as #000000, #000 or #000000FF.

Responses

Request samples

Content type
application/json
Example
{
  • "name": "Spring menu",
  • "type": "url",
  • "qr_code_folder_id": 6,
  • "data": {},
  • "design": {
    }
}

Response samples

Content type
application/json
{}

Update QR code

Change a QR code's name, content, folder, domain or design. The type can't be changed.

The update replaces the code's settings. Send qr_code_folder_id, domain_id and design again with every update, or they are cleared.

Changing a dynamic code's content changes what every printed copy opens. Changing a static code's content changes the image, so printed copies keep the old content.

Authorizations:
api_key
path Parameters
qr_code_id
required
string
Examples: km0yYX0

QR code ID.

Request Body schema: application/json
required

New values for the QR code. The type can't be changed.

name
required
string <= 255 characters

QR code name.

required
URL (url, url_static) (object) or Text (text, text_static) (object) or Call (call, call_static) (object) or SMS (sms, sms_static) (object) or Email (email, email_static) (object) or Wi-Fi (wifi, wifi_static) (object) or Location (geo, geo_static) (object) or Event (event_static) (object) or vCard (vcard_static) (object) or Landing page (event, vcard) (object)

Content of the code, with the fields of the code's type. See Create QR code.

qr_code_folder_id
integer or null

ID of one of your folders, or null for no folder.

domain_id
integer or null

ID of one of your custom domains, or null for the default domain.

qr_page_id
integer or null

ID of the landing page, for dynamic event and vcard codes. Leave it out to keep the current page.

object (QRCodeDesign)

How the QR code looks. Every field is optional. Colors are hex values such as #000000, #000 or #000000FF.

Responses

Request samples

Content type
application/json
{
  • "name": "Spring menu",
  • "qr_code_folder_id": 6,
  • "data": {},
  • "design": {
    }
}

Response samples

Content type
application/json
{}

Get QR code

Get one QR code.

Authorizations:
api_key
path Parameters
qr_code_id
required
string
Examples: km0yYX0

QR code ID.

Responses

Request samples

use GuzzleHttp\Client;

$client = new Client();

$response = $client->request('GET', 'https://api.openqr.io/api/v1/qr-codes/{qr_code_id}',
[
    'headers' => [
        'Authorization' => 'Bearer api_key',
        'Accept' => 'application/json',
    ]
]);

echo $response->getBody()->getContents();

Response samples

Content type
application/json
{}

Archive QR code

Archive a QR code. It no longer counts towards your plan limit, and it appears under status=archived in List QR codes. Restore it from the dashboard.

An archived dynamic code stops redirecting, so every printed copy stops working. Archiving a code that is already archived does nothing.

Authorizations:
api_key
path Parameters
qr_code_id
required
string
Examples: km0yYX0

QR code ID.

Responses

Response samples

Content type
application/json
{}

Files

Upload logos and list the logos you can place in the middle of a QR code.

Upload a logo

Upload a logo image to use on your QR codes. Use the returned id as design.logo_id.

Authorizations:
api_key
Request Body schema: multipart/form-data
required

Logo image to upload

file
required
string <binary>

The logo image. Use a PNG or JPEG image, ideally square.

Responses

Request samples

use GuzzleHttp\Client;

$client = new Client();

$response = $client->request('POST', 'https://api.openqr.io/api/v1/files/qr-logos',
    [
        'headers' => [
            'Authorization' => 'Bearer api_key',
            'Accept' => 'application/json',
        ],
        'multipart' => [
            [
                'name' => 'file',
                'filename' => 'logo.png',
                'contents' => fopen('/path/to/logo.png', 'r'),
            ],
        ],
    ]
);

echo $response->getBody()->getContents();

Response samples

Content type
application/json
{}

List logos

List the logos you can use on QR codes, 20 per page. This includes the logos you uploaded and the ones OpenQR provides (public is true).

Authorizations:
api_key
query Parameters
page
integer >= 1
Default: 1

Page number.

Responses

Request samples

use GuzzleHttp\Client;

$client = new Client();

$response = $client->request('GET', 'https://api.openqr.io/api/v1/files/qr-logos',
[
    'headers' => [
        'Authorization' => 'Bearer api_key',
        'Accept' => 'application/json',
    ]
]);

echo $response->getBody()->getContents();

Response samples

Content type
application/json
{}

Users

Your account's plan and how many QR codes you can still create.

Get plan and usage

Get your account's plan, how many QR codes it allows, and how many you can still create. Check qr_codes_remaining before creating several codes.

Authorizations:
api_key

Responses

Response samples

Content type
application/json
{
  • "account_trial_ends_at": "2026-10-21T18:31:32.000000Z",
  • "account_on_trial": false,
  • "created_qr_codes_count": 3,
  • "qr_codes_limit": 60,
  • "qr_codes_remaining": 57,
  • "subscription": {
    },
  • "plan": {
    }
}