Qravio

Developers

API Reference

Create, manage, and analyze QR codes programmatically with the Qravio REST API.

On this page

Introduction

The Qravio REST API lets you create, manage, and analyze QR codes programmatically. It’s available on the Pro and Agency plans. All requests are made over HTTPS and authenticated with an API key. Responses are JSON.

Base URL
https://api.qravio.app/api/public/v1

Authentication

Authenticate by sending your API key as a bearer token in the Authorization header (or the X-API-Key header). Create and manage keys in your dashboard under Developers. Keep keys secret — they carry full access to your workspace’s QR codes.

Authenticated request
curl https://api.qravio.app/api/public/v1/qrcodes \
  -H "Authorization: Bearer qr_live_your_api_key"

# Alternatively, use the X-API-Key header:
curl https://api.qravio.app/api/public/v1/qrcodes \
  -H "X-API-Key: qr_live_your_api_key"

Pagination

List endpoints return a paginated envelope with an items array plus total, page, page_size, and total_pages. Control the window with the query parameters below.

pageinteger

1-based page number. Default 1.

page_sizeinteger

Items per page. Default 20, max 100.

Paginated response
{
  "items": [ /* … QR Code objects … */ ],
  "total": 128,
  "page": 2,
  "page_size": 20,
  "total_pages": 7
}

Rate limits

Each workspace has a monthly call quota based on its plan. Every response includes rate-limit headers. Exceeding the quota returns 429 with a Retry-After header.

  • Free / StarterNo API access
  • Pro3,000 calls / month
  • Agency25,000 calls / month
Response headers
X-RateLimit-Limit: 25000
X-RateLimit-Remaining: 24871
X-RateLimit-Reset: 1781913600
X-RateLimit-Limitinteger

Your monthly call allowance.

X-RateLimit-Remaininginteger

Calls remaining in the current period.

X-RateLimit-Resetinteger

Unix epoch (seconds) when the quota resets.

Retry-Afterinteger

On 429 only — seconds until the quota resets.

Errors

Qravio uses standard HTTP status codes. Errors return a JSON body with a single detail message.

401Unauthorized

Missing, invalid, revoked, or expired API key.

402Payment Required

Your plan’s QR-code limit (max_qr) is reached.

403Forbidden

Workspace lacks api_access, or the QR type / feature isn’t in your plan.

404Not Found

The resource doesn’t exist in your workspace.

422Unprocessable Entity

Request payload failed validation.

429Too Many Requests

Monthly call quota exceeded. See Retry-After.

503Service Unavailable

Quota/usage lookup failed — retry shortly.

Error response
{
  "detail": "API access requires a Pro or higher plan."
}

The QR Code object

idstring (uuid)

Unique identifier for the QR code.

workspace_idstring (uuid)

The workspace that owns this QR code.

namestring

Display name.

typestring

QR type (e.g. website, vcard, business). See QR types.

categorystring

"dynamic" (editable destination) or "static".

short_codestring | null

6-char short code used in the scan URL.

preview_urlstring | null

Public URL that renders a scannable SVG preview of this QR code. No API key required — safe to use directly in an <img> tag.

statusstring | null

"active" or "inactive".

folder_idstring | null

Parent folder, if organized into one.

custom_domain_idstring | null

Verified custom domain used for the short link.

ab_enabledboolean

True when an A/B test is active (>1 active website destination). Server-computed.

is_password_protectedboolean

True when a visitor password gate is active.

destinationsarray<Destination>

Routing destinations (see below).

designobject | null

Visual design settings (dots, colors, logo, frame, output).

contentobject | null

Type-specific content payload. See QR types.

total_scansinteger

All-time scan count.

created_bystring | null

User who created the QR code.

created_atstring (ISO 8601) | null

Creation timestamp.

updated_atstring (ISO 8601) | null

Last update timestamp.

Destination object

idstring (uuid)

Destination identifier.

target_urlstring

The URL this destination routes to.

weightinteger | null

A/B routing weight 0–100 (default 100).

is_activeboolean | null

Whether this destination is active.

labelstring | null

Human-readable variant name.

variant_keystring | null

Stable variant id (a, b, c…) used for sticky routing & analytics.

rulesobject | null

Conditional routing rules (geo, device, time).

json
{
  "id": "9f8b2c1a-1234-4d5e-8f90-abc123def456",
  "workspace_id": "11111111-1111-1111-1111-111111111111",
  "name": "Spring campaign",
  "type": "website",
  "category": "dynamic",
  "short_code": "Ab3X9z",
  "preview_url": "https://api.qravio.app/api/public/v1/preview/Ab3X9z.svg",
  "status": "active",
  "ab_enabled": false,
  "is_password_protected": false,
  "destinations": [
    {
      "id": "22222222-2222-2222-2222-222222222222",
      "target_url": "https://example.com/spring",
      "weight": 100,
      "is_active": true,
      "label": "Landing",
      "variant_key": "a"
    }
  ],
  "content": { "url": "https://example.com/spring" },
  "total_scans": 1240,
  "created_at": "2026-06-22T10:15:00Z",
  "updated_at": "2026-06-22T10:15:00Z"
}

The Analytics object

total_scansinteger

All-time total scans.

unique_scansinteger

All-time unique (session-deduplicated) visitors.

scans_todayinteger

Scans so far in the current UTC day.

wow_changenumber

Week-over-week change as a percentage.

daily_scansarray<{ date, scans }>

Per-day series, clamped to your plan’s retention window.

scans_by_deviceobject

{ mobile, tablet, desktop, bot } counts.

scans_by_countryobject

Country code → scan count.

last_scanned_atstring (ISO 8601) | null

Timestamp of the most recent scan.

