API Reference

Create and manage short links, their campaign children, their QR codes, and their click stats. Everything you can do in the dashboard, you can do here. Successful responses wrap the result in data; errors return { "error": { "code", "message" } }, where code is stable and message is a sentence for a person.

The full spec is OpenAPI 3.1, at https://1qrl.app/api/v1/openapi.json.

Authentication

Create an API key under Settings, then send it as a bearer token. A key is shown once, when it's made, so store it then.

curl https://1qrl.app/api/v1/links \
  -H "Authorization: Bearer sk_live_your_key_here"

Responses and errors

Success wraps the result in data. An error carries a stable code to branch on, and a message written for a person.

{ "error": { "code": "not_found", "message": "Link not found." } }
400invalid_requestSomething in the request is missing or malformed. The message names it.
401unauthorizedNo valid API key or session was sent, or the key was revoked.
403forbiddenThe route needs a signed-in session; the key routes refuse API keys.
404not_foundNo such link or key in your account, or it was deleted; or no logo is saved under that key.
409conflictThe custom slug is taken, or the account already holds the most active keys.
429rate_limitedA daily limit was reached. Limits reset at midnight UTC.
500server_errorSomething failed on our side. Try again later.

Rate limits

Limits apply per account, not per key, and reset at midnight UTC.

  • 1,000 API requests a day, shared by all of the account's keys.
  • 100 new links a day, campaign links included. Links made in the dashboard and on the homepage count toward the same total.
  • 10 active keys at a time.

Every response to a key-authenticated request carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset for the request limit. Going over either daily limit returns 429.

Paging

Lists come newest first, 50 to a page unless you pass limit, up to 100. While there is more, meta.nextCursor is a string: pass it back as cursor for the next page. On the last page it is null.

Endpoints

Links

Short links and the QR style each one carries.

Create a link

POST/api/v1/links

Shortens longUrl. Without a slug, 1QRL picks a short random one. Each new link counts toward the account's 100 a day.

Body
longUrlrequiredstringThe destination, with its scheme.
slugstringA custom slug of 6 to 32 lowercase letters, digits, and hyphens, not starting with a hyphen or holding two in a row. Some words are reserved. A taken slug, including one retired by a delete, is a 409.
titlestring
domainstringThe redirect host. Defaults to 1qrl.cc. One of 1qrl.cc.
qrConfigQRConfig
Request
curl -X POST https://1qrl.app/api/v1/links \
  -H "Authorization: Bearer sk_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"longUrl":"https://example.com/menus/spring","slug":"spring-menu","title":"Spring menu"}'
Response
{
  "data": {
    "id": 4821,
    "slug": "spring-menu",
    "domain": "1qrl.cc",
    "shortUrl": "https://1qrl.cc/spring-menu",
    "longUrl": "https://example.com/menus/spring",
    "title": "Spring menu",
    "qrConfig": {
      "fg": "#1B1A20",
      "bg": "#FFFFFF",
      "dotStyle": "rounded",
      "finderStyle": "rounded",
      "logo": null
    },
    "parentId": null,
    "utm": {
      "source": null,
      "medium": null,
      "campaign": null
    },
    "createdAt": "2026-10-05T14:02:11.000Z"
  }
}
Statuses
  • 201The new link.
  • 400Something in the request is missing or malformed. The message names it.
  • 401No valid API key or session.
  • 409The custom slug is taken.
  • 429A daily limit was reached: API requests, or new links (campaign links included), each counted per account and reset at midnight UTC.
  • 500Something failed on our side. Try again later.

Campaign links

Children of a link that share its destination and add UTM parameters, so each channel's clicks are counted apart.

Create a campaign link

POST/api/v1/links/{id}/children

A new link with its own slug that redirects to the parent's destination with these UTM parameters added. Give at least one. A campaign link can't have children of its own, and it counts toward the daily limit on new links.

Parameters
idrequiredintegerThe link's id. A campaign link has its own.
Body
utmrequiredobject
utm.sourcestring
utm.mediumstring
utm.campaignstring
Request
curl -X POST https://1qrl.app/api/v1/links/4821/children \
  -H "Authorization: Bearer sk_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"utm":{"source":"poster","medium":"print","campaign":"spring"}}'
Response
{
  "data": {
    "id": 4822,
    "slug": "k7m3pq",
    "domain": "1qrl.cc",
    "shortUrl": "https://1qrl.cc/k7m3pq",
    "longUrl": "https://example.com/menus/spring",
    "title": null,
    "qrConfig": {
      "fg": "#1B1A20",
      "bg": "#FFFFFF",
      "dotStyle": "rounded",
      "finderStyle": "rounded",
      "logo": null
    },
    "parentId": 4821,
    "utm": {
      "source": "poster",
      "medium": "print",
      "campaign": "spring"
    },
    "createdAt": "2026-10-05T14:05:40.000Z"
  }
}
Statuses
  • 201The new campaign link.
  • 400Something in the request is missing or malformed. The message names it.
  • 401No valid API key or session.
  • 404No such link in your account, or it was deleted.
  • 429A daily limit was reached: API requests, or new links (campaign links included), each counted per account and reset at midnight UTC.
  • 500Something failed on our side. Try again later.

QR codes

A link's QR code as an image, in its saved style, and the logos styles carry.

Get a logo

GET/api/v1/logos/{key}

No authentication.

