@open-qr/sdk
Dependency-free TypeScript client for the OpenQR REST API. Generate static QR codes and manage dynamic (editable) codes with scan analytics. Works in Node ≥18, edge runtimes and the browser — it uses the global fetch and ships no dependencies.
Install
pnpm add @open-qr/sdk
Quickstart
Create a free API key at https://openqr.uk/api, then:
import { OpenQR } from "@open-qr/sdk";
const qr = new OpenQR({ apiKey: "oqr_…" });
const svg = await qr.generate({ data: "https://openqr.uk" });
The key is sent as Authorization: Bearer oqr_… on every request — header only, never a query string.
Options
new OpenQR({
apiKey: "oqr_…",
baseUrl: "https://openqr.uk",
fetch: customFetch,
});
Generate QR codes
const svg = await qr.generate({ data: "https://openqr.uk", size: 512, margin: 4 });
const png = await qr.generate({ data: "https://openqr.uk", format: "png", dark: "232E3A", light: "FFFFFF" });
await qr.generate({ data: "https://openqr.uk", theme: "Brand" });
const url = qr.qrUrl({ data: "https://openqr.uk", format: "png" });
Dynamic (editable) codes
A dynamic code is a re-pointable short link on oqr.to — encode the returned short_url in a QR and change where it points later.
const code = await qr.createDynamicCode({ destination: "https://example.com/menu", label: "Spring menu" });
console.log(code.short_url);
console.log(code.rateLimit);
const codes = await qr.listDynamicCodes({ limit: 50 });
const created = await qr.bulkCreateDynamicCodes({
codes: [
{ destination: "https://example.com/menu", label: "Spring menu" },
{ destination: "https://example.com/wifi", label: "Guest wifi" },
],
theme: "Brand",
});
await qr.updateDynamicCode(code.id, { destination: "https://example.com/summer-menu", slug: "summer-menu" });
await qr.deleteDynamicCode(code.id);
Scan analytics
const { scans, analytics } = await qr.getScans(code.id, { days: 30 });
console.log(scans.total, scans.topCountry);
console.log(analytics.by_country, analytics.daily);
Folders
const folders = await qr.listFolders();
const folder = await qr.createFolder("Campaigns");
await qr.renameFolder(folder.id, "Q3 Campaigns");
await qr.updateFolder(folder.id, { name: "Q3 Campaigns" });
await qr.deleteFolder(folder.id);
Themes
A theme is a reusable QR style you can apply by id or name on generation and dynamic-code endpoints.
const themes = await qr.listThemes();
const theme = await qr.createTheme({
name: "Brand",
style: { fgColor: "#232E3A", bgColor: "#FFFFFF", dotType: "rounded", cornerSquareType: "extra-rounded", margin: 4 },
});
await qr.deleteTheme(theme.id);
Errors
Every non-2xx response throws an OpenQRError carrying the HTTP status, a stable code, the server's message, and (where present) rate-limit metadata.
import { OpenQR, OpenQRError } from "@open-qr/sdk";
try {
await qr.createDynamicCode({ destination: "http://localhost" });
} catch (err) {
if (err instanceof OpenQRError) {
console.error(err.status);
console.error(err.code);
console.error(err.message);
console.error(err.retryAfter);
console.error(err.rateLimit);
}
}
| 400 | bad_request | Invalid body, missing field, or a disallowed destination |
| 401 | unauthorized | Missing/invalid/revoked API key |
| 404 | not_found | Code, folder or theme not found or not owned by your key |
| 409 | conflict | Requested custom slug already taken |
| 429 | rate_limited | Dynamic-code creation limit hit (20/hour) — see retryAfter |
| 500 | server_error | Unexpected server-side failure |
License
MIT. See LICENSE.
Note: this package is not yet published to npm — publishing (npm publish, requires an npm token) is the remaining step.