json
{
  "total_scans": 1240,
  "unique_scans": 980,
  "scans_today": 42,
  "wow_change": 12.5,
  "daily_scans": [
    { "date": "2026-06-21", "scans": 58 },
    { "date": "2026-06-22", "scans": 42 }
  ],
  "scans_by_device": { "mobile": 900, "tablet": 60, "desktop": 270, "bot": 10 },
  "scans_by_country": { "US": 720, "IN": 310, "GB": 95 },
  "last_scanned_at": "2026-06-22T11:02:00Z"
}

QR codes

Create, list, update, and delete QR codes, and read their scan analytics.

GET/qrcodes

List QR codes

Returns a paginated list of QR codes in the API key’s workspace, newest first. Supports filtering, search, sorting, and pagination.

Query parameters

folder_idstring (uuid)

Only QR codes in this folder.

categorystring

"dynamic" or "static".

statusstring

Filter by status (active, inactive).

typestring

Filter by QR type (website, vcard, …).

qstring

Case-insensitive match on the QR name.

sortstring

"created_at" (default) or "name".

orderstring

"desc" (default) or "asc".

pageinteger

1-based page number. Default 1.

page_sizeinteger

Items per page. Default 20, max 100.

Request
curl https://api.qravio.app/api/public/v1/qrcodes \
  -H "Authorization: Bearer qr_live_your_api_key"
200 OKA paginated envelope of QR Code objects.
Response
{
  "items": [
    { "id": "9f8b2c1a-…", "name": "Spring campaign", "type": "website", "total_scans": 1240, "…": "…" }
  ],
  "total": 128,
  "page": 1,
  "page_size": 20
}
POST/qrcodes

Create a QR code

Creates a QR code in your workspace. For dynamic QRs, include at least one destination. The content object shape depends on type — see QR types. Plan limits (max_qr) and allowed dynamic_qr_types are enforced.

Body parameters

namestringRequired

Display name.

typestringRequired

QR type (e.g. website, vcard, business).

categorystringRequired

"dynamic" or "static".

contentobjectRequired

Type-specific payload. See QR types.

destinationsarray<Destination>

Required for dynamic QRs — where the code routes.

statusstring

Defaults to "active".

folder_idstring (uuid)

Organize into a folder.

custom_domain_idstring (uuid)

Use a verified custom domain.

designobject

Visual design (dots, colors, logo, frame, output).

page_designobject

Landing-page theme for page-type QRs.

Request
curl -X POST https://api.qravio.app/api/public/v1/qrcodes \
  -H "Authorization: Bearer qr_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Spring campaign",
    "type": "website",
    "category": "dynamic",
    "content": { "url": "https://example.com/spring" },
    "destinations": [{ "target_url": "https://example.com/spring" }]
  }'
201 CreatedThe created QR Code object.
Response
{ "id": "9f8b2c1a-…", "short_code": "Ab3X9z", "type": "website", "…": "…" }
402 Payment RequiredQR-code limit reached for your plan.
403 ForbiddenThe QR type isn’t available on your plan.
POST/qrcodes/bulk

Bulk-create QR codes

Creates up to 50 QR codes in a single request. Each item follows the same shape as Create a QR code. Requires the bulk-generation entitlement (Pro / Agency). Inserts run sequentially and are NOT rolled back if one item fails partway through the batch.

Body parameters

itemsarray<CreateQRCode>Required

1–50 QR codes to create. Each item is a full create payload.

Request
curl -X POST https://api.qravio.app/api/public/v1/qrcodes/bulk \
  -H "Authorization: Bearer qr_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      { "name": "Table 1", "type": "website", "category": "dynamic",
        "content": { "url": "https://example.com/t1" },
        "destinations": [{ "target_url": "https://example.com/t1" }] },
      { "name": "Table 2", "type": "website", "category": "dynamic",
        "content": { "url": "https://example.com/t2" },
        "destinations": [{ "target_url": "https://example.com/t2" }] }
    ]
  }'
201 CreatedAn array of the created QR Code objects.
Response
[ { "id": "…", "short_code": "…", "…": "…" }, { "id": "…", "…": "…" } ]
402 Payment RequiredA create would exceed your plan’s max_qr.
403 ForbiddenBulk generation isn’t available on your plan.
GET/qrcodes/counts

Get QR counts

Returns aggregate counts of QR codes in your workspace — the total, plus a breakdown per folder and per category. A cheap alternative to paging the whole list to build folder/category chips.

Request
curl https://api.qravio.app/api/public/v1/qrcodes/counts \
  -H "Authorization: Bearer qr_live_your_api_key"
200 OKThe counts object.
Response
{
  "total": 128,
  "by_folder": { "0b1c…": 12, "7d2e…": 3 },
  "by_category": { "dynamic": 110, "static": 18 }
}
GET/qrcodes/{id}

Retrieve a QR code

Fetches a single QR code by id.

Path parameters

idstring (uuid)Required

The QR code id. Must belong to your API key’s workspace.

Request
curl https://api.qravio.app/api/public/v1/qrcodes/9f8b2c1a-1234-4d5e-8f90-abc123def456 \
  -H "Authorization: Bearer qr_live_your_api_key"
200 OKThe QR Code object.
404 Not FoundNo such QR code in your workspace.
PATCH/qrcodes/{id}

Update a QR code

Partially updates a QR code. Send only the fields you want to change. Passing destinations replaces all existing destinations.

Path parameters

idstring (uuid)Required

The QR code id. Must belong to your API key’s workspace.

Body parameters

namestring

New display name.

statusstring

"active" or "inactive".

contentobject

Updated type-specific content.

destinationsarray<Destination>

Replaces all destinations.

designobject

Updated design.

folder_idstring | null

Move to a folder, or null to remove.

passwordstring