A saved logo, the image a qrConfig.logo URL points at. It needs no API key or session, so a page can draw it straight from the URL. The key is a hash of the image, so the image at a URL never changes and a browser can keep it for good. Use the URL exactly as given: a query string or another spelling of it is a 404. Anyone with the URL can fetch it, and it can't be guessed. Once no link uses a logo it is deleted, and its URL stops answering within 24 hours.

Parameters
keyrequiredstringThe last part of a qrConfig.logo URL.
Request
curl https://1qrl.app/api/v1/logos/Ab3_-xYz0123456789ABCD \
  -o logo
Statuses
  • 200The logo, as the type its bytes are.
  • 404No logo is saved under that key.
  • 500Something failed on our side. Try again later.

Stats

Clicks on one link.

Profile

Your account's display name and avatar. The email address is shown but can't be changed.

Get your profile

GET/api/v1/profile
Request
curl https://1qrl.app/api/v1/profile \
  -H "Authorization: Bearer sk_live_your_key_here"
Statuses
  • 200Your profile.
  • 401No valid API key or session.
  • 429A daily limit was reached: API requests, or new links (campaign links included), each counted per account and reset at midnight UTC.
  • 500Something failed on our side. Try again later.

Update your profile

PATCH/api/v1/profile

Send either field or both; a field you leave out keeps its value. null clears it, and so does a blank displayName. Sending email is refused with 400 invalid_request: an account's address is permanent.

Body
displayNamestring or nullAt most 64 characters.
avatarstring or nullOne of finder-indigo, module-coral, ring-teal, pair-amber, grid-blue, cross-rose, corner-green, bars-violet, diamond-slate, tail-ink, initials-indigo, initials-coral, initials-teal, initials-amber, initials-blue, initials-rose, initials-green, initials-violet, initials-slate, initials-ink, null.
Request
curl -X PATCH https://1qrl.app/api/v1/profile \
  -H "Authorization: Bearer sk_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"displayName":"Ada Lovelace","avatar":"finder-indigo"}'
Statuses
  • 200Your profile, as saved.
  • 400Something in the request is missing or malformed. The message names it.
  • 401No valid API key or session.
  • 429A daily limit was reached: API requests, or new links (campaign links included), each counted per account and reset at midnight UTC.
  • 500Something failed on our side. Try again later.

API keys

Manage the keys themselves. These routes need a signed-in session and refuse API keys, so a leaked key can't mint more.

List your API keys

GET/api/v1/keys

Signed-in session only: API keys get a 403.

Every key the account has made, revoked ones included. Secrets are never returned.

Statuses
  • 200Your keys.
  • 401No valid API key or session.
  • 403An API key was sent to a route that needs a signed-in session.

Create an API key

POST/api/v1/keys

Signed-in session only: API keys get a 403.

The secret is in data.key in this response and never again; store it now. An account holds up to 10 active keys, and revoking one frees its place.

Body
namerequiredstring1 to 64 characters.
Statuses
  • 201The new key, with its secret.
  • 400Something in the request is missing or malformed. The message names it.
  • 401No valid API key or session.
  • 403An API key was sent to a route that needs a signed-in session.
  • 409The account already holds the most active keys.
  • 500Something failed on our side. Try again later.

Revoke an API key

DELETE/api/v1/keys/{id}

Signed-in session only: API keys get a 403.

The key stops working at once. Revoking can't be undone.

Parameters
idrequiredstringThe key's id.
Statuses
  • 200The key is revoked.
  • 401No valid API key or session.
  • 403An API key was sent to a route that needs a signed-in session.
  • 404No such link in your account, or it was deleted.

Spec

This document, as JSON.

Get this spec

GET/api/v1/openapi.json

No authentication.

This document as OpenAPI 3.1 JSON, for client generators and tools.

Request
curl https://1qrl.app/api/v1/openapi.json
Statuses
  • 200The spec.

Objects

QRConfig

A link's QR style. On create, fields you leave out take the defaults; on update, they keep their current value. Anything else is refused with 400 invalid_request, and the message names the field.

fgstringModule color, as hex.
bgstringBackground color, as hex.
dotStylestringThe shape of each module. One of square, rounded, dots.
finderStylestringThe shape of the three corner squares. One of square, rounded, circle.
logostring or nullThe image in the code's center, or null for none. A response gives a saved logo's URL, https://1qrl.app/api/v1/logos/…, never its bytes: fetch it to get the image. To set one, send a PNG, JPEG, WebP, or GIF as a base64 data URL, at most 48 KB of image; to keep one, send its URL back as you got it. Other URLs and SVG are refused. A logo raises the code's error correction, so it stays scannable with its center covered.

Stats

Clicks on this link alone. A top-level link's numbers don't include its campaign links; ask for each of those by its own id.

totalintegerEvery click, ever.
dailyarray of objectThe last 14 days in UTC, oldest first, with a zero for a day without clicks.
byCountryarray of objectUp to 5 countries, as whole-number percentages of the clicks whose country is known.
byDevicearray of objectUp to 3 device classes, as percentages of the clicks whose device is known.
byReferrerarray of objectUp to 5 referring hosts, as percentages of the clicks that sent one.

ApiKey

idstring
namestring
keyPrefixstringThe key's first characters, to tell keys apart. Not a secret.
lastUsedstring or null
createdAtstring
revokedAtstring or null