oqrely API
The REST API covers the codes of a workspace, their scan analytics, static rendering and webhooks. It is described by an OpenAPI 3.1 document; a Postman collection is built from the same file. Try any operation with your key in the interactive API reference.
An AI assistant that supports remote MCP servers can use the same operations through the oqrely MCP server: you sign in once to allow it, and it works within the same scopes and limits as a key. Works with your AI assistant: setup for Claude, ChatGPT, Cursor, VS Code and more.
Quickstart
Create a key in the app under Developers, keep it in the OQRELY_API_KEY environment variable, then create an editable code. The answer holds its short_url and image links.
curl -X POST https://oqrely.com/v1/codes \
-H "Authorization: Bearer $OQRELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"mode":"editable","type":"url","content":{"url":"https://example.com/"} }'const res = await fetch('https://oqrely.com/v1/codes', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.OQRELY_API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ mode: 'editable', type: 'url', content: { url: 'https://example.com/' } }),
});
const code = await res.json();
console.log(code.short_url);import os, requests
res = requests.post(
"https://oqrely.com/v1/codes",
headers={"Authorization": f"Bearer {os.environ['OQRELY_API_KEY']}"},
json={"mode": "editable", "type": "url", "content": {"url": "https://example.com/"} },
)
print(res.json()["short_url"])<?php
$ch = curl_init('https://oqrely.com/v1/codes');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('OQRELY_API_KEY'), 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => '{"mode":"editable","type":"url","content":{"url":"https://example.com/"} }',
]);
$code = json_decode(curl_exec($ch), true);
echo $code['short_url'];require 'net/http'
require 'json'
uri = URI('https://oqrely.com/v1/codes')
req = Net::HTTP::Post.new(uri, 'Authorization' => "Bearer #{ENV['OQRELY_API_KEY']}", 'Content-Type' => 'application/json')
req.body = { mode: 'editable', type: 'url', content: { url: 'https://example.com/' } }.to_json
res = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') { |h| h.request(req) }
puts JSON.parse(res.body)['short_url']package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"strings"
)
func main() {
body := strings.NewReader(`{"mode":"editable","type":"url","content":{"url":"https://example.com/"} }`)
req, _ := http.NewRequest("POST", "https://oqrely.com/v1/codes", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("OQRELY_API_KEY"))
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
var code struct {
ShortURL string `json:"short_url"`
}
if err := json.NewDecoder(res.Body).Decode(&code); err != nil {
log.Fatal(err)
}
fmt.Println(code.ShortURL)
}Keys and scopes
Send the key in the Authorization: Bearer header on every call. A key is shown once when it is created and stored only as a hash; revoke it in the app at any time. Each key works in one workspace and has scopes, reduced to what the plan of the account includes:
render:POST /v1/renderstatic images.read: everyGET.write: creating and changing codes, folders, tags and webhook endpoints.raw_events: raw scan events, on Business and above.
After a downgrade a key keeps working with the scopes the new plan includes, so write calls answer 403 until the plan allows them again.
Limits, pagination, idempotency
Each plan has a daily quota for API calls and a separate one for renders, counted per account in UTC days (see the pricing page for the numbers). Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (Unix time of the next reset). The first call over the quota answers 429 with Retry-After in seconds. A key can also carry its own lower daily cap.
Lists return { "object": "list", "data": [...], "has_more", "next_cursor" }. Pass next_cursor as cursor to get the next page and limit (1 to 100) for its size. Cursors stay valid while items are added.
Send an Idempotency-Key header with a POST to retry it safely: for 24 hours a repeat with the same key gets the first response again (with Idempotent-Replayed: true) and creates nothing. The same key with a different body answers 422.
Errors
Every error has one shape with a stable code:
{ "error": { "code": "validation_failed", "message": "The request is not valid.", "details": { "issues": [{ "path": "content.url", "message": "Required" }] } } }Common codes: unauthorized, invalid_key, key_revoked, key_expired (401); forbidden_scope, plan_required, step_up_required (403); not_found (404); conflict, slug_taken, invalid_transition (409); validation_failed (422); rate_limited (429).
Sandbox keys
Keys starting with oqr_test_ create sandbox codes. They behave like other codes in the API, but their short links never answer scans, and sandbox keys see only sandbox codes (live keys only live ones). Use them in tests and CI.
Webhooks
On Pro and above, add an https endpoint and choose events; scan.created needs Business or above and arrives in batches, either sampled (about one scan in ten) or full. The signing secret is shown once.
| Event | When |
|---|---|
code.created | A fixed or editable code was created. |
code.updated | Content, design, rules, link settings, name, folder or tags of a code changed. |
code.deleted | A code was moved to the trash, or purged from it (`data.permanent`). |
code.state_changed | A code was turned off or on, archived, restored, blocked, or changed state on schedule. |
destination.changed | The destination of an editable code changed. |
destination.broken | A destination check found the target unreachable. |
code.flagged | A safety check blocked a code. |
form.submitted | A visitor submitted a form on a hosted page. |
page.published | A hosted page was published. |
domain.status_changed | A custom domain changed setup, TLS or health status. |
bulk.completed | A bulk job finished. |
billing.state_changed | The plan or billing state of the account changed. |
scan.created | Scans of the workspace codes, in batches (sampled or full). |
Each delivery is a POST with a JSON body { "id", "object": "event", "type", "created", "workspace_id", "data" } and the headers Oqrely-Event, Oqrely-Event-Id, Oqrely-Delivery and Oqrely-Signature: t=<unix time>,v1=<hex>. The signature is HMAC-SHA256 with the endpoint secret over the timestamp, a dot and the raw body. Check it before parsing the body, and reject timestamps older than five minutes:
import { createHmac, timingSafeEqual } from 'node:crypto';
// header: the Oqrely-Signature header, rawBody: the request body as received (a string)
export function verify(secret, header, rawBody, toleranceSec = 300) {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
const t = Number(parts.t);
if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
return parts.v1?.length === expected.length && timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
}Answer with any 2xx status within 10 seconds. Other answers, timeouts and network errors are retried 7 times, after 1 min, 4 min, 16 min, 1.1 h, 4.3 h, 17.1 h, 24 h. Every attempt is in the delivery log, and any delivery can be sent again from the log or with POST /v1/webhooks/{id}/deliveries/{delivery}/replay (the same body and event id). An endpoint that fails every attempt for a day is turned off and the account owner gets one e-mail; turn it back on in the app or with PATCH /v1/webhooks/{id}.
Reference
Base URL: https://oqrely.com/v1. Request and response fields are in the OpenAPI document.
codes19 endpoints
GET /v1/codes
List codes. Scope: read.
Newest first. Filters combine. Sandbox keys list only sandbox codes.
curl 'https://oqrely.com/v1/codes?limit=10&mode=editable' \
-H "Authorization: Bearer $OQRELY_API_KEY"POST /v1/codes
Create a fixed or editable code. Scope: write.
Editable codes get a short link on the main short domain unless `domain_id` names another. Send an `Idempotency-Key` header to retry safely.
curl -X POST 'https://oqrely.com/v1/codes' \
-H "Authorization: Bearer $OQRELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"mode":"editable","type":"url","name":"Spring flyer","content":{"url":"https://example.com/spring"},"tags":["print"]}'GET /v1/codes/{id}
Get a code. Scope: read.
curl 'https://oqrely.com/v1/codes/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01' \
-H "Authorization: Bearer $OQRELY_API_KEY"PATCH /v1/codes/{id}
Change content, design, rules, link settings, name or folder. Scope: write.
Live on the next scan. Fields are applied in this order: content, design, rules, link, name, folder; the first refusal stops the rest. A destination change on a high-traffic code needs confirmation in the app (403 step_up_required).
curl -X PATCH 'https://oqrely.com/v1/codes/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01' \
-H "Authorization: Bearer $OQRELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"content":{"url":"https://example.com/summer"} }'DELETE /v1/codes/{id}
Move a code to the trash. Scope: write.
The code stops working and can be restored for 30 days (POST /codes/{id}/restore); then it is purged and its short link is retired.
curl -X DELETE 'https://oqrely.com/v1/codes/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01' \
-H "Authorization: Bearer $OQRELY_API_KEY"POST /v1/codes/{id}/turn-off
Turn an editable code off. Scope: write.
curl -X POST 'https://oqrely.com/v1/codes/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01/turn-off' \
-H "Authorization: Bearer $OQRELY_API_KEY"POST /v1/codes/{id}/turn-on
Turn an editable code back on. Scope: write.
curl -X POST 'https://oqrely.com/v1/codes/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01/turn-on' \
-H "Authorization: Bearer $OQRELY_API_KEY"POST /v1/codes/{id}/archive
Archive a code. Scope: write.
curl -X POST 'https://oqrely.com/v1/codes/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01/archive' \
-H "Authorization: Bearer $OQRELY_API_KEY"POST /v1/codes/{id}/unarchive
Bring an archived code back. Scope: write.
curl -X POST 'https://oqrely.com/v1/codes/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01/unarchive' \
-H "Authorization: Bearer $OQRELY_API_KEY"POST /v1/codes/{id}/restore
Restore a code from the trash. Scope: write.
curl -X POST 'https://oqrely.com/v1/codes/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01/restore' \
-H "Authorization: Bearer $OQRELY_API_KEY"GET /v1/codes/{id}/image
Download the image of a code. Scope: read.
SVG, PNG, PDF or EPS with the code design. Counts against the daily API quota.
curl 'https://oqrely.com/v1/codes/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01/image?format=svg' \
-H "Authorization: Bearer $OQRELY_API_KEY" \
-o code.svgPOST /v1/codes/{id}/convert
Make an editable copy of a fixed code, or a fixed copy of an editable one. Scope: write.
A new code; the original stays as it is. A fixed copy holds the editable code's current content.
curl -X POST 'https://oqrely.com/v1/codes/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01/convert' \
-H "Authorization: Bearer $OQRELY_API_KEY"POST /v1/codes/{id}/domain
Move an editable code to another domain. Scope: write.
Its short link moves to that domain. A code that was already downloaded or exported needs `confirm_breaks_printed: true`, because printed copies stop working.
curl -X POST 'https://oqrely.com/v1/codes/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01/domain' \
-H "Authorization: Bearer $OQRELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"domain_id":"5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01"}'GET /v1/codes/{id}/print-record
The note and print runs of a code. Scope: read.
curl 'https://oqrely.com/v1/codes/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01/print-record' \
-H "Authorization: Bearer $OQRELY_API_KEY"PATCH /v1/codes/{id}/note
Set the note of a code. Scope: write.
Up to 2,000 characters; an empty note removes it.
curl -X PATCH 'https://oqrely.com/v1/codes/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01/note' \
-H "Authorization: Bearer $OQRELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"note":"Printed for the spring fair."}'POST /v1/codes/{id}/print-runs
Record a print run of a code. Scope: write.
curl -X POST 'https://oqrely.com/v1/codes/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01/print-runs' \
-H "Authorization: Bearer $OQRELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"quantity":500,"medium":"Flyer A5","printed_on":"2026-10-01","printer":"City Print"}'DELETE /v1/codes/{id}/print-runs/{run_id}
Remove a print run from a code. Scope: write.
curl -X DELETE 'https://oqrely.com/v1/codes/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01/print-runs/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01' \
-H "Authorization: Bearer $OQRELY_API_KEY"POST /v1/codes/{id}/check-again
Ask for a new check of a held code. Scope: write.
When the automatic link check held a code, this runs it again now (10 times a day per code).
curl -X POST 'https://oqrely.com/v1/codes/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01/check-again' \
-H "Authorization: Bearer $OQRELY_API_KEY"GET /v1/destinations/broken
Editable codes whose destination is down. Scope: read.
Open incidents of the daily destination check, newest first: the code, its URL, the last status or error and since when it fails. A code leaves the list when its destination answers again.
curl 'https://oqrely.com/v1/destinations/broken?limit=20' \
-H "Authorization: Bearer $OQRELY_API_KEY"tags3 endpoints
POST /v1/codes/{id}/tags
Add a tag to a code. Scope: write.
Creates the tag in the workspace when it is new.
curl -X POST 'https://oqrely.com/v1/codes/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01/tags' \
-H "Authorization: Bearer $OQRELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"print"}'DELETE /v1/codes/{id}/tags/{name}
Remove a tag from a code. Scope: write.
curl -X DELETE 'https://oqrely.com/v1/codes/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01/tags/print' \
-H "Authorization: Bearer $OQRELY_API_KEY"GET /v1/tags
List tags. Scope: read.
With the number of codes (outside the trash) carrying each tag.
curl 'https://oqrely.com/v1/tags?limit=50' \
-H "Authorization: Bearer $OQRELY_API_KEY"analytics6 endpoints
GET /v1/codes/{id}/scans
Scan counts of a code. Scope: read.
Human scans per UTC day, or summed per country or device code, over a range of up to 366 days.
curl 'https://oqrely.com/v1/codes/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01/scans?group_by=country' \
-H "Authorization: Bearer $OQRELY_API_KEY"GET /v1/codes/{id}/scans/events
Raw scan events of a code. Scope: raw_events.
Newest first, one row per human scan, without any visitor identifier. Needs the raw_events scope (Business and up).
curl 'https://oqrely.com/v1/codes/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01/scans/events?limit=50' \
-H "Authorization: Bearer $OQRELY_API_KEY"POST /v1/codes/{id}/counter/reset
Set the scan counter of an editable code back to 0. Scope: write.
The analytics history stays; only the counter (and scan limits that read it) starts again.
curl -X POST 'https://oqrely.com/v1/codes/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01/counter/reset' \
-H "Authorization: Bearer $OQRELY_API_KEY"GET /v1/scans/export
Export the scans of the workspace. Scope: read.
CSV or XLSX for the range (default 30 days). Small exports come back at once (200); larger ones are prepared in the background (202 with the export to fetch).
curl 'https://oqrely.com/v1/scans/export?format=csv&range=30d' \
-H "Authorization: Bearer $OQRELY_API_KEY" \
-o code.svgGET /v1/scans/exports/{id}
Download a prepared scan export. Scope: read.
404 until it is ready, and after it expires.
curl 'https://oqrely.com/v1/scans/exports/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01' \
-H "Authorization: Bearer $OQRELY_API_KEY" \
-o code.svgGET /v1/scans/realtime
Scans of the last 60 minutes. Scope: read.
Human scans per minute and the latest scans, for the workspace or one code.
curl 'https://oqrely.com/v1/scans/realtime' \
-H "Authorization: Bearer $OQRELY_API_KEY"render1 endpoint
POST /v1/render
Render a static code image. Scope: render.
Encodes `text` as given, or builds the text from a typed `payload` (the same types as the generator). Returns the image bytes. Daily quota: the plan `api.render` value.
curl -X POST 'https://oqrely.com/v1/render' \
-H "Authorization: Bearer $OQRELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"payload":{"type":"url","url":"https://example.com/menu"},"format":"svg"}' \
-o qr.svgfolders4 endpoints
GET /v1/folders
List folders. Scope: read.
curl 'https://oqrely.com/v1/folders?limit=50' \
-H "Authorization: Bearer $OQRELY_API_KEY"POST /v1/folders
Create a folder. Scope: write.
curl -X POST 'https://oqrely.com/v1/folders' \
-H "Authorization: Bearer $OQRELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Spring campaign"}'DELETE /v1/folders/{id}
Delete a folder. Scope: write.
Codes in it move to the top level; nothing is deleted with it but its subfolders.
curl -X DELETE 'https://oqrely.com/v1/folders/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01' \
-H "Authorization: Bearer $OQRELY_API_KEY"POST /v1/folders/{id}/restore
Bring back a deleted folder. Scope: write.
The folder, its subfolders and the codes that were in them, unless they moved since.
curl -X POST 'https://oqrely.com/v1/folders/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01/restore' \
-H "Authorization: Bearer $OQRELY_API_KEY"domains9 endpoints
GET /v1/domains
List domains codes can use. Scope: read.
The oqrely short domains and the custom domains of the workspace. Add and verify custom domains in the app.
curl 'https://oqrely.com/v1/domains?limit=20' \
-H "Authorization: Bearer $OQRELY_API_KEY"POST /v1/domains
Add a custom domain. Scope: write.
Returns the DNS record to set. The first check is queued at once; call POST /domains/{id}/check to check again. Apex domains need a provider that flattens CNAMEs (send `flattening_confirmed: true`).
curl -X POST 'https://oqrely.com/v1/domains' \
-H "Authorization: Bearer $OQRELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"hostname":"qr.example.com"}'GET /v1/domains/{id}
Get a custom domain with its setup status. Scope: read.
curl 'https://oqrely.com/v1/domains/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01' \
-H "Authorization: Bearer $OQRELY_API_KEY"POST /v1/domains/{id}/check
Check DNS, TLS or health now. Scope: write.
One check for the current setup step, as the setup screen runs while you wait.
curl -X POST 'https://oqrely.com/v1/domains/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01/check' \
-H "Authorization: Bearer $OQRELY_API_KEY"POST /v1/domains/{id}/default
Make a domain the default for new codes. Scope: write.
Send `unset: true` to go back to the main short domain.
curl -X POST 'https://oqrely.com/v1/domains/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01/default' \
-H "Authorization: Bearer $OQRELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"unset":false}'PATCH /v1/domains/{id}/site
Set what the domain root and unknown links answer. Scope: write.
Modes: redirect (to `site_url`), link_list (a page with up to 20 links) or not_found.
curl -X PATCH 'https://oqrely.com/v1/domains/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01/site' \
-H "Authorization: Bearer $OQRELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"root_mode":"redirect","site_url":"https://example.com"}'POST /v1/domains/{id}/release-slugs
Release links a previous owner retired on this domain. Scope: write.
Needs a DNS check that verified the domain since you added it, and `confirm: true`: scanners of those old printed codes then reach your links.
curl -X POST 'https://oqrely.com/v1/domains/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01/release-slugs' \
-H "Authorization: Bearer $OQRELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"confirm":true}'DELETE /v1/domains/{id}
Remove a custom domain. Scope: write.
Printed codes on it stop working. Send the hostname as `confirm` to say you mean it.
curl -X DELETE 'https://oqrely.com/v1/domains/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01?confirm=qr.example.com' \
-H "Authorization: Bearer $OQRELY_API_KEY"POST /v1/destination-domains/verify
Prove you control a destination domain. Scope: write.
Checks the DNS TXT record or the meta tag (`oqrely-verify`) on the destination site. A verified domain lifts the trial rule on new destination domains.
curl -X POST 'https://oqrely.com/v1/destination-domains/verify' \
-H "Authorization: Bearer $OQRELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"domain":"example.com"}'workspaces14 endpoints
GET /v1/workspaces
List the workspaces of the account. Scope: read.
A key works in one workspace; this lists every workspace of the same account.
curl 'https://oqrely.com/v1/workspaces?limit=20' \
-H "Authorization: Bearer $OQRELY_API_KEY"GET /v1/workspaces/current
The workspace of this key. Scope: read.
curl 'https://oqrely.com/v1/workspaces/current' \
-H "Authorization: Bearer $OQRELY_API_KEY"PATCH /v1/workspaces/current
Rename the workspace of this key. Scope: write.
curl -X PATCH 'https://oqrely.com/v1/workspaces/current' \
-H "Authorization: Bearer $OQRELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Marketing"}'POST /v1/workspaces
Create a workspace. Scope: write.
Within the plan's workspaces. The key keeps working in its own workspace; create a key in the new one to use it.
curl -X POST 'https://oqrely.com/v1/workspaces' \
-H "Authorization: Bearer $OQRELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Events"}'GET /v1/clients
List client workspaces. Scope: read.
Agency plans: the client workspaces of the account with their codes, scans and members.
curl 'https://oqrely.com/v1/clients' \
-H "Authorization: Bearer $OQRELY_API_KEY"POST /v1/clients
Create a client workspace. Scope: write.
curl -X POST 'https://oqrely.com/v1/clients' \
-H "Authorization: Bearer $OQRELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Corner Bakery"}'POST /v1/clients/{pub_id}/invites
Invite someone to a client workspace. Scope: write.
Returns the invitation link once.
curl -X POST 'https://oqrely.com/v1/clients/abc123/invites' \
-H "Authorization: Bearer $OQRELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","role":"viewer"}'POST /v1/clients/{pub_id}/brand-kit
Choose the brand kit of a client workspace. Scope: write.
Its colours and logo brand what the client's scanners see. Leave `brand_kit_id` out to clear it.
curl -X POST 'https://oqrely.com/v1/clients/abc123/brand-kit' \
-H "Authorization: Bearer $OQRELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"brand_kit_id":null}'POST /v1/clients/{pub_id}/copy
Copy codes between your workspace and a client workspace. Scope: write.
Up to 50 codes per call. Copies get new short links; the originals stay.
curl -X POST 'https://oqrely.com/v1/clients/abc123/copy' \
-H "Authorization: Bearer $OQRELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"direction":"to_client","code_ids":["5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01"]}'GET /v1/branding
The branding scanners see. Scope: read.
curl 'https://oqrely.com/v1/branding' \
-H "Authorization: Bearer $OQRELY_API_KEY"PATCH /v1/branding
Change the branding scanners see. Scope: write.
Display name, logo (an uploaded image file), accent colour or a brand kit, on plans with custom branding.
curl -X PATCH 'https://oqrely.com/v1/branding' \
-H "Authorization: Bearer $OQRELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"display_name":"Corner Bakery","accent":"#0B6E4F"}'GET /v1/integrations/ga4
The GA4 connection of the workspace. Scope: read.
Whether every human scan is sent to a GA4 web stream as a `qr_scan` event. The API secret is never shown.
curl 'https://oqrely.com/v1/integrations/ga4' \
-H "Authorization: Bearer $OQRELY_API_KEY"POST /v1/integrations/ga4
Send scans to GA4. Scope: write.
A measurement id (G-...) and a Measurement Protocol API secret from your GA4 web stream.
curl -X POST 'https://oqrely.com/v1/integrations/ga4' \
-H "Authorization: Bearer $OQRELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"measurement_id":"G-ABCD1234","api_secret":"secret-from-ga4"}'DELETE /v1/integrations/ga4
Stop sending scans to GA4. Scope: write.
curl -X DELETE 'https://oqrely.com/v1/integrations/ga4' \
-H "Authorization: Bearer $OQRELY_API_KEY"templates1 endpoint
GET /v1/templates
List templates. Scope: read.
Published oqrely templates and the templates of the workspace.
curl 'https://oqrely.com/v1/templates?kind=design' \
-H "Authorization: Bearer $OQRELY_API_KEY"webhooks7 endpoints
GET /v1/webhooks
List webhook endpoints. Scope: read.
curl 'https://oqrely.com/v1/webhooks' \
-H "Authorization: Bearer $OQRELY_API_KEY"POST /v1/webhooks
Add a webhook endpoint. Scope: write.
The response carries the signing secret once; store it. URLs must use https and reach a public address.
curl -X POST 'https://oqrely.com/v1/webhooks' \
-H "Authorization: Bearer $OQRELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://hooks.example.com/oqrely","events":["code.created","code.updated"]}'GET /v1/webhooks/{id}
Get a webhook endpoint. Scope: read.
curl 'https://oqrely.com/v1/webhooks/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01' \
-H "Authorization: Bearer $OQRELY_API_KEY"PATCH /v1/webhooks/{id}
Change a webhook endpoint. Scope: write.
Send `enabled: true` to turn an endpoint back on after it was turned off for failing.
curl -X PATCH 'https://oqrely.com/v1/webhooks/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01' \
-H "Authorization: Bearer $OQRELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"events":["code.created","code.deleted"]}'DELETE /v1/webhooks/{id}
Delete a webhook endpoint. Scope: write.
Its delivery log goes with it.
curl -X DELETE 'https://oqrely.com/v1/webhooks/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01' \
-H "Authorization: Bearer $OQRELY_API_KEY"GET /v1/webhooks/{id}/deliveries
Delivery log of an endpoint. Scope: read.
Newest first, each delivery with every attempt (time, status code, duration, error). Kept 30 days.
curl 'https://oqrely.com/v1/webhooks/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01/deliveries?limit=20' \
-H "Authorization: Bearer $OQRELY_API_KEY"POST /v1/webhooks/{id}/deliveries/{delivery}/replay
Send a delivery again. Scope: write.
Queues the identical body (same event id) as a new delivery, signed with a fresh timestamp.
curl -X POST 'https://oqrely.com/v1/webhooks/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01/deliveries/1/replay' \
-H "Authorization: Bearer $OQRELY_API_KEY"pages11 endpoints
GET /v1/pages
List hosted pages. Scope: read.
Newest change first, with whether each is published.
curl 'https://oqrely.com/v1/pages?limit=20' \
-H "Authorization: Bearer $OQRELY_API_KEY"GET /v1/pages/{id}
Get a hosted page with its draft blocks. Scope: read.
curl 'https://oqrely.com/v1/pages/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01' \
-H "Authorization: Bearer $OQRELY_API_KEY"POST /v1/pages
Create a hosted page from structured fields. Scope: write.
A new editable code with a draft page: a title, an optional subtitle and text, and up to 20 links. Publish it with POST /pages/{id}/publish.
curl -X POST 'https://oqrely.com/v1/pages' \
-H "Authorization: Bearer $OQRELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"title":"Spring menu","text":"Fresh every morning.","links":[{"label":"Book a table","url":"https://example.com/book"}]}'PATCH /v1/pages/{id}
Save the draft blocks of a page. Scope: write.
Adding, changing, moving and removing blocks: send the whole block list. Scanners keep the published version until you publish.
curl -X PATCH 'https://oqrely.com/v1/pages/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01' \
-H "Authorization: Bearer $OQRELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"blocks":[{"id":"hd","type":"header","title":"Spring menu"}]}'POST /v1/pages/{id}/publish
Publish the draft of a page. Scope: write.
Live on the next scan. Refused with 409 and the issues while the draft has publishing errors.
curl -X POST 'https://oqrely.com/v1/pages/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01/publish' \
-H "Authorization: Bearer $OQRELY_API_KEY"POST /v1/pages/{id}/unpublish
Take a page offline. Scope: write.
curl -X POST 'https://oqrely.com/v1/pages/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01/unpublish' \
-H "Authorization: Bearer $OQRELY_API_KEY"POST /v1/pages/{id}/revert
Bring back an earlier published version. Scope: write.
The chosen version becomes the live page again.
curl -X POST 'https://oqrely.com/v1/pages/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01/revert' \
-H "Authorization: Bearer $OQRELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"version":1}'PATCH /v1/pages/{id}/settings
Change the settings and theme of a page. Scope: write.
Fields you leave out keep their value. `brand_kit_id` applies a brand kit: its colour, typeface and logo.
curl -X PATCH 'https://oqrely.com/v1/pages/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01/settings' \
-H "Authorization: Bearer $OQRELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"settings":{"title":"Spring menu"},"theme":{"accent":"#0B6E4F"} }'POST /v1/pages/{id}/languages
Add a language version of a page. Scope: write.
Scanners see the version in their browser language. It starts as a copy of the main language.
curl -X POST 'https://oqrely.com/v1/pages/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01/languages' \
-H "Authorization: Bearer $OQRELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"lang":"de"}'PATCH /v1/pages/{id}/languages/{lang}
Set the title and description of a language version. Scope: write.
curl -X PATCH 'https://oqrely.com/v1/pages/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01/languages/de' \
-H "Authorization: Bearer $OQRELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"title":"Speisekarte","description":"Frisch jeden Morgen."}'DELETE /v1/pages/{id}/languages/{lang}
Remove a language version of a page. Scope: write.
curl -X DELETE 'https://oqrely.com/v1/pages/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01/languages/de' \
-H "Authorization: Bearer $OQRELY_API_KEY"submissions1 endpoint
GET /v1/pages/{id}/submissions
List the form responses of a page. Scope: read.
Newest first. Needs the analytics permission in the workspace, like the responses inbox.
curl 'https://oqrely.com/v1/pages/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01/submissions?limit=50' \
-H "Authorization: Bearer $OQRELY_API_KEY"files4 endpoints
GET /v1/files
List uploaded files. Scope: read.
PDFs and images of the workspace, newest first. Upload new files in the app.
curl 'https://oqrely.com/v1/files?kind=pdf' \
-H "Authorization: Bearer $OQRELY_API_KEY"DELETE /v1/files/{id}
Delete a file. Scope: write.
It leaves the file list and the storage meter; pages that still show it keep working until it is purged.
curl -X DELETE 'https://oqrely.com/v1/files/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01' \
-H "Authorization: Bearer $OQRELY_API_KEY"POST /v1/files
Upload a PDF or an image. Scope: write.
The file as base64 in JSON. It is checked for malware and type before it can be used (status processing, then ready). Uploading the same bytes again returns the existing file with status 200.
curl -X POST 'https://oqrely.com/v1/files' \
-H "Authorization: Bearer $OQRELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"menu.png","content_base64":"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGNgYGD4DwABBAEAwS2OUAAAAABJRU5ErkJggg=="}'GET /v1/files/{id}/link
Get a short-lived link to a file. Scope: read.
A signed link to view (or with `download=true`, download) a ready file.
curl 'https://oqrely.com/v1/files/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01/link' \
-H "Authorization: Bearer $OQRELY_API_KEY"members7 endpoints
GET /v1/members
List the members of the workspace. Scope: read.
curl 'https://oqrely.com/v1/members?limit=50' \
-H "Authorization: Bearer $OQRELY_API_KEY"GET /v1/members/invites
List open invitations. Scope: read.
curl 'https://oqrely.com/v1/members/invites' \
-H "Authorization: Bearer $OQRELY_API_KEY"POST /v1/members/invites
Invite someone to the workspace. Scope: write.
Returns the invitation link once. It works for that e-mail address only, once, for 7 days.
curl -X POST 'https://oqrely.com/v1/members/invites' \
-H "Authorization: Bearer $OQRELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","role":"editor"}'DELETE /v1/members/invites/{id}
Withdraw an invitation. Scope: write.
curl -X DELETE 'https://oqrely.com/v1/members/invites/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01' \
-H "Authorization: Bearer $OQRELY_API_KEY"PATCH /v1/members/{user_id}
Change the role of a member. Scope: write.
curl -X PATCH 'https://oqrely.com/v1/members/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01' \
-H "Authorization: Bearer $OQRELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"role":"viewer"}'DELETE /v1/members/{user_id}
Remove a member. Scope: write.
Their codes move to you; their API keys and assistant connections in this workspace stop.
curl -X DELETE 'https://oqrely.com/v1/members/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01' \
-H "Authorization: Bearer $OQRELY_API_KEY"PATCH /v1/members/{user_id}/folders
Limit a member to some folders. Scope: write.
An empty list gives the member the whole workspace again.
curl -X PATCH 'https://oqrely.com/v1/members/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01/folders' \
-H "Authorization: Bearer $OQRELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"folder_ids":[]}'bulk9 endpoints
GET /v1/bulk
List bulk jobs. Scope: read.
curl 'https://oqrely.com/v1/bulk?limit=20' \
-H "Authorization: Bearer $OQRELY_API_KEY"POST /v1/bulk
Create many codes in one job. Scope: write.
Rows of url, name and tags. A request larger than the plan's bulk rows is refused before anything is created.
curl -X POST 'https://oqrely.com/v1/bulk' \
-H "Authorization: Bearer $OQRELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"mode":"editable","rows":[{"url":"https://example.com/a","name":"Table 1"}]}'GET /v1/bulk/{id}
Get a bulk job with its counts. Scope: read.
curl 'https://oqrely.com/v1/bulk/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01' \
-H "Authorization: Bearer $OQRELY_API_KEY"POST /v1/bulk/{id}/cancel
Stop a running bulk job. Scope: write.
Rows already done stay done.
curl -X POST 'https://oqrely.com/v1/bulk/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01/cancel' \
-H "Authorization: Bearer $OQRELY_API_KEY"POST /v1/bulk/preview
Check rows before a bulk job. Scope: write.
Validates every row against the plan's bulk rows and the code rules, without creating anything. Then POST /bulk (or with `dry_run: true`, a dry run you apply later).
curl -X POST 'https://oqrely.com/v1/bulk/preview' \
-H "Authorization: Bearer $OQRELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"mode":"editable","rows":[{"url":"https://example.com/a","name":"Table 1"}]}'POST /v1/bulk/{id}/apply
Apply a dry run. Scope: write.
Runs the planned rows of a finished dry run for real.
curl -X POST 'https://oqrely.com/v1/bulk/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01/apply' \
-H "Authorization: Bearer $OQRELY_API_KEY"POST /v1/bulk/{id}/undo
Undo a bulk update. Scope: write.
Puts back what an update job changed, within 24 hours of the run.
curl -X POST 'https://oqrely.com/v1/bulk/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01/undo' \
-H "Authorization: Bearer $OQRELY_API_KEY"GET /v1/bulk/{id}/report
The rows of a bulk job with their result. Scope: read.
curl 'https://oqrely.com/v1/bulk/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01/report?limit=100' \
-H "Authorization: Bearer $OQRELY_API_KEY"GET /v1/bulk/{id}/files/{name}
Download the ZIP of codes or the print sheet of a job. Scope: read.
`name` is codes.zip or sheet.pdf, as the job made them.
curl 'https://oqrely.com/v1/bulk/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01/files/codes.zip' \
-H "Authorization: Bearer $OQRELY_API_KEY" \
-o code.svgbrand_kits5 endpoints
GET /v1/brand-kits
List brand kits. Scope: read.
Colors, logos and style the workspace applies to new codes. Pass a kit's design to codes.create as `design`.
curl 'https://oqrely.com/v1/brand-kits' \
-H "Authorization: Bearer $OQRELY_API_KEY"POST /v1/brand-kits
Create a brand kit. Scope: write.
curl -X POST 'https://oqrely.com/v1/brand-kits' \
-H "Authorization: Bearer $OQRELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Main brand","colors":{"brand":"#0B6E4F","background":"#FFFFFF","accent":"#F2A541","light":"#E8F5EE"},"frameId":null,"domainId":null}'PATCH /v1/brand-kits/{id}
Change a brand kit. Scope: write.
Send the whole kit, as for create.
curl -X PATCH 'https://oqrely.com/v1/brand-kits/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01' \
-H "Authorization: Bearer $OQRELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Main brand","colors":{"brand":"#0B6E4F","background":"#FFFFFF","accent":"#F2A541","light":"#E8F5EE"},"frameId":null,"domainId":null}'DELETE /v1/brand-kits/{id}
Delete a brand kit. Scope: write.
Codes keep their design.
curl -X DELETE 'https://oqrely.com/v1/brand-kits/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01' \
-H "Authorization: Bearer $OQRELY_API_KEY"POST /v1/brand-kits/{id}/default
Make a brand kit the default for new codes. Scope: write.
curl -X POST 'https://oqrely.com/v1/brand-kits/5ca1ab1e-c0de-4a11-8e5e-0d0c5a3b1e01/default' \
-H "Authorization: Bearer $OQRELY_API_KEY"Versioning and changelog
The version is part of the path (/v1). New optional fields, parameters and event types are added without a new version, so clients should ignore fields they do not know.
Deprecation policy: A breaking change ships as a new version, with at least 12 months of notice before the old version stops. The notice goes to key owners by e-mail and into this changelog.
Changelog
- v1.1: custom domains, file uploads, page settings and languages, bulk preview, dry runs and undo, print records, scan exports and the real-time view, client workspaces, branding and GA4.
- v1.0: codes, analytics, render, folders, tags, domains, workspaces, templates and webhooks.