Enable a visitor password gate.

remove_passwordboolean

Set true to disable the password gate.

Request
curl -X PATCH https://api.qravio.app/api/public/v1/qrcodes/9f8b2c1a-1234-4d5e-8f90-abc123def456 \
  -H "Authorization: Bearer qr_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Summer campaign", "status": "active" }'
200 OKThe updated QR Code object.
DELETE/qrcodes/{id}

Delete a QR code

Permanently deletes a QR code. Printed codes using this short link will stop working.

Path parameters

idstring (uuid)Required

The QR code id. Must belong to your API key’s workspace.

Request
curl -X DELETE https://api.qravio.app/api/public/v1/qrcodes/9f8b2c1a-1234-4d5e-8f90-abc123def456 \
  -H "Authorization: Bearer qr_live_your_api_key"
200 OKDeleted.
Response
{ "message": "QR code deleted successfully." }
404 Not FoundNo such QR code in your workspace.
GET/qrcodes/{id}/analytics

Get QR analytics

Returns scan analytics for a QR code. The daily series is clamped to your plan’s retention window.

Path parameters

idstring (uuid)Required

The QR code id. Must belong to your API key’s workspace.

Request
curl https://api.qravio.app/api/public/v1/qrcodes/9f8b2c1a-1234-4d5e-8f90-abc123def456/analytics \
  -H "Authorization: Bearer qr_live_your_api_key"
200 OKThe Analytics object.

Folders

Organize QR codes into nested folders.

POST/folders

Create a folder

Creates a folder in your workspace to organize QR codes. Folders are available on Starter and higher plans.

Body parameters

namestringRequired

Folder name.

parent_idstring (uuid) | null

Nest under a parent folder, or omit for a top-level folder.

Request
curl -X POST https://api.qravio.app/api/public/v1/folders \
  -H "Authorization: Bearer qr_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Spring 2026" }'
201 CreatedThe created Folder object.
Response
{
  "id": "0b1c2d3e-4f56-4789-abcd-ef0123456789",
  "workspace_id": "11111111-1111-1111-1111-111111111111",
  "name": "Spring 2026",
  "parent_id": null,
  "created_at": "2026-06-22T10:15:00Z",
  "updated_at": "2026-06-22T10:15:00Z"
}
403 ForbiddenFolders aren’t available on your plan.
GET/folders

List folders

Returns folders in your workspace. Filter by parent, or list only top-level folders.

Query parameters

parent_idstring (uuid)

List only children of this folder.

rootboolean

When true, list only top-level (parent-less) folders.

Request
curl https://api.qravio.app/api/public/v1/folders \
  -H "Authorization: Bearer qr_live_your_api_key"
200 OKAn array of Folder objects.
Response
[ {
  "id": "0b1c2d3e-4f56-4789-abcd-ef0123456789",
  "workspace_id": "11111111-1111-1111-1111-111111111111",
  "name": "Spring 2026",
  "parent_id": null,
  "created_at": "2026-06-22T10:15:00Z",
  "updated_at": "2026-06-22T10:15:00Z"
} ]
GET/folders/tree

Get folder tree

Returns the full recursive folder tree for your workspace. Each node includes a depth field.

Request
curl https://api.qravio.app/api/public/v1/folders/tree \
  -H "Authorization: Bearer qr_live_your_api_key"
200 OKAn array of Folder tree nodes (Folder object + depth).
Response
[ { "id": "0b1c…", "name": "Spring 2026", "parent_id": null, "depth": 0, "…": "…" } ]
GET/folders/{id}

Retrieve a folder

Fetches a single folder by id.

Path parameters

idstring (uuid)Required

The folder id. Must belong to your API key’s workspace.

Request
curl https://api.qravio.app/api/public/v1/folders/0b1c2d3e-4f56-4789-abcd-ef0123456789 \
  -H "Authorization: Bearer qr_live_your_api_key"
200 OKThe Folder object.
Response
{
  "id": "0b1c2d3e-4f56-4789-abcd-ef0123456789",
  "workspace_id": "11111111-1111-1111-1111-111111111111",
  "name": "Spring 2026",
  "parent_id": null,
  "created_at": "2026-06-22T10:15:00Z",
  "updated_at": "2026-06-22T10:15:00Z"
}
404 Not FoundNo such folder in your workspace.
PATCH/folders/{id}

Update a folder

Renames or moves a folder. Pass parent_id: null to move it to the top level.

Path parameters

idstring (uuid)Required

The folder id. Must belong to your API key’s workspace.

Body parameters

namestring

New folder name.

parent_idstring (uuid) | null

Move under a new parent, or null for top level.

Request
curl -X PATCH https://api.qravio.app/api/public/v1/folders/0b1c2d3e-4f56-4789-abcd-ef0123456789 \
  -H "Authorization: Bearer qr_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Spring 2026 — EU" }'
200 OKThe updated Folder object.
Response
{
  "id": "0b1c2d3e-4f56-4789-abcd-ef0123456789",
  "workspace_id": "11111111-1111-1111-1111-111111111111",
  "name": "Spring 2026",
  "parent_id": null,
  "created_at": "2026-06-22T10:15:00Z",
  "updated_at": "2026-06-22T10:15:00Z"
}
404 Not FoundNo such folder in your workspace.
DELETE/folders/{id}

Delete a folder

Deletes a folder. Sub-folders are deleted too (cascade); QR codes inside it are kept but become folder-less.

Path parameters

idstring (uuid)Required

The folder id. Must belong to your API key’s workspace.

Request
curl -X DELETE https://api.qravio.app/api/public/v1/folders/0b1c2d3e-4f56-4789-abcd-ef0123456789 \
  -H "Authorization: Bearer qr_live_your_api_key"
