For AI assistants and scripts
Put the paperwork in the archive without touching a browser.
A QRchive sticker turns a physical thing into a permanent document archive. Scan the sticker, and the manuals, photos, reports and records that belong to that thing are there — for whoever is standing in front of it next.
Filling an archive after a job is the tedious part, and it is the part an agent is good at. This page is how yours does it. Most of it works with no sign-in and no keys: an agent that has the sticker’s address has everything it needs. An agent can also read an archive: its details, its files, and the text inside its PDFs. To work as the account owner — reading your archives, filling in their details — there are scoped keys.
$49 CAD per archive, one time. No subscription. Unlocking an archive is always done by a person in a browser. Nothing here can spend your money.
Start from the sticker’s address
Every sticker carries a public address:
https://qrchive.ca/docs/QR-123456-AB7CD
└──┬───┘ └─┬─┘
code tokenThose two values are all the calls below need. The token’s alphabet leaves out 0, O, 1, I and L, so it survives being read off a printed sticker.
What your agent can and cannot do
On an archive that has been unlocked and still accepts additions from anyone, an agent with no credentials can add files, list the sections inside, check whether the archive is unlocked yet, and read it (on the demo archive for now).
Without a key it cannot act as the owner: no listing an account’s archives, no editing written details, no creating sections. With a key it can do those, and still cannot pay for anything, sign in to the dashboard, change the account email, delete the account, or make another key. That last list is permanent.
Adding a file
Three calls. The middle one goes straight to storage, not to us.
1. POST https://qrchive.ca/api/upload/request
{ "qrCodeId": "QR-123456",
"fileName": "as-built.pdf",
"fileSize": 482118,
"fileType": "application/pdf" }
→ { "success": true,
"uploadUrl": "https://…", ← good for 15 minutes
"objectKey": "…",
"expiresAt": "…" }
2. PUT the raw bytes to uploadUrl
Content-Type: application/pdf
(no QRchive headers — the URL carries its own signature)
3. POST https://qrchive.ca/api/upload/confirm
{ "r2ObjectKey": "<objectKey from step 1>",
"qrCodeId": "QR-123456",
"fileName": "as-built.pdf",
"fileSize": 482118,
"fileType": "application/pdf" }
→ { "success": true, "fileId": "…" }Step 3 is not optional. Without it the bytes sit in storage and never join the archive.
Reading an archive
Add /llms.txt or /archive.json to a sticker’s public address:
https://qrchive.ca/docs/QR-123456-AB7CD/llms.txt
https://qrchive.ca/docs/QR-123456-AB7CD/archive.jsonBoth show what anyone who scans the sticker can see: the archive’s details, its contacts, and every file with a link to open it. llms.txt is Markdown sent as plain text. archive.json is the same listing as JSON (schema qrchive.archive/v1). When a PDF has text inside it, its entry also has a text link that returns that text. Short PDFs are printed in full in llms.txt. Follow the links the listing gives you; their form is not part of this contract and may change.
Photos and video are listed by name only. Nothing describes what they show.
Today this is switched on for one demo archive: https://qrchive.ca/docs/QR-101833-JAR39. Every other archive answers 404 at these two addresses until it is switched on. A wrong token also answers 404, the same as the archive page. A private archive answers 403. Errors here are plain text, not the JSON shape below. Nothing is cached, and nothing is indexed by search engines.
Or just send an email
Every archive has its own address, built from the same two values:
qr-123456-ab7cd@in.qrchive.caAnything that can send mail can file documents. Attachments go through the same checks as an upload. Ten messages per hour per archive.
Working as the owner: access keys
An account owner can mint a key in QRchive settings — Company & Account, then Agent access — and hand it to their own agent. Keys are shown once, stored hashed, and revocable at any time.
Authorization: Bearer qrk_9f3a2b1c8d7e6f50_…A key carries only the scopes its owner ticked when they made it:
read— list the account’s archives and their details.upload— add files to any archive the account owns, including one that accepts nothing from anybody else.manage— fill in an archive’s written details, and create or delete its sections.order— start a checkout and get back a link.
PATCH https://qrchive.ca/api/qr/QR-123456
Authorization: Bearer qrk_…
{ "projectName": "Pump house rebuild",
"projectAddress": "1400 Marine Dr",
"installDate": "2026-08-31" }
→ { "success": true }A key also cannot change who may open an archive: making one private, setting its access password, or resetting visitor access all need the owner signed in. Nor can it claim the free roll — that ships something physical, so a person does it.
A key can never pay. The order scope creates a checkout and returns its URL; the archive stays locked until a person opens that link in a browser and pays. There is no scope that completes a payment, and there will not be one.
Six hundred calls per hour per key. Every call is recorded against the key, and the owner sees each key’s last-used date in settings. Revoking a key stops it immediately — it does not quietly drop back to the anonymous upload path.
Connect your AI app (MCP)
QRchive works as an MCP connector. Add this address as a custom connector in any app that supports MCP:
https://qrchive.ca/api/mcpWithout a key, your AI can read archives that have Ask the Archive turned on. Today that is the demo archive; more will follow.
To let it work with your own archives, make an access key under Company & Account → Agent access, and add it in apps that let you set a header (such as Claude Code, Claude Desktop or Cursor):
Authorization: Bearer qrk_…A key does only what its permissions allow. It can never sign in, change your email, delete your account or pay for anything.
The published endpoints
Twelve calls across eight addresses, and no more. Each one is a promise we intend to keep, which is why the list stays short. Anything not on it may change.
| Endpoint | What it does | Who may call it |
|---|---|---|
| POST /api/upload/request | Ask for a short-lived URL to send one file to storage. | None, for an unlocked archive that accepts additions from anyone. |
| POST /api/upload/confirm | Tell QRchive the file finished sending, so it joins the archive. | None, same rule as the request step. |
| GET /api/public/folders | List the sections inside an unlocked archive. | None; needs the code and its token. |
| GET /api/qr/{qrCodeId}/status | Check whether an archive has been unlocked yet. | None; needs the code and its token. |
| GET /docs/{code}-{token}/llms.txt | Read an archive: its details and files, laid out for an AI. | None; needs the code and its token. Demo archive only for now. |
| GET /docs/{code}-{token}/archive.json | The same listing as JSON. | None; needs the code and its token. Demo archive only for now. |
| GET /api/archives | List the archives this account owns. | An access key with the read scope. |
| PATCH /api/qr/{qrCodeId} | Fill in or change an archive's written details. | An access key with the manage scope. |
| GET /api/folders | List the sections inside an archive you own. | An access key with the read scope. |
| POST /api/folders | Create a section inside an archive you own. | An access key with the manage scope. |
| DELETE /api/folders | Delete an empty section from an archive you own. | An access key with the manage scope. |
| POST /api/activate | Start a checkout to unlock a sticker and hand back the link for a person to pay. | An access key with the order scope. |
Machine-readable description: /api/openapi.json (OpenAPI 3.1). Short summary for models: /llms.txt.
Limits
- Accepted types:
pdf jpg jpeg png heic heif webp xlsx docx csv mov mp4 - 50 MB per file; 500 MB for video.
- Contents are checked against the type the name claims. A mismatch is rejected and never added to the archive.
- Files count against the archive’s storage allowance (1 GB is included).
- 500 upload requests per hour per address; 10 emails per hour per archive.
- A private archive, or one whose owner has turned additions off, refuses anonymous uploads — but not the owner’s own key.
- 600 calls per hour per access key.
Errors
Every documented endpoint fails the same shape:
{
"success": false,
"error": { "message": "Storage limit reached (1024 MB)…",
"code": "STORAGE_FULL" },
"code": "STORAGE_FULL"
}Worth branching on: INVALID_FILE_TYPE, FILE_TOO_LARGE, MAGIC_BYTE_MISMATCH, STORAGE_FULL, NOT_ACTIVATED, RATE_LIMIT_EXCEEDED. The first three will never succeed on a retry of the same file. The last one will, after the Retry-After seconds.
With a key, also UNAUTHORIZED — the key is unknown, revoked, or wrong — and FORBIDDEN, which means the key is good but was not given the scope this call needs. Retrying will not fix either; a new key with the right scopes will.
What QRchive does not do
QRchive runs no AI. It does not sort, summarise, or answer questions about what it holds. When a PDF already has text inside it, QRchive copies that text out word for word, and the PDF stays the record. Your agent can read that text wherever an archive’s reading view is switched on, which today means the demo archive only. QRchive keeps your documents and hands them back when the sticker is scanned. Bring your own agent.
Questions
jordan@qrchive.ca. If you are building something against these endpoints, tell us — what gets used is what gets kept.