200 OKDeleted.
Response
{ "message": "Folder '…' deleted successfully." }
404 Not FoundNo such folder in your workspace.

Tags

Label QR codes with campaign tags and manage which tags are applied.

POST/tags

Create a tag

Creates a tag (campaign label) in your workspace. Idempotent by name — creating an existing tag returns it.

Body parameters

namestringRequired

Tag name.

Request
curl -X POST https://api.qravio.app/api/public/v1/tags \
  -H "Authorization: Bearer qr_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{ "name": "summer" }'
201 CreatedThe Tag object.
Response
{
  "id": "aa11bb22-cc33-4d44-8e55-ff6677889900",
  "workspace_id": "11111111-1111-1111-1111-111111111111",
  "name": "summer",
  "qr_count": 12,
  "created_at": "2026-06-22T10:15:00Z"
}
GET/tags

List tags

Returns all tags in your workspace, each with the number of QR codes it’s applied to.

Request
curl https://api.qravio.app/api/public/v1/tags \
  -H "Authorization: Bearer qr_live_your_api_key"
200 OKAn array of Tag objects.
Response
[ {
  "id": "aa11bb22-cc33-4d44-8e55-ff6677889900",
  "workspace_id": "11111111-1111-1111-1111-111111111111",
  "name": "summer",
  "qr_count": 12,
  "created_at": "2026-06-22T10:15:00Z"
} ]
PATCH/tags/{id}

Rename a tag

Renames a tag. The change applies everywhere the tag is used.

Path parameters

idstring (uuid)Required

The tag id. Must belong to your API key’s workspace.

Body parameters

namestringRequired

New tag name.

Request
curl -X PATCH https://api.qravio.app/api/public/v1/tags/aa11bb22-cc33-4d44-8e55-ff6677889900 \
  -H "Authorization: Bearer qr_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{ "name": "summer-2026" }'
200 OKThe updated Tag object.
Response
{
  "id": "aa11bb22-cc33-4d44-8e55-ff6677889900",
  "workspace_id": "11111111-1111-1111-1111-111111111111",
  "name": "summer",
  "qr_count": 12,
  "created_at": "2026-06-22T10:15:00Z"
}
404 Not FoundNo such tag in your workspace.
DELETE/tags/{id}

Delete a tag

Deletes a tag and removes it from every QR code it was applied to. The QR codes themselves are kept.

Path parameters

idstring (uuid)Required

The tag id. Must belong to your API key’s workspace.

Request
curl -X DELETE https://api.qravio.app/api/public/v1/tags/aa11bb22-cc33-4d44-8e55-ff6677889900 \
  -H "Authorization: Bearer qr_live_your_api_key"
200 OKDeleted.
Response
{ "message": "Tag '…' deleted successfully." }
404 Not FoundNo such tag in your workspace.
PUT/qrcodes/{qr_id}/tags

Set a QR code’s tags

Replaces the full set of tags on a QR code. Provide either tag_ids (existing tags) OR names (created on the fly) — not both.

Path parameters

qr_idstring (uuid)Required

The QR code id. Must belong to your API key’s workspace.

Body parameters

tag_idsarray<string (uuid)>

Existing tag ids to apply. Mutually exclusive with names.

namesarray<string>

Tag names to apply (created if missing). Mutually exclusive with tag_ids.

Request
curl -X PUT https://api.qravio.app/api/public/v1/qrcodes/9f8b2c1a-1234-4d5e-8f90-abc123def456/tags \
  -H "Authorization: Bearer qr_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{ "names": ["summer", "in-store"] }'
200 OKThe QR code’s new set of Tag objects.
Response
[ {
  "id": "aa11bb22-cc33-4d44-8e55-ff6677889900",
  "workspace_id": "11111111-1111-1111-1111-111111111111",
  "name": "summer",
  "qr_count": 12,
  "created_at": "2026-06-22T10:15:00Z"
} ]
404 Not FoundNo such QR code in your workspace.
GET/qrcodes/{qr_id}/tags

Get a QR code’s tags

Returns the tags currently applied to a QR code.

Path parameters

qr_idstring (uuid)Required

The QR code id. Must belong to your API key’s workspace.

Request
curl https://api.qravio.app/api/public/v1/qrcodes/9f8b2c1a-1234-4d5e-8f90-abc123def456/tags \
  -H "Authorization: Bearer qr_live_your_api_key"
200 OKAn array of Tag objects.
Response
[ {
  "id": "aa11bb22-cc33-4d44-8e55-ff6677889900",
  "workspace_id": "11111111-1111-1111-1111-111111111111",
  "name": "summer",
  "qr_count": 12,
  "created_at": "2026-06-22T10:15:00Z"
} ]
404 Not FoundNo such QR code in your workspace.

Custom domains

Register and verify your own domains for QR short links.

POST/custom-domains

Register a custom domain

Registers a custom domain for your workspace’s short links and returns the DNS records to add. Available on plans with custom-domain entitlement, up to your plan’s limit. The domain starts in "pending" until verified.

Body parameters

domainstringRequired

Bare hostname, e.g. "links.example.com".

Request
curl -X POST https://api.qravio.app/api/public/v1/custom-domains \
  -H "Authorization: Bearer qr_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{ "domain": "links.example.com" }'
201 CreatedThe Custom Domain object, including the DNS records to add.
Response
{
  "id": "d0aa11bb-22cc-4d33-8e44-ff5566778899",
  "workspace_id": "11111111-1111-1111-1111-111111111111",
  "domain": "links.example.com",
  "status": "pending",
  "txt_record_name": "_qravio-challenge.links.example.com",
  "txt_record_value": "qravio-verify=abc123…",
  "cname_record_name": "links.example.com",
  "cname_record_value": "cname.qravio.app",
  "verified_at": null,
  "created_at": "2026-06-22T10:15:00Z"
}
402 Payment RequiredCustom-domain limit reached for your plan.
403 ForbiddenCustom domains aren’t available on your plan.
GET/custom-domains

List custom domains

Returns all custom domains registered in your workspace.

Request
curl https://api.qravio.app/api/public/v1/custom-domains \
  -H "Authorization: Bearer qr_live_your_api_key"
200 OKAn array of Custom Domain objects.
Response
[ {
  "id": "d0aa11bb-22cc-4d33-8e44-ff5566778899",
  "workspace_id": "11111111-1111-1111-1111-111111111111",
  "domain": "links.example.com",
  "status": "pending",
  "txt_record_name": "_qravio-challenge.links.example.com",
  "txt_record_value": "qravio-verify=abc123…",
  "cname_record_name": "links.example.com",
  "cname_record_value": "cname.qravio.app",
  "verified_at": null,
  "created_at": "2026-06-22T10:15:00Z"
} ]
GET/custom-domains/{id}

Retrieve a custom domain

Fetches a single custom domain, including its current verification status and DNS records.

Path parameters

idstring (uuid)Required

The custom domain id. Must belong to your API key’s workspace.

Request
curl https://api.qravio.app/api/public/v1/custom-domains/d0aa11bb-22cc-4d33-8e44-ff5566778899 \
  -H "Authorization: Bearer qr_live_your_api_key"
200 OKThe Custom Domain object.
Response
{
  "id": "d0aa11bb-22cc-4d33-8e44-ff5566778899",
  "workspace_id": "11111111-1111-1111-1111-111111111111",
  "domain": "links.example.com",
  "status": "pending",
  "txt_record_name": "_qravio-challenge.links.example.com",
  "txt_record_value": "qravio-verify=abc123…",
  "cname_record_name": "links.example.com",
  "cname_record_value": "cname.qravio.app",
  "verified_at": null,
  "created_at": "2026-06-22T10:15:00Z"
}
404 Not FoundNo such domain in your workspace.
PATCH/custom-domains/{id}

Update a custom domain

Changes the domain name. This resets the domain to "pending" and issues fresh DNS records, so you must re-verify ownership.

Path parameters

idstring (uuid)Required

The custom domain id. Must belong to your API key’s workspace.

Body parameters

domainstringRequired

New bare hostname.

Request
curl -X PATCH https://api.qravio.app/api/public/v1/custom-domains/d0aa11bb-22cc-4d33-8e44-ff5566778899 \
  -H "Authorization: Bearer qr_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{ "domain": "go.example.com" }'
200 OKThe updated Custom Domain object (status reset to pending).
Response
{
  "id": "d0aa11bb-22cc-4d33-8e44-ff5566778899",
  "workspace_id": "11111111-1111-1111-1111-111111111111",
  "domain": "links.example.com",
  "status": "pending",
  "txt_record_name": "_qravio-challenge.links.example.com",
  "txt_record_value": "qravio-verify=abc123…",
  "cname_record_name": "links.example.com",
  "cname_record_value": "cname.qravio.app",
  "verified_at": null,
  "created_at": "2026-06-22T10:15:00Z"
}
404 Not FoundNo such domain in your workspace.
DELETE/custom-domains/{id}

Delete a custom domain

Removes a custom domain. QR short links on this domain will stop resolving.

Path parameters

idstring (uuid)Required

The custom domain id. Must belong to your API key’s workspace.

Request
curl -X DELETE https://api.qravio.app/api/public/v1/custom-domains/d0aa11bb-22cc-4d33-8e44-ff5566778899 \
  -H "Authorization: Bearer qr_live_your_api_key"
200 OKDeleted.
404 Not FoundNo such domain in your workspace.
POST/custom-domains/{id}/verify

Verify a custom domain

Checks the domain’s TXT and CNAME DNS records and marks it verified when both resolve. Call after adding the records returned at creation.

Path parameters

idstring (uuid)Required

The custom domain id. Must belong to your API key’s workspace.

Request
curl -X POST https://api.qravio.app/api/public/v1/custom-domains/d0aa11bb-22cc-4d33-8e44-ff5566778899/verify \
  -H "Authorization: Bearer qr_live_your_api_key"
200 OKThe verification result.
Response
{
  "domain_id": "d0aa11bb-…",
  "domain": "links.example.com",
  "status": "verified",
  "txt_verified": true,
  "cname_verified": true,
  "verified_at": "2026-06-22T10:20:00Z",
  "detail": "Domain verified."
}
404 Not FoundNo such domain in your workspace.

Templates

Save and reuse QR design templates across your workspace.

POST/templates

Create a design template

Saves a reusable QR design template. The design object is the same QR design JSON accepted by the design field when creating a QR code. Template names are unique per workspace.

Body parameters

namestringRequired

Template name (unique in the workspace).

designobjectRequired

QR design JSON (dots, colors, logo, frame, output).

descriptionstring

Optional description.

categorystring

e.g. "minimal", "bold", "corporate", "dark".

qr_typestring

QR type this template targets (e.g. website, vcard_plus).

Request
curl -X POST https://api.qravio.app/api/public/v1/templates \
  -H "Authorization: Bearer qr_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Corporate blue",
    "category": "corporate",
    "design": { "dotsColor": "#4648d4", "backgroundColor": "#ffffff" }
  }'
201 CreatedThe Template object.
Response
{
  "id": "5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b",
  "workspace_id": "11111111-1111-1111-1111-111111111111",
  "created_by": "22222222-2222-2222-2222-222222222222",
  "name": "Corporate blue",
  "description": "Brand template for print",
  "category": "corporate",
  "qr_type": "website",
  "design": { "dotsColor": "#4648d4", "…": "…" },
  "usage_count": 4,
  "created_at": "2026-06-22T10:15:00Z",
  "updated_at": "2026-06-22T10:15:00Z"
}
409 ConflictA template with that name already exists in your workspace.
GET/templates

List design templates

Returns your workspace’s design templates. Filter by category or the QR type they target.

Query parameters

categorystring

Filter by category.

qr_typestring

Filter by target QR type.

Request
curl https://api.qravio.app/api/public/v1/templates \
  -H "Authorization: Bearer qr_live_your_api_key"
200 OKAn array of Template objects.
Response
[ {
  "id": "5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b",
  "workspace_id": "11111111-1111-1111-1111-111111111111",
  "created_by": "22222222-2222-2222-2222-222222222222",
  "name": "Corporate blue",
  "description": "Brand template for print",
  "category": "corporate",
  "qr_type": "website",
  "design": { "dotsColor": "#4648d4", "…": "…" },
  "usage_count": 4,
  "created_at": "2026-06-22T10:15:00Z",
  "updated_at": "2026-06-22T10:15:00Z"
} ]
GET/templates/{id}

Retrieve a design template

Fetches a single design template by id.

Path parameters

idstring (uuid)Required

The template id. Must belong to your API key’s workspace.

Request
curl https://api.qravio.app/api/public/v1/templates/5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b \
  -H "Authorization: Bearer qr_live_your_api_key"
200 OKThe Template object.
Response
{
  "id": "5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b",
  "workspace_id": "11111111-1111-1111-1111-111111111111",
  "created_by": "22222222-2222-2222-2222-222222222222",
  "name": "Corporate blue",
  "description": "Brand template for print",
  "category": "corporate",
  "qr_type": "website",
  "design": { "dotsColor": "#4648d4", "…": "…" },
  "usage_count": 4,
  "created_at": "2026-06-22T10:15:00Z",
  "updated_at": "2026-06-22T10:15:00Z"
}
404 Not FoundNo such template in your workspace.
PATCH/templates/{id}

Update a design template

Updates a template’s name, description, or category. Send only the fields you want to change.

Path parameters

idstring (uuid)Required

The template id. Must belong to your API key’s workspace.

Body parameters

namestring

New name.

descriptionstring

New description.

categorystring

New category.

Request
curl -X PATCH https://api.qravio.app/api/public/v1/templates/5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b \
  -H "Authorization: Bearer qr_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Corporate blue v2" }'
200 OKThe updated Template object.
Response
{
  "id": "5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b",
  "workspace_id": "11111111-1111-1111-1111-111111111111",
  "created_by": "22222222-2222-2222-2222-222222222222",
  "name": "Corporate blue",
  "description": "Brand template for print",
  "category": "corporate",
  "qr_type": "website",
  "design": { "dotsColor": "#4648d4", "…": "…" },
  "usage_count": 4,
  "created_at": "2026-06-22T10:15:00Z",
  "updated_at": "2026-06-22T10:15:00Z"
}
404 Not FoundNo such template in your workspace.
DELETE/templates/{id}

Delete a design template

Permanently deletes a design template. QR codes already using its design are unaffected.

Path parameters

idstring (uuid)Required

The template id. Must belong to your API key’s workspace.

Request
curl -X DELETE https://api.qravio.app/api/public/v1/templates/5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b \
  -H "Authorization: Bearer qr_live_your_api_key"
204 No ContentDeleted. No response body.
404 Not FoundNo such template in your workspace.

Analytics

Workspace-level scan analytics: summary, time series, devices, geography, heatmap, and conversion funnel.

GET/analytics/summary

Workspace summary

Total scans, unique visitors, and week-over-week change across all QR codes in your workspace.

Request
curl https://api.qravio.app/api/public/v1/analytics/summary \
  -H "Authorization: Bearer qr_live_your_api_key"
200 OKThe workspace summary.
Response
{ "total_scans": 18240, "unique_visitors": 13110, "scans_wow_change": 8.4 }
GET/analytics/days

Day-wise scans

A daily scan time series for charting.

Query parameters

daysinteger

Look-back window (1–90). Default 30.

Request
curl "https://api.qravio.app/api/public/v1/analytics/days?days=14" \
  -H "Authorization: Bearer qr_live_your_api_key"
200 OKAn array of { date, scans }.
Response
[ { "date": "2026-06-21", "scans": 421 }, { "date": "2026-06-22", "scans": 389 } ]
GET/analytics/devices

Device breakdown

Scan counts and percentages by device type. Requires advanced analytics (Pro+).

Request
curl https://api.qravio.app/api/public/v1/analytics/devices \
  -H "Authorization: Bearer qr_live_your_api_key"
200 OKDevice counts + percentages.
Response
{
  "mobile": 12800, "tablet": 640, "desktop": 4600, "bot": 200,
  "mobile_pct": 70.2, "tablet_pct": 3.5, "desktop_pct": 25.2, "bot_pct": 1.1
}
403 ForbiddenAdvanced analytics isn’t available on your plan.
GET/analytics/top-countries

Top countries

Top countries by scan volume across the workspace. Requires advanced analytics (Pro+).

Query parameters

limitinteger

Max rows (1–50). Default 5.

Request
curl "https://api.qravio.app/api/public/v1/analytics/top-countries?limit=10" \
  -H "Authorization: Bearer qr_live_your_api_key"
200 OKAn array of { country_code, scans }.
Response
[ { "country_code": "US", "scans": 9200 }, { "country_code": "IN", "scans": 4100 } ]
403 ForbiddenAdvanced analytics isn’t available on your plan.
GET/analytics/cities

Top cities

Top cities within a given country. Requires advanced analytics (Pro+).

Query parameters

countrystringRequired

ISO country code (2–3 chars), e.g. US.

limitinteger

Max rows (1–50). Default 10.

Request
curl "https://api.qravio.app/api/public/v1/analytics/cities?country=US&limit=10" \
  -H "Authorization: Bearer qr_live_your_api_key"
200 OKAn array of { city, scans }.
Response
[ { "city": "New York", "scans": 1820 }, { "city": "San Francisco", "scans": 1210 } ]
403 ForbiddenAdvanced analytics isn’t available on your plan.
GET/analytics/heatmap

Scan heatmap

A 7×24 matrix of scans by day-of-week (rows) and UTC hour (columns), with the peak cell. Requires advanced analytics (Pro+).

Request
curl https://api.qravio.app/api/public/v1/analytics/heatmap \
  -H "Authorization: Bearer qr_live_your_api_key"
200 OKThe heatmap matrix.
Response
{
  "matrix": [ [0,1,3, "…24 hours…"], "…7 days…" ],
  "peak": { "day": 2, "hour": 14, "scans": 320 },
  "bucketed_by": "utc_hour"
}
403 ForbiddenAdvanced analytics isn’t available on your plan.
GET/analytics/funnel

Conversion funnel

Scan → action (form submit / link click) conversion funnel for the workspace, or a single QR. Pass format=csv for a CSV download. Requires advanced analytics (Pro+).

Query parameters

qr_idstring (uuid)

Limit the funnel to one QR code.

rangeinteger

Look-back window in days (1–365). Default 30. Clamped to your plan’s retention.

exclude_botsboolean

Exclude bot traffic. Default true.

formatstring

Set to "csv" to download a CSV instead of JSON.

Request
curl "https://api.qravio.app/api/public/v1/analytics/funnel?range=30" \
  -H "Authorization: Bearer qr_live_your_api_key"

# CSV download:
curl "https://api.qravio.app/api/public/v1/analytics/funnel?range=30&format=csv" \
  -H "Authorization: Bearer qr_live_your_api_key" -o funnel.csv
200 OKThe funnel (JSON), or a text/csv file when format=csv.
Response
{
  "range_days": 30,
  "qr_id": null,
  "conversion_rate": 12.5,
  "steps": [
    { "key": "scan", "label": "Scans", "count": 4000, "pct_of_previous": 100.0, "drop_off_pct": 0.0 },
    { "key": "action", "label": "Actions", "count": 500, "pct_of_previous": 12.5, "drop_off_pct": 87.5,
      "breakdown": { "form_submit": 320, "link_click": 180 } }
  ]
}
403 ForbiddenAdvanced analytics isn’t available on your plan.

Leads

Read and export lead-form submissions captured by your QR landing pages.

GET/leads

List lead submissions

Returns lead-form submissions for your workspace, newest first. Each item includes the submitting QR code’s id and name plus the captured form data.

Query parameters

qr_idstring (uuid)

Only submissions from this QR code.

limitinteger

Page size (1–500). Default 50.

offsetinteger

Row offset for pagination. Default 0.

Request
curl "https://api.qravio.app/api/public/v1/leads?limit=50" \
  -H "Authorization: Bearer qr_live_your_api_key"
200 OKA page of lead submissions plus the total count.
Response
{
  "items": [
    {
      "id": "7c8d9e0f-1a2b-4c3d-8e4f-5a6b7c8d9e0f",
      "qr_id": "9f8b2c1a-1234-4d5e-8f90-abc123def456",
      "qr_name": "Trade-show booth",
      "data": { "name": "Jane Doe", "email": "[email protected]", "company": "Acme" },
      "submitted_at": "2026-06-22T10:15:00Z"
    }
  ],
  "total": 128
}
403 ForbiddenLead capture isn’t available on your plan.
GET/leads/export.csv

Export leads as CSV

Streams all lead submissions as a CSV file. Columns are submitted_at, qr_name, and the union of every captured field across submissions.

Query parameters

qr_idstring (uuid)

Only submissions from this QR code.

Request
curl "https://api.qravio.app/api/public/v1/leads/export.csv" \
  -H "Authorization: Bearer qr_live_your_api_key" -o leads.csv
200 OKA text/csv file attachment.
403 ForbiddenLead capture isn’t available on your plan.

Outbound webhooks

Push scan and lead events to your own stack in real time — Zapier, Make, or a custom endpoint. Manage endpoints under Settings → Webhooks.

Free / Starter

No webhook access

Pro

3 endpoints

scan, lead.submitted

Agency

20 endpoints

scan, lead.submitted

Delivery headers

X-Qravio-Eventstring

The event type — scan, lead.submitted, or ping.

X-Qravio-Timestampinteger (unix seconds)

When the delivery was sent. Reject deliveries older than 5 minutes.

X-Qravio-Signaturestring

sha256=<hex> — HMAC-SHA256 of "{timestamp}." + the raw request body, using your endpoint’s signing secret.

X-Qravio-Idempotency-Keystring (uuid)

Dedupe key. A network retry of the same delivery reuses this key; a manual redeliver mints a new one.

scan

Fires for every scan of a QR code in the workspace, for any endpoint subscribed to it. Carries a hashed IP, never the raw address.

json
{
  "event": "scan",
  "id": "7c1f2e3a-1234-4d5e-8f90-abc123def456",
  "created_at": "2026-06-24T10:00:00Z",
  "workspace_id": "11111111-1111-1111-1111-111111111111",
  "data": {
    "qr_id": "9f8b2c1a-1234-4d5e-8f90-abc123def456",
    "country_code": "US",
    "region": "CA",
    "city": "San Francisco",
    "device_type": "mobile",
    "ip_hash": "a1b2c3d4e5f6…",
    "session_id": "s_9f8b2c1a",
    "scanned_at": "2026-06-24T10:00:00Z"
  }
}
lead.submitted

Fires when a visitor submits a Lead Capture form. Carries the submitted field values — enabling this endpoint is a customer-directed onward transfer of that lead’s PII.

json
{
  "event": "lead.submitted",
  "id": "7c1f2e3a-1234-4d5e-8f90-abc123def456",
  "created_at": "2026-06-24T10:00:00Z",
  "workspace_id": "11111111-1111-1111-1111-111111111111",
  "data": {
    "qr_id": "9f8b2c1a-1234-4d5e-8f90-abc123def456",
    "qr_name": "Booth A",
    "fields": { "email": "[email protected]", "name": "Sam" }
  }
}
ping

A synthetic test event sent by the "Send test ping" action in Settings → Webhooks — use it to verify your endpoint and signature check.

json
{
  "event": "ping",
  "id": "7c1f2e3a-1234-4d5e-8f90-abc123def456",
  "created_at": "2026-06-24T10:00:00Z",
  "workspace_id": "11111111-1111-1111-1111-111111111111",
  "data": { "message": "Qravio test ping" }
}

Verify the signature

Compute hmac_sha256(secret, timestamp + "." + raw_body) and compare it to X-Qravio-Signature with a constant-time comparison. Reject deliveries whose timestamp is more than 5 minutes old, and dedupe retries on X-Qravio-Idempotency-Key.

Verify a signature — Python
import hmac, hashlib, time

def verify(secret: str, timestamp: str, raw_body: bytes, signature: str) -> bool:
    if abs(time.time() - int(timestamp)) > 300:
        return False  # stale — possible replay
    expected = hmac.new(secret.encode(), f"{timestamp}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(f"sha256={expected}", signature)
Verify a signature — Node
const crypto = require('crypto');

function verify(secret, timestamp, rawBody, signature) {
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false; // stale
  const expected = crypto.createHmac('sha256', secret)
    .update(`${timestamp}.`).update(rawBody).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(`sha256=${expected}`), Buffer.from(signature));
}

Retries

A failing delivery retries up to 4 times with backoff, then moves to dead_letter — redeliver it manually from the delivery log in Settings → Webhooks. An endpoint auto-pauses after 15 consecutive failures.

Attempt 1Immediate
Attempt 2~1 minute later (±20% jitter)
Attempt 3~10 minutes later (±20% jitter)
Attempt 4~1 hour later (±20% jitter)

QR types

When creating a QR code, the content object’s shape depends on the type. Each type reads from one field below (* = required). Every plan can create every dynamic type but one — QR type is not a paywall lever here.

Every plan — Free included

All 15 dynamic types: website, pdf, images, vcard_plus, video, mp3, list_links, business, social_media, event, coupon, apps, location, menu, review_funnel.

Pro / Agency only

lead_form (Lead Capture) — the one type still behind a plan gate. What is paid is the capability: capturing, viewing and exporting submissions.

websitecontent.urlurl — the live target lives in destinations[]; content.url is the single-destination convenience form
pdf / video / mp3content.fileContentfile_id*, file_path*, file_name*, content_type*, size_bytes*, version*
imagescontent.imagesContentarray of fileContent objects (same required fields)
vcard / vcard_pluscontent.vCardContentfirst_name*, last_name*, phone, email, company, job_title, website, bio, street_address, city, state, country, zip_code
businesscontent.businessContentbusiness_name*, tagline, phone, email, website, street_address, city, state, postal_code, country, logo_url, business_hours, theme
list_linkscontent.listOfLinkContentprofile_name*, bio, profile_url, links[] (id*, title*, url*, icon, position)
social_mediacontent.socialMediaContentprofileName*, bio, instagram, facebook, twitter, linkedin, youtube, website, avatarUrl, coverUrl
eventcontent.eventContentevent_name*, start_date*, end_date*, start_time*, end_time*, timezone, location, description, organizer_name, rsvp_url, website_url
couponcontent.couponContenttitle*, description, discountType, discountValue, couponCode, termsAndConditions, startDate, endDate, businessName, website
appscontent.appContentappName*, appStoreUrl, playStoreUrl, companyName, description, website
locationcontent.locationContentplace_name*, street_address, city, state, postal_code, country, landmark, latitude, longitude, phone, maps_url_override, open_directly (true → 302 to Maps, no landing page)
menucontent.menuContentcurrency, menu_note, show_diet_marks, categories[] (name*, description, items[] → name*, price_minor (integer minor units), price_note, diet, is_available)
review_funnelcontent.reviewFunnelContentgoogle_review_url* (must be a Google-operated host), star_threshold, rating_prompt, thank_you_message — pair with content.leadFormContent for the private-feedback step
lead_formcontent.leadFormContentfields[] (name*, label*, type*, required*, options), success_message, redirect_url, notify_email
urlcontent.urlurl
textcontent.texttext
emailcontent.emailContentemail*, subject, body
smscontent.smsContentnumber*, message*
phonecontent.phoneContentnumber*
whatsappcontent.whatsAppContentnumber*, message
wificontent.wifiContentnetwork_name*, encryption*, password, hidden
bitcoincontent.bitcoinContentaddress*, amount (BTC, ≤ 21,000,000, 8 dp), label, message
upicontent.upiContentpa*, pn, am, tn — INR only; cu is not accepted
paypalcontent.paypalContentusername* (PayPal.me handle, not an email), amount, currency, note

Static types (available on all plans): url, text, vcard, email, sms, phone, whatsapp, wifi, bitcoin, upi, paypal. A static QR encodes its payload in the pixels, so it has no short code, no editable destination and no scan analytics.