# Deleting your account
Source: https://docs.scanova.io/account/delete-account
What happens when you delete a Scanova account, step by step: what gets removed, what stops resolving, and what you should export before you begin at all.
**Delete Account**, in red at the bottom of the profile card on the [Account tab](/account/profile-and-appearance), permanently removes your Scanova account. The flow asks why you're leaving, shows a retention screen tailored to that reason, and ends with a password-confirmed final step. Deletion is immediate once confirmed and can't be undone.
There is no recovery once you complete this flow. Everything below documents the steps so you know what to expect — back out with **Cancel** at any point if you're not sure.
## Selecting a reason
Click **Delete Account** to open the **Delete Account** drawer. **Select a reason** is required before you can continue; an optional **Additional comments** box lets you add detail.
The dropdown offers six reasons:
* Product is complicated/not working as expected
* Didn't get the results I hoped for with QR Codes
* Plans are too expensive
* Found a better alternative
* No longer required and want to delete my data
* Other (please specify below)
Click **Next** to continue to the retention screen for your chosen reason.
## The retention screen
Which screen appears next depends on the reason you picked:
* **Plans are too expensive** shows **A special discount, just for you** — a plan/billing-frequency selector offering up to 40% off, with **Avail Discount Now** (switches your plan instead of deleting) and **Continue with Deletion** buttons
* Every other reason shows **We are sorry to see you go** instead — a reminder of what you'd lose (Dynamic QR Codes, Custom QR Codes, QR Code Tracking & Analytics, Lead Generation, High-resolution QR Codes, Mobile Landing Page Design, with an **Explore All Features** link), a data-security assurance (ISO 27001:2013, GDPR, and SOC 2 compliance), a red warning that the deletion is permanent and can't be undone, and **Cancel** / **Delete Account** buttons
## Confirming deletion
Proceeding from either retention screen (**Continue with Deletion**, or **Delete Account**) reaches the final **Confirm account deletion** step: an acknowledgement checkbox and a **Current Password** field. **Confirm Delete** stays disabled until both are filled in.
Clicking **Confirm Delete** here deletes your account immediately, with no further confirmation. Use **Cancel** on this or any earlier screen to back out safely — canceling returns you to the Account page with nothing changed.
## Related
* [Profile & appearance](/account/profile-and-appearance) — where the **Delete Account** link lives, alongside the rest of your account settings
* [Subscription & billing](/billing/subscription-and-billing) — manage or downgrade your plan without deleting your account
# Email preferences
Source: https://docs.scanova.io/account/email-preferences
Choose which Scanova emails actually reach your inbox, from product announcements to scan reports, without unsubscribing from everything that matters.
The **Email Preferences** tab (`/account/email-preferences`) controls which transactional and marketing emails Scanova sends you. It sits alongside **Account**, **Push Notifications**, and **Appearance** under `/account`.
## Notification categories
Six categories can each be toggled on or off independently:
| Category | What it covers |
| - | - |
| **Subscription Reminders** | Reminder emails about your subscription plan's expiry. |
| **Billing and Payments** | Emails related to payments and invoices. |
| **Expired QR Codes** | Notifications when someone scans an expired QR Code. |
| **API Tokens** | Emails about API token expirations. |
| **Assistance Emails** | Tips and guidance to get the most from the platform. |
| **Promotion Emails** | Latest offers, discounts, and exclusive deals. |
The header above the list shows a running count (e.g. "6 of 6 enabled").
## Turning a category off
Click the toggle to turn a category off. Since this stops emails you might want later, a confirmation dialog appears first:
Click **Confirm** to disable the category, or **Cancel** to leave it unchanged. Turning a category back **on** applies immediately, with no confirmation step.
The change is optimistic — the toggle updates right away and only reverts if the save fails, so you'll rarely notice a delay either way.
## Bulk actions
Next to the "N of 6 enabled" count, **Disable all** and **Enable all** apply the same toggle to every category at once. Disabling all still goes through the same confirmation dialog described above.
## Related
* [Profile & appearance](/account/profile-and-appearance) — the sibling Account tab for profile, security, business info, and localization settings
* [Subscription & billing](/billing/subscription-and-billing) — the plan and payment details behind the Billing and Payments email category above
# Profile & appearance
Source: https://docs.scanova.io/account/profile-and-appearance
Manage your Scanova profile, your password and security settings, business details, localization, and whether the interface renders in light or dark mode.
Your profile, login security, business info, and localization settings all live on the **Account** tab at `/account`. A separate **Appearance** tab controls the dashboard's theme. Both are reachable from the account menu at the bottom of the sidebar.
The card on the left summarizes your profile type, industry, language, time zone, and the date you joined ("Member since"), plus a **Delete account** link. The panel on the right is where you make changes.
Hovering the avatar shows a **Change Photo** overlay — you can upload a profile photo. Without one, it shows your initials in a colored circle instead (for example "DO" for a user named "Docs").
## Updating your name
Click the pencil icon next to **Full Name** to edit it inline. This splits into two separate fields — **First name** and **Last name** — each with its own text input, plus a shared checkmark to save and an X to cancel.
## Changing your email
Click the pencil icon next to **Email Address** to open the **Change Email** drawer.
The drawer shows your current email (read-only) and a **New email** field. Enter the address you want to switch to.
Enter your current password in the **Current Password** field, then click **Proceed**.
Scanova sends a 6-digit verification code to your new email address. Enter it to complete the change.
Changing your email signs you out — you'll need to log back in with the new address. Newsletter subscriptions continue on your previous email, as the drawer notes.
If you signed up with Google or another SSO provider, the email field isn't editable — your email is managed by that identity provider instead.
## Password and multi-factor authentication
The **Login & Security** section shows your password as a masked value and your MFA status as **Off** or **On · \{method}**. Click either pencil icon to open the corresponding drawer:
* **Password** opens **Change Password**, which asks for your current password plus a new password (8–200 characters) and confirmation
* **Multi-factor authentication** opens a method picker for **Passkey** (labeled "Recommended for this device" when your browser supports it), **Email**, or **Authenticator App**
For the full setup walkthrough for each method, see [Multi-factor authentication](/authentication/multi-factor-authentication). For resetting a forgotten password, see [Password reset](/authentication/password-reset).
## Business info
The **Business** section records your profile type and industry — used to tailor recommendations, not to gate features. Click either pencil icon to open the **Edit profile** drawer.
**Profile type** is one of:
* **Individual** — personal use
* **Small Business** — teams & startups
* **Enterprise** — large orgs
**Industry** is a dropdown that depends on the profile type you pick — for **Individual**, the options are **Consultant**, **Freelance**, **Student**, and **Other**.
Click **Save changes** to apply.
## Localization
The **Localization** section controls the language and time zone Scanova uses for you. Click either pencil icon under **Language** or **Time Zone** to open the **Edit localization** drawer.
* **Language** — English, Español, Français, or Deutsch
* **Time zone** — a searchable dropdown listing every IANA time zone (for example **Africa/Abidjan**), each labeled with its **GMT** offset — your current zone shows as, e.g., **Asia/Kolkata (GMT+5:30)**
Click **Save changes** to apply — the change takes effect immediately across the dashboard.
## Push notifications tab
The **Push Notifications** tab (`/account/push-notifications`) sits between Email Preferences and Appearance. As of this writing it shows a single disabled **Browser Push Notifications** toggle labeled "Push notifications aren't available yet. Check back soon." — the feature is visible in the UI but not yet functional.
## Appearance tab
The **Appearance** tab (`/account/appearance`) is separate from the Account tab and controls only the dashboard's visual theme.
Pick **Light**, **Dark**, or **System** (which follows your OS setting) — the change previews immediately. Click **Update preferences** to persist your choice to your account, so it carries across devices and sessions.
The moon/sun icon in the top bar toggles between light and dark instantly for the current session, without opening this tab — use the Appearance tab when you want the choice saved to your account, or to pick **System**.
## Deleting your account
**Delete account**, in red at the bottom of the profile card, starts a multi-step flow: a required reason for leaving, a retention screen tailored to that reason, and a final password-confirmed step. Deletion is immediate once confirmed and can't be undone — see [Deleting your account](/account/delete-account) for the full walkthrough.
## Related
* [Deleting your account](/account/delete-account) — the full Delete Account flow, reason by reason
* [Multi-factor authentication](/authentication/multi-factor-authentication) — the full setup walkthrough for the Manage MFA drawer opened from this page's Login & Security section
* [Email preferences](/account/email-preferences) — the sibling Account tab controlling which transactional and marketing emails you receive
* [Password reset](/authentication/password-reset) — recovering access if you've forgotten the password managed in Login & Security
# AI Studio
Source: https://docs.scanova.io/ai-studio/overview
Describe the landing page you want and let AI Studio generate its blocks and copy, then refine that draft in the page builder before you publish it live.
AI Studio generates a landing page from a plain-language description. Instead of adding blocks one at a time, you describe the page you want in a chat-style sidebar, and the AI drafts it — asking clarifying questions first if your description is too open-ended, or drafting directly if it isn't.
## How to use it
From the dashboard, click **Create QR Code**. On the "Create a new QR Code" screen, click **Start** on the **AI Studio** card.
Type a description of the page you want into the sidebar's chat input, for example:
> "A wedding page for Sarah and James, June 20 at Rosewood Gardens. Include the event schedule, venue map, and an RSVP form."
The AI responds in the chat sidebar. What happens next depends on how specific your prompt was:
* If your prompt leaves out details the AI needs (images, contact info, specific copy), it asks clarifying follow-up questions instead of generating right away
* If your prompt is specific enough, it responds with a confirmation (e.g. "Your AI-generated landing page preview is ready!") once the draft is built
Don't assume a single message always produces a finished page in one step. A vague prompt gets a round of clarifying questions first — reply with the missing details to continue.
## Refining the page
Once a page is generated, the chat input stays open — send follow-up messages to adjust the result (for example, "change the colour scheme to navy and gold," or "add a section for accommodation recommendations"). Each message produces a new version.
## Suggestion chips
If you're not sure how to start, click one of the suggestion chips below the input box:
* Event
* Wedding
* Feedback
* Product launch
* Restaurant
* Portfolio
* Lead Generation
* App Promotion
This chip list matches the live product exactly. Clicking a chip fills the input with a starting prompt you can edit before sending.
## AI credits
Generation is metered. The chat panel shows a running counter — **AI Credits: *n* of *5* used** in this account's case — that increments each time you submit a prompt (including follow-up refinements). Watch this counter if you're working with a limited grant.
## The AI Studio editor
Once you're inside a generation session, the header offers **Preview** and **Publish**, and a right-hand panel with **General** and **Advanced** tabs covering the QR Code name, its current page URL, the page's URL slug, and folder assignment — separate from the multi-page block builder's own Settings/Theme controls.
The live mobile preview in this panel renders through the same published-page domain used by the rest of the product. In some environments that preview domain may be unreachable or gated, in which case the phone mockup stays blank even after a successful generation — this doesn't necessarily mean generation failed; check the chat's own confirmation message and the AI Credits counter instead.
## Publishing
When the page looks the way you want it, click **Publish** in the header and confirm in the publish panel to make the page live. The AI-generated QR Code uses the same dynamic QR Code system as the block-based builder — you can change the page content at any time without reprinting the QR Code.
## Editing an existing AI page
To return to an AI-generated page, open its QR Code from **QR Codes** and click **Edit** (not **Edit Design**, which opens the QR Code's visual pattern/logo designer instead).
Reopening an AI-generated page's editor starts from the default **Describe your page** prompt — the earlier chat thread and generated draft aren't restored. Treat the AI chat as session-scoped rather than something you can resume on a later visit.
## AI in the block builder
There is **no separate "AI page" type** you can pick from the ordinary "Add a page" sheet inside a multi-page QR Code's block builder. AI Studio is its own top-level creation method, reached only from **Create QR Code → AI Studio** as described above — not a page type alongside General, Audio, Gallery, Documents, and Intro Page.
## Access requirements
Access to AI Studio is controlled by an account-level quota rather than by plan tier alone, so availability can differ between two accounts on the same plan. If the entry card shows **Upgrade** rather than **Start**, contact [Support](/support/overview) to confirm your account's entitlement. Once the quota is present the feature works end to end, metered through the AI Credits counter described above.
## Related
* [AI QR / AI Studio](/qr-codes/create/ai-qr) — the Create QR Code entry point this feature is reached from
* [Page Builder overview](/page-builder/overview) — the block-based editor AI Studio populates behind the scenes
* [Quick Code vs. full Page](/qr-codes/create/quick-code-vs-page) — how AI Studio compares to the other two creation paths
# Bot scans
Source: https://docs.scanova.io/analytics/bot-scans
What bot scans are, why automated crawlers and preview tools register as scans, and how the Exclude Bot Traffic filter keeps them out of your analytics.
Bot scans are QR Code scans triggered by automated software, not real people. Scanova detects these using device type identification and flags them separately as **BOT** traffic in scan data.
## What are bot scans
When a QR Code's URL is crawled, previewed, or tested by automated tools, it registers as a scan in analytics. Common sources include:
* **Search engine crawlers** indexing the destination URL (for example Googlebot, Bingbot)
* **Security scanners** checking URLs for malware or phishing
* **Link preview tools** generating previews when a URL is shared on messaging or social platforms
* **Uptime monitors** periodically pinging URLs to check availability
* **QR Code testing tools** used during campaign setup and quality assurance
Some common examples seen in scan data: **Googlebot**, **AhrefsBot**, **Semrushbot** (SEO crawlers); **Twitterbot**, **Slackbot** (link preview generators); **Pingdom**, **UptimeRobot** (monitoring tools).
## Impact on scan data
Bot scans inflate the Total Scans count, making it harder to measure real user engagement accurately. For QR Codes deployed on high-traffic pages or shared widely online, bot scans can make up a significant portion of scan data.
## Excluding bot traffic
Scanova provides an **Exclude Bot Traffic** filter on the [Analytics](/analytics/dashboard) page and in [report](/analytics/reports)/[email report](/analytics/email-reports) requests. When enabled, all metrics — including scan breakdowns by date, location, device, and OS — reflect human scans only, giving a cleaner view of actual campaign performance.
## Related
* [Analytics dashboard](/analytics/dashboard) — where the Exclude Bot Traffic checkbox lives day to day
* [Reports](/analytics/reports) and [Email reports](/analytics/email-reports) — the same filter, available when requesting a scheduled or one-off report
# Analytics dashboard
Source: https://docs.scanova.io/analytics/dashboard
How to read the Analytics dashboard: filtering by QR Codes, folders or tags, the Overview stat cards, the geography and engagement panels, and exporting.
The **Analytics** dashboard (`/analytics`) is where you see how your QR Codes are performing — scan volume, audience, geography, and engagement — across any combination of QR Codes, pages, folders, tags, categories, or users.
## Filtering scan data
Above the charts, a filter bar lets you scope the whole dashboard:
* **Entity type** — switch between QR Codes, Folders, Tags, Categories, or Users
* **Entity selector** — a searchable multi-select for the chosen entity type, with a **Select all** shortcut
* **Date range** — click the date field to open a two-month calendar picker. Pick a start and end date and select **Apply**, or **Clear** to reset.
* **Exclude Bot Traffic** — a checkbox that filters out non-human scans, with a **Learn what it means** link, explained in [Bot scans](/analytics/bot-scans)
After making your selections, select **Get Analytics** to load the data. A **View AI Summary** button (marked Beta) is also available once you have a selection.
The dashboard doesn't load any data until you select at least one entity and select **Get Analytics** — it starts on an empty "Get started by selecting..." state.
Selecting too many entities and/or too wide a date range (for example, every QR Code on the account across several months) doesn't load the dashboard at all — a **"Generate a report instead"** dialog appears instead, reading "This selection is too large to display here. You can generate a downloadable report instead — we'll email you when it's ready," with **Cancel** and **Create Report** buttons. Narrow your selection (fewer entities or a shorter date range) if you want the live dashboard, or use **Create Report** to fall back to an async [Reports](/analytics/reports) job for a large selection.
## Overview stat cards
The **Overview** row shows six headline metrics for the selected entities and date range: **Total Scans**, **Unique Scans**, **Source (Scan/URL)**, **Engagement Rate**, **Leads Captured**, and **Conversion Rate**. **Source (Scan/URL)** breaks down how visitors arrived — via an actual QR Code scan versus the underlying short URL opened directly — shown as a ratio (e.g. "0/0"). Each card shows a trend indicator (percentage change against the previous period) once there's data to compare, and each has an info tooltip explaining how it's calculated.
When the selection has few or no scans, a **"Low scans? Let's fix that. Get quick tips to improve your QR performance now."** banner appears below the Overview cards, with a **See how** link.
Next to the Overview heading are two export menus:
* **Export Analytics Report** — a one-off visual export of the current view as PDF, XLS, or XLSX
* **Export Scan Data** — a one-off export of the underlying scan records as CSV, XLS, or XLSX
These two menus export the dashboard's *current* view instantly. If you need a report you can revisit later, or one delivered on a recurring schedule, see [Reports](/analytics/reports) and [Email reports](/analytics/email-reports) instead.
## Chart panels
Below the Overview cards, the dashboard breaks scan activity down into focused panels:
* **Scans By QR Code** — your top-scanning codes for the period
* **Scans By Day And Time** — a day-of-week × time-of-day heatmap of when scans happen
* **Scans By Date** — a trend chart with **Date** / **Week** / **Month** granularity toggles and a percentage change badge versus the previous period
* **Scans By Country** — a world map of scan volume by country. Scanova notes that this map is **for representational purposes only**.
* **Recommended next moves** — short, auto-generated suggestions based on the period's data (for example, flagging a drop in scans and suggesting you refresh creatives), each tagged with a priority level. With nothing to flag, it shows "No standout actions this period — metrics look balanced."
* **Audience & geography** and **Who is scanning** — audience breakdowns. **Audience & geography** toggles between **By region**, **New vs returning**, and **Scan source** views; **Who is scanning** toggles between **Device**, **Browser**, **OS**, and **Handset** views
* **Which QR Codes are winning** — a ranked comparison of your QR Codes for the period
* **What users did** — on-page event actions, shown as rings representing each event's share of total scans
* **Engagement by QR Code** — per-code engagement detail
* **Where conversions are lost** — a drop-off view for entities with conversion tracking configured
* **Lead Form Data** — data from any lead-capture forms attached to your QR Codes, with table and grid view toggles
Every panel has its own empty state (for example, "No scan data yet for this period") when there's nothing to show for the current filter and date range.
## GPS location scans
**Scans By GPS Location** is a dedicated panel for precise, device-reported scan coordinates (distinct from the country-level map above). On an account with no GPS-tagged scans, it doesn't render a map — instead it shows a message pointing you to **Export Scan Data**, since GPS coordinates are only meaningful once you have data to plot. If your QR Codes have collected high-accuracy location scans, expect this panel to render those as points on a map instead.
## Conversion Tracking
The **Conversion Tracking** entry in the Analytics section of the sidebar (`/analytics/conversion`) is a separate area for defining conversion funnels and events on top of your scan data. It isn't covered on this page — see [Conversion Tracking](/conversion-tracking/introduction) for full setup and usage documentation.
## Where to go next
* [Reports](/analytics/reports) — generate a downloadable, point-in-time analytics or scan-data report
* [Email reports](/analytics/email-reports) — set up a recurring report delivered by email or Slack
## Related
* [Reports](/analytics/reports) — a downloadable, point-in-time export of this same dashboard's data
* [Email reports](/analytics/email-reports) — a recurring, scheduled version of this dashboard's numbers
* [Leads & lead lists](/leads/lead-lists) — background on the Lead Form Data this dashboard surfaces
* [Bot scans](/analytics/bot-scans) — what the Exclude Bot Traffic checkbox above actually filters out, and why
* [How scans are counted](/analytics/scan-counting) — what Total Scans measures when a QR Code has a password, age, lead, form, or location gate
# Email reports
Source: https://docs.scanova.io/analytics/email-reports
Scheduling recurring scan analytics digests by email or Slack: choosing QR Codes, picking recipients, and setting a daily, weekly or monthly frequency.
**Email Reports** (`/analytics/email-report`) sets up **recurring** scan-analytics reports delivered automatically by email (or Slack) on a schedule you define — daily, weekly, or monthly. This is different from [Reports](/analytics/reports), which generates a single, one-off report on demand. Use Email Reports when you want a standing digest; use Reports when you want a specific export right now.
Email Reports is only shown in the sidebar for accounts with export permission (Pro plans and above). The page states that you can create unlimited email reports.
## Setting up a scheduled report
From **Analytics > Email Reports**, select **Setup Email Reports**.
Enter a name in **Report Name** (up to 100 characters) so you can identify it later in the list.
Under **Select QR Codes**, choose **Top 5 Performing QR Codes**, **Top 10 Performing QR Codes**, or **Custom** to pick specific codes yourself. Optionally check **Exclude Bot Traffic** to filter out non-human scans.
Under **Choose Notification Channels**, select **Email Notification**, **Slack Notifications**, or **Both Email & Slack**. Selecting a Slack-based option checks your workspace's Slack connection status before you can proceed — connect Slack under Integrations first if you plan to use it.
Under **Recipients**, enter at least one email address in the **To** field. Select **Cc** or **Bcc** to reveal additional recipient fields if you need to loop in more people without them being primary recipients.
Under **Choose Email Frequency**, choose **Daily**, **Once a week**, or **Once a month**. A helper line below confirms the cadence in plain language (for example, "You will receive the email report every 7 days" for weekly).
Select **Create Report**. A confirmation toast appears, and the new schedule is added to the list on the Email Reports page.
After creating a report, reload the page if the list doesn't immediately show your new entry — a report can be created successfully in the background while the list still shows its empty state until a full refresh.
## Managing existing schedules
The Email Reports table lists each schedule's name, QR Code selection, recipient count, frequency, and creator. Use the **Action** column to edit a schedule (pencil icon) or delete it (trash icon) — deleting prompts a confirmation dialog before removing it.
## Where to go next
* [Reports](/analytics/reports) — for a one-off report generated on demand instead of a recurring schedule
* [Analytics dashboard](/analytics/dashboard) — the live view this data is drawn from
## Related
* [Reports](/analytics/reports) — the one-off counterpart to this recurring schedule
* [Analytics dashboard](/analytics/dashboard) — the live view these scheduled reports summarize
* [Slack](/integrations/slack) — connect your workspace here before choosing Slack as a notification channel above
# Reports
Source: https://docs.scanova.io/analytics/reports
Generating one-off scan analytics reports you can download later, how the request queue works, and when to use a report instead of the live dashboard.
**Reports** (`/analytics/reports`) generates point-in-time, downloadable analytics and scan-data files for a chosen set of QR Codes, folders, tags, categories, or users and a chosen date range. It's the one-off counterpart to [Email reports](/analytics/email-reports), which delivers reports on a recurring schedule instead of on demand.
Reports is only shown in the sidebar for accounts with export permission (Pro plans and above).
## Creating a report
From **Analytics > Reports**, select **Create Report**. This opens a drawer where you configure the report.
Under **Select QR Codes, Folders, Tags, Categories or Users**, choose the entity type from the dropdown, then select the specific items (or **Select all**) in **View/Edit Selection**. Optionally check **Exclude Bot Traffic** to filter out non-human scans.
Select **Select Time Period** to open a date-range picker and choose the start and end dates the report should cover.
Under **Select Reports and Format**, you can request either or both of:
* **Analytics Report** — a visual summary, exportable as **PDF**, **XLS**, or **XLSX**
* **Scan Data Report** — the underlying scan records, exportable as **CSV**, **XLS**, or **XLSX**
Each has its own format dropdown, so you can request an Analytics Report as PDF and a Scan Data Report as CSV in the same request. Both checkboxes are **checked by default** (Analytics Report as PDF, Scan Data Report as CSV) — uncheck either one if you only need the other.
Check **Save this report** to keep this combination of entities, filters, and formats so you can re-run it later without re-entering everything — saved configurations appear under **saved reports**, accessible from a link at the top of the drawer via a **Select saved reports** dropdown.
Select **Create New Report**. The drawer closes and a new row appears in the Reports table with a **Requested On** timestamp and a **Status** of **In Progress**.
## After you request a report
Requested reports are tracked in a table with these columns:
* **Requested On** — when you submitted the request
* **Generated On** — when the report finished processing (shown as `—` while still generating)
* **Requested By** — the user who requested it
* **Status** — a progress indicator: **In Progress**, then either **Ready**/**Completed** or **Failed**
* **Action** — where you'd download the finished file, or cancel a pending request
A report that covers every QR Code on a large account can take a long while to build. Narrowing the entity selection returns a file sooner.
Requesting the **Reports** flow from the [Analytics dashboard](/analytics/dashboard)'s own "selection too large to display" prompt (its **Create Report** button) navigates here (to `/analytics/reports?create=1`) and opens this same Create Report drawer — but your entity selection and date range are **not** carried over. The drawer opens with an empty entity picker and defaults to the last 7 days, so you'll need to re-select everything.
## Where to go next
* [Analytics dashboard](/analytics/dashboard) — the live dashboard this data is drawn from, with instant on-screen exports
* [Email reports](/analytics/email-reports) — for reports delivered automatically on a recurring schedule instead of generated on demand
## Related
* [Analytics dashboard](/analytics/dashboard) — the live view behind every report generated here
* [Email reports](/analytics/email-reports) — the recurring counterpart to this on-demand report
* [Downloading end-user & RSVP data](/leads/downloading-data) — a separate, differently-behaved export mechanism for lead, form, and RSVP data
# How scans are counted
Source: https://docs.scanova.io/analytics/scan-counting
What counts as one scan when a QR Code has a password, age, lead, form, or location gate — and how the data collected through a gate relates to that scan.
A scan is recorded the moment someone opens your QR Code's page or short link — once per visit, regardless of what happens after that. This page explains how that holds true when a QR Code also has a password, age restriction, lead form, linked form, or GPS location gate turned on.
## One scan per visit, gate or no gate
Opening a QR Code that has no gates at all records exactly one scan. A QR Code with one or more gates configured works the same way: the scan is recorded as soon as the gate is shown, and that single scan carries through to whatever the visitor does next.
This means:
* **A gate that's never completed still counts as a scan.** If a visitor sees your password prompt, age check, or lead form and closes the tab without finishing it, that's still one real scan of your QR Code — the same way it would be if there were no gate at all.
* **Multiple gates on one QR Code don't multiply the count.** A QR Code with, say, both an age restriction and a lead form shows both gates in sequence, but the whole sequence — from the first gate to the final page — is one visit, one scan.
* **Data collected through a gate belongs to that same scan.** An age gate's date-of-birth answer, a GPS location gate's coordinates, and a linked form or lead capture's responses are all recorded against the visit that triggered them — they show up together in your [Analytics dashboard](/analytics/dashboard) and [Export Scan Data](/analytics/dashboard#overview-stat-cards), not as separate, disconnected entries.
## What does count as a new scan
A genuinely new page load counts as a new scan — for example, a visitor scanning the same QR Code again later, or opening the link fresh in a new session. This is the same behavior as a QR Code with no gates at all: each real visit is its own scan, which is what **Total Scans** measures.
## Repeat-visibility settings are per QR Code
If you've linked the same form to more than one QR Code (see [Skip button & form gates](/forms/skip-button-and-gates#form-gates-linking-a-form-to-a-qr-code)), each QR Code's own **User-Based Display Rule** setting — for example, hiding the form after someone has already seen or submitted it — is tracked independently per QR Code. A visitor completing or dismissing the form through one QR Code doesn't affect whether the same form still shows on a different QR Code, even though they share the same form.
## Related
* [Analytics dashboard](/analytics/dashboard) — where Total Scans, Unique Scans, and the rest of your scan data live
* [Bot scans](/analytics/bot-scans) — a different kind of scan-counting nuance: automated traffic, not real visitors
* [Skip button & form gates](/forms/skip-button-and-gates) — setting up a password, age, lead, or form gate on a QR Code
# Google login
Source: https://docs.scanova.io/authentication/google-login
Sign in to Scanova with a Google account, how it links to an existing Scanova login, and what happens if the email addresses do not happen to match up.
Scanova lets you sign in or create an account using your Google account instead of a password. Google shows up in two places: as a **sign-in/registration option**, and as a way to **re-confirm your identity** for sensitive changes on accounts that don't have a password set.
## Signing in or registering with Google
**Continue with Google** appears at the top of both the login page (`/auth/login`) and the registration page (`/auth/register`), above the email/password fields.
Selecting **Continue with Google** opens Google's standard account picker/consent flow. If you don't already have a Scanova account, one is created automatically using your Google account's email address; if you do, you're signed straight in.
There's no separate "connect your Google account" toggle in Account settings — Google sign-in isn't something you link to an existing password-based account after the fact. It's either how you originally signed up or logged in, or it's used for the re-authentication described below.
## Re-authenticating with Google
Certain sensitive actions — changing your MFA methods, changing your email, or deleting your account — ask you to confirm your identity first. If your account has a password, that confirmation is a password field. If your account has **no password** (because you signed up or have only ever signed in with Google), Scanova shows a **Continue with Google** button in its place, so you can confirm via Google instead of a password you never set.
This applies in:
* The [Manage MFA drawer](/authentication/multi-factor-authentication) — before adding, changing the default, or removing a method
* The change-email flow in Account settings
* The delete-account flow in Account settings
## Related
* [Multi-factor authentication](/authentication/multi-factor-authentication) — where Google re-authentication appears for password-less accounts
* [Single Sign-On](/authentication/sso) — a separate, organization-wide sign-in option distinct from an individual Google account
# Multi-factor authentication
Source: https://docs.scanova.io/authentication/multi-factor-authentication
Add a second step to Scanova sign in, with an authenticator app or email codes, plus recovery codes to get back in if you ever lose your second factor.
Multi-factor authentication (MFA) adds a second verification step to sign-in, on top of your password. Scanova supports three methods — **email one-time codes**, an **authenticator app** (TOTP), and **passkeys** — and all three live inside a single **Manage MFA** drawer. You can enable more than one method, pick a default, and generate backup codes in case you lose access to your usual method.
MFA is only available for accounts that sign in with a password. If your organization signs in through [SSO](/authentication/sso), your identity provider handles authentication instead, and the MFA option doesn't appear in your account settings.
## Opening Manage MFA
Go to **Account** (your avatar menu, or `/account`) and find the **Login & Security** section on the **Account** tab. The **Multi-Factor Authentication** field shows your current status (**Off**, or **On · \{method}**) with an edit icon that opens the drawer.
## Choosing a method
The first time you open the drawer, you're asked how you'd like to get your verification code:
* **Email** — a one-time code is sent to your registered email address at sign-in
* **Authenticator App** — codes are generated by an app like Google Authenticator (any TOTP-compatible app works)
* **Passkey** — your device's fingerprint, face unlock, or screen lock. See [Passkeys](/authentication/passkeys) for a dedicated walkthrough of this method.
If your browser reports that your device supports a built-in (platform) authenticator, Passkey is listed first and flagged **Recommended for this device**.
Whichever method you pick, you're first asked to re-enter your password to confirm it's you:
If your account has no password (for example, you only ever signed in with Google), this step shows a **Continue with Google** button instead, so you can confirm your identity that way.
## Setting up an authenticator app
Enter your current password and select **Next**.
Scan the QR Code with an app like Google Authenticator, or select the manual-entry key if you can't scan.
Select **Next** once the account appears in your app.
Your authenticator app now shows a 6-digit code that refreshes periodically. Enter it and select **Verify**.
On success you're shown your [backup codes](#saving-backup-codes) — the app is now an active MFA method on your account.
## Email one-time codes
Choosing **Email** skips the app-setup step: after the password check, Scanova sends a 6-digit code to your registered email address and shows the same OTP-entry screen, with a countdown before you can request another code.
## Passkeys
Passkeys use your device's built-in biometrics or screen lock instead of a code. Setup and sign-in both work a little differently from the code-based methods above — see [Passkeys](/authentication/passkeys) for the full walkthrough.
## Adding another method
Once you have at least one method enrolled, the drawer's main view lists it with a **Default** badge and a delete (trash) icon, and the footer button changes to **Add another method**:
Selecting **Add another method** re-opens the method picker — now only showing methods you haven't already enrolled, with copy adjusted to reflect that you already have MFA set up:
If you enroll more than one method, each row gets a **Make default** button (unless it's already the default). Your default method is the one Scanova prompts for first at sign-in — you can still fall back to any other enrolled method or a backup code.
To remove a method, select its trash icon, confirm your password (or re-authenticate with Google), and it's removed immediately.
## Saving backup codes
After enrolling your first MFA method, Scanova generates a set of single-use recovery codes. Save them somewhere safe — each can be used once to sign in if you lose access to your regular MFA method, and they're never shown again after this step.
## Signing in with MFA
Once MFA is enabled, signing in with your password takes you to a security-check screen for your default method instead of straight into your account. For example, with a passkey as the default:
Check **Remember this device for 2 weeks** to skip the MFA challenge on that browser for the next two weeks. If you can't use your default method, select **Use backup code** to enter one of the recovery codes you saved earlier:
## Related
* [Passkeys](/authentication/passkeys) — a closer look at the passkey method
* [Google login](/authentication/google-login) — how Google sign-in is used both for login and for re-confirming your identity on password-less accounts
* [Password reset](/authentication/password-reset) — recovering access if you've forgotten your password (separate from MFA recovery codes)
# Passkeys
Source: https://docs.scanova.io/authentication/passkeys
Sign in to Scanova with a passkey instead of a password, using the fingerprint, face, or device PIN your phone or laptop already uses to unlock itself.
A passkey lets you complete sign-in with your device's fingerprint, face unlock, or screen lock instead of typing a code. It's one of three methods available inside the same **Manage MFA** drawer — alongside email one-time codes and an authenticator app.
This page covers the passkey method specifically. For email and authenticator-app setup, how to set a default method, and backup codes, see [Multi-factor authentication](/authentication/multi-factor-authentication).
## Adding a passkey
Open **Account** → **Login & Security** → the edit icon on **Multi-Factor Authentication**, then choose **Passkey** from the method list (it's listed first, flagged **Recommended for this device**, if your browser detects a built-in authenticator).
Enter your current password to verify it's you before creating the passkey.
Your browser opens its native passkey-creation dialog (Touch ID, Windows Hello, a security key, or your phone's screen lock, depending on your device and browser). This step happens outside the Scanova UI — it's controlled by your operating system or browser.
Once your device confirms the passkey, Scanova asks you to give it a recognizable name (useful if you register passkeys from more than one device).
Select **Save** to finish. You'll then see your [backup codes](/authentication/multi-factor-authentication#saving-backup-codes) if this is your first MFA method.
## Signing in with a passkey
If a passkey is your default MFA method, signing in with your password takes you to a **Security check** screen prompting you to use it:
Select **Sign in with your passkey** to trigger your device's native prompt. Check **Remember this device for 2 weeks** first if you don't want to repeat this step on the same browser for a while. If your passkey isn't available (different device, lost hardware key, and so on), select **Use backup code** to fall back to one of your saved recovery codes.
## Removing a passkey
From the MFA status view, select the trash icon next to the passkey, confirm your password (or re-authenticate with Google on password-less accounts), and it's removed immediately. If it was your only MFA method, MFA is turned off for your account.
## Related
* [Multi-factor authentication](/authentication/multi-factor-authentication) — the full MFA drawer, including email codes, authenticator apps, default methods, and backup codes
* [Google login](/authentication/google-login) — how Google sign-in stands in for your password when your account has none set
# Password reset
Source: https://docs.scanova.io/authentication/password-reset
Reset a forgotten Scanova password from the login screen, what the reset email contains, and what to do if that message never actually turns up for you.
If you've forgotten your password, you can reset it from the login page using a one-time code sent to your email — no need to contact support.
On the login page, select **Forgot password?** next to the password field. This takes you to `/auth/forgot-password`.
Enter your account's email address and select **Send code**.
Scanova emails a 6-digit one-time code to that address. Enter it on the next screen.
If the code doesn't arrive, wait for the countdown next to the code field and select resend. Select **Verify Account** once you've entered the code.
After the code is verified, you're taken to a final screen to choose and confirm a new password for your account.
Resetting your password this way doesn't affect any [multi-factor authentication](/authentication/multi-factor-authentication) methods or [passkeys](/authentication/passkeys) on your account — those stay enrolled, and you'll still be asked for them at your next sign-in if enabled.
## A note on links vs. codes
Scanova also has a separate, direct-link password reset route (`/auth/password/reset/confirm/…`) tied to a reset link rather than a typed code. The **Forgot password?** flow described above — email address, then a 6-digit code — is the one you'll reach from the login page.
## Related
* [Multi-factor authentication](/authentication/multi-factor-authentication) — recovery codes for MFA are separate from this password-reset flow
* [Google login](/authentication/google-login) — if you originally signed up with Google and have no password, use **Continue with Google** to sign in instead of resetting a password
# Single Sign-On (SSO)
Source: https://docs.scanova.io/authentication/sso
Sign in to Scanova through your organisation's own identity provider, so access follows the accounts your IT team already manages centrally for everyone.
Single Sign-On (SSO) lets your team sign in to Scanova through your organization's own identity provider (Okta, Azure AD, Google Workspace, and others via SAML or OIDC) instead of a Scanova-specific password.
Setting up SSO for an organization is done by Scanova's team, not by customers themselves — there's no self-service SSO configuration screen in the app today. If you'd like SSO enabled for your organization, [contact support](https://support.scanova.io) with your identity provider details. This page covers the sign-in experience once that setup is complete.
## Signing in with SSO
From the login page, select **Continue with SSO** (below the email/password form) to go to the SSO sign-in screen at `/auth/sso`.
Type the email address or domain your organization uses with Scanova (for example, `you@company.com`).
Select **Continue with SSO**. Scanova redirects you to your organization's identity provider to complete authentication there.
Once your identity provider confirms your identity, you're redirected back to Scanova and signed in.
## Accounts using SSO
If your account signs in via SSO, a few things work differently elsewhere in Scanova:
* **No password** — there's nothing to change under Account → Login & Security, since your identity provider handles authentication
* **No MFA settings** — the [multi-factor authentication](/authentication/multi-factor-authentication) option is hidden entirely, since your identity provider is responsible for any additional verification it requires
## Related
* [Multi-factor authentication](/authentication/multi-factor-authentication) — MFA setup for password-based accounts (not applicable to SSO accounts)
* [Google login](/authentication/google-login) — a separate sign-in option, distinct from organization-wide SSO
# Building a form
Source: https://docs.scanova.io/forms/building-a-form
How to create a form in Scanova: the six starting templates, all 16 question types, the block picker, and the General and Submit settings tabs in the builder.
Forms let you collect information from the people who scan your QR Code or visit your landing page. You build a form the same way you build a landing page — a block-based canvas with a live mobile preview — but a form's canvas is limited to question blocks plus a header, a submit button, and an optional skip button.
## Navigating to Forms
From the left sidebar, click **Forms**. This opens your forms list, showing every form's name, response count, linked QR Codes, and creation date.
The sidebar also shows a **Lead Lists** item under Forms, tagged **Legacy**. Lead Lists is the deprecated predecessor to Forms — it still works, but new forms should use the Forms builder described here.
## Creating a form
From the forms list, click **New Form** in the top right.
Pick one of six templates:
* **Blank form** — start from an empty canvas
* **Contact form** — Full Name, Email, and Message
* **Feedback survey**
* **Customer satisfaction**
* **Event registration**
* **Lead capture**
The Contact form template's three questions are **Full Name**, **Email**, and **Message** only — there is no phone field. If you need to collect a phone number, add a **Phone Number** question block yourself.
Selecting a template opens the form builder immediately, with the template's blocks already on the canvas.
Every form — regardless of template — is built from the same six-slot shape: a **Form Header**, one or more question blocks, a **Submit Button**, and an optional **Skip Button**. See [Skip button & form gates](/forms/skip-button-and-gates) for how Skip actually works.
## Adding and reordering questions
* Click **Add** above the BLOCKS list to open the question picker
* Drag the handle on the left of any block in the BLOCKS list to reorder it
* Click a block to select it and edit its Content/Design fields in the right panel
* Select a block and click its trash icon to delete it
The picker groups blocks into four tabs — **All**, **Actions**, **Questions**, and **Content** — and reports how many are still available to add (form-level blocks like Form Header, Submit Button, and Skip Button can only be added once each, so they drop out of the count once used).
## Question types
The Questions tab offers 16 question types:
| Type | When to use |
| - | - |
| **Short Answer** | Single-line text (name, city, code) |
| **Paragraph** | Multi-line free text (feedback, comments) |
| **Email** | Validated email address |
| **Phone Number** | Phone number input |
| **Number** | Numeric input with optional min/max |
| **Multiple Choice** | Single selection from a list of options |
| **Checkboxes** | Multiple selections from a list of options |
| **Dropdown** | Selection from a dropdown menu |
| **Linear Scale** | Rating on a numeric scale (e.g. 1–5) |
| **Date** | Date picker |
| **Time** | Time picker |
| **Star Rating** | Rating using star icons |
| **Emoji Rating** | Rating using emoji faces |
| **Like / Dislike** | Thumbs up or thumbs down rating |
| **File Upload** | Respondent uploads a file |
| **Signature** | Digital signature pad |
The **Content** tab has one additional non-question block, **Description** — a rich-text paragraph with formatting, links, and images, for adding instructions or context between questions.
## Form settings
The right panel has two tabs at the form level: **General** and **Submit**.
### General
| Setting | What it does |
| - | - |
| **Form name** | Internal label shown in the dashboard's forms list — not shown to respondents |
### Submit
Configure **After submission** behavior (show a message or redirect to a URL) and a **Send responses to email** address for notifications.
The Submit and Skip button labels are **not** set here. Select the button block on the canvas to change its label and style, and add or remove the Skip button from the block picker.
There is no separate "Skip" tab in form settings. Skip is a canvas block, not a settings toggle — see [Skip button & form gates](/forms/skip-button-and-gates).
## Saving changes
Changes auto-save as you edit — the header shows a live **Saving…**/**Saved** indicator. The primary header button reads **Create Form** before the form's first save, and updates to **Update Form** afterward.
## Linking a form to a QR Code
A form only starts collecting responses once it's linked to a QR Code. Open the QR Code's **Settings** tab and turn on **Capture Leads**, then choose **Capture using Forms** and link your form. The [Skip button & form gates](/forms/skip-button-and-gates) page covers this flow — including the display style, timing, and expiry rules — in detail.
## Where to go next
* [Skip button & form gates](/forms/skip-button-and-gates) — configuring the Skip button and how a linked form gates your landing page
* [Viewing & exporting responses](/forms/responses) — the response viewer, analytics, and export
## Related
* [Viewing & exporting responses](/forms/responses) — where a form's submissions end up once it's collecting data
* [Skip button & form gates](/forms/skip-button-and-gates) — linking this form to a QR Code and configuring how it's gated
* [Page Builder overview](/page-builder/overview) — the block-based landing page editor this form builder is modeled on
# Viewing & exporting responses
Source: https://docs.scanova.io/forms/responses
How to read a form's responses in Scanova: the Overview analytics tab, the filterable response table, the linked QR Codes list, and exporting submissions.
## Opening a form's responses
From the forms list, click a form's name (or its response count) to open the form's detail view. It has three tabs: **Overview**, **Responses**, and **Linked QR Codes**.
## Overview tab
The Overview tab is an analytics dashboard for the form, scoped to a selectable date range (**7d** / **30d** / **90d** / **1y**):
* **Total Entries**, **Completed**, **Skipped**, and **Completion Rate** stat cards, each with a period-over-period trend indicator
* A **Responses over time** chart — daily submission count for the selected period
* A **Responses by Source** breakdown
The side panel also shows the form's ID, its public **Form URL**, total entries, number of linked QR Codes, and creation/modification dates, plus **Export** and **Edit Form** buttons.
## Responses tab
The Responses tab lists every submission for the form in a data table, with columns for:
* **Submitted** — the response's submission date/time
* **QR Code** — which linked QR Code the response came in through (or "Direct" if the form was filled without a QR Code in between)
* **Response** — a truncated preview of the answers, or a **Skipped** badge if the respondent used the Skip button instead of submitting
* Row actions — a button to view the full response in a detail sheet, and a dropdown to delete the response
A **Search responses** box and a **Date** filter sit above the table. The Date filter opens a range calendar with quick presets (**Today**, **Yesterday**, **This Week**, **Last Week**, **This Month**, **Last Month**) plus a two-month calendar view, with **Clear** and **Apply** buttons, for narrowing the table to a specific window.
Before any responses have been collected, the table shows an empty state instead of column headers:
Deleting a response asks for confirmation first ("This cannot be undone. The submission data will be permanently removed.") and immediately refreshes the table and the Overview tab's stat cards.
## Linked QR Codes tab
This tab lists every QR Code currently configured to use the form, with each row showing the QR Code's name, ID, short URL, active/inactive status, and **Manage** / **Unlink** actions. A **Link QR Code** button lets you attach the form to another QR Code without leaving the responses view.
See [Skip button & form gates](/forms/skip-button-and-gates) for how a form actually gets linked to a QR Code, including display style and timing.
## Exporting responses
Click **Export** (available from any tab, in the side panel) to trigger an export of the form's responses. The button shows an **Exporting…** state while the request is in flight.
## Where to go next
* [Building a form](/forms/building-a-form) — templates, question types, and form settings
* [Skip button & form gates](/forms/skip-button-and-gates) — linking a form to a QR Code and configuring how it's gated
## Related
* [Building a form](/forms/building-a-form) — templates, question types, and form settings
* [Skip button & form gates](/forms/skip-button-and-gates) — linking a form to a QR Code and configuring how it's gated
* [Analytics dashboard](/analytics/dashboard) — scan-level analytics alongside this form-level Overview tab
# Skip button & form gates
Source: https://docs.scanova.io/forms/skip-button-and-gates
How the Skip button works, and how to gate a landing page with a linked form: display style, display timing, expiry rules, and per-visitor display settings.
## The Skip button
**Skip Button** is one of the non-question blocks available from the **Add a block** picker (alongside Form Header and Submit Button). Like those two, it can only be added once per form.
* The **Contact form** template ships with a Skip Button block already on the canvas by default
* The **Blank form** template does not include one — add it yourself from the block picker if you want it
* To remove Skip entirely, select the block in the BLOCKS list and delete it, the same way you'd delete a question block
Selecting the block shows its Content tab with three fields — **Button label** (default: "Skip"), **Icon**, and **Icon position** — plus a Design tab for styling it.
Respondents who tap Skip move past the form without submitting an answer — their attempt is recorded as **Skipped** in the [response viewer](/forms/responses) rather than counted as a completed response.
## Form gates: linking a form to a QR Code
A form only starts collecting responses once it's linked to a QR Code, and that link is configured from the QR Code's own settings — not from the form builder.
Open a QR Code and go to its **Settings** tab. Under **Tracking & Analytics**, turn on **Capture Leads**.
Two options appear: **Capture using Forms** (marked *Recommended*) and **Use Existing Lead List** (marked deprecated, with a note suggesting migration to Forms).
With **Capture using Forms** selected, click **Link Form…**. This opens the **Link Form** panel, where you choose which form to attach and how it's gated.
Pick how the form appears over your landing page:
* **Page Takeover** — the form replaces the whole page
* **Modal Popup** — the form appears in a centered dialog over the page
* **Slide-in Panel** — the form slides in from the edge of the screen
**Modal Popup and Slide-in Panel need a Scanova Landing Page.** On a redirect-only QR Code — a Website URL Quick Code, for instance — both are disabled and only **Page Takeover** is available. The panel says so inline when it applies.
Under **Display Timing**, choose **Show Immediately** (the form appears right after the QR Code is scanned) or **Time Delay** (show the form after a delay, in seconds).
**Time Delay also needs a Scanova Landing Page**, since there is no page to delay on a redirect-only QR Code. It is disabled in that case, leaving **Show Immediately**.
Expand **Advanced Settings** for three additional gates, each behind its own toggle:
* **Form Expiry Rules** — stop showing the form to everyone once a limit is reached. Choose **Expiry by lead count** (a numeric threshold) or **Expiry by date & time**.
* **User-Based Display Rule** — control repeat visibility per visitor (only one option can be active at a time)
* **Match QR Code Theme** — show the form using the QR Code's own theme instead of a default style
Click **Link Form** at the bottom of the panel. (Re-opening the link later shows the same options under the heading **Manage Form Settings**, with a **Save Changes** button.) The QR Code's **Capture Leads** section collapses back to show the link as **Active**, and the form's own **Linked QR Codes** tab (see [Viewing & exporting responses](/forms/responses)) lists the QR Code you just linked.
A form can be linked to more than one QR Code. Manage all of a form's links from its own **Linked QR Codes** tab, or unlink a single QR Code from that QR Code's own **Capture Leads** settings.
## Where to go next
* [Building a form](/forms/building-a-form) — templates, question types, and form settings
* [Viewing & exporting responses](/forms/responses) — the response viewer, analytics, and export
## Related
* [Building a form](/forms/building-a-form) — templates, question types, and form settings
* [Viewing & exporting responses](/forms/responses) — where Skipped attempts and completed submissions show up
* [Leads & lead lists](/leads/lead-lists) — the deprecated "Use Existing Lead List" alternative to Capture using Forms
* [How scans are counted](/analytics/scan-counting) — how a form gate affects scan counting, and how User-Based Display Rule behaves when a form is linked to more than one QR Code
# Dashboard overview
Source: https://docs.scanova.io/getting-started/dashboard-overview
A tour of the Scanova Overview dashboard: the scan chart, stat cards, top and least performing QR Codes, usage limits, and how the sidebar is organized.
After logging in, you land on **Overview** (`/overview`) — a summary of your account's QR Code activity plus quick links into the rest of the app.
## What's on the dashboard
The page greets you by name ("Good evening, \{name}") and is built from several panels:
* **QR Code Scans** — a scan-trend chart with 7/14/30-day toggles, plus total scans and daily average
* **QR Codes Created**, **Forms Created**, **Form Responses** — stat cards for the same date range, each with a trend indicator (e.g. "▲ 100% in the last 7 days")
* **Top Performing QR Codes** / **Least Performing QR Codes** — your best- and worst-scanning codes for the selected period
* **Usage & Limits** — your plan tier (e.g. "Pro") and consumption against its limits: number of QR Codes/pages, users, and custom domains — with **View Plan Details** and **Upgrade Plan** buttons
* **Explore Advanced Features** — short pointers to features you may not have used yet, such as Tracking Parameters, AI-generated QR Code Design, and Conditional Redirection, each linking out via **View all**
* **Recent Activity** — a running log of what's changed (e.g. "Website URL QR Code 'my-qr' created — by \{user}"), filterable by **All** / **QR Codes** / **Reports**
* **Learn & Grow** — links into the Help Center on topics like QR Code analytics, customization, and printing
## The sidebar
The left sidebar is where you navigate the rest of the app. It's split into two groups:
**Platform**
* **Overview** — this dashboard
* **QR Codes** — every QR Code you've created, with a **Trash** for recently deleted ones
* **Analytics** — scan-level reporting
* **Forms** — standalone forms, plus a legacy **Lead List** view
* **Bulk Operations** — batch workflows
* **Integrations** — connect design, marketing, and automation tools such as Canva, Google Analytics, Zapier, and Slack to your QR Codes
**Developer**
* **API** — API keys and usage
* **Documentation** — this documentation site, marked with an external-link icon
The **Create QR Code** button sits at the top-right of the page header rather than in the sidebar, and it appears on **Overview** and **QR Codes** only. It's the fastest way into the QR Code creation hub described in [Creating your first QR Code](/getting-started/first-qr-code). From any other section, go to `/qr/create` directly.
Below the main navigation are account-level links: **User Management** and **Settings**, plus **Refer & Earn** when your billing address is outside India. Your account name and email are shown at the very bottom, with a menu holding **Account**, **Subscription**, **Support**, **Book a Demo**, and **Log out**.
## The top bar
Above the page content, the top bar has a sidebar-toggle button on the left, and on the right: a notification bell, a grid-style icon, a dark/light theme toggle, and a help icon.
## Where to go next
* [Creating your first QR Code](/getting-started/first-qr-code) — a full walkthrough of publishing your first QR Code
* [Quick Code vs. full Page](/qr-codes/create/quick-code-vs-page) — how to choose between a direct-action code and a landing page
If you're building against the API instead of clicking through the dashboard, start with [Create QR Code](/api-reference/management-api/qr/create).
## Related
* [What is Scanova?](/getting-started/what-is-scanova) — a platform-level overview of the sections summarized here
* [Website URL QR Code](/qr-codes/create/website-url) — the simplest Quick Code type, a good next step from Overview
* [Folders, tags & bulk operations](/qr-codes/manage/folders-tags-bulk-operations) — organizing the QR Codes that show up in Recent Activity and the QR Codes list
# Creating your first QR Code
Source: https://docs.scanova.io/getting-started/first-qr-code
A complete walkthrough of your first Quick Code: Log In, pick the Website URL type, fill in the destination URL, and publish a scannable Dynamic QR Code.
This walkthrough creates the simplest possible QR Code — a **Quick Code** that redirects to a website URL — from a fresh login to a published, scannable code.
Go to your Scanova login page and Log In with your email and password (or Google/SSO). You land on the **Overview** dashboard at `/overview`.
Click **Create QR Code** at the top-right of the page header. It appears on **Overview** and **QR Codes** only, so from any other section navigate to `/qr/create` instead. This hub presents three starting points as cards, left to right:
* **Quick Code** — a direct QR Code for a URL, phone number, email, Wi-Fi, or plain text, with no landing page
* **Build a Page** — the block-based landing page builder, starting from a template or blank canvas
* **AI Studio** — describe a page and let AI generate it (see [AI QR / AI Studio](/qr-codes/create/ai-qr); this card shows **Upgrade** rather than **Start** on plans that don't include it)
For this walkthrough, click **Start** on the **Quick Code** card.
Quick Code opens a grid of direct-action types — **Website URL**, **Google Maps**, **App Store**, **App Deep Link**, and **GS1** support Dynamic QR Codes, while **Phone Call**, **Email**, **Text Message**, **Wi-Fi**, **Plain Text**, **Event Calendar**, and **Contact Card** are Static-only.
Click **Configure** under **Website URL**. This opens the form at `/qr/quick/url`.
Enter the destination address in the **URL** field (the `https://` prefix is fixed, so type just the rest, e.g. `example.com`).
Leave **Dynamic** selected (it's the default and recommended) so the code stays editable and trackable after publishing. A **Short URL** slug is generated automatically — you can keep it or change it before publishing, since it can't be edited afterward.
Click **Publish** in the top-right. A drawer opens with a live preview of the QR Code and an editable name — rename it here if you want something more specific than the default. Click **Publish** again in the drawer to confirm.
A **Published** confirmation appears with the final QR Code, its short URL, and quick actions.
## What you get
The published code redirects instantly to the URL you entered — there's no landing page to design, since Quick Codes are direct-action. From the confirmation drawer you can download the QR Code image, copy or share its short URL, or open **Edit Design** to restyle the code's colors and frame. The code itself now appears in **QR Codes** in the sidebar, where you can search, tag, or move it into a folder.
If you'd rather build a branded landing page instead of a direct redirect, see [Quick Code vs. full Page](/qr-codes/create/quick-code-vs-page) for how to choose, and the [Page Builder overview](/page-builder/overview) for the block-based editor.
## Creating one via the API
You can create the same Website URL QR Code through the API using category ID `1` (labeled **Website URL**). See [Create QR Code](/api-reference/management-api/qr/create) for the full request schema.
## Related
* [Website URL QR Code](/qr-codes/create/website-url) — a closer look at the Quick Code type used in this walkthrough
* [Quick Code vs. full Page](/qr-codes/create/quick-code-vs-page) — how to choose between a direct-action code, a full Page, and AI Studio
* [Folders, tags & bulk operations](/qr-codes/manage/folders-tags-bulk-operations) — what to do next: organize the QR Code you just published
* [Dashboard overview](/getting-started/dashboard-overview) — a tour of the Overview page you land on after logging in
# Is Scanova free?
Source: https://docs.scanova.io/getting-started/is-scanova-free
Scanova is a paid product with a 14-day free trial, plus a separate free generator on the website that makes Static QR Codes without an account at all.
Scanova is a paid product, but there are two ways to use it without paying: a **14-day free trial** of the full dashboard, and a **free generator on the website** that makes Static QR Codes with no account at all.
Which one you want depends on whether you need to change or track the QR Code after you've made it.
## The 14-day free trial
Signing up gives you 14 days on the full dashboard, including up to **3 Dynamic QR Codes**.
A Dynamic QR Code is the reason to take the trial. It resolves through Scanova, which means you can:
* Change where it points after it's printed, without reprinting anything
* See every scan in [Analytics](/analytics/dashboard) — when, where, and on what device
* Apply [advanced settings](/qr-codes/manage/password-protection) such as password protection, scheduling, and conditional redirection
Static QR Codes aren't restricted during the trial, so you can make those too.
When the 14 days are up, your Dynamic QR Codes are deactivated but kept — see [When your trial or subscription ends](/billing/trial-and-expiry) for exactly what happens and how to bring them back.
## The free generator on scanova.io
If all you need is a QR Code that points somewhere fixed, you don't need an account. The generator on [scanova.io](https://scanova.io) creates Static QR Codes for free:
* No sign-up, and no time limit
* Downloads as a PNG
* Never expires, with no cap on scans
* Plain black and white, with no logo, colors, or frames
* The content is fixed at creation, so it can't be edited or tracked afterwards
That last point is the trade-off. A Static QR Code stores its content inside the code itself, so nothing about it can change once it's printed. If the destination might ever move, or you want to know how many people scanned it, you need a Dynamic QR Code — see [Website URL QR Code](/qr-codes/create/website-url) for how the two differ in practice.
Both kinds of QR Code are permanent in the sense that matters for printing: a Static QR Code works forever, and a Dynamic QR Code keeps working for as long as the subscription behind it is active.
## If you're in education
Teachers, students, and educational institutions can apply for a [free plan](/billing/education-plan) by crediting Scanova on their site. It takes an application rather than a sign-up, so it's slower to start than the trial.
## Choosing between them
| You want to | Use |
| :- | :- |
| Make a fixed QR Code once, for free | The free generator on [scanova.io](https://scanova.io) |
| Change a destination after printing | A Dynamic QR Code — start with the trial |
| Count and analyze scans | A Dynamic QR Code — start with the trial |
| Design, brand, or add a logo to a QR Code | The dashboard — start with the trial |
For what each paid plan includes, see [Plans and pricing](https://scanova.io/pricing).
## Related
* [What is Scanova?](/getting-started/what-is-scanova) — what the platform does and how the dashboard is laid out
* [Creating your first QR Code](/getting-started/first-qr-code) — the full creation flow once you have an account
* [When your trial or subscription ends](/billing/trial-and-expiry) — what happens to your QR Codes when a trial or subscription lapses
* [Plans and pricing](https://scanova.io/pricing) — what each plan costs and includes
# What is Scanova?
Source: https://docs.scanova.io/getting-started/what-is-scanova
An overview of the Scanova platform: how Dynamic QR Codes, the page builder, forms, and scan analytics fit together, and where each one lives in the dashboard.
## Overview
Scanova is a QR Code platform for creating, managing, and tracking QR Codes at scale. Every QR Code you create is one of two kinds:
* A **Quick Code** — a direct-action QR Code that does something the instant it's scanned (opens a URL, dials a number, joins a Wi-Fi network) with no landing page involved
* A **Page** — a QR Code that points to a landing page you build block-by-block (or generate with AI), for cases where scanning should show branded content rather than trigger an immediate action
Once logged in, this is what you land on:
## Who it's for
Scanova's own login page describes it as "the QR Code platform trusted by enterprise teams to power campaigns, packaging and payments — with analytics built in," and lists logos including Walmart, Cisco, Nestlé, Toyota, AT\&T, and PepsiCo. In practice the app serves both self-serve users on a Pro Plan and larger organizations that need multiple team members, custom domains, and API access — every plan tier uses the same dashboard and QR Code creation flow, with higher tiers adding AI Studio, more custom domains, and more team seats.
## The main sections
The dashboard's left sidebar is grouped into **Platform** and **Developer**:
* **Overview** — the landing dashboard shown above: scan trends, top/least performing QR Codes, usage against your plan's limits, recent activity, and **Learn & Grow** links to help articles
* **QR Codes** — the list of every QR Code you've created (Quick Codes and Pages alike), with search, filters, and a **Trash** for recently deleted codes
* **Analytics** — scan-level reporting for your QR Codes
* **Forms** — standalone forms you can build and link to QR Codes, separate from the page builder's own form blocks, plus a legacy **Lead List** view
* **Bulk Operations** — batch creation/management workflows
* **Integrations** — connect design, marketing, and automation tools such as Canva, Google Analytics, Zapier, and Slack to your QR Codes, and track the status of each connection
* **API** and **Documentation** — a link to your API keys/usage and to this documentation site, under the **Developer** group
Account-level areas sit below the main navigation: **User Management** and **Settings**. **Refer & Earn** also appears there when your billing address is outside India.
## Creating your first QR Code
To go from zero to a working QR Code, see [Creating your first QR Code](/getting-started/first-qr-code). If you're integrating against the API instead of the dashboard, create QR Codes with the [Create QR Code](/api-reference/management-api/qr/create) endpoint.
## Related
* [Creating your first QR Code](/getting-started/first-qr-code) — a full walkthrough of publishing your first QR Code
* [Dashboard overview](/getting-started/dashboard-overview) — a closer look at the Overview page and sidebar described above
* [Quick Code vs. full Page](/qr-codes/create/quick-code-vs-page) — the fundamental choice you make every time you create a QR Code
* [Page Builder overview](/page-builder/overview) — the block-based editor behind every landing-page QR Code
# Adobe
Source: https://docs.scanova.io/integrations/adobe
Generate and customise Scanova QR Codes inside Photoshop, InDesign, and Premiere Pro, so print and video artwork can carry a live code as you design it.
The Adobe integration is a set of Scanova plugins for Adobe Creative Cloud apps, distributed through Adobe Exchange — not an OAuth connection. Nothing syncs back into Scanova; it lets you generate a QR Code without leaving your Adobe workflow.
This is a **Design** category integration. It has no "Active connection" state and doesn't count against your integration connection quota — clicking through it in Scanova just opens the Adobe Exchange listing.
## Prerequisites
* A Scanova account (any plan)
* The Adobe Creative Cloud desktop app, with Photoshop, InDesign, or Premiere Pro installed
## Installing the plugin
In the Scanova dashboard, go to **Integrations** in the left sidebar, click **Add Integration**, then filter to the **Design** category. Click **Setup** on the **Adobe Creative Cloud** card to open its setup panel.
Click **Open in marketplace**. This opens a Scanova search on [Adobe Exchange](https://exchange.adobe.com/apps/browse/cc?listingType=plugins\&page=1\&product=All\&q=scanova\&sort=RELEVANCE) in a new tab.
Alternatively, install directly from the desktop app:
1. Open the **Creative Cloud Desktop** app and select the **Stock & Marketplace** tab.
2. Click the **Plugins** tab.
3. Search for **Scanova** to view the full suite of plugins.
4. Click **Install** on the plugin for your workflow (for example, Scanova QR Codes for Photoshop).
Open the installed plugin's panel from within Photoshop, InDesign, or Premiere Pro and sign in with your Scanova account to start generating QR Codes on the canvas or timeline.
## What it's for
Use this when your design work happens in Adobe's apps — packaging layouts in InDesign, ad creative in Photoshop, video overlays in Premiere Pro — and you want a Scanova QR Code placed directly into that file rather than exported and re-imported as a static image.
## Related
* [Canva](/integrations/canva) — the same design-time convenience, as a Canva app
* [Google Docs](/integrations/google-docs) — the same design-time convenience, as an add-on for Docs, Slides, and Sheets
* [Microsoft Office](/integrations/microsoft-office) — the same design-time convenience, as an add-in for Word, Excel, and PowerPoint
# Canva
Source: https://docs.scanova.io/integrations/canva
Create and place Scanova QR Codes inside your Canva designs with the Scanova app, so artwork and its code stay together while you are still designing.
The Canva integration is a Canva app — not an OAuth connection. It doesn't sync data back into Scanova; it lets you generate and drop a Scanova QR Code onto a Canva design without leaving Canva.
This is a **Design** category integration. It has no "Active connection" state and doesn't count against your integration connection quota — clicking through it in Scanova just opens the app's marketplace listing.
## Prerequisites
* A Scanova account (any plan — the catalog and setup panel are visible regardless of plan; only the data-sync integrations enforce a connection quota)
* A Canva account with permission to install apps for the team you design in
## Installing the app
In the Scanova dashboard, go to **Integrations** in the left sidebar, click **Add Integration**, then filter to the **Design** category. Click **Setup** on the **Canva** card to open its setup panel.
Click **Open in marketplace**. This opens Canva's official app listing in a new tab: [canva.com/apps/AAGuh5mzhTo/scanova-qr-codes](https://www.canva.com/apps/AAGuh5mzhTo/scanova-qr-codes?q=scanova).
From the Canva listing, click **Use in a design** (or **Install app**, depending on where you land). Canva adds "Scanova QR Codes" to your apps.
Open the app panel from inside any Canva design and sign in with your Scanova account. From there you can create a new QR Code or pick an existing one and place it on the canvas — no switching tabs.
## What it's for
Use this when your workflow already lives in Canva — social posts, flyers, packaging mockups — and you want a live Scanova QR Code as part of the layout instead of pasting in a static image. It's a design-time convenience, not a way to view scan analytics; for that, open the QR Code in Scanova directly.
Scanova also publishes a [Canva setup walkthrough](https://support.scanova.io/hc/en-us/articles/51478357817241-Integrate-with-Canva) in the Help Center, linked from the **Docs** tag on the Canva card in the catalog.
## Related
* [Adobe](/integrations/adobe) — the same design-time convenience, as a plugin suite for Photoshop, InDesign, and Premiere Pro
* [Google Docs](/integrations/google-docs) — the same design-time convenience, as an add-on for Docs, Slides, and Sheets
* [Microsoft Office](/integrations/microsoft-office) — the same design-time convenience, as an add-in for Word, Excel, and PowerPoint
# Google Ads
Source: https://docs.scanova.io/integrations/google-ads
Send scan data through to Google Ads so you can retarget the people who scanned your QR Codes, and measure the conversions that those scans eventually drive.
Connect a Google Ads Conversion ID to a Dynamic QR Code and Scanova serves the Google Ads tag on every scan, so scans start counting as conversions in your Google Ads account — no code changes on your side.
Google Ads is one of four "tracking ID" integrations that share the same setup form — Meta Pixel, Microsoft Ads, and LinkedIn work the same way. If you've connected one of those already, this page will feel familiar.
## How it works
Google Ads is a client-side integration: Scanova never sends your scan data to Google's servers itself. Instead, when someone scans a QR Code that has this integration attached, they briefly land on an intermediate Scanova page before being redirected to your destination URL. That page loads Google's own conversion-tracking tag, which runs in the visitor's browser and reports the event directly to Google Ads.
This has two practical consequences:
* **Scanova can't confirm delivery.** Because the tag fires from the visitor's browser and never calls back to Scanova, a connection here shows as **Connected** or **Disconnected** only — not the active/inactive/awaiting states you'll see on server-side integrations like [webhooks](/integrations/webhooks). There's no "last synced" timestamp to check.
* **Redirection timing matters.** If the destination redirect happens instantly, some mobile browsers and privacy-restricted browsers can block the tag before it finishes firing. Setup includes a **Redirection delay for tracking accuracy** toggle (on by default) to guard against this — see the advanced-settings step below.
## Prerequisites
* At least one **Dynamic QR Code** with content already generated. The QR Code picker in setup only lists Dynamic QR Codes — Static QR Codes and Dynamic QR Codes with no content yet don't have a redirect step to attach a tag to, so they don't appear.
* Your **Google Ads Conversion ID** — a 9- or 10-digit number, with or without the `AW-` prefix (e.g. `AW-123456789` or `123456789`). Find it in Google Ads under **Tools and Settings → Conversions**, or in Google Tag Manager's container settings if you manage tags that way.
Use a [custom domain](/qr-codes/manage/custom-domains) for QR Codes you're tracking with ad pixels. Conversion cookies are scoped per domain, so scans redirected through Scanova's shared default domain can mix audiences across different Scanova customers running the same kind of campaign. A custom domain keeps your conversion data isolated to your own traffic.
## Connecting Google Ads
Go to **Integrations** in the sidebar. If you already have other connections, select **Add Integration** to open the catalog; if the catalog is already showing, skip this. Find the **Google Ads** card under the **Marketing** category and select **Setup**.
This opens a dedicated setup page (not a drawer) — Google Ads is one of the setup kinds that gets a full page instead of a side panel.
In the **Enter Google Ads Conversion ID** field, paste your Conversion ID. Both `AW-123456789` and `123456789` are accepted; Scanova validates the format and shows an inline error if it doesn't match a valid Conversion ID before you can continue. A green tip below the field reinforces the custom-domain advice above: "Recommended: use a custom domain for reliable tracking. Cookies are stored per domain, so the default Scanova domain can mix audiences across campaigns."
Under **Add tracking code to**, choose:
* **Selected QR Codes** (the default) — pick specific Dynamic QR Codes from the searchable list that appears, or use its "select all" option to apply to your full current list
* **All QR Codes** — applies the tag to every Dynamic QR Code on the account, present and future
If you apply the tag to **All QR Codes** while a specific QR Code also has its own individual tracking-ID integration, only one of the two will actually fire — Scanova shows a note about this conflict when you pick **All QR Codes**: "Note: applying to All QR Codes while also integrating individual QR Codes can cause a conflict where only one tracking ID fires."
Open the **Advanced settings** section (open by default) to check **Redirection delay for tracking accuracy**. Leave it on unless you have a specific reason to redirect instantly — turning it off shows a warning that instant redirects can prevent the tag from firing on mobile or privacy-restricted browsers.
Select **Connect Google Ads**. On success you're returned to the **Integrations** connections list, where the new row shows up immediately.
## Managing a connection
Open any Google Ads row in the **Integrations** connections list to view its details — which QR Codes it's applied to, when it was created, and its **Connected**/**Disconnected** status. Select **Edit connection** to change the Conversion ID, scope, or redirection-delay setting; the same form pre-fills with the saved values. Deleting a connection (from the row's actions, or via bulk select) removes the tag from every QR Code it was applied to.
Because this is a client-side tag, "Disconnected" only means Scanova has stopped serving the tag — it doesn't reflect anything about whether Google is receiving data, which Scanova has no visibility into either way.
## Related
* [Meta Pixel](/integrations/meta-pixel) — the same tracking-ID setup form, for Facebook/Instagram conversions
* [Microsoft Ads](/integrations/microsoft-ads) — the same tracking-ID setup form, for Microsoft Advertising's UET tag
* [LinkedIn Ads](/integrations/linkedin) — the same tracking-ID setup form, for LinkedIn Campaign Manager
# Google Analytics (GA4)
Source: https://docs.scanova.io/integrations/google-analytics
Send QR Code scan data into Google Analytics so that scans appear alongside your website traffic and can be analysed with the reports you already use.
The Google Analytics integration sends your scan (or event-tracking) data to a Google Analytics 4 (GA4) property, so you can analyze QR Code engagement alongside the rest of your web/app analytics. Setup is a single form — Scanova doesn't sign in to your Google account directly; instead, you supply a GA4 Measurement ID and Measurement Protocol API secret that Scanova uses to send data server-side.
## Prerequisites
* A GA4 property
* The property's **Measurement ID** (format `G-XXXXXXXXXX`), found in GA4 under Admin → Data Streams → your stream
* A **Measurement Protocol API secret** for that same data stream, created under Admin → Data Streams → your stream → Measurement Protocol API secrets
## Setting up the connection
Google Analytics opens as its own full page, with a single setup form (no multi-step wizard).
From **Integrations**, select **Add Integration**, then find the **Google Analytics** card and select **Setup**.
Select **Scan Analytics Data** (the default) or **Event Tracking Data**, depending on what you want to appear in GA4.
Enter the ID in `G-XXXXXXXXXX` format, copied from your GA4 property's data stream.
Paste the Measurement Protocol API secret generated for that same data stream.
Select **Connect Google Analytics**. The connection is created and marked active immediately.
Editing an existing connection shows the saved Measurement ID but leaves the **Measurement API Secret** field blank — it isn't re-displayed for security, so you'll need to paste it again to save any change. This also means editing clears the connection's saved status so it's re-verified against GA4 on the next send, rather than continuing to show its previous status.
## What data flows where
Scanova sends the selected data type (scan events or tracked events) to the GA4 property identified by your Measurement ID, authenticated with your Measurement API secret. Once connected, this data appears in your GA4 reports alongside your other web/app traffic.
## Related
* [Google Ads](/integrations/google-ads) — another Google marketing integration, using a client-side conversion tag instead of the Measurement Protocol
* [Meta Pixel](/integrations/meta-pixel) — the equivalent client-side tracking integration for Facebook/Instagram
* [Webhooks](/integrations/webhooks) — a generic alternative for sending the same scan/event data to your own endpoint
# Google Docs
Source: https://docs.scanova.io/integrations/google-docs
Create Scanova QR Codes directly inside Google Docs, Slides, and Sheets with the Scanova add-on, without leaving the document you happen to be writing.
The Google Docs integration is the **Scanova QR Codes** add-on, distributed through Google Workspace Marketplace — not an OAuth connection. Nothing syncs back into Scanova; it lets you generate a QR Code without leaving Docs, Slides, or Sheets.
This is a **Design** category integration, labeled "Google Docs Add-on" in the catalog since the same add-on works across Docs, Slides, and Sheets. It has no "Active connection" state and doesn't count against your integration connection quota — clicking through it in Scanova just opens the Workspace Marketplace listing.
## Prerequisites
* A Scanova account (any plan)
* A Google account with permission to install Workspace add-ons (in a managed workspace, an admin may need to approve it first)
## Installing the add-on
In the Scanova dashboard, go to **Integrations** in the left sidebar, click **Add Integration**, then filter to the **Design** category. Click **Setup** on the **Google Docs Add-on** card to open its setup panel.
Click **Open in marketplace**. This opens the add-on's [Google Workspace Marketplace listing](https://workspace.google.com/marketplace/app/scanova_qr_codes/831759720554) in a new tab.
Alternatively, install it from inside any of the three apps:
1. Open Google Docs, Slides, or Sheets.
2. Go to **Extensions → Add-ons → Get add-ons**.
3. Search for **Scanova QR Codes** and click **Install**.
4. Open it from the **Extensions** menu.
With the add-on open in the sidebar, sign in with your Scanova account, then create or insert a QR Code straight into the document, slide, or spreadsheet you're working on.
## What it's for
Use this when a document, presentation, or spreadsheet is where the work already happens — a proposal, a slide deck, an inventory sheet — and you want a live Scanova QR Code embedded in it instead of pasting in a static image generated elsewhere.
## Related
* [Canva](/integrations/canva) — the same design-time convenience, as a Canva app
* [Adobe](/integrations/adobe) — the same design-time convenience, as a plugin suite for Photoshop, InDesign, and Premiere Pro
* [Microsoft Office](/integrations/microsoft-office) — the same design-time convenience, as an add-in for Word, Excel, and PowerPoint
# HubSpot
Source: https://docs.scanova.io/integrations/hubspot
Send QR Code form submissions and captured leads straight into HubSpot as contacts, companies, deals, or tickets, so sales can follow up without delay.
The HubSpot integration connects your Scanova account to a HubSpot portal via OAuth, then walks you through a guided wizard that maps a form or QR Code's submissions to a HubSpot CRM object — Contact, Company, Deal, or Ticket — so every submission becomes a HubSpot record automatically.
## Prerequisites
* A HubSpot account with permission to authorize new app connections for the portal
* A Scanova plan that includes the HubSpot integration. Without it, connecting returns a "Your plan does not include HubSpot integration" error.
* At least one Form-builder form or QR Code with a submission source (Custom Form, Feedback, RSVP, Real Estate inquiry, or Restaurant feedback) already set up, since the wizard needs something to sync from
## Setting up a connection
HubSpot opens as its own full page (not a side panel), because the wizard needs the room.
From **Integrations**, select **Add Integration**, then find the **HubSpot** card and select **Setup**.
Select **Connect HubSpot**. You're redirected to HubSpot to authorize Scanova, then returned to the wizard. A green banner shows the connected portal ID once authorization succeeds.
Pick the submission source type: **Forms** (any Form-builder form), **Custom Form Component**, **Feedback Form (Feedback QR Code)**, **RSVP Form Component**, **Inquiry Form (Real Estate QR Code)**, or **Feedback Form (Restaurant QR Code)**.
Depending on the type chosen, search for and select the specific form or QR Code whose submissions should sync.
Select which HubSpot object each submission should create: **Create Contact**, **Create Company**, **Create Deal**, or **Create Ticket**.
Match your source's fields to HubSpot properties. HubSpot's featured properties for the chosen object are pre-listed; use **Add a field** to map additional properties from the full catalog. Incompatible mappings are blocked inline — for example, a HubSpot number property can't be mapped to a text field, and a date property can't be mapped to a non-date field.
A mapping the wizard doesn't flag as invalid but that still can't be coerced to the HubSpot property's type is silently dropped at sync time rather than causing an error — the inline validation exists specifically to catch this before it happens.
Name the connection (used later to identify it in Active Connections), review the mapping summary, and optionally select **Send test** to push one sample record to HubSpot and confirm it arrives before selecting **Complete setup**.
## Managing the connection
* **Disconnect** (in the connected banner) stops syncing for the whole portal — every HubSpot connection tied to it stops sending data
* Editing an existing connection from **Active Connections** reopens the same 5-step wizard, prefilled
## What data flows where
Each time the configured form or QR Code receives a new submission, Scanova sends the mapped field values to HubSpot as a new record of the chosen object type (Contact, Company, Deal, or Ticket) in the connected portal.
## Related
* [Slack](/integrations/slack) — another OAuth-connected integration with its own named-rule builder
* [Webhooks](/integrations/webhooks) — a generic alternative if you need submission data somewhere HubSpot doesn't cover
* [Form management](/api-reference/management-api/forms/list) — the API behind the Forms sources this wizard syncs from
* [Lead list management](/api-reference/management-api/leads/list) — the API behind the legacy Lead List sync sources
# LinkedIn Ads
Source: https://docs.scanova.io/integrations/linkedin
Add the LinkedIn Insight Tag to your QR Codes so that the people who scan them can be retargeted and measured through LinkedIn Campaign Manager later.
Connect a LinkedIn Partner ID to a Dynamic QR Code and Scanova serves the LinkedIn Insight Tag on every scan, so scans start counting toward conversions and matched audiences in LinkedIn Campaign Manager.
LinkedIn is one of four "tracking ID" integrations that share the same setup form — Google Ads, Meta Pixel, and Microsoft Ads work the same way. If you've connected one of those already, this page will feel familiar.
## How it works
LinkedIn is a client-side integration: Scanova never sends your scan data to LinkedIn's servers itself. Instead, when someone scans a QR Code that has this integration attached, they briefly land on an intermediate Scanova page before being redirected to your destination URL. That page loads the LinkedIn Insight Tag, which runs in the visitor's browser and reports the event directly to LinkedIn.
This has two practical consequences:
* **Scanova can't confirm delivery.** Because the tag fires from the visitor's browser and never calls back to Scanova, a connection here shows as **Connected** or **Disconnected** only — not the active/inactive/awaiting states you'll see on server-side integrations like [webhooks](/integrations/webhooks). There's no "last synced" timestamp to check.
* **Redirection timing matters.** If the destination redirect happens instantly, some mobile browsers and privacy-restricted browsers can block the tag before it finishes firing. Setup includes a **Redirection delay for tracking accuracy** toggle (on by default) to guard against this — see the advanced-settings step below.
## Prerequisites
* At least one **Dynamic QR Code** with content already generated. The QR Code picker in setup only lists Dynamic QR Codes — Static QR Codes and Dynamic QR Codes with no content yet don't have a redirect step to attach a tag to, so they don't appear.
* Your **LinkedIn Partner ID** — a 4- to 12-digit number. Find it in LinkedIn Campaign Manager under **Account Assets → Insight Tag**.
Use a [custom domain](/qr-codes/manage/custom-domains) for QR Codes you're tracking with the LinkedIn Insight Tag. Conversion cookies are scoped per domain, so scans redirected through Scanova's shared default domain can mix audiences across different Scanova customers running the same kind of campaign. A custom domain keeps your conversion data isolated to your own traffic.
## Connecting LinkedIn
Go to **Integrations** in the sidebar. If you already have other connections, select **Add Integration** to open the catalog; if the catalog is already showing, skip this. Find the **LinkedIn** card under the **Marketing** category and select **Setup**.
This opens a dedicated setup page (not a drawer) — LinkedIn is one of the setup kinds that gets a full page instead of a side panel.
In the **Enter LinkedIn Partner ID** field, paste your 4- to 12-digit Partner ID (placeholder shows a 6-digit example). Scanova validates the format and shows an inline error if it doesn't match a valid Partner ID before you can continue. A green tip below the field reinforces the custom-domain advice above.
Under **Add tracking code to**, choose:
* **Selected QR Codes** (the default) — pick specific Dynamic QR Codes from the searchable list that appears, or use its "select all" option to apply to your full current list
* **All QR Codes** — applies the tag to every Dynamic QR Code on the account, present and future
If you apply the tag to **All QR Codes** while a specific QR Code also has its own individual tracking-ID integration, only one of the two will actually fire — Scanova shows a note about this conflict when you pick **All QR Codes**.
Open the **Advanced settings** section (open by default) to check **Redirection delay for tracking accuracy**. Leave it on unless you have a specific reason to redirect instantly — turning it off shows a warning that instant redirects can prevent the tag from firing on mobile or privacy-restricted browsers.
Select **Connect LinkedIn**. On success you're returned to the **Integrations** connections list, where the new row shows up immediately.
## Managing a connection
Open any LinkedIn row in the **Integrations** connections list to view its details — which QR Codes it's applied to, when it was created, and its **Connected**/**Disconnected** status. Select **Edit connection** to change the Partner ID, scope, or redirection-delay setting; the same form pre-fills with the saved values. Deleting a connection (from the row's actions, or via bulk select) removes the tag from every QR Code it was applied to.
Because this is a client-side tag, "Disconnected" only means Scanova has stopped serving it — it doesn't reflect anything about whether LinkedIn is receiving events, which Scanova has no visibility into either way.
## Related
* [Google Ads](/integrations/google-ads) — the same tracking-ID setup form, for Google Ads conversions
* [Meta Pixel](/integrations/meta-pixel) — the same tracking-ID setup form, for Facebook/Instagram conversions
* [Microsoft Ads](/integrations/microsoft-ads) — the same tracking-ID setup form, for Microsoft Advertising's UET tag
# Meta Pixel
Source: https://docs.scanova.io/integrations/meta-pixel
Add the Meta Pixel to your QR Codes so that scans can be retargeted across Facebook and Instagram, and conversions reported back into Events Manager too.
Connect a Meta Pixel ID to a Dynamic QR Code and Scanova serves the Meta Pixel on every scan, so scans start showing up as events in Meta Events Manager and can feed retargeting audiences and campaign optimization across Facebook and Instagram.
Meta Pixel is one of four "tracking ID" integrations that share the same setup form — Google Ads, Microsoft Ads, and LinkedIn work the same way. If you've connected one of those already, this page will feel familiar.
## How it works
Meta Pixel is a client-side integration: Scanova never sends your scan data to Meta's servers itself. Instead, when someone scans a QR Code that has this integration attached, they briefly land on an intermediate Scanova page before being redirected to your destination URL. That page loads the Meta Pixel script, which runs in the visitor's browser and reports the event directly to Meta.
This has two practical consequences:
* **Scanova can't confirm delivery.** Because the pixel fires from the visitor's browser and never calls back to Scanova, a connection here shows as **Connected** or **Disconnected** only — not the active/inactive/awaiting states you'll see on server-side integrations like [webhooks](/integrations/webhooks). There's no "last synced" timestamp to check.
* **Redirection timing matters.** If the destination redirect happens instantly, some mobile browsers and privacy-restricted browsers can block the pixel before it finishes firing. Setup includes a **Redirection delay for tracking accuracy** toggle (on by default) to guard against this — see the advanced-settings step below.
## Prerequisites
* At least one **Dynamic QR Code** with content already generated. The QR Code picker in setup only lists Dynamic QR Codes — Static QR Codes and Dynamic QR Codes with no content yet don't have a redirect step to attach a pixel to, so they don't appear.
* Your **Meta Pixel ID** — a 15- or 16-digit number. Find it in [Meta Events Manager](https://business.facebook.com/events_manager2) under **Data Sources → your pixel → Settings**.
Use a [custom domain](/qr-codes/manage/custom-domains) for QR Codes you're tracking with the Meta Pixel. Conversion cookies are scoped per domain, so scans redirected through Scanova's shared default domain can mix audiences across different Scanova customers running the same kind of campaign. A custom domain keeps your conversion data isolated to your own traffic.
## Connecting Meta Pixel
Go to **Integrations** in the sidebar. If you already have other connections, select **Add Integration** to open the catalog; if the catalog is already showing, skip this. Find the **Meta Pixel** card under the **Marketing** category and select **Setup**.
This opens a dedicated setup page (not a drawer) — Meta Pixel is one of the setup kinds that gets a full page instead of a side panel.
In the **Enter Meta Pixel ID** field, paste your 15- or 16-digit Pixel ID. Scanova validates the format and shows an inline error if it doesn't match a valid Pixel ID before you can continue. A green tip below the field reinforces the custom-domain advice above.
Under **Add tracking code to**, choose:
* **Selected QR Codes** (the default) — pick specific Dynamic QR Codes from the searchable list that appears, or use its "select all" option to apply to your full current list
* **All QR Codes** — applies the pixel to every Dynamic QR Code on the account, present and future
If you apply the pixel to **All QR Codes** while a specific QR Code also has its own individual tracking-ID integration, only one of the two will actually fire — Scanova shows a note about this conflict when you pick **All QR Codes**.
Open the **Advanced settings** section (open by default) to check **Redirection delay for tracking accuracy**. Leave it on unless you have a specific reason to redirect instantly — turning it off shows a warning that instant redirects can prevent the pixel from firing on mobile or privacy-restricted browsers.
Select **Connect Meta Pixel**. On success you're returned to the **Integrations** connections list, where the new row shows up immediately.
## Managing a connection
Open any Meta Pixel row in the **Integrations** connections list to view its details — which QR Codes it's applied to, when it was created, and its **Connected**/**Disconnected** status. Select **Edit connection** to change the Pixel ID, scope, or redirection-delay setting; the same form pre-fills with the saved values. Deleting a connection (from the row's actions, or via bulk select) removes the pixel from every QR Code it was applied to.
Because this is a client-side pixel, "Disconnected" only means Scanova has stopped serving it — it doesn't reflect anything about whether Meta is receiving events, which Scanova has no visibility into either way.
## Related
* [Google Ads](/integrations/google-ads) — the same tracking-ID setup form, for Google Ads conversions
* [Microsoft Ads](/integrations/microsoft-ads) — the same tracking-ID setup form, for Microsoft Advertising's UET tag
* [LinkedIn Ads](/integrations/linkedin) — the same tracking-ID setup form, for LinkedIn Campaign Manager
# Microsoft Ads
Source: https://docs.scanova.io/integrations/microsoft-ads
Add the Microsoft Advertising UET tag to your QR Codes so that scans build remarketing audiences and report conversions inside Microsoft Advertising too.
Connect a Microsoft Advertising UET (Universal Event Tracking) Tag ID to a Dynamic QR Code and Scanova serves the UET tag on every scan, so scans start counting toward conversions and remarketing audiences in Microsoft Ads.
In the app, this integration is labeled **Microsoft Ads (UET)** — the "UET" refers to Microsoft Advertising's Universal Event Tracking tag, the same mechanism as a Google Ads or Meta pixel. It's one of four "tracking ID" integrations that share the same setup form — Google Ads, Meta Pixel, and LinkedIn work the same way.
## How it works
Microsoft Ads (UET) is a client-side integration: Scanova never sends your scan data to Microsoft's servers itself. Instead, when someone scans a QR Code that has this integration attached, they briefly land on an intermediate Scanova page before being redirected to your destination URL. That page loads the UET tag, which runs in the visitor's browser and reports the event directly to Microsoft Advertising.
This has two practical consequences:
* **Scanova can't confirm delivery.** Because the tag fires from the visitor's browser and never calls back to Scanova, a connection here shows as **Connected** or **Disconnected** only — not the active/inactive/awaiting states you'll see on server-side integrations like [webhooks](/integrations/webhooks). There's no "last synced" timestamp to check.
* **Redirection timing matters.** If the destination redirect happens instantly, some mobile browsers and privacy-restricted browsers can block the tag before it finishes firing. Setup includes a **Redirection delay for tracking accuracy** toggle (on by default) to guard against this — see the advanced-settings step below.
## Prerequisites
* At least one **Dynamic QR Code** with content already generated. The QR Code picker in setup only lists Dynamic QR Codes — Static QR Codes and Dynamic QR Codes with no content yet don't have a redirect step to attach a tag to, so they don't appear.
* Your **UET Tag ID** — a 6- to 16-digit number. Find it in Microsoft Advertising under **Tools → UET Tags**.
Use a [custom domain](/qr-codes/manage/custom-domains) for QR Codes you're tracking with the UET tag. Conversion cookies are scoped per domain, so scans redirected through Scanova's shared default domain can mix audiences across different Scanova customers running the same kind of campaign. A custom domain keeps your conversion data isolated to your own traffic.
## Connecting Microsoft Ads
Go to **Integrations** in the sidebar. If you already have other connections, select **Add Integration** to open the catalog; if the catalog is already showing, skip this. Find the **Microsoft Ads (UET)** card under the **Marketing** category and select **Setup**.
This opens a dedicated setup page (not a drawer) — Microsoft Ads is one of the setup kinds that gets a full page instead of a side panel.
In the **Enter UET Tag ID** field, paste your 6- to 16-digit UET Tag ID. Scanova validates the format and shows an inline error if it doesn't match a valid Tag ID before you can continue. A green tip below the field reinforces the custom-domain advice above.
Under **Add tracking code to**, choose:
* **Selected QR Codes** (the default) — pick specific Dynamic QR Codes from the searchable list that appears, or use its "select all" option to apply to your full current list
* **All QR Codes** — applies the tag to every Dynamic QR Code on the account, present and future
If you apply the tag to **All QR Codes** while a specific QR Code also has its own individual tracking-ID integration, only one of the two will actually fire — Scanova shows a note about this conflict when you pick **All QR Codes**.
Open the **Advanced settings** section (open by default) to check **Redirection delay for tracking accuracy**. Leave it on unless you have a specific reason to redirect instantly — turning it off shows a warning that instant redirects can prevent the tag from firing on mobile or privacy-restricted browsers.
Select **Connect Microsoft Ads (UET)**. On success you're returned to the **Integrations** connections list, where the new row shows up immediately.
## Managing a connection
Open any Microsoft Ads row in the **Integrations** connections list to view its details — which QR Codes it's applied to, when it was created, and its **Connected**/**Disconnected** status. Select **Edit connection** to change the UET Tag ID, scope, or redirection-delay setting; the same form pre-fills with the saved values. Deleting a connection (from the row's actions, or via bulk select) removes the tag from every QR Code it was applied to.
Because this is a client-side tag, "Disconnected" only means Scanova has stopped serving it — it doesn't reflect anything about whether Microsoft Advertising is receiving events, which Scanova has no visibility into either way.
## Related
* [Google Ads](/integrations/google-ads) — the same tracking-ID setup form, for Google Ads conversions
* [Meta Pixel](/integrations/meta-pixel) — the same tracking-ID setup form, for Facebook/Instagram conversions
* [LinkedIn Ads](/integrations/linkedin) — the same tracking-ID setup form, for LinkedIn Campaign Manager
# Microsoft Office
Source: https://docs.scanova.io/integrations/microsoft-office
Insert Scanova QR Codes into Word, PowerPoint, and Excel documents using the Scanova add-in, so office files can carry a live code without any exporting.
The Microsoft Office integration is an Office add-in — not an OAuth connection. It doesn't sync data back into Scanova; it lets you generate and drop a Scanova QR Code into a Word, Excel, or PowerPoint document without leaving Office.
This is a **Design** category integration, same shape as Canva, Google Docs, and Adobe. It has no "Active connection" state and doesn't count against your integration connection quota — clicking through it in Scanova just opens the add-in's marketplace listing.
## Prerequisites
* A Scanova account (any plan — the catalog and setup panel are visible regardless of plan; only the data-sync integrations enforce a connection quota)
* A Microsoft 365 account with permission to install add-ins for the Office apps you use
## Installing the add-in
In the Scanova dashboard, go to **Integrations** in the left sidebar, click **Add Integration**, then filter to the **Design** category. Click **Setup** on the **Microsoft Office** card to open its setup panel.
Click **Open in marketplace**. This opens the official listing on Microsoft's AppSource marketplace in a new tab: [marketplace.microsoft.com/en-us/product/office/WA200009911](https://marketplace.microsoft.com/en-us/product/office/WA200009911).
Open Microsoft Word, Excel, or PowerPoint. Go to **Home → Add-ins → More Add-ins**, search for **"Scanova QR Codes"**, and click **Install**.
Open the add-in from **Home → Scanova QR Codes**. Sign in with your Scanova account, then create a new QR Code or pick an existing one and insert it into your document — no switching apps.
## What it's for
Use this when your document already lives in Word, Excel, or PowerPoint — a proposal, a report, a slide deck — and you want a live Scanova QR Code embedded in the layout instead of pasting in a static image. It's a document-authoring convenience, not a way to view scan analytics; for that, open the QR Code in Scanova directly.
## Related
* [Canva](/integrations/canva) — the same design-time convenience, as a Canva app
* [Adobe](/integrations/adobe) — the same design-time convenience, as a plugin suite for Photoshop, InDesign, and Premiere Pro
* [Google Docs](/integrations/google-docs) — the same design-time convenience, as an add-on for Docs, Slides, and Sheets
# Integrations overview
Source: https://docs.scanova.io/integrations/overview
The integrations catalogue: design tools that create QR Codes where you work, marketing tags that retarget scanners, and automation that moves your data.
**Integrations** (`/integrations`) is where you manage every third-party connection on your account and browse the catalog of services you can add. Each individual integration has its own setup guide — see the list under **Related**, below — this page covers the shared list, catalog, and connection-management UI they all share.
## Your connections at a glance
Four stat cards summarize your account's connections:
* **Total** — every connection you've created, across all integrations
* **Active / Connected** — connections currently working
* **Awaiting Data** — connections that exist but haven't sent or received anything yet (for example, a webhook with no deliveries)
* **Inactive / Disconnected** — connections that have been turned off or lost their authorization
Below the cards, the connections table lists each one's **Application**, what it's **Integration With** (for example, a specific lead list or webhook name), **Status**, **Last Synced** timestamp, **Created By**, and **Created On**. Filter with the search box (by connection name) or the **Status**, **Application**, and **Created** filter buttons.
Switch between the table and a card layout with the two icons above the top-right of the table (**List**/**Grid**). The grid view shows the same fields and Quick Actions as cards instead of rows.
## Quick Actions
Each row (or card) has three Quick Actions:
* **View** (eye icon) — opens a read-only summary: what it's integrated with, status, last synced time, delivery health, who created it and when, when it was last modified, and integration-specific fields (for example, a Google Analytics connection's Measurement ID and scope)
* **Edit connection** (gear icon) — reopens that integration's setup form, prefilled with the saved configuration
* **Delete** (trash icon) — removes the connection after a confirmation
Select multiple rows with their checkboxes to reveal a floating toolbar with a bulk **Delete** action.
## Adding a new integration
Select **Add Integration** to open the **Connect an Integration** catalog — every available service, grouped into **Design**, **Marketing**, and **Automation** categories (or **All** to see everything), with a search box to filter by name.
Each card shows the integration's name, a one-line description, a **Setup** button to start (or add another) connection, and — for integrations with their own dedicated docs page — a **Docs** link that opens it. A card for an integration you already have connections for also shows a small checkmark badge with the count (for example, "✓ 1").
The **Design** category (Canva, Adobe, Google Docs, Microsoft Office) covers marketplace plugins with no data-sync connection — they don't show up in the stat cards or connections table above, since there's nothing to track. See each one's own page for details.
## Related
* [Webhooks](/integrations/webhooks), [Slack](/integrations/slack), [Zapier](/integrations/zapier), [HubSpot](/integrations/hubspot) — Automation-category integrations
* [Google Analytics (GA4)](/integrations/google-analytics), [Google Ads](/integrations/google-ads), [Microsoft Ads](/integrations/microsoft-ads), [Meta Pixel](/integrations/meta-pixel), [LinkedIn Ads](/integrations/linkedin) — Marketing-category integrations
* [Canva](/integrations/canva), [Adobe](/integrations/adobe), [Google Docs](/integrations/google-docs), [Microsoft Office](/integrations/microsoft-office) — Design-category plugins
* [MCP overview](/mcp/overview) — the AI-agent integration, listed in the same catalog under Automation
# Slack
Source: https://docs.scanova.io/integrations/slack
Send scan notifications, lead alerts, and form responses into the Slack channels your team already watches, so nobody has to check the dashboard daily.
The Slack integration connects a Slack workspace to your Scanova account via OAuth, then lets you build named **notification rules** — each one picking a set of events and the channel(s) that should hear about them.
## Prerequisites
* A Slack workspace where you (or an admin) can authorize a new app
* For **private channels**: the Scanova bot must be invited to the channel before it appears in the channel picker — public channels don't need this
## Connecting Slack
From **Integrations**, select **Add Integration**, then find the **Slack** card and select **Setup**. Slack opens as its own full page.
Select **Connect Slack**. A popup opens to Slack's own authorization screen — choose the workspace and authorize Scanova to post messages on your behalf.
Scanova polls for the connection to complete; once it does, the popup closes and a "Slack workspace connected successfully" toast appears, along with a banner showing the connected workspace name.
## Creating a notification rule
Once connected, select **Create rule** to start the 3-step rule builder.
Give the rule a short, recognizable name.
Select one or more of the following events:
* Lead Captured
* QR Code Scan
* Form Responses
* QR Code Created
* QR Code Deleted
* Page Created
* Page Deleted
* Shared User Added
* Shared User Deleted
* Custom Domain Added
* Custom Domain Deleted
If **Lead Captured** is selected, you must also choose at least one lead list to scope the rule to — otherwise every lead across the account would trigger it.
Search and select one or more Slack channels to receive the notifications. Use **Sync channels** to pull a fresh channel list directly from Slack (useful right after connecting, or after creating a new channel in Slack itself).
A private channel only shows up here after the Scanova bot has been added to it — see Slack's own [bot installation guide](https://docs.slack.dev/legacy/legacy-bot-users/#installing-bot) for how to add a bot to a channel.
Select **Create rule** to save it. Rules appear in a list showing the rule name, a summary of its events, and the channels it posts to; each rule can be edited or deleted individually. Once the rule list grows past one page, a search box filters by rule name, event, or channel.
## Managing the connection
**Disconnect** (in the connected banner) removes the workspace entirely — every notification rule for it stops sending, and rules/channels are cleared from the view.
## What data flows where
When a matching event occurs — a scan, a lead capture, a form response, or one of the account-management events — Scanova posts a message to every channel configured on any rule that includes that event.
## Related
* [HubSpot](/integrations/hubspot) — another OAuth-connected integration with its own guided setup wizard
* [Webhooks](/integrations/webhooks) — a generic alternative for pushing the same events to your own endpoint instead of Slack
* [Zapier](/integrations/zapier) — a broader automation option if you need to fan events out beyond Slack
# Webhooks
Source: https://docs.scanova.io/integrations/webhooks
Push real-time Scanova events to your own server, so scans and lead captures reach your backend the moment that they happen rather than on a schedule.
Webhooks are the generic option for getting Scanova data into your own systems: pick a data stream, give Scanova an endpoint URL, and it POSTs that data to your server in real time as events happen — no third-party app in between.
## Prerequisites
* An HTTPS (or HTTP) endpoint that accepts `POST` requests and returns a 2xx status code to acknowledge delivery
* If you plan to send lead data: at least one lead list or Form-builder form already set up as the source
## Setting up a webhook
Webhooks open as their own full page (not a side panel).
From **Integrations**, select **Add Integration**, then find the **Webhooks** card and select **Setup**.
Select one of:
* **Scan Analytics Data** — scan events for your QR Codes
* **Event Tracking Data** — custom event-tracking data
* **Lead List Data** — form or lead-list submissions
If you chose **Lead List Data**, also select which lead list or Form-builder form the submissions should come from.
Enter the full `https://` (or `http://`) URL Scanova should POST to. Select **Add another URL** to send the same payload to up to 5 endpoints total.
Select **Create webhook**. Scanova starts POSTing matching events to every URL you configured as soon as they occur.
Scanova POSTs event payloads to your endpoint in real time. Your endpoint should return a 2xx response to acknowledge delivery — a non-2xx response doesn't automatically retry.
## Editing an existing webhook
Opening a webhook from **Active Connections** reopens the same form, prefilled. Because the read model only exposes one resolved URL per connection, editing shows a single URL field even if the original setup had more than one.
## What data flows where
Depending on the data type chosen, Scanova sends scan events, event-tracking events, or lead/form submissions as a JSON payload to every configured endpoint URL, as soon as the underlying event occurs.
## Related
* [Slack](/integrations/slack) — a similar event-driven integration, scoped specifically to Slack channels
* [Zapier](/integrations/zapier) — routes the same kind of event data through Zapier's app catalog instead of a raw URL
* [Google Analytics (GA4)](/integrations/google-analytics) — another server-side data-out integration for scan/event data
* [Management API overview](/api-reference/management-api/overview) — pull the same data on your own schedule instead of receiving push events
# Zapier
Source: https://docs.scanova.io/integrations/zapier
Connect Scanova to Zapier so that scans, leads, and form responses can trigger workflows in thousands of other apps without anyone having to write code.
Zapier lets you wire Scanova into thousands of other apps — Google Sheets, Slack, HubSpot, and more — without writing code. On the Scanova side, connecting Zapier is a single step: generate a Management API token scoped to the `zapier` environment, then paste it into the Scanova app inside your Zapier account. From there, Zaps are built and managed entirely in Zapier.
This integration is a **token handshake**, not a visual trigger/action builder inside Scanova itself. Everything you'd recognize as "Zapier setup" — choosing triggers, actions, and field mappings — happens on Zapier's platform, using its published Scanova app. Scanova's side only issues and revokes the token that app authenticates with.
## Prerequisites
* A Zapier account
* A Scanova plan that includes the Zapier integration quota (`INTEGRATION_ZAPIER`). If your token is rejected with a quota error, contact [support@scanova.io](mailto:support@scanova.io).
## Setting up the connection
From **Integrations**, select **Add Integration**, then find the **Zapier** card and select **Setup**. This opens a setup panel — Zapier doesn't need a multi-step wizard, since there's nothing to configure on Scanova's side beyond the token itself.
Select **Generate token**. Scanova creates a Management API key with `environment=zapier`, named `zapier_`. The token is shown once — copy it immediately using the copy icon next to the masked value. There's no way to reveal it again later; if you lose it, revoke it and generate a new one.
Open the **Scanova** app inside your Zapier account and paste the token when prompted to authenticate.
Create a Zap with a Scanova trigger — for example, a new scan or a new lead — then connect the destination app (Sheets, Slack, HubSpot, or anything else Zapier supports).
Once the Zap is on, Scanova events start flowing to your chosen destination automatically.
Revoking the token (**Revoke token** in the setup panel) immediately disables every trigger and action configured against it in Zapier — any live Zaps built on this connection stop working.
## How the token actually works
The Zapier integration is built entirely on the Management API — there's no bespoke Zapier-specific backend. For the underlying request/auth mechanics (host, header format, and how the `environment=zapier` quota check and its required `User-Agent: zapier` header interact), see:
How Management API tokens authenticate, including the `zapier` environment's quota and User-Agent requirements.
## What data flows where
Once a Zap is live, Scanova pushes trigger events (such as new scans or new leads) to Zapier, which hands them to whatever action you've configured in the destination app. Zaps that call back into Scanova (for example, creating a QR Code from another app's event) use the same token against the regular Management API data endpoints.
## Related
* [Management API overview](/api-reference/management-api/overview) — the raw-key auth scheme the `zapier` environment token authenticates against
* [Creating & managing API keys](/api-keys/creating-and-managing-keys) — how the `environment=zapier` key handed to Zapier is generated
* [MCP overview](/mcp/overview) — another integration built on a Management API key with its own dedicated environment
# Downloading end-user & RSVP data
Source: https://docs.scanova.io/leads/downloading-data
Download the leads captured by your QR Codes as a spreadsheet, choosing the list and the time period, so the data can be worked with outside of Scanova.
## Two separate export mechanisms
Scanova has **two separate export mechanisms** for two different kinds of data. They look similar (both are labeled "Export CSV") but behave differently and live in different places:
1. **Lead List export** — on a [Lead List](/leads/lead-lists)'s detail page, exports that list's captured contacts.
2. **Data Collected export** — on a QR Code's detail page, exports form/RSVP/feedback submissions collected through that QR's landing page.
Don't confuse the two — a Lead List and a QR Code's linked Form are unrelated data sets, even if a QR Code happens to use both features.
## Lead List export (synchronous)
On a Lead List's detail page (`/lead/[id]`, **Entries** tab), click **Export CSV**.
Go to **Forms > Lead List** in the sidebar, then click into the list you want to export.
On the **Entries** tab, click **Export CSV** in the top-right of the Responses table.
The browser downloads a `.csv` file right away — there's no processing step. A **Download started** toast confirms it; if the request fails, you'll see a **Failed to export entries** toast instead.
This is a direct, synchronous download: the button requests the file and saves it to your browser's downloads as soon as the response comes back — no waiting, no email.
On free-plan accounts, Lead List entry export is capped at the first 25 responses.
The **Analytics** tab on the same page has its own **Export Analytics** button, which downloads the aggregated entry counts and top-sources breakdown (not the raw entries) the same way — immediately, no email involved.
## Data Collected export (asynchronous, emailed)
RSVP responses, custom form submissions, and restaurant feedback collected via a QR Code's landing page live on that QR Code's own detail page, under the **Data Collected** tab — a completely different screen from anything under Lead Lists.
Go to **QR Codes**, open the QR Code whose submissions you want, and click the **Data Collected** tab.
Dynamic QR Codes always show a **Form** sub-tab. QR Codes whose landing page includes an RSVP, feedback, or lead-list block additionally show **RSVP**, **Feedback**, or **Leads** sub-tabs. Select the one with the data you want.
Click **Export CSV** above the responses table.
Unlike the Lead List export, nothing downloads immediately. A toast reads **"Your export is being prepared — you'll receive the download link shortly,"** and the file is generated in the background, then emailed to you as a download link once it's ready.
This export is asynchronous by design — the file is built as a background job and the result is delivered by email, not streamed straight to your browser like the Lead List export above. If you click Export CSV and nothing appears in your downloads, that's expected: check your email instead.
## Which one do I need?
| You want to export... | Use |
| :- | :- |
| Contacts captured by a legacy Lead List | The Lead List's **Entries** tab → **Export CSV** (instant download) |
| Aggregated stats for a Lead List | The Lead List's **Analytics** tab → **Export Analytics** (instant download) |
| Form responses, RSVPs, or feedback submitted through a QR Code's landing page | That QR Code's **Data Collected** tab → **Export CSV** (emailed link) |
## Related
* [Leads & lead lists](/leads/lead-lists) — where the legacy Lead List entries described here come from
* [Form responses](/forms/responses) — viewing Form submissions on the modern, non-legacy path
* [Building a form](/forms/building-a-form) — set up the Forms whose Data Collected exports are covered above
# Leads & lead lists
Source: https://docs.scanova.io/leads/lead-lists
Lead Lists are the deprecated predecessor to Forms. What they still do today, where to find them in the sidebar, and why new lead capture should use Forms.
## Lead Lists are a legacy feature
The app displays this banner at the top of the Lead Lists page:
> **Important Update**
> Lead Lists have now been replaced by Forms — a more flexible and unified way to capture leads.
>
> You can still view and export your existing Lead Lists, but new lead collection setups should be created using Forms.
If you're setting up lead collection for the first time, use [Forms](/forms/building-a-form) instead — this page only covers viewing, exporting, and migrating Lead Lists you already have.
## What a Lead List is
A Lead List is the pre-Forms way of collecting visitor contact details (name, email, phone) through a QR Code's landing page. Each Lead List is a standalone collection of entries, optionally linked to one or more QR Codes so their scans can feed the same list.
Scanova no longer exposes a way to create a new Lead List from the app — the **Lead List** entry in the sidebar (under **Forms**, tagged **Legacy**) only lets you manage and export lists that already exist on your account. New lead-collection setups go through Forms instead.
The screenshot above is a genuinely empty account. If your account has existing Lead Lists, they list in a table instead, with columns for **Name**, **QR Codes** (how many QR Codes feed this list), **Entries** (total captured contacts), **Status** (a **Converted** badge if the list has already been migrated to a Form), and **Created**.
## Opening a Lead List
Go to **Forms > Lead List** in the sidebar, or navigate to `/lead`. Click a list's name to open its detail page at `/lead/[id]`.
The detail page repeats the legacy warning, this time with a **Migrate to Form →** button that converts the list into a Form (see below), and two tabs:
* **Entries** — the raw captured contacts: submission date, name, email, phone, and which QR Code the entry came from. This is also where you export the list's data — see [Downloading end-user & RSVP data](/leads/downloading-data) for how that export works.
* **Analytics** — total entry count and a breakdown of entries by source QR Code, with its own **Export Analytics** button for the aggregated numbers
## Migrating a Lead List to a Form
Click **Migrate to Form →** on a Lead List's detail page to convert it. This creates a new Form from the list and redirects you to it — a one-way move meant for accounts still relying on old Lead Lists that want the newer Forms feature set (conditional logic, email notifications, and analytics on the Form itself).
## Deleting a Lead List
From the Lead Lists table, use the row's action menu and choose **Delete**. The confirmation dialog warns that this permanently removes the list **and all its entries** — there's no undo.
## Related
* [Downloading end-user & RSVP data](/leads/downloading-data) — how exporting a Lead List's entries actually works
* [Form responses](/forms/responses) — the modern, non-legacy way to view submissions once you've migrated to Forms
* [Building a form](/forms/building-a-form) — set up a new Form instead of a Lead List for new lead-collection
# Switching from Classic to New UI
Source: https://docs.scanova.io/onboarding/classic-to-new-ui
Switching between Classic and New Scanova is a one-way, account-wide, owner-only action, not a per-page choice. What changes, and what keeps working fine.
## What switching actually means
"Classic" and "New Scanova" (this app) are Scanova's two full platforms — an older Angular app and this Next.js one — not two editors for the same landing page. `Preference.platform` is a single field on your account, and moving it is a decision for the **whole account**, not an individual QR Code:
* **Only the account owner can switch.** A shared user (any non-owner role) sees a disabled **Switch to Classic** button in the sidebar footer, with a tooltip explaining that only the owner can move the whole account
* **Switching moves everyone on the account together.** If you have shared users, they all move to Classic with you — there's no per-user opt-out
* **It's a platform switch, not an editor toggle.** There is no per-QR-code or per-page "open in Classic" control anywhere in New Scanova
## Switching from New Scanova to Classic
The control lives at the bottom of the sidebar: **Switch to Classic**. Selecting it (as the account owner) opens a confirmation dialog:
The dialog's warning is the load-bearing fact on this whole page, quoted verbatim from the live app:
> **\{N} landing page(s) on this account was/were built in New Scanova.** You won't be able to edit them in Classic, but they'll keep working normally and you can still download their QR Codes.
This is not "your colors will need to be reapplied" — it's **you can no longer edit that page's content or design from Classic at all**. The page keeps serving live traffic normally; you simply lose the ability to open it in Classic's editor. If any teammates are on the account, a second line in the same dialog warns that they'll all move to Classic with you.
Before confirming, the dialog optionally asks why you're leaving (missing something, something broken, performance, just exploring, or other) — answering is optional and doesn't block the switch. Confirming triggers a real, immediate, one-way account migration, with a hard redirect to the legacy app.
There is no in-app undo. Once you confirm, your account is on Classic, and any page built in New Scanova is frozen there — visible in Classic's list but not editable — until (if ever) you switch back.
## Switching back from Classic to New Scanova
This direction is **not self-service from within New Scanova** — there is no "Switch to New" control in this app, because you'd already have to be on Classic to need it. The only path is: a shared user on Classic can *request* that the account owner move the account to New Scanova; the account owner is the one who actually completes the move, from within Classic.
## What's preserved either direction
| | Switching to Classic |
| - | - |
| QR Code image, short URL, and existing scans/analytics | Unaffected — the QR Code identity never changes |
| Content of pages built in Classic | Fully editable in Classic, as always |
| Content of pages built in **New Scanova** | Keeps working live, but becomes **read-only in Classic** — no further edits from Classic |
## Related
* [Quick Code vs. full Page](/qr-codes/create/quick-code-vs-page) — what a "page built in New Scanova" actually is, and why it can't round-trip to Classic
* [Support](/support/overview) — if you're unsure whether switching is right for your account, especially one with shared users, talk to support before confirming
# First-run onboarding wizard
Source: https://docs.scanova.io/onboarding/first-run-wizard
The four-step wizard that new account owners see at /onboarding: choose a starting point, enter a URL, name and design your QR Code, then download it.
The first time a new account owner logs in, Scanova redirects them into a 4-step wizard at `/onboarding` before they ever see the main dashboard — its whole purpose is to get one QR Code created before you're left to explore on your own.
## Who sees this, and why
The wizard only applies to a genuinely new **account owner** — a teammate accepting a shared-user invitation trips the same underlying `first_login` flag on their first login too, but is deliberately exempted, since the wizard assumes permissions (like creating a QR Code) that a Viewer or other restricted role may not hold. A newly-invited teammate goes straight to the dashboard instead.
If profile completion is still outstanding, that gate takes priority — the wizard only appears after your profile is complete.
## Step 1 — Choose
The wizard opens on a personalized welcome ("Welcome, \{name}! Let's create your first QR Code.") with three starting points, matching the same three options available everywhere else in the app (see [Quick Code vs. full Page](/qr-codes/create/quick-code-vs-page)):
* **Website URL** (under **Quick Code**) — a direct-action QR Code that opens a link when scanned
* **Choose a Template** (labeled **Popular**) — a pre-designed page layout, such as Business Card or Custom Page
* **AI Studio** (labeled **AI**) — describe what you need and let AI generate your page content automatically
A top progress indicator (**1 Choose → 2 Create → 3 Configure → 4 Design & Finish**) tracks your position throughout, and a **Visit Dashboard** link in the top right lets you skip the wizard entirely at any point.
## Step 2 — Create
Selecting a starting point opens its type-specific creation form. For **Website URL**, this asks for the destination URL, the **Short URL** that will front it, and whether to switch on **GPS location tracking**, with a live QR Code preview rendering on the right as you type:
## Step 3 — Configure
Next, name the QR Code and optionally pick a design for it:
* **QR Code name** — pre-filled with "My QR Code", so it is safe to leave alone
* **Choose a design (optional)** — a gallery of ready-made QR Code styles, or skip it and style the code later from the dashboard
Selecting **Create QR Code** shows a brief "Generating your QR Code…" state, then advances to the final step.
## Step 4 — Design & Finish
The last step confirms success with a celebratory confetti animation and your finished, scannable QR Code:
From here:
* **Test your QR** — opens the destination in a new tab, the same way a scan would
* **Download QR** — downloads the QR Code image
* **Continue to plans** — the final action, which moves you into the main app
An on-screen note reminds you that the design can be changed later from the dashboard.
## Related
* [Quick Code vs. full Page](/qr-codes/create/quick-code-vs-page) — the same three starting points, available at any time from `/qr/create`, not just during onboarding
* [Classic to New UI](/onboarding/classic-to-new-ui) — a separate, one-time account-level decision, unrelated to this wizard
# Where everything moved in New Scanova
Source: https://docs.scanova.io/onboarding/where-everything-moved
A lookup for anyone moving over from Classic: where each of the 26 QR Code types now lives, what happened to Pages, and how landing pages are built now.
New Scanova is a rebuild of the whole app, so a few things sit somewhere different. Nothing you have made has been deleted, and nothing behaves differently. Your QR Codes, landing pages, forms, scan history and analytics all came across untouched, and every live QR Code keeps resolving exactly as it did.
This article is a lookup. If you are hunting for one specific thing, use your browser's find (Ctrl+F / ⌘F) and type what you used to call it. The old names are all in here.
***
## The one big change
Classic asked you to pick from a grid of **26 QR Code categories**. New Scanova asks what you want the code to *do*, and gives you three routes. Every route ends in the same editor.
| Route | Use it for |
| - | - |
| **Quick Code** | Anything that sends a person somewhere or hands them data: a website, a phone number, Wi-Fi details, a contact card |
| **Build a Page** | Anything where the QR Code should open a landing page you have designed |
| **AI Studio** | The same as Build a Page, but you describe what you want and get a first draft to refine |
You reach all three from **Create QR Code**.
The QR Code types themselves have not been reduced. They are grouped by intent now instead of listed in one long grid.
***
## Where each QR Code type is now
All 26 Classic categories in one table, sorted by the name you used to know them by. If you are hunting for one thing, use your browser's find (Ctrl+F / ⌘F) and type the old name.
Five types kept their job and changed their label. Those are flagged in the last column.
| In Classic | In New Scanova | Where to find it | Notes |
| - | - | - | - |
| AI-generated Page QR Code | AI Studio | AI Studio | Now a main route |
| App Deep Link QR Code | App Deep Link | Quick Code | Dynamic |
| App Store QR Code | App Store | Quick Code | Dynamic |
| Audio QR Code | Audio | Build a Page | |
| Business Card QR Code | Business Card | Build a Page | |
| Calendar Event QR Code | Event Calendar | Quick Code | Static only |
| Coupon QR Code | Coupon | Build a Page | |
| Custom Page QR Code | Custom Page | Build a Page | |
| Document QR Code | Document | Build a Page | |
| Email QR Code | Email | Quick Code | Static only |
| Event QR Code | Event | Build a Page | |
| Feedback QR Code | Feedback | Build a Page | |
| Google Maps QR Code | Google Maps | Quick Code | Dynamic |
| GS1 QR Code | GS1 | Quick Code | Dynamic |
| Image QR Code | Gallery | Build a Page | Renamed |
| Phone Number QR Code | Phone Call | Quick Code | Static only · Renamed |
| Product QR Code | Product | Build a Page | |
| Real Estate QR Code | Real Estate | Build a Page | |
| Restaurant QR Code | Restaurant | Build a Page | |
| Simple VCard QR Code | Contact Card | Quick Code | Static only · Renamed |
| Social Media QR Code | Social Media | Build a Page | |
| Text Message QR Code | Text Message | Quick Code | Static only |
| Text QR Code | Plain Text | Quick Code | Static only · Renamed |
| Website URL QR Code | Website URL | Quick Code | Dynamic |
| Wedding QR Code | Wedding | Build a Page | |
| WiFi QR Code | Wi-Fi | Quick Code | Static only · Renamed |
That is 12 under Quick Code, 13 under Build a Page, and 1 as AI Studio. Nothing was removed.
### Dynamic and Static only
Quick Code describes itself as *"direct-action QR Codes — no landing page needed."* The cards are split into two groups, and the difference between them matters.
**The five Dynamic types** resolve through Scanova, so you can change where they point after they are printed, and every scan is tracked. Website URL also supports redirection rules and UTM parameters.
**The seven Static only types** encode the information directly into the QR Code, which is why they work with no internet connection at all. The trade-off is that there is nothing to change afterwards and nothing to count: a static code cannot be edited once it is printed, and it produces no scan analytics.
This is not new. Static codes have always worked this way. Classic just did not group them under a heading that said so, and New Scanova does.
If you might need to change a phone number later, use a Website URL code pointing at a page instead.
### How Build a Page works now
You no longer pick a category first. Build a Page opens a gallery of ready-made templates, with the category names as chips along the top. Tap a chip to narrow the gallery, or scroll the whole set. The category names are still how templates are organised; they are a filter now rather than a first step.
Document, Image and Audio work exactly as they did, including a Document QR Code with a single file, which still opens that file directly rather than showing a page.
> **Announcement** is not one of your old categories. It is a new template under **Custom Page**.
***
## Pages have been retired
In Classic, **Pages** let you build a standalone landing page without creating a QR Code for it. That is the same thing as a landing page QR Code without the code. Now that building a page is one of the three main routes, keeping a separate Pages section meant two ways to do one job.
**Your Pages have come across as QR Codes.** If you had two Pages, you now have two QR Codes in your QR Codes list, and the pages themselves keep working exactly as before. Nothing to do.
There is no **Pages** item in the sidebar any more. Everything lives under **QR Codes**.
***
## Landing pages are built differently
In Classic you picked a layout, then filled in its fields. In New Scanova you build the page from **blocks**: add them, drag to reorder, and edit each one in place on the page itself.
What that gives you:
* A live preview at mobile, tablet and desktop widths before you publish
* Themes to start from, instead of styling every block by hand
* Per-page settings: its own URL slug, SEO fields and an optional password
* An explicit **Publish** step, so you can save work in progress without it going live
**Pages you built in Classic open in the new editor and keep working.** Going the other way does not work. See *Switching back to Classic* below.
***
## How to do the things you used to do
**Create a QR Code that sends someone to a website, phone number or contact card** Create QR Code → **Quick Code** → pick the type.
**Create a QR Code for a document, image or audio file** Create QR Code → **Build a Page** → pick the Document, Gallery or Audio template.
**Create a QR Code with a landing page** Create QR Code → **Build a Page** → pick a template.
**Have a page written for you** Create QR Code → **AI Studio** → describe the page and refine the draft in the editor.
**Find the Pages you built in Classic** They are in your **QR Codes** list, alongside everything else.
**Change how a QR Code looks: colours, patterns, eye shapes, logo** Open the QR Code and click **Edit Design**. Design is no longer a step you pass through while creating the code. You create the code first, then style it whenever you like.
**See your scan data** **Analytics** in the sidebar, as before.
**Check whether any of your QR Codes have problems** **QR Codes → Health Center**. It runs five checks across your whole account: expired schedules, unverified domains, unpublished drafts, missing forms, and codes with no scans after 30 days. What it finds is ranked by how much of your traffic each problem is costing. It is new; there was no equivalent in Classic.
**Attach or manage a lead capture form** **Forms** in the sidebar, as before.
***
## What else is new
There is more detail on each of these on the [What's new page](https://scanova.io/new-scanova/).
* **A rebuilt interface**, with a proper dark mode that each person on the account sets for themselves rather than it being an account-wide setting
* **A new landing page editor:** build pages from blocks, preview at three widths, publish when you are ready
* **Health Center:** one page showing every QR Code with a problem, ranked by how much of your traffic each one is costing
* **Conversion tracking** *(Pro and above)*: follow a scan through to what people do on your own site, including pages visited, links clicked and how far they scrolled
* **Multi-page landing pages** *(Standard and above)*: several pages behind one QR Code, with navigation between them
* **AI Studio:** describe the page you want and refine the draft it generates
* **Passkey sign-in**, **landing page themes**, and **Arabic and Simplified Chinese** interface languages
***
## Switching between New Scanova and Classic
**Only the account owner can switch.** Switching moves everyone on the account together, so it is the owner's decision. If you are not the owner, you can request it from inside Scanova and the owner gets an email.
**To switch to Classic:** *Switch to Classic* at the foot of the sidebar.
### One thing to know before you switch back
A landing page **built in New Scanova cannot be edited from Classic**. The page keeps working normally, scans and analytics are unaffected, and you can still download its QR Code and change the code's design. The content, though, is only editable in the new editor.
Pages built in Classic are fine in both.
***
## Why can't I see something?
**Multi-page landing pages:** available on **Standard** plans and above.
**Conversion tracking:** available on **Pro** plans and above.
**AI Studio:** on every plan, including Free Trial. The number of generations included depends on your plan, so if you have run out you will need to wait for your allowance to reset or move up a plan.
**Health Center shows my colleague more checks than me.** Two of the five checks cover Forms and Custom Domains. If your role does not have access to those, those checks are hidden and the rest work normally.
**Something else is missing?** Tell us. Reply to any Scanova email or write to [support@scanova.io](mailto:support@scanova.io), and say what you were trying to do.
***
## Still stuck?
Write to [**support@scanova.io**](mailto:support@scanova.io) with the QR Code's name or ID and what you were trying to do.
# Animated effects
Source: https://docs.scanova.io/page-builder/animated-effects
Add entrance animations and hover effects to your blocks, control their timing, and keep a page feeling lively without ever making it tiring to read through.
Animated effects add motion overlays to your landing page — particles that float across the background, a light beam that travels around a card's border, or a glow that pulses around a button. Effects are purely visual: they render with `pointer-events: none`, so they never block taps or clicks on the content beneath them.
## Where effects appear
| Target | What it covers | Set from |
| - | - | - |
| **Background** | The full page, behind all blocks | Header **Theme** → **Customise** → **Effects** |
| **Card** | An individual content block's surface | **Customise** → **Effects** (all cards) OR the block's own **Design** tab → **Effects** (one block) |
| **Button** | An individual button block | **Customise** → **Effects** (all buttons) OR the button's own **Design** tab → **Effects** |
## Setting a global effect
A global effect applies to every card or button on the page at once (background effects are always global — there's only one background).
With your page open in the builder, click **Theme** in the header.
On the theme card that's currently applied, click **Customise**. This opens the **Customise · Global** panel in the right-hand sidebar.
Scroll down and click **Effects** to expand it. You'll see three dropdowns: **Background effect**, **Card effect**, and **Button effect**.
Open a dropdown and choose a named effect. The canvas preview updates immediately, at full visual intensity — exactly as visitors will see it.
Effects render identically in the canvas and on the published page, so there's nothing further to configure — just publish when you're ready.
To remove a global effect, open the same dropdown and choose **None (disabled)**.
## Overriding the effect on a single block
Any individual card or button block can use a different effect than the global setting, or disable effects entirely, without changing the global setting for other blocks.
Click the block in the canvas, or select it from the **Blocks** list on the left.
In the right-hand inspector, click **Design**.
Under the **Effects** heading, open the effect dropdown (labelled **Card Effect** or **Button Effect** depending on the block type) and choose:
* **Theme default** — inherits whatever the global setting is. This is the default for every block.
* **None (disabled)** — turns off effects for this block only, even if the global setting has one applied
* Any named effect — overrides the global setting and uses this effect just for this block
This is useful when most cards on a page should have an effect but one — a form or a disclaimer, for example — should stay static and easy to read.
## Available effects
### Background effects
| Effect | Description |
| - | - |
| Meteor Shower | Diagonal streaks that shoot across the page |
| Particles | Floating dots that drift slowly across the background |
| Animated Gradient | A softly shifting gradient that blends between colours over time |
### Card effects
| Effect | Description |
| - | - |
| Border Beam | A light beam that travels continuously around the card's border |
| Shine Border | A soft shine sweep that glides along the card's border |
| Neon Glow | A glowing outline pulsing around the card |
### Button effects
| Effect | Description |
| - | - |
| Border Beam | A light beam that travels continuously around the button's border |
| Shine Border | A soft shine sweep that glides along the button's border |
| Neon Glow | A glowing outline pulsing around the button |
In the global Customise panel, the dropdowns default to **Preset default** (the effect your active theme ships with) rather than **Theme default** — that label is reserved for per-block overrides, where it means "inherit the global setting." Functionally, choosing **None (disabled)** always turns effects off for its scope.
## Canvas preview vs. published page
Effects render at full visual intensity in both the canvas builder and the published landing page — what you see while editing is what visitors see. There's no separate "preview mode" needed to check how an effect looks.
## Performance notes
Effects are lightweight and client-rendered, and they don't call any external service. They run entirely in the browser and have minimal impact on page load time.
## Related
* [Themes](/page-builder/themes) — setting theme-wide default effects from the Customise panel's Effects section
* [Design options](/page-builder/design-options) — the per-block Effects group that overrides the global setting
* [Blocks reference](/page-builder/blocks-reference) — the blocks that carry a Card or Button effect
# Blocks reference
Source: https://docs.scanova.io/page-builder/blocks-reference
Every block available in the page builder, what each one puts on the page, and the content and design options you get once a block has been selected on canvas.
## What blocks are
Blocks are the individual sections that make up a landing page — a heading, a paragraph, a gallery, a form. You add them from the left sidebar, drag to reorder, and edit each one's content and appearance in the right-hand inspector.
Every block has a **Content** tab for its data (text, images, links). Most blocks also have a **Design** tab for per-block style overrides, and an **Event Tracking** tab for click/conversion tracking.
Most card-based blocks share the same four Design-tab controls — **Appearance** (Card / Full width), **Card Background**, **Card Surface**, and **Shadow** — plus a **Transparent background** switch. Rather than repeating this on every block below, see [Design Options](/page-builder/design-options) for what each control does. If a block has no Design tab at all (for example Footer or Menu), it has no style overrides to set.
## Adding a block
1. Click **+ Add** at the top of the **Blocks** list in the left sidebar.
2. The **Add a block** picker opens, showing every block available for the current page type — filter by category tab, or search by name.
3. Click a block's card to add it to the bottom of the page.
The picker's tabs — **All, Content, Media, Social, Interaction, Layout** — are the categories used below. A blank/general page offers 17 blocks; picking a category-specific template (Restaurant, Wedding, Real Estate, Business Card, Feedback, Audio, Document, Image) unlocks additional blocks scoped to that category, noted individually below.
***
## Content blocks
Text, branding, and profile blocks that make up the core information on a page.
| Block | Description |
| - | - |
| Page Title | Main heading shown at the top of the page |
| Hero Section | Hero section with profile image, cover, title and links |
| Description | A rich-text paragraph with formatting, links and images |
| Company Branding | Company logo and name |
| Contact Us | Contact information with links and icon |
| Contact Details | Phone numbers, emails, websites and addresses — Business Card, Real Estate, vCard, and Dynamic vCard pages only |
| Coupon Details | Coupon code with offer title, details and validity dates |
| File Download | A downloadable file link |
| Map | Embedded Google Map with a directions button |
| GS1 Product Info | Automatically shows this GS1 code's live product data (GTIN, batch/lot, etc.) when the page is linked to a GS1 code — nothing to configure |
| About Us | About section with text and images — Restaurant pages only |
| Brand Info | Restaurant banner, logo, name and tagline — Restaurant pages only |
| Footer | Footer text shown at the bottom of the page — Restaurant pages only |
| Menu | Restaurant menu — single PDF or multiple sections with items and prices — Restaurant pages only |
| Couple Names | Display the couple's names with an optional decorative separator — Wedding pages only |
| Event Schedule | Date, time and recurring settings for the event, with an Add to Calendar button — Wedding and Event pages only |
| Event Venue | Venue address or online meeting link — Wedding and Event pages only |
| Logo Section | Branded logo shown at the top of the feedback page — Feedback pages only |
| Profile Info | Personal profile — name, photo, designation and company — Business Card, vCard, and Dynamic vCard pages only |
| Agent Profile | Real estate agent — name, photo, license number and company logo — Real Estate pages only |
| Property Listings | List of properties with images, specs and pricing — Real Estate pages only |
**Hero Section** was previously called "Page Header" in older documentation. The block type slug (`page_header`) is unchanged, but the on-screen label is now **Hero Section**.
## Media blocks
Images, video, audio, and downloadable files.
| Block | Description |
| - | - |
| Image Gallery | Up to 8 images shown as a banner or gallery — carousel, grid, list, or slider with captions |
| Video | Embedded video from a URL (YouTube, Vimeo, or a direct link) |
| Audio Player | Audio playlist with optional track titles and cover art — Audio pages only; added automatically and can't be removed |
| Documents | File downloads with titles and download links — Document pages only |
| Gallery Page | Photo gallery with a grid or list layout — Image pages only |
## Social blocks
Ways for visitors to follow or share your page.
| Block | Description |
| - | - |
| Social Profiles | Links to social media profiles — list or grid layout |
| Share Buttons | Social share buttons so visitors can share your page |
## Interaction blocks
Blocks that let visitors take action — fill in a form, redeem a coupon, or leave a review.
| Block | Description |
| - | - |
| Button | A call-to-action button with a link (URL, email, phone, SMS, or another QR Code) |
| Custom Form | A custom form to collect visitor responses |
| Feedback Form | Star-rating feedback form with configurable parameters and fields — Restaurant pages only |
| Review Us | Links to review platforms like Google, TripAdvisor, and Yelp — Restaurant pages only |
| RSVP | RSVP form for guests to respond to an invitation — Wedding and Event pages only |
The Button block's link type options are **URL, Email, Phone, SMS**, and **QR Code**. The "QR Code" option links to a *different* QR Code's own landing page — it doesn't create in-page navigation between pages of the same multi-page site.
## Layout blocks
Structural elements that control page flow, spacing, and advanced behavior.
| Block | Description |
| - | - |
| Separator | A visual divider between blocks (9 decoration styles) |
| Custom Script | Inject custom HTML or JavaScript into the page — requires a plan with the Custom Script quota |
| Auto Redirect | Countdown timer with a Skip/Continue button that redirects visitors — Intro pages only |
***
## Reordering blocks
Drag the grip handle on any block card in the left sidebar to move it to a new position. The canvas updates in real time.
## Deleting a block
Select a block on the canvas or in the left sidebar, then click the trash icon in the block card header. Most blocks can be deleted freely — including Page Title and Coupon Details, despite older documentation claiming otherwise. A small number of blocks that anchor a specific page type still can't be deleted: **Audio Player** (on Audio pages), **Logo Section** (on Feedback pages), **Menu** (on Restaurant pages), and **Agent Profile** (on Real Estate pages).
## Copying and pasting a block
Select a block and press Cmd/Ctrl+C to copy it. Press Cmd/Ctrl+V to paste a copy below the current selection. Pasted blocks keep the same content and design settings as the original.
## Related
* [Design options](/page-builder/design-options) — the per-block Design tab controls referenced throughout this page
* [Animated effects](/page-builder/animated-effects) — the Card Effect and Button Effect options available on card and button blocks
* [Page types](/page-builder/page-types) — which page type unlocks which category-specific blocks
* [Building a form](/forms/building-a-form) — configuring the Custom Form block's questions in the dedicated form builder
# Design options
Source: https://docs.scanova.io/page-builder/design-options
Style a page without touching any code: colors, typography, spacing, backgrounds, and the per block design controls in the builder's right hand panel today.
## What Design Options are
Most blocks have a **Design** tab in the right-hand inspector. Design options let you override the visual style of a single block without changing the rest of the page. When a design field is left empty (showing "Theme default"), the block inherits its value from the active theme instead.
This page covers the destination *page's* per-block design — not the QR Code's own scannable pattern (colors, logo, frame). For that, see [Custom QR Code design](/qr-codes/manage/custom-design).
## How to access Design Options
Click a block on the canvas or in the left sidebar to select it.
In the right panel, click the **Design** tab. (If a block has no visual style to override, this tab is disabled — for example Footer, Menu, and Contact Details have no Design tab at all.)
The canvas updates in real time.
To reset a field to its theme default, clear it (or reselect "Theme default" from its dropdown).
## Typography
Text style options appear on blocks that contain paragraph text, headings, or labels — Page Title, Description, Event Schedule, Event Venue, Couple Names, and others.
| Option | Description |
| - | - |
| Text color | Overrides the color of the block's text. Leave empty to use the theme's foreground color. |
| Font family | Overrides the typeface for this block. Accepts any font in the picker. |
| Font size | A continuous size **multiplier**, not a Small/Medium/Large scale: **0.5×, 0.75×, 1×, 1.25×, 1.5×, 2×, 2.5×, 3×, 3.5×, 4×**. 1× is the theme's default size for that text. |
Font Size is **not** a named scale (Small/Medium/Large/Extra Large). It's a numeric multiplier applied to the theme's base size for that text element — pick 1× to keep the theme default, or a higher/lower multiplier to scale it up or down.
## Card style
Card style options control a block's container appearance. These appear together on any block with visible card chrome (Description, Coupon Details, About Us, Brand Info, and most others).
| Option | Description |
| - | - |
| Style (sometimes labeled "Appearance") | Choose **Card** (an inset panel with a border and shadow) or **Full width** (no outer inset — the block spans the full content width). |
| Card Background | Overrides the background color of the block's card. Leave empty to use the theme's surface color. Has no visible effect when Transparent background is on. |
| Transparent background | Forces a fully see-through background for this block, instead of the theme's surface color — works with either Style setting. |
## Effects (per-block)
In addition to Card Background, most card-based blocks expose three more overrides under an **Effects** group in the Design tab:
| Option | Description |
| - | - |
| Card Effect | Choose "Theme default" to inherit the theme's card effect, or override it for this block only — see [Animated Effects](/page-builder/animated-effects) for the full catalog. |
| Card Surface | Overrides the card's material (defaults to "Theme default") — mirrors the theme-level Surface section's "Card material" control, exposed here on a per-block basis. |
| Shadow | Overrides the shadow variant used for this block's card (defaults to "Theme default") — mirrors the theme-level Surface section's shadow control. |
**Card Surface** and **Shadow** are per-block overrides of the same tokens set globally in the theme's **Surface** section (see [Themes](/page-builder/themes)). Setting them here affects only the selected block; everything else on the page keeps using the theme's values.
## Button styles
Button style options appear on the **Button** block and on blocks with a built-in button (Map, File Download).
| Option | Description |
| - | - |
| Button color | Background fill color of the button |
| Border color | Color of the button's border |
| Corner radius | A 5-option preset picker — **None, S, M, L, Full** — not a raw pixel value |
| Border width | Thickness of the button border in pixels. Set to 0 to remove the border. |
## Icon styles
Icon style options appear on blocks that display a decorative icon. For example, on the **Event Venue** block (Wedding and Event pages):
| Option | Description |
| - | - |
| Icon colour | Color applied to the block's icon |
Not every block with an icon has this control — for example, Contact Us shows icons but has no Icon colour override in its Design tab.
## Separator styles
Separator style options appear on blocks that include a built-in decorative line. For example, on the **Couple Names** block (Wedding pages):
| Option | Description |
| - | - |
| Separator style | Chooses the visual design of the separator (styles 01–09) |
| Separator colour | Color of the separator line |
These differ from the standalone **Separator** block — they're decorative lines built into specific blocks.
## Page-level effects
Many blocks' Effects group lets that block override the theme's default background, card, or button effect for itself only. See [Animated Effects](/page-builder/animated-effects) for the full list and how to set the theme-wide defaults.
## Precedence
Design options follow this priority order (highest to lowest):
1. Per-block Design tab setting (if set)
2. Page-level theme override (in the Theme picker's Customise panel)
3. Preset theme default
An empty field in the Design tab means "use the theme value." A non-empty field overrides the theme for that block only.
## Tips
* Use per-block overrides sparingly. Overriding too many blocks individually makes the page look inconsistent. Apply most customization through the theme, and reserve block overrides for specific emphasis.
* To highlight a single Button block, give it a distinct button color while leaving all other blocks at their theme defaults
* If you want a block to stand out — for a call-out section or important information — set its Card Background explicitly rather than changing the whole theme
## Related
* [Themes](/page-builder/themes) — the page-level tokens these per-block overrides fall back to
* [Animated effects](/page-builder/animated-effects) — the full catalog behind the Card Effect and Button Effect options
* [Blocks reference](/page-builder/blocks-reference) — which blocks expose a Design tab at all
# Event tracking
Source: https://docs.scanova.io/page-builder/event-tracking
Name the interactions on a landing page — a button press, a directions tap, a form submit — so your analytics shows what people did after they scanned.
Scan analytics tell you that someone scanned a QR Code: when, where, on what device. They stop at the moment the page opens. **Event tracking** picks up from there, recording what the person actually did once they arrived.
A page with two buttons — *Book a demo* and *Download the brochure* — gets the same scan count either way. Event tracking is what tells you which one people pressed.
## Where it lives
Event tracking is part of [Build a Page](/page-builder/overview) — it belongs to the blocks on a landing page, so a [Quick Code](/qr-codes/create/quick-code-vs-page) has nothing to track.
Select a block in the page builder and the panel on the right shows three tabs: **Content**, **Design**, and **Event Tracking**.
There is no switch to turn on. If your plan includes event tracking, the tab is there and filling in a label is all it takes — earlier versions of Scanova gated this behind a toggle, and that toggle is gone.
## Which blocks support it
The tab appears on every block, but only blocks with something to press support it. Open **Event Tracking** on a block that doesn't and it says so plainly: *"This block does not support event tracking."*
| Block | What gets tracked | Field |
| :- | :- | :- |
| **Button** | The button press | Button tracking |
| **Map** | The directions tap | Directions button tracking |
| **Custom Form** | The form submit | Submit button tracking |
The pattern is the interactive element, not the block as a whole — on a Map block it's the **Get Directions** button that's counted, not the map itself.
## Naming an event
Each supported block takes one label, described in the panel as *"A label for this interaction in analytics"*, with `hero_cta_click` offered as the example.
That label is the only thing separating one interaction from another in your reports, so it's worth a moment's thought:
* Name the thing and the place, not just the thing — `pricing_cta_click` beats `button_1`
* Keep one convention across a campaign, so events group when you compare pages
* Reuse the same label across pages when you want a combined figure, and vary it when you want them apart
The label is what creates the event, so a supported block left blank has nothing to report. Walk the interactive blocks before publishing rather than discovering the gap when you read the campaign's numbers.
## Related
* [Page Builder overview](/page-builder/overview) — building the page the blocks sit on
* [Blocks reference](/page-builder/blocks-reference) — every block type and what it does
* [Analytics dashboard](/analytics/dashboard) — scan-level analytics, which event tracking sits alongside
* [Conversion Tracking](/conversion-tracking/introduction) — measuring what happens on **your own** website after a scan, which is a different product area
# Build a page from your website
Source: https://docs.scanova.io/page-builder/import-brand
Import your logo, brand name, description, images, social links, and brand color straight from your own website, onto a blank page or into a template.
**Import brand** reads your website and fills a landing page with what it finds: your logo, brand name, description, images, contact details, social links, and brand color. On a blank page it builds the page for you. On a template it fills the template's blocks and leaves the layout, design, and theme untouched.
**Import brand** is part of **Build a Page**, which is available on the Lite Plan and above.
## Where to find Import brand
| Where | What it does |
| - | - |
| **Choose a Template** screen, top right | Opens a blank page with the importer already open |
| **Page builder** header | Opens the importer on the page you're editing |
| **Page builder** bottom bar, on mobile | The same as the header button |
Import brand appears on landing pages only. It isn't shown in the form builder, or on audio, image, and document pages.
## Importing your brand
Click **Import brand**. A dialog opens asking for your website address.
Type the address, for example `yourbrand.com`. The `https://` prefix is optional. An address that isn't valid is rejected straight away, with **Enter a valid website address, like yourbrand.com.**
Reading the site takes up to about 20 seconds.
The preview shows the logo, brand name, and description that were found, along with a line listing exactly which blocks will change.
Clear the **Brand colour** checkbox to leave your page's existing colors alone, or pick one of the colors found on your site. The color you pick becomes the accent color for buttons and highlights.
Click **Fill template** on a template, or **Replace page content** on a blank page. The page updates and saves itself.
To try a different address without starting over, click **Different website** in the preview.
## What you get on a blank page
The page is built from your website, and blocks with no matching data are left out, so you get no placeholder or demo content.
| Block | Added when |
| - | - |
| Profile Info | Business card, vCard, and dynamic vCard pages |
| Company Branding | All other page types |
| Description | A description was found |
| Image Gallery | A usable image was found |
| Contact Details | Contact data was found, on business card, vCard, dynamic vCard, or real estate pages |
| Social Profiles | At least one social link was found |
| Button | Always, as **Visit website** |
## What gets filled in a template
Your template keeps its blocks, their order, its design, and its theme. Only the content is written in.
| Template block | What your website fills |
| - | - |
| Profile Info | Company name and logo. The person's name, photo, and job title stay as they are |
| Company Branding | Brand name and logo |
| Description | Your website's description |
| Banner image | Your website's main image, in the template's own banner style |
| Contact Details | Emails, phones, address, and website |
| Social Profiles | Facebook, Instagram, X, LinkedIn, YouTube, TikTok, and Pinterest links |
| Button | **Visit website**, pointing at your site |
A few things worth knowing before you apply an import:
* Only the **first** block of each type is filled. A second description or button keeps its own content.
* Blocks your website can't fill, such as a menu or gallery, are left exactly as they are
* Contact details are filled on business card, vCard, dynamic vCard, and real estate pages. A template that shows a contact block may still keep its own placeholder details.
* If your site has something the template has no block for, it isn't added. The preview lists it under **Not in this template**.
* The template's first button is relabeled **Visit website**
The preview's **Filled with website content** line is the reliable guide to what will actually change. Read it before you apply.
## Undoing an import
**Cmd/Ctrl+Z** restores the page as it was. If you applied a brand color to a template, press it twice: once for the content, and once for the color.
## When your website can't be read
Some sites can't be read, and the message tells you which one you're hitting.
| What happened | What you'll see |
| - | - |
| The address isn't valid | Enter a valid website address, like yourbrand.com. |
| The site blocks automated access | This website blocks automated access (bot protection), so its details can't be imported. Add them manually instead. |
| The site doesn't exist | We couldn't find that website. Check the spelling of the address. |
| The site is too slow | That website took too long to respond. Try again in a moment. |
| The page is a 404 | That page doesn't exist. Check the address or try the homepage. |
| The site returned an error | That website returned an error. Try again later or use another page. |
| The link isn't a web page | That link isn't a web page. Enter your homepage address. |
| Too many imports at once | Too many imports in a short time. Wait a minute and try again. |
Bot protection is the most common one. If your site sits behind Cloudflare or a similar service, add your details by hand instead.
## Limits
* You need to be logged in, and you can run up to 10 imports a minute
* Logos are saved as PNGs up to 256 px, and images as JPEGs up to 1200 px. Logos supplied only as SVG files are skipped, so a site may import with no logo.
* Nothing is cached. Every import reads your site as it is at that moment.
* Your brand name comes from your site's own metadata, exactly as written there, so it may read longer than you expect
## Related
* [Templates](/page-builder/templates) — picking the template an import will fill
* [Page Builder overview](/page-builder/overview) — editing the blocks once they're filled
* [Themes](/page-builder/themes) — changing colors after an import, or putting a template's own colors back
# Keyboard shortcuts
Source: https://docs.scanova.io/page-builder/keyboard-shortcuts
The keyboard shortcuts that the page builder supports, for selecting, duplicating, reordering, and deleting blocks without reaching for the mouse each time.
## Shortcuts reference
| Action | Mac | Windows / Linux |
| - | - | - |
| Undo | Cmd+Z | Ctrl+Z |
| Redo | Cmd+Shift+Z | Ctrl+Shift+Z |
| Copy selected block | Cmd+C | Ctrl+C |
| Paste block | Cmd+V | Ctrl+V |
| Toggle preview | Cmd+\\ | Ctrl+\\ |
| Deselect / exit preview | Esc | Esc |
| Toggle help | ? | ? |
## Where to find this list in the app
Press **?** anywhere in the builder (with focus outside a text field) to open the **Help** drawer, then switch to its **Keyboard Shortcuts** tab.
**"?" opens the Help drawer, not a dedicated shortcuts modal.** The Help drawer normally shows a contextual help article for whatever's currently selected — a block, or the current page type. Pressing **?** toggles that same drawer open and closed; the shortcuts list above lives on its second tab, alongside the **Article** tab, rather than in a standalone popup.
## Notes
* **Undo** and **Redo** step through your edit history one action at a time — adding, removing, moving, and editing blocks all count. Both are also available as buttons in the canvas's floating toolbar (see [Page Builder overview](/page-builder/overview)).
* **Copy** captures the selected block, including its content and design settings. **Paste** inserts it below the current selection (or at the bottom of the page if nothing is selected)
* **Toggle preview** switches the canvas into a preview-style view — for example, the "Add your first block..." onboarding helper disappears from an empty page. It does **not** hide the right-hand inspector panel; the Content/Design/Event Tracking (or Settings/Design/SEO) tabs stay visible and usable while preview is toggled on.
* **Escape** exits preview mode, or deselects the current block if you're already in normal editing mode
Copy and paste is the fastest way to duplicate a block with the same settings — for example, several buttons with identical styling but different labels and links. Copy the first one, paste it, then just change the label and link on each copy.
## Related
* [Page Builder overview](/page-builder/overview) — the canvas toolbar where Undo/Redo also live as buttons
* [Publishing](/page-builder/publishing) — what Toggle preview does relative to the draft/published states
# Multi-page navigation
Source: https://docs.scanova.io/page-builder/multi-page-navigation
Put several pages behind a single QR Code and let your visitors move between them, using navigation that you configure inside the page builder editor itself.
## How it works
Multi-page landing pages are available on the **Standard Plan** and above. On a lower plan a QR Code is limited to a single page.
When a QR Code has more than one page, visitors need a way to move between them. Each page has a name and can have an icon; these show up in the navigation controls. Navigation appears automatically as soon as a QR Code has a second page — there's nothing separate to turn on.
Navigation style is a single, QR-wide setting — not a per-page choice. Every page on the QR Code uses the same style.
## Navigation styles
The Page Settings dialog offers exactly three styles: **Bottom**, **Top**, and **None**.
### Bottom
A row of tabs anchored to the bottom of the screen — the default and most familiar pattern for mobile visitors, similar to the tab bar in most mobile apps.
### Top
A horizontal tab bar at the top of the page content. Works well when the page has a prominent header block and you want navigation to read as a section switcher.
### None
No navigation controls are shown. Visitors only move between pages through whatever links or buttons you build into the content itself.
There is no "Sidebar" navigation style. If you're working from older documentation or notes that mention a slide-out sidebar drawer for page navigation, that feature doesn't exist in the current page builder — only Bottom, Top, and None are available.
## Configuring navigation
Click the gear icon next to the **Pages** list in the left sidebar, or click **Settings** in the header.
Under **Navigation style**, pick **Bottom**, **Top**, or **None**.
Click a page in the **Pages** list to give it an icon; this shows in the Bottom and Top navigation styles.
Drag pages in the list — the order here matches the order visitors see in the navigation.
For the full list of page types you can add and how each one behaves, see [Page types](/page-builder/page-types).
## Related
* [Page types](/page-builder/page-types) — the page types you can add to a multi-page site
* [Page Builder overview](/page-builder/overview) — where Page Settings and the Pages list live in the editor
* [Page security](/page-builder/page-security) — password-protecting an individual page that still appears in navigation
# Page Builder overview
Source: https://docs.scanova.io/page-builder/overview
The block based landing page editor: how a page gets assembled from blocks, previewed at mobile, tablet and desktop widths, styled with a theme, and published.
## What the page builder is
The page builder is Scanova's block-based editor for landing pages: add content blocks (headings, images, video, maps, contact details, forms, and more), arrange them on a canvas, theme the result, and publish it to the QR Code's live URL. Every "Build a Page" QR Code — and every Custom Page, Restaurant, Wedding, Event, and similar category — opens into the same editor.
The sidebar item for your QR Codes is labeled **QR Codes**, not "My QR Codes."
## Creating a page
From the sidebar, click **Create QR Code** (or go to `/qr/create`). You land on a picker with three cards: **AI Studio** ("Tell us what you want — AI generates the blocks and copy"), **Build a Page** ("Block-based landing page builder. Start from a template or blank canvas"), and **Quick Code** ("A direct QR Code for a URL, phone, email, Wi-Fi, or plain text. No landing page"). Every path lands in the same editor.
This opens **Choose a template** (`/qr/new`) — a gallery filtered by category (Business Card, Restaurant, Wedding, Event, Product, Social Media, and more), plus a **Start Blank** option and a **My Templates** tab for anything you've saved as a template.
Picking a template or **Start Blank** opens the editor itself (`/qr/new/landing`), pre-populated with that template's blocks or empty and ready for you to add your first block.
To edit a page you already built, go to **QR Codes** in the sidebar and click the pencil/edit icon on its row, or open the QR Code's detail page and click **Edit Page**.
Only Dynamic QR Codes with a landing page (category) support the page builder. Direct-action Quick Codes (URL, phone, email, Wi-Fi, plain text) have no page to edit — see [Quick Code vs. full Page](/qr-codes/create/quick-code-vs-page).
## The builder layout
The editor has three main areas: a left sidebar (Blocks and Pages), a center canvas showing a live device preview, and a right-hand inspector.
### Left sidebar
* **Blocks** — every block on the current page, in order. Click one to select it; drag to reorder; click **+ Add** to open the block picker.
* **Pages** — every page in the QR Code (for multi-page sites). Click **+ Add** to open the [Add a page](/page-builder/page-types) sheet, or the gear icon to open [Page Settings](/page-builder/multi-page-navigation).
### Canvas
A live preview of the current page inside a phone, tablet, or desktop frame. Click any block to select it and edit it in the right inspector. A small floating toolbar sits in the canvas's top-right corner — this is where **Undo** and **Redo** live (not in the header), alongside a preview toggle and a couple of layout/grid controls.
### Right panel (inspector)
* With a block selected, the tabs are **Content**, **Design**, and **Event Tracking**
* With nothing selected, the tabs are **Settings**, **Design**, and **SEO** for the page itself — this is also where you'll find the page's **Password protection** toggle (see [Page-level password protection](/page-builder/page-security))
## Header controls
The header across the top of the editor provides: **Back**, a device switcher (**Mobile** / **Tablet** / **Desktop**), **Settings** (opens [Page Settings](/page-builder/multi-page-navigation), including navigation style), **Theme**, **Save as template**, **Publish**, and a **Help** button. Undo/Redo are not here — see [Keyboard shortcuts](/page-builder/keyboard-shortcuts) and the canvas toolbar described above.
## Autosave
Your edits save automatically as you work — this is a real server-backed save, not just a browser-local draft. See [Publishing](/page-builder/publishing) for exactly what that means and how the draft/published states work.
## Creating a page-builder QR Code via the API
Page-builder content is created the same way as any other landing-page QR Code through the API: `POST /qr/` with a `category` ID for a landing-page category (for example `9` for Custom Page, `14` for Wedding, `20` for Event, or `25` for Restaurant) and an `info` payload shaped for that category. See [Create QR Code](/api-reference/management-api/qr/create) for the full request schema and [Category List](/v1/api-reference/references/category-list) for every category ID.
The block-by-block content you assemble in the editor (pages, blocks, and their settings) isn't something you construct directly through this endpoint — it's stored and edited through the builder UI itself. The API's `info` payload controls the QR Code's category and top-level configuration, not individual block content.
## Related
* [Page types](/page-builder/page-types) — the five page types available inside a multi-page site
* [Blocks reference](/page-builder/blocks-reference) — every content block you can add to a page
* [Templates](/page-builder/templates) — starting from a pre-built template instead of a blank canvas
* [Quick Code vs. full Page](/qr-codes/create/quick-code-vs-page) — deciding between a direct-action Quick Code and a full page
# Page security
Source: https://docs.scanova.io/page-builder/page-security
Restrict who can open a published page, with a password, an age gate, or any of the other access controls available on a landing page that you have built.
## Page-level vs. QR-level password protection
On a multi-page site, you can require a password to view an individual page — without password-protecting the whole QR Code. Visitors can still see that the page exists in navigation; they just can't see its content until they enter the correct password.
This is different from **QR-level password protection**, set in the QR Code's own settings, which hides the entire destination — visitors don't see anything, not even that other pages exist, until they enter the password. Page-level protection is more targeted: use it when most of your site should stay open but one or two pages need to stay private.
Live UI wording: the toggle is labeled **Password protection**, with helper text that reads **"Require a password before showing this page"** before you've set a password, and **"Visitors must enter a password before viewing this page"** once one is set.
## When to use page-level password protection
* A public event page with a members-only "Resources" page
* A menu page that's open to everyone, plus a staff-only "Internal" page on the same QR Code
* A wedding site where the main details are public but a gift-registry or address page is shared only with close family
If you want to hide the *entire* site behind one password — including the fact that it has multiple pages — use QR-level password protection instead, from the QR Code's settings.
## Setting a password on a page
Open the page in the page builder and select it from the **Pages** list in the left sidebar.
In the right-hand inspector, make sure nothing is selected on the canvas so you're viewing the page-level tabs, then open **Settings**.
Under **PROTECTION**, turn on **Password protection**. A **Set a password** field and **Set password** button appear.
Enter a password and click **Set password**. A "Password saved" confirmation appears, the helper text updates to confirm protection is active, and a **Change password** link takes its place for future edits.
Publish your changes — the password takes effect for visitors once the QR Code is published.
Password protection is set per page. If you have three pages and want all three protected, turn on **Password protection** separately on each one.
## What a visitor sees
Based on the product's own design intent (see [Publishing](/page-builder/publishing) for how the rest of the publish flow behaves):
* The protected page's navigation tab still shows its name and icon, so visitors know the page exists and is protected
* Its content is replaced with a password prompt until the correct password is entered
## Changing or removing a page's password
* **To change the password:** open the same **Protection** section and click **Change password**, enter a new one, and save. Visitors who already unlocked the page on their device will need to enter the new password the next time it's checked.
* **To remove password protection:** turn off the **Password protection** toggle and publish. The page becomes visible to everyone immediately.
## Using both QR-level and page-level protection together
You can combine the two if needed — for example, a QR-level password that protects the whole site, plus an extra page-level password on one especially sensitive page inside it. This isn't blocked, but if QR-level protection is already active, visitors already need a password just to reach any page at all, so double-check that layering both is really the setup you intend.
## Related
* [Multi-page navigation](/page-builder/multi-page-navigation) — how a protected page still appears in navigation
* [Publishing](/page-builder/publishing) — when a page's password protection actually takes effect for visitors
* [Page Builder overview](/page-builder/overview) — where the page-level Settings tab (including Password protection) lives
# Page types
Source: https://docs.scanova.io/page-builder/page-types
The kinds of page you can build behind a QR Code, from a single landing page through to a multi page site, and what each type is actually best suited to doing.
## Pages within a QR Code
A landing page can contain one or more pages. Single-page QR Codes show one scrollable page; multi-page QR Codes add navigation so visitors can switch between pages (see [Multi-page navigation](/page-builder/multi-page-navigation)). Each page has its own **page type**, which determines which blocks are available on it.
## Adding a page
In the left sidebar's **Pages** section, click **+ Add**.
Above the **OR** divider are three creation *methods*, not page types — pick one of these, or pick a page type below instead:
* **AI Studio** — describe the page and let AI build it
* **Choose Template** — start from a pre-built layout
* **Import from QR Code** — copy a page from another QR Code
Below the divider, pick one of the five **PAGE TYPE** options (described below), then click **Add page**.
**AI Studio is a creation method, not a page type.** It sits alongside Choose Template and Import from QR Code, above the page-type list — it isn't one of the five options you pick from below the divider.
## Page types
### General
Free-form page with any block types. The standard, general-purpose page — supports the full content-block set (text, images, video, maps, social profiles, buttons, forms, and more).
### Audio
Audio playlist with an optional intro page. Built around the **Audio Player** block, which manages a playlist of tracks that visitors can play, pause, and (optionally) download.
### Gallery
Image gallery with an optional cover page. A full-screen, swipeable image gallery — good for photography portfolios, product galleries, or event photo albums.
### Documents
File downloads with an optional intro page. Displays a PDF or other document for visitors to view or download — good for menus, brochures, instruction manuals, or price lists.
### Intro Page
Splash screen with a logo, tagline, and redirect. A dedicated landing screen you can put in front of the rest of a multi-page site — for example, a branded splash page that then sends visitors on to your main content.
## Creating a page-builder QR Code via the API
The page type and block content you configure here live inside the builder itself, not in a payload you construct through the API. To create the underlying QR Code for a page-builder site via the API, see [Create QR Code](/api-reference/management-api/qr/create) and pick a landing-page category ID from the [Category List](/v1/api-reference/references/category-list) — for example `9` (Custom Page) for a general-purpose site.
## Related
* [Page Builder overview](/page-builder/overview) — the editor layout and how pages fit into a QR Code
* [Multi-page navigation](/page-builder/multi-page-navigation) — how visitors move between pages once you've added more than one
* [Blocks reference](/page-builder/blocks-reference) — the blocks each page type unlocks
* [Templates](/page-builder/templates) — starting a new page from a template instead of picking a page type directly
# Publishing
Source: https://docs.scanova.io/page-builder/publishing
Publishing a page: what the Publish step actually does, how a draft differs from a live page, and how to push later changes to a page that is already live now.
## Draft and published states
Your landing page has two states: **draft** and **published**. You can make as many changes as you like to the draft without affecting what visitors see. When you're ready, you publish it.
| State | Description |
| - | - |
| Draft | Your current work in progress. Saved automatically. Only visible to you in the builder. |
| Published | The version visitors see when they scan the QR Code. Updated only when you publish. |
## Autosave is real, server-backed saving
Changes save automatically as you work — but this is not just a local browser draft. Within a few seconds of your very first edit, the header status cycles **"Unsaved" → "Saving…" → "Saved"**, and a real `POST /qr/` request creates and persists the QR Code record on the server — before you've clicked Publish at all. Every edit after that keeps the draft saved server-side the same way.
Because autosave is server-backed, you don't need to worry about losing work if you close the tab or your browser crashes — your draft is already saved to your account, not just to local storage.
## Recovering an unsaved draft
If you have unpublished changes and reopen the editor for that QR Code (including via `Edit Page`), the builder detects that the draft is newer than the last published version and asks what you'd like to do.
Choose **Continue editing draft** to pick up where you left off, or **Edit live page** to start from the last published version instead.
## Previewing before publishing
* **Device switcher** — click **Mobile**, **Tablet**, or **Desktop** in the header to see how the page adapts
* **Preview mode** — press Cmd/Ctrl+\ to toggle a preview-style canvas view. See [Keyboard shortcuts](/page-builder/keyboard-shortcuts) for exactly what this does and doesn't change.
## The publish flow
Click **Publish** in the top-right of the header. This opens a **Publish QR Code** panel — not a simple confirmation dialog — with **Basics**, **Advanced**, and **QR Design** tabs, a live QR Code preview, an editable name and Short URL/slug, a folder picker, and tags.
Adjust the name, slug, folder, or tags if needed, then click **Publish** at the bottom of the panel.
The header shows **Publishing…** for a few seconds, then **Published ✓**. A success panel appears with a **Live** badge, the QR Code, its live URL, and quick actions (**View live page**, **View details**, **Edit Design**, **Download**, **Copy URL**, **Share**).
Publishing takes only a few seconds. Once it completes, anyone who scans the QR Code sees the new version immediately.
## After publishing
* The QR Code's live URL serves the updated page; the QR Code image itself never changes
* You can keep editing in the builder without affecting the live page — your next changes become a new draft
* Republishing works the same way each time: make changes, click **Publish** (the button reads **Update live page** once a page has unpublished changes since its last publish), and confirm
## Checking the live page
After publishing, view the live page by scanning the QR Code, or by clicking **View live page** from the success panel or the QR Code's detail page.
Always verify the live page on a real mobile device after major layout changes — the canvas preview is accurate, but a real device test is the most reliable check.
Page-builder QR Codes are created the same way as other landing-page categories through the API — see [Create QR Code](/api-reference/management-api/qr/create) for the request schema.
## Related
* [Keyboard shortcuts](/page-builder/keyboard-shortcuts) — the preview-mode shortcut referenced above
* [Page security](/page-builder/page-security) — password protection, which takes effect once you publish
* [Templates](/page-builder/templates) — saving your published page's layout as a reusable template
# Templates
Source: https://docs.scanova.io/page-builder/templates
Start from a ready made template instead of a blank canvas, how the gallery is organised by category, and what happens to a template once you have picked it.
Every landing page starts from either a pre-built template or a blank canvas. Templates give you a starting set of blocks and a matching theme, which you can freely edit once you're inside the builder — nothing about a template locks you in.
## Reaching the template gallery
From **QR Codes**, click **Create QR Code**. This opens the **Create a new QR Code** screen with three starting paths.
Click **Start** on the **Build a Page** card — the block-based landing page builder.
This takes you to **Choose a template** (`/qr/new`), a searchable, filterable gallery of starting points.
## Browsing and filtering the gallery
The **Gallery** tab shows every available template as a card with a live-style preview, grouped under category tags along the top — **All**, **Announcement**, **Audio**, **Business Card**, **Coupon**, **Document**, **Event**, **Feedback**, **Gallery**, **Product**, **Real Estate**, **Restaurant**, **Social Media**, and **Wedding** — each with a count of templates in that category.
Click a category tag to narrow the grid to just that category, or use the **Search templates** box to filter by name — search and category filtering can be used together.
Hover over a template's preview to scroll through its full mobile layout before picking it. Clicking a card opens the builder with that template's blocks and theme already applied.
## Starting blank
The first card in every view of the gallery is **Start Blank** — it skips template selection entirely and opens the builder with an empty canvas (no blocks, default theme), letting you build a page from scratch.
## My Templates
The **My Templates** tab shows templates you've saved yourself (see below) — it's separate from the shared **Gallery** and is empty until you save your first one.
On a new account, **My Templates** shows this empty state with a **Go to the builder →** shortcut back to a blank canvas. It fills in with your saved templates as soon as you use **Save as template** (below).
## Saving your own template
Yes — the builder has a **Save as template** action that captures your current page (blocks, theme, and any per-block design overrides) as a reusable template under **My Templates**.
Open a page in the builder (from a template or from **Start Blank**) and add or edit blocks as usual.
In the builder header, click **Save as template**.
This button only appears for standard landing pages. It's hidden for AI Studio pages, since those are single raw-HTML pages without the multi-page/theme structure a template needs.
A dialog opens with a **Template name** field, pre-filled with your page's current name. Edit it if you want a different name, then click **Save**.
A confirmation toast appears, and the template is immediately available from the **My Templates** tab the next time you create a QR Code.
Saved templates capture the full page — including multi-page structure and theme overrides — so applying one later reproduces the exact design you saved, not just a default look.
## Related
* [Page Builder overview](/page-builder/overview) — the editor a template or blank canvas opens into
* [Quick Code vs. full Page](/qr-codes/create/quick-code-vs-page) — deciding whether you need a full page (and therefore a template) at all
* [Page types](/page-builder/page-types) — page types available once you're inside the builder
# Themes
Source: https://docs.scanova.io/page-builder/themes
Start a page from a ready made theme instead of styling every block by hand, then switch themes later on without losing any of the content you already wrote.
## What a theme controls
A theme controls the overall visual style of a landing page: background color, surface color, text color, accent color, font, corner radius, and shadow/surface style. Switching themes instantly updates every block on the page — themes never change your content, only how it looks.
## Opening the theme picker
Click the **Theme** button in the Page Builder header. The theme picker opens in the right panel, showing preset themes (**Presets** tab) and any themes you've saved (**Saved** tab).
## Preset themes
The picker has **21 preset themes**, spread across two pages of 12 and 9 cards.
| Page | Preset themes |
| - | - |
| 1 | Modern, Classic, Midnight, Emerald, Aqua, Sharp, Pill, Flat, Navy, Sage, Noir, Coral Pop |
| 2 | Terracotta Craft, Cobalt Tech, Blush Romance, Charcoal Editorial, Mint Festive, Aurora Pulse, Prism Glass, Frosted, Legacy |
Older documentation described "Ten preset themes" and listed **Royal, Retro, Sunset**, and **Mono Dark** among them. None of those four exist in the current picker — the catalog has since grown to 21 presets, six of which (Modern, Classic, Midnight, Emerald, Navy, Aqua) carry over from the original ten.
Click any theme card to apply it. The canvas updates immediately so you can compare themes without leaving the editor.
## Theme overrides
After selecting a preset, click **Customise** on its card to open a panel where you can independently override any of the theme's design tokens without changing the base preset. The panel is organized into seven sections, in this order:
### Color
Override the theme's color tokens individually: Background, Surface, Surface alt, Accent, Button text (accent foreground), Heading, Body, Muted, and Border. Each field falls back to the preset's value when left empty.
### Background
Choose how the page background is rendered — a solid Color, a Gradient (angle and color stops), an Image (with fit and opacity controls), or a Video (with opacity control) — plus a background effect (see [Animated Effects](/page-builder/animated-effects)).
**Block transparency** — when a background effect is active, this slider controls how much a "Full width" block's own surface fades to let the effect show through underneath it. It applies regardless of background type.
### Typography
Override the font family separately for headings, body text, and labels/buttons. Pick from the built-in font list, or type any Google Font name (for example `Montserrat` or `Roboto`) to use it instead of the preset's default stack.
Font overrides here apply to any block that doesn't have its own per-block font setting. A block's own [Design tab](/page-builder/design-options) font, when set, takes priority over this theme-level override.
### Shape
Override the corner radius independently for buttons, cards, images, and inputs.
### Surface
Override the shadow and surface style used for cards and elevated blocks, including a **Card material** control (Preset default / Card / Subtle / Elevated) alongside the shadow-variant control.
This section was previously named "Shadow" in older documentation. It's now labeled **Surface** and also exposes a Card material control in addition to the shadow variant. The same Card Surface and Shadow tokens can be overridden per block — see [Design Options](/page-builder/design-options).
### Effects
Set the theme-wide default background, card, and button effects. See [Animated Effects](/page-builder/animated-effects) for the full catalog.
### Spacing
Control how much breathing room the page uses.
* **Space between blocks** — the vertical gap that separates each block on the page
* **Spacing scale** — a slider that makes all spacing on the page tighter or roomier at once (gaps, card padding, block spacing) without changing text size. Leave it at the default for the theme's density, or adjust it for an airier or denser layout.
## Dark and light mode
Themes are defined with a fixed color palette — they don't automatically switch between dark and light mode based on the visitor's device preference. If you want a dark page, choose a dark preset (for example Midnight or Noir) or set a dark background color using the override.
## Per-block design overrides
In addition to page-level themes, many blocks have their own Design tab in the inspector where you can override colors, fonts, and card style for that block only. See [Design Options](/page-builder/design-options) for details.
## Resetting overrides
To remove an override and return to the theme default, clear the field in the Customise panel. An empty field tells the page to use the theme's value.
## Related
* [Design options](/page-builder/design-options) — per-block overrides of the same tokens set here
* [Animated effects](/page-builder/animated-effects) — background, card, and button effects set from the Effects section
* [Page Builder overview](/page-builder/overview) — where the Theme button lives in the editor header
# AI QR / AI Studio
Source: https://docs.scanova.io/qr-codes/create/ai-qr
Describe the page you want and let AI Studio generate the blocks and copy for you, then refine that draft in the page builder before publishing the QR Code.
**AI Studio** is one of the three starting points on the [Create QR Code hub](/qr-codes/create/quick-code-vs-page) (`/qr/create`), alongside Quick Code and Build a Page. Instead of assembling a landing page block-by-block, you describe what you want in plain text and AI generates the page's blocks and copy for you to refine or publish as-is.
This page covers the Create QR Code entry point and prompt screen only. For what happens after you submit a prompt — the chat-based generation flow, refining the result, the editor, AI credits, and publishing — see [AI Studio](/ai-studio/overview), which documents a real generation verified end to end.
## What the hub shows
On the Create QR Code hub, the AI Studio card reads:
> Tell us what you want, and AI generates the blocks and copy. Refine by re-describing, or publish as-is.
with a sample prompt shown as placeholder text — *"Wedding for Mary & John, June 15 at Riverside Estate"* — and an estimated generation time of about 1 minute, badged **AI**.
## Plan gating varies by account
Access depends on your plan. Where AI Studio is included, the card shows a plain **Start** button. Where it is not, the card is locked, **Start** is replaced by **Upgrade**, and `/qr/new/ai` redirects to a paywall screen. If you see the lock and expect otherwise, check your plan's feature list or contact support.
## The prompt screen
Clicking **Start** on an unlocked account opens the page builder directly at `/qr/new/landing?category=ai`, with a dedicated **AI Studio · AI Page** panel in place of the usual Blocks list:
* A **Describe Your Page** heading and prompt: "Tell the AI what kind of landing page you need and it will generate one instantly."
* A row of example-prompt chips to click instead of typing your own: **Event**, **Wedding**, **Feedback**, **Product Launch**, **Restaurant**, **Portfolio**, **Lead Generation**, **App Promotion**
* A text box at the bottom of the panel — "Describe the page you want to create…" — with a submit arrow, and a disclaimer: "AI can make mistakes. Check important info before publishing."
* The canvas itself starts empty, prompting "Describe your page in the sidebar — AI will generate blocks based on your prompt and populate this page."
The right-hand **Settings** panel is the same one every page-builder page gets — Page icon, Page URL slug (pre-filled `ai-page`), and Password protection — confirming this really is a normal page-builder QR Code (Custom Page category) with an AI-assisted authoring step in front of it, not a separate product.
## What happens after you submit a prompt
See [AI Studio](/ai-studio/overview) for the full, verified walkthrough: the AI's clarifying questions when a prompt is too vague, refining the result with follow-up messages, the AI Credits meter, the editor's settings panel, and publishing.
A generated page lands in the same block-based [Page Builder](/page-builder/overview) used by Build a Page — the Settings panel above is identical to what Build a Page shows, and the URL confirms the underlying category is the same Custom Page (`category=ai` is only used before generation to select the AI Studio entry point).
## Creating a page via the API
AI Studio produces a landing page, which the API represents the same way [Build a Page](/qr-codes/create/quick-code-vs-page) does — as a **Custom Page** (category `9`) QR Code rather than a Quick Code category. There's no separate "AI" category: the AI-assisted authoring happens in the dashboard UI, not as a distinct API operation. See [Create QR Code](/api-reference/management-api/qr/create) for the request schema and [Category List](/v1/api-reference/references/category-list) for the full category mapping.
## Related
* [AI Studio](/ai-studio/overview) — the full generation flow, from your first prompt through publishing
* [Quick Code vs. full Page](/qr-codes/create/quick-code-vs-page) — how AI Studio compares to Quick Code and manual Build a Page
* [Page Builder overview](/page-builder/overview) — the block-based editor an AI-generated page lands in for further edits
* [Templates](/page-builder/templates) — the manual alternative to AI generation: start from a pre-built template instead
# App Deep Link QR Code
Source: https://docs.scanova.io/qr-codes/create/app-deep-link
Create a QR Code that opens your app directly when it is installed, and falls back to the right app store when it is not, all from a single Dynamic code.
## What it's for
An App Deep Link QR Code opens your app directly to a specific screen — via its custom URI scheme — if the app is installed on the scanning device. If it isn't installed, the code falls back to your app's store listing, or to a plain web page as a last resort. It's a **Quick Code** — a direct-action QR Code with no landing page. In the app, the tile for it reads "Open your app directly if it's installed, otherwise fall back to the store."
App Deep Link is **always a dynamic** QR Code — this is required so the resolution logic (app → store → fallback) can run at scan time. You get a Scanova short URL (`scnv.io/...`) and can attach a custom domain, but there's no landing page of its own.
## Resolution order
The form is organized into three sections, checked in this order at scan time:
1. **App installed** — the device opens your app directly via the matching **Android app URI** or **iOS app URI**.
2. **App not installed, OS recognized** — the device is sent to the matching **store link** (Google Play or Apple App Store), if you provided one.
3. **Neither applies** — the scanner is sent to the **Fallback web URL**, which is why that field is required.
## Create an App Deep Link QR Code
From the sidebar, go to **QR Codes**, then click **Create QR Code** (or navigate to `/qr/create`). Click the **Quick Code** card.
On the **Quick Code** type grid, click **Configure** under **App Deep Link**, in the **Dynamic** section at the top (this type has no Static option). This opens the form at `/qr/quick/appDeepLink`.
Under **App URIs**, enter at least one of **Android app URI** or **iOS app URI** — the custom scheme your app registers to open directly (e.g. `myapp://profile`).
Under **Store links**, optionally add your Google Play and/or Apple App Store URLs, shown when the app isn't installed but the device's OS is recognized. Under **Fallback URL**, enter a **Fallback web URL** — this field is required, since it's the last-resort destination for every other case.
Click **Publish**. A panel opens to confirm or rename the QR Code, assign a folder or tags, and adjust QR Code design before confirming.
## Creating one via the API
To create an App Deep Link QR Code through the API rather than the UI, use category ID `23` (labeled **App Deep Link** in the [Create QR Code](/api-reference/management-api/qr/create) **Category dropdown reference** table, `allowed_qr_types: "dy"` — only `"dy"` is valid for `qr_type`). Unlike every other category on this page, the `info` payload here is a **bare array** of typed blocks, not a `{type, data}` object:
```json theme={null}
[
{
"type": "intentUri",
"data": [
{ "type": "android", "uri": "acmeapp://profile/42" },
{ "type": "ios", "uri": "acmeapp://profile/42" }
]
},
{
"type": "appStore",
"data": [
{ "type": "playStore", "url": "https://play.google.com/store/apps/details?id=com.acme.app" },
{ "type": "appleStore", "url": "https://apps.apple.com/app/id123456789" }
]
},
{
"type": "fallback",
"data": { "url": "https://acme.com/profile/42" }
}
]
```
Each block is optional in principle, but the `fallback` block's `url` is effectively required — publishing without a fallback fails validation the same way the dashboard form blocks it. Include an `intentUri` block only if you have at least one app URI.
See [Create QR Code](/api-reference/management-api/qr/create) for the full request schema.
## Related
* [App Store QR Code](/qr-codes/create/app-store) — a simpler "send to the store" alternative when you don't need in-app deep linking
* [Creating your first QR Code](/getting-started/first-qr-code) — a full walkthrough of the Quick Code flow this type belongs to
* [Folders, tags & bulk operations](/qr-codes/manage/folders-tags-bulk-operations) — organize the QR Code after publishing it
# App Store QR Code
Source: https://docs.scanova.io/qr-codes/create/app-store
Create a QR Code that sends every scanner to the correct store for their device, Google Play on Android and the App Store on iOS, from one printed link.
## What it's for
An App Store QR Code sends scanners straight to your app's listing in Google Play or the Apple App Store — automatically picking the right store for the scanning device. It's a **Quick Code** — a direct-action QR Code with no landing page. In the app, the tile for it reads "Send scanners to the right app store for their device."
App Store is **always a dynamic** QR Code — this is required for the automatic, OS-based redirect at scan time. You get a Scanova short URL (`scnv.io/...`) and can attach a custom domain, but there's no landing page of its own.
## Redirect behavior
With **Auto-detect device and redirect** on (the default), a scan behaves as follows:
* An **Android** device is redirected straight to the Google Play URL
* An **iOS or Mac** device is redirected straight to the Apple App Store URL
* Any other device sees a choice screen listing both links
Turning the toggle **off** always shows the choice screen with both links, to every scanner, regardless of device.
If you only fill in one store URL, that's the only link scanners on any device will be offered.
## Create an App Store QR Code
From the sidebar, go to **QR Codes**, then click **Create QR Code** (or navigate to `/qr/create`). Click the **Quick Code** card.
On the **Quick Code** type grid, click **Configure** under **App Store**, in the **Dynamic** section at the top (this type has no Static option). This opens the form at `/qr/quick/appStore`.
Enter your **Google Play Store URL** and/or **Apple App Store URL** — add at least one. Leave **Auto-detect device and redirect** on unless you specifically want every scanner to see a choice screen regardless of device.
Because this type is always dynamic, a Scanova short URL is generated automatically (for example `scnv.io/6z02`). Edit the slug, attach a verified custom domain, or enable UTM tracking parameters under **Advanced settings**.
Click **Publish**. A panel opens to confirm or rename the QR Code, assign a folder or tags, and adjust QR Code design before confirming.
## Creating one via the API
To create an App Store QR Code through the API rather than the UI, use category ID `10` (labeled **App Store** in the [Create QR Code](/api-reference/management-api/qr/create) **Category dropdown reference** table, `allowed_qr_types: "dy"` — only `"dy"` is valid for `qr_type`), with an `info` payload shaped like:
```json theme={null}
{
"type": "appStore",
"data": [
{ "type": "playStore", "url": "https://play.google.com/store/apps/details?id=com.acme.app" },
{ "type": "appleStore", "url": "https://apps.apple.com/app/id123456789" }
]
}
```
`data` is an array with 0–2 entries (`playStore` and/or `appleStore`) — include at least one. An optional sibling `settings: { "autoDetectDisabled": true }` disables the auto-redirect and always shows the choice screen; omit it (or leave it `false`) to keep auto-detect on.
See [Create QR Code](/api-reference/management-api/qr/create) for the full request schema.
## Related
* [App Deep Link QR Code](/qr-codes/create/app-deep-link) — opens straight to a specific screen in your app instead of a store listing
* [Creating your first QR Code](/getting-started/first-qr-code) — a full walkthrough of the Quick Code flow this type belongs to
* [Folders, tags & bulk operations](/qr-codes/manage/folders-tags-bulk-operations) — organize the QR Code after publishing it
# Audio QR Code
Source: https://docs.scanova.io/qr-codes/create/audio
Create a QR Code that plays an audio file from a landing page, using the Audio template in the page builder, for tours, narration, and recorded messages.
## What it's for
An audio page plays sound on scan — a narrated story, a track list, a voice message — through a dedicated **Audio Player** block rather than a generic media embed. It's a full page-builder page (not a separate upload-only utility): the player is one block among the page builder's block types, so you can combine it with other blocks or start it from a template.
As with the other categories on this page, "Audio QR Code" is reached through the **Build a Page** flow filtered to the Audio tag — there's no separate upload-and-generate route for audio.
## Find the audio template and reach the builder
From **QR Codes → Create QR Code** (`/qr/create`), click the **Build a Page** card.
On **Choose a template** (`/qr/new`), click the **Audio** tag. This tag filters to a single template: **Short Story Narrations**.
Audio has the thinnest template selection of any tagged category in the gallery — only one option. If Short Story Narrations' dark, literary styling doesn't fit your use case, start from **Start Blank** and add the **Audio Player** block yourself; it's a first-class block type, not tied to this one template.
Click the card to open its preview panel, then **Use this template**.
Selecting it opens the builder at `/qr/new/landing?template=audio-short-stories`. The canvas shows a dark, book-like mobile preview listing two audio tracks ("The Black Cat — Edgar Allan Poe" and "Markheim — Robert Louis Stevenson"), and the left-hand blocks panel lists exactly one block: **Audio Player**.
Reaching this canvas confirms the flow — publishing isn't required to verify it. Open the **Audio Player** block to upload your own tracks, then use **Publish** from the top bar when ready.
## Creating one via the API
To create an audio-style QR Code through the API rather than the UI, use category ID `16` (labeled **Audio**) — see [Create QR Code](/api-reference/management-api/qr/create) for the full request schema and [Category List](/v1/api-reference/references/category-list) for the complete ID-to-label mapping.
## Related
* [Page Builder overview](/page-builder/overview) — the block-based editor this template opens into, and how to add the Audio Player block yourself
* [Templates](/page-builder/templates) — browsing the gallery and starting from a blank canvas instead
* [Creating your first QR Code](/getting-started/first-qr-code) — the general Create QR Code flow this category is reached from
* [Folders, tags & bulk operations](/qr-codes/manage/folders-tags-bulk-operations) — organize the QR Code after publishing it
# Business Card QR Code
Source: https://docs.scanova.io/qr-codes/create/business-card
Create a QR Code that opens a branded digital business card page, built from the Business Card templates, with your contact details, role, and social links.
## What it's for
A business card page puts your contact details on a mobile-friendly profile: name, title, company, phone, email, address, and social links, plus a **Save Contact** button that adds you straight to the scanner's phone contacts. It's built for the same use case as a paper business card — handed out at a conference, printed on packaging, or added to an email signature — but the page stays editable after you print the code.
Like every category on this page, "Business Card QR Code" isn't a separate creation route — it's the **Build a Page** flow filtered to business-card-tagged templates.
**Business Card is not Contact Card.** Contact Card is a separate Quick Code (`/qr/quick/simple-vcard`) that encodes your details directly, with no landing page and no scan tracking. Use Business Card when you want a branded page — see [Contact Card](/qr-codes/create/contact-card) for the full comparison.
## Find a business-card template and reach the builder
From **QR Codes → Create QR Code** (`/qr/create`), click the **Build a Page** card to open the block-based page builder's template gallery.
On **Choose a template** (`/qr/new`), click the **Business Card** tag. This tag filters to **Pastel & Co.**, **Teal Professional**, **Navy Executive**, **Bold & Modern**, **Fresh Green**, **Dark Professional**, **Sky Blue**, **Minimal Dark**, **Acqua**, **Emerald Profile**, **Corporate**, and **Omega Million**.
Clicking a card opens a preview panel with the template's name, a short style description, its category tag, and a **Use this template** button. The **Corporate** template was used for verification here — described as "Dark navy business card: centered profile, full contacts and social links" and tagged **business card**.
Selecting it opens the builder at `/qr/new/landing?template=bc-corporate`. This template ships with 3 blocks: **Profile Info**, **Contact Details**, and **Social Profiles** — all editable from the left-hand blocks panel. The canvas preview shows a name, pronouns, title, and company, contact rows for mobile/email/website, and a **Save Contact** button.
Reaching this canvas confirms the flow — you don't need to publish to verify it works. Edit the profile fields and contact details, then use **Publish** from the top bar when the page is ready.
## Creating one via the API
To create a business-card-style QR Code through the API rather than the UI, use category ID `24` (labeled **Business Card**) — see [Create QR Code](/api-reference/management-api/qr/create) for the full request schema and [Category List](/v1/api-reference/references/category-list) for the complete ID-to-label mapping.
## Related
* [Page Builder overview](/page-builder/overview) — the block-based editor this template opens into
* [Templates](/page-builder/templates) — browsing, filtering, and saving your own templates in the gallery
* [Creating your first QR Code](/getting-started/first-qr-code) — the general Create QR Code flow this category is reached from
* [Folders, tags & bulk operations](/qr-codes/manage/folders-tags-bulk-operations) — organize the QR Code after publishing it
# Calendar Event QR Code
Source: https://docs.scanova.io/qr-codes/create/calendar-event
Create an Event Calendar QR Code that prompts the scanner to save an event, with its name, start and end times, location, and description all attached.
## What it's for
A Calendar Event QR Code prompts the scanning device to save an event straight into its calendar app — with the name, time, location, and description already filled in. It's a **Quick Code** — a direct-action QR Code with no landing page. In the app, the tile for it reads "Prompt the scanner to save an event to their calendar," and the tile is labeled **Event Calendar** (the underlying category name is "Calendar Event" — both refer to the same type).
Calendar Event is **Static only** — there's no Dynamic option for this type. Once published, the encoded event can't be edited or tracked; to change it, create a new QR Code.
## Create a Calendar Event QR Code
From the sidebar, go to **QR Codes**, then click **Create QR Code** (or navigate to `/qr/create`). Click the **Quick Code** card.
On the **Quick Code** type grid, click **Configure** under **Event Calendar**, in the **Static Only** section. This opens the form at `/qr/quick/calendar`.
Fill in the fields:
* **Event name** — required
* **Start date & time** — required
* **End date & time** — optional; if set, it must be on or after the start date, or an inline error blocks publishing
* **Location** (placeholder "1 Infinite Loop, Cupertino, CA") — optional
* **Description** — optional, multi-line
* **Time zone** — optional, defaults to "None (device default)" (the scanner's own device time zone)
Click **Publish**. A panel opens to confirm or rename the QR Code (it defaults to the event name), assign a folder or tags, and adjust QR Code design before confirming. Once published, you can revisit design (colors, frame) from **Edit Design** on the confirmation screen.
## Creating one via the API
To create a Calendar Event QR Code through the API rather than the UI, use category ID `12` (labeled **Calendar Event**, `allowed_qr_types: "st"` — only `"st"` is valid for `qr_type`), with an `info` payload shaped like:
```json theme={null}
{
"type": "calendar",
"data": {
"name": "Product Launch Webinar",
"startDate": "2026-09-10T15:00",
"endDate": "2026-09-10T16:00",
"location": "Online — Zoom",
"description": "Join us for the launch of our new product line.",
"timeZone": "Asia/Kolkata"
}
}
```
Only `name` and `startDate` are required by the backend schema; both date fields are free-form strings with no enforced format — the dashboard form sends its `datetime-local` input's raw value (`YYYY-MM-DDTHH:mm`) as shown above.
Category ID `12` ("Calendar Event") is distinct from category ID `20` ("Event"), which is a different, page-builder category — don't confuse the two.
See [Create QR Code](/api-reference/management-api/qr/create) for the full request schema.
The [Category List](/v1/api-reference/references/category-list) reference table doesn't currently list Calendar Event (or the other Quick Code–only categories on this page) — the ID above was confirmed directly against the live `/qr/category/` endpoint.
## Related
* [Event QR Code](/qr-codes/create/event) — the different, page-builder "Event" category (schedule, venue, tickets) — don't confuse the two
* [Creating your first QR Code](/getting-started/first-qr-code) — a full walkthrough of the Quick Code flow this type belongs to
* [Folders, tags & bulk operations](/qr-codes/manage/folders-tags-bulk-operations) — organize the QR Code after publishing it
# Contact Card QR Code
Source: https://docs.scanova.io/qr-codes/create/contact-card
Create a Contact Card QR Code that prompts the scanner to save your details straight to their phone, with name, job title, phone, email, and company name.
## What it's for
A Contact Card QR Code prompts whoever scans it to save your details — name, phone, email, company, address — straight into their phone's contacts app. It's a **Quick Code**: a direct-action QR Code with no landing page, built from a small form rather than the page builder.
Contact Card is **Static only** — there's no Dynamic option for this type, matching [Wi-Fi](/qr-codes/create/wifi). Once published, the encoded details can't be edited or tracked; to change them, create a new QR Code. This also means [Advanced settings](/qr-codes/manage/age-restriction) like password protection and age restriction — which require a Dynamic QR Code — aren't available for a Contact Card.
Contact Card is distinct from **Business Card**, a separate, Dynamic category that renders a full landing page. Use Contact Card for a plain "save my contact" QR Code with no landing page; use [Business Card](/qr-codes/create/business-card) when you want a branded page with a photo, social links, and scan tracking.
## Create a Contact Card QR Code
From the sidebar, go to **QR Codes**, then click **Create QR Code** (or navigate to `/qr/create`). Click the **Quick Code** card.
Scroll to the **Static Only** section and click **Configure** under **Contact Card** — "Prompt the scanner to save your contact details." This opens the form at `/qr/quick/simple-vcard`.
Type into **Full name** — this is the only required field, placeholder "Jane Smith".
All remaining fields are optional: **Job title**, **Company**, **Phone**, **Email**, **Website** (with a fixed `https://` prefix), and an **Address** block (Street address, City, State / Province, Country, Postal code). The live QR Code preview updates as you type, growing denser with each field added since a vCard encodes everything directly into the code itself.
Click **Publish**. Because this category is Static only, the panel shows a **Design** picker and a notice that "Advanced settings need a Dynamic QR Code" instead of the Advanced settings list that Dynamic categories get — there's no Short URL or Copy URL option either, since a Static code has no destination link to share.
Confirming publish shows the finished QR Code, tagged **Static** and **Simple VCard**, with **View details**, **Edit Design**, and **Download** actions — no **Copy URL** option, since a Static code has no destination link to copy.
## Creating one via the API
To create a Contact Card QR Code through the API rather than the UI, use category ID `7` (labeled **Simple VCard**, slug `vCard`, `allowed_qr_types: "st"` — only `"st"` is valid for `qr_type`), with an `info` payload shaped as an **array of two blocks**:
```json theme={null}
[
{
"type": "profile_info",
"data": {
"name": "Priya Nair",
"title": "Partnerships Lead",
"company": "Scanova"
}
},
{
"type": "contact_details",
"data": {
"emails": [{ "email": "priya.nair@scanova.io" }],
"phoneNumbers": [{ "phoneNumber": "+91 98765 43210" }],
"addresses": [
{
"street": "221 Main Street",
"city": "Bengaluru",
"state": "Karnataka",
"country": "India",
"postalCode": "560001"
}
],
"website": [{ "url": "https://scanova.io" }],
"faxes": []
}
}
]
```
`profile_info.data` only accepts `name` (required), `title`, and `company`. `contact_details.data` accepts `emails`, `phoneNumbers`, `website`, `addresses`, and `faxes`, each an array of objects — every entry can carry an optional `label` alongside its value (e.g. `{"email": "...", "label": "Work"}`) except `addresses`, which uses `addressLabel` instead. Every field in both blocks is optional except `name`.
See [Create QR Code](/api-reference/management-api/qr/create) for the full request schema.
The [Category List](/v1/api-reference/references/category-list) reference table doesn't currently list Simple VCard (or the other Quick Code–only categories on this page) — the ID above was confirmed directly against the live `/qr/category/` endpoint and by publishing a live Contact Card QR Code.
## Related
* [Business Card QR Code](/qr-codes/create/business-card) — the Dynamic, landing-page alternative for sharing contact details
* [Creating your first QR Code](/getting-started/first-qr-code) — a full walkthrough of the Quick Code flow this type belongs to
* [Quick Code vs. full Page](/qr-codes/create/quick-code-vs-page) — how Contact Card and the other Quick Code types compare to a full landing page
* [Folders, tags & bulk operations](/qr-codes/manage/folders-tags-bulk-operations) — organize the QR Code after publishing it
# Coupon QR Code
Source: https://docs.scanova.io/qr-codes/create/coupon
Create a QR Code that opens a coupon landing page, built from the Coupon templates, with the offer, its terms, and an expiry date that scanners can see.
## What it's for
A coupon page presents a single offer — a discount, a buy-one-get-one deal, a promo — with product imagery, your branding, a description of the deal, a call-to-action button (e.g. "Order Now"), and a dedicated **Coupon Details** block showing a copyable code, validity window, and usage terms. It's built for print materials — flyers, packaging, in-store signage — where scanning the code should surface a redeemable offer rather than a generic landing page.
Like every category on this page, "Coupon QR Code" isn't a separate creation route — it's the **Build a Page** flow filtered to Coupon-tagged templates.
## Find a coupon template and reach the builder
From **QR Codes → Create QR Code** (`/qr/create`), click the **Build a Page** card to open the block-based page builder's template gallery.
On **Choose a template** (`/qr/new`), click the **Coupon** tag. This tag filters to: **Retail Discount** and **Food Deal**.
Clicking a card opens a preview panel with the template's name, a short style description, its category tag, and a **Use this template** button. The **Food Deal** template was used for verification here — described as "Playful and rounded — a buy-one-get-one coupon for a restaurant or cafe" and tagged **Coupon**. Its preview showed a redeemable code ("PIZZABOGO") with a copy icon, validity dates, and a usage cap ("Valid for 200 uses").
Selecting it opens the builder at `/qr/new/landing?template=coupon-food-deal`. This template ships with 5 blocks: **Image Gallery**, **Company Branding**, **Description**, a **Button** (e.g. "Order Now"), and **Coupon Details** — all editable from the left-hand blocks panel.
Reaching this canvas confirms the flow — you don't need to publish to verify it works. Edit the offer copy, redeemable code, and validity window in the **Coupon Details** block, then use **Publish** from the top bar when the page is ready.
## Creating one via the API
To create a coupon-style QR Code through the API rather than the UI, use category ID `17` (labeled **Coupon**) — see [Create QR Code](/api-reference/management-api/qr/create) for the full request schema and [Category List](/v1/api-reference/references/category-list) for the complete ID-to-label mapping.
## Related
* [Page Builder overview](/page-builder/overview) — the block-based editor this template opens into
* [Templates](/page-builder/templates) — browsing, filtering, and saving your own templates in the gallery
* [Product QR Code](/qr-codes/create/product) — a similar retail use case, showcasing a product rather than a redeemable offer
* [Folders, tags & bulk operations](/qr-codes/manage/folders-tags-bulk-operations) — organize the QR Code after publishing it
# Custom Page QR Code
Source: https://docs.scanova.io/qr-codes/create/custom-page
Build a landing page from scratch behind a QR Code using the block based page builder, starting from a blank canvas or from any template in the gallery.
## What it's for
"Custom Page" is the platform's generic, catch-all page category — internally named `dynamicText` and labeled **Custom Page** in the QR category list (ID `9`). It's what you get when a page doesn't fit one of the more specific template categories (Restaurant, Product, Feedback, and so on): any combination of blocks, built from scratch or from a loosely-themed starting point.
**Naming mismatch:** there is no "Custom Page" button, tag, or label anywhere in the app's UI. The template gallery's tag filter bar does **not** include a "Custom Page" tag. Searching the gallery for "custom" no longer returns zero results — it now substring-matches templates like **Customer Service Feedback** and **Retail Customer Feedback**, which have nothing to do with this category — so don't rely on that search as a way to locate it. The only reliable, dedicated route to this category is the **Start Blank** option on the template gallery, which the interface never calls "Custom Page."
## Two ways to reach this category
This is the direct, always-available route to the `dynamicText` / Custom Page category — confirmed by inspecting the actual create request the app sends.
From **QR Codes → Create QR Code** (`/qr/create`), click the **Build a Page** card.
On **Choose a template** (`/qr/new`), the **Start Blank** card sits first in the gallery grid, before any tag filtering — it's not tucked under a category tag.
Clicking it navigates straight to `/qr/new/landing` (no `?template=` query string) and opens an empty canvas: **0 blocks**, with prompts to add a **Page Title**, **Description**, or **Button** to get started.
Verified via the network request the builder sends on the first block add: `POST /qr/` with `"category":9` in the body — `9` is the `dynamicText` / Custom Page category ID. Start Blank reliably creates a Custom Page-category QR Code regardless of which blocks you add afterward.
The gallery does have one pre-built template that actually carries the `dynamicText` / Custom Page category under the hood — it's just filed under the **Announcement** tag, not a "Custom Page" tag.
Click the **Announcement** tag (for example: **Special Announcement**, **Quick Update**, **Simple Note**, and **Coming Soon**). **Special Announcement** — "Ocean teal with a decorative background — a flexible update or announcement page" — is the one confirmed below to carry the Custom Page category; the other three weren't individually re-checked, but expect them to behave the same way given they share the tag.
Selecting it and clicking **Use this template** opens `/qr/new/landing?template=dynamictext-announcement`, with 3 blocks: **Description**, **Button**, **Share Buttons**.
This was confirmed directly against the templates API: `dynamictext-announcement` returns `"category": {"id": 9, "slug": "dynamicText", "name": "Custom Page"}` and `"filter_tags": ["Announcement"]` — the template is Custom Page-categorized, but discoverable only through the Announcement tag.
## Creating one via the API
To create a Custom Page QR Code through the API rather than the UI, use category ID `9` (slug `dynamicText`, labeled **Custom Page**) — see [Create QR Code](/api-reference/management-api/qr/create) for the full request schema and [Category List](/v1/api-reference/references/category-list) for the complete ID-to-label mapping.
## Related
* [Page Builder overview](/page-builder/overview) — the block-based editor Start Blank opens into
* [Templates](/page-builder/templates) — the Start Blank card and category-tagged templates described above, in full
* [Creating your first QR Code](/getting-started/first-qr-code) — the general Create QR Code flow this category is reached from
* [Folders, tags & bulk operations](/qr-codes/manage/folders-tags-bulk-operations) — organize the QR Code after publishing it
# Email QR Code
Source: https://docs.scanova.io/qr-codes/create/email
Create a QR Code that opens the scanner's email app with the recipient, the subject, and the body already filled in, so all they have to do is press send.
## What it's for
An Email QR Code opens the device's default mail app with the recipient, subject, and body already filled in, so scanning it is a one-tap way to start an email — a contact address, a support inbox, an RSVP reply. It's a **Quick Code** — a direct-action QR Code with no landing page. In the app, the tile for it reads "Opens the mail app with subject and body pre-filled."
Email is **Static only** — there's no Dynamic option for this type. Once published, the encoded address and message can't be edited or tracked; to change them, create a new QR Code.
## Create an Email QR Code
From the sidebar, go to **QR Codes**, then click **Create QR Code** (or navigate to `/qr/create`). Click the **Quick Code** card.
On the **Quick Code** type grid, click **Configure** under **Email**, in the **Static Only** section. This opens the form at `/qr/quick/email`.
Fill in the fields:
* **To (email address)** (placeholder `hello@example.com`) — required, validated as a proper email address
* **Subject** (placeholder "Hi there!") — optional
* **Body** (placeholder "Your message…") — optional
Click **Publish**. A panel opens to confirm or rename the QR Code (it defaults to the recipient address), assign a folder or tags, and adjust QR Code design before confirming. Once published, you can revisit design (colors, frame) from **Edit Design** on the confirmation screen.
## Creating one via the API
To create an Email QR Code through the API rather than the UI, use category ID `2` (labeled **Email**, `allowed_qr_types: "st"` — only `"st"` is valid for `qr_type`), with an `info` payload shaped like:
```json theme={null}
{
"type": "email",
"data": {
"to": "sales@acme.com",
"subject": "Product inquiry",
"body": "Hi, I'd like to learn more about your product."
}
}
```
Only `to` is required by the backend schema. The schema also accepts `cc` and `bcc` string fields — the dashboard's Email Quick Code form doesn't expose fields for either, but both are valid if you set them directly through the API.
See [Create QR Code](/api-reference/management-api/qr/create) for the full request schema.
The [Category List](/v1/api-reference/references/category-list) reference table doesn't currently list Email (or the other Quick Code–only categories on this page) — the ID above was confirmed directly against the live `/qr/category/` endpoint.
## Related
* [Text Message QR Code](/qr-codes/create/text-message) — a similar direct-action type, pre-filling an SMS instead of an email
* [Creating your first QR Code](/getting-started/first-qr-code) — a full walkthrough of the Quick Code flow this type belongs to
* [Folders, tags & bulk operations](/qr-codes/manage/folders-tags-bulk-operations) — organize the QR Code after publishing it
# Event QR Code
Source: https://docs.scanova.io/qr-codes/create/event
Create a QR Code that opens an event landing page, built from the Event templates, with the schedule, venue, and registration details your attendees need.
## What it's for
An event page gives attendees everything they need for a conference, festival, or one-off gathering: a title and description, an image gallery, a **ticket or registration button**, an **event schedule** (start/end dates and times, with time zone), and **venue details**. It's meant for print materials — posters, flyers, badges — that need to point to more than a single URL can hold.
Like every category on this page, "Event QR Code" isn't a separate creation route — it's the **Build a Page** flow filtered to Event-tagged templates.
## Find an event template and reach the builder
From **QR Codes → Create QR Code** (`/qr/create`), click the **Build a Page** card to open the block-based page builder's template gallery.
On **Choose a template** (`/qr/new`), click the **Event** tag. This tag filters to: **Corporate Conference** and **Music & Arts Festival**.
Clicking a card opens a preview panel with the template's name, a short style description, its category tag, and a **Use this template** button. The **Music & Arts Festival** template was used for verification here — described as "Mint and hot pink — a lively multi-day festival page with a particles background" and tagged **Event**.
Selecting it opens the builder at `/qr/new/landing?template=event-music-festival`. This template ships with 7 blocks: **Page Title**, **Image Gallery**, **Description**, a **Button** (e.g. "Get Tickets"), **Event Schedule**, **Event Venue**, and **Share Buttons** — all editable from the left-hand blocks panel.
Reaching this canvas confirms the flow — you don't need to publish to verify it works. Edit the schedule dates, venue, and ticket link, then use **Publish** from the top bar when the page is ready.
## Creating one via the API
To create an event-style QR Code through the API rather than the UI, use category ID `20` (labeled **Event**) — see [Create QR Code](/api-reference/management-api/qr/create) for the full request schema and [Category List](/v1/api-reference/references/category-list) for the complete ID-to-label mapping.
## Related
* [Calendar Event QR Code](/qr-codes/create/calendar-event) — a lighter Quick Code alternative that just saves a single event to a calendar, no landing page
* [Page Builder overview](/page-builder/overview) — the block-based editor this template opens into
* [Templates](/page-builder/templates) — browsing, filtering, and saving your own templates in the gallery
* [Folders, tags & bulk operations](/qr-codes/manage/folders-tags-bulk-operations) — organize the QR Code after publishing it
# Feedback QR Code
Source: https://docs.scanova.io/qr-codes/create/feedback
Create a QR Code that opens a feedback form page, built from the Feedback templates, so you can collect ratings and comments after a visit or an order.
## What it's for
A feedback page collects structured responses — star ratings, multiple-choice questions, free-text comments — from a scan, then shows a thank-you screen. It's built for post-visit or post-purchase surveys: table tents asking about a meal, receipt stickers asking about service, or event follow-ups asking attendees to rate the experience.
Feedback templates are structurally different from other page-builder categories: instead of general-purpose content blocks, they're built from a fixed four-block sequence — **Logo Section**, **Start Page**, **Custom Form**, and **Success Page** — which is what makes the form-and-thank-you flow work.
As with the other categories on this page, "Feedback QR Code" is reached through the same **Build a Page** flow, filtered to feedback-tagged templates — not a separate creation route.
## Find a feedback template and reach the builder
From **QR Codes → Create QR Code** (`/qr/create`), click the **Build a Page** card.
On **Choose a template** (`/qr/new`), click the **Feedback** tag. The category includes **Restaurant Dine-in Feedback**, **Product Feedback**, **Professional Event Feedback**, **Customer Service Feedback**, **Retail Customer Feedback**, **NPS Rating**, **Entertainment Event Feedback**, **Restaurant Delivery Feedback**, **Course Evaluation Feedback**, **Employer Feedback Form**, and **Appointment Scheduler Form**. The exact set changes as templates are added.
Clicking a card opens a preview panel with the template's name, description, category tag, and a **Use this template** button. **Product Feedback** was used for verification — described as "Plain white & grey — star ratings and NPS for a product, exact legacy default."
Selecting it opens the builder at `/qr/new/landing?template=fb-product-saas`. The canvas shows a mobile preview of a "What do you think of our products?" start screen with a **Give Feedback** button, and the left-hand blocks panel lists **Logo Section**, **Start Page**, **Custom Form**, and **Success Page**. The Custom Form itself asks a star rating, a repurchase-intent question, an ease-of-use question, and a customer-service rating.
Reaching this canvas confirms the flow — publishing isn't required to verify it. Edit the **Custom Form** block to add or change survey questions, then use **Publish** from the top bar when ready.
## Creating one via the API
To create a feedback-style QR Code through the API rather than the UI, use category ID `26` (labeled **Feedback**) — see [Create QR Code](/api-reference/management-api/qr/create) for the full request schema and [Category List](/v1/api-reference/references/category-list) for the complete ID-to-label mapping.
## Related
* [Restaurant QR Code](/qr-codes/create/restaurant) — pairs well with feedback pages, and includes a template with its own Feedback Form block
* [Page Builder overview](/page-builder/overview) — the block-based editor this template opens into
* [Templates](/page-builder/templates) — browsing, filtering, and saving your own templates in the gallery
* [Folders, tags & bulk operations](/qr-codes/manage/folders-tags-bulk-operations) — organize the QR Code after publishing it
# Google Maps QR Code
Source: https://docs.scanova.io/qr-codes/create/google-maps
Create a QR Code that opens a location in Google Maps or in OpenStreetMap with a single tap, so scanners get directions without typing any address in.
## What it's for
A Google Maps QR Code opens a specific location — a store, a venue, a booth — directly in Google Maps when scanned, with one tap to get directions. It's a **Quick Code**: a direct-action QR Code with no landing page, built by picking a place on a map rather than filling out a form.
Unlike most Quick Code types, Google Maps supports **both Dynamic and Static** QR Codes — the location, short URL, and Advanced settings described below only apply to the Dynamic option.
## Create a Google Maps QR Code
From the sidebar, go to **QR Codes**, then click **Create QR Code** (or navigate to `/qr/create`). Click the **Quick Code** card.
On the **Quick Code** type grid, click **Configure** under **Google Maps**. This opens the form at `/qr/quick/maps`.
The **Map provider** toggle offers **Google** and **OpenStreetMap** — OpenStreetMap is selected by default. This choice only affects which map widget you use to *find and preview* the location; picking a place on the Google widget shows a static preview image with the note "Dragging the pin needs the OpenStreetMap provider — this map is a static preview," while OpenStreetMap gives you an interactive, draggable pin.
Type into **Search for a place...** and pick a result from the autocomplete dropdown. The map recenters on that location and drops a pin, showing the full formatted address and its latitude/longitude underneath.
A location target is required before you can publish.
**QR Code type** defaults to **Dynamic** (editable later, trackable, supports [Advanced settings](/qr-codes/manage/age-restriction)). Switching to **Static** encodes the location directly — it can't be edited or tracked afterward, and it drops the Short URL and Advanced settings sections entirely, since those only exist for Dynamic codes.
If you keep Dynamic, a **Short URL** section appears where you set a slug (or a [custom domain](/qr-codes/manage/custom-domains), if configured) — required before publishing.
Click **Publish**. A panel opens to confirm or rename the QR Code (it defaults to the location's formatted address), assign a folder or tags, and adjust QR Code design before confirming.
## Creating one via the API
To create a Google Maps QR Code through the API rather than the UI, use category ID `11` (labeled **Google Maps**, slug `map`), with an `info` payload shaped like:
```json theme={null}
{
"type": "map",
"data": {
"provider": "google",
"latitude": "37.8176155",
"longitude": "-122.4783123",
"placeId": "323254185",
"placeName": "Golden Gate Bridge, Golden Gate Bridge East Sidewalk, San Francisco, California, 94129, United States"
}
}
```
`data.provider` is confirmed to always be `"google"` in the stored payload, even for a QR Code created against the OpenStreetMap widget — the provider toggle in the UI only changes which map is used to pick the location, not what's saved. `placeId` is sourced from whichever provider you searched with, so its format varies (a numeric OpenStreetMap-style ID in the example above, rather than a typical Google Place ID string).
Both `qr_type: "dy"` and `qr_type: "st"` are accepted for this category — Static is a selectable option alongside the Dynamic default. See [Create QR Code](/api-reference/management-api/qr/create) for the full request schema and [Category List](/v1/api-reference/references/category-list) for the complete ID-to-label mapping (Google Maps is listed there as category `11`).
## Related
* [Creating your first QR Code](/getting-started/first-qr-code) — a full walkthrough of the Quick Code flow this type belongs to
* [Quick Code vs. full Page](/qr-codes/create/quick-code-vs-page) — how Google Maps and the other Quick Code types compare to a full landing page
* [Custom domains](/qr-codes/manage/custom-domains) — use your own domain for the Dynamic short URL
* [Folders, tags & bulk operations](/qr-codes/manage/folders-tags-bulk-operations) — organize the QR Code after publishing it
# GS1 QR Code
Source: https://docs.scanova.io/qr-codes/create/gs1
Create a GS1 Digital Link QR Code for a product, encoding its GTIN and other application identifiers, and resolving each scan to the pages you choose.
## What it's for
A GS1 QR Code encodes a [GS1 Digital Link](https://ref.gs1.org/standards/digital-link/) — a URI built from GS1 Application Identifiers (AIs) that identifies a product, location, shipment, or other supply-chain object. In the app, the category is described as "GS1 Application Identifier codes for products, locations & logistics."
Unlike the other Quick Code types on this page, GS1 isn't a single-screen form: it's a dedicated 5-step wizard at `/qr/quick/gs1`, separate from the generic Quick Code shell, because a GS1 code's data is structured (a primary identifier plus optional qualifiers, extra attributes, and resolution targets) rather than a handful of flat fields.
GS1 is the one Quick Code category that can be either **Dynamic** or **Static** — which one depends entirely on the resolver you pick in step 1, not on a separate type toggle. See **Step 1: Resolver** below.
## The 5 steps
The wizard's step indicator shows **Resolver → Identify → Data Attributes → Resolution → Review**. The **Resolution** step only appears when you choose a Scanova-hosted resolver (**Scanova Resolver** or **Your Custom Domain**) in step 1 — pick **GS1 Global Resolver** or **Custom Resolver** instead, and step 4 is skipped entirely, since Scanova has no destination to configure for a code it doesn't resolve.
### 1. Resolver — who resolves this code?
This decides which domain appears in the final digital link, and whether the resulting QR Code is Dynamic or Static:
| Option | Domain | QR Code type | Notes |
| :- | :- | :- | :- |
| Scanova Resolver | `scnv.io` | Dynamic | Default. Hosted by Scanova — works out of the box. |
| Your Custom Domain | One of your verified custom domains | Dynamic | Requires a custom-domain plan quota; locked (with an upgrade prompt) otherwise. |
| GS1 Global Resolver | `id.gs1.org` | Static | Resolved by GS1's own infrastructure, not Scanova. |
| Custom Resolver | A resolver domain you type in | Static | You control the resolver; Scanova only stores the identifier data. |
### 2. Identify — what does this code identify?
Pick the **Application Identifier** that matches what you're labeling (for example `(01) Global Trade Item Number (GTIN)`, the default), then enter its **Value**. The value field enforces the AI's real GS1 rules live — required length, digit-only vs. alphanumeric format, and, for AIs `00` and `01`, a valid GS1 check digit.
Each Application Identifier can also expose **Qualifiers** — for GTIN these are Serial Number (AI 21), Consumer Product Variant (AI 22), and Batch or Lot Number (AI 10), all optional. Next to Serial Number there's a "Need more than one? Bulk generate" link into [Bulk Operations](/qr-codes/manage/folders-tags-bulk-operations) for generating a range of serialized codes at once.
### 3. Data Attributes
An optional step for attaching extra GS1 AI-keyed values to the same code — dates, measurements, and similar. Most GS1 codes don't need this; the step starts collapsed with an "Add data attributes" toggle rather than empty fields.
### 4. Resolution (Scanova-hosted resolvers only)
Configure what the code actually resolves to when scanned. A **Product Page** entry is always present and effectively required — it's the destination a bare scan lands on. You can add further link types (**Datasheet**, **Video**, **Custom**) that GS1-aware apps can discover separately.
For each entry, choose:
* **Simple URL** — a bare destination URL, or
* **Existing QR** — reference one of your other dynamic, non-GS1 QR Codes (a Website URL, a page-builder QR, a Document, or an App Store QR, depending on the intent you pick)
The Product Page entry also offers **Auto-build a page** — this creates a real landing-page QR Code from the AI values you've already entered (after you pick a theme in a preview dialog), then wires it in as that entry's "Existing QR" reference, so it stays a normal page-builder QR Code you can keep editing afterward.
A GS1 code can never reference another GS1 code as a resolution target — this option is filtered out of the picker entirely, not just discouraged.
### 5. Review & publish
Shows the assembled **Digital link preview** (e.g. `https://scnv.io/LpqE/01/04012345678901`) and a plain-language summary of what the code identifies, who resolves it, and where a scan lands. You can also rename the QR Code here before publishing.
## Create a GS1 QR Code
From the sidebar, go to **QR Codes**, then click **Create QR Code** (or navigate to `/qr/create`). Click the **Quick Code** card.
On the **Quick Code** type grid, click **Configure** under **GS1**, in the **Dynamic** section at the top. This opens the wizard at `/qr/quick/gs1`.
On the **Resolver** step, pick who resolves the code. **Scanova Resolver** is selected by default and requires no further setup — click **Continue**.
On the **Identify** step, pick an Application Identifier and enter its value (for example, a 14-digit GTIN). Add any qualifiers you need, then **Continue**.
Skip this step unless you need to attach extra GS1 attributes, then **Continue**.
If you chose a Scanova-hosted resolver, set at least a **Product Page** destination — either a Simple URL, an existing QR Code, or an auto-built page — then **Continue**.
Check the assembled digital link and summary, adjust the QR Code's name if needed, then click **Publish**. A panel opens to confirm the QR Code's folder and tags, and adjust QR Code design, before confirming.
## Creating one via the API
To create a GS1 QR Code through the API rather than the UI, use category ID `31` (labeled **GS1** in the [Create QR Code](/api-reference/management-api/qr/create) **Category dropdown reference** table, `allowed_qr_types: "bt"` — both `"dy"` and `"st"` are valid for `qr_type`, matching whichever resolver you choose). The `info` payload is a **bare array** of typed blocks, one per wizard step that has data:
```json theme={null}
[
{
"type": "resolver",
"data": { "name": "scanova_resolver", "domain": "scnv.io" }
},
{
"type": "primaryKey",
"data": { "id": "01", "value": "04012345678901" }
},
{
"type": "qualifier",
"data": [{ "id": 21, "value": "SER456" }]
},
{
"type": "dataAttributes",
"data": [{ "id": "3103", "value": "000195" }]
},
{
"type": "resolution",
"data": {
"linkType": "pip",
"name": "simple_url",
"target": { "url": "https://acme.com/products/widget-42" }
}
}
]
```
`qualifier`, `dataAttributes`, and `resolution` blocks are each optional and repeatable (one `resolution` block per configured link type); `resolver` and `primaryKey` are required. A `resolution` block's `target` is either `{ "url": "..." }` (when `name` is `"simple_url"`) or `{ "qrid": "..." }` (when `name` is `"qr_reference"`) — `resolution` blocks are omitted entirely for `gs1`/`custom_resolver` resolvers, which have no Scanova-hosted destination to store.
See [Create QR Code](/api-reference/management-api/qr/create) for the full request schema.
## Related
* [Product QR Code](/qr-codes/create/product) — a marketing landing page for a product, versus GS1's structured supply-chain identifier
* [Folders, tags & bulk operations](/qr-codes/manage/folders-tags-bulk-operations) — bulk-generate a range of serialized GS1 codes, and organize codes after publishing
* [Quick Code vs. full Page](/qr-codes/create/quick-code-vs-page) — how GS1 fits among the other Quick Code types
# Phone Number QR Code
Source: https://docs.scanova.io/qr-codes/create/phone-number
Create a QR Code that opens the scanner's dialer with your number already entered, so a call is one tap away from a poster, a card, or your packaging.
## What it's for
A Phone Number QR Code opens the device's dialer with a number already filled in, so scanning it is a one-tap way to call a business, support line, or contact. It's a **Quick Code** — a direct-action QR Code with no landing page.
In the app, this type's tile and page title read **"Phone Call"** rather than "Phone Number" — the underlying category (and this page) still calls it Phone Number, matching the `name` returned for this category by the `/qr/category/` API endpoint.
Phone Number is **Static only** — there's no Dynamic option for this type. Once published, the encoded number can't be edited or tracked; to change it, create a new QR Code.
## Create a Phone Number QR Code
From the sidebar, go to **QR Codes**, then click **Create QR Code** (or navigate to `/qr/create`). Click the **Quick Code** card.
On the **Quick Code** type grid, click **Configure** under **Phone Call**, in the **Static Only** section — described there as "Open the dialer pre-filled with your number." This opens the form at `/qr/quick/phone`.
Type the number into the single **Phone number** field (placeholder: `+1 415 555 0100`). This is the only field on the form and is required to publish.
The field is validated on blur: it accepts digits, spaces, parentheses, dashes, and an optional leading `+`, and needs between 6 and 15 digits overall (matching the E.164 maximum length). An entry like `123` fails with **"Please enter a valid phone number."**
Click **Publish**. A panel opens to confirm or rename the QR Code (it defaults to "Call \"), assign a folder or tags, and adjust QR Code design before confirming.
## Creating one via the API
To create a Phone Number QR Code through the API rather than the UI, use category ID `4` (labeled **Phone Number**, `allowed_qr_types: "st"` — only `"st"` is valid for `qr_type`), with an `info` payload shaped like:
```json theme={null}
{
"type": "phoneNumber",
"data": {
"number": "+14155550142"
}
}
```
The backend accepts any string for `number` (no format validation server-side) — the phone-format check described above is client-side only, to guarantee scanning devices can actually dial the result. See [Create QR Code](/api-reference/management-api/qr/create) for the full request schema.
The [Category List](/v1/api-reference/references/category-list) reference table doesn't currently list Phone Number (or the other Quick Code–only categories on this page) — the ID above was confirmed directly against the live `/qr/category/` endpoint.
## Related
* [Text Message QR Code](/qr-codes/create/text-message) — uses the same phone-number validation to pre-fill an SMS instead of a call
* [Creating your first QR Code](/getting-started/first-qr-code) — a full walkthrough of the Quick Code flow this type belongs to
* [Folders, tags & bulk operations](/qr-codes/manage/folders-tags-bulk-operations) — organize the QR Code after publishing it
# Product QR Code
Source: https://docs.scanova.io/qr-codes/create/product
Create a QR Code that opens a product landing page, built from the Product templates, with images, specifications, and a link through to purchase one.
## What it's for
A product page showcases a single item — packaging, an artisan good, a tech launch — with an image gallery, a description, a call-to-action button (e.g. "Shop the Collection"), share buttons, and optionally a map for pickup locations. Shoppers scan it from packaging, a shelf-talker, or a print ad to land on a page with more detail than a barcode or a Website URL redirect can carry.
Like every category on this page, "Product QR Code" isn't a separate creation route — it's the **Build a Page** flow filtered to product-tagged templates.
## Find a product template and reach the builder
From **QR Codes → Create QR Code** (`/qr/create`), click the **Build a Page** card to open the block-based page builder's template gallery.
On **Choose a template** (`/qr/new`), click the **Product** tag. This tag filters to: **Tech Product Launch**, **Handmade Goods**, and **Flash Drop**.
Clicking a card opens a preview panel with the template's name, description, category tag, and a **Use this template** button. The **Handmade Goods** template was used for verification here — described as "Sage and cream — an organic shop page for small-batch artisan work" and tagged **Product**.
Selecting it opens the builder at `/qr/new/landing?template=product-artisan-goods`. This template ships with 5 blocks: **Image Gallery**, **Description**, **Button**, **Share Buttons**, and **Map** — all editable from the left-hand blocks panel, with page settings (URL slug, password protection) on the right.
Reaching this canvas confirms the flow — you don't need to publish to verify it works. Edit the gallery images, description copy, and button link, then use **Publish** from the top bar when the page is ready.
## Creating one via the API
To create a product-style QR Code through the API rather than the UI, use category ID `18` (labeled **Product**) — see [Create QR Code](/api-reference/management-api/qr/create) for the full request schema and [Category List](/v1/api-reference/references/category-list) for the complete ID-to-label mapping.
## Related
* [GS1 QR Code](/qr-codes/create/gs1) — a structured supply-chain identifier for a product, rather than a marketing landing page
* [Page Builder overview](/page-builder/overview) — the block-based editor this template opens into
* [Templates](/page-builder/templates) — browsing, filtering, and saving your own templates in the gallery
* [Folders, tags & bulk operations](/qr-codes/manage/folders-tags-bulk-operations) — organize the QR Code after publishing it
# Quick Code vs. full Page
Source: https://docs.scanova.io/qr-codes/create/quick-code-vs-page
How to choose between a Quick Code, a page built in the block editor, and AI Studio, and what each route can and cannot do once the code is published.
Every QR Code in Scanova starts from the same hub — `/qr/create` — where you choose one of three starting points before anything else. Picking correctly up front avoids rebuilding a QR Code later, since a **Quick Code** and a **Page** lead to different editors with different capabilities.
## Quick Code — direct action, no landing page
A **Quick Code** does something the instant it's scanned: it redirects to a URL, opens the dialer, drops in a Wi-Fi network, composes an email or text, or shows plain static text. Nothing is rendered in the browser first — there's no landing page to design, host, or theme.
Scanova's own copy for this card: *"Direct-action QR Codes for Website URL, Map (Google & OpenStreet), App Store, Deep Link, and GS1, plus static-only types like phone, email, and Wi-Fi. No landing page needed."* — estimated at under 30 seconds to create.
Clicking **Start** on Quick Code opens a grid of types to choose from: **Website URL**, **Google Maps**, **App Store**, **App Deep Link**, and **GS1** support Dynamic (editable, trackable) QR Codes, while **Phone Call**, **Email**, **Text Message**, **Wi-Fi**, **Plain Text**, **Event Calendar**, and **Contact Card** are Static only. Picking a type (e.g. **Website URL**) opens its configuration form directly — e.g. `/qr/quick/url` — with no page-builder step in between.
**Choose Quick Code when** the scan should trigger an immediate action and you don't need branded content, multiple sections, or analytics beyond scan counts.
## Build a Page — the block-based landing page builder
**Build a Page** is Scanova's copy: *"Block-based landing page builder. Start from a template or blank canvas."* — estimated at 2–5 minutes. Clicking **Start** takes you to `/qr/new`, a **Choose a template** gallery filtered by category (Business Card, Restaurant, Wedding, Event, Product, Social Media, and more, or **Start Blank**):
Picking a template (or Start Blank) opens the page builder itself at `/qr/new/landing`, where you assemble the page from content blocks (hero, schedule, gallery, forms, and more — see the [Page Builder overview](/page-builder/overview)) before publishing.
**Choose Build a Page when** the QR Code should show a full landing page — multiple content blocks, your own branding, or something more than a single redirect.
## AI Studio — AI-generated pages
**AI Studio** works like Build a Page but skips manual block assembly: you describe what you want ("Wedding for Mary & John, June 15 at Riverside Estate") and AI generates the blocks and copy, which you can then refine by re-describing or publish as-is — estimated at \~1 minute.
Access to AI Studio depends on your plan. If the card shows **Upgrade** rather than **Start**, your plan does not include AI landing-page generation — check your plan's feature list, or contact support. See [AI QR / AI Studio](/qr-codes/create/ai-qr) for the prompt screen as it appears once the feature is available.
## Quick reference
| | Quick Code | Build a Page | AI Studio |
| - | - | - | - |
| Result | Direct action (redirect, dial, join Wi-Fi, etc.) | Multi-block landing page | AI-generated landing page |
| Editor | A single configuration form | Block-based page builder | AI prompt, then the page builder |
| Typical time | \< 30s | 2–5 min | \~1 min to generate |
| Editable after publishing | Dynamic types: yes. Static types: no | Yes | Yes |
## Creating one via the API
Both paths ultimately create a QR Code through the same [Create QR Code](/api-reference/management-api/qr/create) endpoint — the `category` field determines whether it behaves as a Quick Code (e.g. category `1` for Website URL) or a Page. See [Category List](/v1/api-reference/references/category-list) for the full mapping.
## Related
* [Creating your first QR Code](/getting-started/first-qr-code) — a full walkthrough of the Quick Code path described above
* [Page Builder overview](/page-builder/overview) — a closer look at the block-based editor behind Build a Page
* [AI QR / AI Studio](/qr-codes/create/ai-qr) — a closer look at the AI-generated path summarized above
* [Website URL QR Code](/qr-codes/create/website-url) — the simplest Quick Code type to start with
# Restaurant QR Code
Source: https://docs.scanova.io/qr-codes/create/restaurant
Create a QR Code that opens a restaurant landing page, built from the Restaurant templates, with your menu, your opening hours, and your contact details.
## What it's for
A restaurant page is a landing page built for dine-in, takeout, or delivery businesses. Instead of a single menu image, you build a full page from blocks — brand info, a structured menu, a feedback form, an "About Us" section, review links, and social profiles — that guests reach by scanning a table tent, door sticker, or receipt.
There's no separate "Restaurant QR Code" creation route. It's reached through the same **Build a Page** flow used for every page-builder QR: pick a restaurant-tagged template from the gallery (or start from a blank canvas and add the same blocks yourself), then customize it in the page builder.
## Find a restaurant template and reach the builder
From the sidebar, go to **QR Codes**, then click **Create QR Code** (or navigate to `/qr/create`).
On the create hub, click the **Build a Page** card. This is the block-based landing page builder — it's distinct from **Quick Code** (a direct QR Code with no landing page) and **AI Studio** (AI-generated blocks from a text prompt).
You land on **Choose a template** (`/qr/new`), a searchable, tag-filterable gallery. Click the **Restaurant** tag to filter to restaurant-tagged templates. The tag filters to **Fine Dining** and **Casual Cafe**.
Both templates open normally. **Fine Dining** is described as "Timeless blue and ivory — an elegant page for an upscale restaurant"; **Casual Cafe** as "Fresh greens — a bright, friendly page for a cafe or casual eatery."
Click a template card to open its preview panel, which shows the template name, a one-line description, its category tag, and a **Use this template** button. Click **Use this template** to continue.
You're dropped into the builder at `/qr/new/landing?template=restaurant-casual-cafe` (the query string encodes the template you picked). The **Casual Cafe** template ships with 7 blocks: **Brand Info**, **Menu**, **Feedback Form**, **About Us**, **Review Us**, **Social Profiles**, and **Footer** — all editable from the left-hand blocks panel.
You don't need to fully publish to have a working page — the builder auto-manages a draft. Reaching this canvas is enough to confirm the flow works; edit content, theme, and SEO settings from the right-hand panel and click **Publish** when ready.
## Creating one via the API
Page-builder pages are still QR Codes with a category. When creating a restaurant-style QR Code through the API (rather than the UI), use category ID `25` (labeled **Restaurant**). See [Create QR Code](/api-reference/management-api/qr/create) for the full request schema, and [Category List](/v1/api-reference/references/category-list) for how category IDs map to labels.
Use category ID `25` for Restaurant. Some older references list a second ID, `44`; it is not a live category.
## Related
* [Feedback QR Code](/qr-codes/create/feedback) — a dedicated feedback-form page, including a template built specifically for dine-in feedback
* [Page Builder overview](/page-builder/overview) — the block-based editor this template opens into
* [Templates](/page-builder/templates) — browsing, filtering, and saving your own templates in the gallery
* [Folders, tags & bulk operations](/qr-codes/manage/folders-tags-bulk-operations) — organize the QR Code after publishing it
# Simple Text QR Code
Source: https://docs.scanova.io/qr-codes/create/simple-text
Create a Plain Text QR Code that shows a short message when scanned, with no internet connection needed, because the text is encoded in the code itself.
## What it's for
A Simple Text QR Code shows plain text on the scanning device — a note, an ingredient list, a table number, a short instruction — with no internet connection required to display it. It's a **Quick Code** — a direct-action QR Code with no landing page. In the app, the tile for it reads "Show static text on scan — no internet needed," and the page itself is labeled **Plain Text**.
Don't confuse this with a vCard/contact-style "simple" QR Code elsewhere in the app (**Contact Card**, the `simple-vcard` type). Simple Text only ever encodes a block of plain text — no contact fields, no structure.
Simple Text is **Static only** — there's no Dynamic option for this type. Once published, the encoded text can't be edited or tracked; to change it, create a new QR Code.
## Create a Simple Text QR Code
From the sidebar, go to **QR Codes**, then click **Create QR Code** (or navigate to `/qr/create`). Click the **Quick Code** card.
On the **Quick Code** type grid, click **Configure** under **Plain Text**, in the **Static Only** section. This opens the form at `/qr/quick/text`.
Type the text to display into the **Text content** field — a multi-line textarea with the placeholder "Type your message here…". This is the only field on the form and is required to publish.
Below the field, a callout reminds you this type has no editable or trackable alternative: "Need dynamic tracking or editable content? Plain Text QR Codes are always static. Try Build a Page for a QR Code you can track, edit, and update after publishing."
Click **Publish**. A panel opens to confirm or rename the QR Code (it defaults to "Plain Text" regardless of the content you entered), assign a folder or tags, and adjust QR Code design before confirming.
## Creating one via the API
To create a Simple Text QR Code through the API rather than the UI, use category ID `3` (labeled **Simple Text**, `allowed_qr_types: "st"` — only `"st"` is valid for `qr_type`), with an `info` payload shaped like:
```json theme={null}
{
"type": "text",
"data": {
"text": "Table 12 — thanks for dining with us!"
}
}
```
See [Create QR Code](/api-reference/management-api/qr/create) for the full request schema.
The [Category List](/v1/api-reference/references/category-list) reference table doesn't currently list Simple Text (or the other Quick Code–only categories on this page) — the ID above was confirmed directly against the live `/qr/category/` endpoint.
## Related
* [Creating your first QR Code](/getting-started/first-qr-code) — a full walkthrough of the Quick Code flow this type belongs to
* [Quick Code vs. full Page](/qr-codes/create/quick-code-vs-page) — how Simple Text compares to a full landing page
* [Folders, tags & bulk operations](/qr-codes/manage/folders-tags-bulk-operations) — organize the QR Code after publishing it
# Social Media QR Code
Source: https://docs.scanova.io/qr-codes/create/social-media
Create a QR Code that opens a link in bio style landing page, built from the Social Media templates, gathering all of your profiles in a single place.
## What it's for
A social media page is a "link in bio"-style hub: one scan lands visitors on a page listing your social profiles (Instagram, TikTok, YouTube, X, LinkedIn, Pinterest, and similar) plus optional call-to-action buttons — "Watch My Latest Video," "Join My Free Newsletter." It's built for creators, agencies, and personal brands that want a single printable code linking out to everywhere they post, rather than one QR Code per platform.
Like every category on this page, "Social Media QR Code" isn't a separate creation route — it's the **Build a Page** flow filtered to Social Media–tagged templates.
## Find a social-media template and reach the builder
From **QR Codes → Create QR Code** (`/qr/create`), click the **Build a Page** card to open the block-based page builder's template gallery.
On **Choose a template** (`/qr/new`), click the **Social Media** tag. This tag filters to: **Link in Bio** and **Brand Hub**.
Clicking a card opens a preview panel with the template's name, a short style description, its category tag, and a **Use this template** button. The **Link in Bio** template was used for verification here — described as "Deep dark canvas — a personal creator hub for all your links" and tagged **Social Media**.
Selecting it opens the builder at `/qr/new/landing?template=smedia-link-in-bio`. This template ships with 7 blocks: **Company Branding**, **Page Title**, **Description**, three **Button** blocks (e.g. "Watch My Latest Video," "Join My Free Newsletter," "Book a 1:1 Mentoring Call"), and **Social Profiles** — all editable from the left-hand blocks panel.
Reaching this canvas confirms the flow — you don't need to publish to verify it works. Edit the profile text, button links, and social profile URLs, then use **Publish** from the top bar when the page is ready.
## Creating one via the API
To create a social-media-style QR Code through the API rather than the UI, use category ID `15` (labeled **Social Media**) — see [Create QR Code](/api-reference/management-api/qr/create) for the full request schema and [Category List](/v1/api-reference/references/category-list) for the complete ID-to-label mapping.
## Related
* [Page Builder overview](/page-builder/overview) — the block-based editor this template opens into
* [Templates](/page-builder/templates) — browsing, filtering, and saving your own templates in the gallery
* [Creating your first QR Code](/getting-started/first-qr-code) — the general Create QR Code flow this category is reached from
* [Folders, tags & bulk operations](/qr-codes/manage/folders-tags-bulk-operations) — organize the QR Code after publishing it
# Text Message QR Code
Source: https://docs.scanova.io/qr-codes/create/text-message
Create a QR Code that opens the scanner's messaging app with your number and a pre written message ready to go, so all they need do is press send now.
## What it's for
A Text Message QR Code opens the device's SMS composer with both the recipient number and a message already filled in, so scanning it is a one-tap way to text a business — a feedback line, an order number, an opt-in keyword. It's a **Quick Code** — a direct-action QR Code with no landing page. In the app, the tile for it reads "Open SMS composer with a pre-filled message."
Text Message is **Static only** — there's no Dynamic option for this type. Once published, the encoded number and message can't be edited or tracked; to change them, create a new QR Code.
## Create a Text Message QR Code
From the sidebar, go to **QR Codes**, then click **Create QR Code** (or navigate to `/qr/create`). Click the **Quick Code** card.
On the **Quick Code** type grid, click **Configure** under **Text Message**, in the **Static Only** section. This opens the form at `/qr/quick/sms`.
Fill in both fields:
* **Phone number** (placeholder `+1 415 555 0100`) — validated on blur the same way as the [Phone Number](/qr-codes/create/phone-number) type: digits, spaces, parentheses, dashes, and an optional leading `+`, with 6–15 digits total
* **Message** (placeholder "Hello!") — a single-line text field, sent as the SMS body
Both fields are required to enable publishing, though neither is visually marked with a required indicator on the form itself.
Click **Publish**. A panel opens to confirm or rename the QR Code (it defaults to "SMS \"), assign a folder or tags, and adjust QR Code design before confirming.
## Creating one via the API
To create a Text Message QR Code through the API rather than the UI, use category ID `5` (labeled **Text Message**, `allowed_qr_types: "st"` — only `"st"` is valid for `qr_type`), with an `info` payload shaped like:
```json theme={null}
{
"type": "sms",
"data": {
"contactNumber": "+14155550142",
"message": "Thanks for scanning — here's 10% off your next visit."
}
}
```
Both `contactNumber` and `message` are required by the backend schema. See [Create QR Code](/api-reference/management-api/qr/create) for the full request schema.
The [Category List](/v1/api-reference/references/category-list) reference table doesn't currently list Text Message (or the other Quick Code–only categories on this page) — the ID above was confirmed directly against the live `/qr/category/` endpoint.
## Related
* [Email QR Code](/qr-codes/create/email) — a similar direct-action type, opening the mail app instead of the SMS composer
* [Phone Number QR Code](/qr-codes/create/phone-number) — the same phone-number validation, used to open the dialer instead
* [Creating your first QR Code](/getting-started/first-qr-code) — a full walkthrough of the Quick Code flow this type belongs to
# Website URL QR Code
Source: https://docs.scanova.io/qr-codes/create/website-url
Create a QR Code that redirects to any web address, as a Dynamic code you can edit and track after printing, or as a Static code that encodes the link.
## What it's for
A Website URL QR Code redirects anyone who scans it straight to a web address — a landing page, a product listing, a menu hosted elsewhere, anything with a URL. It's the simplest Quick Code: no landing page is built, the QR Code just encodes (or, for a Dynamic code, redirects to) the link you provide.
This is a **Quick Code** — a direct-action QR Code with no landing page, distinct from **Build a Page** (the block-based landing page builder). See [Quick Code vs. full Page](/qr-codes/create/quick-code-vs-page) for when to use which.
## Create a Website URL QR Code
From the sidebar, go to **QR Codes**, then click **Create QR Code** (or navigate to `/qr/create`). Click the **Quick Code** card — described as "Direct-action QR Codes for Website URL, Map (Google & OpenStreet), App Store, Deep Link, and GS1, plus static-only types like phone, email, and Wi-Fi. No landing page needed."
On the **Quick Code** type grid, click **Configure** under **Website URL**, in the **Dynamic** section at the top. This opens the form at `/qr/quick/url`.
Type the destination address into the **URL** field. The input has a fixed `https://` prefix, so you only need to enter the rest (e.g. `example.com`) — the placeholder shows `example.com`. An invalid or empty URL blocks publishing.
Website URL is the one Quick Code type that supports both **Dynamic** (editable later, trackable, supports [Conditional Redirection](/qr-codes/manage/conditional-redirection) and UTM tracking) and **Static** (encodes the URL directly; can't be edited or tracked after publishing) — every other Quick Code type on this page is Static only. Dynamic is selected and recommended by default.
If you keep Dynamic, a **Short URL** section appears where you can set a slug (or a [custom domain](/qr-codes/manage/custom-domains), if configured) — a slug is required before a Dynamic code can be published.
Click **Publish** in the top-right. A panel opens where you can confirm or rename the QR Code (it defaults to "Your QR Code", so rename it here if you want something more specific), assign a folder or tags, and adjust QR Code design before confirming.
## Creating one via the API
To create a Website URL QR Code through the API rather than the UI, use category ID `1` (labeled **Website URL**, `allowed_qr_types: "bt"` — it accepts both `"dy"` and `"st"` for `qr_type`), with an `info` payload shaped like:
```json theme={null}
{
"type": "url",
"data": {
"url": "https://example.com"
}
}
```
See [Create QR Code](/api-reference/management-api/qr/create) for the full request schema and [Category List](/v1/api-reference/references/category-list) for the complete ID-to-label mapping.
## Related
* [Creating your first QR Code](/getting-started/first-qr-code) — this exact QR Code type walked through end-to-end from a fresh login
* [Quick Code vs. full Page](/qr-codes/create/quick-code-vs-page) — how Website URL compares to a full landing page
* [Folders, tags & bulk operations](/qr-codes/manage/folders-tags-bulk-operations) — organize the QR Code after publishing it
* [Conditional redirection](/qr-codes/manage/conditional-redirection) — route scans by device, location, or time once the code is Dynamic
# Wedding QR Code
Source: https://docs.scanova.io/qr-codes/create/wedding
Create a QR Code that opens a wedding landing page, built from the Wedding templates, with your schedule, venue, RSVP, and travel details for your guests.
## What it's for
A wedding page turns a printed invitation, save-the-date, or table card into a full digital hub for the event: couple names, a description of how you met, an image gallery, ceremony and reception schedule, venue details, an **RSVP form** guests fill out right on the page, and a button linking to a registry. It's meant to replace (or supplement) a standalone wedding website with something guests reach by scanning a code on the physical invite.
Like every category on this page, "Wedding QR Code" isn't a separate creation route — it's the **Build a Page** flow filtered to Wedding-tagged templates.
## Find a wedding template and reach the builder
From **QR Codes → Create QR Code** (`/qr/create`), click the **Build a Page** card to open the block-based page builder's template gallery.
On **Choose a template** (`/qr/new`), click the **Wedding** tag. This tag filters to: **Royal Affair**, **Blush Romance**, and **Rustic Bloom**. Searching "wedding" in the search box returns the same 3 templates, even with the **All** tag still selected.
Clicking a card opens a preview panel with the template's name, a short style description, its category tag, and a **Use this template** button. The **Royal Affair** template was used for verification here — described as "Champagne gold on near-black — a formal, black-tie ballroom wedding" and tagged **Wedding**.
Selecting it opens the builder at `/qr/new/landing?template=wed-royal-affair`. This template ships with 8 blocks: **Page Title**, **Image Gallery**, **Couple Names**, **Description**, **Event Schedule**, **Event Venue**, **RSVP**, and a **Button** (e.g. "View Our Registry") — all editable from the left-hand blocks panel.
Reaching this canvas confirms the flow — you don't need to publish to verify it works. Edit the couple's names, ceremony schedule, venue, and RSVP form, then use **Publish** from the top bar when the page is ready.
## Creating one via the API
To create a wedding-style QR Code through the API rather than the UI, use category ID `14` (labeled **Wedding**) — see [Create QR Code](/api-reference/management-api/qr/create) for the full request schema and [Category List](/v1/api-reference/references/category-list) for the complete ID-to-label mapping.
## Related
* [Page Builder overview](/page-builder/overview) — the block-based editor this template opens into
* [Templates](/page-builder/templates) — browsing, filtering, and saving your own templates in the gallery
* [Event QR Code](/qr-codes/create/event) — a similar schedule-and-venue page structure, built for conferences and festivals instead
* [Folders, tags & bulk operations](/qr-codes/manage/folders-tags-bulk-operations) — organize the QR Code after publishing it
# Wi-Fi QR Code
Source: https://docs.scanova.io/qr-codes/create/wifi
Create a Wi-Fi QR Code that connects the scanner to your network automatically, with the network name, the security type, and the password encoded in it.
## What it's for
A Wi-Fi QR Code lets a phone camera join a wireless network automatically — no typing the SSID or password by hand. It's a **Quick Code**: a direct-action QR Code with no landing page, built from a small form rather than the page builder. The tile for it in the app reads "Connect visitors to your Wi-Fi network instantly."
Wi-Fi is **Static only** — there's no Dynamic option for this type. Once published, the encoded network credentials can't be edited or tracked; to change them, create a new QR Code.
## Create a Wi-Fi QR Code
From the sidebar, go to **QR Codes**, then click **Create QR Code** (or navigate to `/qr/create`). Click the **Quick Code** card.
On the **Quick Code** type grid, click **Configure** under **Wi-Fi**, in the **Static Only** section. This opens the form at `/qr/quick/wifi`.
Type the network's SSID into **Network name (SSID)** — this is the only required field. The placeholder shows "My Network".
The **Security type** dropdown defaults to **WPA2** and also offers **WPA**, **WEP**, and **None (open network)**. Selecting **None** hides the **Password** field entirely, since an open network has no password to encode.
For any security type other than None, enter the network's password in the **Password** field. If the network doesn't broadcast its SSID, turn on **Hidden network (SSID is not broadcast)** so scanning devices know to join it by name instead of picking it from a visible list.
Click **Publish**. A panel opens to confirm or rename the QR Code (it defaults to "Wi-Fi: \"), assign a folder or tags, and adjust QR Code design before confirming.
## Creating one via the API
To create a Wi-Fi QR Code through the API rather than the UI, use category ID `6` (labeled **WiFi**, `allowed_qr_types: "st"` — only `"st"` is valid for `qr_type`), with an `info` payload shaped like:
```json theme={null}
{
"type": "wifi",
"data": {
"authentication": "WPA2",
"ssid": "Guest-WiFi",
"password": "SampleGuestPass123",
"hidden": false
}
}
```
`authentication` accepts `WPA`, `WPA2`, `WEP`, or `none`; `password` and `hidden` are optional (hidden defaults to `false`). See [Create QR Code](/api-reference/management-api/qr/create) for the full request schema.
The [Category List](/v1/api-reference/references/category-list) reference table doesn't currently list WiFi (or the other Quick Code–only categories on this page) — the ID above was confirmed directly against the live `/qr/category/` endpoint.
## Related
* [Creating your first QR Code](/getting-started/first-qr-code) — a full walkthrough of the Quick Code flow this type belongs to
* [Quick Code vs. full Page](/qr-codes/create/quick-code-vs-page) — how Wi-Fi and the other Quick Code types compare to a full landing page
* [Folders, tags & bulk operations](/qr-codes/manage/folders-tags-bulk-operations) — organize the QR Code after publishing it
# Dynamic and Static QR Codes
Source: https://docs.scanova.io/qr-codes/dynamic-vs-static
The difference between Dynamic and Static QR Codes: where the data lives, what can change after printing, which is tracked, and which types support each.
Every QR Code you make in Scanova is either **Dynamic** or **Static**. The difference is where the information lives, and it decides everything else — whether you can change the QR Code after printing it, and whether you can count the scans.
## The difference in one line
A **Static** QR Code stores its content inside the pattern itself. A **Dynamic** QR Code stores a short Scanova link, and the real destination sits on our side where it can be changed.
| | Dynamic | Static |
| :- | :- | :- |
| What the pattern holds | A short link that resolves through Scanova | The content itself |
| Change the destination after printing | Yes | No |
| Scan analytics | Yes — time, location, device | No |
| Works with no internet connection | No | Yes |
| Advanced settings (password, scheduling, redirection rules) | Yes | No |
| Pattern complexity | Always small, whatever the content | Grows with the amount of content |
| Needs an active subscription | Yes | No |
## Why you would pick each one
**Choose Dynamic when the QR Code outlives the decision behind it.** Anything printed, shipped, or mounted — packaging, signage, business cards, a poster campaign — because the destination will eventually need to move and you cannot reprint what is already in the world. It is also the only way to know whether a campaign worked, since a Static code produces no scan data at all.
**Choose Static when the content is the answer and will never change.** A Wi-Fi password on a café wall, a phone number, a plain text label. These work offline, they never expire, and they carry on working whether or not you have a subscription — nothing needs to resolve.
A Static QR Code cannot be edited once it exists. There is no setting to change, because the content is physically encoded in the pattern — changing it means generating a new QR Code and replacing whatever it was printed on.
## Which types are which
**Quick Codes** are split between the two:
* **Dynamic** — [Website URL](/qr-codes/create/website-url), [Google Maps](/qr-codes/create/google-maps), [App Store](/qr-codes/create/app-store), [App Deep Link](/qr-codes/create/app-deep-link), and [GS1](/qr-codes/create/gs1)
* **Static only** — [Phone Call](/qr-codes/create/phone-number), [Email](/qr-codes/create/email), [Text Message](/qr-codes/create/text-message), [Wi-Fi](/qr-codes/create/wifi), [Plain Text](/qr-codes/create/simple-text), [Event Calendar](/qr-codes/create/calendar-event), and [Contact Card](/qr-codes/create/contact-card)
Website URL is the one type where you choose: Dynamic is selected by default, and Static is available if you want it.
**Pages are always Dynamic.** Anything built in the [page builder](/page-builder/overview) or [AI Studio](/ai-studio/overview) resolves through Scanova by definition, since the landing page is hosted there.
GS1 is the exception to the pattern. It is Dynamic or Static depending on the resolver you pick in step 1 of its wizard — the Scanova resolver and a custom domain are both Dynamic, while the GS1 Global Resolver is Static because GS1's own infrastructure answers the scan. See [GS1 QR Code](/qr-codes/create/gs1).
## What only Dynamic can do
Several features exist only on Dynamic QR Codes, for the same underlying reason — they need a server-side step at scan time, and a Static code does not have one:
* [Analytics](/analytics/dashboard) — scan counts, geography, devices, and languages
* [Password protection](/qr-codes/manage/password-protection) and [age restriction](/qr-codes/manage/age-restriction)
* [Scheduling](/qr-codes/manage/scheduling) and [conditional redirection](/qr-codes/manage/conditional-redirection)
* [UTM parameters](/qr-codes/manage/utm-builder) and [custom domains](/qr-codes/manage/custom-domains)
Publishing a Static QR Code and then looking for these will show a notice rather than the setting.
## Related
* [Quick Code vs. full Page](/qr-codes/create/quick-code-vs-page) — the other choice you make at creation, and how it interacts with this one
* [Is Scanova free?](/getting-started/is-scanova-free) — Static QR Codes are free from the website generator; Dynamic needs a trial or subscription
* [When your trial or subscription ends](/billing/trial-and-expiry) — what happens to Dynamic QR Codes when a subscription lapses
* [Creating your first QR Code](/getting-started/first-qr-code) — the creation flow end to end
# Age restriction
Source: https://docs.scanova.io/qr-codes/manage/age-restriction
Put an age gate in front of a QR Code's landing page, asking each scanner for a date of birth before the content behind the code is ever shown to them.
## What age restriction controls
Age restriction gates a Dynamic QR Code's content behind a date-of-birth prompt — anyone who scans it must confirm they're at least a minimum age before the destination loads. It's a per-QR toggle, available wherever [Advanced settings](/qr-codes/manage/password-protection) live: the **Publish** panel while creating a Dynamic QR Code, or the **Settings** tab of an already-published one.
Age restriction is only available on **Dynamic** QR Codes. Publishing a Static QR Code (like [Wi-Fi](/qr-codes/create/wifi) or [Contact Card](/qr-codes/create/contact-card)) shows a notice instead — "Advanced settings need a Dynamic QR Code" — since Static codes have no server-side step to enforce a gate at.
## Turning it on
Open the QR Code and go to **Settings**. Under **Security & Access**, find **Age restriction** — "Restrict content for users below a certain age. Users will be prompted to enter their date of birth to continue."
Switching **Age restriction** on immediately expands a **Minimum Age** field, defaulting to `18`.
Enter the minimum age and click **Save**. The field's helper text confirms: "Users must be at least this age to view the content."
Turning the toggle off at any point removes the gate — no confirmation step, no save needed beyond the toggle itself.
## Setting it while creating a QR Code
The same control is available before publishing: in the **Publish** panel, open the **Advanced** tab, find **Age restriction** under **Security & Access**, and turn it on the same way. It saves along with the rest of the QR Code when you click **Publish**.
## Related
* [Password protection](/qr-codes/manage/password-protection) — the other content gate in the same Security & Access section, requiring a password instead of an age
* [GPS & geofencing](/qr-codes/manage/gps-geofencing) — a location-based restriction, in the same panel
* [QR Code scheduling](/qr-codes/manage/scheduling) — a time-based restriction, in the Targeting & Lifecycle section of the same Settings tab
# AI Design
Source: https://docs.scanova.io/qr-codes/manage/ai-design
Generate a QR Code design from a description, letting AI produce an on-brand pattern and colorway that you can refine before you publish or download it.
**AI Design** generates a full background image for a QR Code's pattern from a plain-text description, instead of picking colors and shapes by hand. Open it from the **Design** editor's top bar — switch from **Custom Design** to **AI Design**.
Switching to AI Design **clears your current custom design** — a confirmation dialog warns you before it discards anything you've built on the Custom Design tab.
## Generating a design
Type a description into the **Prompt** field (up to 200 characters), or click one of the six example chips — **Pizza with basil, surreal lighting**, **Clothing store in a mall**, **Starry Night city scene**, **Greek statue, ink art**, **Snowy house, pine trees**, or **Ocean ripples, brushstroke** — to use it as-is.
Use the optional **Negative Prompt** field to describe what to avoid in the generated image.
Click **Generate**. Two variations are produced in parallel, each shown as its own **Generation Result** card with a per-card status.
The disclaimer under the Generate button — "AI can make mistakes. Check the design before using it." — applies here the same way it does for [AI Studio](/ai-studio/overview): review a successful result for readability and contrast before publishing.
## Related
* [Custom QR Code design](/qr-codes/manage/custom-design) — the manual (non-AI) design editor this tab sits alongside, including its own **Smart Design** brand-color generator
* [AI Studio](/ai-studio/overview) — a different AI feature that generates a whole landing *page*, not a QR Code background image
* [Test QR Code](/qr-codes/manage/test-qr-code) — scan-verify an AI-generated design before using it, since a busy background can hurt scannability
# Bulk Operations
Source: https://docs.scanova.io/qr-codes/manage/bulk-operations
Run jobs across many QR Codes at once: create them from a spreadsheet, swap URLs, rename, re-skin, organize, delete, or export the images as a single ZIP.
**Bulk Operations** (`/bulk-operations`) is where you act on many QR Codes in one pass instead of opening them one at a time. Everything here runs as a background job, so you start it and leave — you get a progress card on the jobs list and an email when it finishes.
It's a top-level item in the sidebar, with [Media Library](#media-library) nested under it.
## The jobs list
The landing page is a record of every bulk job the account has run, with four counters across the top: **Total** (all time), **In Progress** (now running), **Completed** (recent), and **Failed** (needs attention).
Each row shows the **job** name, its **type** and category, **status**, a **progress** bar, when it **started**, and which **user** ran it. A finished job that produced a file shows a download control on its own row.
A job can take a minute to appear here after you start it, and the page does not refresh itself — reload before concluding that a job failed to queue.
## Starting a job
**New Operation** opens a chooser with seven job types, split into the four most used and three further down:
| Job | What it does |
| :- | :- |
| **Create QR Codes** | Generate many new QR Codes from a spreadsheet |
| **Update QR Codes** | Change where codes point — swap URLs in bulk from a spreadsheet |
| **Update Details** | Rename and re-tag codes without changing their URLs |
| **Download** | Export QR Code images as PNG, SVG, PDF, or EPS in a ZIP |
| **Apply Design** | Re-skin many codes with a saved design preset |
| **Organize** | Move codes between folders, or add and remove tags |
| **Delete** | Archive or permanently remove multiple codes |
The split matters when you're deciding which one you want: **Update QR Codes** changes destinations, **Update Details** deliberately doesn't. Renaming a hundred codes won't touch where any of them point.
## Creating QR Codes in bulk
Creation runs as a four-step wizard: **Category → Spreadsheet → Settings → Review**.
Bulk creation supports four categories: **Website URL**, **Business Card**, **Custom Page**, and **GS1**. Every code in a single job shares the category you pick here, so a mixed batch means more than one job.
Download the sample sheet, fill in one row per QR Code, and upload it back. The uploader accepts **CSV or XLSX, up to 10 MB**.
**The template is specific to the category you picked** — a Website URL sheet arrives with the columns `name`, `tags`, `custom_slug` and the destination URL, where a Business Card sheet asks for contact fields. Download it after choosing the category rather than reusing a sheet from an earlier job.
Everything here is optional — the header says so, and a job runs perfectly well with all five switched off.
| Setting | What it applies to every code in the job |
| :- | :- |
| **Add design** | A design from **In-built Templates** or your **Saved Templates** |
| **Add custom domain** | Your own domain in place of the default `scnv.io` short links |
| **Auto-organize into folder** | A folder to place every code from the job into |
| **Scheduling** | Activation and deactivation times |
| **Tracking parameters** | UTM parameters appended to every short link |
**Job name** is optional but worth filling in — it's what you'll scan for on the jobs list later, and the default batch names are just a timestamp.
Below it, a summary shows the category, the uploaded file, and each of the five settings as On, Off or None, so you can confirm the job before running it. **Start generation** queues it.
## Exporting images in bulk
**Download** is a three-step wizard — **Select codes → Format → Review** — and produces a single ZIP. You choose the codes with the same search and filters as the QR Codes list, including selecting everything that matches a filter rather than ticking rows one by one.
## Media Library
**Media Library** (`/media`) is where everything you have uploaded to the account lives, in one place rather than scattered across individual QR Codes.
You can search it, filter by **Type** and by **Source**, switch between list and grid, and upload new files directly. The sidebar nests it under Bulk Operations, though it has its own route.
## Related
* [Folders, tags & bulk operations](/qr-codes/manage/folders-tags-bulk-operations) — the bulk action bar on the QR Codes list, for smaller batches without a job
* [Download QR Code images](/qr-codes/manage/download) — exporting a single code and the formats available
* [Custom QR Code design](/qr-codes/manage/custom-design) — saving the design presets that Apply Design uses
* [Bulk endpoints](/api-reference/management-api/bulk-operation/list) — running the same jobs through the API
# Conditional redirection
Source: https://docs.scanova.io/qr-codes/manage/conditional-redirection
Send scans to different destinations based on the country, the device, the operating system, or the browser language of whoever scanned your Dynamic code.
## What conditional redirection does
Conditional redirection sends scanners to different destination URLs based on **Country**, **Device**, **Operating System**, or **Browser Language** — all from a single dynamic QR Code. It's a different feature from [QR Code scheduling](/qr-codes/manage/scheduling) and [GPS & geofencing](/qr-codes/manage/gps-geofencing): those live on a QR Code's **Settings** tab and react to time or location, while conditional redirection is wired into the **URL itself** — the field where you set what a link or button points to — and reacts to who's scanning.
Live UI wording: the modal is titled **Traffic Routing Rules**, with the subtext **"Sets are checked top to bottom. The first match wins."**
You'll find it in two places that both open the same modal:
* The **URL field of a link or button block** in the page builder
* The **Advanced settings** of a Website URL Quick Code, under a **Conditional Redirection** toggle
## Setting up a rule
While creating or editing a Website URL Quick Code, open **Advanced settings** and turn on the **Conditional Redirection** toggle. A **Configure rules** button appears.
Until at least one rule is added, the section shows "Add at least one rule, or turn this off — it won't do anything until then."
On a link or button block's URL field in the page builder, open the conditional rules option next to it.
In **Condition Set 1**, the first row is prefixed **IF**. Choose a field — **Country**, **Device**, **Operating System**, or **Browser Language** — and an operator, **Includes** or **Excludes**.
Use the **Search or select…** box to pick one or more values for that field (for example, several countries). Selected values appear as removable chips. Select **+ Add condition** to add another condition to the same set — additional conditions within one set are combined with AND.
Under **Redirect Users To**, enter the URL visitors matching this condition set should land on.
Select **+ Add Condition Set** to add another ordered set of conditions with its own redirect URL. Sets are evaluated top to bottom, and the first matching set wins — later sets are never checked once an earlier one matches.
Select **Save rules**.
## Default redirect for non-matching visitors
A **Default redirect** panel at the bottom of the modal shows the URL visitors are sent to when they don't match *any* condition set — this is always the primary URL already set on the button or link, shown read-only for reference. There's no separate field to configure it here; change the primary URL itself if you need to change this fallback.
## Saved templates
The modal has two tabs: **Set Up** (the rule sets described above) and **Saved Templates**. Select **Save as template** to store a configured set of rules by name so you can re-apply the same routing logic to other links or buttons later, instead of rebuilding it from scratch.
Any redirect URL you leave blank is dropped when you save — condition sets need a redirect URL to take effect. If you enter a URL, it's validated on save; an invalid URL blocks saving with a toast asking you to fix it.
## Related
* [QR Code scheduling](/qr-codes/manage/scheduling) — reacts to time instead of who's scanning, on the QR Code's Settings tab
* [GPS & geofencing](/qr-codes/manage/gps-geofencing) — reacts to visitor location instead of country/device/OS/language
* [Blocks reference](/page-builder/blocks-reference) — link and button blocks are where conditional redirection is configured in the page builder
# Custom QR Code design
Source: https://docs.scanova.io/qr-codes/manage/custom-design
Style a QR Code with your own colors, patterns, eye shapes, logo, frame, and error correction level, without affecting where the code actually resolves.
The **Design** editor (open a QR Code's details page and click **Edit Design**, or the palette icon in the QR Codes list) styles the QR Code's own scannable pattern — its dot shapes, colors, logo, and an optional frame around it. This is a different feature from [Page Builder's design options](/page-builder/design-options), which style the *landing page* a QR Code points to, not the QR Code image itself.
Every change here can affect scannability. The editor warns you at the specific settings most likely to break a scan (low contrast, a shrunk quiet zone, a low error-correction level) — always use **Test QR Code** (below) before printing or publishing a custom design.
## Custom Design tab
The editor opens on **Custom Design**, with five sections under **QR Code** in the left sidebar, plus **Shapes** and **Frames** alongside it:
* **Logo** — place a logo in the center of the QR Code, from a built-in icon gallery, your own icon set, an uploaded image, or plain text. Controls: **Size**, **Margin**, **Logo Color**, **Remove blocks around logo** (clears the QR Code modules directly behind it, on by default), **Use as Background** (places the logo behind the pattern instead), and **Stroke** (adds a border around it).
* **Data Patterns** — the shape of the QR Code's data dots, picked from a gallery of styles (squares, dots, rounded, and more), with a **Dot Size** slider and a **Single Color** / **Gradient** fill choice
* **Eyes** — the shape and color of the three large corner squares ("eyes"). Choose a pattern from **Choose Pattern** or your own saved ones under **My Patterns**, and apply it to **Both Eyes**, just **Inner Eyes**, or just **Outer Eyes**, each with its own color.
* **Background** — a solid **Color** or **Transparent** background, plus a **Quiet Zone** slider (the blank margin around the pattern). The panel warns that reducing the quiet zone may hurt scannability, and to always test before finalizing.
* **Error Correction** — one of four levels, each trading a larger/denser pattern for more damage tolerance: **L — Low** (7%, smallest/cleanest), **M — Medium** (15%, good balance), **Q — Quartile** (25%, high recovery), **H — High** (30%, best recovery). QR Codes carrying a center logo need a higher level, since the logo itself covers part of the pattern.
**Shapes** wraps the whole QR Code in an outer silhouette (heart, shield, hexagon, and more), with **Stroke Width**, **Padding Around Eyes**, and separate **Stroke**, **Data**, and **Background** colors.
**Frames** adds a bordered call-to-action graphic around the QR Code — pick a **Suggested** frame (matched to common use cases) or browse **All**, then set **Primary**/**Secondary**/**Text**/**Background** colors, the **Frame Text** (defaults to "Scan Me"), its **Font**, and its **Position**.
## Templates
The **Templates** section (top of the left sidebar) is a library of saved designs — your own previously-saved custom designs under **Saved**, or ready-made starting points under **Recommended**. Selecting one applies its full look (logo, patterns, colors, frame) to the current QR Code in one step.
## Smart Design
**Smart Design** generates a set of coordinated designs automatically from your branding, instead of styling each section by hand. Upload a logo image (PNG/JPG/SVG, up to 5 MB) — used both as the QR Code's logo and as the source for a matching color palette — or skip the upload and set a **Seed Color** to use as a fallback. Click **Generate Designs** to produce a grid of auto-generated variants; click any one to apply it to the QR Code, or **Regenerate** for a fresh batch.
For a fully AI-generated background image behind the QR Code instead of a color-derived pattern, see [AI Design](/qr-codes/manage/ai-design).
## Related
* [AI Design](/qr-codes/manage/ai-design) — generate a full background image for the QR Code from a text prompt
* [Test QR Code](/qr-codes/manage/test-qr-code) — scan-verify a design before printing or publishing it
* [Download](/qr-codes/manage/download) — export the finished design as PNG, JPG, or vector formats
* [Page Builder design options](/page-builder/design-options) — the separate editor for the destination page's own look, not the QR Code pattern
# Custom domains
Source: https://docs.scanova.io/qr-codes/manage/custom-domains
Replace the default scnv.io short link with a domain of your own, adding the DNS records that Scanova needs, then verifying and assigning it to codes.
Custom domains let your QR Codes and short links resolve through your own sub-domain (for example `offer.yourbrand.com`) instead of the default `scnv.io` short URL.
Open **Settings > Custom Domain** in the sidebar, or go directly to `/settings/domains`.
With no domains added yet, this page instead shows an empty state: a globe icon, "No custom domains added yet," and the same **Add Domain** button.
Custom domains are a Pro-and-above feature. On a plan without domain access, this page shows an upgrade prompt instead of the setup flow below.
## Adding and verifying a domain
Click **Add Domain**. A drawer opens asking for the sub-domain name you want to use — the full name including the sub-domain, for example `offer.myweb.com`.
An **Advanced Settings** section lets you disable case-sensitivity on this domain's URL slugs before you continue — this can't be changed later, so decide up front if you need it.
Once you continue, the domain is created and the drawer shows the exact DNS records to add at your registrar: a **CNAME** record pointing your sub-domain at Scanova's target, and a **TXT** record (on `@`, the root) proving ownership.
Every value has a copy-to-clipboard button next to it. If you'd rather not do this yourself, see [Sending DNS instructions to your IT team](#sending-dns-instructions-to-your-it-team), below.
Click **Verify**. If both records resolve, the domain is marked verified immediately. If not — which is expected right after adding new DNS records — you'll see a **"could not verify yet"** message:
> Unable to verify your custom domain right now. Please note that sometimes it will take up to 24 hours before our system can recognise the changes on your subdomain. You can try and re-verify the domain after some time. In case of any issues, please reach out our support.
DNS changes can take up to **48 hours** to propagate in the worst case. Give it that long before assuming something has gone wrong.
A domain whose DNS records do not resolve is still created successfully, and shows **Unverified** status straight away — there is nothing yet for the check to find.
## After adding a domain
Once a domain exists in your list, its row shows its verification status, whether it's your **Default** domain, and a **···** menu:
* **Reverify** — re-checks DNS without restarting setup. Use this after you've actually added the records and given DNS time to propagate.
* **Setup Again** — restarts the setup drawer at step 1 with the current host prefilled, in case you need to correct a typo. Since a domain name can't be edited after creation, changing it here deletes the old entry and creates a new one — only after the replacement is confirmed and successfully created, so you're never left with no domain at all.
* **Send setup instructions** — emails the exact DNS records to whoever manages your DNS
* **Root domain settings** — choose what happens when someone visits the bare domain directly, without a QR Code path: **No action — show a generic page**, **Redirect to a URL**, or **Show a custom landing page** (picking one of your own page-builder pages)
* **Delete** — removes the domain after a confirmation dialog, then shows a "Domain deleted" toast. Any QR Codes and forms using that domain fall back to the default Scanova short URL.
### Sending DNS instructions to your IT team
If you'd rather hand the DNS records to whoever manages your domain, use **Send setup instructions** from the row menu instead of copying each value yourself:
Enter a recipient email (for example, your IT team's address) and an optional note, then send. The email includes the exact CNAME and TXT records for that domain — the same values shown in the DNS records step above.
## Custom SSL
A **Custom SSL Certificate** option is available in the row's **···** menu for any domain. Custom SSL is an **Enterprise** feature — on a plan without it, the option opens a request dialog: "Bring your own SSL certificate for \," with "Custom SSL certificates are available on Enterprise plans. Tell us a bit about your needs and we'll follow up," a **Your name** / **Work email** form, and a **Request access** button.
## Related
* [Upgrading](/billing/upgrading) — custom domains are a Pro-and-above feature; upgrade here if your plan doesn't include them
* [Subscription & billing](/billing/subscription-and-billing) — manage the plan that gates domain access
* [QR Health Center](/qr-codes/manage/qr-health-center) — flags domains stuck in an unverified state as an open issue
# Downloading a QR Code
Source: https://docs.scanova.io/qr-codes/manage/download
Download a QR Code in the format and the size that you need, from PNG for screens to SVG, EPS or PDF for print, and what each option is actually best for.
## Where to download from
Every QR Code has a **Download** action — the download-icon button in each row of the [QR Codes list](/qr-codes/manage/folders-tags-bulk-operations), or the **Download** button on a QR Code's own detail page. Both open the same **Download QR Code** panel.
This is a different action from **Export**, which pulls scan/analytics *data* out of the platform rather than the QR Code image itself — see [Exporting QR Code data](/qr-codes/manage/export).
## Choosing a format and size
From the QR Codes list, click the download icon in the row for the QR Code you want. The panel shows a live preview, the current **Format**, **Size**, and estimated file size at the top.
**Format** splits into two groups: **Raster** (**PNG** or **JPG** — fixed pixel dimensions) and **Vector** (**SVG**, **PDF**, or **EPS** — scale to any size with no quality loss). Switching to a vector format replaces the **Size** section's pixel options with a note that it "scales infinitely," and the estimated file size drops to a few KB regardless of how large you print it.
For PNG or JPG, **Size** offers three presets — **Web** (450×450 px), **Print** (1200×1200 px, selected by default), and **Poster** (2400×2400 px) — plus **Custom**, which reveals separate **W** and **H** fields (with a lock icon between them to keep them linked) and a unit dropdown defaulting to **Pixel**.
The filename preview at the bottom (e.g. `golden-gate-bridge-docs-copy_1200x1200.png`) updates live as you change format and size. Click **Download** to save the file.
## Related
* [Exporting QR Code data](/qr-codes/manage/export) — pull scan/analytics data instead of the QR Code image
* [Duplicating a QR Code](/qr-codes/manage/duplicate) — the **Copy design** option there reuses whatever design you've applied, without needing a fresh download
* [Bulk operations](/qr-codes/manage/folders-tags-bulk-operations) — download or generate QR Codes in bulk instead of one at a time
* [Custom QR Code design](/qr-codes/manage/custom-design) — style the QR Code before downloading it
# Duplicating a QR Code
Source: https://docs.scanova.io/qr-codes/manage/duplicate
Copy an existing QR Code to reuse its content and design, what carries over to the copy, what is reset, and when duplicating beats starting from scratch.
## What duplicating does
Duplicate creates a new QR Code that starts as a copy of an existing one — same category, same content — so you don't have to re-enter everything to make a variant. It's available from the overflow (**⋮**) menu on a QR Code's detail page (just **Duplicate** and **Move to Trash** there), or from the row's own **⋮** menu in the [QR Codes list](/qr-codes/manage/folders-tags-bulk-operations) — a longer menu with **Analytics**, **Duplicate**, **Move to Folder**, **Deactivate**, and **Delete**.
## Duplicating a QR Code
Click the **⋮** menu and select **Duplicate**. This opens the **Duplicate QR Code** dialog.
**New name** defaults to the original name with `(copy)` appended. **Number of copies** defaults to `1`, up to a maximum of **10** at once.
The name field has a **100-character limit**. Appending `(copy)` to an already-long name can push it over that limit — the dialog blocks saving with "Ensure this field has no more than 100 characters" until you shorten it.
Under **Copy options**: **Copy design** (include the custom design and styling) and **Copy custom domain** (reuse the same custom domain, shown by name) are both on by default. Turn either off to have the duplicate fall back to the default design or Scanova's default domain instead.
Click **Duplicate**. You land back on the QR Codes list with a confirmation toast ("Duplicated "\"") and the new copy at the top, tagged with a fresh QR ID and its own short URL slug — everything else about it starts identical to the source.
A duplicate is a fully independent QR Code — it doesn't stay linked to the source. Editing, deleting, or changing settings on one has no effect on the other.
## Related
* [QR Code details](/qr-codes/manage/qr-code-details) — where the **⋮** menu that starts a duplicate also lives
* [Downloading a QR Code](/qr-codes/manage/download) — get an image of the duplicate once it's set up the way you want
* [Folders, tags & bulk operations](/qr-codes/manage/folders-tags-bulk-operations) — organize duplicates alongside the rest of your QR Codes
# Exporting QR Code data
Source: https://docs.scanova.io/qr-codes/manage/export
Export your QR Code list as a spreadsheet, choosing both the columns and the scope, so you can report on your codes or work with them outside Scanova.
## What you can export
From **Analytics**, once you've selected QR Code(s), folder(s), or tag(s) and a date range and clicked **Get Analytics**, two independent export actions appear next to the **Overview** heading:
* **Export Analytics Report** — the dashboard itself (charts, totals, breakdowns), as **PDF**, **PNG**, **JPEG**, **XLS**, or **XLSX**
* **Export Scan Data** — the underlying scan-level rows, as **CSV**, **XLS**, or **XLSX**
This is a different action from **Download**, which saves the QR Code's own image — see [Downloading a QR Code](/qr-codes/manage/download).
## Exporting a report or raw data
In **Analytics**, choose **QR Codes**, **Folders**, **Tags**, **Categories**, or **Users** and a date range, then click **Get Analytics**.
Click either dropdown and choose a format. **Export Scan Data** offers **CSV**, **XLS**, and **XLSX**:
For a small enough selection, the export downloads directly. For a large selection — for example, every QR Code on the account across a wide date range — a **Generate a report instead** dialog appears: "This selection is too large to display here. You can generate a downloadable report instead — we'll email you when it's ready." Click **Create Report** to queue it.
## Related
* [Downloading a QR Code](/qr-codes/manage/download) — export the QR Code image itself, not its scan data
* [Email reports](/analytics/email-reports) — schedule recurring analytics emails instead of exporting on demand
# Folders, tags & bulk operations
Source: https://docs.scanova.io/qr-codes/manage/folders-tags-bulk-operations
Organize your QR Codes with folders and tags, and use the bulk action bar to move, tag, activate, or delete many of them in a single pass from the list.
The QR Codes list (`/qr/list`) is where you organize everything you've created. Three tools work together here: a collapsible **folder sidebar** for grouping codes, **tags** for cross-cutting labels, and **bulk actions** for operating on many QR Codes at once.
## Organize with folders
Folders live in a collapsible sidebar on the left of the QR Codes list, not as filter chips in the toolbar. Toggle it with the small collapse icon next to the **LIBRARY** section header, at the top of the sidebar itself — collapsing it replaces the sidebar with a **Folders** pill button next to **Health Center** in the page header, which re-expands it when clicked. Your preference is remembered in your browser (via `localStorage`) the next time you visit.
Folders are available on the **Pro Plan** and above. On a lower plan the sidebar and its toggle don't appear at all, and the **Move to Folder** bulk action is hidden too. Contact support if you expect folders on your plan but don't see them.
At the top of the sidebar, above your own folders, are three built-in virtual entries:
* **All QR Codes** — every QR Code you own, regardless of folder
* **Uncategorized** — QR Codes not currently assigned to any folder
* **Pages** — only shown once your account has at least one legacy "Page"-flagged QR Code; this is a marker carried over from the classic app, not something you assign yourself
### Creating and managing folders
Click the small **+** button next to the "FOLDERS" section header in the sidebar. The dialog reads "Group related QR Codes together. A QR Code can live in one folder."
Folder names are limited to 150 characters (placeholder example: "Spring Campaign"). Pick one of 7 swatches — a grey Default plus 6 accent colors (blue, purple, green, orange, red, teal) — the color tints the folder icon everywhere it appears, including in the drag-and-drop drop zone.
The new folder appears in the sidebar immediately and becomes the active selection.
Each folder in the list has a `···` menu (visible on hover) with:
* **Edit Folder** — rename it
* **Change Color** — swap its accent color without opening the full edit dialog
* **View Analytics** — jumps to `/analytics` filtered to this folder's QR Codes
* **Delete folder** — see below
Deleting a folder asks what to do with the QR Codes inside it: **Move to Uncategorized** (the QR Codes stay active, just unassigned) or **Delete QR Codes permanently** (the QR Codes themselves stop working — this cannot be undone). Choose carefully; the second option is destructive to the QR Codes, not just the folder.
A search box above the folder list filters folders by name (debounced, so it doesn't fire on every keystroke), and the list lazy-loads more folders as you scroll if you have a large number of them.
### Moving QR Codes into folders
You can drag a QR Code card or row directly onto a folder in the sidebar (or onto **Uncategorized** to clear its folder). Drop targets highlight as you drag over them. For moving several QR Codes at once without dragging, use the **Move to Folder** bulk action described below.
Both drag-and-drop and the bulk "Move to Folder" action require a permission grant (`FOLDER_CAN_CREATE_AND_MANAGE`) in addition to the plan quota — a shared user without that grant won't see these controls, so they never hit a permission error after the fact.
## Tag QR Codes
Tags are a lightweight, freeform way to label QR Codes that cuts across folders — a QR Code can carry multiple tags and still live in one folder (or none).
To tag a single QR Code, click **+ Add tags** in its row (or the existing tag chip, once it has one) to open the tag manager:
Start typing in the input. Existing tags that match what you've typed appear as suggestions below — click one to add it, or press **Enter** to create a new tag from what you typed.
Click the **×** on a tag chip to remove it, or press **Backspace** in an empty input to remove the last one added.
Saving replaces this QR Code's tag list with your current selection.
Once at least one tag exists on your account, a **Tags** filter appears in the toolbar next to Type, Status, and Category — pick one or more tags to filter the list down to matching QR Codes.
## Bulk actions
Select two or more QR Codes using the checkboxes in the leftmost column to bring up a floating action bar at the bottom of the list.
Depending on your plan and permissions, the bar can include:
* **Download** — opens the download panel for all selected QR Codes at once
* **Manage Tags** — opens the same tag manager as above, but scoped to every selected QR Code. Saving **replaces** the tags on all of them with whatever you set — it isn't additive.
* **Move to Folder** — only shown if your plan and permissions allow folder management (see the folder note above)
* **Activate** / **Deactivate** — flips `is_active` on the selected QR Codes. Static QR Codes are skipped automatically (a static code's payload is baked into the image itself, so activation state means nothing for it) — the confirmation dialog tells you how many rows were skipped for this reason.
* **Delete** — moves the selected QR Codes to Trash (see [Trash, restore & permanent delete](/qr-codes/manage/trash-restore))
Every bulk action shows exactly how many rows it will affect in its confirmation dialog title (for example, "Delete Permanently (3)"), so you can double-check your selection before confirming anything destructive.
## Related
* [Your first QR Code](/getting-started/first-qr-code) — where a QR Code comes from before you're organizing it with folders and tags
* [Trash, restore & permanent delete](/qr-codes/manage/trash-restore) — what happens after the bulk Delete action here moves codes out of the main list
* [Shared users](/team/shared-users) — folder management and the bulk "Move to Folder" action both require the `FOLDER_CAN_CREATE_AND_MANAGE` permission grant covered there
# GPS & geofencing
Source: https://docs.scanova.io/qr-codes/manage/gps-geofencing
Record the precise location of every scan, and restrict a QR Code so that it only resolves for people who are scanning inside an area that you define.
## What geofencing does
Geofencing restricts a QR Code's content to visitors scanning from within a radius around a location you choose. Visitors outside that radius — or anyone who declines to share their location — see a fallback instead: either a custom message, or a redirect to a different URL.
Live UI wording: the row is labeled **Geo-fencing**, with helper text **"Restrict content to users scanning within a specific location area."** It lives under the **Security & Access** section of a QR Code's Settings tab, not Targeting & Lifecycle — it's grouped with Password protection and Age restriction rather than with QR Code scheduling and Scan limit.
Geofencing requires the visitor's browser to grant location permission. If permission is denied, the same fallback shown for out-of-range visitors is used.
## Setting up a geofence
Open the QR Code and go to **Settings**. Under **Security & Access**, select **Geo-fencing** to expand the section.
Select **Configure location** to open the **Set Location** modal.
Type a place name into **Search Location** and select **Search** — or select the locate-me icon next to it to use your current location. The result repositions a pin on an embedded OpenStreetMap view.
Typing a place name alone doesn't set the location — the map only updates once you select **Search** (or use the locate-me button). Filling the text field and closing the modal without searching won't save a location.
Enter a numeric value in **Range** and choose a unit: **Feet (ft)**, **Meters (m)**, **Kilometers (km)**, or **Miles (mi)**. This defines the radius around the pinned location that counts as "in range."
Under **Fallback Action**, choose what happens for visitors who deny location permission or scan from outside the range:
The default option. Enter text in **Display Text** (up to 500 characters, with a live character counter) — this is shown to out-of-range visitors instead of the QR Code's normal content. The built-in default is "This QR Code is location-restricted. The content linked to this QR Code is only accessible when scanned from a specific location."
Selecting this swaps the **Display Text** field for a **Fallback URL** field. Out-of-range visitors are redirected there instead of seeing a message.
Select **Save**. The **Geo-fencing** row switches to **On**, and the **Configure location** button updates to show the place name you set (for example, "Configure · Golden Gate Bridge").
## Editing a geofence
Reopen **Configure location** at any time to search a new location, change the range or unit, or switch the fallback action between a message and a redirect URL.
## Related
* [QR Code scheduling](/qr-codes/manage/scheduling) — another Settings-tab restriction, gated by time instead of location
* [Conditional redirection](/qr-codes/manage/conditional-redirection) — routes by country/device/OS/language rather than a location radius, and lives in the URL field instead of Settings
# Password protection
Source: https://docs.scanova.io/qr-codes/manage/password-protection
Require a password before a QR Code will show its landing page, so that printed codes can front content that should not be open to absolutely everyone.
## What password protection controls
Password protection gates a Dynamic QR Code's content behind a password prompt — anyone who scans it must enter the correct password before the destination loads. It's a per-QR toggle, available wherever [Advanced settings](/qr-codes/manage/age-restriction) live: the **Publish** panel while creating a Dynamic QR Code, or the **Settings** tab of an already-published one.
This is distinct from your account **login password**. It's a separate, per-QR-code password that visitors enter to view that one QR Code's content — setting or changing it has no effect on your Scanova account credentials.
Password protection is only available on **Dynamic** QR Codes. Publishing a Static QR Code (like [Wi-Fi](/qr-codes/create/wifi) or [Contact Card](/qr-codes/create/contact-card)) shows a notice instead — "Advanced settings need a Dynamic QR Code" — since Static codes have no server-side step to check a password against.
## Turning it on
Open the QR Code and go to **Settings**. Under **Security & Access**, find **Password protection** — "Require a password before showing your page."
Switching **Password protection** on immediately expands a **Set a password** field with a **Set password** button and a show/hide (eye) toggle for the typed value.
Type a password and click **Set password**. The field's helper text confirms: "Visitors must enter this password before viewing the QR content."
Once a password is set, the field's label and helper text change to reflect that: "A password is set. Enter a new one above to replace it," alongside an **Update** button. The stored password itself is never shown back — the field always starts empty, so you can only overwrite it, not reveal the current value.
Turning the toggle off at any point removes the gate — no confirmation step needed beyond the toggle itself.
## Setting it while creating a QR Code
The same control is available before publishing: in the **Publish** panel, open the **Advanced** tab, find **Password protection** under **Security & Access**, and turn it on the same way. It saves along with the rest of the QR Code when you click **Publish**.
## Related
* [Age restriction](/qr-codes/manage/age-restriction) — the other content gate in the same Security & Access section, requiring a minimum age instead of a password
* [GPS & geofencing](/qr-codes/manage/gps-geofencing) — a location-based restriction, in the same panel
* [QR Code scheduling](/qr-codes/manage/scheduling) — a time-based restriction, in the Targeting & Lifecycle section of the same Settings tab
# QR Code details
Source: https://docs.scanova.io/qr-codes/manage/qr-code-details
The QR Code detail view: its overview tab, the data collected from each scan, and the settings that control security, tracking, and lifecycle behaviour.
## What the details page shows
Clicking any QR Code — from the [QR Codes list](/qr-codes/manage/folders-tags-bulk-operations), a folder, or a search result — opens its details page at `/qr/`, organized into three tabs: **Overview**, **Data Collected**, and **Settings**.
### Overview
Performance for the selected period (**7d**, **30d**, **90d**, or **1y**): **Total Scans** (all-time), **Scans**, **Unique Scans**, and **Source (Scan/URL)** — followed by a **Scans over time** chart and an **Audience** section (**Devices**, **Top countries**). Every metric and chart shows an explicit empty state ("No scan data for this period," "No data yet for this period") rather than a blank space when a QR Code hasn't been scanned yet.
### Data Collected
Submissions from the QR Code's landing page, if it has a form attached. With none connected, the tab shows "No form connected" and a **Connect Form** button — see [Building a form](/forms/building-a-form).
### Settings
Every per-QR toggle — [password protection](/qr-codes/manage/password-protection), [age restriction](/qr-codes/manage/age-restriction), [geo-fencing](/qr-codes/manage/gps-geofencing), [scheduling](/qr-codes/manage/scheduling), and more — grouped under **Security & Access**, **Tracking & Analytics**, and **Targeting & Lifecycle**.
## The QR Code panel
Alongside the tabs, a persistent panel shows the QR Code image, name, short URL, an **Active** toggle (pause/resume the QR Code without deleting it), and three actions: **Edit Content**, **Edit Design**, and **Download**. Below that, a **Details** block lists **Type** (Dynamic/Static), **Category**, **Short URL**, **QR ID**, **Total scans**, **Folder**, and **Created** date.
## Row-level actions
The **⋮** menu next to the panel (also present in the QR Codes list) offers **Duplicate** and **Move to Trash** — see [Duplicating a QR Code](/qr-codes/manage/duplicate). The share icon opens the same share options as the panel's own share button, and the download icon opens the [Download](/qr-codes/manage/download) panel directly, without needing to click into **Edit Content** first.
## Related
* [Duplicating a QR Code](/qr-codes/manage/duplicate) — reachable from this page's overflow menu
* [Downloading a QR Code](/qr-codes/manage/download) — the panel's Download button and the row-level download icon both open this
* [Exporting QR Code data](/qr-codes/manage/export) — pull the Overview tab's underlying scan data as a file
* [QR Health Center](/qr-codes/manage/qr-health-center) — a cross-account view of the same kind of issues this page's Settings tab controls
# QR Health Center
Source: https://docs.scanova.io/qr-codes/manage/qr-health-center
The Health Center reviews every QR Code on your account for expiry, drafts, form links, and domain problems, and ranks what it finds by its scan impact.
QR Health Center is a single page that surfaces problems across every QR Code on your account — codes outside their active schedule, unpublished drafts, broken form links, and incomplete domain verification — so you don't have to check each QR Code individually. Open it from `/qr/health`, or via the **Health Center** button in the QR Codes list header.
Previously, diagnosing "my QR Code isn't working" meant checking three separate places — the QR Code list, the forms module, and custom domain settings. Health Center runs those checks across your whole account and presents everything on one screen, ranked by how much real traffic each problem is costing you.
## Overview and metrics
At the top, a summary bar shows the percentage of your QR Codes currently healthy, out of your total count, alongside your total lifetime scans. The bar is colour-coded:
| Colour | Meaning |
| - | - |
| Green — Healthy | No problems found |
| Amber — Needs attention | At least one problem found |
| Grey — Paused | Switched off intentionally |
Below it, five metric cards break the same picture down by category:
* **Critical** — the highest-risk live issues
* **Warnings** — needs action soon
* **Needs Review** — operational cleanup, lower urgency
* **Healthy** — QR Codes with no open issue
* **Paused by User** — intentionally deactivated QR Codes, excluded from the issue counts above (a paused QR Code isn't "unhealthy," it's just off on purpose)
**The five cards are not designed to add up to your QR Code total.** Critical, Warnings and Needs Review count **issues**; Healthy and Paused by User count **QR Codes**. A single QR Code can carry more than one issue at the same time.
## The open issues table
Below the metrics, a filterable, searchable table lists every open issue: which QR Code (name and ID), its severity, the issue type and a short description, its lifetime scan count, and a **View issue** button. Filter by **Severity** or **Issue type**, or search by QR Code name, QR Code ID, owner, or issue text. Each filter label carries a count, so you can see the shape of your problems before applying anything.
Issues are sorted **Critical → Warning → Needs Review**, and within each tier by scan volume, highest first. The top row is therefore always the costliest problem on the account — which is exactly where the **Open top issue** button jumps to.
Click **View issue** to open a detail drawer for that specific QR Code:
The drawer is organized into three sections:
* **Why this issue surfaced** — the reason code, category, owner, and issue-specific fields (for example, draft-saved and published-live timestamps for a publishing issue, or CNAME/TXT verification status for a domain issue), plus a plain-language impact statement
* **What to do next** — a short resolution instruction and a checklist of the specific conditions the health check found
* **Traffic context** — the QR Code's relative visit weight and created/last-updated timestamps, so you can judge how urgent a fix really is
An **Open QR Code Details** button in the drawer footer takes you straight to that QR Code.
## Health monitor: the underlying checks
Further down, the **Health monitor** section lists the individual live checks Health Center runs, each as its own card. Each card shows how many QR Codes it currently affects, or "All clear" if none.
| Check (Health monitor) | Issue name (issues table) | What it flags | Why it matters | Where you fix it |
| - | - | - | - | - |
| **Schedule** | Scheduled inactive | The QR Code is outside its live window — expired, or its start date hasn't arrived | Anyone scanning right now cannot reach the content | QR Code editor → schedule |
| **Domain verification** | Domain verification failed | The QR Code uses a branded custom domain that isn't fully verified, or the domain is no longer available | Branded links may fail or fall back, damaging trust in printed material | Settings → Domains |
| **Draft publishing** | Draft not published | Changes were saved but never published | Visitors still see the older live version; your work isn't reaching anyone | QR Code editor → publish |
| **Form link** | Form missing | The QR Code collects leads, but the linked form no longer exists | Visitors reach a capture flow that can't complete — leads are lost silently | QR Code editor → attach a form |
| **No scans yet** | No scans yet | A **dynamic** QR Code more than **30 days** old with zero lifetime scans | Usually a setup or distribution gap worth reviewing | Review destination and distribution |
Click any card to open a drawer describing what that specific check looks for and listing the affected QR Codes (or confirming none are affected):
Health Center refreshes on demand — use the **Refresh** button in the header rather than reloading the page, and the "Refreshed" timestamp next to the page title tells you how current the data is.
## How severity is calculated
Severity is **not** a fixed scan threshold. It is calculated against your own account's traffic distribution, so the ranking stays meaningful whether you have 50 scans or 500,000.
The principle: a problem is urgent in proportion to how many real people it affects. A broken QR Code nobody scans is housekeeping. A broken QR Code carrying most of your traffic is an emergency.
| Severity | What it means |
| - | - |
| **Critical** | The QR Code sits in the top tier of your account's own scan distribution. Real users are actively hitting a broken experience right now. |
| **Warning** | The QR Code carries meaningful traffic — at or above your account's typical scan volume — but isn't in the top tier. |
| **Needs Review** | The QR Code carries little or no traffic. The problem is real and worth resolving, but nobody is currently experiencing it. |
How the ranking is built:
1. Every active QR Code's lifetime scan count is collected.
2. Only QR Codes with actual traffic shape the distribution, so a large number of zero-scan codes can't drag the thresholds down artificially.
3. Your account's own median and top-tier cut-offs are calculated from that set.
4. Each flagged QR Code is placed against those cut-offs to get its severity.
### Two deliberate refinements
**Small accounts are capped at Warning.** Until an account has at least **ten** QR Codes carrying real traffic, there isn't enough data to rank fairly — so no issue is raised to Critical. This is why a new or lightly-used account often sees mostly **Needs Review** and **Warning** issues rather than a wall of red.
**Draft not published never escalates to Critical.** A stale draft doesn't break a scan — the visitor simply sees older content — so it is capped at **Warning** even on your busiest QR Codes, and never outranks a genuinely broken experience.
## Scope and access
* **Paused QR Codes are excluded from every check.** Pausing is treated as a deliberate decision, so a paused code never generates an issue. It still appears in your account totals as Paused. Resume it to bring it back under monitoring.
* **The page adapts to your permissions.** If your role doesn't have access to Forms or Custom Domains, you simply see fewer checks — the page loads and works normally with the remaining ones. This is why a colleague may see more checks than you do.
* **Anyone who can view QR Codes can open Health Center.**
## Empty and edge-case states
Health Center adapts its messaging to your account's actual situation rather than showing a generic empty table:
* **No QR Codes yet** — the page invites you to create your first one instead of showing an empty issues table
* **Everything paused** — Health Center explains that paused codes are excluded from issue counts by design, with a link back to the QR Code list
* **All clear** — if you have QR Codes and none currently have an open issue, the page confirms your account is healthy and offers quick links to the QR Code list, forms, or domains depending on what you have access to
* **Filters exclude everything** — if your search or filter combination matches no issues, the page prompts you to widen the search rather than reading as "no problems found."
## Related
* [QR Code scheduling](/qr-codes/manage/scheduling) — the activation/deactivation rules behind the **Schedule** health check
* [Custom domains](/qr-codes/manage/custom-domains) — CNAME/TXT verification is what the **Domain verification** health check monitors
* [Publishing](/page-builder/publishing) — draft vs. live state is what the **Draft publishing** health check compares
* [Building a form](/forms/building-a-form) — broken form links are what the **Form link** health check flags
# QR Code scheduling
Source: https://docs.scanova.io/qr-codes/manage/scheduling
Set a QR Code to activate and deactivate on the dates you choose, so a campaign can go live and retire on its own without anyone remembering to do it.
## What QR Code scheduling controls
QR Code scheduling lets a dynamic QR Code turn itself on and off automatically, without you having to remember to flip a switch on launch day or after a campaign ends. It's built from **two independent rules**, not a single start/end date range:
* An **Activation Rule** — when the QR Code should become active
* A **Deactivation Rule** — when the QR Code should stop being active
You can set either one on its own, both together, or neither. They aren't linked to each other: setting an activation date doesn't require a deactivation date, and vice versa.
Live UI wording: the row is labeled **QR Code scheduling**, with helper text **"Automatically activate and deactivate by date."** Expanding it shows two separate cards, **Add Activation Rule** and **Add Deactivation Rule**, each with its own **Add** button — confirming these are two independent rules rather than one date range.
## Setting an activation rule
Open the QR Code and go to **Settings**. Under **Targeting & Lifecycle**, find **QR Code scheduling** and select it to expand the section.
In the **Add Activation Rule** card, select **Add**.
In **Select Activation Date and Time**, pick a date and a time. The **Time Zone** field defaults to your browser's local time zone (for example, `Asia/Kolkata (GMT+5:30)`) and can be changed.
The date picker only allows **future** dates and times for an activation rule — past days are disabled, and picking today with a time that has already passed is rejected with "Activation date and time must be in the future." Deactivation rules have no such restriction (see below).
**Custom Message (optional)** is shown to visitors who scan the QR Code *before* the activation date/time arrives. Leave it blank to fall back to the product's default text, "This QR Code has not been activated yet...".
Select **Save Rule**. The **QR Code scheduling** row switches to **On**, and the QR Code's details panel shows a **Scheduled** status with the activation date.
## Setting a deactivation rule
The **Add Deactivation Rule** card next to it works the same way, with its own **Add** button:
In the **Add Deactivation Rule** card, select **Add**.
Pick a **Select Deactivation Date and Time** and confirm the **Time Zone**.
Unlike the activation rule, the deactivation date picker does **not** block past dates — you can pick a date that has already passed. Doing so expires the QR Code immediately once you save, so double-check the date before saving if you don't want that.
**Custom Message (optional)** is shown to visitors who scan the QR Code *after* it has expired. The default fallback text is "This QR Code is no longer active...".
Select **Save Rule**.
## What visitors see outside the scheduled window
Before the activation date/time (if an activation rule is set) or after the deactivation date/time (if a deactivation rule is set), scanners see the custom message you configured instead of the QR Code's normal destination. These messages are plain informational text — they don't redirect to a different URL. If you need scanners sent to a different destination based on conditions like country or device (rather than a fixed message), that's a separate feature; see [Conditional redirection](/qr-codes/manage/conditional-redirection).
## Editing or removing a rule
Once a rule is saved, its card shows the configured date, time zone, and a **Scheduled** status badge, with **Edit** and **Remove** links. Select **Edit** to reopen the same modal pre-filled with the current values, or **Remove** to delete the rule and turn that half of the schedule back off.
## Related
* [Conditional redirection](/qr-codes/manage/conditional-redirection) — routes visitors to different URLs based on who's scanning, rather than when
* [GPS & geofencing](/qr-codes/manage/gps-geofencing) — another Settings-tab restriction, based on location instead of time
* [QR Health Center](/qr-codes/manage/qr-health-center) — flags QR Codes currently outside their scheduled window
# Test QR Code
Source: https://docs.scanova.io/qr-codes/manage/test-qr-code
Check that a QR Code resolves to the right destination before you print it, using the built in test action rather than scanning it with your own phone.
**Test QR Code** lets you scan a QR Code with your own phone, right from the [Design editor](/qr-codes/manage/custom-design), before you print or publish it — useful after a custom color, logo, or frame change that might affect scannability. Open it from the **Test QR Code** icon in the vertical toolbar beside the canvas (on either the **Custom Design** or **AI Design** tab).
## How it works
Click the Test QR Code icon in the canvas toolbar. A drawer opens showing your QR Code's current design and a **Waiting for scan results...** status.
Follow the **How to Scan** instructions: on iPhone, your camera app scans it directly; on Android, use your camera app or download a Scanner App from the Play Store.
If your phone can't scan it, the drawer's footer links to **Read our guide** (this page) or **support team** (the in-app [Support Center](/support/overview)) for troubleshooting.
## Related
* [Custom QR Code design](/qr-codes/manage/custom-design) — the editor this tool is opened from, and the settings most likely to need a scan test after changing them
* [AI Design](/qr-codes/manage/ai-design) — an AI-generated background is exactly the kind of busy design worth testing here before publishing
* [Support](/support/overview) — the in-app help hub linked from this drawer's footer
# Trash, restore & permanent delete
Source: https://docs.scanova.io/qr-codes/manage/trash-restore
How deleting a QR Code works: it moves to the Trash, keeps serving scans or stops, can be restored for 30 days, and is then permanently removed for good.
Deleting a QR Code from the main list never removes it immediately — it's a soft delete. The QR Code moves to Trash, where you have 30 days to restore it before it's gone for good.
## Deleting from the QR Code list
From `/qr/list`, click the **···** menu at the end of a QR Code's row and choose **Delete**.
A **Move to Trash** dialog names the QR Code directly, e.g. *"Are you sure you want to move "Untitled" to Trash? It will be permanently deleted after 30 days."* Confirm with **Move to Trash**.
A toast appears immediately, naming the QR Code and confirming it was moved to Trash, with an **Undo** button right in the toast. Clicking it restores the QR Code on the spot — no need to visit the Trash page at all.
Selecting multiple QR Codes and using the bulk **Delete** action works the same way — one confirmation, one toast, one **Undo** covering the whole batch. See [Folders, tags & bulk operations](/qr-codes/manage/folders-tags-bulk-operations) for more on bulk actions.
The Undo toast is the fastest way to recover a QR Code you just deleted by mistake. Once it disappears (or you dismiss it), you can still recover the QR Code from the Trash page — it just takes a couple more clicks.
## The Trash page
Visit **Trash** under QR Codes in the sidebar, or go directly to `/qr/trash`, to see every QR Code you've deleted.
A banner at the top states the retention policy plainly: **"Items in Trash are permanently deleted after 30 days."** This is a real, enforced limit — once an item passes 30 days in Trash, it's gone automatically, whether or not you visit this page again.
The list supports the same **Type**, **Category**, and a **Deleted** date filter as the main QR Code list, plus search by name, so you can find a specific deleted QR Code in a large trash.
### Restoring or permanently deleting QR Codes
There's no per-row **···** menu on the Trash page — check one or more rows with their checkbox to bring up a floating action bar at the bottom, with **Restore** and **Delete Permanently**. This is the same interaction whether you check one row or many; even restoring or deleting a single item goes through this selection bar, not a row-level menu.
* **Restore** — brings the QR Code(s) back to the main list, active again. The confirmation dialog title shows the count (e.g. "Restore (1)") and confirms: *"Restore 1 QR Codes from Trash? They will become active again."*
* **Delete Permanently** — immediately and irreversibly deletes the QR Code(s). The confirmation dialog is explicit about this, again with the count in its title (e.g. "Delete Permanently (1)"):
There is no undo for **Delete Permanently** — unlike the soft delete from the main list, this confirmation dialog means exactly what it says. A success toast (e.g. "1 QR Codes permanently deleted") confirms it's done.
### Bulk restore and bulk delete
Selecting more rows works exactly the same way — the floating bar and both confirmation dialogs simply update their counts (for example, "Delete Permanently (3)").
### Emptying the entire trash
The **Empty Trash** button in the page header (only shown when Trash isn't empty) permanently deletes every QR Code currently in Trash in one action, after a confirmation dialog reading "Permanently delete all items in Trash? This cannot be undone." Use this only when you're certain nothing in Trash is worth keeping — it bypasses the 30-day grace period for everything at once.
## Related
* [Folders, tags & bulk operations](/qr-codes/manage/folders-tags-bulk-operations) — the bulk Delete action that sends multiple QR Codes to Trash in one step
* [QR Health Center](/qr-codes/manage/qr-health-center) — surfaces issues across your active QR Codes, so you know what's worth keeping before you empty Trash
* [Your first QR Code](/getting-started/first-qr-code) — the basics of creating a QR Code, for context on what ends up in Trash
# UTM builder
Source: https://docs.scanova.io/qr-codes/manage/utm-builder
Add UTM parameters to a Dynamic QR Code's destination so that scans arrive tagged in your analytics tool, and the traffic can be attributed correctly.
## What the UTM builder does
The UTM builder appends standard UTM tracking parameters to a Dynamic QR Code's outbound URL, so a scan shows up correctly attributed in an external analytics tool (like Google Analytics) rather than as an anonymous visit. It's the **Tracking Parameters (UTM)** toggle under **Advanced settings** on any Dynamic Quick Code form (for example, [Website URL](/qr-codes/create/website-url) or [Google Maps](/qr-codes/create/google-maps)), next to **Conditional Redirection**.
UTM builder is only available on **Dynamic** QR Codes with an outbound destination URL — it doesn't apply to Static QR Codes or to categories with no external redirect.
## Adding tracking parameters
On the QR Code's create (or edit) form, under **Advanced settings**, switch on **Tracking Parameters (UTM)** — "Append UTM tags to outbound URLs for marketing analytics." A **Configure parameters** button appears, along with a warning that nothing happens until at least one parameter is added: "Add at least one parameter, or turn this off — it won't do anything until then."
Click **Configure parameters**. The dialog shows a live **URL preview** of the destination URL with a running character count (max 2000), five standard UTM fields — **utm\_campaign**, **utm\_medium**, **utm\_source**, **utm\_term**, **utm\_content** — each with its own hint and example (e.g. `utm_source` — "Identify the source of traffic (e.g. Facebook, LinkedIn, poster\_qr, newsletter)"), and a **Custom parameters** section for anything outside the standard five.
Type into any of the standard fields — the **URL preview** at the top updates live as query parameters, e.g. `?utm_source=poster_qr&utm_medium=print&utm_campaign=summer_sale`. You don't need to fill in all five.
Click **Add Custom Parameter** to add a **Parameter name** / **Value** pair for anything not covered by the standard five (placeholder example: `ref`). Each custom row has its own delete (trash) icon.
Click **Save**. The **Configure parameters** button is replaced with **Edit parameters (N)**, where N is the number of parameters set, and the QR Code's preview panel gains a **UTM** badge alongside its Dynamic/category badges.
Use **Reset Parameters** in the dialog to clear every field at once, or **Cancel** to discard unsaved changes.
## Related
* [Website URL QR Code](/qr-codes/create/website-url) — the most common place to attach UTM parameters, alongside Conditional Redirection
* [Conditional redirection](/qr-codes/manage/conditional-redirection) — the other Advanced setting in the same section, routing by who's scanning rather than tagging the destination
* [Exporting QR Code data](/qr-codes/manage/export) — pull scan data to cross-reference against what your analytics tool attributes from these UTM tags
# End-User Tracking
Source: https://docs.scanova.io/settings/user-tracking
Control what Scanova records about the people who scan your QR Codes, and how those tracking choices affect the analytics you see back in the dashboard.
**End-User Tracking** (`/settings/user-tracking`) controls whether Scanova assigns a shared Tracking ID to link a single visitor's activity — a QR Code scan, a form entry, and their GPS location — across your exports. It's off by default: by default, the app doesn't capture data at the end-user level, so a scan, a form entry, and a location are recorded as separate, unlinked events for privacy reasons.
## What enabling it does
Turning on **Enable End-User Tracking** assigns a unique Tracking ID to each visitor. That ID is added as a new column in your raw data exports (CSV/Excel) for Analytics, Lead Generation, and Custom Forms — letting you bridge data points across those features for the same person. With it enabled, you can identify:
* **User Journeys** — which specific visitor scanned a QR Code, and when
* **Lead Attribution** — which Tracking ID is associated with a specific Lead Generation entry
* **Response Mapping** — a specific visitor's answers within a Custom Form
* **Location History** — where a specific visitor was located during each scan (via Exact GPS)
## Related
* [Downloading lead data](/leads/downloading-data) — one of the exports the Tracking ID column appears in
* [Form responses](/forms/responses) — Custom Form response data that can be bridged via the same Tracking ID
* [GPS tracking & geo-fencing](/qr-codes/manage/gps-geofencing) — the location data End-User Tracking can link to a specific visitor
# Logs export
Source: https://docs.scanova.io/team/logs-export
Export a CSV of everything your team has done over a period you choose, for audits, for compliance reviews, or simply to see who changed what, and when.
**Logs Export** (`/users/user-logs`) generates a downloadable CSV of your team's activity for a time period you choose, emailed to you once it's ready. It sits alongside [Roles](/team/roles-and-permissions) and [Activity](/team/activity) under **User Management** in the sidebar — all three only appear for accounts with the right plan and permissions.
## Requesting an export
Select **Export User Logs** in the page header.
Under **Select Time Period**, choose **Last 7 Days**, **Last 30 Days**, **Last 90 Days**, **Last 180 Days**, or **Custom** for a specific date range.
Select **Export Logs**. A toast confirms: "User Logs Export is in progress. You will receive an email on completion." The request appears immediately in **Export History** below, named **Activity Logs**, with a status of **In Progress**.
The **Select Time Period** dropdown's options exist in the page but don't render visibly when you click it — a rendering issue this docs pass also found on several other pages' popups (dropdowns, date pickers). If the list doesn't appear, try a different browser or zoom level, or reload and try again.
## Export History
Every requested export is tracked in a table:
| Column | What it shows |
| - | - |
| **Report** | The export's name (e.g. "Activity Logs") and an ID number. |
| **Status** | **In Progress**, **Completed**, or **Failed**. |
| **Requested By** | The teammate who started the export. |
| **Requested On** | When the export was requested. |
| **Completed On** | When it finished — shown as `—` while still in progress. |
| **Download** | A button to download the finished CSV, disabled until the export completes. |
Filter the table with the **Search by requester** box or the **Status** filter (Completed / In Progress / Failed).
## Related
* [Activity](/team/activity) — the live feed and trends this export's underlying data is drawn from
* [Shared users](/team/shared-users) — inviting the teammates whose activity shows up here
* [Roles & permissions](/team/roles-and-permissions) — what each role can see and do, including whether they can request an export
# Roles & permissions
Source: https://docs.scanova.io/team/roles-and-permissions
The roles you can give a shared user, what each one is allowed to do, and how permissions decide which parts of the dashboard they get to see at all today.
Every teammate you invite is assigned a **role** that determines exactly what they can see and do in your account. Roles live under **User Management > Roles** in the left sidebar (`/users/user-roles`), and the same role list drives the **Choose Role** dropdown in the [Invite Users drawer](/team/shared-users).
The **Roles** and **Activity** items under **User Management** only appear in the sidebar for accounts with the right plan and permissions — see [Custom roles are plan-gated](#custom-roles-are-plan-gated) below for what that looks like when they're hidden.
## Default system roles
Every account starts with five built-in roles: **Viewer**, **Manager**, **Admin**, **Analyst**, and **Billing Manager**.
The **Choose Role** dropdown isn't a fixed list — it shows the five system roles plus every custom role your account has created (see [Custom roles are plan-gated](#custom-roles-are-plan-gated) below), so its exact contents vary by account.
Selecting a role in the invite drawer shows a live **Can / Cannot** permission preview for system roles (custom roles select silently without a preview). Here's what each system role grants:
| Role | Can | Cannot |
| - | - | - |
| **Admin** | Create, design, edit, and delete QR Codes; view and export analytics; create, update, and delete Lead Lists; set up or remove Custom Domains; generate/update/download QR Codes via Bulk Operation; export shared users data; view custom tags; create and manage folders | View or edit billing information; add or remove users; create or remove custom tags |
| **Manager** | Create, design, and edit QR Codes; view and export analytics; set up new Custom Domains; export shared users data; generate/update/download QR Codes via Bulk Operation; create and manage folders | Delete QR Codes, Lead Lists, and Custom Domains; view or edit billing information; add or remove users; view custom tags |
| **Billing Manager** | View or edit billing information; upgrade or downgrade the subscription plan; add, update, or remove payment methods; view payment history; download invoices | View, create, and update QR Codes or Lead Lists; add or remove users; export shared users data; generate/update/download QR Codes via Bulk Operation; view, set up, or remove custom domains and custom tags |
| **Analyst** | View, download, and export QR Codes data; view and export analytics; view Lead Lists' data & analytics; view the Custom Domain list; view and export shared users data; download QR Codes via Bulk Operation; view custom tags | View or edit billing information; create, design, or edit QR Codes and lead lists; add or remove users; generate or update QR Codes via Bulk Operation; create or remove custom tags |
| **Viewer** | View and export scan analytics/reports; export shared users data; view and export Lead Lists' data & analytics; download QR Codes via Bulk Operation | Edit or download QR Codes; view or edit billing information; add or remove users; create or edit Lead Lists; view custom tags |
**Full Access** doesn't show a Can/Cannot preview — selecting it in the invite drawer leaves the permissions panel empty. It's a separate, unrestricted role distinct from the account **Owner** (the account you signed up with, which isn't invited or assigned a role at all).
## Viewing a role's permissions
From the Users table, click the eye icon next to any teammate's role to open a read-only **View User Role** drawer. For a default system role, every permission is grouped by category — QR Code, Pages, Tags, Analytics, Event Tracking, Integrations, and more — with each individual permission shown as a checked or unchecked checkbox, and a banner confirming the role can't be edited:
## Custom roles are plan-gated
Beyond the five default roles, an account can create fully **custom roles** with its own checkbox-per-permission selection, grouped by the same categories shown above. This is managed from the **Roles** tab (`/users/user-roles`), which lists every role — default and custom — with a **Create Role** button, plus a **Type** column (**System** or **Custom**) and a permission count per role.
Creating and managing custom roles requires a dedicated quota on your plan. Without that quota the **Roles** tab does not appear in the sidebar, and `/users/user-roles` loads an empty page rather than an error. Contact support if you expect to have it.
On an account with the quota, **Create Role** opens a **Create User Role** drawer with a **User Role Name** field and the same category-grouped permission checkboxes shown in the read-only view above — just editable instead of disabled — and a **Save Role** button.
Existing custom roles on the Roles tab get a **View / Edit** action instead of system roles' read-only **View**, opening the same drawer pre-filled and editable. Contact support if you believe your plan should include this and the Roles tab isn't showing up for you.
## Related
* [Shared users](/team/shared-users) — inviting teammates, assigning roles, and managing the Users table
* [Upgrading your plan](/billing/upgrading) — custom roles are plan-gated; upgrading is how you unlock the quota that reveals the Roles tab
# Shared users
Source: https://docs.scanova.io/team/shared-users
Invite your colleagues onto a Scanova account, assign each of them a role, and manage or remove their access again once they no longer need to have it.
The **Users** page (`/users`) is where you invite teammates to your account and manage everyone who already has access. Every invited teammate is a **shared user** — a separate login tied to a role that controls what they can see and do, distinct from your own account (the **Owner**).
## Inviting a user
From the Users page, click **Invite User** in the top right.
Fill in **User's Name** and **Email Address**. Both are required before you can send the invite.
Pick a role from the **Choose Role** dropdown. The panel below it updates immediately with a live **Can / Cannot** breakdown for whichever role is selected — see [Roles & permissions](/team/roles-and-permissions) for what each of the six roles grants.
This toggle only appears for accounts on a plan that includes the tag-based permissions feature.
By default, an invited user can see every QR Code in the account. If your plan includes this feature, a **Tag-based Permission** switch appears below the role picker — turning it on reveals an **Assign Tags** multi-select plus an **Include access to untagged QR Codes** checkbox (checked by default), so you can scope a shared user down to only the QR Codes carrying specific tags.
Click **Send Invite**. The invitation link is valid for **7 days** — a success toast confirms this, and the invite date shows up in the Users table.
An invited user starts in **Pending** status until they accept the invite. Click **Resend invite** on their row at any time if the original email didn't arrive or has expired.
## The Users table
Every teammate you've invited shows up in a table with these columns:
| Column | What it shows |
| - | - |
| **User** | Name and account type (e.g. Member) |
| **Email** | The invited email address |
| **Role** | An inline dropdown — change a user's role directly from the table, plus an eye icon to open a read-only view of that role's full permissions |
| **Status** | **Pending** (with a **Resend invite** link) or **Active** |
| **MFA** | Whether that user has multi-factor authentication enabled (hidden entirely for SSO-login accounts) |
| **Tag Permissions** | On accounts with the tag-based permissions feature, an **Edit** link opening the same Tag-based Permission toggle described above, scoped to that user |
| **Actions** | A remove (trash) icon for that single user |
Filter the table by **Roles** or **Status** using the two filter buttons above it, or search by name.
### Bulk actions
Select one or more rows with their checkboxes to reveal a floating toolbar with three bulk actions:
* **Change Role** — opens a small dialog to assign a new role to every selected user at once
* **Deactivate** — only affects users with **Active** status; pending invitations are excluded from a bulk deactivate even if their row is selected
* **Remove** — permanently removes the selected shared user(s) from your account, after a confirmation
### Per-user tag permissions
On accounts with the tag-based permissions feature enabled, each row's **Tag Permissions** column has an **Edit** link opening a small **Tag Permissions** dialog — the same **Tag-based Permission** toggle (and, once turned on, the same **Assign Tags** and **include untagged** options) available at invite time, editable after the fact, with **Cancel**/**Save** buttons.
### Exporting the list
Click **Export** in the page header to download a CSV of every shared user on the account — including the account Owner — with columns for role, invite/acceptance timestamps, status, tag permissions, MFA status, QR Codes created, and last login.
## Related
* [Roles & permissions](/team/roles-and-permissions) — what each default role grants, and how custom roles work
* [Activity](/team/activity) — what these shared users have been doing, once they're active
* [Logs Export](/team/logs-export) — download that same activity as a CSV
* [Single Sign-On](/authentication/sso) — on SSO-login accounts, the MFA column above is hidden entirely since the identity provider handles authentication
# Creating & managing API keys
Source: https://docs.scanova.io/api-keys/creating-and-managing-keys
Generate, name, and revoke the Management API keys that authenticate your requests, and find where each of those keys lives inside the Scanova dashboard.
This page covers the dashboard UI for Management API keys.
## Where to find it
API keys live at `/api/keys`, under **Developer → API** in the sidebar. This is the same page whether you have zero keys or several — an empty account sees an explainer instead of a bare table:
Access to this page is gated by two things: a permission (view or manage) and a plan quota (`MANAGEMENT_API`). If your plan doesn't grant that quota, you won't see this page at all — contact support if you expect Management API access on your plan.
## Generating a key
Select **Generate API Key** (in the header, or the empty-state's own button) to open the creation drawer:
| Field | Notes |
| - | - |
| **Name** | Required, up to 100 characters. Shown in the key list so you can tell keys apart later — pick something that identifies the integration, e.g. "Zapier — production". |
| **Expiration** | One of **in 1 Month**, **in 3 Months**, **in 1 Year**, or **Never**. This maps directly to the `expiry` field set when the key is created (30 / 90 / 365 / -1 days) — there's no way to change it after creation; create a new key instead. |
Selecting **Create** immediately shows the raw key value in a one-time dialog:
The created-key dialog reads *"Copy your API key now. You won't be able to see it again."* Copy it immediately; it is not shown again.
If you choose **Never** for expiration, the dialog shows an additional amber warning that a key set to never expire should be treated with extra care, since it won't rotate on its own.
## Managing existing keys
The table lists every key on the account: **Name**, a masked **API Key** value with a copy-to-clipboard icon, **Created On**, **Created By**, and **Expires On** (or "Never"). An expired key's date is shown in red.
This account's plan only allows **1** API key at a time — the note directly under the page header ("You can create upto \{limit} API keys") reflects your specific plan's quota, not a platform-wide limit. If you're on an unlimited plan, that note doesn't appear at all.
## Revoking a key
Select the trash icon at the end of a key's row to open a confirmation drawer showing the key's name, its masked value, its Active/Expired badge, and creation date. Confirming revokes it immediately and permanently — there's no "pause" state, and any request already in flight with that key starts failing right away.
## Related
* [API usage dashboard](/api-keys/usage-dashboard) — where (and currently, where not) you can see how much a key has been used
# API usage dashboard
Source: https://docs.scanova.io/api-keys/usage-dashboard
There is no dedicated API usage dashboard in the app today, and per-key usage figures aren't currently exposed through a documented interface either.
Scanova has no dedicated API usage dashboard in the app today. The **Developer → API** section contains a single page, [API Keys](/api-keys/creating-and-managing-keys), and `/api/usage` returns a 404. Usage figures aren't currently exposed through a documented public interface either — until either ships, there's no way to see this data from outside the dashboard product itself.
## Related
* [Creating & managing API keys](/api-keys/creating-and-managing-keys) — the one Developer → API page that does exist today
# Introduction
Source: https://docs.scanova.io/api-reference/getting-started/introduction
The Scanova API: create and manage QR Codes, folders, leads, forms, and analytics over HTTP at api.scanova.io, authenticated with an API key you generate.
The **Scanova API** is the programmatic interface to your Scanova account. Anything you can do in the dashboard — create a QR Code, move it between folders, read a form response, pull scan analytics — you can do over HTTP from your own application.
## Base URL
Every endpoint in this tab lives on one host, and takes an API key as the raw `Authorization` header value:
```bash theme={null}
curl "https://api.scanova.io/qr/" \
-H "Authorization: YOUR_API_KEY"
```
Send the key on its own. It is not a bearer token, so no `Bearer` or `Token` prefix.
Responses are JSON. Paths end in a trailing slash; leaving it off returns a `301` to the slashed form rather than the resource, so send the slash.
## What you can manage
Create, read, update, download, and delete QR Codes, plus health checks and bulk operations
Organize codes into folders, apply shared or custom tags, and manage custom domains and themes
Build forms, read and export responses, manage lead lists, and route submissions to webhooks
Scan totals, geography, devices, and languages, with raw scan export for your own warehouse
Shared users and roles, the media library, activity logs, payments, and the Zapier, HubSpot, and Slack integrations are all covered too — see the sidebar for the complete list.
## Before your first call
* A Scanova account you can Log In to
* An API key, generated from **Developer → API** in the dashboard — see [Creating & managing API keys](/api-keys/creating-and-managing-keys)
* A backend or admin tool that can send HTTPS requests and keep a secret off the client
An API key acts on your live account. There is no separate test environment, so a key labeled `sandbox` reads and writes the same real QR Codes, folders, and leads as any other key.
## Elsewhere in the docs
Three things sit outside this tab and are easy to confuse with it:
| You want to | Go to |
| :- | :- |
| Measure what happens on your site after a scan | [Conversion Tracking](/conversion-tracking/introduction) |
| Give an AI assistant access to this same API | [MCP](/mcp/overview) |
| Work against the older API | [API reference (Legacy)](/v1/index) |
## Next steps
Key environments, the rules every endpoint follows, and what to check when a call returns `401`
`GET /qr/` — the endpoint most integrations start with
Something not behaving as documented? Email [support@scanova.io](mailto:support@scanova.io) or search the [Help Center](https://support.scanova.io).
# Get the latest SSL certificate request
Source: https://docs.scanova.io/api-reference/management-api/custom-domain/certificate-request-retrieve
api-reference/openapi/management-api.json GET /custom-domain/{pk}/certificate-request/
GET /custom-domain/{pk}/certificate-request/
Gets the most recent SSL certificate signing request (CSR) for a domain, on `api.scanova.io`, authenticated with your raw API key.
```
GET https://api.scanova.io/custom-domain/{pk}/certificate-request/
Authorization: YOUR_API_KEY
```
Enterprise-only. Requires the `CUSTOM_DOMAIN_CUSTOM_SSL` quota, which is granted manually — no self-serve plan includes it.
The domain's `id`.
```bash cURL theme={null}
curl "https://api.scanova.io/custom-domain/42/certificate-request/" \
-H "Authorization: YOUR_API_KEY"
```
```json 200 theme={null}
{
"id": 5,
"organization": "Example Inc",
"organizational_unit": "IT",
"country": "US",
"state": "CA",
"locality": "San Francisco",
"email": "ssl@example.com",
"csr": "-----BEGIN CERTIFICATE REQUEST-----\nMIIC...\n-----END CERTIFICATE REQUEST-----",
"created": "2026-09-01T09:00:00Z"
}
```
Returns `null` if no CSR has been created yet. This lets a client resume an in-progress bring-your-own-certificate flow without tracking CSR state itself. `403` if the account lacks the `CUSTOM_DOMAIN_CUSTOM_SSL` quota; `404` if `pk` isn't a domain owned by this account.
## Related
* [Create an SSL certificate request](/api-reference/management-api/custom-domain/certificate-request-create) — generate a new CSR.
* [Upload an SSL certificate](/api-reference/management-api/custom-domain/ssl-certificate-upload) — the next step once a CA has issued the certificate.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Add a custom domain
Source: https://docs.scanova.io/api-reference/management-api/custom-domain/create
api-reference/openapi/management-api.json POST /custom-domain/
POST /custom-domain/
Connects a new custom domain to the account, on `api.scanova.io`, authenticated with your raw API key.
```
POST https://api.scanova.io/custom-domain/
Authorization: YOUR_API_KEY
```
The domain to connect, e.g. `links.example.com`. A scheme prefix, trailing slash, and casing are all normalized automatically — send it however you like.
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/custom-domain/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "domain": "links.example.com" }'
```
```json 201 theme={null}
{
"id": 42,
"domain": "https://links.example.com",
"is_default": false,
"disable_slug_case": false,
"txt_value": "scanova-verification=abc123...",
"is_txt_verified": false,
"is_cname_verified": false,
"is_migrated": false,
"dns_provider": { "name": "Cloudflare", "placeholders": { "cname": { "content": { "label": "Target" } } } },
"txt_error": null,
"cname_error": null,
"root_action": "no_action",
"redirect": null,
"template": null,
"protocol": "https",
"remove_qr_query": false,
"cname_target": "shortcname.scanova.io",
"domain_connect_supported": false,
"domain_connect_checked_at": null,
"created": "2026-09-08T10:00:00Z",
"modified": "2026-09-08T10:00:00Z"
}
```
Adding a domain doesn't verify it — the response's `txt_value` and `cname_target` are the DNS records you (or the domain owner) still need to add, and `is_txt_verified`/`is_cname_verified` start `false`. Scanova asynchronously checks whether the domain's registrar supports Domain Connect one-click setup right after creation; `domain_connect_supported` reflects the outcome once that check completes.
`400` if the domain is invalid or already connected to any account (uniqueness is case-insensitive). `403` if the account lacks the `CUSTOM_DOMAIN` quota, has an inactive plan, lacks `CUSTOM_DOMAIN_CAN_ADD` permission, or has hit its domain limit.
## Related
* [List custom domains](/api-reference/management-api/custom-domain/list) — see connected domains.
* [Verify a custom domain's DNS records](/api-reference/management-api/custom-domain/verify) — check the records once added.
* [Get a Domain Connect setup link](/api-reference/management-api/custom-domain/domain-connect-link) — one-click DNS setup, when supported.
* [Email DNS setup instructions](/api-reference/management-api/custom-domain/send-instructions) — hand the manual steps off to someone else.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Get a Domain Connect setup link
Source: https://docs.scanova.io/api-reference/management-api/custom-domain/domain-connect-link
api-reference/openapi/management-api.json GET /custom-domain/{pk}/domain-connect/
GET /custom-domain/{pk}/domain-connect/
Gets a one-click Domain Connect setup link for a domain, on `api.scanova.io`, authenticated with your raw API key.
```
GET https://api.scanova.io/custom-domain/{pk}/domain-connect/
Authorization: YOUR_API_KEY
```
The domain's `id`, from [List custom domains](/api-reference/management-api/custom-domain/list).
```bash cURL theme={null}
curl "https://api.scanova.io/custom-domain/42/domain-connect/" \
-H "Authorization: YOUR_API_KEY"
```
```json 200 theme={null}
{
"domain_connect_link": "https://dcc.godaddy.com/manage/links.example.com/dns?providerId=scanova&..."
}
```
Domain Connect lets supported registrars (e.g. GoDaddy) apply the required DNS records automatically once the domain owner approves, instead of entering them by hand. `domain_connect_link` comes back `null` when the domain's registrar doesn't support the protocol — fall back to [Email DNS setup instructions](/api-reference/management-api/custom-domain/send-instructions) or manual entry in that case. `404` if `pk` isn't a domain owned by this account.
## Related
* [Add a custom domain](/api-reference/management-api/custom-domain/create) — `domain_connect_supported` on the domain record reflects the same registrar-support check.
* [Email DNS setup instructions](/api-reference/management-api/custom-domain/send-instructions) — the manual-entry fallback.
* [Verify a custom domain's DNS records](/api-reference/management-api/custom-domain/verify) — confirm setup completed.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# List custom domains
Source: https://docs.scanova.io/api-reference/management-api/custom-domain/list
api-reference/openapi/management-api.json GET /custom-domain/
GET /custom-domain/
Lists the account's own connected custom domains, on `api.scanova.io`, authenticated with your raw API key.
```
GET https://api.scanova.io/custom-domain/
Authorization: YOUR_API_KEY
```
Requires the `CUSTOM_DOMAIN` plan quota and `CUSTOM_DOMAIN_CAN_VIEW` permission.
```bash cURL theme={null}
curl "https://api.scanova.io/custom-domain/" \
-H "Authorization: YOUR_API_KEY"
```
```json 200 theme={null}
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 42,
"domain": "https://links.example.com",
"is_default": false,
"disable_slug_case": false,
"txt_value": "scanova-verification=abc123...",
"is_txt_verified": false,
"is_cname_verified": false,
"is_migrated": false,
"dns_provider": { "name": "Cloudflare", "placeholders": { "cname": { "content": { "label": "Target" } } } },
"txt_error": null,
"cname_error": null,
"root_action": "no_action",
"redirect": null,
"template": null,
"protocol": "https",
"remove_qr_query": false,
"cname_target": "shortcname.scanova.io",
"domain_connect_supported": false,
"domain_connect_checked_at": null,
"created": "2026-09-08T10:00:00Z",
"modified": "2026-09-08T10:00:00Z"
}
]
}
```
`txt_value` and `cname_target` are the DNS records this domain still needs (or already has) pointed at Scanova — see [Add a custom domain](/api-reference/management-api/custom-domain/create) for how they're generated. `is_txt_verified`/`is_cname_verified` reflect the last verification run, not a live check.
## Related
* [Add a custom domain](/api-reference/management-api/custom-domain/create) — connect a new one.
* [Verify a custom domain's DNS records](/api-reference/management-api/custom-domain/verify) — re-check DNS and update these flags.
* [List verified domains for QR Code assignment](/api-reference/management-api/custom-domain/verified-list) — the picker-friendly equivalent for assigning a domain to a QR Code.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Email DNS setup instructions
Source: https://docs.scanova.io/api-reference/management-api/custom-domain/send-instructions
api-reference/openapi/management-api.json POST /custom-domain/{pk}/send-instructions/
POST /custom-domain/{pk}/send-instructions/
Emails a domain's DNS setup instructions to a recipient, on `api.scanova.io`, authenticated with your raw API key.
```
POST https://api.scanova.io/custom-domain/{pk}/send-instructions/
Authorization: YOUR_API_KEY
```
The domain's `id`, from [List custom domains](/api-reference/management-api/custom-domain/list).
Recipient email address. Need not belong to a Scanova user — this is meant for handing setup off to whoever manages DNS for the domain (e.g. an IT team).
An optional note included in the email, max 1,000 characters.
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/custom-domain/42/send-instructions/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "it-team@example.com",
"note": "Please add these records before Friday."
}'
```
```json 200 theme={null}
{ "detail": "Setup instructions sent." }
```
The email includes the domain's CNAME target and TXT verification value — the same values returned by [List custom domains](/api-reference/management-api/custom-domain/list). `404` if `pk` isn't a domain owned by this account.
## Related
* [List custom domains](/api-reference/management-api/custom-domain/list) — see the same DNS values in the API response.
* [Get a Domain Connect setup link](/api-reference/management-api/custom-domain/domain-connect-link) — a one-click alternative when the registrar supports it.
* [Verify a custom domain's DNS records](/api-reference/management-api/custom-domain/verify) — check once the records are in place.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# List custom domain root-page templates
Source: https://docs.scanova.io/api-reference/management-api/custom-domain/templates-list
api-reference/openapi/management-api.json GET /custom-domain/templates/
GET /custom-domain/templates/
Lists the account's templates usable for a custom domain's root page, on `api.scanova.io`, authenticated with your raw API key.
```
GET https://api.scanova.io/custom-domain/templates/
Authorization: YOUR_API_KEY
```
```bash cURL theme={null}
curl "https://api.scanova.io/custom-domain/templates/" \
-H "Authorization: YOUR_API_KEY"
```
```json 200 theme={null}
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 3,
"template_id": "tpl-001",
"template_name": "Coming Soon",
"template_description": "A simple coming-soon landing page."
}
]
}
```
A template's `id` is what you assign to a domain's `template` field when its `root_action` is `custom_page` — see [Update a custom domain](/api-reference/management-api/custom-domain/update).
## Related
* [Update a custom domain](/api-reference/management-api/custom-domain/update) — assign a template as the domain's root page.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# List verified domains for QR Code assignment
Source: https://docs.scanova.io/api-reference/management-api/custom-domain/verified-list
api-reference/openapi/management-api.json GET /custom-domain/verified/
GET /custom-domain/verified/
Lists domains eligible for QR short-link assignment, on `api.scanova.io`, authenticated with your raw API key.
```
GET https://api.scanova.io/custom-domain/verified/
Authorization: YOUR_API_KEY
```
Omit the platform default domain (`new.scnv.io`), keeping only domains this account owns.
```bash cURL theme={null}
curl "https://api.scanova.io/custom-domain/verified/?exclude_platform_domains=true" \
-H "Authorization: YOUR_API_KEY"
```
```json 200 theme={null}
[
{
"id": 0,
"domain": "new.scnv.io",
"is_default": true,
"disable_slug_case": false,
"is_platform_domain": true
},
{
"id": 42,
"domain": "links.example.com",
"is_default": false,
"disable_slug_case": false,
"is_platform_domain": false
}
]
```
This is the picker-friendly counterpart to [List custom domains](/api-reference/management-api/custom-domain/list): not paginated, and includes a synthesized platform-default row (`id: 0`) unless `exclude_platform_domains=true` is passed. Use `exclude_platform_domains=true` for flows that must only accept a domain the account personally owns — the `id: 0` sentinel isn't a real domain record and won't resolve against the `/custom-domain/{pk}/` endpoints.
## Related
* [List custom domains](/api-reference/management-api/custom-domain/list) — the full, paginated record set with verification and DNS detail.
* [Verify a custom domain's DNS records](/api-reference/management-api/custom-domain/verify) — get a domain to appear here.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Verify a custom domain's DNS records
Source: https://docs.scanova.io/api-reference/management-api/custom-domain/verify
api-reference/openapi/management-api.json POST /custom-domain/verify/
POST /custom-domain/verify/
Re-checks a custom domain's DNS records and updates its verification status, on `api.scanova.io`, authenticated with your raw API key.
```
POST https://api.scanova.io/custom-domain/verify/
Authorization: YOUR_API_KEY
```
Requires an active plan.
The domain's `id`, from [List custom domains](/api-reference/management-api/custom-domain/list).
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/custom-domain/verify/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "id": 42 }'
```
```json 200 theme={null}
{
"id": 42,
"domain": "https://links.example.com",
"is_default": false,
"disable_slug_case": false,
"txt_value": "scanova-verification=abc123...",
"is_txt_verified": true,
"is_cname_verified": true,
"is_migrated": false,
"dns_provider": { "name": "Cloudflare", "placeholders": { "cname": { "content": { "label": "Target" } } } },
"txt_error": null,
"cname_error": null,
"root_action": "no_action",
"redirect": null,
"template": null,
"protocol": "https",
"remove_qr_query": false,
"cname_target": "shortcname.scanova.io",
"domain_connect_supported": false,
"domain_connect_checked_at": null,
"created": "2026-09-08T10:00:00Z",
"modified": "2026-09-08T10:00:00Z"
}
```
This isn't a status check — it performs a live TXT and CNAME lookup against the domain right now, and persists whatever it finds (including `txt_error`/`cname_error` when a lookup fails). Expect this call to take a moment, and don't poll it faster than DNS propagation would ever justify. `400` if `id` isn't a domain owned by this account.
## Related
* [List custom domains](/api-reference/management-api/custom-domain/list) — see the updated flags without re-running verification.
* [Add a custom domain](/api-reference/management-api/custom-domain/create) — where `txt_value`/`cname_target` come from.
* [Get a Domain Connect setup link](/api-reference/management-api/custom-domain/domain-connect-link) — an alternative one-click setup path that verifies automatically on completion.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# API overview
Source: https://docs.scanova.io/api-reference/management-api/overview
The complete Scanova API reference: how API keys, their environments, and plan quotas work across each of the endpoints served from api.scanova.io.
The **Scanova API** is the programmatic interface for everything you can do in the Scanova dashboard: create and update QR Codes, manage folders and tags, read leads and form submissions, pull analytics, and manage shared users and roles. Every endpoint is served from `api.scanova.io` and authenticated with an API key.
This page covers the architecture that applies to every endpoint. For the full endpoint list, see the pages linked at the bottom of this page.
## Getting started
While logged into the Scanova dashboard, create a key via the dashboard's key-management UI — see [Creating & managing API keys](/api-keys/creating-and-managing-keys). Choose an `environment` — `sandbox`, `live`, `zapier`, or `mcp` — when you create it; this choice is permanent for that key and determines which plan quota is checked on every request the key makes (see below).
The key value returned by the create call is the only time you'll see it in full — store it securely. There's no "reveal" endpoint after the fact.
Send the key as the raw `Authorization` header value against `api.scanova.io`, for example [`GET /qr/`](/api-reference/management-api/qr/list) to list your QR Codes.
## Plan quota
Every request to a data endpoint checks a plan quota tied to the key's `environment`:
| `environment` | Quota checked | Also requires |
| :- | :- | :- |
| `live` or `sandbox` | `MANAGEMENT_API` | — |
| `zapier` | `INTEGRATION_ZAPIER` | The request's `User-Agent` header must literally be `zapier` |
| `mcp` | `INTEGRATION_MCP` | The request's `User-Agent` header must literally be `mcp` |
If a request to the data host returns `401` with the message **"Your plan does not have management API quota"** (or the Zapier/MCP-specific equivalents), your plan doesn't grant that quota. Today, the canonical Pro plan does not include API access — only Free Trial, Enterprise, and Internal plans do. Contact [support@scanova.io](mailto:support@scanova.io) if you believe your account should have it.
`sandbox` is a labeling and quota concept only — it is **not** an isolated test environment. A key created with `environment: sandbox` reads and writes the same real QR Codes, folders, and leads as a `live` key. Don't rely on it for throwaway test data.
A `zapier`/`mcp` key additionally only authenticates when the calling client's `User-Agent` header matches its environment — a `zapier` key rejects requests unless `User-Agent: zapier`, and likewise for `mcp`. This is enforced independently of the quota check above.
## What's in this section
`GET /qr/` — the core data endpoint most integrations start with.
`POST /qr/` — the core data endpoint most integrations start with.
See the sidebar for the full list, including retrieving/updating/deleting a single QR Code, downloading QR Code images, and browsing the trash.
## Related
* [API Reference introduction](/api-reference/getting-started/introduction) — the parent section this overview belongs to.
* [Creating & managing API keys](/api-keys/creating-and-managing-keys) — generate the key this whole API surface authenticates with.
* [List QR Codes](/api-reference/management-api/qr/list) — the core data endpoint most integrations start with.
* [Create a QR Code](/api-reference/management-api/qr/create) — the core data endpoint most integrations start with.
* [MCP overview](/mcp/overview) — an integration built entirely on this same API, using a dedicated `mcp` environment.
# Create a business card template
Source: https://docs.scanova.io/api-reference/management-api/qr-assets/business-card-create
api-reference/openapi/management-api.json POST /qr-assets/business-card-template/
POST /qr-assets/business-card-template/
Saves a new business card template, on `api.scanova.io`, authenticated with your raw API key.
```
POST https://api.scanova.io/qr-assets/business-card-template/
Authorization: YOUR_API_KEY
```
A client-chosen identifier for this template, max 150 characters — unlike [QR Code design templates](/api-reference/management-api/qr-assets/template-create), where `template_id` is server-generated, here **you** supply it. Pick a value unique to your integration.
Template name, max 150 characters.
The vCard/business-card data this template pre-fills.
Design color data, typically JSON encoded as a string.
Font family name.
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/qr-assets/business-card-template/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"template_id": "bct-blue-01",
"name": "Blue Card",
"info": "John Doe\nCEO",
"color": "{\"primary\":\"#0033ff\"}",
"font": "Roboto"
}'
```
```json 201 theme={null}
{
"id": 8,
"template_id": "bct-blue-01",
"name": "Blue Card",
"info": "John Doe\nCEO",
"color": "{\"primary\":\"#0033ff\"}",
"font": "Roboto",
"background_img": null
}
```
`403` if the account lacks the `SAVE_DESIGN_TEMPLATE` quota, has an inactive plan, or lacks `QR_CODE_CAN_ADD`/`QR_CODE_CAN_EDIT` permission.
## Related
* [List business card templates](/api-reference/management-api/qr-assets/business-card-list) — see saved templates.
* [Update a business card template](/api-reference/management-api/qr-assets/business-card-update) — edit an existing one.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Delete a business card template
Source: https://docs.scanova.io/api-reference/management-api/qr-assets/business-card-delete
api-reference/openapi/management-api.json DELETE /qr-assets/business-card-template/{pk}/
DELETE /qr-assets/business-card-template/{pk}/
Deletes a business card template owned by this account, on `api.scanova.io`, authenticated with your raw API key.
```
DELETE https://api.scanova.io/qr-assets/business-card-template/{pk}/
Authorization: YOUR_API_KEY
```
The template's `id`.
```bash cURL theme={null}
curl -X DELETE "https://api.scanova.io/qr-assets/business-card-template/8/" \
-H "Authorization: YOUR_API_KEY"
```
A successful call returns `204 No Content`. `404` if `pk` isn't a template owned by this account.
## Related
* [Retrieve a business card template](/api-reference/management-api/qr-assets/business-card-retrieve) — check before deleting.
* [List business card templates](/api-reference/management-api/qr-assets/business-card-list) — see what else is saved.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# List business card templates
Source: https://docs.scanova.io/api-reference/management-api/qr-assets/business-card-list
api-reference/openapi/management-api.json GET /qr-assets/business-card-template/
GET /qr-assets/business-card-template/
Lists the account's own business card templates, on `api.scanova.io`, authenticated with your raw API key.
```
GET https://api.scanova.io/qr-assets/business-card-template/
Authorization: YOUR_API_KEY
```
```bash cURL theme={null}
curl "https://api.scanova.io/qr-assets/business-card-template/" \
-H "Authorization: YOUR_API_KEY"
```
```json 200 theme={null}
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 8,
"template_id": "bct-blue-01",
"name": "Blue Card",
"info": "John Doe\nCEO",
"color": "{\"primary\":\"#0033ff\"}",
"font": "Roboto",
"background_img": "https://cdn.scanova.io/upload/bct/8.png"
}
]
}
```
Requires the `SAVE_DESIGN_TEMPLATE` plan quota. Used to save vCard/business-card QR Code design presets, distinct from the general-purpose templates in [List QR Code design templates](/api-reference/management-api/qr-assets/template-list).
## Related
* [Create a business card template](/api-reference/management-api/qr-assets/business-card-create) — save a new one.
* [Update a business card template](/api-reference/management-api/qr-assets/business-card-update) — edit an existing one.
* [Delete a business card template](/api-reference/management-api/qr-assets/business-card-delete) — remove one.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Retrieve a business card template
Source: https://docs.scanova.io/api-reference/management-api/qr-assets/business-card-retrieve
api-reference/openapi/management-api.json GET /qr-assets/business-card-template/{pk}/
GET /qr-assets/business-card-template/{pk}/
Retrieves one business card template, on `api.scanova.io`, authenticated with your raw API key.
```
GET https://api.scanova.io/qr-assets/business-card-template/{pk}/
Authorization: YOUR_API_KEY
```
The template's `id`, from [List business card templates](/api-reference/management-api/qr-assets/business-card-list).
```bash cURL theme={null}
curl "https://api.scanova.io/qr-assets/business-card-template/8/" \
-H "Authorization: YOUR_API_KEY"
```
```json 200 theme={null}
{
"id": 8,
"template_id": "bct-blue-01",
"name": "Blue Card",
"info": "John Doe\nCEO",
"color": "{\"primary\":\"#0033ff\"}",
"font": "Roboto",
"background_img": "https://cdn.scanova.io/upload/bct/8.png"
}
```
`404` if `pk` isn't a template owned by this account.
## Related
* [List business card templates](/api-reference/management-api/qr-assets/business-card-list) — find the `id`.
* [Update a business card template](/api-reference/management-api/qr-assets/business-card-update) — edit this template.
* [Delete a business card template](/api-reference/management-api/qr-assets/business-card-delete) — remove this template.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Update a business card template
Source: https://docs.scanova.io/api-reference/management-api/qr-assets/business-card-update
api-reference/openapi/management-api.json PATCH /qr-assets/business-card-template/{pk}/
PATCH /qr-assets/business-card-template/{pk}/
Updates a business card template owned by this account, on `api.scanova.io`, authenticated with your raw API key.
```
PUT https://api.scanova.io/qr-assets/business-card-template/{pk}/
PATCH https://api.scanova.io/qr-assets/business-card-template/{pk}/
Authorization: YOUR_API_KEY
```
The template's `id`.
Both `PUT` and `PATCH` are supported, and accept the same fields documented in [Create a business card template](/api-reference/management-api/qr-assets/business-card-create), including `template_id` (unlike QR Code design templates, this one can be changed after creation).
```bash cURL theme={null}
curl -X PATCH "https://api.scanova.io/qr-assets/business-card-template/8/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Blue Card v2" }'
```
```json 200 theme={null}
{
"id": 8,
"template_id": "bct-blue-01",
"name": "Blue Card v2",
"info": "John Doe\nCEO",
"color": "{\"primary\":\"#0033ff\"}",
"font": "Roboto",
"background_img": "https://cdn.scanova.io/upload/bct/8.png"
}
```
`404` if `pk` isn't a template owned by this account.
## Related
* [Retrieve a business card template](/api-reference/management-api/qr-assets/business-card-retrieve) — view before updating.
* [Create a business card template](/api-reference/management-api/qr-assets/business-card-create) — the field reference this update reuses.
* [Delete a business card template](/api-reference/management-api/qr-assets/business-card-delete) — remove it entirely.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# List category templates
Source: https://docs.scanova.io/api-reference/management-api/qr-assets/category-template-list
api-reference/openapi/management-api.json GET /qr-assets/category-template/
GET /qr-assets/category-template/
Lists the shared catalog of predefined design templates per QR category, on `api.scanova.io`.
```
GET https://api.scanova.io/qr-assets/category-template/
Authorization: YOUR_API_KEY
```
This endpoint doesn't require an API key — it's intentionally open to anonymous callers (guest access to the Publish Panel's template gallery). The dataset is a shared, non-personalized catalog, not scoped to any account, so there's nothing user-specific to protect.
Filter to templates for one QR category, e.g. `website-url`.
```bash cURL theme={null}
curl "https://api.scanova.io/qr-assets/category-template/?category_slug=website-url"
```
```json 200 theme={null}
[
{
"id": 21,
"name": "Ocean Blue",
"template_id": "cat-tpl-21",
"category_slug": "website-url",
"pattern_info": "{\"dotStyle\":\"round\"}",
"image": "https://cdn.scanova.io/upload/category-templates/21.png",
"created": "2026-01-15T08:00:00Z"
}
]
```
## Not paginated
The response is a plain JSON array, not the usual `{count, next, previous, results}` envelope every other list endpoint in this module uses — the full matching set comes back in one response.
## Related
* [List QR Code design templates](/api-reference/management-api/qr-assets/template-list) — the account-owned equivalent, which does paginate.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Create a conditional redirect template
Source: https://docs.scanova.io/api-reference/management-api/qr-assets/conditional-redirect-create
api-reference/openapi/management-api.json POST /qr-assets/conditional-redirect-template/
POST /qr-assets/conditional-redirect-template/
Saves a new conditional redirect template, on `api.scanova.io`, authenticated with your raw API key.
```
POST https://api.scanova.io/qr-assets/conditional-redirect-template/
Authorization: YOUR_API_KEY
```
Template name, max 100 characters.
An array of condition groups. Each group has a `logic` operator (`AND`/`OR`), a `conditions` array of `{field, operator, value}` checks, and a `redirect_url` to send matching scans to.
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/qr-assets/conditional-redirect-template/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"template_name": "Location-based redirect",
"data": [
{
"logic": "AND",
"conditions": [{ "field": "country", "operator": "eq", "value": "US" }],
"redirect_url": "https://example.com/us"
}
]
}'
```
```json 201 theme={null}
{
"id": 3,
"template_name": "Location-based redirect",
"data": [
{
"logic": "AND",
"conditions": [{ "field": "country", "operator": "eq", "value": "US" }],
"redirect_url": "https://example.com/us"
}
],
"created": "2026-09-01T09:00:00Z",
"modified": "2026-09-01T09:00:00Z"
}
```
Saving a template doesn't attach it to any QR Code by itself — apply it to a QR Code's conditional-redirect configuration separately.
## Related
* [List conditional redirect templates](/api-reference/management-api/qr-assets/conditional-redirect-list) — see saved templates.
* [Update a conditional redirect template](/api-reference/management-api/qr-assets/conditional-redirect-update) — edit an existing one.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Delete a conditional redirect template
Source: https://docs.scanova.io/api-reference/management-api/qr-assets/conditional-redirect-delete
api-reference/openapi/management-api.json DELETE /qr-assets/conditional-redirect-template/{pk}/
DELETE /qr-assets/conditional-redirect-template/{pk}/
Deletes a conditional redirect template owned by this account, on `api.scanova.io`, authenticated with your raw API key.
```
DELETE https://api.scanova.io/qr-assets/conditional-redirect-template/{pk}/
Authorization: YOUR_API_KEY
```
The template's `id`.
```bash cURL theme={null}
curl -X DELETE "https://api.scanova.io/qr-assets/conditional-redirect-template/3/" \
-H "Authorization: YOUR_API_KEY"
```
A successful call returns `204 No Content`. `404` if `pk` isn't a template owned by this account. Deleting a template that's already applied to a QR Code doesn't remove the redirect rules from that QR Code — the rules were copied onto it when applied, not referenced live.
## Related
* [Retrieve a conditional redirect template](/api-reference/management-api/qr-assets/conditional-redirect-retrieve) — check before deleting.
* [List conditional redirect templates](/api-reference/management-api/qr-assets/conditional-redirect-list) — see what else is saved.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# List conditional redirect templates
Source: https://docs.scanova.io/api-reference/management-api/qr-assets/conditional-redirect-list
api-reference/openapi/management-api.json GET /qr-assets/conditional-redirect-template/
GET /qr-assets/conditional-redirect-template/
Lists the account's own conditional redirect templates, on `api.scanova.io`, authenticated with your raw API key.
```
GET https://api.scanova.io/qr-assets/conditional-redirect-template/
Authorization: YOUR_API_KEY
```
Filter by a substring of `template_name`.
```bash cURL theme={null}
curl "https://api.scanova.io/qr-assets/conditional-redirect-template/?search=location" \
-H "Authorization: YOUR_API_KEY"
```
```json 200 theme={null}
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 3,
"template_name": "Location-based redirect",
"data": [
{
"logic": "AND",
"conditions": [{ "field": "country", "operator": "eq", "value": "US" }],
"redirect_url": "https://example.com/us"
}
],
"created": "2026-09-01T09:00:00Z",
"modified": "2026-09-01T09:00:00Z"
}
]
}
```
`data` holds the reusable rule set — an array of condition groups, each combining field/operator/value checks with a logic operator and a target `redirect_url`, matched against a scanning visitor's device, location, or time to decide where to send them.
## Related
* [Create a conditional redirect template](/api-reference/management-api/qr-assets/conditional-redirect-create) — save a new one.
* [Update a conditional redirect template](/api-reference/management-api/qr-assets/conditional-redirect-update) — edit an existing one.
* [Delete a conditional redirect template](/api-reference/management-api/qr-assets/conditional-redirect-delete) — remove one.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Retrieve a conditional redirect template
Source: https://docs.scanova.io/api-reference/management-api/qr-assets/conditional-redirect-retrieve
api-reference/openapi/management-api.json GET /qr-assets/conditional-redirect-template/{pk}/
GET /qr-assets/conditional-redirect-template/{pk}/
Retrieves one conditional redirect template, on `api.scanova.io`, authenticated with your raw API key.
```
GET https://api.scanova.io/qr-assets/conditional-redirect-template/{pk}/
Authorization: YOUR_API_KEY
```
The template's `id`, from [List conditional redirect templates](/api-reference/management-api/qr-assets/conditional-redirect-list).
```bash cURL theme={null}
curl "https://api.scanova.io/qr-assets/conditional-redirect-template/3/" \
-H "Authorization: YOUR_API_KEY"
```
```json 200 theme={null}
{
"id": 3,
"template_name": "Location-based redirect",
"data": [
{
"logic": "AND",
"conditions": [{ "field": "country", "operator": "eq", "value": "US" }],
"redirect_url": "https://example.com/us"
}
],
"created": "2026-09-01T09:00:00Z",
"modified": "2026-09-01T09:00:00Z"
}
```
`404` if `pk` isn't a template owned by this account.
## Related
* [List conditional redirect templates](/api-reference/management-api/qr-assets/conditional-redirect-list) — find the `id`.
* [Update a conditional redirect template](/api-reference/management-api/qr-assets/conditional-redirect-update) — edit this template.
* [Delete a conditional redirect template](/api-reference/management-api/qr-assets/conditional-redirect-delete) — remove this template.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Update a conditional redirect template
Source: https://docs.scanova.io/api-reference/management-api/qr-assets/conditional-redirect-update
api-reference/openapi/management-api.json PATCH /qr-assets/conditional-redirect-template/{pk}/
PATCH /qr-assets/conditional-redirect-template/{pk}/
Updates a conditional redirect template owned by this account, on `api.scanova.io`, authenticated with your raw API key.
```
PUT https://api.scanova.io/qr-assets/conditional-redirect-template/{pk}/
PATCH https://api.scanova.io/qr-assets/conditional-redirect-template/{pk}/
Authorization: YOUR_API_KEY
```
The template's `id`.
Both `PUT` and `PATCH` are supported, and accept the same `template_name`/`data` fields documented in [Create a conditional redirect template](/api-reference/management-api/qr-assets/conditional-redirect-create).
```bash cURL theme={null}
curl -X PATCH "https://api.scanova.io/qr-assets/conditional-redirect-template/3/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "template_name": "Location-based redirect (EU added)" }'
```
```json 200 theme={null}
{
"id": 3,
"template_name": "Location-based redirect (EU added)",
"data": [
{
"logic": "AND",
"conditions": [{ "field": "country", "operator": "eq", "value": "US" }],
"redirect_url": "https://example.com/us"
}
],
"created": "2026-09-01T09:00:00Z",
"modified": "2026-09-08T09:00:00Z"
}
```
Updating `data` replaces the entire condition-group array — there's no endpoint to patch a single condition in place, so send the full array back with your change included. `404` if `pk` isn't a template owned by this account.
## Related
* [Retrieve a conditional redirect template](/api-reference/management-api/qr-assets/conditional-redirect-retrieve) — view before updating.
* [Create a conditional redirect template](/api-reference/management-api/qr-assets/conditional-redirect-create) — the field reference this update reuses.
* [Delete a conditional redirect template](/api-reference/management-api/qr-assets/conditional-redirect-delete) — remove it entirely.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# List custom frames
Source: https://docs.scanova.io/api-reference/management-api/qr-assets/custom-frame-list
api-reference/openapi/management-api.json GET /qr-assets/custom-frame/
GET /qr-assets/custom-frame/
Lists the account's own custom QR Code frames, on `api.scanova.io`, authenticated with your raw API key.
```
GET https://api.scanova.io/qr-assets/custom-frame/
Authorization: YOUR_API_KEY
```
List-only — there's no create, update, or delete endpoint for custom frames in the Scanova API; they're uploaded through the dashboard. Unlike most of this module, there's no plan quota gate on this endpoint.
```bash cURL theme={null}
curl "https://api.scanova.io/qr-assets/custom-frame/" \
-H "Authorization: YOUR_API_KEY"
```
```json 200 theme={null}
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 4,
"svg": "https://cdn.scanova.io/upload/frames/4.svg",
"primary_color": "#000000",
"secondary_color": "#ffffff",
"text_color": "#000000",
"bg_color": "#ffffff",
"top": 0,
"left": 0
}
]
}
```
`top`/`left` are pixel placement offsets used when rendering the frame around a QR Code.
## Related
* [List QR Code design templates](/api-reference/management-api/qr-assets/template-list) — where a frame's id is used once selected.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Create a user design template
Source: https://docs.scanova.io/api-reference/management-api/qr-assets/design-template-create
api-reference/openapi/management-api.json POST /qr-assets/design-template/
POST /qr-assets/design-template/
Saves a new design template for a QR category or a form, on `api.scanova.io`, authenticated with your raw API key.
```
POST https://api.scanova.io/qr-assets/design-template/
Authorization: YOUR_API_KEY
```
Template name, max 150 characters.
`qr_category` or `form`.
The `QRCategory` id this template belongs to. **Required** when `resource_type` is `qr_category`; **must be omitted** when `resource_type` is `form`.
Arbitrary JSON design data.
Gradient style, e.g. `linear`.
Color palette, typically JSON encoded as a string.
Font style data.
Marks this record as a reusable template rather than a one-off save.
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/qr-assets/design-template/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Blue Gradient",
"resource_type": "qr_category",
"category": 3,
"design_info": { "bg": "#0033ff" },
"gradient_type": "linear",
"is_template": true
}'
```
```json 201 theme={null}
{
"id": 9,
"name": "Blue Gradient",
"resource_type": "qr_category",
"category": 3,
"design_info": { "bg": "#0033ff" },
"gradient_type": "linear",
"color_palette": null,
"font_styles": null,
"is_template": true,
"info": null,
"template_id": null
}
```
## The `resource_type`/`category` pairing rule
`category` is required for `resource_type=qr_category` and forbidden for `resource_type=form` — both the serializer and a database check constraint enforce this, so it can't drift. `400` with `{"category": "This field is required for qr_category templates."}` or the inverse if you get it backwards.
## Related
* [List user design templates](/api-reference/management-api/qr-assets/design-template-list) — see saved templates.
* [Update a user design template](/api-reference/management-api/qr-assets/design-template-update) — edit an existing one.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Delete a user design template
Source: https://docs.scanova.io/api-reference/management-api/qr-assets/design-template-delete
api-reference/openapi/management-api.json DELETE /qr-assets/design-template/{pk}/
DELETE /qr-assets/design-template/{pk}/
Deletes a design template owned by this account, on `api.scanova.io`, authenticated with your raw API key.
```
DELETE https://api.scanova.io/qr-assets/design-template/{pk}/
Authorization: YOUR_API_KEY
```
The template's `id`.
```bash cURL theme={null}
curl -X DELETE "https://api.scanova.io/qr-assets/design-template/9/" \
-H "Authorization: YOUR_API_KEY"
```
A successful call returns `204 No Content`. `404` if `pk` isn't a template owned by this account.
## Related
* [Retrieve a user design template](/api-reference/management-api/qr-assets/design-template-retrieve) — check before deleting.
* [List user design templates](/api-reference/management-api/qr-assets/design-template-list) — see what else is saved.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# List user design templates
Source: https://docs.scanova.io/api-reference/management-api/qr-assets/design-template-list
api-reference/openapi/management-api.json GET /qr-assets/design-template/
GET /qr-assets/design-template/
Lists the account's own design templates for a QR category or a form, on `api.scanova.io`, authenticated with your raw API key.
```
GET https://api.scanova.io/qr-assets/design-template/
Authorization: YOUR_API_KEY
```
`qr_category` or `form`.
**Required** when `resource_type` is `qr_category` (the default). **Must be omitted** when `resource_type` is `form`.
```bash cURL theme={null}
curl "https://api.scanova.io/qr-assets/design-template/?resource_type=qr_category&category_id=3" \
-H "Authorization: YOUR_API_KEY"
```
```json 200 theme={null}
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 9,
"name": "Blue Gradient",
"resource_type": "qr_category",
"category": 3,
"design_info": { "bg": "#0033ff" },
"gradient_type": "linear",
"color_palette": "[\"#000\",\"#fff\"]",
"font_styles": null,
"is_template": true,
"info": null,
"template_id": "abc123"
}
]
}
```
`400` if `category_id` is missing for `resource_type=qr_category`, present for `resource_type=form`, or `resource_type` is neither `qr_category` nor `form`.
## Related
* [Create a user design template](/api-reference/management-api/qr-assets/design-template-create) — save a new one.
* [Update a user design template](/api-reference/management-api/qr-assets/design-template-update) — edit an existing one.
* [Delete a user design template](/api-reference/management-api/qr-assets/design-template-delete) — remove one.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Retrieve a user design template
Source: https://docs.scanova.io/api-reference/management-api/qr-assets/design-template-retrieve
api-reference/openapi/management-api.json GET /qr-assets/design-template/{pk}/
GET /qr-assets/design-template/{pk}/
Retrieves one user design template, on `api.scanova.io`, authenticated with your raw API key.
```
GET https://api.scanova.io/qr-assets/design-template/{pk}/
Authorization: YOUR_API_KEY
```
The template's `id`, from [List user design templates](/api-reference/management-api/qr-assets/design-template-list).
```bash cURL theme={null}
curl "https://api.scanova.io/qr-assets/design-template/9/" \
-H "Authorization: YOUR_API_KEY"
```
```json 200 theme={null}
{
"id": 9,
"name": "Blue Gradient",
"resource_type": "qr_category",
"category": 3,
"design_info": { "bg": "#0033ff" },
"gradient_type": "linear",
"color_palette": "[\"#000\",\"#fff\"]",
"font_styles": null,
"is_template": true,
"info": null,
"template_id": "abc123"
}
```
`404` if `pk` isn't a template owned by this account.
## Related
* [List user design templates](/api-reference/management-api/qr-assets/design-template-list) — find the `id`.
* [Update a user design template](/api-reference/management-api/qr-assets/design-template-update) — edit this template.
* [Delete a user design template](/api-reference/management-api/qr-assets/design-template-delete) — remove this template.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Update a user design template
Source: https://docs.scanova.io/api-reference/management-api/qr-assets/design-template-update
api-reference/openapi/management-api.json PATCH /qr-assets/design-template/{pk}/
PATCH /qr-assets/design-template/{pk}/
Updates a design template owned by this account, on `api.scanova.io`, authenticated with your raw API key.
```
PUT https://api.scanova.io/qr-assets/design-template/{pk}/
PATCH https://api.scanova.io/qr-assets/design-template/{pk}/
Authorization: YOUR_API_KEY
```
The template's `id`.
Both `PUT` and `PATCH` are supported, and accept the same fields documented in [Create a user design template](/api-reference/management-api/qr-assets/design-template-create) — including the `resource_type`/`category` pairing rule, which still applies on update.
```bash cURL theme={null}
curl -X PATCH "https://api.scanova.io/qr-assets/design-template/9/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "design_info": { "bg": "#112233" } }'
```
```json 200 theme={null}
{
"id": 9,
"name": "Blue Gradient",
"resource_type": "qr_category",
"category": 3,
"design_info": { "bg": "#112233" },
"gradient_type": "linear",
"color_palette": "[\"#000\",\"#fff\"]",
"font_styles": null,
"is_template": true,
"info": null,
"template_id": "abc123"
}
```
`404` if `pk` isn't a template owned by this account.
## Related
* [Retrieve a user design template](/api-reference/management-api/qr-assets/design-template-retrieve) — view before updating.
* [Create a user design template](/api-reference/management-api/qr-assets/design-template-create) — the field reference and `resource_type`/`category` rule this update reuses.
* [Delete a user design template](/api-reference/management-api/qr-assets/design-template-delete) — remove it entirely.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# List custom eye patterns
Source: https://docs.scanova.io/api-reference/management-api/qr-assets/eye-pattern-list
api-reference/openapi/management-api.json GET /qr-assets/eye-pattern/
GET /qr-assets/eye-pattern/
Lists the account's own custom QR Code eye patterns, on `api.scanova.io`, authenticated with your raw API key.
```
GET https://api.scanova.io/qr-assets/eye-pattern/
Authorization: YOUR_API_KEY
```
Requires the `CUSTOM_EYE_PATTERN` plan quota. List-only — there's no create, update, or delete endpoint for eye patterns in the Scanova API; custom eye patterns are uploaded through the dashboard.
```bash cURL theme={null}
curl "https://api.scanova.io/qr-assets/eye-pattern/" \
-H "Authorization: YOUR_API_KEY"
```
```json 200 theme={null}
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 7,
"name": "Rounded Diamond",
"svg": "https://cdn.scanova.io/upload/eye-patterns/7.svg",
"inner_eye_color": "#000000",
"outer_eye_color": "#333333"
}
]
}
```
`403` if the account's plan doesn't include the `CUSTOM_EYE_PATTERN` quota.
## Related
* [List QR Code design templates](/api-reference/management-api/qr-assets/template-list) — where an eye pattern's id is used once selected.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Create a QR Code design template
Source: https://docs.scanova.io/api-reference/management-api/qr-assets/template-create
api-reference/openapi/management-api.json POST /qr-assets/template/
POST /qr-assets/template/
Saves a new QR Code design template, on `api.scanova.io`, authenticated with your raw API key.
```
POST https://api.scanova.io/qr-assets/template/
Authorization: YOUR_API_KEY
```
Template name, max 150 characters.
`"P"` (Poster) or `"N"` (Normal). Create-only — see the note below.
Design pattern data, typically JSON encoded as a string (dot style, eye shape, colors, logo).
A base64-encoded image, or omit to save pattern data only.
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/qr-assets/template/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Summer Poster",
"template_type": "P",
"pattern_info": "{\"dotStyle\":\"round\"}",
"image": "data:image/png;base64,iVBORw0KGgo..."
}'
```
```json 201 theme={null}
{
"id": 12,
"name": "Summer Poster",
"template_id": "a1b2c3d4e5f6",
"template_type": "P",
"template_type_display": "Poster",
"pattern_info": "{\"dotStyle\":\"round\"}",
"image": "https://cdn.scanova.io/upload/images/42/template-foo.png"
}
```
`template_id` is server-generated and unique — you never set it. `403` if the account lacks the `SAVE_DESIGN_TEMPLATE` quota, has an inactive plan, or lacks `QR_CODE_CAN_ADD`/`QR_CODE_CAN_EDIT` permission (relevant for shared users with restricted roles).
## Related
* [List QR Code design templates](/api-reference/management-api/qr-assets/template-list) — see saved templates.
* [Update a QR Code design template](/api-reference/management-api/qr-assets/template-update) — edit an existing one; `template_type` can't be changed here.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Delete a QR Code design template
Source: https://docs.scanova.io/api-reference/management-api/qr-assets/template-delete
api-reference/openapi/management-api.json DELETE /qr-assets/template/{pk}/
DELETE /qr-assets/template/{pk}/
Deletes a QR Code design template owned by this account, on `api.scanova.io`, authenticated with your raw API key.
```
DELETE https://api.scanova.io/qr-assets/template/{pk}/
Authorization: YOUR_API_KEY
```
The template's `id`.
```bash cURL theme={null}
curl -X DELETE "https://api.scanova.io/qr-assets/template/12/" \
-H "Authorization: YOUR_API_KEY"
```
A successful call returns `204 No Content`. `404` if `pk` isn't a template owned by this account. Deleting a template doesn't affect any QR Code that was previously created from it — the pattern data was copied onto the QR Code at creation time, not referenced live.
## Related
* [Retrieve a QR Code design template](/api-reference/management-api/qr-assets/template-retrieve) — check before deleting.
* [List QR Code design templates](/api-reference/management-api/qr-assets/template-list) — see what else is saved.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# List QR Code design templates
Source: https://docs.scanova.io/api-reference/management-api/qr-assets/template-list
api-reference/openapi/management-api.json GET /qr-assets/template/
GET /qr-assets/template/
Lists the account's own saved QR Code design templates, on `api.scanova.io`, authenticated with your raw API key.
```
GET https://api.scanova.io/qr-assets/template/
Authorization: YOUR_API_KEY
```
Requires the `SAVE_DESIGN_TEMPLATE` plan quota — see [API overview](/api-reference/management-api/overview). `template_type` distinguishes a `"P"` (Poster) template from a `"N"` (Normal) one.
```bash cURL theme={null}
curl "https://api.scanova.io/qr-assets/template/" \
-H "Authorization: YOUR_API_KEY"
```
```json 200 theme={null}
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 12,
"name": "Summer Poster",
"template_id": "a1b2c3d4e5f6",
"template_type": "P",
"template_type_display": "Poster",
"pattern_info": "{\"dotStyle\":\"round\"}",
"image": "https://cdn.scanova.io/upload/images/42/template-foo.png"
}
]
}
```
`?template_type=P` or `?template_type=N` filters by type. Scope: strictly the account's own templates.
## Related
* [Create a QR Code design template](/api-reference/management-api/qr-assets/template-create) — save a new one.
* [Update a QR Code design template](/api-reference/management-api/qr-assets/template-update) — edit an existing one.
* [Delete a QR Code design template](/api-reference/management-api/qr-assets/template-delete) — remove one.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Retrieve a QR Code design template
Source: https://docs.scanova.io/api-reference/management-api/qr-assets/template-retrieve
api-reference/openapi/management-api.json GET /qr-assets/template/{pk}/
GET /qr-assets/template/{pk}/
Retrieves one QR Code design template, on `api.scanova.io`, authenticated with your raw API key.
```
GET https://api.scanova.io/qr-assets/template/{pk}/
Authorization: YOUR_API_KEY
```
The template's `id`, from [List QR Code design templates](/api-reference/management-api/qr-assets/template-list).
```bash cURL theme={null}
curl "https://api.scanova.io/qr-assets/template/12/" \
-H "Authorization: YOUR_API_KEY"
```
```json 200 theme={null}
{
"id": 12,
"name": "Summer Poster",
"template_id": "a1b2c3d4e5f6",
"template_type": "P",
"template_type_display": "Poster",
"pattern_info": "{\"dotStyle\":\"round\"}",
"image": "https://cdn.scanova.io/upload/images/42/template-foo.png"
}
```
`404` if `pk` isn't a template owned by this account.
## Related
* [List QR Code design templates](/api-reference/management-api/qr-assets/template-list) — find the `id`.
* [Update a QR Code design template](/api-reference/management-api/qr-assets/template-update) — edit this template.
* [Delete a QR Code design template](/api-reference/management-api/qr-assets/template-delete) — remove this template.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Update a QR Code design template
Source: https://docs.scanova.io/api-reference/management-api/qr-assets/template-update
api-reference/openapi/management-api.json PATCH /qr-assets/template/{pk}/
PATCH /qr-assets/template/{pk}/
Updates a QR Code design template owned by this account, on `api.scanova.io`, authenticated with your raw API key.
```
PUT https://api.scanova.io/qr-assets/template/{pk}/
PATCH https://api.scanova.io/qr-assets/template/{pk}/
Authorization: YOUR_API_KEY
```
The template's `id`.
Both `PUT` (full replace) and `PATCH` (partial update) are supported, and accept the same `name`/`pattern_info`/`image` fields documented in [Create a QR Code design template](/api-reference/management-api/qr-assets/template-create).
```bash cURL theme={null}
curl -X PATCH "https://api.scanova.io/qr-assets/template/12/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Winter Poster" }'
```
```json 200 theme={null}
{
"id": 12,
"name": "Winter Poster",
"template_id": "a1b2c3d4e5f6",
"template_type": "P",
"template_type_display": "Poster",
"pattern_info": "{\"dotStyle\":\"round\"}",
"image": "https://cdn.scanova.io/upload/images/42/template-foo.png"
}
```
## `template_type` can't be changed here
`template_type` is create-only — sending it in a `PUT`/`PATCH` body is silently dropped rather than rejected, so the template keeps its original Poster/Normal type for its whole life. `404` if `pk` isn't a template owned by this account.
## Related
* [Retrieve a QR Code design template](/api-reference/management-api/qr-assets/template-retrieve) — view before updating.
* [Create a QR Code design template](/api-reference/management-api/qr-assets/template-create) — the field reference this update reuses.
* [Delete a QR Code design template](/api-reference/management-api/qr-assets/template-delete) — remove it entirely.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Create a text logo template
Source: https://docs.scanova.io/api-reference/management-api/qr-assets/text-logo-create
api-reference/openapi/management-api.json POST /qr-assets/text-logo/
POST /qr-assets/text-logo/
Saves a new text logo template, on `api.scanova.io`, authenticated with your raw API key.
```
POST https://api.scanova.io/qr-assets/text-logo/
Authorization: YOUR_API_KEY
```
The logo text, max 20 characters.
Bold styling.
Italic styling.
Hex color, without the `#` prefix, max 10 characters.
Font size.
Font family name, max 100 characters.
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/qr-assets/text-logo/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"text": "SALE",
"is_bold": true,
"color": "FF0000",
"font_size": 120,
"font_family": "Arial"
}'
```
```json 201 theme={null}
{
"id": 5,
"text": "SALE",
"is_bold": true,
"is_italic": false,
"color": "FF0000",
"font_size": 120,
"font_family": "Arial",
"has_user": true,
"modified": "2026-09-01T10:00:00Z"
}
```
The created logo is always owned by this account — `has_user` comes back `true`.
## Related
* [List text logo templates](/api-reference/management-api/qr-assets/text-logo-list) — see saved and default logos.
* [Delete a text logo template](/api-reference/management-api/qr-assets/text-logo-delete) — remove this logo.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Delete a text logo template
Source: https://docs.scanova.io/api-reference/management-api/qr-assets/text-logo-delete
api-reference/openapi/management-api.json DELETE /qr-assets/text-logo/{pk}/
DELETE /qr-assets/text-logo/{pk}/
Deletes a text logo template owned by this account, on `api.scanova.io`, authenticated with your raw API key.
```
DELETE https://api.scanova.io/qr-assets/text-logo/{pk}/
Authorization: YOUR_API_KEY
```
The text logo's `id`.
```bash cURL theme={null}
curl -X DELETE "https://api.scanova.io/qr-assets/text-logo/5/" \
-H "Authorization: YOUR_API_KEY"
```
A successful call returns `204 No Content`. `404` if `pk` isn't a logo owned by this account — including a shared system default, which can't be deleted through this endpoint.
## Related
* [List text logo templates](/api-reference/management-api/qr-assets/text-logo-list) — find the `id`.
* [Create a text logo template](/api-reference/management-api/qr-assets/text-logo-create) — save a replacement.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# List text logo templates
Source: https://docs.scanova.io/api-reference/management-api/qr-assets/text-logo-list
api-reference/openapi/management-api.json GET /qr-assets/text-logo/
GET /qr-assets/text-logo/
Lists text logo templates on `api.scanova.io`.
```
GET https://api.scanova.io/qr-assets/text-logo/
Authorization: YOUR_API_KEY
```
Unlike every other endpoint in this module, this one doesn't require an API key — it's readable anonymously, returning only the shared system default text logos. Send your key to also see the account's own saved logos alongside the defaults.
```bash cURL theme={null}
curl "https://api.scanova.io/qr-assets/text-logo/" \
-H "Authorization: YOUR_API_KEY"
```
```json 200 theme={null}
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 5,
"text": "SALE",
"is_bold": true,
"is_italic": false,
"color": "FF0000",
"font_size": 120,
"font_family": "Arial",
"has_user": true,
"modified": "2026-09-01T10:00:00Z"
}
]
}
```
`has_user: false` marks a shared system default; `has_user: true` marks one owned by this account.
## Related
* [Create a text logo template](/api-reference/management-api/qr-assets/text-logo-create) — save a new one.
* [Delete a text logo template](/api-reference/management-api/qr-assets/text-logo-delete) — remove one owned by this account.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# List and generate AI QR Code variants
Source: https://docs.scanova.io/api-reference/management-api/qr/ai-list-create
api-reference/openapi/management-api.json GET /qr/{qrid}/ai/
GET, POST /qr/{qrid}/ai/
Lists and generates AI artwork variants for one **Dynamic** QR Code.
```
GET https://api.scanova.io/qr/{qrid}/ai/
POST https://api.scanova.io/qr/{qrid}/ai/
Authorization: YOUR_API_KEY
```
The QR Code's `qrid`. Must be a Dynamic (`dy`) QR Code — a static one returns `400`.
Generating consumes the `AI_QR_CODE` quota.
### Generate (POST)
The image prompt, max 200 characters.
What to avoid, max 50 characters.
Every other field — `weight`, `qr_code_pattern`, `preset`, `provider`, `status`, `selected`, `is_scannable`, `qr_image_url`, `qr_image`, `created` — is read-only.
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/qr/Qa1b2c3d4e5f6g7h8/ai/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "cyberpunk city skyline at night",
"negative_prompt": "blurry, low quality"
}'
```
```json 201 Created theme={null}
{
"results": [
{ "id": 501, "provider": "quickqr", "status": "generating", "selected": false },
{ "id": 502, "provider": "qrcode_ai", "status": "generating", "selected": false }
]
}
```
One `POST` creates **one record per configured AI provider**, so the response is a `{"results": [...]}` list, not a single object. Don't write client code that expects a single created resource — and remember each of those records counts against your generation quota.
Generation is asynchronous. `status` moves `new` → `generating` → `generated` (or `failed`). The default `GET` returns only `generated` variants, so poll in-flight records explicitly with `?ids=501,502` until they leave `generating`.
## Related
* [Retrieve and select an AI variant](/api-reference/management-api/qr/ai-retrieve-update) — read one record and mark it as the chosen design.
* [Create a QR Code](/api-reference/management-api/qr/create) — `ai_session_id` links a new QR Code to an AI Assistant session.
* [Update a QR Code's design](/api-reference/management-api/qr/update-pattern) — the non-AI design path.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Retrieve and select an AI variant
Source: https://docs.scanova.io/api-reference/management-api/qr/ai-retrieve-update
api-reference/openapi/management-api.json GET /qr/{qrid}/ai/{id}/
GET, PATCH /qr/{qrid}/ai/{id}/
Reads one AI variant record, and selects it as the QR Code's chosen design.
```
GET https://api.scanova.io/qr/{qrid}/ai/{id}/
PATCH https://api.scanova.io/qr/{qrid}/ai/{id}/
Authorization: YOUR_API_KEY
```
The QR Code's `qrid`.
The AI variant record's id, from [List and generate AI QR Code variants](/api-reference/management-api/qr/ai-list-create).
`GET` is also the polling endpoint while a generation is in flight — watch `status` until it leaves `generating`.
### Select (PATCH)
The **only** writable field on this resource.
```bash cURL theme={null}
curl -X PATCH "https://api.scanova.io/qr/Qa1b2c3d4e5f6g7h8/ai/501/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"selected": true
}'
```
Setting `selected: true` **deselects every other AI variant** for the same QR Code — exactly one variant is selected at a time. There is no call that selects two, and no need to deselect the previous one first.
## Related
* [List and generate AI QR Code variants](/api-reference/management-api/qr/ai-list-create) — the collection this record belongs to.
* [Retrieve a QR Code](/api-reference/management-api/qr/retrieve) — the QR Code the selected variant applies to.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Copy a QR Code
Source: https://docs.scanova.io/api-reference/management-api/qr/bulk-copy-create
api-reference/openapi/management-api.json POST /qr/multi/
POST /qr/multi/
Creates up to ten copies of an existing QR Code.
```
POST https://api.scanova.io/qr/multi/
Authorization: YOUR_API_KEY
```
The source QR Code to copy.
Base name for the copies, max 250 characters.
How many copies to create, from `1` to `10`.
Optional custom domain for the copies' short URLs.
The remaining fields — `domain`, `password`, `lead_list`, `form`, `high_accuracy_mode`, `pattern_info`, `tags`, `is_copy`, `is_page`, `has_qr` — are **booleans that mean "copy this aspect from the source?"**, not values:
Copy the source's custom domain.
Copy the source's password protection.
Copy the attached lead list.
Copy the attached form.
Copy the design.
Copy the tags.
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/qr/multi/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"qrid": "Qa1b2c3d4e5f6g7h8",
"name": "Summer Sale Landing",
"number_of_copies": 3,
"domain": true,
"password": false,
"lead_list": false,
"form": false,
"high_accuracy_mode": false,
"pattern_info": true,
"tags": true,
"is_copy": true,
"is_page": false,
"has_qr": true
}'
```
Returns `201` with the created QR Codes as a list, each in the full detail shape. Every copy consumes QR-count quota exactly as a fresh create would.
This path also answers to `PUT` for an unrelated operation — see [Bulk-update QR Code fields](/api-reference/management-api/qr/bulk-field-update). Same path, different verb, different contract.
## Related
* [Bulk-update QR Code fields](/api-reference/management-api/qr/bulk-field-update) — the `PUT` operation on this same path.
* [Create a QR Code](/api-reference/management-api/qr/create) — create a fresh QR Code instead of a copy.
* [List QR Codes](/api-reference/management-api/qr/list) — find the source `qrid`.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Bulk-update QR Code fields
Source: https://docs.scanova.io/api-reference/management-api/qr/bulk-field-update
api-reference/openapi/management-api.json PUT /qr/multi/
PUT /qr/multi/
Sets the same field values across many QR Codes at once.
```
PUT https://api.scanova.io/qr/multi/
Authorization: YOUR_API_KEY
```
The `qrid` values to update.
Apply to **every** QR Code the account owns, ignoring `qrids`.
Any remaining top-level keys are applied as raw QR Code field overrides — `{"is_active": false}` in the example below.
```bash cURL theme={null}
curl -X PUT "https://api.scanova.io/qr/multi/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"qrids": [
"Qa1b2c3d4e5f6g7h8",
"Qb2c3d4e5f6g7h8i9"
],
"is_active": false
}'
```
Returns `204 No Content`.
This is a **low-level field-set that bypasses per-category schema validation** entirely for the fields it writes. It does not carry the same safety as [Update a QR Code](/api-reference/management-api/qr/update) — a value that endpoint would reject can be written here, leaving content that fails validation the next time it is edited or published. Use it for simple flags across a selection, not for content.
Toggling `is_active` has a side effect beyond the flag itself: it clears or re-derives `expire_on` on the related dynamic URL objects, so a previously-expired QR Code reactivated this way does not immediately re-expire.
## Related
* [Update a QR Code](/api-reference/management-api/qr/update) — the validated, single-QR update path.
* [Copy a QR Code](/api-reference/management-api/qr/bulk-copy-create) — the `POST` operation on this same path.
* [Delete many QR Codes](/api-reference/management-api/qr/multi-delete) — the bulk delete equivalent.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# List QR categories
Source: https://docs.scanova.io/api-reference/management-api/qr/category-list
api-reference/openapi/management-api.json GET /qr/category/
GET /qr/category/
The QR category catalog — the source of the `category` primary keys and slugs that every create, update, and filter call in this module needs.
```
GET https://api.scanova.io/qr/category/
Authorization: YOUR_API_KEY
```
On the Scanova API host this endpoint **requires authentication**. The same catalog is open on the public API host, so code ported from there will start returning `401` here unless it sends the `Authorization` header.
### Query parameters
Comma-separated filter on the QR types a category supports, e.g. `st,dy,bt`. Returns only categories that allow at least one of them.
Reshape the response into named sections instead of one `All` bucket. One of `recommended`, `use_case`, `content_type`, or `favourite`.
```bash cURL theme={null}
curl -G "https://api.scanova.io/qr/category/" \
-H "Authorization: YOUR_API_KEY" \
-d view_type=recommended
```
```json 200 OK theme={null}
{
"All": [
{
"id": 3,
"name": "Website URL",
"slug": "url",
"allowed_qr_types": "dy",
"allowed_qr_types_display": "Dynamic",
"has_landing_page": false,
"is_active": true,
"is_new": false
}
]
}
```
With `?view_type=recommended` the same rows come back grouped under section names instead:
```json theme={null}
{
"Recommended QR Code Categories": [ ... ],
"Other QR Code Categories": [ ... ]
}
```
The response is a **dict keyed by section name**, not a bare array, and it is never paginated. Each row also carries `description`, `preview_image`, `tags`, `helpdesk_link`, `qr_name_pattern`, `created`, and `modified`.
`allowed_qr_types` is what makes a create call succeed or fail: a static-only category rejects `qr_type: "dy"` and vice versa. `qr_name_pattern` is the template used to auto-name a QR Code created without a `name`.
## Related
* [Create a QR Code](/api-reference/management-api/qr/create) — where these `category` ids are used.
* [Validate QR Code content](/api-reference/management-api/qr/validate-info) — check an `info` payload against a category's schema.
* [List and add favourite categories](/api-reference/management-api/qr/favorite-category-list-create) — populate the `favourite` view.
* [Change a QR Code's category](/api-reference/management-api/qr/change-category) — move an existing Dynamic QR Code to another category.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Change a QR Code's category
Source: https://docs.scanova.io/api-reference/management-api/qr/change-category
api-reference/openapi/management-api.json PATCH /qr/{qrid}/change-category/
PATCH /qr/{qrid}/change-category/
Moves a Dynamic QR Code to a different category.
```
PATCH https://api.scanova.io/qr/{qrid}/change-category/
Authorization: YOUR_API_KEY
```
The QR Code's `qrid`.
Primary key of the destination category.
```bash cURL theme={null}
curl -X PATCH "https://api.scanova.io/qr/Qa1b2c3d4e5f6g7h8/change-category/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"category": 8
}'
```
**This is destructive.** On success the QR Code's stored `info` is wiped to an empty string and its previous content and media are discarded — the old category's content cannot be recovered. It is not a metadata-only edit. Expect to immediately follow up with [Update a QR Code](/api-reference/management-api/qr/update) to populate `info` for the new category.
The short URL, `qrid`, scan history, and design survive the change; only the content does not.
```json 400 Bad Request theme={null}
{
"qr_type": ["Edit category on static QR codes is not allowed."]
}
```
Category changes are refused for **static** QR Codes and for **static-only** target categories — a static QR Code's payload is baked into the printed pattern, so there is nothing to repoint.
## Related
* [Update a QR Code](/api-reference/management-api/qr/update) — populate `info` for the new category afterwards.
* [List QR categories](/api-reference/management-api/qr/category-list) — pick a destination category that allows `dy`.
* [Retrieve a QR Code](/api-reference/management-api/qr/retrieve) — confirm the wiped content and new category.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Bulk-create child QR Codes
Source: https://docs.scanova.io/api-reference/management-api/qr/child-bulk-create
api-reference/openapi/management-api.json POST /qr/{parent_qrid}/children/bulk/
POST /qr/{parent_qrid}/children/bulk/
Creates many child variants under one GS1 parent in a single call.
```
POST https://api.scanova.io/qr/{parent_qrid}/children/bulk/
Authorization: YOUR_API_KEY
```
The `qrid` of the GS1 parent QR Code.
Non-empty list of child-create-shaped objects — each entry takes the same `qualifiers`, `dataAttributes`, `variantLabel`, and `name` fields as [Create a child QR Code](/api-reference/management-api/qr/child-list-create#create-post).
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/qr/Qz9y8x7w6v5u4t3s/children/bulk/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"rows": [
{
"qualifiers": [
{
"id": "10",
"value": "LOT-2026-04"
}
],
"variantLabel": "500ml bottle"
},
{
"qualifiers": [
{
"id": "10",
"value": "LOT-2026-05"
}
],
"variantLabel": "500ml bottle"
}
]
}'
```
Returns `201` with the created children as a list.
The `CHILD_QR_CODES` quota is checked for the whole batch up front, using a future limit of `len(rows) - 1`, so a batch that would exceed the plan fails before any child is created rather than part-way through.
## Related
* [List and create child QR Codes](/api-reference/management-api/qr/child-list-create) — the single-child form, and the field reference.
* [Delete a child QR Code](/api-reference/management-api/qr/child-delete) — remove a variant created here.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Delete a child QR Code
Source: https://docs.scanova.io/api-reference/management-api/qr/child-delete
api-reference/openapi/management-api.json DELETE /qr/{parent_qrid}/children/{child_qrid}/
DELETE /qr/{parent_qrid}/children/{child_qrid}/
Permanently deletes one child variant.
```
DELETE https://api.scanova.io/qr/{parent_qrid}/children/{child_qrid}/
Authorization: YOUR_API_KEY
```
The `qrid` of the GS1 parent QR Code.
The `qrid` of the child variant to delete.
Optional. Retain the child's scan-tracking rows after the variant itself is gone.
```bash cURL theme={null}
curl -X DELETE "https://api.scanova.io/qr/Qz9y8x7w6v5u4t3s/children/Qc3d4e5f6g7h8i9j0/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"keep_tracking": false
}'
```
This delete is **immediate and permanent**. Unlike [Delete a QR Code](/api-reference/management-api/qr/delete), it does **not** go through Trash and cannot be restored — a child variant has no standalone detail page to restore to. Confirm with the user before calling.
Returns `204 No Content`. Pass `keep_tracking: true` to keep the historical scan data for reporting after the variant is removed.
## Related
* [List and create child QR Codes](/api-reference/management-api/qr/child-list-create) — the collection this removes from.
* [Delete a QR Code](/api-reference/management-api/qr/delete) — the soft-delete flow that applies to ordinary QR Codes.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Link a child QR Code
Source: https://docs.scanova.io/api-reference/management-api/qr/child-link
api-reference/openapi/management-api.json POST /qr/{parent_qrid}/children/link/
POST /qr/{parent_qrid}/children/link/
Adopts an existing standalone QR Code as a variant of a GS1 parent, instead of creating a fresh child.
```
POST https://api.scanova.io/qr/{parent_qrid}/children/link/
Authorization: YOUR_API_KEY
```
The `qrid` of the GS1 parent QR Code.
The `qrid` of an existing standalone QR Code owned by this account.
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/qr/Qz9y8x7w6v5u4t3s/children/link/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"qrid": "Qb2c3d4e5f6g7h8i9"
}'
```
A linked child reports `source: "linked"` in the child listing, distinguishing it from one created in place.
## Related
* [List and create child QR Codes](/api-reference/management-api/qr/child-list-create) — the collection this adds to.
* [Delete a child QR Code](/api-reference/management-api/qr/child-delete) — detach and permanently delete a variant.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# List and create child QR Codes
Source: https://docs.scanova.io/api-reference/management-api/qr/child-list-create
api-reference/openapi/management-api.json GET /qr/{parent_qrid}/children/
GET, POST /qr/{parent_qrid}/children/
Lists and creates GS1 **child** QR Codes — per-lot or per-serial variants of a GS1 parent.
```
GET https://api.scanova.io/qr/{parent_qrid}/children/
POST https://api.scanova.io/qr/{parent_qrid}/children/
Authorization: YOUR_API_KEY
```
The `qrid` of the GS1 parent QR Code.
A child is a **lightweight variant**, not a full standalone QR Code: it shares the parent's identity and GTIN and only carries its own qualifier values (a different lot or serial). It has no detail page of its own, which is why children are managed entirely through these sub-resource endpoints.
Creating consumes the `CHILD_QR_CODES` quota — an **independent bucket** from `TOTAL_QR_CODES` and `DYNAMIC_QR_CODES` — and requires `QR_CODE_CAN_ADD`.
### Create (POST)
Non-empty list of `{"id": ..., "value": ...}` pairs — the GS1 application identifiers that distinguish this variant, e.g. `{"id": "10", "value": "LOT-2026-04"}` for a batch/lot.
Optional additional GS1 data attributes.
Optional human-readable label for the variant.
Optional display name. Derived from the qualifiers if omitted.
The create body uses **camelCase** keys (`dataAttributes`, `variantLabel`) while the listing response uses snake\_case (`data_attributes`, `variant_label`). This asymmetry is intentional in the current contract — don't normalize one into the other.
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/qr/Qz9y8x7w6v5u4t3s/children/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"qualifiers": [
{
"id": "10",
"value": "LOT-2026-04"
}
],
"dataAttributes": [],
"variantLabel": "500ml bottle"
}'
```
`source` in the response says how the child came to exist: `created` for one made here, `linked` for an existing QR Code adopted through [Link a child QR Code](/api-reference/management-api/qr/child-link). The response is not paginated.
## Related
* [Bulk-create child QR Codes](/api-reference/management-api/qr/child-bulk-create) — create many variants in one call.
* [Link a child QR Code](/api-reference/management-api/qr/child-link) — adopt an existing standalone QR Code as a variant.
* [Delete a child QR Code](/api-reference/management-api/qr/child-delete) — permanently remove one variant.
* [Check GS1 references](/api-reference/management-api/qr/gs1-references) — why a parent with children cannot be deleted.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Create a QR Code
Source: https://docs.scanova.io/api-reference/management-api/qr/create
api-reference/openapi/management-api.json POST /qr/
POST /qr/
Lists or creates QR Codes on `api.scanova.io`, authenticated with your raw API key — see the [overview](/api-reference/management-api/overview) if you haven't set that up yet.
```
GET https://api.scanova.io/qr/
POST https://api.scanova.io/qr/
Authorization: YOUR_API_KEY
```
`POST /qr/` creates a new QR Code using the same serializer and field contract as the regular dashboard's create-QR endpoint. Rather than re-deriving the full field reference here, see [Create QR Code](/v1/api-reference/endpoint/qr_manager/create) for the complete required/optional field table (`category`, `info`, `name`, `qr_type`, plus advanced fields like `pattern_info`, `custom_domain`, `expire_on`, `lead_list`, `password`, and geo-fencing options) — every field documented there applies identically here.
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/qr/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"category": 1,
"info": "{\"type\":\"url\",\"data\":{\"url\":\"https://example.com\"}}",
"qr_type": "dy",
"name": "Product launch flyer"
}'
```
A successful create returns `201 Created` with the newly created QR Code, serialized with the **full** detail shape (the same one returned by [`GET /qr/{qrid}/`](/api-reference/management-api/qr/retrieve)) — richer than the slim list shape shown above, and including fields like `info` and `pattern_info` that the list endpoint omits for performance.
Creating a QR Code is subject to the same plan-based QR-count limits (dynamic/static) as creating one from the dashboard, in addition to the `MANAGEMENT_API` (or `INTEGRATION_ZAPIER`/`INTEGRATION_MCP`) quota gate described in the [overview](/api-reference/management-api/overview#plan-quota).
## Related
* [List QR Codes](/api-reference/management-api/qr/list) — the other operation on this same endpoint.
* [Retrieve](/api-reference/management-api/qr/retrieve) — operate on a single QR Code returned by this list.
* [Update](/api-reference/management-api/qr/update) — operate on a single QR Code returned by this list.
* [Delete](/api-reference/management-api/qr/delete) — operate on a single QR Code returned by this list.
* [Download a QR Code image](/api-reference/management-api/qr/download-image) — render one of these QR Codes as a PNG/SVG/PDF/etc.
* [List trashed QR Codes](/api-reference/management-api/qr/trash-list) — where a QR Code goes after being deleted from this list.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Delete
Source: https://docs.scanova.io/api-reference/management-api/qr/delete
api-reference/openapi/management-api.json DELETE /qr/{qrid}/
DELETE /qr/{qrid}/
Retrieves, updates, or deletes a single QR Code on `api.scanova.io`, authenticated with your raw API key.
```
GET https://api.scanova.io/qr/{qrid}/
PUT https://api.scanova.io/qr/{qrid}/
PATCH https://api.scanova.io/qr/{qrid}/
DELETE https://api.scanova.io/qr/{qrid}/
Authorization: YOUR_API_KEY
```
The QR Code's `qrid` (e.g. `QR7K9M2XN`) — the human-readable identifier returned by [`GET /qr/`](/api-reference/management-api/qr/list), not the numeric `id`.
The legacy `/qrcode/{qrid}/` path is also still reachable, kept for backward compatibility — both paths resolve to the identical view and behavior. This reference documents the canonical `/qr/{qrid}/` path (under the same prefix as the list/create endpoint).
`DELETE` soft-deletes the QR Code — it moves to the trash (see [QR Code trash](/api-reference/management-api/qr/trash-list)) rather than being permanently removed, and returns `204 No Content`.
```bash cURL theme={null}
curl -X DELETE "https://api.scanova.io/qr/QR7K9M2XN/" \
-H "Authorization: YOUR_API_KEY"
```
## Scope and errors
Every method here only operates on QR Codes owned by the account the key belongs to — requesting a `qrid` you don't own (or that doesn't exist) returns `404`, not `403`, so you can't use this endpoint to probe for the existence of other accounts' QR Codes.
## Related
* [Retrieve](/api-reference/management-api/qr/retrieve) — the other operation on this same endpoint.
* [Update](/api-reference/management-api/qr/update) — the other operation on this same endpoint.
* [List QR Codes](/api-reference/management-api/qr/list) — find a `qrid` to delete here.
* [Download a QR Code image](/api-reference/management-api/qr/download-image) — render this QR Code as an image file.
* [List trashed QR Codes](/api-reference/management-api/qr/trash-list) — where a QR Code lands after `DELETE` here.
# Download a GS1 barcode
Source: https://docs.scanova.io/api-reference/management-api/qr/download-gs1-barcode
api-reference/openapi/management-api.json GET /qr/{qrid}/barcode/
GET /qr/{qrid}/barcode/
Renders the linear (EAN-13 / GS1-128) barcode for a GS1 QR Code — the companion barcode printed alongside the 2D code on retail packaging.
```
GET https://api.scanova.io/qr/{qrid}/barcode/
Authorization: YOUR_API_KEY
```
The `qrid` of a QR Code in the `gs1` category.
Output format: `png` or `svg`.
```bash cURL theme={null}
curl -G "https://api.scanova.io/qr/Qz9y8x7w6v5u4t3s/barcode/" \
-H "Authorization: YOUR_API_KEY" \
-d file=svg \
-o "gtin-01234567890128.svg"
```
Only QR Codes whose category slug is `gs1` are accepted — anything else returns `400`. A `400` can also mean the stored GS1 data cannot be encoded as a linear barcode (for example an incomplete GTIN), which is a data problem rather than a request problem.
The response is a binary file attachment. Rendering round-trips to the generator service.
## Related
* [Download a QR Code image](/api-reference/management-api/qr/download-image) — the 2D QR Code image for the same QR Code.
* [List and create GS1 batch recalls](/api-reference/management-api/qr/gs1-recall-list-create) — recall notices for a GTIN and lot.
* [List and create child QR Codes](/api-reference/management-api/qr/child-list-create) — per-lot and per-serial variants of a GS1 QR Code.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Download a QR Code image
Source: https://docs.scanova.io/api-reference/management-api/qr/download-image
api-reference/openapi/management-api.json GET /qr/{qrid}/download/
GET /qr/{qrid}/download/
Renders a stored QR Code as an image file.
```
GET https://api.scanova.io/qr/{qrid}/download/
Authorization: YOUR_API_KEY
```
The QR Code's `qrid`.
### Query parameters
Output format: `png`, `jpg`, `jpeg`, `svg`, `pdf`, `eps`, or `ps`.
Output size in pixels, from `10` to `2400`.
Return a print-ready PDF. This path is generated locally rather than through the generator service, and ignores `file`.
Pre-computed encode payload override. Dynamic QR Codes never need this — it exists for static categories whose client-side encoding the backend does not independently reproduce.
```bash cURL theme={null}
curl -G "https://api.scanova.io/qr/Qa1b2c3d4e5f6g7h8/download/" \
-H "Authorization: YOUR_API_KEY" \
-d file=svg \
-d size=600 \
-o "summer-sale-landing.svg"
```
The response is a **binary file** (`Content-Disposition: attachment`), not JSON.
Except for `for_print`, rendering round-trips to an external QR Code generator service. A `503` therefore means that upstream call failed, not that your request was wrong — retry, and contact support if it persists.
## Related
* [Download a rendered QR Code image](/api-reference/management-api/qr/download-rendered) — send your own rendered data instead of rendering server-side.
* [Download a GS1 barcode](/api-reference/management-api/qr/download-gs1-barcode) — the linear barcode form, for GS1 QR Codes.
* [Export QR Codes](/api-reference/management-api/qr/export) — an asynchronous export for large selections.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Download many rendered QR Code images
Source: https://docs.scanova.io/api-reference/management-api/qr/download-multi
api-reference/openapi/management-api.json POST /qr/download/multi/
POST /qr/download/multi/
Batch form of [Download a rendered QR Code image](/api-reference/management-api/qr/download-rendered): send a list of download requests, get back a single ZIP.
```
POST https://api.scanova.io/qr/download/multi/
Authorization: YOUR_API_KEY
```
Same gates as the single endpoint — `QR_CODE_CAN_DOWNLOAD`, plus `RASTER_EXPORTING` or `VECTOR_EXPORTING` depending on the formats requested.
A non-empty list of download requests. Each entry takes the same fields as the single-download body: `name`, `data`, `qrid`, `size`, `for_print`.
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/qr/download/multi/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": [
{
"name": "Summer Sale Landing",
"data": "data:image/png;base64,iVBORw0KGgo...",
"size": 600
},
{
"name": "Product Launch Poster",
"data": "data:image/png;base64,iVBORw0KGgo...",
"size": 600
}
]
}'
```
The response is a **binary ZIP archive**, not JSON. Nothing is persisted server-side — for large selections that you'd rather receive by email, use [Export QR Codes](/api-reference/management-api/qr/export) instead.
## Related
* [Download a rendered QR Code image](/api-reference/management-api/qr/download-rendered) — the single-file form of this endpoint.
* [Export QR Codes](/api-reference/management-api/qr/export) — an asynchronous export for large selections.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Download a rendered QR Code image
Source: https://docs.scanova.io/api-reference/management-api/qr/download-rendered
api-reference/openapi/management-api.json POST /qr/download/
POST /qr/download/
Turns QR data your client has already rendered into a downloadable file.
```
POST https://api.scanova.io/qr/download/
Authorization: YOUR_API_KEY
```
Requires the `QR_CODE_CAN_DOWNLOAD` role permission, plus `RASTER_EXPORTING` quota for `png`/`jpg`/`jpeg` or `VECTOR_EXPORTING` quota for `pdf`/`eps`/`svg`/`ps`.
Base filename for the download. Falls back to the QR Code's identifier.
The rendered QR Code — a base64 raster payload or SVG markup.
Optional source QR Code. **Required** when `for_print` is `true`.
Output size in pixels.
Produce a print-ready output. Requires `qrid`.
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/qr/download/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Summer Sale Landing",
"data": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0i...",
"size": 600
}'
```
The success response is a **binary file**, not JSON — it comes back with `Content-Disposition: attachment` and the format implied by `data`. Write it straight to disk (`-o file.svg` with cURL); don't try to parse it. Nothing is stored server-side, so there is no URL to fetch it again later.
## Related
* [Download many rendered QR Code images](/api-reference/management-api/qr/download-multi) — the same operation for a batch, returned as a ZIP.
* [Download a QR Code image](/api-reference/management-api/qr/download-image) — let the server render the QR Code from its stored data instead.
* [Export QR Codes](/api-reference/management-api/qr/export) — an asynchronous, emailed export for large selections.
* [Check scannability](/api-reference/management-api/qr/scan-check) — verify a rendered design before downloading it.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Export QR Codes
Source: https://docs.scanova.io/api-reference/management-api/qr/export
api-reference/openapi/management-api.json POST /qr/export/
POST /qr/export/
Queues an export of QR Code metadata. Requires the `QR_CODE_CAN_EXPORT` role permission (`PAGE_CAN_EXPORT` for landing pages).
```
POST https://api.scanova.io/qr/export/
Authorization: YOUR_API_KEY
```
Write-only — the `qrid` values to export.
Write-only — serialized query parameters selecting what to export, as an alternative to an explicit `data` list.
Read-only — the export record's id.
Read-only — export progress.
Read-only — the finished file's URL. `null` until the export completes.
Read-only — completion timestamp. `null` until the export completes.
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/qr/export/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": [
"Qa1b2c3d4e5f6g7h8"
],
"filters": "type=dy&status=active"
}'
```
This endpoint is **asynchronous**. The `201` response is a *pending* record — `result_file` is `null` at that point. The export is generated by a background worker; wait for the completion email or poll the record until `status` reports completion and `result_file` is populated. Don't treat the `201` as "the export is ready".
## Related
* [Download many rendered QR Code images](/api-reference/management-api/qr/download-multi) — a synchronous ZIP of images rather than a queued export.
* [List QR Codes](/api-reference/management-api/qr/list) — the filter syntax `filters` serializes.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Remove a favourite category
Source: https://docs.scanova.io/api-reference/management-api/qr/favorite-category-delete
api-reference/openapi/management-api.json DELETE /qr/favorite-category/{category_id}/
DELETE /qr/favorite-category/{category_id}/
Removes one category from the calling account's favourites.
```
DELETE https://api.scanova.io/qr/favorite-category/{category_id}/
Authorization: YOUR_API_KEY
```
Primary key of the **category**, not of the favourite row returned by the list endpoint.
Takes no request body and returns `204 No Content`.
```bash cURL theme={null}
curl -X DELETE "https://api.scanova.io/qr/favorite-category/3/" \
-H "Authorization: YOUR_API_KEY"
```
## Related
* [List and add favourite categories](/api-reference/management-api/qr/favorite-category-list-create) — the list/create side of this resource.
* [List QR categories](/api-reference/management-api/qr/category-list) — the `favourite` view that reads these rows.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# List and add favourite categories
Source: https://docs.scanova.io/api-reference/management-api/qr/favorite-category-list-create
api-reference/openapi/management-api.json GET /qr/favorite/
GET, POST /qr/favorite/
Lists the calling account's favourited QR categories, and adds new ones. One `ListCreateAPIView`, so both operations live on the same path.
```
GET https://api.scanova.io/qr/favorite/
POST https://api.scanova.io/qr/favorite/
Authorization: YOUR_API_KEY
```
Favourites are always scoped to the account the key belongs to, and the response is not paginated.
Primary key of the category to favourite, from [List QR categories](/api-reference/management-api/qr/category-list).
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/qr/favorite/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"qr_category": 3
}'
```
```json 200 OK theme={null}
[
{ "id": 17, "qr_category": 3 }
]
```
Favourites are what `?view_type=favourite` reads on [List QR categories](/api-reference/management-api/qr/category-list) — add one here and it shows up in that section.
## Related
* [Remove a favourite category](/api-reference/management-api/qr/favorite-category-delete) — the delete side of this resource.
* [List QR categories](/api-reference/management-api/qr/category-list) — the categories you can favourite, and the `favourite` view that reads them.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# List and create GS1 batch recalls
Source: https://docs.scanova.io/api-reference/management-api/qr/gs1-recall-list-create
api-reference/openapi/management-api.json GET /qr/gs1-recalls/
GET, POST /qr/gs1-recalls/
Publishes and lists GS1 batch/lot recall notices for the account.
```
GET https://api.scanova.io/qr/gs1-recalls/
POST https://api.scanova.io/qr/gs1-recalls/
Authorization: YOUR_API_KEY
```
Creating requires the `GS1_RECALL_CREATE` role permission.
The recalled product's GTIN, max 14 characters.
The recalled batch or lot value, max 20 characters.
The notice shown to anyone scanning an affected code.
Whether the recall is currently in force.
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/qr/gs1-recalls/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"gtin": "01234567890128",
"batch_lot_value": "LOT-2026-04",
"message": "This batch has been recalled due to a labeling error. Contact support."
}'
```
Recalls are **account-wide**, not scoped to one QR Code or GS1 family. An active recall matches **any** GS1 scan whose batch/lot value matches, regardless of which specific QR Code resolved it. A too-broad `batch_lot_value` will surface the recall notice on products you didn't intend.
There is no uniqueness constraint: the same GTIN and lot can carry more than one recall over time. To end a recall, flip `is_active` to `false` with [Update a GS1 batch recall](/api-reference/management-api/qr/gs1-recall-update) — don't delete the row, so the history is preserved.
## Related
* [Update a GS1 batch recall](/api-reference/management-api/qr/gs1-recall-update) — resolve a recall by deactivating it.
* [List and create child QR Codes](/api-reference/management-api/qr/child-list-create) — the per-lot variants a recall typically targets.
* [Download a GS1 barcode](/api-reference/management-api/qr/download-gs1-barcode) — the linear barcode for a GS1 QR Code.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Update a GS1 batch recall
Source: https://docs.scanova.io/api-reference/management-api/qr/gs1-recall-update
api-reference/openapi/management-api.json GET /qr/gs1-recalls/{pk}/
GET, PATCH /qr/gs1-recalls/{pk}/
Reads and updates one GS1 batch recall notice.
```
GET https://api.scanova.io/qr/gs1-recalls/{pk}/
PATCH https://api.scanova.io/qr/gs1-recalls/{pk}/
Authorization: YOUR_API_KEY
```
The recall record's id.
Set to `false` to end the recall.
```bash cURL theme={null}
curl -X PATCH "https://api.scanova.io/qr/gs1-recalls/12/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"is_active": false
}'
```
Deactivating is the intended way to resolve a recall — the row stays in place so the recall history is preserved and auditable. `gtin`, `batch_lot_value`, and `message` remain editable for corrections.
## Related
* [List and create GS1 batch recalls](/api-reference/management-api/qr/gs1-recall-list-create) — the collection this record belongs to.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Check GS1 references
Source: https://docs.scanova.io/api-reference/management-api/qr/gs1-references
api-reference/openapi/management-api.json GET /qr/{qrid}/gs1-references/
GET /qr/{qrid}/gs1-references/
Reports whether a landing QR Code is used as the **resolution target** of one or more GS1 QR Codes.
```
GET https://api.scanova.io/qr/{qrid}/gs1-references/
Authorization: YOUR_API_KEY
```
The `qrid` of the non-GS1 QR Code to check.
```bash cURL theme={null}
curl "https://api.scanova.io/qr/Qa1b2c3d4e5f6g7h8/gs1-references/" \
-H "Authorization: YOUR_API_KEY"
```
```json 200 OK theme={null}
{
"is_gs1_referenced": true,
"gs1_codes": [
{ "name": "Cocoa 500g — GTIN 01234567890128", "qrid": "Qz9y8x7w6v5u4t3s" }
]
}
```
Call this before deleting. Every delete path in this module — [single](/api-reference/management-api/qr/delete), [bulk](/api-reference/management-api/qr/multi-delete), and [permanent](/api-reference/management-api/qr/trash-delete) — refuses with `400` when a QR Code is GS1-referenced, and never cascades the deletion. This endpoint tells you exactly which GS1 QR Codes to repoint first.
## Related
* [Delete a QR Code](/api-reference/management-api/qr/delete) — the delete this check protects.
* [Permanently delete trashed QR Codes](/api-reference/management-api/qr/trash-delete) — the same guard applies here.
* [List and create child QR Codes](/api-reference/management-api/qr/child-list-create) — the other GS1 relationship that blocks deletion.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Check hash availability
Source: https://docs.scanova.io/api-reference/management-api/qr/hash-check
api-reference/openapi/management-api.json POST /qr/hash/check/
POST /qr/hash/check/
Checks whether a custom short-URL slug is free before you commit to it.
```
POST https://api.scanova.io/qr/hash/check/
Authorization: YOUR_API_KEY
```
Requires the `CUSTOM_URL_HASH` quota.
The candidate slug: 3–50 characters, `a`–`z`, `A`–`Z`, `0`–`9`, `_`, and `-`.
Custom-domain primary key to check against. Omit for the default short domain.
Read-only — the availability verdict in the response.
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/qr/hash/check/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url_hash": "summer-sale",
"domain": 42
}'
```
```json 400 Bad Request theme={null}
{
"url_hash": ["Unavailable. Try again"]
}
```
Availability is **domain-scoped** — the same slug can be free on one custom domain and taken on another, so always send the `domain` you intend to publish on. A successful check is not a reservation: the slug is only yours once a QR Code claims it.
## Related
* [Generate an available hash](/api-reference/management-api/qr/hash-generate) — let the server pick a free slug instead.
* [Change a QR Code's short-URL slug](/api-reference/management-api/qr/url-hash-update) — apply a checked slug to an existing QR Code.
* [Create a QR Code](/api-reference/management-api/qr/create) — pass `url_hash` at creation time.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Generate an available hash
Source: https://docs.scanova.io/api-reference/management-api/qr/hash-generate
api-reference/openapi/management-api.json POST /qr/hash/generate/
POST /qr/hash/generate/
Asks the server for a random slug that is currently free, instead of guessing one and checking it.
```
POST https://api.scanova.io/qr/hash/generate/
Authorization: YOUR_API_KEY
```
Requires the `CUSTOM_URL_HASH` quota — the same gate as [Check hash availability](/api-reference/management-api/qr/hash-check).
Custom-domain primary key to generate against. Omit for the default short domain.
Read-only — the generated slug.
Read-only — always `true` for a freshly generated slug.
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/qr/hash/generate/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"domain": 42
}'
```
Generation does not reserve the slug. Claim it promptly by passing it as `url_hash` on [Create a QR Code](/api-reference/management-api/qr/create) or [Change a QR Code's short-URL slug](/api-reference/management-api/qr/url-hash-update).
## Related
* [Check hash availability](/api-reference/management-api/qr/hash-check) — verify a slug you chose yourself.
* [Change a QR Code's short-URL slug](/api-reference/management-api/qr/url-hash-update) — apply a generated slug to an existing QR Code.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# QR Code health center
Source: https://docs.scanova.io/api-reference/management-api/qr/health
api-reference/openapi/management-api.json GET /qr/health/
GET /qr/health/
Returns the aggregated dataset that backs the dashboard's QR Code health center — QR Code, form, and custom-domain signals for the whole account in one call.
```
GET https://api.scanova.io/qr/health/
Authorization: YOUR_API_KEY
```
```bash cURL theme={null}
curl "https://api.scanova.io/qr/health/" \
-H "Authorization: YOUR_API_KEY"
```
This endpoint is a **dashboard aggregate**, not a stable per-field contract: it groups signals by module and gains keys as the health center grows. Read the groups you care about and ignore the rest rather than validating the payload strictly.
For a limited or shared user, modules the role cannot access are listed in `restricted_modules` and their sections are omitted — the request still succeeds with `200` rather than failing with `403`.
## Related
* [List QR Codes](/api-reference/management-api/qr/list) — drill into the QR Codes behind these counts.
* [List trashed QR Codes](/api-reference/management-api/qr/trash-list) — the soft-deleted QR Codes excluded from these counts.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# List industries
Source: https://docs.scanova.io/api-reference/management-api/qr/industries-list
api-reference/openapi/management-api.json GET /qr/industries/
GET /qr/industries/
Reference list of industries used to classify accounts and drive category recommendations.
```
GET https://api.scanova.io/qr/industries/
Authorization: YOUR_API_KEY
```
This is a **public** endpoint (`AllowAny`) — it needs no API key, and the response is not paginated. Cache it rather than fetching it per request.
Restrict the list to the industries offered for a given onboarding profile type.
```bash cURL theme={null}
curl "https://api.scanova.io/qr/industries/" \
-H "Authorization: YOUR_API_KEY"
```
## Related
* [List QR categories](/api-reference/management-api/qr/category-list) — the recommendation surface these industries feed.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Link QR Codes to your account
Source: https://docs.scanova.io/api-reference/management-api/qr/link
api-reference/openapi/management-api.json POST /qr/link/
POST /qr/link/
Claims QR Codes that were created before login and assigns them to the authenticated account.
```
POST https://api.scanova.io/qr/link/
Authorization: YOUR_API_KEY
```
A non-empty list of `qrid` values to claim.
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/qr/link/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"qr": [
"Qa1b2c3d4e5f6g7h8"
]
}'
```
```json 200 OK theme={null}
"success"
```
This exists for the sign-up flow, where a visitor builds a QR Code anonymously on the public API and then registers. Since QR Code creation on the Scanova API host always requires an API key, QR Codes created **through this API** are already owned and never need linking.
## Related
* [Create a QR Code](/api-reference/management-api/qr/create) — creation on this host is always owned, so it needs no linking.
* [List QR Codes](/api-reference/management-api/qr/list) — confirm the claimed QR Codes now appear in the account.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# List QR Codes
Source: https://docs.scanova.io/api-reference/management-api/qr/list
api-reference/openapi/management-api.json GET /qr/
GET /qr/
Lists or creates QR Codes on `api.scanova.io`, authenticated with your raw API key — see the [overview](/api-reference/management-api/overview) if you haven't set that up yet.
```
GET https://api.scanova.io/qr/
POST https://api.scanova.io/qr/
Authorization: YOUR_API_KEY
```
`GET /qr/` returns a paginated, filterable list of the QR Codes owned by the account the key belongs to.
### Query parameters
Free-text search against QR name, `qrid`, dynamic URL slug, and folder name.
Comma-separated category slugs, e.g. `url,event`. Matches any of the listed categories.
Comma-separated tag names. Matches any of the listed tags.
Comma-separated QR types: `dy` (Dynamic), `st` (Static).
Folder ID — returns only QR Codes in that folder.
ISO date. Returns QR Codes created on or after this date.
ISO date. Returns QR Codes created on or before this date.
One or more of `name`, `created`, `qr_type`, `category__name`, comma-separated. Prefix a field with `-` to sort descending, e.g. `ordering=-created`.
Page number.
Results per page, up to a maximum of `100`.
```json 200 OK theme={null}
{
"count": 214,
"next": 2,
"previous": null,
"results": [
{
"id": 88231,
"qrid": "QR7K9M2XN",
"name": "Product launch flyer",
"qr_type": "dy",
"qr_type_display": "Dynamic",
"category": {
"id": 1,
"name": "Website URL",
"slug": "url",
"description": "Link a QR code straight to any website.",
"preview_image": "https://cdn.scanova.io/categories/url.png",
"allowed_qr_types": ["dy", "st"],
"allowed_qr_types_display": ["Dynamic", "Static"],
"is_active": true
},
"dynamic_url_object": {
"url_hash": "a1b2c3",
"complete_url": "https://qr.link/a1b2c3",
"visit_count": 1042
},
"thumbnail": "https://cdn.scanova.io/thumbnails/QR7K9M2XN.png",
"is_active": true,
"folder": null,
"created": "2026-06-02T14:03:11Z",
"modified": "2026-08-10T08:41:02Z",
"tags_list": ["campaign-2026", "print"],
"is_password_protected": false,
"is_age_restricted": false,
"created_by": { "full_name": "Jordan Lee" },
"published_at": "2026-06-02T14:05:00Z",
"is_currently_draft": false
}
]
}
```
`next` and `previous` are **page numbers** (or `null`), not full URLs — this API's pagination differs from plain DRF `PageNumberPagination` in that respect. Fetch the next page with `?page=2`, not by following a URL from the response.
`category` is the full category object — the fields shown above are a representative subset; the same object also carries `tags`, `is_new`, `has_landing_page`, `helpdesk_link`, `qr_name_pattern`, `created`, and `modified`. `dynamic_url_object` is only present for Dynamic (`dy`) QR Codes.
## Related
* [Create a QR Code](/api-reference/management-api/qr/create) — the other operation on this same endpoint.
* [Retrieve](/api-reference/management-api/qr/retrieve) — operate on a single QR Code returned by this list.
* [Update](/api-reference/management-api/qr/update) — operate on a single QR Code returned by this list.
* [Delete](/api-reference/management-api/qr/delete) — operate on a single QR Code returned by this list.
* [Download a QR Code image](/api-reference/management-api/qr/download-image) — render one of these QR Codes as a PNG/SVG/PDF/etc.
* [List trashed QR Codes](/api-reference/management-api/qr/trash-list) — where a QR Code goes after being deleted from this list.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Migrate a legacy theme
Source: https://docs.scanova.io/api-reference/management-api/qr/migrate-legacy-theme
api-reference/openapi/management-api.json PATCH /qr/{qrid}/migrate-theme/
PATCH /qr/{qrid}/migrate-theme/
Migrates a legacy QR Code onto the current theme schema.
```
PATCH https://api.scanova.io/qr/{qrid}/migrate-theme/
Authorization: YOUR_API_KEY
```
The QR Code's `qrid`.
Primary key of the theme to adopt. Nullable — omit or send `null` to migrate without picking a named theme.
Per-QR overrides layered on top of the theme. Schema-validated when non-empty.
```bash cURL theme={null}
curl -X PATCH "https://api.scanova.io/qr/Qa1b2c3d4e5f6g7h8/migrate-theme/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"theme_id": 7,
"theme_overrides": {
"color": {
"accent": "#0b3d91"
}
}
}'
```
This endpoint only accepts **legacy** QR Codes — those whose `version` is below `2`. A QR Code already on the current schema is rejected:
```json theme={null}
{
"non_field_errors": ["This QR code already uses the new theme schema."]
}
```
Check `version` on [Retrieve a QR Code](/api-reference/management-api/qr/retrieve) before calling, and treat this as a one-time migration rather than a way to change themes. For QR Codes already on the current schema, set the theme through the normal update path.
## Related
* [Retrieve a QR Code](/api-reference/management-api/qr/retrieve) — read `version` to see whether migration applies.
* [Update a QR Code](/api-reference/management-api/qr/update) — the normal path for theming a current-schema QR Code.
* [Update a QR Code's design](/api-reference/management-api/qr/update-pattern) — the design layer, separate from the theme.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# List QR Codes (minimal)
Source: https://docs.scanova.io/api-reference/management-api/qr/minimal-list
api-reference/openapi/management-api.json GET /qr/minimal/
GET /qr/minimal/
A light listing shape for QR Code pickers and dropdowns — the same filtering as [List QR Codes](/api-reference/management-api/qr/list) with far fewer fields per row.
```
GET https://api.scanova.io/qr/minimal/
Authorization: YOUR_API_KEY
```
Permission here is an **OR** of the analytics-view, QR-edit, and lead-generation roles rather than `QR_CODE_CAN_VIEW` alone. A user who can only read analytics or manage leads can still populate a QR Code selector with this endpoint.
### Query parameters
Set to `1` to return the with-lead shape instead, which adds `lead_id` and `form_id` to each row.
Pass `NON_LANDING_PAGE_QR_CODES` to exclude landing-page and GS1 categories — useful when the picker is for a feature those categories don't support.
Free-text search, same fields as the main list endpoint.
```bash cURL theme={null}
curl -G "https://api.scanova.io/qr/minimal/" \
-H "Authorization: YOUR_API_KEY" \
-d withlead=1
```
## Related
* [List QR Codes](/api-reference/management-api/qr/list) — the full list shape, with the complete filter set.
* [List QR categories](/api-reference/management-api/qr/category-list) — resolve the `category_slug` values returned here.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Delete many QR Codes
Source: https://docs.scanova.io/api-reference/management-api/qr/multi-delete
api-reference/openapi/management-api.json POST /qr/multi-delete/
POST /qr/multi-delete/
Soft-deletes many QR Codes in one call. They move to Trash, exactly as with a single [Delete a QR Code](/api-reference/management-api/qr/delete).
```
POST https://api.scanova.io/qr/multi-delete/
Authorization: YOUR_API_KEY
```
`qrid` values to delete, or the single-element sentinel `["all"]` to select everything the filter allows.
A JSON object reusing the [List QR Codes](/api-reference/management-api/qr/list) filter set, to select QR Codes by criteria rather than by id.
A JSON search specification, applied alongside `filter`.
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/qr/multi-delete/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"ids": [
"Qa1b2c3d4e5f6g7h8",
"Qb2c3d4e5f6g7h8i9"
]
}'
```
`filter` distinguishes an explicitly-null folder from an absent one:
* `"filter": {"folder": null}` means **uncategorized QR Codes only**.
* A `filter` with no `folder` key at all means **no folder restriction**.
These are different selections. Send the key explicitly only when you mean "not in any folder".
The same GS1 referential-integrity guard as the single delete runs here: if any selected QR Code is a GS1 resolution target or a GS1 parent with child variants, the call is refused with `400` rather than deleting the rest.
## Related
* [Delete a QR Code](/api-reference/management-api/qr/delete) — the single-QR equivalent.
* [List QR Codes](/api-reference/management-api/qr/list) — the filter set `filter` reuses.
* [Restore trashed QR Codes](/api-reference/management-api/qr/trash-restore) — undo a bulk delete.
* [Permanently delete trashed QR Codes](/api-reference/management-api/qr/trash-delete) — make it irreversible.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# QR Codes Management overview
Source: https://docs.scanova.io/api-reference/management-api/qr/overview
Shared quotas, permissions, and conventions for every /qr/ endpoint
This page collects what's common across every endpoint in this module — the QR Code CRUD surface of the Scanova API, mounted at `qr/` on `api.scanova.io`. Read [API overview](/api-reference/management-api/overview) first for the authentication scheme; this page only covers what's specific to QR Codes.
## Authentication is always required here
Unlike the public (OAuth2-authenticated) API, QR **creation** on the Scanova API host always requires an authenticated request — there is no anonymous-QR-then-link-later flow when you're calling with an API key. [Link QR Codes to your account](/api-reference/management-api/qr/link) exists for account-linking scenarios that originate elsewhere (e.g. the public API), not for calls made directly against this host.
## Quotas
Most mutating endpoints are gated by one or more of these plan-level quotas:
`TOTAL_QR_CODES`, `DYNAMIC_QR_CODES`, `STATIC_QR_CODES`, `GS1_QR_CODES`, `CHILD_QR_CODES`, `AI_QR_CODE`, `CUSTOM_URL_HASH`, `RASTER_EXPORTING`, `VECTOR_EXPORTING`, `CUSTOM_TAG`, `TAG_MANAGEMENT`, `SHARED_USERS`.
A quota failure returns `403` with either a flat "your plan doesn't include this" message, or (when the check is a remaining-count limit rather than plain existence) a `{"detail": "...", "limit": }` shape naming the exhausted limit.
## Role permissions
Independent of quota, most endpoints also check a role-based permission on the calling account:
`QR_CODE_CAN_VIEW` / `PAGE_CAN_VIEW`, `QR_CODE_CAN_ADD` / `PAGE_CAN_ADD` / `GS1_CAN_ADD`, `QR_CODE_CAN_EDIT` / `PAGE_CAN_EDIT`, `QR_CODE_CAN_DELETE` / `PAGE_CAN_DELETE`, `QR_CODE_CAN_EXPORT` / `PAGE_CAN_EXPORT`, `QR_CODE_CAN_DOWNLOAD`, `QR_CODE_TAG_CAN_ADD`, `GS1_RECALL_CREATE`.
The `PAGE_*` variant of a permission applies when the QR Code (or category) is a landing-page type; the plain variant applies otherwise.
## Soft delete and Trash
QR Codes are soft-deleted by default: [Delete a QR Code](/api-reference/management-api/qr/delete) moves a QR Code to Trash rather than removing it. Trash has its own [list](/api-reference/management-api/qr/trash-list), [restore](/api-reference/management-api/qr/trash-restore), and [permanent delete](/api-reference/management-api/qr/trash-delete) endpoints — restoring is itself quota-checked against your current plan limits, so a restore can fail with `403` if it would put the account over its limit.
## The GS1 referential-integrity guard
Every delete path (single, bulk, and permanent) runs the same check before removing anything: a QR Code that a GS1 code's resolution references, or a GS1 parent with linked child variants, cannot be deleted — the request fails with `400` rather than cascading. See [Check GS1 references](/api-reference/management-api/qr/gs1-references) to check before you delete, and [child QR Codes](/api-reference/management-api/qr/child-list-create) for the parent/variant relationship itself.
## Related
* [List QR Codes](/api-reference/management-api/qr/list)
* [Create a QR Code](/api-reference/management-api/qr/create)
* [API overview](/api-reference/management-api/overview) — authentication and general quota architecture.
# Issue a draft preview token
Source: https://docs.scanova.io/api-reference/management-api/qr/preview-token
api-reference/openapi/management-api.json POST /qr/{qrid}/preview-token/
POST /qr/{qrid}/preview-token/
Issues a short-lived token that lets the landing app render this QR Code's **unpublished draft** without a login — the "Preview" button in the editor.
```
POST https://api.scanova.io/qr/{qrid}/preview-token/
Authorization: YOUR_API_KEY
```
The QR Code's `qrid`.
Takes no request body.
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/qr/Qa1b2c3d4e5f6g7h8/preview-token/" \
-H "Authorization: YOUR_API_KEY"
```
```json 200 OK theme={null}
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"complete_url": "https://scnv.io/a1b2c3d4"
}
```
The token is a JWT with a short lifetime — about **3,600 seconds** by default. Mint it when the user opens a preview, not ahead of time, and never embed it in anything long-lived: anyone holding it can read the unpublished draft.
A QR Code that has no `url_hash` yet returns `400` — there is no URL to preview against until one is assigned.
## Related
* [Create a QR Code](/api-reference/management-api/qr/create) — the draft state machine this previews.
* [Update a QR Code](/api-reference/management-api/qr/update) — save a draft, then preview it, then publish.
* [Change a QR Code's short-URL slug](/api-reference/management-api/qr/url-hash-update) — assign the `url_hash` this endpoint requires.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Retrieve
Source: https://docs.scanova.io/api-reference/management-api/qr/retrieve
api-reference/openapi/management-api.json GET /qr/{qrid}/
GET /qr/{qrid}/
Retrieves, updates, or deletes a single QR Code on `api.scanova.io`, authenticated with your raw API key.
```
GET https://api.scanova.io/qr/{qrid}/
PUT https://api.scanova.io/qr/{qrid}/
PATCH https://api.scanova.io/qr/{qrid}/
DELETE https://api.scanova.io/qr/{qrid}/
Authorization: YOUR_API_KEY
```
The QR Code's `qrid` (e.g. `QR7K9M2XN`) — the human-readable identifier returned by [`GET /qr/`](/api-reference/management-api/qr/list), not the numeric `id`.
The legacy `/qrcode/{qrid}/` path is also still reachable, kept for backward compatibility — both paths resolve to the identical view and behavior. This reference documents the canonical `/qr/{qrid}/` path (under the same prefix as the list/create endpoint).
`GET` returns the full detail shape — every field the create/update serializer accepts, plus computed fields, but without AI-assistant fields (`ai_qr_code`, `ai_session_id`, `ai_chat_history`) and cross-database response-count fields that the list endpoint also omits.
```json 200 OK theme={null}
{
"id": 88231,
"qrid": "QR7K9M2XN",
"name": "Product launch flyer",
"qr_type": "dy",
"qr_type_display": "Dynamic",
"category": { "id": 1, "name": "Website URL", "slug": "url" },
"info": "{\"type\":\"url\",\"data\":{\"url\":\"https://example.com\"}}",
"pattern_info": null,
"dynamic_url_object": {
"url_hash": "a1b2c3",
"complete_url": "https://qr.link/a1b2c3",
"visit_count": 1042
},
"thumbnail": "https://cdn.scanova.io/thumbnails/QR7K9M2XN.png",
"is_active": true,
"folder": null,
"created": "2026-06-02T14:03:11Z",
"modified": "2026-08-10T08:41:02Z",
"tags_list": ["campaign-2026", "print"],
"is_password_protected": false,
"published_at": "2026-06-02T14:05:00Z",
"is_currently_draft": false
}
```
## Scope and errors
Every method here only operates on QR Codes owned by the account the key belongs to — requesting a `qrid` you don't own (or that doesn't exist) returns `404`, not `403`, so you can't use this endpoint to probe for the existence of other accounts' QR Codes.
## Related
* [Update](/api-reference/management-api/qr/update) — the other operation on this same endpoint.
* [Delete](/api-reference/management-api/qr/delete) — the other operation on this same endpoint.
* [List QR Codes](/api-reference/management-api/qr/list) — find a `qrid` to retrieve here.
* [Create a QR Code](/api-reference/management-api/qr/create) — creating a QR Code returns this same full detail shape.
* [Download a QR Code image](/api-reference/management-api/qr/download-image) — render this QR Code as an image file.
* [List trashed QR Codes](/api-reference/management-api/qr/trash-list) — where a QR Code lands after `DELETE` here.
# Check scannability
Source: https://docs.scanova.io/api-reference/management-api/qr/scan-check
api-reference/openapi/management-api.json POST /qr/scan/check/
POST /qr/scan/check/
Runs contrast and pattern analysis on a rendered QR Code image and reports whether it is likely to scan reliably. This is what the designer uses to warn about low-contrast color choices.
```
POST https://api.scanova.io/qr/scan/check/
Authorization: YOUR_API_KEY
```
Public endpoint (`AllowAny`) — it analyses the image you send and stores nothing.
The rendered QR Code image, base64-encoded.
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/qr/scan/check/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..."
}'
```
```json 200 OK theme={null}
{
"is_scannable": true
}
```
## Related
* [Update a QR Code's design](/api-reference/management-api/qr/update-pattern) — save the design once it checks out.
* [Download a rendered QR Code image](/api-reference/management-api/qr/download-rendered) — turn the same rendered data into a file.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Share a QR Code by email
Source: https://docs.scanova.io/api-reference/management-api/qr/share-email
api-reference/openapi/management-api.json POST /qr/{qrid}/share/email/
POST /qr/{qrid}/share/email/
Emails a business-card QR Code to one recipient, with wallet-pass links attached.
```
POST https://api.scanova.io/qr/{qrid}/share/email/
Authorization: YOUR_API_KEY
```
The `qrid` of a QR Code in the `businessCard` category.
The recipient's email address.
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/qr/Qa1b2c3d4e5f6g7h8/share/email/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "recipient@example.com"
}'
```
```json 200 OK theme={null}
{
"message": "QR code shared successfully."
}
```
This endpoint supports the **`businessCard` category only**. Every other category returns `400` — there is no generic "email me this QR Code" operation.
The server renders a PNG of the QR Code itself and attaches Apple Wallet and Google Wallet links to the message, so the recipient can save the card to their phone.
## Related
* [Download a QR Code image](/api-reference/management-api/qr/download-image) — get the image yourself instead of emailing it.
* [Retrieve a QR Code](/api-reference/management-api/qr/retrieve) — check the QR Code's category before calling.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Detach tags
Source: https://docs.scanova.io/api-reference/management-api/qr/tag-association-delete
api-reference/openapi/management-api.json POST /qr/tags/delete/
POST /qr/tags/delete/
Removes tag associations for the given tag names across every QR Code the account owns.
```
POST https://api.scanova.io/qr/tags/delete/
Authorization: YOUR_API_KEY
```
Tag names to detach.
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/qr/tags/delete/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"tags": [
"print"
]
}'
```
Returns `204 No Content`. If none of the given names exist for this account the call returns `400` instead — a partially-matching list succeeds for the names that do exist.
This is a `POST` that deletes: the tag names travel in the body rather than the path, because tags are identified by name and a name can contain characters awkward to place in a URL. Detaching also fires the tag-cleanup signal, which removes tags left attached to nothing.
## Related
* [Apply tags to many QR Codes](/api-reference/management-api/qr/tags-multi-create) — the inverse of this operation.
* [List tag details](/api-reference/management-api/qr/tag-details-list) — see which tags are still in use.
* [Update a QR Code's tags](/api-reference/management-api/qr/update-tags) — clear tags on one QR Code instead.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# List tag details
Source: https://docs.scanova.io/api-reference/management-api/qr/tag-details-list
api-reference/openapi/management-api.json GET /qr/tags/details/
GET /qr/tags/details/
The account's tags with a usage count each — the data behind a tag-management screen.
```
GET https://api.scanova.io/qr/tags/details/
Authorization: YOUR_API_KEY
```
Substring match on the tag name.
One of `name`, `created`, `qr_code_assigned`. Prefix with `-` to sort descending, e.g. `ordering=-qr_code_assigned` for the most-used tags first.
```bash cURL theme={null}
curl -G "https://api.scanova.io/qr/tags/details/" \
-H "Authorization: YOUR_API_KEY" \
-d ordering=-qr_code_assigned
```
`qr_code_assigned` is an annotated count of the QR Codes the tag is attached to, computed per request. Names are returned lowercased.
## Related
* [List tag names](/api-reference/management-api/qr/tags-list) — the same tags as bare strings, for autocomplete.
* [Apply tags to many QR Codes](/api-reference/management-api/qr/tags-multi-create) — raise these counts in bulk.
* [Detach tags](/api-reference/management-api/qr/tag-association-delete) — drop these counts back to zero for a tag.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# List tag names
Source: https://docs.scanova.io/api-reference/management-api/qr/tags-list
api-reference/openapi/management-api.json GET /qr/tags/
GET /qr/tags/
The account's tag names, as bare lowercase strings — the autocomplete source for tag inputs.
```
GET https://api.scanova.io/qr/tags/
Authorization: YOUR_API_KEY
```
Substring match on the tag name.
Results per page, up to `100`.
```bash cURL theme={null}
curl -G "https://api.scanova.io/qr/tags/" \
-H "Authorization: YOUR_API_KEY" \
-d search=camp
```
`results` is an array of **strings**, not objects. Tag names are always stored and returned lowercased, so `Campaign-2026` and `campaign-2026` are the same tag. For per-tag usage counts use [List tag details](/api-reference/management-api/qr/tag-details-list).
## Related
* [List tag details](/api-reference/management-api/qr/tag-details-list) — the same tags with assigned-QR counts.
* [Apply tags to many QR Codes](/api-reference/management-api/qr/tags-multi-create) — create and attach tags in bulk.
* [Update a QR Code's tags](/api-reference/management-api/qr/update-tags) — set the tags on one QR Code.
* [Detach tags](/api-reference/management-api/qr/tag-association-delete) — remove tag associations across the account.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Apply tags to many QR Codes
Source: https://docs.scanova.io/api-reference/management-api/qr/tags-multi-create
api-reference/openapi/management-api.json POST /qr/tags/multi/
POST /qr/tags/multi/
Creates any missing tags and attaches them to many QR Codes in one call.
```
POST https://api.scanova.io/qr/tags/multi/
Authorization: YOUR_API_KEY
```
Requires the `TAG_MANAGEMENT` quota and the `QR_CODE_TAG_CAN_ADD` role permission.
`qrid` values to tag.
Tag names to apply. Names are lowercased, and any that don't exist yet are created.
Apply the tags to **every** QR Code the account owns, ignoring `ids`.
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/qr/tags/multi/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"ids": [
"Qa1b2c3d4e5f6g7h8"
],
"tags": [
"campaign-2026",
"print"
]
}'
```
```json 201 Created theme={null}
"success"
```
`all: true` is account-wide and has no undo — it tags every QR Code, including ones outside any folder or filter you were looking at. Detaching afterwards means calling [Detach tags](/api-reference/management-api/qr/tag-association-delete) for those tag names.
## Related
* [List tag names](/api-reference/management-api/qr/tags-list) — the existing tag names.
* [Update a QR Code's tags](/api-reference/management-api/qr/update-tags) — set tags on a single QR Code instead.
* [Detach tags](/api-reference/management-api/qr/tag-association-delete) — the inverse of this operation.
* [List tag details](/api-reference/management-api/qr/tag-details-list) — confirm the resulting assigned counts.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Permanently delete trashed QR Codes
Source: https://docs.scanova.io/api-reference/management-api/qr/trash-delete
api-reference/openapi/management-api.json POST /qr/trash/delete/
POST /qr/trash/delete/
Permanently removes QR Codes that are already in Trash.
```
POST https://api.scanova.io/qr/trash/delete/
Authorization: YOUR_API_KEY
```
`qrid` values to delete permanently, or the single-element sentinel `["all"]`.
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/qr/trash/delete/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"ids": [
"Qe5f6g7h8i9j0k1l"
]
}'
```
**This is irreversible.** It is a hard delete: the rows leave the database and no restore is possible. It only ever operates on QR Codes that are **already soft-deleted** — it is not a shortcut past Trash for a live QR Code.
The GS1 referential-integrity guard applies here too: a QR Code that is a GS1 resolution target, or a GS1 parent with child variants, is refused with `400`. Related suspicious-QR records are hard-deleted alongside the QR Code, in the same transaction.
## Related
* [List trashed QR Codes](/api-reference/management-api/qr/trash-list) — find the ids to delete.
* [Restore trashed QR Codes](/api-reference/management-api/qr/trash-restore) — the reversible alternative.
* [Check GS1 references](/api-reference/management-api/qr/gs1-references) — find out whether this delete will be blocked.
* [Delete a QR Code](/api-reference/management-api/qr/delete) — the soft delete that must happen first.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# List trashed QR Codes
Source: https://docs.scanova.io/api-reference/management-api/qr/trash-list
api-reference/openapi/management-api.json GET /qr/trash/
GET /qr/trash/
Lists the account's soft-deleted QR Codes — everything that has been through [Delete a QR Code](/api-reference/management-api/qr/delete) but not yet permanently removed.
```
GET https://api.scanova.io/qr/trash/
Authorization: YOUR_API_KEY
```
Supports the same `search`, `category`, `tags`, `type`, and `ordering` parameters as [List QR Codes](/api-reference/management-api/qr/list#query-parameters).
```bash cURL theme={null}
curl -G "https://api.scanova.io/qr/trash/" \
-H "Authorization: YOUR_API_KEY" \
-d ordering=-created
```
The trash shape extends the minimal listing shape rather than the full one. Notably it adds `deleted` (the soft-deletion timestamp), the nested `category`, `tags_list`, and `scan_count`, and reduces `dynamic_url_object` to just `{"has_qr": ...}`.
Only the calling account's soft-deleted rows are ever returned.
## Related
* [Restore trashed QR Codes](/api-reference/management-api/qr/trash-restore) — bring these QR Codes back.
* [Permanently delete trashed QR Codes](/api-reference/management-api/qr/trash-delete) — remove them for good.
* [Delete a QR Code](/api-reference/management-api/qr/delete) — the operation that fills this list.
* [List QR Codes](/api-reference/management-api/qr/list) — the equivalent listing for active QR Codes.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Restore trashed QR Codes
Source: https://docs.scanova.io/api-reference/management-api/qr/trash-restore
api-reference/openapi/management-api.json POST /qr/trash/restore/
POST /qr/trash/restore/
Restores soft-deleted QR Codes out of Trash.
```
POST https://api.scanova.io/qr/trash/restore/
Authorization: YOUR_API_KEY
```
`qrid` values to restore, or the single-element sentinel `["all"]`.
Scopes an `["all"]` restore: `page` for landing pages, `qr` for ordinary QR Codes.
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/qr/trash/restore/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"ids": [
"Qe5f6g7h8i9j0k1l"
]
}'
```
Restoring is **quota-checked**. The static, dynamic, and total QR Code buckets are each evaluated against the count you would have *after* the restore, so a bulk restore can be refused with `403` even though the QR Codes already existed once. Restore in smaller batches, or free up quota first.
Success returns `200 OK` — not `201` and not `204`, unlike the other bulk operations in this module.
## Related
* [List trashed QR Codes](/api-reference/management-api/qr/trash-list) — find the ids to restore.
* [Permanently delete trashed QR Codes](/api-reference/management-api/qr/trash-delete) — the irreversible alternative.
* [Delete a QR Code](/api-reference/management-api/qr/delete) — what put these QR Codes in Trash.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Update
Source: https://docs.scanova.io/api-reference/management-api/qr/update
api-reference/openapi/management-api.json PUT /qr/{qrid}/
PUT /qr/{qrid}/
Retrieves, updates, or deletes a single QR Code on `api.scanova.io`, authenticated with your raw API key.
```
GET https://api.scanova.io/qr/{qrid}/
PUT https://api.scanova.io/qr/{qrid}/
PATCH https://api.scanova.io/qr/{qrid}/
DELETE https://api.scanova.io/qr/{qrid}/
Authorization: YOUR_API_KEY
```
The QR Code's `qrid` (e.g. `QR7K9M2XN`) — the human-readable identifier returned by [`GET /qr/`](/api-reference/management-api/qr/list), not the numeric `id`.
The legacy `/qrcode/{qrid}/` path is also still reachable, kept for backward compatibility — both paths resolve to the identical view and behavior. This reference documents the canonical `/qr/{qrid}/` path (under the same prefix as the list/create endpoint).
Both `PUT` and `PATCH` are supported (`PUT` for a full replace, `PATCH` for a partial update) and accept the same field set documented for [Create a QR Code](/api-reference/management-api/qr/create). A successful update returns `200 OK` with the updated detail representation shown above.
```bash cURL theme={null}
curl -X PATCH "https://api.scanova.io/qr/QR7K9M2XN/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Product launch flyer — v2" }'
```
## Scope and errors
Every method here only operates on QR Codes owned by the account the key belongs to — requesting a `qrid` you don't own (or that doesn't exist) returns `404`, not `403`, so you can't use this endpoint to probe for the existence of other accounts' QR Codes.
## Related
* [Retrieve](/api-reference/management-api/qr/retrieve) — the other operation on this same endpoint.
* [Delete](/api-reference/management-api/qr/delete) — the other operation on this same endpoint.
* [List QR Codes](/api-reference/management-api/qr/list) — find a `qrid` to operate on here, or the full field set this endpoint's update accepts.
* [Create a QR Code](/api-reference/management-api/qr/create) — find a `qrid` to operate on here, or the full field set this endpoint's update accepts.
* [Download a QR Code image](/api-reference/management-api/qr/download-image) — render this QR Code as an image file.
* [List trashed QR Codes](/api-reference/management-api/qr/trash-list) — where a QR Code lands after `DELETE` here.
# Update a QR Code's design
Source: https://docs.scanova.io/api-reference/management-api/qr/update-pattern
api-reference/openapi/management-api.json PATCH /qr/{qrid}/pattern/
PATCH /qr/{qrid}/pattern/
Replaces the design (eye shape, body pattern, colors, frame, logo) for one QR Code.
```
PATCH https://api.scanova.io/qr/{qrid}/pattern/
Authorization: YOUR_API_KEY
```
The QR Code's `qrid`.
Design data as a JSON-encoded string. Validated as JSON **and** against the design-pattern schema. Nullable — send `null` to drop back to the default design.
Optional base64 preview matching the new design. **If you omit it, the server generates one for you** from the new pattern.
```bash cURL theme={null}
curl -X PATCH "https://api.scanova.io/qr/Qa1b2c3d4e5f6g7h8/pattern/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"pattern_info": "{\"eye\":{\"shape\":\"rounded\"},\"body\":{\"shape\":\"dots\"},\"color\":{\"foreground\":\"#0b3d91\"}}"
}'
```
Omitting `thumbnail` is usually what you want — server-side generation guarantees the preview matches the stored design. Send your own only when the client has already rendered the exact image you want stored.
## Related
* [Check scannability](/api-reference/management-api/qr/scan-check) — verify a design's contrast before saving it.
* [Update a QR Code's thumbnail](/api-reference/management-api/qr/update-thumbnail) — change only the preview image.
* [Migrate a legacy theme](/api-reference/management-api/qr/migrate-legacy-theme) — for QR Codes still on the pre-v2 theme schema.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Update a QR Code's tags
Source: https://docs.scanova.io/api-reference/management-api/qr/update-tags
api-reference/openapi/management-api.json PATCH /qr/{qrid}/tags/
PATCH /qr/{qrid}/tags/
Sets the tags on one QR Code, without going through the full update serializer.
```
PATCH https://api.scanova.io/qr/{qrid}/tags/
Authorization: YOUR_API_KEY
```
The QR Code's `qrid`.
Write-only, comma-separated tag names. Names are lowercased, and any that don't exist yet are created.
Read-only — the resulting tags.
```bash cURL theme={null}
curl -X PATCH "https://api.scanova.io/qr/Qa1b2c3d4e5f6g7h8/tags/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"tags": "campaign-2026, print"
}'
```
Omitting `tags` and sending it empty mean different things:
* **`tags` absent from the body** — existing tags are preserved.
* **`"tags": ""`** — all tags are removed from this QR Code.
So a `PATCH` with an empty body is a no-op, not a clear.
## Related
* [Apply tags to many QR Codes](/api-reference/management-api/qr/tags-multi-create) — the bulk equivalent.
* [List tag names](/api-reference/management-api/qr/tags-list) — the existing tag names to choose from.
* [Detach tags](/api-reference/management-api/qr/tag-association-delete) — remove a tag across the whole account.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Update a QR Code's thumbnail
Source: https://docs.scanova.io/api-reference/management-api/qr/update-thumbnail
api-reference/openapi/management-api.json PATCH /qr/{qrid}/thumbnail/
PATCH /qr/{qrid}/thumbnail/
Replaces just the stored preview image for one QR Code, without going through the full update serializer.
```
PATCH https://api.scanova.io/qr/{qrid}/thumbnail/
Authorization: YOUR_API_KEY
```
The QR Code's `qrid`.
Base64-encoded image. Write-only and nullable — send `null` to clear the stored thumbnail.
```bash cURL theme={null}
curl -X PATCH "https://api.scanova.io/qr/Qa1b2c3d4e5f6g7h8/thumbnail/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"thumbnail": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..."
}'
```
The response is the **full** QR Code, not just the thumbnail — and `thumbnail` itself is write-only, so the value you sent is not echoed back.
## Related
* [Update a QR Code's design](/api-reference/management-api/qr/update-pattern) — changing the design can regenerate the thumbnail for you.
* [Update a QR Code](/api-reference/management-api/qr/update) — the general-purpose update endpoint.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Change a QR Code's short-URL slug
Source: https://docs.scanova.io/api-reference/management-api/qr/url-hash-update
api-reference/openapi/management-api.json PATCH /qr/{qrid}/url-hash/
PATCH /qr/{qrid}/url-hash/
Repoints a Dynamic QR Code's short URL to a new slug.
```
PATCH https://api.scanova.io/qr/{qrid}/url-hash/
Authorization: YOUR_API_KEY
```
The QR Code's `qrid`.
The new slug. Must match `^[a-zA-Z0-9\-_]{3,50}$` and be free on this QR Code's domain.
```bash cURL theme={null}
curl -X PATCH "https://api.scanova.io/qr/Qa1b2c3d4e5f6g7h8/url-hash/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url_hash": "summer-sale-2026"
}'
```
```json 200 OK theme={null}
{
"url_hash": "summer-sale-2026",
"complete_url": "https://scnv.io/summer-sale-2026",
"warning": "Any QR codes already printed with the previous short URL will stop working."
}
```
**Every QR Code already printed with the previous slug stops working.** The old short URL is not kept as a redirect. The API returns this as an explicit `warning` field in the success response — surface it to the user rather than swallowing it. Only change a slug for a QR Code that has not been distributed yet.
A successful change marks the QR Code as using a custom hash and invalidates the landing app's cache for both the old and the new slug, so the switch takes effect immediately. `400` means the slug is malformed; `409` means it is already taken on this domain.
## Related
* [Check hash availability](/api-reference/management-api/qr/hash-check) — confirm the slug is free before calling this.
* [Generate an available hash](/api-reference/management-api/qr/hash-generate) — have the server pick a free slug.
* [Retrieve a QR Code](/api-reference/management-api/qr/retrieve) — read the current `dynamic_url_object.url_hash`.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Validate QR Code content
Source: https://docs.scanova.io/api-reference/management-api/qr/validate-info
api-reference/openapi/management-api.json POST /qr/validate-info/
POST /qr/validate-info/
Dry-runs an `info` payload against a category's JSON schema, without creating anything.
```
POST https://api.scanova.io/qr/validate-info/
Authorization: YOUR_API_KEY
```
This is a **public** endpoint (`AllowAny`) and has **no side effects** — it neither creates nor modifies a QR Code. Use it to validate a form as the user types, then call [Create a QR Code](/api-reference/management-api/qr/create) once.
The candidate content, as a JSON-encoded string.
Primary key of the category whose schema to validate against.
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/qr/validate-info/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"info": "{\"type\":\"url\",\"data\":{\"url\":\"https://example.com/summer-sale\"}}",
"category": 3
}'
```
```json 200 OK theme={null}
"valid"
```
A failure returns `400` with per-field errors. Validation covers two things: that `info` matches the category's JSON schema, and that the components it uses are within the plan's component allowance.
## Related
* [Create a QR Code](/api-reference/management-api/qr/create) — the endpoint this pre-validates for.
* [List QR categories](/api-reference/management-api/qr/category-list) — pick the `category` to validate against.
* [Update a QR Code](/api-reference/management-api/qr/update) — the same schema applies to updates and to publishing a draft.
* [QR Codes Management overview](/api-reference/management-api/qr/overview) — the quotas, role permissions, and Trash model shared by every endpoint in this module.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Free plan for education
Source: https://docs.scanova.io/billing/education-plan
Teachers, students, and educational institutions can use Scanova free by crediting it on their site: who qualifies, and the two steps needed to apply.
Scanova gives teachers, students, and educational institutions a free plan in exchange for a credit link. You add the credit to your site, tell Scanova where it is, and the plan is activated.
## Who qualifies
Teachers, students, and educational institutions. If you're unsure whether your case counts, ask [support@scanova.io](mailto:support@scanova.io) before setting anything up.
## Step 1: credit Scanova on your site
Add a link to Scanova in your site's credits or resources section, with **Scanova** as the anchor text.
If you'd rather paste it in directly, use this:
```html theme={null}
```
Use the URL exactly as written. The `utm_` parameters on the end are how Scanova identifies the credit as coming from the education program, and a link without them may not be recognized.
## Step 2: tell Scanova where it is
Email [support@scanova.io](mailto:support@scanova.io) with two things:
* The email address your Scanova account is registered under
* The URL of the page where the credit appears
The credit needs to be live before you send this — support checks the page.
## Activation
Once the credit is verified, your free plan is activated. If you haven't heard back, reply on the same email thread rather than applying again.
## Why the credit is asked for
The credit is what funds the arrangement. It's how Scanova reaches new users through the education program, which is what makes offering the free plan to educators and students sustainable.
## Related
* [Is Scanova free?](/getting-started/is-scanova-free) — the two free routes open to everyone, without applying
* [When your trial or subscription ends](/billing/trial-and-expiry) — what happens to your QR Codes when a plan lapses
* [Subscription & billing](/billing/subscription-and-billing) — your current plan and renewal details
* [Plans and pricing](https://scanova.io/pricing) — what the paid plans cost and include
# Payment methods
Source: https://docs.scanova.io/billing/payment-methods
The cards and payment routes Scanova accepts, what to do when a card is declined, when wire transfer applies, and how card details are stored securely.
Most subscriptions are paid by card in the dashboard. If that doesn't work for you — a declined card, a finance team that needs an invoice — there are two fallbacks.
## Paying by card or PayPal
The default route is checkout inside your Scanova account, by debit card, credit card, or PayPal. All major cards are accepted, including Visa, Mastercard, American Express, and Discover.
Your account upgrades automatically once the payment succeeds.
## When the default checkout doesn't work
Cards are sometimes declined by the gateway rather than by your bank, and a second gateway often succeeds where the first failed. Support can send you a one-time payment link generated through PayPal or Stripe:
Email [support@scanova.io](mailto:support@scanova.io) saying you want to pay by custom link, and include the plan you want and the subscription period. If you've already agreed a specific price with the team, mention that too.
The link comes from a different gateway than the one that failed. Pay by debit card, credit card, or PayPal, then send support a confirmation.
Once the payment is verified, the team applies your plan and subscription period.
## Wire transfer
Wire transfer is available in two situations:
* You are a business or enterprise with an invoicing process — service agreement, vendor registration, purchase order, payment terms
* You are an Indian customer paying in INR, in which case the minimum subscription period is 12 months
Email [support@scanova.io](mailto:support@scanova.io) with your plan and period to get the bank details and an invoice. Send proof of payment once it clears, and the subscription is activated.
## Changing your card
Update a card from the **Payment Method** card on [Subscription & billing](/billing/subscription-and-billing).
## How your card details are stored
**Scanova does not store your card number.** No one at Scanova can see it.
Payment details are held by the gateway that processed them — 2Checkout or Stripe — and reused there for recurring payments if you left auto-renewal on. Both are certified to **PCI Service Provider Level 1**, the most stringent certification in the payments industry.
## Related
* [Subscription & billing](/billing/subscription-and-billing) — your current plan, renewal date, payment method, and billing history
* [Upgrading your plan](/billing/upgrading) — comparing plans and the checkout itself
* [Refund policy](/billing/refund-policy) — when a charge can be refunded
* [Plans and pricing](https://scanova.io/pricing) — what each plan costs and includes
# Refund policy
Source: https://docs.scanova.io/billing/refund-policy
When Scanova issues a refund and when it does not: the two exceptional cases, the claim windows for monthly and annual renewals, and how to request one.
Scanova's refund policy is narrow by design, and the reason is the free trial: you can try the product before paying, and you choose between monthly and annual billing to suit how long you need it.
## When a refund is not issued
Refunds are **not** granted in these situations:
* A QR Code campaign ends earlier than planned and the codes are no longer needed
* A campaign's results do not meet expectations, leading to it being stopped
* Scanova does not meet expectations
The [14-day free trial](/billing/trial-and-expiry) exists so these can be tested before any money changes hands. During it you can create up to 3 Dynamic QR Codes, and Static QR Codes are not restricted.
## Case 1: an unintended auto-renewal charge
Auto-renewal is enabled by default when you buy a subscription. That is deliberate — it keeps your account and your Dynamic QR Codes resolving, so codes already printed and in circulation don't stop working between billing periods.
You can decline auto-renewal at the time of purchase, or turn it off later at any point from [Subscription & billing](/billing/subscription-and-billing).
If auto-renewal wasn't turned off and a charge goes through unintentionally, you can claim a refund within a window that depends on your renewal period:
| Renewal period | Refund claim is made within |
| :- | :- |
| 1 month | 7 days |
| 12 months | 14 days |
Approved refunds are processed within 24 hours, and the amount is credited in 7–10 working days.
## Case 2: recurring technical issues
If technical problems with Scanova affect your QR Code campaigns, contact the team at [support@scanova.io](mailto:support@scanova.io) and they will work to resolve them.
Support runs 24/5, Monday to Friday, over live chat, email, phone, and scheduled video calls. If the team doesn't respond promptly, you can ask for the escalation matrix.
Where an issue stays unresolved, or several technical issues occur, you can request a refund. Each request needs management approval and is handled case by case.
## Requesting a refund
Email [support@scanova.io](mailto:support@scanova.io) with your account details and which of the two cases applies. For an auto-renewal claim, send it inside the window in the table above.
## Related
* [Subscription & billing](/billing/subscription-and-billing) — your renewal date, and where to turn auto-renewal off
* [When your trial or subscription ends](/billing/trial-and-expiry) — what happens to your QR Codes when billing stops
* [Payment methods](/billing/payment-methods) — how to pay, and how to change a card
* [Plans and pricing](https://scanova.io/pricing) — what each plan costs and includes
# Subscription & billing
Source: https://docs.scanova.io/billing/subscription-and-billing
Review the plan your account is on, the limits that come with it, your billing address and tax details, and the invoices Scanova has issued you so far.
The **Subscription** page (`/subscription`) is where you manage your plan, renewal, payment method, and usage. It's reachable from the sidebar and has four tabs: **Overview**, **Plans**, **Billing**, and **Payment History**. This page covers the **Overview** tab and **Payment History**; for upgrading or downgrading your plan, see [Upgrading your plan](/billing/upgrading).
There is no separate **AI Credits** tab. Navigating to `/subscription/credits` redirects back to Overview — AI credit allowances sit within your plan's limits rather than on a tab of their own.
## Current Plan
The **Current Plan** card shows your plan name (e.g. **Pro Plan**), an **Active** status badge, and headline limits for that plan (leads, QR Codes, AI Insights). If your subscription has an end date, a banner below states it (e.g. "Subscription will end on \{date}"). **Change Plan** takes you to the plan picker described in [Upgrading your plan](/billing/upgrading).
## Renewal Details
This card shows your billing cycle and expiry date, plus whether **Auto-renewal** is enabled or disabled. Click **Manage Auto-renewal** to open the auto-renewal dialog.
The toggle in this dialog only turns auto-renewal **off** — once it's off, the switch is disabled here, so re-enabling auto-renewal isn't done from this dialog (renew or resubscribe from the **Plans** tab instead).
On a lifetime/non-expiring plan, this dialog shows a simplified message instead — "You're on a lifetime plan. Your subscription never expires and auto-renewal doesn't apply." — with just a **Close** button, since there's nothing to renew or cancel. The toggle/warning described above applies to plans with a real expiry date.
Turning auto-renewal off walks you through a short cancellation flow: a reason for leaving, an optional comment, and a password (or Google) confirmation step, before it takes effect. Your plan otherwise stays active until the current period's expiry date.
## Payment Method
This card shows the payment method on file — a card (masked digits and expiry), UPI ID, wallet, or bank mandate — or **No payment method** if you haven't added one yet (for example, if your plan was granted without going through checkout).
Do not enter real payment details while exploring this page unless you intend to complete a purchase. If a payment method exists, the card shows an **Update Payment Method** or **Manage Payment Method** button; with none on file, the card is informational only, as shown in the screenshot below.
## Usage Overview
This card tracks your current billing period's consumption against your plan's limits with a progress bar per resource:
* **QR Codes** — total used out of your plan limit, broken down into static and dynamic counts underneath
* **Total Scans** — scans recorded this period (unlimited on plans without a scan cap)
* **Custom Domains** — domains connected out of your plan's allowance
* **Team Members** — seats used out of your plan's allowance
* **Lead Generation** — leads captured this period (unlimited on plans without a cap)
If you're approaching or over a limit on any of these, [Upgrading your plan](/billing/upgrading) covers how to move to a higher tier.
## Billing History
Click **View Billing History** at the bottom of the Overview tab (or the **Payment History** tab directly) to see your transaction log at `/subscription/history`.
Each row lists the transaction ID, subscription, amount, payment method used, status, and date; use **View** to filter the table. A new account with no billing history yet shows a centered "No payments yet" message instead of an empty table.
## Other tabs on this page
One additional tab lives alongside Overview and Payment History:
* **Billing** (`/subscription/billing`) — your billing address and tax details (for example, a GST number in India) used on invoices and receipts, separate from your payment method. With no address on file, this shows a **No Billing Information** empty state with an **Add Billing Information** button; once set, it shows a **Current Billing Information** card (name, street address, city, state/province, postal code, country, and a "Last updated" date) with an **Edit Billing Information** button instead.
A separate **Billing Contact Email** section below lets you add one additional email address to receive billing emails (invoices, payment-failure alerts) alongside the account owner, who always receives them regardless.
## Related
* [Upgrading your plan](/billing/upgrading) — comparing plans, add-ons, and how downgrades are blocked when usage exceeds a lower tier's limits
* [Shared users](/team/shared-users) — managing the team members counted against the Team Members usage bar above
* [Email preferences](/account/email-preferences) — controls whether you receive the Billing and Payments and Subscription Reminders emails referenced elsewhere in Scanova
# When your trial or subscription ends
Source: https://docs.scanova.io/billing/trial-and-expiry
What the 14-day free trial includes, what happens to your QR Codes when a trial or a paid subscription ends, and how to bring the deactivated ones back.
Your QR Codes are never deleted when a trial or a paid subscription ends. They stop resolving, and they stay in your account until you subscribe again.
## What the free trial includes
The free trial runs for **14 days** and lets you create up to **3 Dynamic QR Codes**.
Static QR Codes are not part of that limit — the trial doesn't restrict them.
The trial is a good way to see how [Dynamic QR Codes](/qr-codes/create/website-url) behave in practice: editing a destination after publishing, and watching scans arrive in [Analytics](/analytics/dashboard).
## What happens when it ends
When your 14 days are up and you haven't subscribed, your Dynamic QR Codes are **deactivated**. Three things follow from that:
* A deactivated QR Code stops resolving, so anyone scanning it no longer reaches your destination
* The QR Code itself, its design, its destination, and its scan history all stay in your account
* Nothing is deleted, and you don't need to recreate anything later
**A lapsed paid subscription behaves the same way.** If a subscription ends without renewing, that account's Dynamic QR Codes are deactivated on the same terms — they stop resolving, and everything is retained.
This matters most for codes you have already printed or shipped. A deactivated QR Code is still physically scannable, so the scan happens and then fails to reach anything. If a code is out in the world on packaging, signage, or business cards, plan the renewal before the end date rather than after it.
## Bringing your QR Codes back
Subscribe, and your deactivated QR Codes start resolving again at the destinations they already had. There's no separate reactivation step and nothing to re-enter.
See [Upgrading your plan](/billing/upgrading) for the checkout, and [Subscription & billing](/billing/subscription-and-billing) for your renewal date and auto-renewal setting.
Auto-renewal is what stops a paid subscription lapsing in the first place. You can check or change it from the **Renewal Details** section of [Subscription & billing](/billing/subscription-and-billing).
## Related
* [Upgrading your plan](/billing/upgrading) — comparing plans and completing checkout
* [Subscription & billing](/billing/subscription-and-billing) — your current plan, renewal date, payment method, and billing history
* [QR Health Center](/qr-codes/manage/qr-health-center) — find codes that aren't resolving, across your whole account
* [Plans and pricing](https://scanova.io/pricing) — what each plan costs and what it includes
# Upgrading your plan
Source: https://docs.scanova.io/billing/upgrading
Move your account up to a higher plan, what changes the moment that you do, and how the cost gets worked out when you upgrade partway through a period.
The **Plans** tab (`/subscription/upgrade`) is where you compare and switch plans. Reach it from the **Change Plan** button on the [Subscription overview](/billing/subscription-and-billing), or the **Plans** tab directly.
Selecting a plan here starts a real checkout. If you're exploring the page, open dialogs to see what they contain, then cancel or close them rather than confirming — don't complete a purchase or submit payment details unless you actually intend to change your plan.
## The plan and pricing selector
A **Monthly** / **Yearly** toggle switches the billing period for every plan card at once; **Yearly** is the default and shows a "Save up to 40%" badge, with the discounted price and the original price struck through.
A currency selector next to the toggle lets you price plans in a different currency (for example ₹ INR or \$ USD).
The **Basic** plan is annual-only — in Monthly mode it still shows a price, but with an **Annual Only** badge next to it, since billing for it is always yearly regardless of which toggle is selected.
Each card (**Basic**, **Lite**, **Standard**, **Pro**, **Enterprise**) lists its QR Code limit as a badge and its included features underneath. **Pro** is marked **Most Popular**. **Enterprise** hides pricing entirely and shows **Contact Sales** instead of **Get Started** — see [Enterprise quotes](#enterprise-quotes) below.
Below the main grid, a separate **Ultra Lite** card offers a single Dynamic QR Code with lifetime validity for a one-time fee — this is unaffected by the Monthly/Yearly toggle, since it's always a one-time purchase.
## Pro-plan add-ons
Plans that support add-ons (including Pro) show a **"Customize your \{plan} plan"** dialog before checkout, with quantity steppers for extra **Users** and **Custom Domains** beyond the plan's base allowance. Each increment shows its added cost for the billing period you selected. Confirm to carry those quantities into checkout, or cancel to proceed with the plan's default allowance.
## Enterprise quotes
Click **Contact Sales** on the Enterprise card to open a short lead form.
Fill in your name, company, work email, and needs, then **Send Request** — Scanova's team responds within 24 hours, per the dialog. If you'd rather get a number immediately, the **Build a custom quote** and **request a quote** links switch this same dialog to a self-serve calculator and a more detailed estimation form, respectively, instead of the quick contact form.
## Downgrading
Choosing a lower-tier plan than your current one runs a pre-check against your actual usage before anything changes. If your usage fits within the lower plan's limits, you'll see a downgrade confirmation step summarizing your current plan's remaining access, the new plan's start date, and a checklist of what changes — you must check an acknowledgement box before **Confirm Downgrade** is enabled.
If your usage exceeds what the target plan allows, the downgrade is blocked instead:
Each violated resource is listed with how far over the limit you are (e.g. "5 over limit") and, where applicable, the specific items involved (like named custom domains or a team member) and a **Manage these** button that takes you to the relevant management page (QR Codes, Domains, or Users) to bring your usage down first. There's no "downgrade anyway" option — you resolve the violations, then try again.
Since a downgrade check reflects your live usage, freeing up resources (removing a shared user, deleting unused QR Codes) before retrying is the fastest way past a blocked downgrade.
## Related
* [Subscription & billing](/billing/subscription-and-billing) — your current plan, renewal, payment method, and usage overview
* [Shared users](/team/shared-users) — managing the team members that count toward the Users add-on and downgrade checks above
# Notifications
Source: https://docs.scanova.io/notifications/overview
The notifications Scanova can send you, where they appear in the dashboard, and how to choose which events actually reach you rather than all of them.
Scanova has **two separate notification surfaces**: the bell icon in the top bar (present on every dashboard page), and a dedicated `/notifications` page. They are not the same system — see [Two notification systems](#two-notification-systems) below before you go looking for one in the other.
This page documents `/notifications`, the module in scope here — a simple **All / Unread** inbox for account-level events like team invitations, quota warnings, and lead or form-response alerts.
This is not where you control which emails Scanova sends you. For that, see [Email preferences](/account/email-preferences) — a separate, six-category toggle list at `/account/email-preferences`. This page is about in-app messages you read in the dashboard, not your inbox.
## The notifications page
Open `/notifications` directly (there's no sidebar link to it — see below). The layout is a two-pane inbox:
* **Left pane** — a scrollable list of notifications, each showing a title, a one-line body preview, and a relative timestamp. Unread items carry a filled dot and bold title; read items don't.
* **Right pane** — a detail view for whichever notification you've selected, showing the full title, an absolute timestamp, and the full body text. If the notification carries a deep link, an **Open** button appears in the detail header.
At the top of the list, two tabs filter what's shown:
| Tab | Shows |
| - | - |
| **All** | Every notification on your account, read or unread. |
| **Unread** | Only unread notifications. The tab label shows a live count, e.g. "Unread (3)", once you have at least one. |
A **Mark all read** button appears next to the tab row whenever you have at least one unread notification, and clears them all in a single click.
Selecting an unread notification from the list marks it read immediately (no separate confirmation) and updates its dot/bold styling in place.
## Notification types
Four event types are wired to fire a notification:
| Event | Who's notified |
| - | - |
| A shared user accepts your team invitation | Account owner |
| Your account hits a plan quota limit (dynamic QR Codes, custom domains, shared users, or AI QR tokens) | Account owner |
| A lead is captured through a lead-generation QR Code or form | Account owner |
| Someone submits a form on one of your QR Code landing pages | Account owner |
## Two notification systems
The bell icon in the top bar — the notification entry point every user actually sees, on every dashboard page — is **a different, separate widget** from the `/notifications` page described above:
* The bell renders a third-party embedded inbox widget (attributed "Inbox by Novu" in its own footer) with its own read/unread state, its own popover UI, and its own real-time delivery — this is what the four event types in the table above actually feed
* The `/notifications` page documented above is separate from that widget, with its own data. It is not linked from the sidebar or the top bar, so you reach it by entering the URL directly
For your notifications day-to-day, use the bell icon in the top bar rather than `/notifications`.
## Related
* [Email preferences](/account/email-preferences) — controlling which emails Scanova sends you, a separate system from both notification surfaces on this page
# Changelog
Source: https://docs.scanova.io/resources/changelog
What has changed in Scanova recently, with the newest updates first, so you can see which features shipped and when they became available to your account.
This is a living page — new entries are added as customer-visible changes ship. It isn't an exhaustive engineering log (internal refactors, test coverage, and CI fixes are left out on purpose); it covers changes you'd actually notice while using the product. The entries below were confirmed directly against the project's commit history at the time this page was written.
Scanova added a dedicated **GS1** category to Quick Code creation — a 5-step wizard (Resolver → Identify → Data Attributes → Resolution → Review) for encoding [GS1 Digital Link](https://ref.gs1.org/standards/digital-link/) URIs for retail and supply-chain use cases. Unlike every other Quick Code type, a GS1 code can be either Dynamic or Static depending entirely on which resolver you choose in step 1.
See [GS1 QR Code](/qr-codes/create/gs1) for the full walkthrough.
Creating a Management API key with **Never** selected as its expiration now shows an explicit amber warning in the key-created dialog, since a non-expiring key won't rotate automatically and deserves extra care if it's ever exposed.
See [Creating & managing API keys](/api-keys/creating-and-managing-keys) for where this appears.
The **Billing History** download action was split into two distinct options — **Receipt** and **Invoice** — instead of a single combined download, matching the two different documents accounting teams typically need.
See [Subscription & billing](/billing/subscription-and-billing#billing-history) for the Payment History table this download lives on (documented before this specific split shipped, so the two-button detail isn't shown there yet — confirmed instead directly from source for this entry).
## Related
* [Troubleshooting](/resources/troubleshooting) and [FAQ](/resources/faq) — for gaps and quirks found alongside these changes, not yet resolved
# FAQ
Source: https://docs.scanova.io/resources/faq
Answers to the questions Scanova gets asked most, covering QR Code types, Dynamic versus Static codes, scan tracking, plans, and account management too.
Scanova also has its own in-app FAQ search at [Support](/support/overview) — this page is a shorter, docs-specific set focused on the questions that come up most often.
## Product basics
A **Quick Code** does something the instant it's scanned (open a URL, dial a number, join Wi-Fi) with no landing page at all. A **Page** renders a full, block-based landing page first. Some Quick Code types are Dynamic (editable after publishing) and some are Static (fixed forever) — the split isn't the same for every type. See [Quick Code vs. full Page](/qr-codes/create/quick-code-vs-page) for the full breakdown.
Two ways, depending on how much content you have.
**Several buttons on one page.** Build a Page and add a [Button block](/page-builder/blocks-reference) for each destination. Each button carries its own link — a URL, an email address, a phone number, an SMS, or another QR Code — so one scan can offer a whole set of choices.
**Several pages behind one code.** [Multi-page navigation](/page-builder/multi-page-navigation) puts more than one page behind a single QR Code, with navigation between them. It suits menus, event programs, and product ranges — content that has outgrown a single page. Available on the Standard Plan and above.
Either way the QR Code itself stays the same, so nothing needs reprinting when the destinations change.
GS1 is the only Quick Code category that can be either Dynamic or Static depending on which resolver you choose in step 1 of its 5-step wizard — not a separate toggle like every other type. See [GS1 QR Code](/qr-codes/create/gs1) for the full wizard walkthrough.
Not automatically within New Scanova. See [Switching from Classic to New UI](/onboarding/classic-to-new-ui) if the specific question is about moving a page's platform rather than its content history.
## Why can't I see a feature I expect on my plan?
Three features are gated by account-level quotas rather than by plan tier alone. If one of them is missing for you on a paid plan, it very likely isn't just you:
The folder sidebar on the QR Codes page is gated behind a `FOLDER_MANAGEMENT` plan quota, granted on the Pro Plan and above. See [Folders, tags & bulk operations](/qr-codes/manage/folders-tags-bulk-operations).
The `AI_QR_TOKENS` quota AI Studio checks for isn't actually granted on the canonical Pro plan, despite the paywall copy describing it as a Pro feature. See [AI QR / AI Studio](/qr-codes/create/ai-qr).
Same pattern — `MANAGEMENT_API` was also found missing from the canonical Pro plan. See [Creating & managing API keys](/api-keys/creating-and-managing-keys).
## Notifications and support
In the bell icon in the top bar, on every dashboard page — that's a separate, real-time notification widget. The dedicated `/notifications` page also exists and works, but has no link to it anywhere in the app's navigation; it's reachable only via direct URL. See [Notifications](/notifications/overview) for the full explanation of both.
No — they're unrelated systems. [Notifications](/notifications/overview) is in-app messages; [Email preferences](/account/email-preferences) is a separate six-category toggle list controlling which transactional and marketing emails Scanova sends you.
Nowhere in the dashboard UI today — this is a genuine gap, not a hidden setting. Call the usage endpoints directly instead. See [API usage dashboard](/api-keys/usage-dashboard).
## Account platform
Switching is a one-way, account-wide action, not a per-page choice. See [Switching from Classic to New UI](/onboarding/classic-to-new-ui) for exactly what is and isn't preserved.
No — only the account owner. A shared user sees a disabled button with an explanatory tooltip. See [Switching from Classic to New UI](/onboarding/classic-to-new-ui).
## Related
* [Troubleshooting](/resources/troubleshooting) — longer walkthroughs for the issues above, plus a few more
* [Support](/support/overview) — live chat, WhatsApp, phone numbers, and the in-app FAQ search
# QR Code campaign best practices
Source: https://docs.scanova.io/resources/qr-campaign-best-practices
What separates a QR Code campaign that works from one that doesn't: choosing Dynamic, printing it at the right size, and measuring the scans honestly.
Most QR Code campaigns fail for reasons you can fix before anything goes to print. The destination changes and the code can't follow. The code is too small for the distance people stand at. Nobody knows why they should scan it. None of that is bad luck — each one is a decision made early, usually without realizing it was a decision.
## Choose Dynamic before you print anything
This is the one that cannot be undone later.
A Static QR Code encodes its destination into the pattern itself, so the destination is fixed the moment the code exists. A [Dynamic QR Code](/qr-codes/dynamic-vs-static) resolves through Scanova, so you can point it somewhere else at any time — after the packaging is printed, after the banner is hung, after the campaign has already started.
Campaigns change. Landing pages get renamed, offers expire, microsites get retired. With a Dynamic QR Code, that's an edit. With a Static one, it's a reprint.
Dynamic is also the only way to get scan data at all. A Static code produces none — there's no server-side step for Scanova to count.
## Print it at a size people can actually scan
Size follows viewing distance, not the space left on the layout. A billboard code and a business-card code are solving different problems, and the billboard one loses if it is sized to look tidy rather than to be read from across a street.
* Put it at eye level and where people already stop walking
* Keep it off curved, reflective, or textured surfaces, which distort the pattern
* Leave the quiet zone — the clear margin around the code — free of other artwork
* Keep contrast strong: a dark code on a light background survives bad lighting, a low-contrast one doesn't
Then check it before the print run, not after. [Test QR Code](/qr-codes/manage/test-qr-code) confirms a code resolves, and if you've customized the design, [Custom QR Code design](/qr-codes/manage/custom-design) covers how far you can push colors and logos before scannability suffers.
## Tell people what they get
A bare QR Code on a poster asks for effort and promises nothing. Name the reward next to it — *Scan for 10% off*, *Scan to see the menu*, *Scan to register* — in text large enough to read from the same distance the code is scanned from.
## Make the destination survive a phone
Whoever scans is standing up, on mobile data, with one hand. A slow page loses them before it renders.
Send them to something built for that: a [page built in Scanova](/page-builder/publishing) renders on mobile by default, or point the code at a page you already know is fast. Either way, deliver what the call to action promised, immediately — not a homepage they have to navigate from.
## Make the link look like yours
A scan preview showing your own domain reads as legitimate in a way a generic short link does not, and on packaging or signage that difference matters. [Custom domains](/qr-codes/manage/custom-domains) replace the default short URL with one of your own.
## Know whether it worked
Scan counts alone won't tell you much. Three things make the numbers usable:
* **[UTM parameters](/qr-codes/manage/utm-builder)** carry the campaign into your own analytics, so QR Code traffic sits beside your other channels instead of arriving as direct traffic
* **[Bot scan exclusion](/analytics/bot-scans)** filters out link previewers and crawlers, which otherwise inflate early numbers and make launch day look better than it was
* **[Scheduled reports](/analytics/reports)** put the same figures in front of the people who asked for them, without anyone logging in
Give each placement its own QR Code rather than reusing one everywhere. Two codes on two channels tell you which channel worked; one code across both tells you nothing. [Folders and tags](/qr-codes/manage/folders-tags-bulk-operations) keep that manageable once a campaign runs to dozens of codes.
## Keep control after launch
A printed code is permanent. What it does doesn't have to be.
* [Scheduling](/qr-codes/manage/scheduling) turns a code on or off at a set date, which suits a seasonal offer or an event that ends
* [Conditional redirection](/qr-codes/manage/conditional-redirection) sends scanners to different destinations by country, language, device, or time
* [QR Health Center](/qr-codes/manage/qr-health-center) surfaces codes that have stopped working without anyone noticing — expired schedules, unpublished drafts, broken form links — across the whole account at once
Check the Health Center during a campaign, not after it. A code that stopped resolving in week one is only expensive if nobody notices until week six.
## Related
* [Dynamic and Static QR Codes](/qr-codes/dynamic-vs-static) — the choice that determines everything above
* [Test QR Code](/qr-codes/manage/test-qr-code) — verify a code resolves before it goes to print
* [Custom QR Code design](/qr-codes/manage/custom-design) — colors, logos, and frames, and their effect on scannability
* [Analytics dashboard](/analytics/dashboard) — where scan performance actually shows up
# Troubleshooting
Source: https://docs.scanova.io/resources/troubleshooting
Fixes for the problems people hit most often, from a QR Code that will not scan, through unverified domains, to a shared user who cannot see a feature.
This page collects the problems people actually hit — plan-quota gaps, interface quirks, and genuinely confusing flows — each pointing to the specific page that covers it. It isn't a generic troubleshooting checklist.
## Account & sign-in
See [Multi-factor authentication](/authentication/multi-factor-authentication#saving-backup-codes) for how backup codes work, and save them the moment you enroll a method. If you've lost both your MFA method and your backup codes, there is no in-app self-recovery path — contact [Support](/support/overview) directly.
Use [Password reset](/authentication/password-reset) from the login screen. This is separate from MFA recovery codes — losing your password doesn't affect your MFA enrollment, and vice versa.
That's expected, not a bug — [Multi-factor authentication](/authentication/multi-factor-authentication) only applies to password-based sign-in. If your organization signs in through [SSO](/authentication/sso), your identity provider handles that layer instead, and Scanova doesn't manage it.
## QR Codes and scanning
Open [QR Health Center](/qr-codes/manage/qr-health-center) (`/qr/health`) before checking codes one by one — it surfaces unpublished drafts, expired schedules, broken form links, and incomplete domain verification across your whole account in one place.
This is a fundamental, upfront choice, not a setting you can change after creation for every type. See [Quick Code vs. full Page](/qr-codes/create/quick-code-vs-page) for exactly which types are "Dynamic" (editable after publish) versus "Static" (fixed at creation).
## Folders, tags, and team permissions
This is very likely a plan-quota gap, not a bug on your end. [Folders, tags & bulk operations](/qr-codes/manage/folders-tags-bulk-operations#organize-with-folders) confirms the folder sidebar is plan-gated (`FOLDER_MANAGEMENT`), and that quota starts at the Pro Plan. If you're on the Pro Plan or above and still don't see folders, contact [Support](/support/overview).
Both require a separate permission grant (`FOLDER_CAN_CREATE_AND_MANAGE`) on top of the plan quota above. A shared user without that specific permission won't see those controls at all — ask your account owner to grant it. See [Folders, tags & bulk operations](/qr-codes/manage/folders-tags-bulk-operations).
This isn't a bug — Lead Lists are a legacy feature. Scanova no longer exposes a way to create one from the app; new lead capture goes through [Forms](/forms/building-a-form) instead. See [Leads & lead lists](/leads/lead-lists) for what you can still do with existing lists (view, export).
## AI Studio and API access
Access is controlled by the `AI_QR_TOKENS` quota the app checks, and not every plan grants it — so you can hit this paywall on a paid plan. See [AI QR / AI Studio](/qr-codes/create/ai-qr) for the exact wording shown. If it happens to you, check your plan's feature list or contact support rather than assuming your account is misconfigured.
Management API access is also plan-gated (`MANAGEMENT_API`), and — like the AI Studio and Folders gaps above — not every plan grants it. See [Creating & managing API keys](/api-keys/creating-and-managing-keys) for where the limit is shown in the UI.
There currently isn't one. See [API usage dashboard](/api-keys/usage-dashboard) for the endpoints to call directly in the meantime.
## Switching between Classic and New Scanova
This is expected, one-way behavior, not a bug — see [Switching from Classic to New UI](/onboarding/classic-to-new-ui) for the corrected explanation. A page built in New Scanova keeps working live but becomes permanently read-only once viewed from Classic; there's no in-app undo.
Also expected — switching platform is an account-wide decision only the account owner can make. Ask them to switch it from their own account.
## Related
* [FAQ](/resources/faq) — shorter, single-answer questions that didn't need a full walkthrough
* [Support](/support/overview) — live chat, WhatsApp, and phone numbers if nothing above resolves it
# Support
Source: https://docs.scanova.io/support/overview
How to get help with Scanova: what the Help Center covers, how to reach support directly, and what information to include so the reply is useful first time.
The **Support Center** (`/support`) is Scanova's in-app help hub — a single page combining FAQ search, live chat, phone numbers, a demo video, and a link out to the full documentation and knowledge base.
## Searching the knowledge base
The search bar at the top queries Scanova's knowledge base directly (support.scanova.io) as you type, with a short debounce. Results appear in a dropdown below the search bar; selecting one opens the full article in a side drawer without leaving the page.
## Frequently Asked Questions
The FAQ card shows three questions each from three categories — **Learn**, **Get Started**, and **Advanced** — as a quick-reference set:
| Category | Sample questions shown |
| - | - |
| **Learn** | Difference between Static and Dynamic QR Codes, how to scan a QR Code, which QR Code type to create |
| **Get Started** | What you can create with Scanova, brand customization, free-trial QR Code limits |
| **Advanced** | Tracking and analytics, bulk generation, using the Management API |
Selecting any question opens its full answer in the same side drawer used for search results. **View all \{N} articles** at the bottom of the card links out to the full FAQ index on `support.scanova.io`.
## Chat With Us
This card lists support hours in three time zones and two ways to reach a live person:
| Time zone | Hours |
| - | - |
| ET | Mon 12:30am – Sat 01:30am |
| UTC | Mon 4:30am – Sat 05:30am |
| IST | Mon 10:00am – Sat 11:00am |
* **Live Chat** opens an embedded chat widget. If the widget cannot load, the button surfaces a message instead — *"Live chat isn't available right now. Please reach us on WhatsApp or email below."* — so you are pointed at another channel rather than left with a dead click.
* **WhatsApp** opens `wa.me` with a phone number chosen automatically based on the current time in India Standard Time — a different WhatsApp line covers each of three IST shift windows
## Call Us
Three phone numbers, one per region:
| Region | Number |
| - | - |
| USA | +1-855-440-7400 |
| UK | +44 8081 969 969 |
| IND | +91 9289093031 |
Each number is a `tel:` link — selecting it on a phone or a device with calling capability dials it directly.
## Documentation
Links out to `docs.scanova.io` — the same documentation site this page belongs to.
## Other quick actions
* **Watch Demo Video** — opens a YouTube walkthrough in a new tab
* **Book a Demo** — opens Scanova's Cal.com scheduling link in a new tab
## Related
* [Multi-factor authentication](/authentication/multi-factor-authentication) — if you're locked out of your account rather than looking for product help, start there before contacting support
* [Troubleshooting](/resources/troubleshooting) and [FAQ](/resources/faq) — curated answers to common issues, distinct from the in-app FAQ search above
# Activity
Source: https://docs.scanova.io/team/activity
See what your team has been doing across the account, with trends over time and AI-written summaries that point out what has actually changed recently.
**Activity** (`/users/user-activity`) is a dashboard for what your invited teammates have been doing on the account — a feed of individual events, trend charts, and an AI-generated weekly summary. It sits alongside [Roles](/team/roles-and-permissions) and [Logs Export](/team/logs-export) under **User Management** in the sidebar — all three only appear for accounts with the right plan and permissions.
This page tracks **shared/invited-user** activity, not the account owner's own actions. Publishing a QR Code as the account owner does not appear in the feed, Team Contributions, or Activity Trends — that matches the page's own framing ("see what your team has been doing"), but it does mean you won't see your own activity here.
## Team Contributions
A card summarizing which teammates have created QR Codes or forms, and how many. With no qualifying activity yet, it reads "No team member has created a QR Code or Form yet."
## Activity Insight
An AI-generated weekly summary of team activity. It needs **7 days of activity** to produce its first summary — until then it reads "Your first weekly insight will appear here after 7 days of activity."
## Activity Trends
A chart of activity volume over time, with a period selector: **Last 7 Days**, **Last 30 Days** (the default), **Last 90 Days**, or **Custom**. With nothing to chart, it reads "No activity in this period yet."
## Activity Feed
A table of individual events at the bottom of the page, with four columns you can show or hide via the **View** button's **Toggle columns** menu: **Event**, **Actor**, **Target**, and **Occurred At**.
Filter the feed by:
* **Search by target** — free-text search
* **Event Type** — a checklist of event types: **QR Created**, **QR Updated**, **QR Deleted**, **Login**, **Shared User Login**, **Share Granted**, **Share Revoked**
* **Date** — quick presets (**Today**, **Yesterday**, **This Week**, **Last Week**, **This Month**, **Last Month**) or a custom two-month calendar range, with **Clear**/**Apply** buttons
Several of this page's dropdowns and the date-range calendar render their options invisibly — a rendering issue this docs pass also found on several other pages' popups. The options are present and selectable in principle; if a menu doesn't visibly respond to a click, try a different browser or zoom level, or reload and try again.
## Related
* [Logs Export](/team/logs-export) — download this same underlying activity as a CSV instead of viewing it live
* [Shared users](/team/shared-users) — inviting the teammates whose activity shows up here
* [Roles & permissions](/team/roles-and-permissions) — what each role can see and do on the account
# Bulk-download QR Codes
Source: https://docs.scanova.io/api-reference/management-api/bulk-operation/download
api-reference/openapi/management-api.json POST /bulk-operation/download/
POST /bulk-operation/download/
Packages many QR Codes for download, asynchronously, on `api.scanova.io`, authenticated with your raw API key.
```
POST https://api.scanova.io/bulk-operation/download/
Authorization: YOUR_API_KEY
```
A label for this operation, shown in [List bulk operations](/api-reference/management-api/bulk-operation/list).
Specific QR ids to download. Alternatively use `filter`, `search`, or `bulk_op_id`.
A saved filter selecting which QR Codes to download, as an alternative to `qr_id_list`.
A search term selecting which QR Codes to download, as an alternative to `qr_id_list`. `"all"` selects every owned QR Code.
An existing bulk operation's `operation_id` — re-download the QR Codes that operation generated, as an alternative to selecting by id/filter/search.
The download format, e.g. `zip`, `csv`, `xlsx`.
The image size for downloaded QR Code images, where applicable.
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/bulk-operation/download/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Batch download",
"qr_id_list": ["a1b2c3d4", "e5f6g7h8"],
"result_file_format": "zip"
}'
```
```json 200 theme={null}
{
"operation_id": "bo-dl-001",
"message": ["Your file is being processed. We'll email you once it's ready."]
}
```
Asynchronous — poll [List bulk operations](/api-reference/management-api/bulk-operation/list) for `status`/`progress`, then fetch `result_file`. `400` if no QR Codes match the given selection.
## Related
* [Export owned URL QR Codes as CSV](/api-reference/management-api/bulk-operation/qr-list) — a quick source of ids for `qr_id_list`.
* [Run a bulk quick action](/api-reference/management-api/bulk-operation/quick-action) — uses the same targeting parameters for organize/apply-design/delete instead.
* [List bulk operations](/api-reference/management-api/bulk-operation/list) — poll for completion.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Bulk-generate QR Codes
Source: https://docs.scanova.io/api-reference/management-api/bulk-operation/generate
api-reference/openapi/management-api.json POST /bulk-operation/generate/
POST /bulk-operation/generate/
Creates many QR Codes from an uploaded sheet, asynchronously, on `api.scanova.io`, authenticated with your raw API key.
```
POST https://api.scanova.io/bulk-operation/generate/
Authorization: YOUR_API_KEY
```
A label for this operation, shown in [List bulk operations](/api-reference/management-api/bulk-operation/list).
The filled-in sheet, one row per QR Code. The **first row's `qr code id`** column supplies the template QR Code — its category and field schema are what every other row is validated against.
A custom domain id to use for the generated QR Codes' short links.
Design pattern data applied to all generated QR Codes, typically JSON encoded as a string.
Optional auto-deactivation rule applied to all generated QR Codes: `expire_on`/`activate_on` (future ISO-8601 datetimes) and/or `expire_on_scan_count` (positive integer).
A folder id to save the generated QR Codes into.
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/bulk-operation/generate/" \
-H "Authorization: YOUR_API_KEY" \
-F "name=September event QR batch" \
-F "file=@event-qrs.xlsx" \
-F 'auto_deactivate={"expire_on":"2026-12-31T23:59:59Z"}'
```
```json 200 theme={null}
{
"operation_id": "bo-8f3a1c",
"message": ["Your file is being processed. We'll email you once it's ready."]
}
```
## This is asynchronous
The `200` response confirms the job was accepted, not that the QR Codes exist yet — the actual work runs on a background worker. Poll [List bulk operations](/api-reference/management-api/bulk-operation/list) (filter locally by `operation_id`) and watch `status` (`new` → `completed`/`partially_completed`/`failed`) and `progress`, then read `result_file`/`log_file` once done. The account also gets an email notification on completion.
`400` for an invalid file/columns, a missing or invalid template QR Code, or exceeded quota (`BULK_OPERATION` or `TOTAL_QR_CODES`). `403` if the account lacks the `BULK_OPERATION` quota, has an inactive plan, or lacks `BULK_OPERATION_CAN_GENERATE` permission.
## Related
* [Bulk-generate QR Codes (v2 templates)](/api-reference/management-api/bulk-operation/generate-v2) — the v2-template equivalent.
* [Validate a bulk operation file](/api-reference/management-api/bulk-operation/validate) — check the file before spending this call.
* [Download a sample file](/api-reference/management-api/bulk-operation/sample-file) — where to start the sheet from.
* [List bulk operations](/api-reference/management-api/bulk-operation/list) — poll for completion.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Bulk-generate QR Codes (v2 templates)
Source: https://docs.scanova.io/api-reference/management-api/bulk-operation/generate-v2
api-reference/openapi/management-api.json POST /bulk-operation/generate/v2/
POST /bulk-operation/generate/v2/
Creates many QR Codes from an uploaded sheet using v2 (theme-based) templates, asynchronously, on `api.scanova.io`, authenticated with your raw API key.
```
POST https://api.scanova.io/bulk-operation/generate/v2/
Authorization: YOUR_API_KEY
```
A label for this operation, shown in [List bulk operations](/api-reference/management-api/bulk-operation/list).
The filled-in sheet — same template-QR-from-first-row convention as [Bulk-generate QR Codes](/api-reference/management-api/bulk-operation/generate). `url`, `gs1`, and `ai` category templates are rejected here.
An active theme's id to apply to the generated QR Codes.
A folder id to save the generated QR Codes into.
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/bulk-operation/generate/v2/" \
-H "Authorization: YOUR_API_KEY" \
-F "name=September event QR batch" \
-F "file=@event-qrs.xlsx" \
-F "theme_id=4"
```
```json 200 theme={null}
{
"operation_id": "bo-9a1b2c",
"message": ["Your file is being processed. We'll email you once it's ready."]
}
```
Same asynchronous pattern as [Bulk-generate QR Codes](/api-reference/management-api/bulk-operation/generate): poll [List bulk operations](/api-reference/management-api/bulk-operation/list) for `status`/`progress`, then `result_file`/`log_file`.
`400` for an invalid file/columns, an unsupported category (`url`, `gs1`, or `ai`), or exceeded quota. `403` if the account lacks the `BULK_OPERATION` quota, has an inactive plan, or lacks `BULK_OPERATION_CAN_GENERATE` permission.
## Related
* [Bulk-generate QR Codes](/api-reference/management-api/bulk-operation/generate) — the v1-template equivalent.
* [List bulk operations](/api-reference/management-api/bulk-operation/list) — poll for completion.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Bulk-generate a GS1 serial range
Source: https://docs.scanova.io/api-reference/management-api/bulk-operation/gs1-serial-range-generate
api-reference/openapi/management-api.json POST /bulk-operation/gs1/serial-range-generate/
POST /bulk-operation/gs1/serial-range-generate/
Generates a contiguous range of GS1 QR Codes from a starting serial number, asynchronously, on `api.scanova.io`, authenticated with your raw API key.
```
POST https://api.scanova.io/bulk-operation/gs1/serial-range-generate/
Authorization: YOUR_API_KEY
```
No file upload — this endpoint generates the range programmatically from `start_serial` and `count`.
A label for this operation.
The first serial number in the range, digits only, max 20 characters.
How many sequential serials to generate, minimum 1.
An existing GS1 QR Code's id to copy shared field values from. Provide this **or** `shared_info`, not neither.
The GS1 field values shared by every serial in the range, as JSON. Provide this **or** `sample_qrid`, not neither.
A custom domain id to use for the generated QR Codes' short links.
Design pattern data applied to all generated QR Codes.
Optional auto-deactivation rule applied to all generated QR Codes.
UTM tracking parameters applied to all generated QR Codes' links.
A folder id to save the generated QR Codes into.
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/bulk-operation/gs1/serial-range-generate/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "GS1 batch 001-100",
"start_serial": "00000001",
"count": 100,
"sample_qrid": "a1b2c3d4"
}'
```
```json 200 theme={null}
{
"operation_id": "bo-gs1-001",
"message": ["Your GS1 serial range is being processed. We'll email you once it's ready."]
}
```
Same asynchronous pattern as [Bulk-generate QR Codes](/api-reference/management-api/bulk-operation/generate) — poll [List bulk operations](/api-reference/management-api/bulk-operation/list). The first assembled GS1 payload in the range is schema-validated before any QR Codes are created, so a malformed `shared_info` shape fails fast.
`400` if the resulting serial exceeds 20 characters at any point in the range, if neither `shared_info` nor `sample_qrid` is provided, or if quota (`BULK_OPERATION` or `TOTAL_QR_CODES`, checked against the full `count`) is exceeded. `403` if the account lacks the `BULK_OPERATION` quota, has an inactive plan, or lacks `BULK_OPERATION_CAN_GENERATE` permission.
## Related
* [Bulk-generate QR Codes](/api-reference/management-api/bulk-operation/generate) — the general file-upload-based equivalent for non-GS1 categories.
* [List bulk operations](/api-reference/management-api/bulk-operation/list) — poll for completion.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# List bulk operations
Source: https://docs.scanova.io/api-reference/management-api/bulk-operation/list
api-reference/openapi/management-api.json GET /bulk-operation/
GET /bulk-operation/
Lists tracking records for the account's bulk operations, on `api.scanova.io`, authenticated with your raw API key.
```
GET https://api.scanova.io/bulk-operation/
Authorization: YOUR_API_KEY
```
```bash cURL theme={null}
curl "https://api.scanova.io/bulk-operation/" \
-H "Authorization: YOUR_API_KEY"
```
```json 200 theme={null}
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"operation_id": "bo-8f3a1c",
"name": "September event QR batch",
"count": 500,
"operation_type": "generate",
"operation_type_display": "Generate",
"status": "completed",
"created": "2026-09-08T09:00:00Z",
"result_file": "https://cdn.scanova.io/bulk/bo-8f3a1c/result.xlsx",
"progress": 100,
"error_message": null,
"log_file": "https://cdn.scanova.io/bulk/bo-8f3a1c/log.csv",
"failed_count": 0,
"folder": null,
"created_by": "Jane Doe",
"qr_category": "website-url"
}
]
}
```
This is the endpoint every async bulk endpoint tells you to poll. `status` moves from `new` to `completed`, `partially_completed`, or `failed`; `progress` is a 0–100 completion percentage. Once `status` isn't `new`, `result_file` and `log_file` (when relevant) hold download URLs.
Scope: the account's own operations, plus — for team accounts — operations created by any shared user, unless the call is made via account impersonation, in which case scope narrows to just the impersonated user.
## Related
* [Get bulk operation stats](/api-reference/management-api/bulk-operation/stats) — aggregate counts over this same list.
* [Rename a bulk operation](/api-reference/management-api/bulk-operation/rename) — relabel an entry here.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Export owned URL QR Codes as CSV
Source: https://docs.scanova.io/api-reference/management-api/bulk-operation/qr-list
api-reference/openapi/management-api.json GET /bulk-operation/download/qr-list/
GET /bulk-operation/download/qr-list/
Exports a CSV of the account's own active `url`-category QR Codes, on `api.scanova.io`, authenticated with your raw API key.
```
GET https://api.scanova.io/bulk-operation/download/qr-list/
Authorization: YOUR_API_KEY
```
```bash cURL theme={null}
curl "https://api.scanova.io/bulk-operation/download/qr-list/" \
-H "Authorization: YOUR_API_KEY" \
-o qr-ids.csv
```
The response is a `text/csv` file download, not JSON — handy as a source of QR ids for [Bulk-download QR Codes](/api-reference/management-api/bulk-operation/download) or [Run a bulk quick action](/api-reference/management-api/bulk-operation/quick-action). `400` if the account has no active `url` QR Codes to export.
## Related
* [Bulk-download QR Codes](/api-reference/management-api/bulk-operation/download) — use the exported ids as `qr_id_list`.
* [Run a bulk quick action](/api-reference/management-api/bulk-operation/quick-action) — same, for organize/apply-design/delete.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Run a bulk quick action
Source: https://docs.scanova.io/api-reference/management-api/bulk-operation/quick-action
api-reference/openapi/management-api.json POST /bulk-operation/quick-action/
POST /bulk-operation/quick-action/
Applies one quick action to many QR Codes at once, asynchronously, on `api.scanova.io`, authenticated with your raw API key.
```
POST https://api.scanova.io/bulk-operation/quick-action/
Authorization: YOUR_API_KEY
```
One of `apply_design`, `organize`, or `delete`.
Specific QR ids to act on. Alternatively use `filter`, `search`, or `bulk_op_id` — same targeting options as [Bulk-download QR Codes](/api-reference/management-api/bulk-operation/download).
A saved filter selecting which QR Codes to act on, as an alternative to `qr_id_list`.
A search term selecting which QR Codes to act on, as an alternative to `qr_id_list`. `"all"` selects every owned QR Code.
An existing bulk operation's `operation_id` — act on the QR Codes that operation generated.
Required when `operation` is `apply_design` — the design pattern data to apply.
Required when `operation` is `organize` — the destination folder id, which must belong to this account.
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/bulk-operation/quick-action/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"operation": "organize",
"qr_id_list": ["a1b2c3d4", "e5f6g7h8"],
"folder": 12
}'
```
```json 200 theme={null}
{
"operation_id": "bo-qa-001",
"message": ["Your request is being processed. We'll email you once it's ready."]
}
```
Asynchronous — poll [List bulk operations](/api-reference/management-api/bulk-operation/list) for `status`/`progress`. `400` for an invalid `operation` value, or a missing `folder`/`pattern_info` for the chosen operation.
`delete` is irreversible for QR Codes that don't support soft-delete recovery in your plan — confirm the target selection before calling with `operation: "delete"`.
## Related
* [Bulk-download QR Codes](/api-reference/management-api/bulk-operation/download) — the same targeting parameters, for downloading instead of acting.
* [Export owned URL QR Codes as CSV](/api-reference/management-api/bulk-operation/qr-list) — a quick source of ids for `qr_id_list`.
* [List bulk operations](/api-reference/management-api/bulk-operation/list) — poll for completion.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Rename a bulk operation
Source: https://docs.scanova.io/api-reference/management-api/bulk-operation/rename
api-reference/openapi/management-api.json PATCH /bulk-operation/{operation_id}/rename/
PATCH /bulk-operation/{operation_id}/rename/
Renames a bulk operation, on `api.scanova.io`, authenticated with your raw API key.
```
PATCH https://api.scanova.io/bulk-operation/{operation_id}/rename/
Authorization: YOUR_API_KEY
```
The operation's `operation_id`, from [List bulk operations](/api-reference/management-api/bulk-operation/list).
The new name.
```bash cURL theme={null}
curl -X PATCH "https://api.scanova.io/bulk-operation/bo-8f3a1c/rename/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "September event QR batch (final)" }'
```
```json 200 theme={null}
{
"operation_id": "bo-8f3a1c",
"name": "September event QR batch (final)"
}
```
Unlike the generate/update/download/quick-action endpoints, renaming deliberately requires no `BULK_OPERATION` quota or plan permission beyond authentication — so an operation can still be renamed even after the account's bulk operation quota is exhausted. `404` if `operation_id` isn't an operation owned by this account.
## Related
* [List bulk operations](/api-reference/management-api/bulk-operation/list) — find the `operation_id`.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Get a bulk operation's result-file settings
Source: https://docs.scanova.io/api-reference/management-api/bulk-operation/result-format-retrieve
api-reference/openapi/management-api.json GET /bulk-operation/download/format/{operation_id}/
GET /bulk-operation/download/format/{operation_id}/
Gets a bulk operation's result-file settings, on `api.scanova.io`, authenticated with your raw API key.
```
GET https://api.scanova.io/bulk-operation/download/format/{operation_id}/
Authorization: YOUR_API_KEY
```
The operation's `operation_id`, from [List bulk operations](/api-reference/management-api/bulk-operation/list).
```bash cURL theme={null}
curl "https://api.scanova.io/bulk-operation/download/format/bo-8f3a1c/" \
-H "Authorization: YOUR_API_KEY"
```
```json 200 theme={null}
{
"operation_id": "bo-8f3a1c",
"result_file_format": "xlsx",
"result_file_size": "500",
"folder": null
}
```
`404` if `operation_id` isn't an operation owned by this account.
## Related
* [Regenerate a bulk operation's result file](/api-reference/management-api/bulk-operation/result-format-update) — change the format and re-trigger generation.
* [List bulk operations](/api-reference/management-api/bulk-operation/list) — find the `operation_id` and check overall status.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Regenerate a bulk operation's result file
Source: https://docs.scanova.io/api-reference/management-api/bulk-operation/result-format-update
api-reference/openapi/management-api.json PATCH /bulk-operation/download/format/{operation_id}/
PATCH /bulk-operation/download/format/{operation_id}/
Changes a bulk operation's result-file format (or destination folder) and re-triggers generation, on `api.scanova.io`, authenticated with your raw API key.
```
PATCH https://api.scanova.io/bulk-operation/download/format/{operation_id}/
Authorization: YOUR_API_KEY
```
The operation's `operation_id`.
The file format to regenerate the result in, e.g. `csv`, `xlsx`, `zip`.
A folder id to save the regenerated result into.
```bash cURL theme={null}
curl -X PATCH "https://api.scanova.io/bulk-operation/download/format/bo-8f3a1c/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "result_file_format": "csv" }'
```
```json 200 theme={null}
{
"operation_id": "bo-8f3a1c",
"result_file_format": "csv",
"result_file_size": "500",
"folder": null
}
```
This kicks off an asynchronous regeneration — the response confirms the request was accepted, not that the new file is ready yet. Poll [List bulk operations](/api-reference/management-api/bulk-operation/list) and watch `result_file` for the updated download URL. `404` if `operation_id` isn't an operation owned by this account.
## Related
* [Get a bulk operation's result-file settings](/api-reference/management-api/bulk-operation/result-format-retrieve) — check current settings before changing them.
* [List bulk operations](/api-reference/management-api/bulk-operation/list) — watch for the regenerated file.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Download a sample file
Source: https://docs.scanova.io/api-reference/management-api/bulk-operation/sample-file
api-reference/openapi/management-api.json GET /bulk-operation/sample/{slug}/
GET /bulk-operation/sample/{slug}/
Downloads a sample sheet for a QR category, on `api.scanova.io`, authenticated with your raw API key.
```
GET https://api.scanova.io/bulk-operation/sample/{slug}/
Authorization: YOUR_API_KEY
```
The QR category slug, e.g. `vcard`.
For any category except `url`, build the sample from this QR Code's own field values instead of the generic category template — useful when you want a fill-in sheet shaped like an existing QR Code.
```bash cURL theme={null}
curl "https://api.scanova.io/bulk-operation/sample/vcard/" \
-H "Authorization: YOUR_API_KEY" \
-o vcard-sample.csv
```
The response is a CSV or XLSX file download, not JSON — its columns match what [Bulk-generate QR Codes](/api-reference/management-api/bulk-operation/generate) expects for that category.
## Related
* [Bulk-generate QR Codes](/api-reference/management-api/bulk-operation/generate) — the endpoint this sample sheet feeds.
* [Validate a bulk operation file](/api-reference/management-api/bulk-operation/validate) — check your filled-in sheet before generating.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Get bulk operation stats
Source: https://docs.scanova.io/api-reference/management-api/bulk-operation/stats
api-reference/openapi/management-api.json GET /bulk-operation/stats/
GET /bulk-operation/stats/
Gets aggregate counts across the account's bulk operations, on `api.scanova.io`, authenticated with your raw API key.
```
GET https://api.scanova.io/bulk-operation/stats/
Authorization: YOUR_API_KEY
```
```bash cURL theme={null}
curl "https://api.scanova.io/bulk-operation/stats/" \
-H "Authorization: YOUR_API_KEY"
```
```json 200 theme={null}
{
"total": 12,
"in_progress": 1,
"completed": 10,
"failed": 1
}
```
Scope matches [List bulk operations](/api-reference/management-api/bulk-operation/list): the account's own operations, plus (for team accounts) operations created by shared users, unless called via account impersonation.
## Related
* [List bulk operations](/api-reference/management-api/bulk-operation/list) — see the underlying records these counts summarize.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Bulk-update QR Code content
Source: https://docs.scanova.io/api-reference/management-api/bulk-operation/update-content
api-reference/openapi/management-api.json POST /bulk-operation/update/
POST /bulk-operation/update/
Updates the destination content (and optionally design) of many QR Codes from an uploaded sheet, asynchronously, on `api.scanova.io`, authenticated with your raw API key.
```
POST https://api.scanova.io/bulk-operation/update/
Authorization: YOUR_API_KEY
```
A label for this operation, shown in [List bulk operations](/api-reference/management-api/bulk-operation/list).
The filled-in sheet. Must have exactly the columns `qr code id`, `name`, `url` — see [Download a bulk-update sample sheet](/api-reference/management-api/bulk-operation/update-sheet).
Design pattern data applied to the updated QR Codes, typically JSON encoded as a string, validated against the QR category's design schema.
A folder id to move the updated QR Codes into.
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/bulk-operation/update/" \
-H "Authorization: YOUR_API_KEY" \
-F "name=October redirect update" \
-F "file=@url-updates.csv"
```
```json 200 theme={null}
{
"operation_id": "bo-upd-002",
"message": ["Your file is being processed. We'll email you once it's ready."]
}
```
Asynchronous — poll [List bulk operations](/api-reference/management-api/bulk-operation/list) for `status`/`progress`. `400` if the file doesn't have exactly the required three columns, or row data is invalid. `403` if the account lacks the `BULK_OPERATION` quota, has an inactive plan, or lacks `BULK_OPERATION_CAN_UPDATE` permission.
## Related
* [Download a bulk-update sample sheet](/api-reference/management-api/bulk-operation/update-sheet) — where this file comes from.
* [Bulk-update QR name/tags](/api-reference/management-api/bulk-operation/update-details) — the counterpart for updating name/tags instead.
* [List bulk operations](/api-reference/management-api/bulk-operation/list) — poll for completion.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Bulk-update QR name/tags
Source: https://docs.scanova.io/api-reference/management-api/bulk-operation/update-details
api-reference/openapi/management-api.json POST /bulk-operation/update-details/
POST /bulk-operation/update-details/
Updates the name and tags on many QR Codes from an uploaded sheet, asynchronously, on `api.scanova.io`, authenticated with your raw API key.
```
POST https://api.scanova.io/bulk-operation/update-details/
Authorization: YOUR_API_KEY
```
A label for this operation, shown in [List bulk operations](/api-reference/management-api/bulk-operation/list).
The filled-in sheet. Must have exactly the columns `qr code id`, `name`, `tags` — see [Download a bulk-update sample sheet](/api-reference/management-api/bulk-operation/update-sheet) (`?type=update_details`).
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/bulk-operation/update-details/" \
-H "Authorization: YOUR_API_KEY" \
-F "name=October tag cleanup" \
-F "file=@tag-updates.csv"
```
```json 200 theme={null}
{
"operation_id": "bo-upd-001",
"message": ["Your file is being processed. We'll email you once it's ready."]
}
```
Asynchronous — poll [List bulk operations](/api-reference/management-api/bulk-operation/list) for `status`/`progress`. `400` if the file doesn't have exactly the required three columns, or row data is invalid. `403` if the account lacks the `BULK_OPERATION` quota, has an inactive plan, or lacks `BULK_OPERATION_CAN_UPDATE` permission.
## Related
* [Download a bulk-update sample sheet](/api-reference/management-api/bulk-operation/update-sheet) — where this file comes from.
* [Bulk-update QR Code content](/api-reference/management-api/bulk-operation/update-content) — the counterpart for updating destination content instead.
* [List bulk operations](/api-reference/management-api/bulk-operation/list) — poll for completion.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Download a bulk-update sample sheet
Source: https://docs.scanova.io/api-reference/management-api/bulk-operation/update-sheet
api-reference/openapi/management-api.json POST /bulk-operation/update/sheet/
POST /bulk-operation/update/sheet/
Builds and downloads a prefilled sheet for a set of the account's own QR Codes, on `api.scanova.io`, authenticated with your raw API key.
```
POST https://api.scanova.io/bulk-operation/update/sheet/
Authorization: YOUR_API_KEY
```
`update_details` selects the name/tags column set (see [Bulk-update QR name/tags](/api-reference/management-api/bulk-operation/update-details)); omit for the content-update column set (see [Bulk-update QR Code content](/api-reference/management-api/bulk-operation/update-content)).
Specific QR ids to include. Alternatively use `filter` or `search` (pass `"all"` to select every owned QR Code).
A saved filter selecting which QR Codes to include, as an alternative to `qr_id_list`.
A search term selecting which QR Codes to include, as an alternative to `qr_id_list`. `"all"` selects every owned QR Code.
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/bulk-operation/update/sheet/?type=update_details" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "qr_id_list": ["a1b2c3d4", "e5f6g7h8"] }' \
-o update-details-sample.csv
```
This runs synchronously and returns the file directly — unlike the generate/update/download endpoints, there's no `operation_id` to poll. Rows are prefilled with each QR Code's current values, so you only need to edit the cells you want to change.
## Related
* [Bulk-update QR name/tags](/api-reference/management-api/bulk-operation/update-details) — where a `?type=update_details` sheet is submitted.
* [Bulk-update QR Code content](/api-reference/management-api/bulk-operation/update-content) — where the default sheet is submitted.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Validate a bulk operation file
Source: https://docs.scanova.io/api-reference/management-api/bulk-operation/validate
api-reference/openapi/management-api.json POST /bulk-operation/validate/
POST /bulk-operation/validate/
Dry-run validates an uploaded sheet against a category's schema, on `api.scanova.io`, authenticated with your raw API key.
```
POST https://api.scanova.io/bulk-operation/validate/
Authorization: YOUR_API_KEY
```
The sheet to validate (CSV or XLSX), typically a filled-in copy of a [sample file](/api-reference/management-api/bulk-operation/sample-file).
The QR category slug the sheet is meant for, max 100 characters.
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/bulk-operation/validate/" \
-H "Authorization: YOUR_API_KEY" \
-F "category=vcard" \
-F "file=@vcard-batch.csv"
```
```json 200 theme={null}
{
"is_valid": true,
"validation_errors": [],
"validation_warnings": [],
"file_info": { "rows": 500, "columns": ["qr code id", "name", "phone"] },
"message": "File is valid."
}
```
Nothing is created by this call — no `BulkOperation` record, no QR Codes. Use it to surface column/row errors before spending a [Bulk-generate QR Codes](/api-reference/management-api/bulk-operation/generate) call. `200` with `is_valid: true` on success; `400` with `is_valid: false` and populated `validation_errors` otherwise.
## Related
* [Download a sample file](/api-reference/management-api/bulk-operation/sample-file) — the template this sheet should be filled from.
* [Bulk-generate QR Codes](/api-reference/management-api/bulk-operation/generate) — the real run, once validation passes.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Create an SSL certificate request
Source: https://docs.scanova.io/api-reference/management-api/custom-domain/certificate-request-create
api-reference/openapi/management-api.json POST /custom-domain/{pk}/certificate-request/
POST /custom-domain/{pk}/certificate-request/
Generates a new SSL certificate signing request (CSR) for a domain, on `api.scanova.io`, authenticated with your raw API key.
```
POST https://api.scanova.io/custom-domain/{pk}/certificate-request/
Authorization: YOUR_API_KEY
```
Enterprise-only. Requires the `CUSTOM_DOMAIN_CUSTOM_SSL` quota, which is granted manually — no self-serve plan includes it.
The domain's `id`.
Organization name for the certificate subject.
Organizational unit for the certificate subject.
2-letter ISO country code — uppercased automatically; rejected if it isn't exactly 2 alphabetic characters.
State or province.
City or locality.
Contact email for the certificate subject.
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/custom-domain/42/certificate-request/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"organization": "Example Inc",
"organizational_unit": "IT",
"country": "us",
"state": "CA",
"locality": "San Francisco",
"email": "ssl@example.com"
}'
```
```json 201 theme={null}
{
"id": 5,
"organization": "Example Inc",
"organizational_unit": "IT",
"country": "US",
"state": "CA",
"locality": "San Francisco",
"email": "ssl@example.com",
"csr": "-----BEGIN CERTIFICATE REQUEST-----\nMIIC...\n-----END CERTIFICATE REQUEST-----",
"created": "2026-09-08T09:00:00Z"
}
```
A private key and the CSR are generated server-side — the private key is never returned by any endpoint or accepted as input, and only the CSR text comes back. Submit the returned `csr` to your certificate authority, then bring the issued certificate back via [Upload an SSL certificate](/api-reference/management-api/custom-domain/ssl-certificate-upload).
`400` if `country` isn't exactly 2 alphabetic characters. `403` if the account lacks the `CUSTOM_DOMAIN_CUSTOM_SSL` quota.
## Related
* [Get the latest SSL certificate request](/api-reference/management-api/custom-domain/certificate-request-retrieve) — resume an in-progress flow.
* [Upload an SSL certificate](/api-reference/management-api/custom-domain/ssl-certificate-upload) — the next step once your CA issues the certificate.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Remove a custom domain
Source: https://docs.scanova.io/api-reference/management-api/custom-domain/delete
api-reference/openapi/management-api.json DELETE /custom-domain/{pk}/
DELETE /custom-domain/{pk}/
Disconnects a custom domain from the account, on `api.scanova.io`, authenticated with your raw API key.
```
DELETE https://api.scanova.io/custom-domain/{pk}/
Authorization: YOUR_API_KEY
```
Requires the `CUSTOM_DOMAIN_CAN_DELETE` permission.
The domain's `id`.
```bash cURL theme={null}
curl -X DELETE "https://api.scanova.io/custom-domain/42/" \
-H "Authorization: YOUR_API_KEY"
```
A successful call returns `204 No Content`.
## QR Codes must be unlinked first
If the domain is still linked to any QR Code — including trashed ones — this returns `400` with `{"error": ["This domain is linked to QR Code(s)... Delete permanently the QR Code(s) first..."]}` instead of removing it. Permanently delete (not just trash) the linked QR Codes before retrying. `404` if `pk` isn't a domain owned by this account.
## Related
* [Retrieve a custom domain](/api-reference/management-api/custom-domain/retrieve) — check for linked QR Codes before deleting.
* [List custom domains](/api-reference/management-api/custom-domain/list) — see what else is connected.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Retrieve a custom domain
Source: https://docs.scanova.io/api-reference/management-api/custom-domain/retrieve
api-reference/openapi/management-api.json GET /custom-domain/{pk}/
GET /custom-domain/{pk}/
Retrieves one custom domain, on `api.scanova.io`, authenticated with your raw API key.
```
GET https://api.scanova.io/custom-domain/{pk}/
Authorization: YOUR_API_KEY
```
The domain's `id`, from [List custom domains](/api-reference/management-api/custom-domain/list).
```bash cURL theme={null}
curl "https://api.scanova.io/custom-domain/42/" \
-H "Authorization: YOUR_API_KEY"
```
```json 200 theme={null}
{
"id": 42,
"domain": "https://links.example.com",
"is_default": false,
"disable_slug_case": false,
"txt_value": "scanova-verification=abc123...",
"is_txt_verified": true,
"is_cname_verified": true,
"is_migrated": false,
"dns_provider": { "name": "Cloudflare", "placeholders": { "cname": { "content": { "label": "Target" } } } },
"txt_error": null,
"cname_error": null,
"root_action": "no_action",
"redirect": null,
"template": null,
"protocol": "https",
"remove_qr_query": false,
"cname_target": "shortcname.scanova.io",
"domain_connect_supported": false,
"domain_connect_checked_at": null,
"created": "2026-09-08T10:00:00Z",
"modified": "2026-09-08T10:00:00Z"
}
```
`404` if `pk` isn't a domain owned by this account.
## Related
* [List custom domains](/api-reference/management-api/custom-domain/list) — find the `id`.
* [Update a custom domain](/api-reference/management-api/custom-domain/update) — change its root-page behavior or protocol.
* [Remove a custom domain](/api-reference/management-api/custom-domain/delete) — disconnect it.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Get SSL certificate status
Source: https://docs.scanova.io/api-reference/management-api/custom-domain/ssl-certificate-retrieve
api-reference/openapi/management-api.json GET /custom-domain/{pk}/ssl-certificate/
GET /custom-domain/{pk}/ssl-certificate/
Gets the SSL certificate status for a domain, on `api.scanova.io`, authenticated with your raw API key.
```
GET https://api.scanova.io/custom-domain/{pk}/ssl-certificate/
Authorization: YOUR_API_KEY
```
Enterprise-only. Requires the `CUSTOM_DOMAIN_CUSTOM_SSL` quota, which is granted manually — no self-serve plan includes it.
The domain's `id`.
```bash cURL theme={null}
curl "https://api.scanova.io/custom-domain/42/ssl-certificate/" \
-H "Authorization: YOUR_API_KEY"
```
```json 200 theme={null}
{
"status": "ISSUED",
"expiry_status": "healthy",
"days_until_expiry": 82,
"issued_at": "2026-08-01T00:00:00Z",
"expires_at": "2026-11-01T00:00:00Z"
}
```
If no certificate has been uploaded yet, `status` and `expiry_status` come back `null`. `403` if the account lacks the `CUSTOM_DOMAIN_CUSTOM_SSL` quota; `404` if `pk` isn't a domain owned by this account.
## Related
* [Upload an SSL certificate](/api-reference/management-api/custom-domain/ssl-certificate-upload) — activate a certificate for this domain.
* [Create an SSL certificate request](/api-reference/management-api/custom-domain/certificate-request-create) — the CSR a certificate here must match.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Upload an SSL certificate
Source: https://docs.scanova.io/api-reference/management-api/custom-domain/ssl-certificate-upload
api-reference/openapi/management-api.json POST /custom-domain/{pk}/ssl-certificate/
POST /custom-domain/{pk}/ssl-certificate/
Uploads an issued SSL certificate for a domain and activates it, on `api.scanova.io`, authenticated with your raw API key.
```
POST https://api.scanova.io/custom-domain/{pk}/ssl-certificate/
Authorization: YOUR_API_KEY
```
Enterprise-only. Requires the `CUSTOM_DOMAIN_CUSTOM_SSL` quota, which is granted manually — no self-serve plan includes it. Requires a prior CSR — see [Create an SSL certificate request](/api-reference/management-api/custom-domain/certificate-request-create).
The domain's `id`.
The issued certificate, PEM-encoded.
The certificate authority's intermediate chain, PEM-encoded, if your CA provides one separately.
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/custom-domain/42/ssl-certificate/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"certificate": "-----BEGIN CERTIFICATE-----\nMIID...\n-----END CERTIFICATE-----",
"certificate_chain": "-----BEGIN CERTIFICATE-----\nMIID...\n-----END CERTIFICATE-----"
}'
```
```json 200 theme={null}
{
"detail": "Certificate activated.",
"status": "ISSUED",
"expires_at": "2026-12-08T00:00:00Z"
}
```
Scanova validates the certificate against the domain and the CSR's key, imports it into AWS ACM, and links it to serve live traffic — this is fully automated, with no manual review step. Three outcomes are possible:
* **`200`** — validated, imported, and activated. Traffic on this domain now serves with the new certificate.
* **`202`** — imported into ACM but not yet issued; not linked to serve traffic yet. Check back shortly.
* **`207` Multi-Status** — issued, but linking it to serve traffic failed; Scanova's support team is notified automatically and will follow up.
`400` if there's no CSR for this domain yet, the certificate doesn't match the domain or the CSR's key, or AWS ACM rejects the import (the error message is included in the response).
## Related
* [Get SSL certificate status](/api-reference/management-api/custom-domain/ssl-certificate-retrieve) — check status after uploading.
* [Create an SSL certificate request](/api-reference/management-api/custom-domain/certificate-request-create) — the CSR required before uploading.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Update a custom domain
Source: https://docs.scanova.io/api-reference/management-api/custom-domain/update
api-reference/openapi/management-api.json PATCH /custom-domain/{pk}/
PATCH /custom-domain/{pk}/
Updates a custom domain's configuration, on `api.scanova.io`, authenticated with your raw API key.
```
PUT https://api.scanova.io/custom-domain/{pk}/
PATCH https://api.scanova.io/custom-domain/{pk}/
Authorization: YOUR_API_KEY
```
The domain's `id`.
Whether new QR Codes use this domain by default.
What to do when someone visits the bare domain (no QR slug): `no_action` (default), `url_redirect`, or `custom_page`.
The URL to send visitors to when `root_action` is `url_redirect`.
A `CustomDomainTemplate` id to serve as the root page when `root_action` is `custom_page` — see [List custom domain root-page templates](/api-reference/management-api/custom-domain/templates-list).
`none`, `http`, or `https`. Blank defaults to `https`.
Strips the `?qr=1` tracking parameter from short-link URLs when set.
```bash cURL theme={null}
curl -X PATCH "https://api.scanova.io/custom-domain/42/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"root_action": "url_redirect",
"redirect": "https://example.com/landing"
}'
```
```json 200 theme={null}
{
"id": 42,
"domain": "https://links.example.com",
"is_default": false,
"disable_slug_case": false,
"txt_value": "scanova-verification=abc123...",
"is_txt_verified": true,
"is_cname_verified": true,
"is_migrated": false,
"dns_provider": { "name": "Cloudflare", "placeholders": { "cname": { "content": { "label": "Target" } } } },
"txt_error": null,
"cname_error": null,
"root_action": "url_redirect",
"redirect": "https://example.com/landing",
"template": null,
"protocol": "https",
"remove_qr_query": false,
"cname_target": "shortcname.scanova.io",
"domain_connect_supported": false,
"domain_connect_checked_at": null,
"created": "2026-09-08T10:00:00Z",
"modified": "2026-09-08T10:00:00Z"
}
```
## Most fields are read-only here
Only `is_default`, `root_action`, `redirect`, `template`, `protocol`, and `remove_qr_query` can be written through this endpoint — every other field, including `domain` itself, is read-only once the domain is created. `404` if `pk` isn't a domain owned by this account.
## Related
* [Retrieve a custom domain](/api-reference/management-api/custom-domain/retrieve) — view before updating.
* [List custom domain root-page templates](/api-reference/management-api/custom-domain/templates-list) — find a `template` id for `custom_page`.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Move QR Codes into a folder
Source: https://docs.scanova.io/api-reference/management-api/folders/bulk-move
api-reference/openapi/management-api.json POST /folder/{id}/bulk_move/
POST /folder/{id}/bulk_move/
Moves one or more QR Codes into a folder — either an explicit list of QR Codes, or every QR Code matching a filter.
Requires the account's `FOLDER_MANAGEMENT` quota and the `FOLDER_CAN_CREATE_AND_MANAGE` permission. See the [API overview](/api-reference/management-api/overview) for authentication details.
Target folder ID.
Either a list of `qrid` strings, or the single-element sentinel `["all"]` to move every QR Code matching `filter`/`search` below. Sending anything other than an array returns `400` with `{"error": "qr_code_ids must be a list"}`. An empty list is a no-op: `200` with `moved_count: 0`.
Only meaningful alongside `qr_code_ids: ["all"]`. A folder ID restricts the source to QR Codes currently in that folder; the literal string `"uncategorized"` restricts it to QR Codes with no folder at all.
Omitting `from` entirely is **not** the same as `"uncategorized"` — it means "no source restriction, consider every QR Code regardless of its current folder." Confusing the two is an easy mistake if you're building a "move all" UI around the account's full QR list.
Only honored alongside `qr_code_ids: ["all"]`. Accepts the same filter keys as [List QR Codes](/api-reference/management-api/overview) (`category`, `tags`, `type`, `status`, `qrid`, `users`, `no_user`, `created_from`, `created_till`, `scan_type`, `scan_count1`, `scan_count2`). A `folder` key here is ignored even if sent — use `from` instead to scope by source folder.
Only honored alongside `qr_code_ids: ["all"]` — `{"search_value": "..."}`, matched the same way the main QR Code search does.
Only QR Codes you own are eligible, and a soft-deleted (trashed) QR Code is always excluded — passing a trashed `qrid` is silently a no-op. If the target folder's `folder_type` is `page`, only page-builder QR Codes are moved (and vice versa for `qr` folders); an `["all"]` move against the wrong `folder_type` silently skips the ineligible QR Codes rather than erroring. QR Codes already in the target folder don't count toward `moved_count`.
```bash cURL Move specific QR codes theme={null}
curl --request POST \
--url 'https://api.scanova.io/folder/42/bulk_move/' \
--header 'Authorization: YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"qr_code_ids": ["qr_8f2a1c", "qr_9b3d2e"]
}'
```
```bash cURL Move everything Uncategorized theme={null}
curl --request POST \
--url 'https://api.scanova.io/folder/42/bulk_move/' \
--header 'Authorization: YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"qr_code_ids": ["all"],
"from": "uncategorized"
}'
```
```json 200 theme={null}
{
"message": "Successfully moved 2 QR codes to folder 'Marketing Campaigns'",
"moved_count": 2
}
```
`404` if the target folder or `from` (when a real folder ID) isn't owned by this account.
## Related
* [Unassign QR Codes from a folder](/api-reference/management-api/folders/bulk-unassign) — the inverse operation.
* [List QR Codes in a folder](/api-reference/management-api/folders/qr-codes) — confirm the result.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota architecture this endpoint is part of.
# Unassign QR Codes from a folder
Source: https://docs.scanova.io/api-reference/management-api/folders/bulk-unassign
api-reference/openapi/management-api.json POST /folder/{id}/bulk_unassign/
POST /folder/{id}/bulk_unassign/
Removes one or more QR Codes from a folder, moving them to "Uncategorized" — it never deletes or trashes the QR Code itself.
Requires the account's `FOLDER_MANAGEMENT` quota and the `FOLDER_CAN_CREATE_AND_MANAGE` permission. See the [API overview](/api-reference/management-api/overview) for authentication details.
Folder ID to unassign QR Codes from. Unlike [Move QR Codes into a folder](/api-reference/management-api/folders/bulk-move), there is no separate `from` field — the source is always this folder.
Either a list of `qrid` strings currently in this folder, or the single-element sentinel `["all"]` to unassign every matching QR Code (optionally narrowed by `filter`/`search` below).
Only honored alongside `qr_code_ids: ["all"]`. Same whitelisted keys as [Move QR Codes into a folder](/api-reference/management-api/folders/bulk-move).
Only honored alongside `qr_code_ids: ["all"]` — `{"search_value": "..."}`.
Only QR Codes you own, currently in this folder, and not soft-deleted are eligible. If this folder's `folder_type` is `page`, only page-builder QR Codes are unassigned by an `["all"]` request (and vice versa for `qr` folders).
```bash cURL theme={null}
curl --request POST \
--url 'https://api.scanova.io/folder/42/bulk_unassign/' \
--header 'Authorization: YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"qr_code_ids": ["all"],
"filter": { "tags": "Wholescale" }
}'
```
```json 200 theme={null}
{
"message": "Successfully unassigned 1 QR codes",
"unassigned_count": 1
}
```
`404` if `id` isn't owned by this account.
## Related
* [Move QR Codes into a folder](/api-reference/management-api/folders/bulk-move) — the inverse operation.
* [List QR Codes in a folder](/api-reference/management-api/folders/qr-codes) — confirm the result.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota architecture this endpoint is part of.
# Create a folder
Source: https://docs.scanova.io/api-reference/management-api/folders/create
api-reference/openapi/management-api.json POST /folder/
POST /folder/
Folders group QR Codes for organization — the same folders shown in the dashboard's **QR Codes > Folders** sidebar. Use this endpoint to list an account's folders or create a new one from your own systems.
Both operations require an API key with `MANAGEMENT_API` (or `MANAGEMENT_API_SANDBOX`) quota, sent as the raw `Authorization` header value. See the [API overview](/api-reference/management-api/overview) for authentication details.
```bash cURL theme={null}
curl --request POST \
--url 'https://api.scanova.io/folder/' \
--header 'Authorization: YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"name": "Holiday Campaign",
"color": "#22C55E"
}'
```
```json 201 theme={null}
{
"id": 42,
"name": "Holiday Campaign",
"color": "#22C55E",
"created_by": 1029,
"folder_type": "qr",
"qr_codes_count": 0,
"created": "2026-08-16T20:40:02.115000+05:30",
"updated": "2026-08-16T20:40:02.115000+05:30"
}
```
Folder name, up to 150 characters. Must be unique for this account within the given `folder_type` — a duplicate returns a 400 with `{"name": ["Folder name already exists."]}`.
Hex color, e.g. `#4F6BF4`.
`qr` or `page`.
Creating a folder requires the account's `FOLDER_MANAGEMENT` quota (folder-management is a paid capability on some plans) — a 403 with `"Your plan does not have folder management quota."` means the plan doesn't include it. Listing folders has no such gate.
## Related
* [List folders](/api-reference/management-api/folders/list) — the other operation on this same endpoint.
* [Update a folder](/api-reference/management-api/folders/update) — rename or recolor an existing one.
* [Move QR Codes into a folder](/api-reference/management-api/folders/bulk-move) — assign QR Codes to the folder you just created.
* [Tag management](/api-reference/management-api/tags/manage) — the other QR-organization mechanism, used alongside (or instead of) folders.
* [List / create QR Codes](/api-reference/management-api/qr/list) — filter QR Codes by a folder ID returned here via `?folder=`.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota architecture this endpoint is part of.
# Delete a folder
Source: https://docs.scanova.io/api-reference/management-api/folders/delete
api-reference/openapi/management-api.json DELETE /folder/{id}/
DELETE /folder/{id}/
Deletes a folder. A query parameter controls what happens to the QR Codes inside it — there is no default "safe" behavior beyond leaving those three modes explicit, so choose one deliberately.
Requires the account's `FOLDER_MANAGEMENT` quota and the `FOLDER_CAN_CREATE_AND_MANAGE` permission. See the [API overview](/api-reference/management-api/overview) for authentication details.
Folder ID.
If `true`, the folder is deleted and its QR Codes are unassigned (moved to "Uncategorized") rather than deleted.
If `true`, every QR Code in the folder **owned by your account** is permanently deleted along with the folder. QR Codes in the same folder that belong to a different user on a shared account are not deleted — only their folder assignment is cleared.
### Delete modes
| Query string | Behavior | Response body |
| - | - | - |
| *(none)* | Folder is deleted; its QR Codes' folder assignment is cleared. | `204`, empty. |
| `?move_to_uncategorized=true` | Same as above, worded explicitly. | `204` with `{"message": "Folder deleted and QR codes moved to Uncategorized."}` |
| `?delete_permanently=true` | Folder **and** its QR Codes (owned by you) are permanently deleted. | `204` with `{"message": "Folder and associated QR codes deleted."}` |
A `204` response normally has no body — the two modes above that pass a query parameter are an exception and do return a JSON body despite the `204` status. Some HTTP clients silently discard a `204` body; don't rely on it if you can't confirm yours reads it. If you send both `move_to_uncategorized=true` and `delete_permanently=true` at once, `move_to_uncategorized` wins.
```bash cURL Delete only theme={null}
curl --request DELETE \
--url 'https://api.scanova.io/folder/42/' \
--header 'Authorization: YOUR_API_KEY'
```
```bash cURL Move QR codes to Uncategorized theme={null}
curl --request DELETE \
--url 'https://api.scanova.io/folder/42/?move_to_uncategorized=true' \
--header 'Authorization: YOUR_API_KEY'
```
```bash cURL Delete QR codes permanently theme={null}
curl --request DELETE \
--url 'https://api.scanova.io/folder/42/?delete_permanently=true' \
--header 'Authorization: YOUR_API_KEY'
```
`404` if `id` isn't owned by this account.
## Related
* [Retrieve a folder](/api-reference/management-api/folders/retrieve) — check what's in it first.
* [List QR Codes in a folder](/api-reference/management-api/folders/qr-codes) — review contents before an irreversible `delete_permanently`.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota architecture this endpoint is part of.
# List folders
Source: https://docs.scanova.io/api-reference/management-api/folders/list
api-reference/openapi/management-api.json GET /folder/
GET /folder/
Folders group QR Codes for organization — the same folders shown in the dashboard's **QR Codes > Folders** sidebar. Use this endpoint to list an account's folders or create a new one from your own systems.
Requires an API key, sent as the raw `Authorization` header value — see the [API overview](/api-reference/management-api/overview) for authentication details. Listing folders has no additional quota/permission gate (reads are never restricted); only creating, renaming, or deleting a folder requires the account's `FOLDER_MANAGEMENT` quota.
```bash cURL theme={null}
curl --request GET \
--url 'https://api.scanova.io/folder/' \
--header 'Authorization: YOUR_API_KEY'
```
```json 200 theme={null}
{
"count": 2,
"next": null,
"previous": null,
"results": [
{
"id": 41,
"name": "Product Launch",
"color": "#4F6BF4",
"created_by": 1029,
"folder_type": "qr",
"qr_codes_count": 12,
"created": "2026-07-01T09:12:04.221000+05:30",
"updated": "2026-07-14T16:03:11.884000+05:30"
},
{
"id": 40,
"name": "Retail Campaign",
"color": null,
"created_by": 1029,
"folder_type": "qr",
"qr_codes_count": 0,
"created": "2026-06-18T11:45:32.009000+05:30",
"updated": "2026-06-18T11:45:32.009000+05:30"
}
]
}
```
Results are paginated (page size 10 by default, up to 100 via `page_size`) and ordered newest first.
Page number to retrieve.
Number of folders per page. Maximum 100.
Filter to `qr` (standard QR Code folders) or `page` (folders scoped to page-builder pages).
Case-insensitive substring match against the folder name.
### Response fields
Folder ID — pass this as the `folder` value when filtering QR Code analytics by folder, or when moving QR Codes into the folder from the dashboard.
Folder name. Unique per account and `folder_type`.
Hex color (e.g. `#4F6BF4`) shown next to the folder in the sidebar, or `null` if unset.
User ID of whoever created the folder — `null` if that user has since been removed from the account.
`qr` or `page`.
Number of QR Codes currently assigned to this folder.
ISO 8601 creation timestamp.
ISO 8601 timestamp of the last update.
## Related
* [Create a folder](/api-reference/management-api/folders/create) — the other operation on this same endpoint.
* [Retrieve](/api-reference/management-api/folders/retrieve), [update](/api-reference/management-api/folders/update), and [delete](/api-reference/management-api/folders/delete) a single folder.
* [List QR Codes in a folder](/api-reference/management-api/folders/qr-codes) — the QR Codes assigned to a folder returned here.
* [Tag management](/api-reference/management-api/tags/manage) — the other QR-organization mechanism, used alongside (or instead of) folders.
* [List / create QR Codes](/api-reference/management-api/qr/list) — filter QR Codes by a folder ID returned here via `?folder=`.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota architecture this endpoint is part of.
# List QR Codes in a folder
Source: https://docs.scanova.io/api-reference/management-api/folders/qr-codes
api-reference/openapi/management-api.json GET /folder/{id}/qr_codes/
GET /folder/{id}/qr_codes/
Lists the QR Codes currently assigned to a folder, using the same fields and filters as the main QR Code list endpoint.
Requires an API key. Reads have no additional quota/permission gate. See the [API overview](/api-reference/management-api/overview) for authentication details.
Folder ID.
Page number to retrieve.
Number of QR Codes per page. Maximum 100.
```bash cURL theme={null}
curl --request GET \
--url 'https://api.scanova.io/folder/42/qr_codes/' \
--header 'Authorization: YOUR_API_KEY'
```
```json 200 theme={null}
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 501,
"qrid": "qr_8f2a1c",
"name": "Storefront Poster",
"qr_type": "dy",
"qr_type_display": "Dynamic",
"category": { "id": 3, "name": "Website URL", "slug": "url" },
"dynamic_url_object": { "is_page": false },
"thumbnail": null,
"is_active": true,
"folder": { "id": 42, "name": "Marketing Campaigns", "color": "#4F6BF4" },
"created": "2026-09-01T10:05:00+05:30",
"modified": "2026-09-01T10:05:00+05:30",
"tags_list": [],
"is_password_protected": false,
"is_age_restricted": false,
"is_designer": false,
"pattern_type": null,
"created_by": 1029,
"published_at": "2026-09-01T10:05:00+05:30",
"is_next_app_qr": false,
"is_currently_draft": false
}
]
}
```
`folder` in each result is a truncated object (`id`/`name`/`color` only) — fetch [Retrieve a folder](/api-reference/management-api/folders/retrieve) for the full record. `404` if `id` isn't owned by this account — this endpoint doubles as an ownership check and never reveals whether a folder ID belongs to someone else.
## Related
* [Retrieve a folder](/api-reference/management-api/folders/retrieve) — the folder's own metadata.
* [Move QR Codes into a folder](/api-reference/management-api/folders/bulk-move) — assign more QR Codes here.
* [Unassign QR Codes from a folder](/api-reference/management-api/folders/bulk-unassign) — remove QR Codes from here.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota architecture this endpoint is part of.
# Retrieve a folder
Source: https://docs.scanova.io/api-reference/management-api/folders/retrieve
api-reference/openapi/management-api.json GET /folder/{id}/
GET /folder/{id}/
Fetches a single folder by ID.
Requires an API key, sent as the raw `Authorization` header value. Reads have no additional quota/permission gate. See the [API overview](/api-reference/management-api/overview) for authentication details.
Folder ID, from [List folders](/api-reference/management-api/folders/list).
```bash cURL theme={null}
curl --request GET \
--url 'https://api.scanova.io/folder/42/' \
--header 'Authorization: YOUR_API_KEY'
```
```json 200 theme={null}
{
"id": 42,
"name": "Marketing Campaigns",
"color": "#4F6BF4",
"created_by": 1029,
"folder_type": "qr",
"qr_codes_count": 12,
"created": "2026-09-01T10:00:00+05:30",
"updated": "2026-09-05T12:30:00+05:30"
}
```
A folder belonging to a different account returns `404`, not `403` — this endpoint never reveals whether an ID belongs to someone else.
## Related
* [List folders](/api-reference/management-api/folders/list) — find a folder's `id`.
* [Update a folder](/api-reference/management-api/folders/update) — rename or recolor it.
* [Delete a folder](/api-reference/management-api/folders/delete) — remove it.
* [List QR Codes in a folder](/api-reference/management-api/folders/qr-codes) — the QR Codes assigned to it.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota architecture this endpoint is part of.
# Update a folder
Source: https://docs.scanova.io/api-reference/management-api/folders/update
api-reference/openapi/management-api.json PATCH /folder/{id}/
PATCH /folder/{id}/
Renames a folder, changes its color, or changes its `folder_type`. Accepts `PUT` as well as `PATCH` — send only the fields you want to change.
Requires the account's `FOLDER_MANAGEMENT` quota and the `FOLDER_CAN_CREATE_AND_MANAGE` permission (the same gate as [Create a folder](/api-reference/management-api/folders/create), checked for quota *existence* only — not the remaining-count check a create runs). See the [API overview](/api-reference/management-api/overview) for authentication details.
Folder ID.
New name, up to 150 characters. Must stay unique for this account within the folder's `folder_type` — renaming to a name already used by another folder of the *same* `folder_type` returns `400` with `{"name": ["Folder name already exists."]}`. A `qr` folder and a `page` folder may share a name.
Hex color, e.g. `#4F6BF4`, or `null` to clear it.
`qr` or `page`.
```bash cURL theme={null}
curl --request PATCH \
--url 'https://api.scanova.io/folder/42/' \
--header 'Authorization: YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"name": "Marketing Campaigns 2026"
}'
```
```json 200 theme={null}
{
"id": 42,
"name": "Marketing Campaigns 2026",
"color": "#4F6BF4",
"created_by": 1029,
"folder_type": "qr",
"qr_codes_count": 12,
"created": "2026-09-01T10:00:00+05:30",
"updated": "2026-09-09T08:00:00+05:30"
}
```
`404` if `id` isn't owned by this account.
## Related
* [Retrieve a folder](/api-reference/management-api/folders/retrieve) — view before updating.
* [Delete a folder](/api-reference/management-api/folders/delete) — remove it instead.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota architecture this endpoint is part of.
# List active lead lists
Source: https://docs.scanova.io/api-reference/management-api/leads/active-list
api-reference/openapi/management-api.json GET /lead/active/
GET /lead/active/
Returns a minimal-shape list of lead lists that are both active and not yet converted to a Form — useful for populating a "select a lead list" picker without the heavier detail fields.
Requires an API key with `MANAGEMENT_API` (or `MANAGEMENT_API_SANDBOX`) quota, sent as the raw `Authorization` header value — see the [API overview](/api-reference/management-api/overview).
```bash cURL theme={null}
curl --request GET \
--url 'https://api.scanova.io/lead/active/' \
--header 'Authorization: YOUR_API_KEY'
```
```json 200 theme={null}
[
{ "id": 205, "lead_id": "L9c2f4a1e", "name": "Trade Show Signup", "created": "2026-08-16T20:44:51.008000+05:30" }
]
```
The response is a flat array, not paginated, and carries only `id`, `lead_id`, `name`, and `created` — for the full detail shape, use [Retrieve a lead list](/api-reference/management-api/leads/retrieve).
## Related
* [List lead lists](/api-reference/management-api/leads/list) — the full paginated list, including inactive lists.
* [Retrieve a lead list](/api-reference/management-api/leads/retrieve) — the full detail shape for one entry here.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota architecture this endpoint is part of.
# Get lead analytics
Source: https://docs.scanova.io/api-reference/management-api/leads/analytics
api-reference/openapi/management-api.json POST /lead/analytics/
POST /lead/analytics/
Returns aggregated analytics (entry counts, conversion breakdown, and traffic source) for one or more lead lists.
Requires an API key with `MANAGEMENT_API` (or `MANAGEMENT_API_SANDBOX`) quota, and the `LEAD_GENERATION_ANALYTICS_CAN_VIEW` permission, sent as the raw `Authorization` header value — see the [API overview](/api-reference/management-api/overview).
An array of strings identifying the lead lists to analyze — either `lead_id`s or QR `url_hash`es, depending on `filter_by`.
`lead_id` (default) or `qr_hash`.
Comma-separated subset of `entries_count`, `leads_conversion`, `source` to compute.
Start date (`YYYY-MM-DD`). Defaults to 7 days before `to`.
End date (`YYYY-MM-DD`). Defaults to today.
```bash cURL theme={null}
curl --request POST \
--url 'https://api.scanova.io/lead/analytics/?type=entries_count,leads_conversion,source&from=2026-08-01&to=2026-08-31' \
--header 'Authorization: YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{ "q": ["L9c2f4a1e"], "filter_by": "lead_id" }'
```
```json 200 theme={null}
{
"entries_count": {
"total": 14,
"elevation": { "percent": 12, "previous": 12, "type": "+", "period": { "start": "2026-07-01", "end": "2026-08-01" } }
},
"leads_conversion": {
"all": [["2026-08-01", 3], ["2026-08-02", 1]],
"leads": [["2026-08-01", 2], ["2026-08-02", 1]],
"skip": [["2026-08-01", 1], ["2026-08-02", 0]]
},
"source": [["Booth QR", 9.0], ["Flyer QR", 5.0]]
}
```
`elevation` is `null` when both the requested period and its prior comparison period have zero entries, to avoid a misleading percentage. `400` under `q` if it's missing or empty — this endpoint's contract is `{"q": [...], "filter_by": "..."}`, not `{"lead_list": [...], "date_from": "...", "date_to": "..."}` from an older, incompatible shape.
## Related
* [Export lead analytics](/api-reference/management-api/leads/analytics-export) — download these same breakdowns as a file.
* [List lead lists](/api-reference/management-api/leads/list) — find `lead_id` values to analyze.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota architecture this endpoint is part of.
# Export lead analytics
Source: https://docs.scanova.io/api-reference/management-api/leads/analytics-export
api-reference/openapi/management-api.json POST /lead/analytics/export/
POST /lead/analytics/export/
Downloads lead analytics (all three breakdowns: entry counts, conversion, and source) as a file.
Requires an API key with `MANAGEMENT_API` (or `MANAGEMENT_API_SANDBOX`) quota, and the `LEAD_GENERATION_ANALYTICS_CAN_VIEW` permission, sent as the raw `Authorization` header value — see the [API overview](/api-reference/management-api/overview).
Same as [Get lead analytics](/api-reference/management-api/leads/analytics) — `lead_id`s or QR `url_hash`es, per `filter_by`.
`lead_id` (default) or `qr_hash`.
`csv` (a ZIP of one CSV per breakdown), or `xls`/`xlsx` (one workbook with three sheets).
Start date (`YYYY-MM-DD`). Defaults to 7 days before `to`.
End date (`YYYY-MM-DD`). Defaults to today.
```bash cURL theme={null}
curl --request POST \
--url 'https://api.scanova.io/lead/analytics/export/?file_format=xlsx&from=2026-08-01&to=2026-08-31' \
--header 'Authorization: YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{ "q": ["L9c2f4a1e"] }' \
--output lead_analytics.xlsx
```
The response is a file download — `application/zip` with `Content-Disposition: attachment; filename=lead_analytics.zip` for `csv`, or the XLSX MIME type with `filename=lead_analytics.xlsx` for `xls`/`xlsx`. Unlike [Get lead analytics](/api-reference/management-api/leads/analytics), there's no `type` filter — the export always includes all three breakdowns.
`400` with a plain-text body if `file_format` isn't recognized:
```json 400 theme={null}
{ "detail": "Invalid file format" }
```
## Related
* [Get lead analytics](/api-reference/management-api/leads/analytics) — the JSON equivalent, with a `type` filter.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota architecture this endpoint is part of.
# Convert a lead list to a Form
Source: https://docs.scanova.io/api-reference/management-api/leads/convert
api-reference/openapi/management-api.json POST /lead/{lead_id}/convert/
POST /lead/{lead_id}/convert/
Migrates a legacy lead list into a [Form](/api-reference/management-api/forms/create) — Scanova's current, recommended lead-capture builder.
Requires an API key with `MANAGEMENT_API` (or `MANAGEMENT_API_SANDBOX`) quota, sent as the raw `Authorization` header value — see the [API overview](/api-reference/management-api/overview).
The lead list's `lead_id`. No request body.
```bash cURL theme={null}
curl --request POST \
--url 'https://api.scanova.io/lead/L9c2f4a1e/convert/' \
--header 'Authorization: YOUR_API_KEY'
```
```json 201 theme={null}
{
"id": 512,
"form_id": "F3b9c1a2",
"lead_id": "L9c2f4a1e"
}
```
Converting a lead list:
* Creates a new Form from the lead list's field definitions.
* Re-links every QR Code currently attached to the lead list to the new Form instead.
* Migrates active webhooks to the Form's webhook mechanism (the lead list's own webhooks are marked inactive).
* Migrates any lead notifications configured for this lead list into Form notifications.
* Marks the lead list `is_converted: true` — from this point it no longer appears in [List lead lists](/api-reference/management-api/leads/list) or [List active lead lists](/api-reference/management-api/leads/active-list), and further edits/entries/exports go through the Forms endpoints instead.
This is not idempotent in a friendly way: calling convert a second time on an already-converted lead list returns `404`, since the lookup filters to non-converted lists. There is no "undo" — check `is_converted` (via [Retrieve a lead list](/api-reference/management-api/leads/retrieve), which will 404 once converted) before retrying.
`400` under `data` if the transformed form data unexpectedly fails Form schema validation (a rare shape-drift edge case, not something a well-formed lead list should trigger).
## Related
* [List lead lists](/api-reference/management-api/leads/list) — lead lists eligible for conversion.
* [Form management](/api-reference/management-api/forms/create) — where the converted resource lives afterward.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota architecture this endpoint is part of.
# Create a lead list
Source: https://docs.scanova.io/api-reference/management-api/leads/create
api-reference/openapi/management-api.json POST /lead/
POST /lead/
Lead lists are Scanova's original lead-capture feature — a form definition (`data`) attached to a QR Code's landing page. List or create them here.
Lead Lists are a **legacy** feature. Scanova's dashboard now steers new lead-capture use cases toward [Forms](/api-reference/management-api/forms/create) instead, which cover the same "collect visitor data from a QR Code scan" use case with a more capable builder. Existing lead lists keep working, and this endpoint is unaffected, but build new integrations against Forms unless you specifically need to manage a pre-existing lead list.
Requires an API key with `MANAGEMENT_API` (or `MANAGEMENT_API_SANDBOX`) quota, sent as the raw `Authorization` header value — see the [API overview](/api-reference/management-api/overview).
```bash cURL theme={null}
curl --request POST \
--url 'https://api.scanova.io/lead/' \
--header 'Authorization: YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"name": "Trade Show Signup",
"data": "{\"fields\":[{\"type\":\"email\",\"label\":\"Email\",\"required\":true}]}"
}'
```
```json 201 theme={null}
{
"id": 205,
"lead_id": "L9c2f4a1e",
"name": "Trade Show Signup",
"created": "2026-08-16T20:44:51.008000+05:30",
"is_active": true,
"usage_count": 0,
"entries_count": 0,
"linked_qrs": [],
"modified": "2026-08-16T20:44:51.008000+05:30",
"data": "{\"fields\":[{\"type\":\"email\",\"label\":\"Email\",\"required\":true}]}",
"webhooks": []
}
```
Lead list name, up to 128 characters.
A JSON-encoded string (not a raw object) describing the lead form's fields. Validated against the same lead-form schema the dashboard's lead-list builder uses — malformed JSON or a schema violation returns a 400 under `data`.
Whether the form renders on the landing page.
The response includes `data` and `webhooks` (a list of `{id, url, created, modified}` notification webhooks) — fields only present on the detail shape returned by create/retrieve, not on the list endpoint above.
## Related
* [List lead lists](/api-reference/management-api/leads/list) — the other operation on this same endpoint.
* [Form management](/api-reference/management-api/forms/create) — the current, recommended lead-capture builder this legacy feature is being steered toward.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota architecture this endpoint is part of.
# Delete a lead list
Source: https://docs.scanova.io/api-reference/management-api/leads/delete
api-reference/openapi/management-api.json DELETE /lead/{lead_id}/
DELETE /lead/{lead_id}/
Permanently deletes a lead list.
Unlike QR Codes and most other resources in this API, lead lists are **hard-deleted** — there is no trash/restore step. Deleting a lead list also removes its entries and webhooks; this cannot be undone.
Requires an API key with `MANAGEMENT_API` (or `MANAGEMENT_API_SANDBOX`) quota, sent as the raw `Authorization` header value — see the [API overview](/api-reference/management-api/overview).
The lead list's `lead_id`.
```bash cURL theme={null}
curl --request DELETE \
--url 'https://api.scanova.io/lead/L9c2f4a1e/' \
--header 'Authorization: YOUR_API_KEY'
```
A successful call returns `204 No Content`. `404` if `lead_id` isn't owned by this account.
## Related
* [Retrieve a lead list](/api-reference/management-api/leads/retrieve) — check contents before deleting.
* [List lead lists](/api-reference/management-api/leads/list) — see what else exists.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota architecture this endpoint is part of.
# Delete a lead entry
Source: https://docs.scanova.io/api-reference/management-api/leads/entry-delete
api-reference/openapi/management-api.json DELETE /lead/{lead_id}/entry/{pk}/
DELETE /lead/{lead_id}/entry/{pk}/
Deletes a single lead submission.
Requires an API key with `MANAGEMENT_API` (or `MANAGEMENT_API_SANDBOX`) quota, and the `LEAD_GENERATION_ENTRY_CAN_DELETE` permission, sent as the raw `Authorization` header value — see the [API overview](/api-reference/management-api/overview).
The lead list's `lead_id`.
The entry's UUID.
```bash cURL theme={null}
curl --request DELETE \
--url 'https://api.scanova.io/lead/L9c2f4a1e/entry/4e6a9b2c-0f77-4d1a-9c6e-3a2b7f9d1e40/' \
--header 'Authorization: YOUR_API_KEY'
```
A successful call returns `204 No Content`. `404` if `lead_id` isn't owned by this account, or `pk` isn't an entry on this lead list.
## Related
* [List lead entries](/api-reference/management-api/leads/entry-list) — find the entry's `pk`.
* [Retrieve a lead entry](/api-reference/management-api/leads/entry-retrieve) — check contents before deleting.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota architecture this endpoint is part of.
# Export lead entries
Source: https://docs.scanova.io/api-reference/management-api/leads/entry-export
api-reference/openapi/management-api.json GET /lead/{lead_id}/entry/export/
GET /lead/{lead_id}/entry/export/
Downloads a lead list's entries as a CSV or Excel file.
Requires an API key with `MANAGEMENT_API` (or `MANAGEMENT_API_SANDBOX`) quota, and the `LEAD_GENERATION_ENTRY_CAN_VIEW` permission, sent as the raw `Authorization` header value — see the [API overview](/api-reference/management-api/overview).
The lead list's `lead_id`.
`csv`, `xls`, or `xlsx`.
Only entries created on or after this date (`YYYY-MM-DD`).
Only entries created on or before this date (`YYYY-MM-DD`).
Comma-separated entry UUIDs to scope the export to a specific selection — mirrors the dashboard's "download selected" action. Invalid UUIDs are silently ignored rather than rejected.
Same substring search as [List lead entries](/api-reference/management-api/leads/entry-list) — mirrors "download filtered results".
```bash cURL theme={null}
curl --request GET \
--url 'https://api.scanova.io/lead/L9c2f4a1e/entry/export/?file_format=csv&from=2026-08-01&to=2026-08-31' \
--header 'Authorization: YOUR_API_KEY' \
--output entries.csv
```
The response is a file download (`Content-Type: text/csv`, or the XLSX MIME type for `xls`/`xlsx`), not JSON, with `Content-Disposition: attachment; filename=-.`.
The exported file always includes a `tracking_id` column, regardless of tracking permissions elsewhere on the account. If a submitting QR Code has since been deleted, its column value renders as ` (Deleted)` rather than failing the export. With zero matching entries, the file still contains a header row (derived from the lead form's own question labels) rather than being empty.
On a free-plan account (`plan_type` 1), the export is capped at 25 rows — if more entries matched, two trailing rows are appended noting the cap and prompting an upgrade.
`400` with a plain-text body (not the standard DRF error shape) if `file_format` isn't one of `csv`, `xls`, `xlsx`:
```json 400 theme={null}
{ "detail": "requested file format unknown" }
```
## Related
* [List lead entries](/api-reference/management-api/leads/entry-list) — the JSON equivalent, with the same filters.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota architecture this endpoint is part of.
# List lead entries
Source: https://docs.scanova.io/api-reference/management-api/leads/entry-list
api-reference/openapi/management-api.json GET /lead/{lead_id}/entry/
GET /lead/{lead_id}/entry/
Lists submissions ("entries") collected by a lead list's form.
Requires an API key with `MANAGEMENT_API` (or `MANAGEMENT_API_SANDBOX`) quota, and the `LEAD_GENERATION_ENTRY_CAN_VIEW` permission, sent as the raw `Authorization` header value — see the [API overview](/api-reference/management-api/overview).
The lead list's `lead_id`.
Filters to entries submitted through a specific QR Code, by `qrid`.
Only entries created on or after this date (`YYYY-MM-DD`).
Only entries created on or before this date (`YYYY-MM-DD`).
Substring search across all submitted field values — since lead-form fields are admin-defined per list, there's no fixed column to search, so this matches anywhere in the entry's raw data.
`created` or `-created`. Default is `-created`.
Page number.
```bash cURL theme={null}
curl --request GET \
--url 'https://api.scanova.io/lead/L9c2f4a1e/entry/?ordering=-created' \
--header 'Authorization: YOUR_API_KEY'
```
```json 200 theme={null}
{
"count": 14,
"next": "https://api.scanova.io/lead/L9c2f4a1e/entry/?page=2",
"previous": null,
"results": [
{
"id": "4e6a9b2c-0f77-4d1a-9c6e-3a2b7f9d1e40",
"lead_list_id": "L9c2f4a1e",
"qr_code_id": "Q7f3a",
"data": [{ "question": "Email", "answer": "alice@example.com" }],
"tracking_data_id": "8b1d4e2a-1c3f-4a9d-9e21-5f6a7b8c9d10",
"created": "2026-09-01T10:03:12.552000+05:30"
}
]
}
```
`id` is the entry's UUID (used as `pk` on the retrieve/delete endpoints below), and `lead_list_id`/`qr_code_id` reference the parent lead list and submitting QR Code by their string identifiers (`lead_id`/`qrid`), not numeric ids. `404` if `lead_id` isn't owned by this account.
## Related
* [Retrieve a lead entry](/api-reference/management-api/leads/entry-retrieve) — get one entry's full detail.
* [Export lead entries](/api-reference/management-api/leads/entry-export) — download entries as CSV/XLSX.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota architecture this endpoint is part of.
# Retrieve a lead entry
Source: https://docs.scanova.io/api-reference/management-api/leads/entry-retrieve
api-reference/openapi/management-api.json GET /lead/{lead_id}/entry/{pk}/
GET /lead/{lead_id}/entry/{pk}/
Retrieves a single lead submission, including the parent lead list's summary.
Requires an API key with `MANAGEMENT_API` (or `MANAGEMENT_API_SANDBOX`) quota, and the `LEAD_GENERATION_ENTRY_CAN_VIEW` permission, sent as the raw `Authorization` header value — see the [API overview](/api-reference/management-api/overview).
The lead list's `lead_id`.
The entry's UUID, from [List lead entries](/api-reference/management-api/leads/entry-list).
```bash cURL theme={null}
curl --request GET \
--url 'https://api.scanova.io/lead/L9c2f4a1e/entry/4e6a9b2c-0f77-4d1a-9c6e-3a2b7f9d1e40/' \
--header 'Authorization: YOUR_API_KEY'
```
```json 200 theme={null}
{
"id": "4e6a9b2c-0f77-4d1a-9c6e-3a2b7f9d1e40",
"lead_list_id": "L9c2f4a1e",
"qr_code_id": "Q7f3a",
"data": [{ "question": "Email", "answer": "alice@example.com" }],
"tracking_data_id": "8b1d4e2a-1c3f-4a9d-9e21-5f6a7b8c9d10",
"created": "2026-09-01T10:03:12.552000+05:30",
"lead_list": {
"id": 205,
"lead_id": "L9c2f4a1e",
"name": "Trade Show Signup",
"created": "2026-08-16T20:44:51.008000+05:30",
"is_active": true,
"usage_count": 2,
"entries_count": 14,
"linked_qrs": [],
"linked_qrs_count": 1,
"modified": "2026-08-20T09:12:03.441000+05:30"
}
}
```
`404` if `lead_id` isn't owned by this account, or `pk` isn't an entry on this lead list.
## Related
* [List lead entries](/api-reference/management-api/leads/entry-list) — find the entry's `pk`.
* [Delete a lead entry](/api-reference/management-api/leads/entry-delete) — remove this entry.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota architecture this endpoint is part of.
# Link QR Codes to a lead list
Source: https://docs.scanova.io/api-reference/management-api/leads/link-qrs
api-reference/openapi/management-api.json POST /lead/{lead_id}/link-qrs/
POST /lead/{lead_id}/link-qrs/
Sets the full set of QR Codes attached to a lead list.
This call **replaces** the entire link set — every QR Code currently linked to this lead list is unlinked first, then exactly the QR Codes you send are linked. To keep an existing link, include its `qrid` in the request.
Requires an API key with `MANAGEMENT_API` (or `MANAGEMENT_API_SANDBOX`) quota, sent as the raw `Authorization` header value — see the [API overview](/api-reference/management-api/overview).
The lead list's `lead_id`.
The request body is a **raw JSON array** of QR `qrid` strings — not wrapped in an object.
```bash cURL theme={null}
curl --request POST \
--url 'https://api.scanova.io/lead/L9c2f4a1e/link-qrs/' \
--header 'Authorization: YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '["Q7f3a", "Qa91d"]'
```
```json 200 theme={null}
{
"id": 205,
"lead_id": "L9c2f4a1e",
"name": "Trade Show Signup",
"created": "2026-08-16T20:44:51.008000+05:30",
"is_active": true,
"usage_count": 2,
"entries_count": 14,
"linked_qrs": [
{ "qr_code__id": 88, "qr_code__qrid": "Q7f3a", "qr_code__name": "Booth QR", "qr_code__qr_type": "dy" }
],
"linked_qrs_count": 1,
"modified": "2026-09-08T10:12:00.000000+05:30"
}
```
Only dynamic QR Codes (`qr_type: "dy"`) owned by this account can be linked — a `qrid` for a static or foreign QR Code is silently dropped from the target set rather than raising an error. If a target QR Code was previously attached to a Form, that Form link is removed, since a QR Code can only be attached to one lead-capture resource at a time.
## Related
* [Retrieve a lead list](/api-reference/management-api/leads/retrieve) — view the current link set.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota architecture this endpoint is part of.
# List lead lists
Source: https://docs.scanova.io/api-reference/management-api/leads/list
api-reference/openapi/management-api.json GET /lead/
GET /lead/
Lead lists are Scanova's original lead-capture feature — a form definition (`data`) attached to a QR Code's landing page. List or create them here.
Lead Lists are a **legacy** feature. Scanova's dashboard now steers new lead-capture use cases toward [Forms](/api-reference/management-api/forms/create) instead, which cover the same "collect visitor data from a QR Code scan" use case with a more capable builder. Existing lead lists keep working, and this endpoint is unaffected, but build new integrations against Forms unless you specifically need to manage a pre-existing lead list.
Requires an API key with `MANAGEMENT_API` (or `MANAGEMENT_API_SANDBOX`) quota, sent as the raw `Authorization` header value — see the [API overview](/api-reference/management-api/overview).
```bash cURL theme={null}
curl --request GET \
--url 'https://api.scanova.io/lead/' \
--header 'Authorization: YOUR_API_KEY'
```
```json 200 theme={null}
{
"count": 0,
"next": null,
"previous": null,
"results": []
}
```
An account with no lead lists returns an empty `results` array, as above. This endpoint only ever returns lead lists that have **not** been converted to a Form; once a lead list is converted (via the dashboard's "Migrate to Form" flow), it drops out of this list permanently and becomes reachable only through the Forms endpoints.
Filter to active (`true`) or inactive (`false`) lead lists. Inactive lead lists don't render their form on the landing page.
### Response fields (per entry)
Unique, immutable lead-list identifier.
Internal name — not shown on the public landing page.
Number of QR Codes this lead list is currently attached to.
Total number of lead submissions collected.
QR Codes this lead list is attached to.
## Related
* [Create a lead list](/api-reference/management-api/leads/create) — the other operation on this same endpoint.
* [Form management](/api-reference/management-api/forms/create) — the current, recommended lead-capture builder this legacy feature is being steered toward.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota architecture this endpoint is part of.
# Retrieve a lead list
Source: https://docs.scanova.io/api-reference/management-api/leads/retrieve
api-reference/openapi/management-api.json GET /lead/{lead_id}/
GET /lead/{lead_id}/
Retrieves a single lead list, including its form `data` and configured `webhooks`.
Requires an API key with `MANAGEMENT_API` (or `MANAGEMENT_API_SANDBOX`) quota, sent as the raw `Authorization` header value — see the [API overview](/api-reference/management-api/overview).
The lead list's `lead_id`.
Alternate lookup: pass the numeric `id` here instead of using `lead_id` in the path (the path segment is still required by the route, but is ignored when `id` is present).
```bash cURL theme={null}
curl --request GET \
--url 'https://api.scanova.io/lead/L9c2f4a1e/' \
--header 'Authorization: YOUR_API_KEY'
```
```json 200 theme={null}
{
"id": 205,
"lead_id": "L9c2f4a1e",
"name": "Trade Show Signup",
"is_active": true,
"usage_count": 2,
"entries_count": 14,
"linked_qrs": [
{ "qr_code__id": 88, "qr_code__qrid": "Q7f3a", "qr_code__name": "Booth QR", "qr_code__qr_type": "dy" }
],
"linked_qrs_count": 1,
"created": "2026-08-16T20:44:51.008000+05:30",
"modified": "2026-08-20T09:12:03.441000+05:30",
"data": "{\"fields\":[{\"type\":\"email\",\"label\":\"Email\",\"required\":true}]}",
"webhooks": [{ "id": 4, "url": "https://example.com/hook", "created": "2026-08-16T20:44:51.008000+05:30", "modified": "2026-08-16T20:44:51.008000+05:30" }]
}
```
`404` if `lead_id` isn't a lead list owned by this account.
## Related
* [List lead lists](/api-reference/management-api/leads/list) — find the `lead_id`.
* [Update a lead list](/api-reference/management-api/leads/update) — change `name`, `data`, or `is_active`.
* [Delete a lead list](/api-reference/management-api/leads/delete) — remove it permanently.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota architecture this endpoint is part of.
# Update a lead list
Source: https://docs.scanova.io/api-reference/management-api/leads/update
api-reference/openapi/management-api.json PATCH /lead/{lead_id}/
PATCH /lead/{lead_id}/
Updates a lead list's name, form definition, or active state.
Requires an API key with `MANAGEMENT_API` (or `MANAGEMENT_API_SANDBOX`) quota, sent as the raw `Authorization` header value — see the [API overview](/api-reference/management-api/overview).
```
PUT https://api.scanova.io/lead/{lead_id}/
PATCH https://api.scanova.io/lead/{lead_id}/
```
The lead list's `lead_id`. Immutable — it cannot be changed via this endpoint.
Lead list name, up to 128 characters.
A JSON-encoded string (not a raw object) describing the lead form's fields. Validated against the same schema used on create — malformed JSON or a schema violation returns `400` under `data`.
Whether the form renders on the landing page.
```bash cURL theme={null}
curl --request PATCH \
--url 'https://api.scanova.io/lead/L9c2f4a1e/' \
--header 'Authorization: YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{ "is_active": false }'
```
```json 200 theme={null}
{
"id": 205,
"lead_id": "L9c2f4a1e",
"name": "Trade Show Signup",
"is_active": false,
"usage_count": 2,
"entries_count": 14,
"linked_qrs": [
{ "qr_code__id": 88, "qr_code__qrid": "Q7f3a", "qr_code__name": "Booth QR", "qr_code__qr_type": "dy" }
],
"linked_qrs_count": 1,
"created": "2026-08-16T20:44:51.008000+05:30",
"modified": "2026-09-08T10:12:00.000000+05:30",
"data": "{\"fields\":[{\"type\":\"email\",\"label\":\"Email\",\"required\":true}]}",
"webhooks": []
}
```
Both `PUT` and `PATCH` are supported. `404` if `lead_id` isn't owned by this account.
## Related
* [Retrieve a lead list](/api-reference/management-api/leads/retrieve) — view current values before updating.
* [Delete a lead list](/api-reference/management-api/leads/delete) — remove it permanently.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota architecture this endpoint is part of.
# Accept a shared-user invitation
Source: https://docs.scanova.io/api-reference/management-api/shared-users/accept-invitation
api-reference/openapi/management-api.json POST /multi-users/accept-invitation/
POST /multi-users/accept-invitation/
Accepts a shared-user invitation and sets the teammate's password, on `api.scanova.io`.
```
POST https://api.scanova.io/multi-users/accept-invitation/
```
Unlike every other endpoint on this page, this one does **not** take an API key — it's `AllowAny`. It's meant to be called from the invited teammate's own browser, from the link in their invitation email, not from the inviting account's integration.
The invitation hash from the emailed link, max 250 characters.
The teammate's new password.
Password confirmation — must match `password1`.
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/multi-users/accept-invitation/" \
-H "Content-Type: application/json" \
-d '{
"hash": "a1b2c3d4e5f6...",
"password1": "correct-horse-battery-staple",
"password2": "correct-horse-battery-staple"
}'
```
```json 200 theme={null}
"Invitation accepted"
```
## Hash validity
A hash is valid only if it exists, hasn't already been used, and was sent within the account's configured invitation-link-validity window — both the original invite and any [resend](/api-reference/management-api/shared-users/resend-invitation) refresh the timestamp this window is measured from. `400` with `"Verification link is invalid or expired."` if the hash fails any of those checks, or if the two passwords don't match or fail password policy.
## Related
* [Invite a shared user](/api-reference/management-api/shared-users/invite) — where the hash in the emailed link comes from.
* [Resend a shared-user's invitation email](/api-reference/management-api/shared-users/resend-invitation) — get a fresh hash if the original expired.
* [API overview](/api-reference/management-api/overview) — the auth scheme this endpoint deliberately does **not** use.
# Export shared users as CSV
Source: https://docs.scanova.io/api-reference/management-api/shared-users/export
api-reference/openapi/management-api.json GET /multi-users/export/
GET /multi-users/export/
Exports a CSV of the account's shared users on `api.scanova.io`, authenticated with your raw API key.
```
GET https://api.scanova.io/multi-users/export/
Authorization: YOUR_API_KEY
```
```bash cURL theme={null}
curl "https://api.scanova.io/multi-users/export/" \
-H "Authorization: YOUR_API_KEY" \
-o shared-users.csv
```
Response is a `text/csv` file download, not JSON. Columns: name, email, role, invitation-sent/accepted timestamps, status, tags, MFA status/enforcement, QR Code count, and last login — one row per shared user, plus a row for the account owner.
## Related
* [List shared users](/api-reference/management-api/shared-users/list) — the JSON equivalent, for programmatic use rather than a downloadable report.
* [List roles](/api-reference/management-api/shared-users/roles-list) — what the `role` column values correspond to.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Invite a shared user
Source: https://docs.scanova.io/api-reference/management-api/shared-users/invite
api-reference/openapi/management-api.json POST /multi-users/
POST /multi-users/
Shared users are the teammates you invite into your Scanova account — the same list shown on the dashboard's **Users** table. This endpoint lets you list, invite, update, or remove them programmatically.
This endpoint was not previously documented. It requires an API key with `MANAGEMENT_API` (or `MANAGEMENT_API_SANDBOX`) quota, sent as the raw `Authorization` header value — see the [API overview](/api-reference/management-api/overview) — plus the account's own Team quota for shared users. For what each role actually grants, see [Roles & permissions](/team/roles-and-permissions); for the wire format of roles themselves, see [Roles (access levels)](/api-reference/management-api/shared-users/roles-list).
```bash cURL theme={null}
curl --request POST \
--url 'https://api.scanova.io/multi-users/' \
--header 'Authorization: YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"email": "teammate@example.com",
"name": "Jordan Lee",
"access_level": 5
}'
```
```json 201 theme={null}
{
"id": 118,
"shared_user": {
"id": 88213,
"first_name": "Jordan",
"last_name": "Lee",
"full_name": "Jordan Lee",
"email": "teammate@example.com",
"is_active": false,
"is_locked": false
},
"access_level": {
"id": 5,
"name": "Manager",
"slug": "manager",
"permissions": [],
"is_custom": false
},
"invitation_sent_on": null,
"invitation_accepted_on": null,
"is_invitation_sent": false,
"is_invitation_accepted": false,
"status": "Invitation Sent",
"is_active": false,
"created": "2026-08-16T20:41:09.442000+05:30",
"modified": "2026-08-16T20:41:09.442000+05:30",
"include_untagged": true,
"enable_tag_permission": false,
"tags": []
}
```
Email address for the new teammate. Must not already belong to another Scanova user — a duplicate returns a 400 with `{"email": ["Email address is already used."]}`.
Display name, up to 20 characters.
ID of a role from [`GET /multi-users/access-levels/`](/api-reference/management-api/shared-users/roles-list) — either a default system role or a custom one this account created.
Tag IDs to scope this user's access to. Requires the account's `CUSTOM_TAG`/tag-permission quota — otherwise rejected with a 403.
Restrict this user's visible QR Codes to `tags`.
When `enable_tag_permission` is true, whether untagged QR Codes are still visible.
Inviting a teammate always sends them an invitation email — creating the record here has the same effect as clicking **Invite Users** in the dashboard's Users table. There is no way to skip the email via this endpoint.
## Related
* [List shared users](/api-reference/management-api/shared-users/list) — the other operation on this same endpoint.
* [Retrieve a shared user](/api-reference/management-api/shared-users/retrieve) — operate on the shared user's record after inviting them.
* [Update a shared user](/api-reference/management-api/shared-users/update) — operate on the shared user's record after inviting them.
* [Remove a shared user](/api-reference/management-api/shared-users/remove) — operate on the shared user's record after inviting them.
* [List roles](/api-reference/management-api/shared-users/roles-list) — look up or create the `access_level` ID this endpoint's invite/update calls require.
* [Create a custom role](/api-reference/management-api/shared-users/roles-create) — look up or create the `access_level` ID this endpoint's invite/update calls require.
* [Shared users](/team/shared-users) — the dashboard's own Users table, showing the same invite/deactivate/remove actions.
* [Roles & permissions](/team/roles-and-permissions) — what each role actually grants a teammate.
# List shared users
Source: https://docs.scanova.io/api-reference/management-api/shared-users/list
api-reference/openapi/management-api.json GET /multi-users/
GET /multi-users/
Shared users are the teammates you invite into your Scanova account — the same list shown on the dashboard's **Users** table. This endpoint lets you list, invite, update, or remove them programmatically.
This endpoint was not previously documented. It requires an API key with `MANAGEMENT_API` (or `MANAGEMENT_API_SANDBOX`) quota, sent as the raw `Authorization` header value — see the [API overview](/api-reference/management-api/overview) — plus the account's own Team quota for shared users. For what each role actually grants, see [Roles & permissions](/team/roles-and-permissions); for the wire format of roles themselves, see [Roles (access levels)](/api-reference/management-api/shared-users/roles-list).
```bash cURL theme={null}
curl --request GET \
--url 'https://api.scanova.io/multi-users/' \
--header 'Authorization: YOUR_API_KEY'
```
```json 200 theme={null}
{
"count": 0,
"next": null,
"previous": null,
"results": []
}
```
An account with no invited teammates returns an empty `results` array, as above. The shape below is what each entry looks like once teammates exist.
Exact-match filter on the shared user's email address.
Case-insensitive substring match against the shared user's first name.
Comma-separated role names to filter by, e.g. `Admin,Manager`.
One of `invitation_sent`, `active`, `inactive`.
One of `enabled`, `disabled`, `pending`.
`shared_user__email`, `shared_user__first_name`, or either prefixed with `-` for descending.
### Response fields (per entry)
Shared-user record ID — use this as `{pk}` for the retrieve/update/delete endpoints below.
The invited user's account.
User ID.False when deactivated by the owner.True if locked out under the account's password policy.
The assigned role, in the same shape returned by [`GET /multi-users/access-levels/`](/api-reference/management-api/shared-users/roles-list).
Computed status: `Invitation Sent`, `Active`, `Inactive` (no login in 60+ days), or `Locked`.
Whether the shared user's account is active (distinct from `status` — a deactivated user always shows `is_active: false` regardless of invitation state).
ISO 8601 timestamp of the (most recent) invitation email.
ISO 8601 timestamp of when the invite was accepted, or `null` if still pending.
If true, this user's QR Code visibility is restricted to the tags in `tags` below.
When tag-based permission is enabled, whether the user can also see untagged QR Codes.
Tags this user is scoped to (only meaningful when `enable_tag_permission` is true), in `{id, name}` form.
## Related
* [Invite a shared user](/api-reference/management-api/shared-users/invite) — the other operation on this same endpoint.
* [Retrieve a shared user](/api-reference/management-api/shared-users/retrieve) — operate on a single shared user returned by this list.
* [Update a shared user](/api-reference/management-api/shared-users/update) — operate on a single shared user returned by this list.
* [Remove a shared user](/api-reference/management-api/shared-users/remove) — operate on a single shared user returned by this list.
* [List roles](/api-reference/management-api/shared-users/roles-list) — look up or create the `access_level` ID this endpoint's invite/update calls require.
* [Create a custom role](/api-reference/management-api/shared-users/roles-create) — look up or create the `access_level` ID this endpoint's invite/update calls require.
* [Shared users](/team/shared-users) — the dashboard's own Users table, showing the same invite/deactivate/remove actions.
* [Roles & permissions](/team/roles-and-permissions) — what each role actually grants a teammate.
# List permission categories
Source: https://docs.scanova.io/api-reference/management-api/shared-users/permissions
api-reference/openapi/management-api.json GET /multi-users/permission/
GET /multi-users/permission/
Lists every active permission category and the shared permissions available within it, on `api.scanova.io`, authenticated with your raw API key.
```
GET https://api.scanova.io/multi-users/permission/
Authorization: YOUR_API_KEY
```
```bash cURL theme={null}
curl "https://api.scanova.io/multi-users/permission/" \
-H "Authorization: YOUR_API_KEY"
```
```json 200 theme={null}
[
{
"name": "QR Codes",
"permissions": [
{ "id": 23, "code": "QR_CODE_CAN_VIEW", "name": "Can view QR Code", "description": "Allows viewing QR code details.", "is_boolean": true }
]
}
]
```
Not paginated. This is the bulk, category-grouped alternative to reading permission IDs off an existing role's `permissions` array — use it to build a full role-editor UI (grouped checkboxes by category) without first fetching a role.
## Related
* [Create a custom role](/api-reference/management-api/shared-users/roles-create) — use permission IDs from here to build `permissions`.
* [List roles](/api-reference/management-api/shared-users/roles-list) — see which permissions an existing role already grants.
* [Roles & permissions](/team/roles-and-permissions) — the human-facing explanation of what each permission grants.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Remove a shared user
Source: https://docs.scanova.io/api-reference/management-api/shared-users/remove
api-reference/openapi/management-api.json DELETE /multi-users/{pk}/
DELETE /multi-users/{pk}/
Shared users are the teammates you invite into your Scanova account — the same list shown on the dashboard's **Users** table. This endpoint lets you list, invite, update, or remove them programmatically.
This endpoint was not previously documented. It requires an API key with `MANAGEMENT_API` (or `MANAGEMENT_API_SANDBOX`) quota, sent as the raw `Authorization` header value — see the [API overview](/api-reference/management-api/overview) — plus the account's own Team quota for shared users. For what each role actually grants, see [Roles & permissions](/team/roles-and-permissions); for the wire format of roles themselves, see [Roles (access levels)](/api-reference/management-api/shared-users/roles-list).
The shared-user record ID from the list response above.
```bash cURL theme={null}
curl --request DELETE \
--url 'https://api.scanova.io/multi-users/118/' \
--header 'Authorization: YOUR_API_KEY'
```
`DELETE` returns `204 No Content`.
## Related
* [List shared users](/api-reference/management-api/shared-users/list) — find the `pk` to operate on here.
* [Retrieve a shared user](/api-reference/management-api/shared-users/retrieve) — another operation on this same `{pk}` endpoint.
* [Update a shared user](/api-reference/management-api/shared-users/update) — another operation on this same `{pk}` endpoint.
* [List roles](/api-reference/management-api/shared-users/roles-list) — look up or create the `access_level` ID this endpoint's invite/update calls require.
* [Create a custom role](/api-reference/management-api/shared-users/roles-create) — look up or create the `access_level` ID this endpoint's invite/update calls require.
* [Shared users](/team/shared-users) — the dashboard's own Users table, showing the same invite/deactivate/remove actions.
* [Roles & permissions](/team/roles-and-permissions) — what each role actually grants a teammate.
# Resend a shared-user's invitation email
Source: https://docs.scanova.io/api-reference/management-api/shared-users/resend-invitation
api-reference/openapi/management-api.json POST /multi-users/{pk}/resend-invitation/
POST /multi-users/{pk}/resend-invitation/
Resends the invitation email for a shared user who hasn't accepted yet, on `api.scanova.io`, authenticated with your raw API key.
```
POST https://api.scanova.io/multi-users/{pk}/resend-invitation/
Authorization: YOUR_API_KEY
```
The shared user's `id`, from [List shared users](/api-reference/management-api/shared-users/list).
```bash cURL theme={null}
curl -X POST "https://api.scanova.io/multi-users/118/resend-invitation/" \
-H "Authorization: YOUR_API_KEY"
```
```json 200 theme={null}
"Invitation resent"
```
Resending refreshes `invitation_sent_on`, which extends the invitation link's validity window — a teammate whose original invite expired can still accept after a resend. `404` if `pk` isn't a shared user on this account (including one who already accepted — there's nothing to resend at that point, since [Accept a shared-user invitation](/api-reference/management-api/shared-users/accept-invitation) invalidates the hash on use).
## Related
* [Invite a shared user](/api-reference/management-api/shared-users/invite) — the original invitation this resends.
* [List shared users](/api-reference/management-api/shared-users/list) — check `is_invitation_accepted` before resending.
* [Accept a shared-user invitation](/api-reference/management-api/shared-users/accept-invitation) — what the teammate does with the resent link.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Retrieve a shared user
Source: https://docs.scanova.io/api-reference/management-api/shared-users/retrieve
api-reference/openapi/management-api.json GET /multi-users/{pk}/
GET /multi-users/{pk}/
Shared users are the teammates you invite into your Scanova account — the same list shown on the dashboard's **Users** table. This endpoint lets you list, invite, update, or remove them programmatically.
This endpoint was not previously documented. It requires an API key with `MANAGEMENT_API` (or `MANAGEMENT_API_SANDBOX`) quota, sent as the raw `Authorization` header value — see the [API overview](/api-reference/management-api/overview) — plus the account's own Team quota for shared users. For what each role actually grants, see [Roles & permissions](/team/roles-and-permissions); for the wire format of roles themselves, see [Roles (access levels)](/api-reference/management-api/shared-users/roles-list).
The shared-user record ID from the list response above.
```bash cURL theme={null}
curl --request GET \
--url 'https://api.scanova.io/multi-users/118/' \
--header 'Authorization: YOUR_API_KEY'
```
`GET` returns the same shape as the invite response above.
## Related
* [List shared users](/api-reference/management-api/shared-users/list) — find the `pk` to operate on here.
* [Update a shared user](/api-reference/management-api/shared-users/update) — another operation on this same `{pk}` endpoint.
* [Remove a shared user](/api-reference/management-api/shared-users/remove) — another operation on this same `{pk}` endpoint.
* [List roles](/api-reference/management-api/shared-users/roles-list) — look up or create the `access_level` ID this endpoint's invite/update calls require.
* [Create a custom role](/api-reference/management-api/shared-users/roles-create) — look up or create the `access_level` ID this endpoint's invite/update calls require.
* [Shared users](/team/shared-users) — the dashboard's own Users table, showing the same invite/deactivate/remove actions.
* [Roles & permissions](/team/roles-and-permissions) — what each role actually grants a teammate.
# Create a custom role
Source: https://docs.scanova.io/api-reference/management-api/shared-users/roles-create
api-reference/openapi/management-api.json POST /multi-users/access-levels/
POST /multi-users/access-levels/
Every shared user is assigned a role — Scanova's dashboard calls these "access levels" internally, and the same term appears throughout this endpoint. Use this endpoint to look up available roles before inviting a teammate via [`POST /multi-users/`](/api-reference/management-api/shared-users/invite), or to create a custom role from your own systems.
This endpoint was not previously documented. It requires an API key with `MANAGEMENT_API` (or `MANAGEMENT_API_SANDBOX`) quota — see the [API overview](/api-reference/management-api/overview). For what each role actually grants a teammate, see the human-facing [Roles & permissions](/team/roles-and-permissions) guide — this page only documents the wire format.
Creating custom roles requires a dedicated quota on the account's plan (the same gate that hides the dashboard's **Roles** tab on unsupported plans — see [Custom roles are plan-gated](/team/roles-and-permissions#custom-roles-are-plan-gated)). A 403 here means the plan doesn't include it.
```bash cURL theme={null}
curl --request POST \
--url 'https://api.scanova.io/multi-users/access-levels/' \
--header 'Authorization: YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"name": "Regional Support",
"permissions": [23, 24, 31]
}'
```
```json 201 theme={null}
{
"id": 42,
"name": "Regional Support",
"slug": null,
"permissions": [
{ "id": 23, "code": "QR_CODE_CAN_VIEW", "name": "Can view QR Code", "description": "Allows viewing QR code details.", "is_boolean": true },
{ "id": 24, "code": "ANALYTICS_CAN_VIEW", "name": "Can view Analytics", "description": "Allows viewing analytics.", "is_boolean": true },
{ "id": 31, "code": "LEAD_GENERATION_CAN_VIEW", "name": "Can view Lead Lists", "description": "Allows viewing lead lists.", "is_boolean": true }
],
"is_custom": true
}
```
Role name. Must be unique among this account's roles — a duplicate returns a 400 with `{"name": ["Can not create another user role with the same name."]}`.
Permission IDs to grant, from the `permissions[].id` values in the list response above.
To discover permission IDs, either read them off an existing role's `permissions` array (every account has at least the 6 system roles to inspect), or use [List permission categories](/api-reference/management-api/shared-users/permissions) for the full, category-grouped catalog independent of any one role.
## Related
* [List roles](/api-reference/management-api/shared-users/roles-list) — the other operation on this same endpoint.
* [Retrieve a role](/api-reference/management-api/shared-users/roles-retrieve) — look up a single role by id.
* [List permission categories](/api-reference/management-api/shared-users/permissions) — the full permission catalog, independent of any one role.
* [Invite a shared user](/api-reference/management-api/shared-users/invite) — assign a role ID from this list when inviting or updating a teammate.
* [Update a shared user](/api-reference/management-api/shared-users/update) — assign a role ID from this list when inviting or updating a teammate.
* [Roles & permissions](/team/roles-and-permissions) — the human-facing explanation of what each role grants, including the custom-roles plan gate.
* [Shared users](/team/shared-users) — the dashboard's Users table where these roles are assigned via the UI.
# Delete a custom role
Source: https://docs.scanova.io/api-reference/management-api/shared-users/roles-delete
api-reference/openapi/management-api.json DELETE /multi-users/access-levels/{pk}/
DELETE /multi-users/access-levels/{pk}/
Deletes one of the account's own custom roles on `api.scanova.io`, authenticated with your raw API key.
```
DELETE https://api.scanova.io/multi-users/access-levels/{pk}/
Authorization: YOUR_API_KEY
```
The role's `id`.
```bash cURL theme={null}
curl -X DELETE "https://api.scanova.io/multi-users/access-levels/42/" \
-H "Authorization: YOUR_API_KEY"
```
A successful call returns `204 No Content`.
## Reassign teammates first
If the role is still assigned to any shared user, this returns a friendly `400` (not a raw database integrity error) asking you to reassign those teammates to a different role before deleting. As with update, a `pk` belonging to a system role or another account's custom role returns `404`.
## Related
* [Retrieve a role](/api-reference/management-api/shared-users/roles-retrieve) — check who's assigned before deleting.
* [Update a shared user](/api-reference/management-api/shared-users/update) — reassign a teammate off this role first.
* [List roles](/api-reference/management-api/shared-users/roles-list) — pick a replacement role.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# List roles
Source: https://docs.scanova.io/api-reference/management-api/shared-users/roles-list
api-reference/openapi/management-api.json GET /multi-users/access-levels/
GET /multi-users/access-levels/
Every shared user is assigned a role — Scanova's dashboard calls these "access levels" internally, and the same term appears throughout this endpoint. Use this endpoint to look up available roles before inviting a teammate via [`POST /multi-users/`](/api-reference/management-api/shared-users/invite), or to create a custom role from your own systems.
This endpoint was not previously documented. It requires an API key with `MANAGEMENT_API` (or `MANAGEMENT_API_SANDBOX`) quota — see the [API overview](/api-reference/management-api/overview). For what each role actually grants a teammate, see the human-facing [Roles & permissions](/team/roles-and-permissions) guide — this page only documents the wire format.
```bash cURL theme={null}
curl --request GET \
--url 'https://api.scanova.io/multi-users/access-levels/' \
--header 'Authorization: YOUR_API_KEY'
```
```json 200 theme={null}
{
"count": 6,
"next": null,
"previous": null,
"results": [
{
"id": 3,
"name": "Viewer",
"slug": "viewer",
"permissions": [
{
"id": 23,
"code": "QR_CODE_CAN_VIEW",
"name": "Can view QR Code",
"description": "Allows viewing QR code details.",
"is_boolean": true
}
],
"is_custom": false
},
{
"id": 4,
"name": "Analyst",
"slug": "analyst",
"permissions": ["..."],
"is_custom": false
},
{
"id": 5,
"name": "Billing Manager",
"slug": "billing-manager",
"permissions": ["..."],
"is_custom": false
},
{
"id": 6,
"name": "Manager",
"slug": "manager",
"permissions": ["..."],
"is_custom": false
},
{
"id": 7,
"name": "Admin",
"slug": "admin",
"permissions": ["..."],
"is_custom": false
},
{
"id": 8,
"name": "Full Access",
"slug": "full-access",
"permissions": ["..."],
"is_custom": false
}
]
}
```
A fresh account returns exactly these 6 system roles (Viewer, Analyst, Billing Manager, Manager, Admin, Full Access), matching the [Roles & permissions](/team/roles-and-permissions#default-system-roles) picker in the dashboard's Invite Users drawer. IDs shown above are illustrative, and permission arrays are truncated for brevity — a real response includes every permission the role grants, in full.
Results are paginated (page size 10 by default) and can include custom roles the account has created, alongside the 6 system defaults.
Filter to `system` (the built-in roles, `user` is null on these) or `custom` (roles this account created).
Substring match against the role name.
`name` or `-name`.
### Response fields (per role)
Role ID — pass this as `access_level` when inviting or updating a shared user.
Role display name, e.g. `Manager`.
Stable slug for system roles (e.g. `manager`); `null` for custom roles.
`false` for the 6 built-in system roles, `true` for a role this account created itself.
Every permission this role grants.
Permission ID — pass this in `permissions` when creating a custom role below.Stable permission code, e.g. `QR_CODE_CAN_VIEW`.Human-readable permission name.Longer description of what the permission allows.Whether this permission is a simple on/off flag (`true`) rather than a numeric quota.
## Related
* [Create a custom role](/api-reference/management-api/shared-users/roles-create) — the other operation on this same endpoint.
* [Invite a shared user](/api-reference/management-api/shared-users/invite) — assign a role ID from this list when inviting or updating a teammate.
* [Update a shared user](/api-reference/management-api/shared-users/update) — assign a role ID from this list when inviting or updating a teammate.
* [Roles & permissions](/team/roles-and-permissions) — the human-facing explanation of what each role grants, including the custom-roles plan gate.
* [Shared users](/team/shared-users) — the dashboard's Users table where these roles are assigned via the UI.
# Retrieve a role
Source: https://docs.scanova.io/api-reference/management-api/shared-users/roles-retrieve
api-reference/openapi/management-api.json GET /multi-users/access-levels/{pk}/
GET /multi-users/access-levels/{pk}/
Retrieves a single role (access level) on `api.scanova.io`, authenticated with your raw API key.
```
GET https://api.scanova.io/multi-users/access-levels/{pk}/
Authorization: YOUR_API_KEY
```
The role's `id`, from [List roles](/api-reference/management-api/shared-users/roles-list).
```bash cURL theme={null}
curl "https://api.scanova.io/multi-users/access-levels/3/" \
-H "Authorization: YOUR_API_KEY"
```
```json 200 theme={null}
{
"id": 3,
"name": "Viewer",
"slug": "viewer",
"permissions": [
{ "id": 23, "code": "QR_CODE_CAN_VIEW", "name": "Can view QR Code", "description": "Allows viewing QR code details.", "is_boolean": true }
],
"is_custom": false
}
```
Visible: the account's own custom roles, plus every system role (`is_custom: false`) — those are shared across all accounts, not just this one's.
## Related
* [List roles](/api-reference/management-api/shared-users/roles-list) — find a role `id`.
* [Update a custom role](/api-reference/management-api/shared-users/roles-update) — modify this role, if it's one of the account's own.
* [Delete a custom role](/api-reference/management-api/shared-users/roles-delete) — remove this role, if it's one of the account's own.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Update a custom role
Source: https://docs.scanova.io/api-reference/management-api/shared-users/roles-update
api-reference/openapi/management-api.json PATCH /multi-users/access-levels/{pk}/
PATCH /multi-users/access-levels/{pk}/
Updates one of the account's own custom roles on `api.scanova.io`, authenticated with your raw API key.
```
PUT https://api.scanova.io/multi-users/access-levels/{pk}/
PATCH https://api.scanova.io/multi-users/access-levels/{pk}/
Authorization: YOUR_API_KEY
```
The role's `id`.
Both `PUT` and `PATCH` are supported (`PUT` for a full replace, `PATCH` for a partial update) and accept the same `name`/`permissions` fields documented for [Create a custom role](/api-reference/management-api/shared-users/roles-create).
```bash cURL theme={null}
curl -X PATCH "https://api.scanova.io/multi-users/access-levels/42/" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "permissions": [23, 24, 31, 40] }'
```
```json 200 theme={null}
{
"id": 42,
"name": "Regional Support",
"slug": null,
"permissions": [
{ "id": 23, "code": "QR_CODE_CAN_VIEW", "name": "Can view QR Code", "description": "Allows viewing QR code details.", "is_boolean": true }
],
"is_custom": true
}
```
## System roles can't be edited here
Only the account's own custom roles (`is_custom: true`) are writable through this endpoint — a `pk` belonging to a system role (or another account's custom role) returns `404`, the same as one that doesn't exist at all.
## Related
* [Retrieve a role](/api-reference/management-api/shared-users/roles-retrieve) — see the role before updating it.
* [Create a custom role](/api-reference/management-api/shared-users/roles-create) — the field reference this update reuses.
* [Delete a custom role](/api-reference/management-api/shared-users/roles-delete) — remove this role entirely.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Update a shared user
Source: https://docs.scanova.io/api-reference/management-api/shared-users/update
api-reference/openapi/management-api.json PUT /multi-users/{pk}/
PUT /multi-users/{pk}/
Shared users are the teammates you invite into your Scanova account — the same list shown on the dashboard's **Users** table. This endpoint lets you list, invite, update, or remove them programmatically.
This endpoint was not previously documented. It requires an API key with `MANAGEMENT_API` (or `MANAGEMENT_API_SANDBOX`) quota, sent as the raw `Authorization` header value — see the [API overview](/api-reference/management-api/overview) — plus the account's own Team quota for shared users. For what each role actually grants, see [Roles & permissions](/team/roles-and-permissions); for the wire format of roles themselves, see [Roles (access levels)](/api-reference/management-api/shared-users/roles-list).
The shared-user record ID from the list response above.
```bash Update role theme={null}
curl --request PUT \
--url 'https://api.scanova.io/multi-users/118/' \
--header 'Authorization: YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{"access_level": 3}'
```
```bash Deactivate theme={null}
curl --request PUT \
--url 'https://api.scanova.io/multi-users/118/' \
--header 'Authorization: YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{"is_active": false}'
```
`PUT` returns the same shape as the invite response above.
`email` and `name` are create-only — sending them in a `PUT` body is silently ignored rather than erroring, since a shared user's identity can't be changed after the invite is sent. To update the role, tag scope, or active state, use only the fields you intend to change.
## Related
* [List shared users](/api-reference/management-api/shared-users/list) — find the `pk` to operate on here.
* [Retrieve a shared user](/api-reference/management-api/shared-users/retrieve) — another operation on this same `{pk}` endpoint.
* [Remove a shared user](/api-reference/management-api/shared-users/remove) — another operation on this same `{pk}` endpoint.
* [List roles](/api-reference/management-api/shared-users/roles-list) — look up or create the `access_level` ID this endpoint's invite/update calls require.
* [Create a custom role](/api-reference/management-api/shared-users/roles-create) — look up or create the `access_level` ID this endpoint's invite/update calls require.
* [Shared users](/team/shared-users) — the dashboard's own Users table, showing the same invite/deactivate/remove actions.
* [Roles & permissions](/team/roles-and-permissions) — what each role actually grants a teammate.
# Detach a tag from a shared user
Source: https://docs.scanova.io/api-reference/management-api/tags/attached-detach
api-reference/openapi/management-api.json DELETE /tag/attached/
DELETE /tag/attached/
Removes a shared user's attribution for a tag, on `api.scanova.io`, authenticated with your raw API key.
```
DELETE https://api.scanova.io/tag/attached/
Authorization: YOUR_API_KEY
```
The shared user's email address. Must belong to an existing shared user on this account.
The tag name to detach. Required for `DELETE` (optional on the corresponding `GET`).
```bash cURL theme={null}
curl -X DELETE "https://api.scanova.io/tag/attached/?added_by=teammate@example.com&tag=vip" \
-H "Authorization: YOUR_API_KEY"
```
A successful call returns `204 No Content`.
## What this actually does
This does **not** delete the tag, and it does **not** remove the tag from any QR Code. It only clears the *attribution* — which shared user gets credit for having added that tag — on every matching `TagRelation` row. The QR Codes keep the tag; it simply stops showing up as "added by" that teammate.
`400` if `added_by` isn't a shared user on this account, or if there's no matching tag-relation row to clear (`{"error": ["Tag does not exist."]}`).
## Related
* [List tags attached by a shared user](/api-reference/management-api/tags/attached-list) — find what to detach.
* [Shared user management](/api-reference/management-api/shared-users/list) — find `added_by` email addresses.
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# List tags attached by a shared user
Source: https://docs.scanova.io/api-reference/management-api/tags/attached-list
api-reference/openapi/management-api.json GET /tag/attached/
GET /tag/attached/
Lists tags that a specific shared user has personally attached to QR Codes, on `api.scanova.io`, authenticated with your raw API key.
```
GET https://api.scanova.io/tag/attached/
Authorization: YOUR_API_KEY
```
The shared user's email address. Must belong to an existing shared user on this account.
Filter to a single tag name.
```bash cURL theme={null}
curl "https://api.scanova.io/tag/attached/?added_by=teammate@example.com" \
-H "Authorization: YOUR_API_KEY"
```
```json 200 theme={null}
[
{ "id": 12, "name": "vip" }
]
```
Not paginated — returns a plain array. This is for auditing/managing what one team member has tagged, distinct from [tag-based permission scoping](/api-reference/management-api/tags/shared) — a tag can show up here even if it isn't one the shared user is permission-scoped to see.
## Related
* [Detach a tag from a shared user](/api-reference/management-api/tags/attached-detach) — remove one of these attributions.
* [List tags visible to your shared-user role](/api-reference/management-api/tags/shared) — what this shared user can *see*, not what they've tagged.
* [Shared user management](/api-reference/management-api/shared-users/list) — find `added_by` email addresses (the account's shared users).
* [API overview](/api-reference/management-api/overview) — the auth scheme and quota rules that apply to this endpoint.
# Create a custom tag
Source: https://docs.scanova.io/api-reference/management-api/tags/custom-create
api-reference/openapi/management-api.json POST /tag/custom/
POST /tag/custom/
Creates a custom tag for the requesting account on `api.scanova.io`, authenticated with your raw API key.
```
POST https://api.scanova.io/tag/custom/
Authorization: YOUR_API_KEY
```
Tag name, up to 50 characters. Rejected if it contains `<` or `>` (blocks e.g. `
```
## Option A: Install directly in HTML (recommended)
Paste the snippet inside your site's `` tag, **before the closing ``**:
```html theme={null}
Your Page
```
**Where to paste in common platforms:**
| Platform | Where to add the snippet |
| - | - |
| WordPress | Appearance → Theme Editor → `header.php`, before `` |
| Shopify | Online Store → Themes → Edit Code → `theme.liquid`, before `` |
| Webflow | Project Settings → Custom Code → Head Code |
| Squarespace | Settings → Advanced → Code Injection → Header |
| Wix | Settings → Custom Code → Add Code → Head |
| Custom HTML | Directly in your HTML template `` |
## Option B: Google Tag Manager
Use GTM if you manage all your tags from the GTM console and cannot edit your site's HTML directly.
1. Open your GTM container
2. Click **Tags → New**
3. Choose **Custom HTML** as the tag type
4. Paste the full Scanova snippet
5. Set the trigger to **All Pages**
6. Name the tag (e.g. "Scanova Conversion Tracking") and click **Save**
7. **Submit** and **Publish** the container
Do not install the snippet both directly in HTML and via GTM on the same page. This will load the SDK twice and send duplicate events.
**GTM tips:**
* Place the tag to fire as early as possible — use "DOM Ready" or "Page View" triggers, not "Window Loaded"
* If your site is a single-page app (SPA), read the [SPA & Framework Guide](/conversion-tracking/browser/spa-frameworks) before configuring GTM triggers
## Step 2: Verify the installation
After deploying your snippet:
1. Open your website in a browser
2. Open **DevTools → Network** tab
3. Filter by `/ct`
4. Reload the page
You should see a `POST` request to `https://t.scanova.io/ct` returning `200`.
For a full verification checklist, see [Verify & Debug](/conversion-tracking/browser/verify).
## Next steps
Learn all init options and what each toggle controls.
Track specific user actions from your JavaScript code.
React, Next.js, and Vue-specific setup.
Confirm events are being received correctly.
# Browser SDK Overview
Source: https://docs.scanova.io/conversion-tracking/browser/overview
Track user behaviour on your website after a QR Code scan using the Scanova Browser SDK — page views, clicks, scroll depth, form submissions, and custom events.
The Scanova Browser SDK is a lightweight JavaScript snippet (\~10 KB) that you add to your website once. After installation, it automatically tracks page views, link clicks, scroll depth, and form submissions — all attributed to the QR Code scan that brought the user to your site.
## What the SDK does
When a user arrives at your site via a QR Code scan, the URL contains a `scnv` parameter:
```
https://yoursite.com/?scnv=7ad26d4f-3181-4ef8-b6ca-b8f59499dd43
```
The SDK:
1. **Reads the `scnv` parameter** and saves it to `localStorage` (60-day expiry)
2. **Tracks built-in events** automatically (page views, clicks, scroll, forms) based on your configuration
3. **Attaches attribution** — every event carries the `scan_session_id`, linking it to the originating QR Code
4. **Persists across pages** — if a user navigates to another page on your site, the attribution follows them
## Auto-tracked events
Enable these with a single toggle in the init options:
| Option | What it tracks | Event type sent |
| - | - | - |
| `autoPageview` | Every page load | `pageview` |
| `autoClicks` | Clicks on links, buttons, and `[role="button"]` elements | `click` |
| `autoForms` | Form submissions | `form_submit` |
| `autoScroll` | Scroll depth at 25%, 50%, 75%, and 90% | `scroll` |
Full details: [Auto-Tracked Events](/conversion-tracking/browser/auto-tracking)
## Custom events
Beyond auto-tracking, you can send any event from your JavaScript code:
```javascript theme={null}
scanova('track', 'cta_click', { button: 'Start Free Trial' });
```
Full details: [Custom Events](/conversion-tracking/browser/custom-events)
## Requirements
* A website where you can edit the HTML `` section or add a tag via Google Tag Manager
* A tracking site created in **Analytics → Conversion Tracking** with your domain added to **Allowed Domains**
* The `site_id` from the dashboard
## Before you install
Make sure:
* [ ] Your tracking site is created in the dashboard
* [ ] Your website's domain is listed under **Allowed Domains** for that site
* [ ] You know where to add a `
```
The same pattern applies for GDPR consent — only load the SDK after the user grants tracking consent. See [Privacy and GDPR](/conversion-tracking/event-model#privacy-and-gdpr) for details.
# SPA & Framework Setup
Source: https://docs.scanova.io/conversion-tracking/browser/spa-frameworks
Install the Scanova Browser SDK in React, Next.js, Vue, and other single-page application frameworks. Avoid double-init and capture route-change page views correctly.
Single-page applications (SPAs) have a different page lifecycle than traditional multi-page sites. The browser does not fully reload on navigation, so the standard `autoPageview` will only fire once — on the initial load. This guide shows how to handle tracking correctly in React, Next.js, and Vue.
## The key difference with SPAs
In a traditional website, every page navigation triggers a full browser reload, which re-runs the SDK and fires a `pageview`. In a SPA:
* The page loads **once**
* Subsequent navigations are JavaScript-driven (the URL changes, but the page does not reload)
* `autoPageview` fires only on that first load
* **You must manually fire a `pageview` event on each route change**
## General rules for all SPAs
1. **Load the SDK snippet once** — in your root HTML file or root component, not in every page/route component
2. **Call `scanova('init', ...)` once** — in your app's entry point
3. **Disable `autoPageview`** — manage page view events yourself on route changes
4. **Fire `scanova('track', 'pageview')` on each route change** — using your router's navigation event or hook
***
## React (Create React App / Vite)
**`public/index.html`** — load the SDK:
```html theme={null}
```
**`src/main.jsx`** or **`src/App.jsx`** — init once and track route changes:
```jsx theme={null}
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
// Init once at app boot (outside any component)
window.scanova?.('init', 'YOUR_SITE_ID', {
autoPageview: false, // manage manually
autoClicks: true,
autoForms: true,
autoScroll: true,
});
// Hook to track route changes
function usePageTracking() {
const location = useLocation();
useEffect(() => {
window.scanova?.('track', 'pageview', {
path: location.pathname,
});
}, [location.pathname]);
}
export default function App() {
usePageTracking();
return ;
}
```
***
## Next.js (App Router)
In Next.js App Router, use a client component in your root layout.
**`app/layout.tsx`:**
```tsx theme={null}
import ScanovaTracker from '@/components/ScanovaTracker';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
```
**`components/ScanovaTracker.tsx`:**
```tsx theme={null}
'use client';
import { usePathname, useSearchParams } from 'next/navigation';
import { useEffect } from 'react';
export default function ScanovaTracker() {
const pathname = usePathname();
const searchParams = useSearchParams();
useEffect(() => {
window.scanova?.('track', 'pageview', {
path: pathname,
});
}, [pathname, searchParams]);
return null;
}
```
***
## Next.js (Pages Router)
**`pages/_app.tsx`:**
```tsx theme={null}
import { useEffect } from 'react';
import { useRouter } from 'next/router';
import type { AppProps } from 'next/app';
export default function App({ Component, pageProps }: AppProps) {
const router = useRouter();
useEffect(() => {
// Init once
(window as any).scanova?.('init', 'YOUR_SITE_ID', {
autoPageview: false,
autoClicks: true,
autoForms: true,
autoScroll: true,
});
// Track initial page
(window as any).scanova?.('track', 'pageview', { path: router.pathname });
// Track subsequent navigations
const handleRouteChange = (url: string) => {
(window as any).scanova?.('track', 'pageview', { path: url });
};
router.events.on('routeChangeComplete', handleRouteChange);
return () => router.events.off('routeChangeComplete', handleRouteChange);
}, []);
return ;
}
```
Add the SDK loader to **`pages/_document.tsx`**:
```tsx theme={null}
import { Html, Head, Main, NextScript } from 'next/document';
export default function Document() {
return (
);
}
```
***
## Vue 3 (Vue Router)
**`index.html`** — load the SDK:
```html theme={null}
```
**`src/main.ts`:**
```typescript theme={null}
import { createApp } from 'vue';
import { createRouter } from 'vue-router';
import App from './App.vue';
const router = createRouter({ ... });
// Init once
window.scanova?.('init', 'YOUR_SITE_ID', {
autoPageview: false,
autoClicks: true,
autoForms: true,
autoScroll: true,
});
// Track route changes
router.afterEach((to) => {
window.scanova?.('track', 'pageview', { path: to.path });
});
createApp(App).use(router).mount('#app');
```
***
## Common mistakes
| Mistake | Result | Fix |
| - | - | - |
| Calling `scanova('init', ...)` in every page/route component | Multiple init calls → duplicate auto-tracking listeners → doubled events | Call `init` once in the app root only |
| Using `autoPageview: true` in a SPA | Only fires on first load, misses all navigations | Disable it, track manually on route change |
| Adding the SDK script to every page component | SDK loads multiple times | Load from root HTML or root layout once |
| Not waiting for SDK to load before calling `track` | Silent failures | Use `window.scanova?.()` optional chaining, or the queue pattern (calls before SDK loads are queued automatically) |
| Using `autoScroll: true` and expecting per-route scroll milestones | Milestones are not reset on route change — a user who scrolled 75% on `/home` will not re-trigger those milestones on `/pricing` | Disable `autoScroll` in SPAs and send `scroll` events manually if per-route scroll depth matters |
# Verify & Debug Browser SDK
Source: https://docs.scanova.io/conversion-tracking/browser/verify
Confirm the Scanova Browser SDK is installed correctly, events are being sent, and attribution is working. Step-by-step DevTools checklist.
## Verification checklist
Use this checklist after installing the snippet to confirm everything is working:
### 1. Check for the network request
1. Open your website in Chrome or Firefox
2. Open **DevTools** (F12 or right-click → Inspect)
3. Go to the **Network** tab
4. Filter by `/ct`
5. Hard-reload the page (Ctrl+Shift+R / Cmd+Shift+R)
You should see a `POST` request to `https://t.scanova.io/ct` with status `200`.
### 2. Inspect the request payload
Click the `/ct` request → **Payload** tab. Confirm:
* `site_id` matches your tracking site ID from the dashboard
* `event_type` is `pageview` (if `autoPageview: true`)
* `scan_session_id` is present (if you visited with a `?scnv=` URL parameter)
**Example of a healthy payload:**
```json theme={null}
{
"event_id": "f9ac7db6-f900-4d8e-8918-c846834195a8",
"site_id": "YOUR_SITE_ID",
"event_type": "pageview",
"scan_session_id": "7ad26d4f-3181-4ef8-b6ca-b8f59499dd43",
"page_url": "https://yoursite.com/?scnv=7ad26d4f-3181-4ef8-b6ca-b8f59499dd43",
"timestamp": "2026-05-13T10:00:00.000Z"
}
```
### 3. Test QR attribution
Visit your page with a `?scnv=` parameter (simulate a scan):
```
https://yoursite.com/?scnv=00000000-0000-0000-0000-000000000001
```
Check the payload — `scan_session_id` should match the value you used.
### 4. Enable debug mode temporarily
Add `debug: true` to your init call and reload. The SDK prints all activity to the browser console:
```javascript theme={null}
scanova('init', 'YOUR_SITE_ID', { debug: true, autoPageview: true });
```
Console output should show:
```
[QCG SDK] Initialized with Site ID: YOUR_SITE_ID
[QCG SDK] Loaded version 1.x.x
```
Remove `debug: true` before deploying to production.
***
## Common errors and fixes
### No `/ct` request appears
| Cause | Fix |
| - | - |
| Snippet not in `` | Paste snippet before ``, not in `` |
| Script blocked by CSP | Add `https://cdn.scanova.io` and `https://t.scanova.io` to your `Content-Security-Policy` |
| Ad blocker or browser extension blocking the request | Test in a private/incognito window with extensions disabled |
| `autoPageview: false` and no manual `track` call | Either enable `autoPageview` or add a manual event |
| Called `init` but SDK hasn't loaded yet | The SDK queues calls made before it loads — this should resolve automatically |
### `400` — Bad request
The `site_id` is invalid, empty, or belongs to an inactive site. Double-check that:
* You are using the `site_id` from the correct tracking site in the dashboard
* The site is active (check the status in the dashboard)
### `403` — Domain not allowed
Your website's domain is not in the **Allowed Domains** list for this tracking site.
1. Go to **Analytics → Conversion Tracking**
2. Select your site → click **Edit**
3. Add the exact hostname (e.g. `yoursite.com`, `www.yoursite.com`, `staging.yoursite.com`)
4. Save
The domain check uses the `Origin` or `Referer` header of the request. Make sure you add exactly the hostname shown in the browser address bar — including or excluding `www` matters.
### `422` — Validation error
The event payload failed validation. Common causes:
* `event_type` is missing or empty
* A field exceeds the allowed length
* `metadata` contains a raw email address — use `user_identifiers` with a hashed value instead
* `metadata` exceeds 10 KB
### `429` — Rate limit exceeded
Your site is sending events faster than the allowed rate (100 events/min per IP for browser events). This is rare during normal usage. If you see this in testing, space out your test events.
### Events appear in network but not in dashboard
* Wait up to 60 seconds — there is a short processing delay
* Confirm you are looking at the correct tracking site in the dashboard
* Verify the `site_id` in the event payload matches the dashboard site
### No `/ct` request appears for a specific visit
If a visit produces no network request, the user most likely arrived without a QR Code scan — the `?scnv=` parameter is absent. The SDK skips sending any event when there is no `scan_session_id`. This is expected behaviour: the SDK only tracks QR-scan-originated traffic.
### Two events firing per page load
You have both `autoPageview: true` and a manual `scanova('track', 'page_view', ...)` call in your code. Remove one. See [Auto-Tracked Events](/conversion-tracking/browser/auto-tracking#page-view) for details.
# Event Model Reference
Source: https://docs.scanova.io/conversion-tracking/event-model
Complete field reference for browser and server conversion events — what to send, what each field does, and how events are stored and enriched.
Every event you send to Scanova Conversion Tracking — whether from the browser or your server — follows a common structure. This page documents every field you can include and explains what the tracking pipeline does with it.
## Browser events (`POST /ct`)
Browser events are sent automatically by the SDK or manually via `scanova('track', ...)`. They represent actions that happen in the user's browser.
### Fields you can send
| Field | Type | Required | Description |
| - | - | - | - |
| `event_id` | UUID string | No | Unique identifier for this event. Auto-generated by the SDK if omitted. Send a stable ID from your code if you want safe retries. |
| `site_id` | string | Yes | Your tracking site ID from the dashboard. |
| `event_type` | string | Yes | The type of event. Use snake\_case (e.g. `page_view`, `cta_click`, `signup_completed`). |
| `scan_session_id` | UUID string | No | Set automatically by the SDK from the `scnv` URL parameter. Required for QR attribution. |
| `web_session_id` | UUID string | No | Set automatically by the SDK per browser session (30-minute timeout). |
| `visitor_id` | UUID string | No | Set automatically by the SDK. Persists for 1 year via cookie. |
| `page_url` | string | No | Full URL of the current page including query string. |
| `page_title` | string | No | Page title. Not sent by the Browser SDK; only useful for custom direct API integrations. |
| `referrer` | string | No | The referring URL. |
| `event_time` | ISO 8601 string | No | Event time in UTC. Defaults to server receive time if omitted. An explicit value is honoured rather than replaced with receive time, so a backdated event lands in the window it names — useful when seeding or migrating data. Note: the Browser SDK sends a `timestamp` key which is a separate, internal field — when calling the API directly, use `event_time`. |
| `device` | object | No | Browser/device context — see below. |
| `metadata` | object | No | Custom key-value data. Max 10 KB, max depth 5. No raw email addresses. |
**`device` object fields:**
| Field | Description |
| - | - |
| `user_agent` | Browser user-agent string |
| `screen_width` | Screen width in pixels |
| `screen_height` | Screen height in pixels |
| `language` | Browser language (e.g. `en-US`) |
### Example browser event payload
```json theme={null}
{
"event_id": "f9ac7db6-f900-4d8e-8918-c846834195a8",
"site_id": "N74rwgykxCwUe7SDFb2BMVN8Kc0aCQvKajiUKz9MSk3Bldn70jw8uLE3dUTgeS6r",
"event_type": "cta_click",
"scan_session_id": "7ad26d4f-3181-4ef8-b6ca-b8f59499dd43",
"page_url": "https://yoursite.com/?scnv=7ad26d4f-3181-4ef8-b6ca-b8f59499dd43",
"referrer": "",
"timestamp": "2026-05-13T10:00:00.000Z",
"device": {
"user_agent": "Mozilla/5.0 ...",
"screen_width": 1680,
"screen_height": 1050,
"language": "en-US"
},
"metadata": {
"button_text": "Start Free Trial",
"section": "pricing"
}
}
```
***
## Server events (`POST /server-events`)
Server events are sent from your backend using an API key. They represent actions that happen on your server, such as completed purchases, confirmed sign-ups, or leads created in your CRM.
### Fields you can send
| Field | Type | Required | Description |
| - | - | - | - |
| `event_id` | UUID string | Recommended | Unique event ID for safe retries. Generate once, persist, reuse on retry. |
| `site_id` | string | Yes | Your tracking site ID. Must match the API key's authorized site. |
| `event_name` | string | Yes | Semantic event name (e.g. `purchase`, `signup`, `lead`). |
| `scan_session_id` | UUID string | Yes | Links this server event to the originating QR scan. Pass from browser to your server. |
| `web_session_id` | UUID string | No | The browser's web session ID — pass from the browser if available, for richer session linking. |
| `visitor_id` | UUID string | No | The browser's persistent visitor ID — pass from the browser if available. |
| `event_time` | ISO 8601 string | No | When the event occurred. Defaults to time of receipt. |
| `conversion_value` | object | No | `{ "amount": 99.99, "currency": "USD" }`. Currency must be a 3-letter ISO 4217 code. |
| `user_identifiers` | object | No | Hashed user identifiers — see below. Never send raw email or phone. |
| `properties` | object | No | Custom key-value metadata. Max 10 KB. No raw emails. |
| `consent` | string | No | `granted`, `denied`, or `pending`. |
**`user_identifiers` object fields (all hashed):**
| Field | Description |
| - | - |
| `email_hash` | SHA-256 hash of the user's lowercase email address |
| `phone_hash` | SHA-256 hash of the user's E.164-format phone number |
| `external_id` | Your internal user/customer ID |
### Example server event payload
```json theme={null}
{
"event_id": "550e8400-e29b-41d4-a716-446655440000",
"site_id": "N74rwgykxCwUe7SDFb2BMVN8Kc0aCQvKajiUKz9MSk3Bldn70jw8uLE3dUTgeS6r",
"event_name": "purchase",
"scan_session_id": "7ad26d4f-3181-4ef8-b6ca-b8f59499dd43",
"conversion_value": { "amount": 49.99, "currency": "USD" },
"user_identifiers": {
"email_hash": "b94d27b9934d3e08a52e52d7da7dabfac484efe04294e576fc583c381f2d9c5d",
"external_id": "usr_1234"
},
"properties": {
"order_id": "ord_9876",
"plan": "pro"
}
}
```
***
## Event naming conventions
Use consistent, descriptive names in `snake_case`. A clear naming scheme makes reports easier to read.
| Good | Avoid |
| - | - |
| `page_view` | `pageView`, `PageView`, `pv` |
| `cta_click` | `click1`, `btnClick` |
| `signup_completed` | `signup`, `reg` |
| `purchase` | `buy`, `order_placed_final` |
## Idempotency
Both browser and server events support an `event_id` field for deduplication:
* If the same `event_id` arrives more than once, the duplicate is stored but marked `is_duplicate = 1`
* Duplicates are excluded from analytics views automatically
* **Always reuse the same `event_id` when retrying a failed server event**
## Privacy and GDPR
* **Never send raw email addresses** in `metadata` or `properties`. Use `user_identifiers` with hashed values.
* The `consent` field controls PII handling in the pipeline:
* `granted` — all fields are stored normally
* `denied` or `pending` — `page_url`, `referrer`, `city`, and `metadata` are stripped before storage
* The `consent` value itself is always stored as an audit trail
### Sending consent from the browser
The Browser SDK does not have a built-in consent API. The recommended approach is to **conditionally load the SDK** based on your consent management platform's decision:
```html theme={null}
```
### Sending consent from the server
For server events, pass the `consent` value directly in every request payload alongside your other fields.
# How Conversion Tracking Works
Source: https://docs.scanova.io/conversion-tracking/how-it-works
Understand the full data flow from QR Code scan to conversion report — attribution model, session lifecycle, and event pipeline explained.
## The scan-to-report journey
Here is what happens from the moment a user scans your QR Code to the moment that event appears in your reports.
```mermaid theme={null}
sequenceDiagram
participant User
participant QR as QR Code
participant Scanova as Scanova Redirect
participant Site as Your Website
participant SDK as Browser SDK
participant API as Tracking API
participant Reports as Analytics / Reports
User->>QR: Scans QR Code
QR->>Scanova: Opens scnv.io short URL
Scanova->>Site: Redirects to your URL with ?scnv=
Site->>SDK: SDK loads, reads ?scnv parameter
SDK->>SDK: Saves scan_session_id to localStorage
SDK->>API: Sends page_view event with scan_session_id
User->>Site: Clicks a button, fills a form, scrolls
SDK->>API: Sends click / form_submit / scroll events
Site->>API: Your server sends purchase event (server-side)
API->>Reports: Events processed and attributed to QR Code
```
## The scan session
The `scnv` query parameter is the key to attribution. It contains a **scan session ID** — a UUID that uniquely identifies a single scan of a single QR Code by a single user at a specific moment in time.
```
https://yoursite.com/landing?scnv=7ad26d4f-3181-4ef8-b6ca-b8f59499dd43
└─────────────────────────────────────┘
scan_session_id
```
The Browser SDK does the following when it loads on a page with this parameter:
1. Reads the `scnv` value from the URL
2. Saves it to `localStorage` with a 60-day expiry
3. Attaches it as `scan_session_id` to every event sent from that browser
This means if the user navigates away and comes back within 60 days, their subsequent actions are still attributed to the original scan.
## Attribution across pages and sessions
The scan session ID persists across page navigations within the same domain. If a user:
1. Scans a QR Code → lands on `/landing`
2. Clicks through to `/pricing`
3. Signs up on `/signup`
All three page views are attributed to the same QR scan, as long as the SDK is installed on all pages.
## Browser events vs server events
There are two types of events:
| Type | Sent from | Auth | Best for |
| - | - | - | - |
| **Browser events** | User's browser via SDK | None (public) | Page views, clicks, scroll, form submissions |
| **Server events** | Your backend via API | API key required | Purchases, sign-ups, leads, any server-side action |
Both event types accept a `scan_session_id`, which links them to the QR scan. For server events, your backend needs to receive the `scan_session_id` from the browser — typically by passing it in a form field, session cookie, or API call.
## How events are processed
Once the Tracking API receives an event, it goes through a processing pipeline:
1. **Validation** — checks required fields, payload size, and site/domain authorization
2. **Deduplication** — marks repeated `event_id` values as duplicates (they are still stored, not dropped)
3. **Device enrichment** — parses user-agent to extract device type, browser, and OS
4. **Location enrichment** — resolves IP address to country and city using GeoIP
5. **Identity linking** — resolves `scan_session_id` to QR Code ID and user ID from the Scanova database
6. **Fraud detection** — heuristic scoring to flag bot-like behaviour
7. **Privacy / GDPR** — strips PII fields if `consent` is `denied` or `pending`
Processed events appear in your dashboard reports with a short ingestion delay (typically under 10 seconds).
## Identity and sessions
Each event the SDK sends carries three separate identity values. Understanding the difference helps when reading reports:
| Identity | Cookie/Storage key | Lifetime | What it represents |
| - | - | - | - |
| `scan_session_id` | `localStorage._scnv` | 60 days | The specific QR Code scan that started this user journey. The primary attribution key. |
| `web_session_id` | Cookie `_scnv_ws` | 30 minutes of inactivity | One continuous browsing session. Resets after 30 minutes of inactivity, like a standard session. |
| `visitor_id` | Cookie `_scnv_vid` | 1 year | A persistent anonymous identifier for a browser. Allows returning QR scan visitors to be recognised across multiple scan sessions. |
A single visitor may have many web sessions. A single web session may have many events. All events within 60 days of a scan share the same `scan_session_id`.
## Data availability
* Events are ingested and queued immediately on receipt
* Processed events appear in reports within seconds under normal load
* Short delays (up to a few minutes) can occur during high-traffic spikes
# What is Conversion Tracking?
Source: https://docs.scanova.io/conversion-tracking/introduction
Measure what users do on your website after scanning a QR Code — page views, clicks, purchases, and custom events — all attributed back to the exact QR Code that drove the visit.
Scanova **Conversion Tracking** connects offline QR Code scans to online user behaviour. Every time someone scans your QR Code, they carry a unique session identifier to your website. The tracking SDK uses that identifier to attribute every subsequent action — a page view, a button click, a completed purchase — back to the original scan.
## What you can measure
See which QR Codes drive the most visits, from which countries, and on which devices.
Automatically track clicks, scroll depth, and form submissions without writing any code.
Send purchases, sign-ups, and leads securely from your server, attributed to the QR scan.
Pass conversion value and currency to calculate actual ROI per QR Code.
## How QR attribution works
When a user scans your QR Code, Scanova appends a `scnv` parameter to your landing page URL:
```text theme={null}
https://yoursite.com/landing?scnv=7ad26d4f-3181-4ef8-b6ca-b8f59499dd43
```
The Browser SDK reads this parameter on page load, saves it to `localStorage`, and attaches it as `scan_session_id` to every event it sends. This is how a server-side purchase confirmation can be attributed to the QR scan that started the journey — even hours later.
## Two ways to send events
A lightweight JavaScript snippet you add to your website. Automatically tracks page views, clicks, scroll depth, and form submissions. No backend required to get started.
Send conversion events directly from your backend using an API key. Use this for purchases, sign-ups, and any event that happens server-side.
## Before you start
* An active Scanova account
* A tracking site created in **Analytics → Conversion Tracking**
* A website where you can add a `
```
Using Google Tag Manager? See [Install via GTM](/conversion-tracking/browser/install#option-b-google-tag-manager).
## Step 3: Link your QR Codes to the tracking site
For events to be attributed to a specific QR Code, that QR Code's destination domain must match one of your tracking site's **Allowed Domains**.
1. Open a QR Code in the Scanova dashboard
2. Go to its **Settings** or **Redirect** configuration
3. Confirm the destination URL is on a domain you added in Step 1 (e.g. `yoursite.com`)
If the QR Code redirects to a domain that is **not** in the tracking site's Allowed Domains, browser events from that domain will be rejected with `403`. Attribution only works when the destination domain matches.
## Step 4: Verify the SDK is working
1. Open your website in a browser
2. Open DevTools → **Network** tab
3. Filter by `/ct`
4. Reload the page — you should see a `POST` request to `https://t.scanova.io/ct` with a `200` response
If you see the request, the SDK is working. If not, see [Browser SDK Troubleshooting](/conversion-tracking/troubleshooting#browser-sdk-issues).
## Step 5: (Optional) Send server-side conversions
For purchase confirmations, sign-ups, or other backend events, generate an API key and send events from your server.
1. Open **Tracking Setup** on the same site, then the **Track via API** tab
2. Select **Add New Key**, give it a name, copy and store it securely
3. Send a `POST` request to `https://track.scanova.io/server-events`:
```bash theme={null}
curl -X POST "https://track.scanova.io/server-events" \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-d '{
"site_id": "YOUR_SITE_ID",
"event_name": "purchase",
"scan_session_id": "SCAN_SESSION_ID_FROM_BROWSER",
"conversion_value": { "amount": 49.99, "currency": "USD" }
}'
```
Never expose your `X-API-Key` in browser-side code. It is for server use only.
Full guide: [Send Server Events](/conversion-tracking/server/send-events)
## Step 6: View your data
Open **Analytics → Conversion Tracking** in the dashboard, select your site, and open the **Analytics** tab. You should see your first events appear within 30–60 seconds of verifying the SDK in Step 3.
***
## Where to go next
Learn all SDK options — auto-tracking toggles, debug mode, and more.
Track specific user actions like button clicks and form completions.
Send secure backend conversions with attribution.
React, Next.js, and Vue-specific setup instructions.
# Available tools
Source: https://docs.scanova.io/mcp/available-tools
Every tool the Scanova MCP server exposes to a connected AI assistant, grouped by what it works on, with each one's read-only, write, or destructive type.
A connected AI assistant discovers everything below with `tools/list`, then calls each tool with `tools/call`. The server speaks both the current MCP revision (2026-07-28, where a client starts with `server/discover`) and earlier ones (where it starts with `initialize`), and identifies itself as `scanova-mcp`. All 82 tools operate against your own Scanova account, using whichever credential your client authenticated with — see [Setup](/mcp/setup).
Each tool's **Type** reflects its MCP annotation: **Read-only** tools only read, **Write** tools add something or change it without taking anything away, and **Destructive** tools can delete, overwrite, or switch off something that's live — which includes changing what a printed QR Code shows. The groupings below are ours, to make the list easier to scan — the server itself returns one flat list.
## Scanova docs bridge
Lets the assistant consult this documentation site live, mid-conversation.
| Tool | Type | Description |
| :- | :- | :- |
| `probe_docs_mcp` | Read-only | Checks connectivity to Scanova's docs MCP server and lists its available tools |
| `query_docs` | Read-only | Searches this documentation (`mode="search"`), or runs a read-only filesystem-style command — `cat`, `head`, `rg`, `tree`, `jq` — against it (`mode="filesystem"`), to read a specific reference page directly |
## Categories and validation
Reference data the assistant can consult before it creates anything.
| Tool | Type | Description |
| :- | :- | :- |
| `get_qr_categories` | Read-only | Lists available QR Code categories — URL, vCard, WiFi, Document, Social Media, and the rest |
| `get_qr_category_fields` | Read-only | Returns the required and optional `info` JSON field reference for a category, or for every category at once. Static reference data, so it costs no API call |
| `validate_qr_info` | Read-only | Checks a category and `info` payload before `create_qr_code` or `update_qr_code` runs, catching malformed JSON, missing required fields, and invalid values |
## QR Code design
Design previews are never saved: the assistant shows options first, then applies the one you choose with `set_qr_design`.
| Tool | Type | Description |
| :- | :- | :- |
| `get_qr_design_options` | Read-only | Returns the full catalog of design options — data patterns, gradient styles, eye shapes, frames, error-correction levels, and fonts — so the assistant can offer real choices before calling `set_qr_design` |
| `preview_qr_design` | Read-only | Previews a design without saving it: the merged design, scan-safety checks (colour contrast of the dots and eyes, error correction with a logo, inverted or transparent backgrounds), a real scan test on a render, and the image. Works for an existing QR Code or new content |
| `set_qr_design` | Destructive | Applies a visual design to an existing QR Code from friendly named parameters. Only the fields you specify change; everything else is merged onto the QR Code's current design. Refuses a design that fails the scan-safety checks unless you accept the risk (`accept_risk`) |
## QR Code management
| Tool | Type | Description |
| :- | :- | :- |
| `create_qr_code` | Write | Creates a new QR Code, once the name, category, type, and `info` payload are all known |
| `open_qr_code_creation_form` | Read-only | Opens an interactive form instead, for when you have asked for a QR Code without supplying everything it needs |
| `list_qr_codes` | Read-only | Lists QR Codes and Pages in the account, with paging and search, or narrows to just one of the two |
| `retrieve_qr_code` | Read-only | Gets the details of a specific QR Code |
| `update_qr_code` | Destructive | Updates an existing QR Code |
| `activate_qr_code` | Write | Activates a QR Code |
| `deactivate_qr_code` | Destructive | Deactivates a QR Code |
| `delete_qr_code` | Destructive | Permanently deletes a QR Code |
| `download_qr_code` | Read-only | Downloads a QR Code image as PNG, JPG, or PDF |
| `list_tags` | Read-only | Lists the tags on the account's QR Codes. Read-only by design — tags come into being when you attach one to a QR Code, not through a create call |
| `update_qr_tags` | Destructive | Replaces a QR Code's tags with the ones you give (an empty list removes them all). Tags that don't exist yet are created |
## Trash and health
| Tool | Type | Description |
| :- | :- | :- |
| `list_trashed_qr_codes` | Read-only | Lists QR Codes in the trash — deleted but still restorable — with when each was deleted and its scan count |
| `restore_qr_codes` | Write | Restores QR Codes from the trash so they work again, by `qrid`. Counts against your plan's QR Code limits, and never restores the whole trash in one call |
| `get_qr_health` | Read-only | Runs the account's QR Code health check: problems that stop codes working as intended, such as inactive or expired codes, broken destinations, and forms or custom domains that need attention |
## GS1 recalls
| Tool | Type | Description |
| :- | :- | :- |
| `list_gs1_recalls` | Read-only | Lists the account's GS1 batch/lot recall notices — which GTIN and batch, the message shown to people who scan an affected product, and whether each is active |
| `create_gs1_recall` | Write | Recalls a product batch: everyone who scans a GS1 QR Code for that GTIN and batch/lot sees your message instead |
| `update_gs1_recall` | Destructive | Changes a recall notice's message, or switches it off once the recall is over. Its history is kept |
## Folders
| Tool | Type | Description |
| :- | :- | :- |
| `create_folder` | Write | Creates a folder to organize QR Codes or Pages |
| `list_folders` | Read-only | Lists all folders of a given type, `qr` or `page` |
| `update_folder` | Write | Renames an existing folder |
| `delete_folder` | Destructive | Deletes a folder. The tool asks first what should happen to the QR Codes inside it — move them to Uncategorized, or delete them with the folder |
| `move_qr_codes_to_folder` | Write | Moves several QR Codes into a folder at once |
| `unassign_qr_codes_from_folder` | Write | Removes several QR Codes from a folder, leaving them Uncategorized |
## Forms
| Tool | Type | Description |
| :- | :- | :- |
| `list_forms` | Read-only | Lists all lead-capture forms, optionally filtered by active status |
| `retrieve_form` | Read-only | Gets detailed information about a specific form, including its configuration |
| `create_form` | Write | Creates a form from a title and its questions — each with an answer type (short answer, email, multiple choice, star rating, and more) and options for choice questions — and attaches it to a Dynamic QR Code straight away if you pass one. Also accepts a form's full block structure |
| `update_form` | Destructive | Updates a form's name or active status |
| `delete_form` | Destructive | Permanently deletes a form |
| `attach_form_to_qr` | Write | Attaches a lead-capture form to a QR Code, by the form's numeric `id` or its `form_id` |
| `detach_form_from_qr` | Destructive | Removes the lead-capture form from a QR Code |
| `list_form_templates` | Read-only | Lists ready-made form templates to start a new form from |
| `list_form_responses` | Read-only | Lists the responses a form has collected, newest first: the answers, when each came in, and which QR Code it came through. Filters by date range, QR Code, or a search term |
| `get_form_analytics` | Read-only | Gets a form's performance: total responses and skips against the previous period, both by date, and responses by source QR Code |
| `get_form_question_analytics` | Read-only | Gets how each question of a form was answered: how many responses answered it, and for choice, rating, and scale questions how often each answer was given |
| `list_form_notifications` | Read-only | Lists the email alerts set up for new form responses — who they go to, how often, which forms, and whether each is on |
| `create_form_notification` | Write | Emails someone when forms get new responses — for every response, or as a daily, weekly, or monthly digest. The address receives respondents' answers |
| `update_form_notification` | Destructive | Changes a form alert's recipient, frequency, or forms, or switches it off. Its forms are kept unless you pass new ones |
## Lead lists
| Tool | Type | Description |
| :- | :- | :- |
| `list_lead_lists` | Read-only | Lists all lead lists, optionally filtered by active status. Forms is the tool to reach for in new work |
| `retrieve_lead_list` | Read-only | Gets detailed information about a specific lead list, including its webhook configuration |
| `update_lead_list` | Destructive | Updates a lead list's name or active status |
| `delete_lead_list` | Destructive | Permanently deletes a lead list |
| `attach_lead_list_to_qr` | Write | Attaches a lead list to a QR Code for lead capture |
| `detach_lead_list_from_qr` | Destructive | Removes the lead list from a QR Code |
## Analytics and reports
| Tool | Type | Description |
| :- | :- | :- |
| `get_account_stats` | Read-only | Retrieves account-level usage counters — total QR Codes, scans, users, and so on |
| `get_qr_analytics` | Read-only | Retrieves QR Code performance metrics broken down by device, geography, and date |
| `get_analytics_overview` | Read-only | Gets the account's scan overview: all-time totals, this month, recent months, and scans by date |
| `list_analytics_reports` | Read-only | Lists the scan-analytics reports generated for the account, with each one's scope, dates, status, and a download link once it's ready |
| `create_analytics_report` | Write | Generates a scan-analytics report — summary analytics, raw scan data, or both — for chosen QR Codes, tags, or folders over a date range. It's built in the background and emailed to whoever asked for it |
## Plan and billing
Read-only: the assistant can explain the plan and billing history, but changes to a subscription stay in the app.
| Tool | Type | Description |
| :- | :- | :- |
| `get_current_plan` | Read-only | Retrieves the account's active plan — expiry, billing state, and the full list of quotas it grants |
| `list_available_plans` | Read-only | Lists the plans the account can move to, with their features and quotas |
| `get_downgrade_impact` | Read-only | Before a move to a smaller plan, shows which of the account's QR Codes, users, domains, and other resources exceed that plan |
| `list_payments` | Read-only | Lists the account's payment history — amount, date, and status of each payment |
| `list_orders` | Read-only | Lists the account's orders — plan purchases, renewals, and top-ups — and their status |
| `list_quota_topups` | Read-only | Lists extra quota bought on top of the plan, and when each top-up runs out |
## Users and roles
| Tool | Type | Description |
| :- | :- | :- |
| `list_users` | Read-only | Lists all users in the account |
| `get_user` | Read-only | Gets the details of a specific user |
| `add_user` | Write | Invites a new user to the account by email |
| `resend_user_invitation` | Write | Sends a team member's invitation email again, for someone who hasn't accepted yet |
| `remove_user` | Destructive | Removes a user from the account |
| `list_user_roles` | Read-only | Lists every role that can be assigned |
| `create_custom_role` | Write | Creates a custom role. Needs a dedicated plan quota, and returns `403` where the plan doesn't grant it. Call `list_user_roles` first to see what already exists |
| `update_user_role` | Destructive | Changes the role of an existing user |
## Activity
| Tool | Type | Description |
| :- | :- | :- |
| `get_activity_feed` | Read-only | Lists what happened in the account — who created, changed, or deleted what, and when — filtered by event type, team member, or date |
| `get_activity_summary` | Read-only | Gets counts of the account's activity over a period |
## Custom domains
| Tool | Type | Description |
| :- | :- | :- |
| `list_custom_domains` | Read-only | Lists the account's custom short-link domains (for example `qr.example.com`), which one is the default, and whether each is verified |
| `get_custom_domain` | Read-only | Gets one custom domain's details — its DNS (TXT/CNAME) verification, SSL status, and whether it's the default |
## Bulk operations and media
| Tool | Type | Description |
| :- | :- | :- |
| `list_bulk_operations` | Read-only | Lists the account's bulk operations — bulk QR Code generation and updates — with each one's status and results |
| `get_bulk_operation_stats` | Read-only | Gets totals across the account's bulk operations |
| `list_media` | Read-only | Lists files in the account's media library — images, PDFs, videos, and more — for example to pick a logo or a document for a QR Code |
## Integrations and Conversion Tracking
| Tool | Type | Description |
| :- | :- | :- |
| `get_integrations_overview` | Read-only | Shows which integrations the account has connected — webhooks, Zapier, Slack, HubSpot, Google Analytics — and their state |
| `list_webhooks` | Read-only | Lists the account's webhooks: where each sends events, for which forms or QR Codes, and whether it's on |
| `list_tracking_sites` | Read-only | Lists the websites set up for Conversion Tracking, with their domains |
| `list_tracking_funnels` | Read-only | Lists a tracking site's conversion funnels — the steps from scan to conversion |
## Landing pages
| Tool | Type | Description |
| :- | :- | :- |
| `list_page_templates` | Read-only | Lists landing page templates for QR Codes that open a page — Scanova's own, or just the account's saved ones |
**Destructive tools skip a confirmation dialog by design** — the MCP protocol has no built-in "are you sure?" step; whatever confirmation happens is up to your AI client's own UI conventions. Review what a `delete_*`, `update_*`, or `remove_user` tool call is about to do before approving it, the same way you would for any other write action you delegate to an assistant.
## Related
* [MCP overview](/mcp/overview) — what the server is, what it can do at a glance, and its access requirements
* [MCP setup](/mcp/setup) — connect an AI client to the server that exposes these tools
* [Management API overview](/api-reference/management-api/overview) — the underlying API these tools call on your behalf
# MCP overview
Source: https://docs.scanova.io/mcp/overview
Connect Claude, ChatGPT, Cursor, and other MCP-compatible AI tools to your Scanova account, and work on your QR Codes and analytics in plain language.
The **Model Context Protocol (MCP)** is an open protocol for connecting AI assistants (Claude, ChatGPT, Cursor, VS Code Copilot, and others) to external tools and data over a standard JSON-RPC interface. Instead of copy-pasting into a dashboard, you talk to Scanova in plain language from inside the AI tool you already use — "list my active QR Codes," "create a QR Code for our new landing page," "show me last week's scan analytics."
Scanova runs a dedicated MCP server for this, rather than an API wrapper glued onto an existing endpoint. The server is open source: [github.com/trycon/scanova-mcp](https://github.com/trycon/scanova-mcp).
## What you can do with it
Once connected, your AI assistant can act on your Scanova account through the same Management API the dashboard itself uses. Broadly, that covers:
* **QR Codes** — create, list, update, retrieve, activate, deactivate, delete, download as PNG, JPG or PDF, tag, and restore from the trash, plus a health check for codes that need attention
* **Design** — preview designs with scan-safety checks and a real scan test before anything is saved, then apply the one you choose
* **Categories and validation** — look up what a category needs, and check a payload before creating anything with it
* **Analytics** — account-level usage counters, a scan overview, per-QR Code scan analytics by device, geography, and date, and reports generated and emailed to you
* **Folders and tags** — create, list, rename, and delete folders, move QR Codes between them, and read or set the tags in use
* **Forms and lead lists** — create forms from a title and questions, read their responses and analytics, set up email alerts for new responses, and attach forms or lead lists to a QR Code
* **GS1 recalls** — post, change, or end a batch/lot recall notice for GS1 QR Codes
* **Users, roles, and activity** — list, invite, and remove users, change their roles, create custom roles, and read who did what in the account
* **Your plan and billing** — read the active plan, the plans you could move to, what a downgrade would affect, and your payment and order history
* **The rest of your workspace** — custom domains, bulk operations, the media library, integrations and webhooks, Conversion Tracking sites, and landing page templates, read-only
* **Scanova's own docs** — a bridge tool lets the assistant search or read this documentation site live while it works, so it can look up something like the pattern-info schema before calling another tool
See [Available tools](/mcp/available-tools) for the complete, per-tool list with descriptions.
## Where it lives
In the dashboard, go to **Integrations** and open the **Model Context Protocol** card, filed under **Automation**. This opens a setup panel with your server URL, per-client connection steps, and a token fallback for clients that don't support OAuth — see [Setup](/mcp/setup) for the full walkthrough.
## Access requirements
The MCP integration is gated behind an entitlement quota codename, `INTEGRATION_MCP`, checked on both the dashboard card (to show **Connect** instead of **Upgrade**) and on every request the MCP server makes to the Management API on your behalf. This is a plan-managed grant rather than a quota you turn on yourself.
`INTEGRATION_MCP` is distinct from the `MANAGEMENT_API` quota that the plain Management API uses, so holding one does not imply the other. If the **Model Context Protocol** card shows **Upgrade** instead of **Connect**, your account does not have it yet — contact [support@scanova.io](mailto:support@scanova.io).
Generate a connection, and wire it into Claude, ChatGPT, Perplexity, or a config-file-based client.
Every tool the server exposes, grouped by what it works on.
## Related
* [MCP setup](/mcp/setup) — connect Claude, ChatGPT, Perplexity, or a config-file client to the server described here
* [Available tools](/mcp/available-tools) — the complete, per-tool reference for everything the server exposes
* [Management API overview](/api-reference/management-api/overview) — the `environment=mcp` token type and its `INTEGRATION_MCP` quota gate, documented in full
# MCP setup
Source: https://docs.scanova.io/mcp/setup
Connect Claude, ChatGPT, Perplexity, or any other MCP-compatible client to your Scanova account, over OAuth or with a token where OAuth is not supported.
There are two ways to connect, depending on whether your AI tool has native OAuth-based connector support.
## Prerequisite
Your account needs the MCP integration enabled — see [Access requirements](/mcp/overview#access-requirements). If the **Integrations → Model Context Protocol** card shows **Upgrade** instead of **Connect**, the rest of this page won't apply yet.
## Step 1: Open the MCP integration
From the dashboard, go to **Integrations** and click the **Model Context Protocol** card. This opens a setup panel showing your Scanova MCP server URL:
```
https://mcp.scanova.io/mcp
```
with an **OAuth** badge and a copy button. This is the one URL every client below connects to.
## Option A: OAuth-native clients (Claude, ChatGPT, Perplexity)
The panel has a tab per client, each with its own numbered steps and a **Client ID** / **Client Secret** pair shown inline. Copy both from the panel itself — they are not reproduced here. You then complete an OAuth sign-in and approve access to your Scanova account.
In Claude, open **Settings → Connectors**.
Click **+ Add custom connector**.
Paste `https://mcp.scanova.io/mcp` from the panel.
Paste the **Client ID** and **Client Secret** shown in the panel's Claude tab.
Click **Add**, then **Connect**, and approve access. Sign in to Scanova when prompted.
In ChatGPT, open **Settings → Apps → Advanced settings** and turn on **Developer mode**.
Create an App and paste `https://mcp.scanova.io/mcp` as the server URL.
Click **Advanced OAuth settings** and paste the **Client ID** and **Client Secret** shown in the panel's ChatGPT tab.
Authorize Scanova when prompted, then enable it from the tools menu in your chat.
In Perplexity, open **Customise → Connectors** and click **+ Add Custom Connector**.
Add a name and paste `https://mcp.scanova.io/mcp` as the server URL.
Click the **Advanced** toggle and paste the **Client ID** and **Client Secret** shown in the panel's Perplexity tab.
Click **Add**, authorize Scanova when prompted, and start using it in your Perplexity conversations.
## Option B: Config-file clients (Cursor, VS Code, Claude Desktop, or anything else)
For MCP clients that don't have a native OAuth connector UI — they take a JSON config file instead — expand **Use a token instead** at the bottom of the panel. This generates a Scanova [Management API](/api-reference/management-api/overview) key with `environment=mcp`, the same token type described in [Creating & managing API keys](/api-keys/creating-and-managing-keys). Click **Generate token**, then copy the value shown (masked in the panel; copy button reveals the full value to your clipboard).
Add it to your client's MCP config as the `Authorization` header value — this is the exact format documented in the server's own README:
```json Cursor (~/.cursor/mcp.json) theme={null}
{
"mcpServers": {
"scanova-mcp": {
"transport": "http",
"url": "https://mcp.scanova.io/mcp",
"headers": {
"Authorization": "YOUR_SCANOVA_MCP_TOKEN"
}
}
}
}
```
```json VS Code (~/.vscode/mcp.json) theme={null}
{
"mcpServers": {
"scanova-mcp": {
"transport": "http",
"url": "https://mcp.scanova.io/mcp",
"headers": {
"Authorization": "YOUR_SCANOVA_MCP_TOKEN"
}
}
}
}
```
```json Claude Desktop (claude_desktop_config.json) theme={null}
{
"mcpServers": {
"scanova-mcp": {
"transport": "http",
"url": "https://mcp.scanova.io/mcp",
"headers": {
"Authorization": "YOUR_SCANOVA_MCP_TOKEN"
}
}
}
}
```
Put the raw token value in the `Authorization` header — no `Bearer` prefix. The server reads whatever's in that header (it also accepts `X-API-Key` or `Scanova-API-Key`) and forwards it as-is to Scanova's Management API, which rejects anything other than the bare key.
Restart your IDE/client after saving the config.
## Revoking access
**Token clients:** back in the panel's expanded **Use a token instead** section, click the trash icon next to the token and confirm. This immediately disables every MCP client using that token.
**OAuth clients (Claude/ChatGPT/Perplexity):** remove the connector from that AI tool's own connector/settings list — it uses a standard OAuth access token issued to that specific client, independent of the token fallback above.
## Troubleshooting
* **"Invalid token" / tool calls failing with a 401** — the token was revoked or has expired. Generate a new one and update your client's config.
* **Tool not found after adding the server** — restart your IDE or client; most MCP clients only fetch the tool list once, at startup or connection time
* **Connection errors** — double check the URL is exactly `https://mcp.scanova.io/mcp` (not `/health` or the bare origin) and that your network can reach it
## Next step
See [Available tools](/mcp/available-tools) for everything your AI assistant can now do.
## Related
* [MCP overview](/mcp/overview) — what the server does, access requirements, and the `INTEGRATION_MCP` quota gate
* [Available tools](/mcp/available-tools) — every tool available once you are connected
* [Creating & managing API keys](/api-keys/creating-and-managing-keys) — the same key type the token-fallback option here generates, with `environment=mcp`
* [Management API overview](/api-reference/management-api/overview) — the raw-key auth scheme the token fallback authenticates against
# API Authentication
Source: https://docs.scanova.io/conversion-tracking/api/authentication
How to authenticate server-side Conversion Tracking API requests using site-scoped API keys.
Server event endpoints (`POST /server-events` and `POST /server-events/batch`) require an API key. Browser events (`POST /ct`) are public and do not require authentication — they are validated by domain instead.
## API key header
Include your key in every server event request:
```http theme={null}
POST /server-events HTTP/1.1
Host: track.scanova.io
Content-Type: application/json
X-API-Key: YOUR_SITE_API_KEY
```
## Key scope
API keys are **site-scoped**. A key generated for Site A cannot be used to send events for Site B.
The `site_id` in your request body must match the site that issued the key. A mismatch returns `403 Forbidden`.
## Generating a key
Keys are created in the dashboard:
1. Go to **Analytics → Conversion Tracking → Setup Conversion Tracking**
2. Select **Tracking Setup** on your tracking site
3. Open the **Track via API** tab
4. Select **Add New Key** and name it
A site can hold **up to 2 active keys** at a time, so rotate an old key out when you add a new one.
Full instructions: [Generate an API Key](/conversion-tracking/server/generate-api-key-dashboard)
## Security rules
* **Never include your API key in frontend/browser code** — it would be publicly visible
* Store keys in environment variables or a secrets manager (AWS Secrets Manager, HashiCorp Vault, etc.)
* Create separate keys for production and staging environments
* Revoke keys immediately if they are accidentally exposed
## Error responses
| Status | Meaning |
| - | - |
| `401 Unauthorized` | `X-API-Key` header is missing |
| `403 Forbidden` | Key is invalid, revoked, or `site_id` does not match the key's site |
# Browser Events API — POST /ct
Source: https://docs.scanova.io/conversion-tracking/api/browser-events
Reference for the browser event collection endpoint. Used by the Scanova Browser SDK to send page views, clicks, and custom events from user browsers.
The `/ct` endpoint receives browser events sent by the Scanova Browser SDK. In most cases you do not call this endpoint directly — the SDK handles it. This page documents the request format for custom integrations or debugging.
**Base URL:** `https://t.scanova.io`
## Endpoint
```
POST https://t.scanova.io/ct
```
No authentication header is required. Requests are validated by checking the `Origin` or `Referer` header against the site's **Allowed Domains** list.
## Request
**Headers:**
```http theme={null}
Content-Type: application/json
Origin: https://yoursite.com
```
**Body:**
```json theme={null}
{
"event_id": "f9ac7db6-f900-4d8e-8918-c846834195a8",
"site_id": "YOUR_SITE_ID",
"event_type": "cta_click",
"scan_session_id": "7ad26d4f-3181-4ef8-b6ca-b8f59499dd43",
"web_session_id": "2d0c328a-01d0-4010-85f4-f327130d1bd4",
"visitor_id": "363fe851-7d8f-4090-902f-0f5a462829f5",
"page_url": "https://yoursite.com/pricing",
"referrer": "https://yoursite.com/",
"timestamp": "2026-05-13T10:00:00.000Z",
"device": {
"user_agent": "Mozilla/5.0 ...",
"screen_width": 1440,
"screen_height": 900,
"language": "en-US"
},
"metadata": {
"button_text": "Start Free Trial",
"section": "pricing"
}
}
```
## Fields
| Field | Type | Required | Description |
| - | - | - | - |
| `site_id` | string | Yes | Tracking site ID |
| `event_type` | string | Yes | Event name in snake\_case |
| `event_id` | UUID string | No | Deduplication ID. Auto-generated if omitted. |
| `scan_session_id` | UUID string | No | QR scan attribution ID |
| `web_session_id` | UUID string | No | Browser session ID (30-min timeout) |
| `visitor_id` | UUID string | No | Persistent visitor ID (1-year cookie) |
| `page_url` | string | No | Current page URL |
| `referrer` | string | No | Referring URL |
| `timestamp` | ISO 8601 | No | Event time. Defaults to receipt time. |
| `device` | object | No | User-agent, screen dimensions, language |
| `metadata` | object | No | Custom data. Max 10 KB, depth 5. |
| `consent` | string | No | `granted`, `denied`, or `pending` |
## Response
**Success (`200`):**
```json theme={null}
{
"event_id": "f9ac7db6-f900-4d8e-8918-c846834195a8"
}
```
**Error responses:**
| Status | Cause |
| - | - |
| `400` | Missing required field or invalid `site_id` |
| `403` | Request `Origin`/`Referer` not in the site's Allowed Domains |
| `413` | Payload exceeds size limit |
| `422` | Field validation error |
| `429` | Rate limit exceeded (100 requests/min per IP) |
## CORS
The `/ct` endpoint supports CORS. Browser requests from allowed domains are permitted with:
```
Access-Control-Allow-Origin:
Access-Control-Allow-Methods: POST, OPTIONS
```
Preflight (`OPTIONS`) requests return `204` with the appropriate CORS headers.
## Batch endpoint
To send multiple browser events in a single request:
```
POST https://t.scanova.io/collect/batch
```
Request body wraps an array:
```json theme={null}
{
"events": [
{ "site_id": "...", "event_type": "pageview", ... },
{ "site_id": "...", "event_type": "scroll", "metadata": { "scroll_depth": 25 } }
]
}
```
Up to 100 events per batch request.
# Conversion Analytics
Source: https://docs.scanova.io/conversion-tracking/api/ct-analytics
POST /analytics/ct/
Generate conversion analytics for selected QR Codes, with KPI, trend, event/site breakdown, and funnel outputs.
## Query Parameters
| Parameter | Type | Required | Description |
| :- | :- | :- | :- |
| `type` | `string` | Yes | Comma-separated sections to return. Supported: `count`, `trend`, `event_breakdown`, `site_breakdown`, `funnel`, `scan_overview`, `hybrid_funnel`, `summary`, `traffic`, `audience`, `behavior`, `qr_performance`, `raw`. |
| `from` | `string (YYYY-MM-DD)` | Yes | Inclusive start date. |
| `to` | `string (YYYY-MM-DD)` | Yes | Inclusive end date. |
| `all` | `boolean` | No | Include all QR Codes in scope. |
| `search` | `string` | No | Search value used with `all=true`. |
| `category` | `string` | No | Comma-separated category slugs. |
| `users` | `string` | No | Comma-separated user IDs. |
| `conversion_events` | `string` | No | Comma-separated conversion event keys. Default: `purchase,signup,lead,conversion`. |
| `funnel_steps` | `string` | No | Ordered comma-separated event keys for funnel calculation. |
## Request Body
| Field | Type | Required | Description |
| :- | :- | :- | :- |
| `filter_by` | `string` | Yes | One of: `qrid`, `id`, `tags`. |
| `q` | `array[string]` | Yes | Values corresponding to `filter_by`. |
## Example Request
```bash theme={null}
curl -X POST "https://api.scanova.io/analytics/ct/?type=count,trend,event_breakdown,site_breakdown,funnel&from=2026-03-03&to=2026-03-10&search=trac&conversion_events=purchase,signup,lead,conversion&funnel_steps=pageview,click,purchase" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"filter_by": "qrid",
"q": ["Q20a18d4af3e947a1"]
}'
```
## Example Response
```json theme={null}
{
"conversion": {
"meta": {
"warnings": [],
"unlinked_qrs": []
},
"count": {
"total_events": 12,
"total_conversions": 12,
"unique_sessions": 2,
"conversion_rate": 600.0,
"revenue": 0.0,
"primary_currency": null,
"currency_warning": null,
"period": {
"start": "2026-02-08",
"end": "2026-03-10"
}
},
"trend": [
{
"date": "2026-02-26",
"unique_sessions": 1,
"conversions": 7,
"events": 7,
"conversion_rate": 700.0
},
{
"date": "2026-03-02",
"unique_sessions": 1,
"conversions": 5,
"events": 5,
"conversion_rate": 500.0
}
],
"event_breakdown": [
{
"event": "scroll",
"total_events": 7,
"unique_sessions": 2,
"conversions": 7
},
{
"event": "click",
"total_events": 3,
"unique_sessions": 1,
"conversions": 3
},
{
"event": "pageview",
"total_events": 2,
"unique_sessions": 2,
"conversions": 2
}
],
"site_breakdown": [
{
"site": {
"site_id": "pID5CJUPr6VgP53FJLCeNZpk9tQwQi0JpPmECQoxAEeTzrtzoQxCOAkSxrCQb3c4",
"name": "Sample Site 1"
},
"total_events": 12,
"unique_sessions": 2,
"conversions": 12,
"conversion_rate": 600.0
}
],
"funnel": [
{
"step": "pageview",
"sessions": 2,
"drop_off_percent": 0
},
{
"step": "click",
"sessions": 1,
"drop_off_percent": 50.0
},
{
"step": "scroll",
"sessions": 1,
"drop_off_percent": 0.0
}
],
"hybrid_funnel": [
{
"step": "scan",
"sessions": 17,
"drop_off_percent": 0
},
{
"step": "pageview",
"sessions": 2,
"drop_off_percent": 88.24
},
{
"step": "click",
"sessions": 1,
"drop_off_percent": 50.0
},
{
"step": "scroll",
"sessions": 1,
"drop_off_percent": 0.0
}
]
},
"scan_overview": {
"total": 17,
"elevation": {
"percent": 100,
"previous": 0,
"type": "+",
"period": {
"start": "2026-01-08T18:07:00Z",
"end": "2026-02-07T18:07:00Z"
}
},
"unique": 2,
"unique_elevation": {
"percent": 100,
"previous": 0,
"type": "+",
"period": {
"start": "2026-01-08T18:07:00Z",
"end": "2026-02-07T18:07:00Z"
}
},
"scan_qr": 0,
"scan_url": 17
}
}
```
# Export Conversion Analytics
Source: https://docs.scanova.io/conversion-tracking/api/ct-export
POST /analytics/ct/export/
Export aggregated conversion analytics in CSV/XLS/XLSX formats.
## Query Parameters
| Parameter | Type | Required | Description |
| :- | :- | :- | :- |
| `type` | `string` | No | Comma-separated sections. Typical: `count,trend,event_breakdown,site_breakdown,funnel,scan_overview,hybrid_funnel`. |
| `from` | `string (YYYY-MM-DD)` | Yes | Inclusive start date. |
| `to` | `string (YYYY-MM-DD)` | Yes | Inclusive end date. |
| `file_format` | `string` | Yes | One of: `csv`, `xls`, `xlsx`. |
| `sheet` | `string` | No | CSV-only selected sheet key. Examples: `count`, `trend`, `event_breakdown`, `site_breakdown`, `funnel`, `hybrid_funnel`, `scan_overview`. |
| `all` | `boolean` | No | Include all QR Codes in scope. |
| `search` | `string` | No | Search value used with `all=true`. |
| `category` | `string` | No | Comma-separated category slugs. |
| `users` | `string` | No | Comma-separated user IDs. |
| `conversion_events` | `string` | No | Comma-separated conversion event keys. |
| `funnel_steps` | `string` | No | Ordered comma-separated event keys for funnel calculation. |
## Request Body
| Field | Type | Required | Description |
| :- | :- | :- | :- |
| `filter_by` | `string` | Yes | One of: `qrid`, `id`, `tags`. |
| `q` | `array[string]` | Yes | Values corresponding to `filter_by`. |
## Example
```bash theme={null}
curl -X POST "https://api.scanova.io/analytics/ct/export/?type=count,trend,event_breakdown,site_breakdown,funnel,scan_overview,hybrid_funnel&from=2026-03-03&to=2026-03-10&file_format=xlsx" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"filter_by": "qrid",
"q": ["Q20a18d4af3e947a1"]
}'
```
# Export Raw Conversion Events
Source: https://docs.scanova.io/conversion-tracking/api/ct-raw
POST /analytics/ct/raw/
Export row-level conversion events in CSV/XLS/XLSX formats.
## Query Parameters
| Parameter | Type | Required | Description |
| :- | :- | :- | :- |
| `from` | `string (YYYY-MM-DD)` | Yes | Inclusive start date. |
| `to` | `string (YYYY-MM-DD)` | Yes | Inclusive end date. |
| `file_format` | `string` | Yes | One of: `csv`, `xls`, `xlsx`. |
| `all` | `boolean` | No | Include all QR Codes in scope. |
| `search` | `string` | No | Search value used with `all=true`. |
| `category` | `string` | No | Comma-separated category slugs. |
| `users` | `string` | No | Comma-separated user IDs. |
| `conversion_events` | `string` | No | Comma-separated conversion event keys. |
| `funnel_steps` | `string` | No | Ordered comma-separated event keys. |
## Request Body
| Field | Type | Required | Description |
| :- | :- | :- | :- |
| `filter_by` | `string` | Yes | One of: `qrid`, `id`, `tags`. |
| `q` | `array[string]` | Yes | Values corresponding to `filter_by`. |
## Example
```bash theme={null}
curl -X POST "https://api.scanova.io/analytics/ct/raw/?from=2026-03-03&to=2026-03-10&file_format=csv" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"filter_by": "qrid",
"q": ["Q20a18d4af3e947a1"]
}'
```
# Errors & Rate Limits
Source: https://docs.scanova.io/conversion-tracking/api/errors-rate-limits
HTTP status codes, error response format, rate limits, and error handling guidance for Scanova Conversion Tracking APIs.
## HTTP status codes
| Code | Meaning | Action |
| - | - | - |
| `200` | Event accepted | No action needed |
| `400` | Bad request — missing field, invalid format, or inactive `site_id` | Fix the request payload |
| `401` | Missing `X-API-Key` header | Add the `X-API-Key` header |
| `403` | Invalid key, revoked key, or `site_id`/key mismatch | Check your API key and `site_id` |
| `413` | Payload too large | Reduce payload size or split into smaller batches |
| `422` | Validation error — see response body for details | Fix the specific field error |
| `429` | Rate limit exceeded | Back off and retry after the `Retry-After` header value |
| `500` / `502` / `503` | Server error | Retry with exponential backoff |
## Error response format
All error responses return a JSON body with details:
```json theme={null}
{
"detail": [
{
"loc": ["body", "scan_session_id"],
"msg": "field required",
"type": "value_error.missing"
}
]
}
```
For `400` errors with a single message:
```json theme={null}
{
"detail": "site_id not found or inactive"
}
```
## Rate limits
| Endpoint | Limit | Scope |
| - | - | - |
| `POST /ct` (browser events) | 100 requests / min | Per IP address |
| `POST /collect/batch` | 100 requests / min | Per IP address |
| `POST /server-events` | 1,000 requests / min | Per API key |
| `POST /server-events/batch` | 1,000 requests / min | Per API key |
Short bursts above the limit are handled with a small grace window. Design your clients to back off on `429` rather than relying on burst tolerance.
## Error handling pattern
### For server events
```javascript theme={null}
async function sendEvent(payload) {
const response = await fetch('https://track.scanova.io/server-events', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.SCANOVA_API_KEY,
},
body: JSON.stringify(payload),
});
if (response.ok) return await response.json();
const error = await response.json().catch(() => null);
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get('Retry-After') ?? '60', 10);
throw new RetryableError(`Rate limited — retry after ${retryAfter}s`, retryAfter);
}
if (response.status >= 500) {
throw new RetryableError(`Server error ${response.status}`);
}
// 400, 401, 403, 422 — do not retry, fix the request
throw new PermanentError(`Request failed ${response.status}: ${JSON.stringify(error)}`);
}
```
### Retry logic
Only retry on transient errors. Stop immediately on permanent errors.
| Retry? | Status codes |
| - | - |
| Yes — retry with backoff | `429`, `500`, `502`, `503`, `504`, network timeout |
| No — fix the request | `400`, `401`, `403`, `413`, `422` |
See [Idempotency & Retries](/conversion-tracking/server/idempotency-retries) for a full retry implementation with exponential backoff.
## Common `422` causes
| Error | Fix |
| - | - |
| `scan_session_id` invalid format | Must be a valid UUID (e.g. `7ad26d4f-3181-4ef8-b6ca-b8f59499dd43`) |
| `event_name` missing | Required for server events |
| `conversion_value.currency` invalid | Must be a 3-letter ISO 4217 code (e.g. `USD`, `EUR`, `GBP`) |
| `properties` too large | Keep under 10 KB |
| Raw email in `properties` | Use `user_identifiers.email_hash` with a SHA-256 hash instead |
# Batch Server Events API — POST /server-events/batch
Source: https://docs.scanova.io/conversion-tracking/api/events-batch
POST /server-events/batch
Send up to 100 server-side conversion events in a single authenticated request. Recommended for queued delivery and high-volume backends.
Use the batch endpoint when you have multiple events to send — it reduces HTTP overhead and is the preferred approach for worker-based or queue-driven architectures.
Use the site-scoped API key generated from your tracking site dashboard: **Analytics → Conversion Tracking → Setup Conversion Tracking**, then **Tracking Setup → Track via API** on your site.
See [Send Server Events](/conversion-tracking/server/send-events#sending-a-batch-of-events) for a full batch request example.
# Server Events API — POST /server-events
Source: https://docs.scanova.io/conversion-tracking/api/events-collect
POST /server-events
Send a single authenticated server-side conversion event — purchase, signup, lead, or any backend action — attributed to a QR Code scan.
Use this endpoint to report a single backend conversion event. For sending multiple events in one request, use [POST /server-events/batch](/conversion-tracking/api/events-batch).
Generate the required `X-API-Key` from your tracking site: **Analytics → Conversion Tracking → Setup Conversion Tracking**, then **Tracking Setup → Track via API** on your site.
See [Send Server Events](/conversion-tracking/server/send-events) for code examples in Node.js, Python, PHP, and cURL.
# Changelog
Source: https://docs.scanova.io/conversion-tracking/changelog
Updates and improvements to Scanova Conversion Tracking documentation and SDK.
## 2026-05-13
**Documentation restructure**
* Reorganised all Conversion Tracking docs into focused, single-topic pages
* Added **Browser SDK** section with dedicated pages for installation, SDK configuration reference, auto-tracked events, custom events, and SPA/framework integration
* Added **SDK Configuration Reference** — all init options documented with defaults and behaviour
* Added **Auto-Tracked Events** — detailed payload documentation for `pageview`, `click`, `form_submit`, and `scroll`
* Added **Custom Events** guide with examples for common use cases
* Added **SPA & Framework Guide** — React, Next.js (App Router and Pages Router), and Vue 3
* Added **Browser Events API reference** — `POST /ct` endpoint documented
* Expanded **Server-Side Overview** with attribution flow and `scan_session_id` passing patterns
* Expanded **Send Server Events** with PHP examples and batch usage
* Expanded **Idempotency & Retries** with full implementation examples in Node.js and Python
* Expanded **Troubleshooting** with attribution, duplicate event, and performance sections
* Expanded **FAQ** from 5 to 20+ questions covering browser SDK, server events, privacy, and reporting
* Rewrote **How It Works** with full sequence diagram and attribution lifecycle explanation
* Rewrote **Event Model** to separate public API contract from internal storage schema
* Rewrote **Quick Start** to a true 5-step guide
## 2026-03-05
**Initial release**
* Added public Conversion Tracking tab and documentation
* Added client-side setup and verification guides
* Added server-side API key and event delivery guides
* Added OpenAPI-backed server event reference pages
# FAQ
Source: https://docs.scanova.io/conversion-tracking/faq
Frequently asked questions about Scanova Conversion Tracking — setup, attribution, events, privacy, and billing.
## General
### Do I need both browser and server events?
No — you can use either one on its own. Browser events require no backend setup and are the fastest way to get started. Server events require an API key and backend code, but are more reliable (not affected by ad blockers) and allow you to report revenue values and server-side conversions.
Most setups use both: the browser SDK for engagement data, and server events for confirmed conversions.
### Can I track conversions without a QR Code scan?
No. The Browser SDK requires a `scan_session_id` to send any event — if a user arrives at your site without scanning a QR Code, the SDK detects the missing session and sends nothing. The server events API also requires `scan_session_id` as a mandatory field.
Scanova Conversion Tracking is built specifically for QR-scan-attributed measurement. It does not function as a general-purpose web analytics tool.
***
## Browser SDK
### Which browsers does the SDK support?
The SDK supports all modern browsers: Chrome, Firefox, Safari, Edge, and mobile equivalents. It does not require any specific browser features beyond `XMLHttpRequest` or `fetch` and `localStorage`.
### Can the SDK be blocked by ad blockers?
Yes — browser events sent from `t.scanova.io` can be blocked by privacy-focused ad blockers and browser extensions. For critical conversion tracking (purchases, sign-ups), use [server-side events](/conversion-tracking/server/send-events) which cannot be blocked.
### Does the SDK affect my page performance?
The SDK loads asynchronously (`async` attribute) and does not block page rendering. The script is approximately 10 KB minified and gzipped. Events are sent in the background after the page loads.
### Can I use the SDK on multiple pages?
Yes — install the snippet in your site's global `` template and it will run on every page. The `scan_session_id` persists across pages via `localStorage`, so the full user journey is attributed to the original QR scan.
### What happens if a user visits without scanning a QR Code?
No events are sent. The Browser SDK checks for a `scan_session_id` before sending any event. If there is none (the user arrived directly, not via a QR scan), the SDK skips the event entirely. Nothing is stored for that visit.
### How long does scan attribution last?
60 days. If a user scans a QR Code and returns to your site within 60 days, their visits are still attributed to that scan. After 60 days, the `_scnv` localStorage entry expires and attribution is lost.
***
## Server-Side Events
### How do I get the scan\_session\_id on my server?
Pass it from the browser to your server. Common approaches:
* Include it in a hidden form field
* Send it in the request body when your frontend calls your API
* Read it from the URL parameter when the page loads and store it in the user's server-side session
See [Send Server Events](/conversion-tracking/server/send-events#passing-scan_session_id-from-browser-to-server) for code examples.
### Can I send batch events?
Yes. Use `POST /server-events/batch` with up to 100 events per request. This is recommended for high-volume backends and queue-based architectures.
### What happens if I retry a failed request?
If you resend the same `event_id`, the duplicate is marked as such and excluded from conversion counts. Always use the same `event_id` across retries. See [Idempotency & Retries](/conversion-tracking/server/idempotency-retries).
### Can I send events for past timestamps?
Yes — use the `event_time` field to set when the event occurred. This is useful when sending events asynchronously from a queue. The field accepts ISO 8601 format in UTC.
***
## Privacy & Data
### Should I send raw email addresses?
No. Never include raw email addresses in `metadata` or `properties`. Use `user_identifiers.email_hash` with a SHA-256 hash of the lowercased email address.
### What is the `consent` field for?
The `consent` field carries the user's GDPR consent signal to the tracking pipeline:
* `granted` — all fields are stored normally
* `denied` or `pending` — PII fields (`page_url`, `referrer`, `city`, `metadata`) are stripped before storage
The `consent` value itself is always stored as an audit record.
### How long is data retained?
Events are retained for 2 years by default. Orphan records (events whose scan session cannot be resolved) are automatically deleted after 24 hours.
***
## Dashboard & Reporting
### How long until events appear in reports?
Typically within 10–30 seconds under normal load. Delays up to a few minutes can occur during high-traffic spikes.
### How do I pause tracking for a site?
Open **Analytics → Conversion Tracking → Setup Conversion Tracking**, select **Tracking Setup** on the site, and use the **Site Status** toggle on the **Embed Script** tab. When a site is paused, events from that site return `400`.
### What is an orphan event and when does it get deleted?
An orphan event is one where the `scan_session_id` is present but cannot yet be resolved to a QR Code — this happens when an event arrives before the identity pipeline has linked the session to a QR Code record. Orphan events are automatically cleaned up after 24 hours if the session data never arrives. If the data does arrive in time, the orphan is backfilled and retained for the standard 2-year period.
# Generate an API Key
Source: https://docs.scanova.io/conversion-tracking/server/generate-api-key-dashboard
Create a site-scoped API key for server-side Conversion Tracking events from the Scanova dashboard. Keys are shown once — store them securely.
Each tracking site has its own API keys. A key is scoped to one site — it can only be used to send events for that site's `site_id`.
## Steps
1. Log in to the Scanova dashboard
2. Go to **Analytics → Conversion Tracking**
3. Click on your tracking site to open site details
4. Select **Tracking Setup** on your site, then the **Track via API** tab
5. Click **Generate API Key**
6. Enter a descriptive name (e.g. `Production Backend`, `Checkout Service`)
7. Click **Generate**
8. **Copy the key immediately** — it is shown only once
The raw API key is displayed one time only. If you close the dialog without copying it, you will need to revoke the key and generate a new one.
## Store the key securely
Save the key as an environment variable in your backend. Never hardcode it in source code.
```bash theme={null}
# .env
SCANOVA_API_KEY=your_key_here
```
```javascript theme={null}
// Node.js
const apiKey = process.env.SCANOVA_API_KEY;
```
```python theme={null}
# Python
import os
api_key = os.environ['SCANOVA_API_KEY']
```
## Key limits and management
* A site can have **up to 2 active API keys** at a time. Rotate an old key out when you create a new one
* Revoke unused or compromised keys immediately from the **Track via API** tab
* With a cap of two, a production/staging split uses both slots — plan rotation accordingly
## How to use the key
Include it as a header on every server event request:
```http theme={null}
POST /server-events HTTP/1.1
Host: track.scanova.io
Content-Type: application/json
X-API-Key: YOUR_API_KEY
```
The key must match the `site_id` you include in the request body. A mismatch returns `403`.
## Next step
[Send your first server event →](/conversion-tracking/server/send-events)
# Idempotency & Retries
Source: https://docs.scanova.io/conversion-tracking/server/idempotency-retries
How to safely retry failed server event requests without creating duplicate conversions. Use a stable event_id and exponential backoff.
Network failures and temporary server errors happen. The server events API is designed to handle retries safely — as long as you follow the `event_id` pattern.
## How idempotency works
Every event accepts an `event_id` field. When the same `event_id` arrives more than once, the pipeline marks the second occurrence as a duplicate and excludes it from analytics. Your conversion counts stay accurate even if you retry a request multiple times.
**The rule:** generate the `event_id` once, before your first attempt, and reuse the same value for every retry of that event.
## Implementation pattern
```javascript theme={null}
// Node.js example
import crypto from 'node:crypto';
async function sendWithRetry(eventPayload, maxAttempts = 5) {
// Generate event_id once — do not regenerate on retry
const payload = {
...eventPayload,
event_id: eventPayload.event_id ?? crypto.randomUUID(),
};
let delay = 1000; // start at 1 second
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
const response = await fetch('https://track.scanova.io/server-events', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.SCANOVA_API_KEY,
},
body: JSON.stringify(payload),
signal: AbortSignal.timeout(10_000), // 10s timeout
});
// Do not retry on client errors — fix the payload or auth first
if (response.status >= 400 && response.status < 500) {
const error = await response.json();
throw new Error(`Permanent error ${response.status}: ${JSON.stringify(error)}`);
}
if (response.ok) return; // success
// Server error (5xx) — retry
} catch (err) {
if (attempt === maxAttempts) throw err;
}
await new Promise(resolve => setTimeout(resolve, delay));
delay = Math.min(delay * 2, 30_000); // cap at 30 seconds
}
}
```
```python theme={null}
# Python example
import os
import time
import uuid
import requests
def send_with_retry(event_payload: dict, max_attempts: int = 5) -> None:
payload = {**event_payload, "event_id": event_payload.get("event_id") or str(uuid.uuid4())}
delay = 1.0
for attempt in range(1, max_attempts + 1):
try:
response = requests.post(
"https://track.scanova.io/server-events",
headers={
"Content-Type": "application/json",
"X-API-Key": os.environ["SCANOVA_API_KEY"],
},
json=payload,
timeout=10,
)
if 400 <= response.status_code < 500:
raise ValueError(f"Permanent error {response.status_code}: {response.text}")
if response.ok:
return
except ValueError:
raise # do not retry permanent errors
except Exception:
if attempt == max_attempts:
raise
time.sleep(delay)
delay = min(delay * 2, 30) # cap at 30 seconds
```
## Retry schedule
| Attempt | Delay before attempt |
| - | - |
| 1 | Immediate |
| 2 | 1 second |
| 3 | 2 seconds |
| 4 | 4 seconds |
| 5 | 8 seconds |
Cap your delay at 30–60 seconds. After 5 failed attempts, log the event and alert — do not retry indefinitely.
## When to retry vs when to stop
| Status | Action |
| - | - |
| Network timeout / connection error | Retry with backoff |
| `429` Too Many Requests | Retry after the `Retry-After` header value (or 60 seconds) |
| `500`, `502`, `503`, `504` | Retry with backoff |
| `400` Bad Request | Stop — fix the payload |
| `401` Unauthorized | Stop — check your API key |
| `403` Forbidden | Stop — check key/site\_id match |
| `422` Unprocessable Entity | Stop — fix the field validation error |
## Persisting event\_id for critical events
For high-value conversions (purchases, subscriptions), generate and persist the `event_id` before the network call — so you can retry even after a process restart:
```javascript theme={null}
// Generate and store before the API call
const eventId = crypto.randomUUID();
await db.trackingEvents.create({ eventId, status: 'pending', orderId });
try {
await sendWithRetry({ event_id: eventId, event_name: 'purchase', ... });
await db.trackingEvents.update({ eventId, status: 'sent' });
} catch (err) {
await db.trackingEvents.update({ eventId, status: 'failed' });
// Retry from your job queue
}
```
# Server-Side Events Overview
Source: https://docs.scanova.io/conversion-tracking/server/overview
Send secure conversion events directly from your backend — purchases, sign-ups, leads — attributed to the QR Code scan that started the user journey.
Server-side events let your backend report conversions directly to the Scanova Tracking API using an API key. Unlike browser events, server events cannot be blocked by ad blockers, are not affected by browser restrictions, and can carry sensitive attribution data securely.
## When to use server events
| Use browser events | Use server events |
| - | - |
| Page views and navigation | Purchase confirmations |
| Clicks and scroll depth | Subscription activations |
| Form field interactions | Lead creation in your CRM |
| Any action visible in the browser | Sign-up email verifications |
| — | Any action that happens server-side |
Use both together for the fullest picture: the browser SDK tracks the journey, and your server reports when a conversion actually happened.
## How attribution works server-side
The `scan_session_id` is the attribution link between a QR scan and a server event. Here is the typical flow:
```
User scans QR Code
↓
Lands on yoursite.com/?scnv=7ad26d4f-...
↓
SDK saves scan_session_id to localStorage
↓
User completes a purchase form
↓
Browser sends scan_session_id to your checkout API
↓
Your server sends a purchase event with that scan_session_id
↓
Scanova attributes the purchase to the QR scan
```
**Your responsibility:** pass the `scan_session_id` from the browser to your server. Common ways to do this:
* Include it in a hidden form field
* Send it in the API request body from your frontend JavaScript
* Store it in the user's session on your server (read it from the URL parameter when the page loads)
```javascript theme={null}
// Read from SDK (browser-side)
// Note: _scnv is the SDK's internal storage key — structure may change.
// Use this pattern only if no higher-level SDK method is available.
const scanSessionId = localStorage.getItem('_scnv')
? JSON.parse(localStorage.getItem('_scnv')).sid
: null;
// Include in your API call to your backend
await fetch('/api/checkout', {
method: 'POST',
body: JSON.stringify({ ...orderData, scan_session_id: scanSessionId })
});
```
## Requirements
* A tracking site created in **Analytics → Conversion Tracking**
* A site-scoped API key (generated from the site's **Tracking Setup → Track via API** tab)
* Your server can make outbound HTTPS POST requests
* You have a way to receive the `scan_session_id` from the user's browser
## Security requirements
* **Never expose your API key in frontend code or client-side JavaScript**
* Store the key in an environment variable or secrets manager
* Rotate keys periodically from the dashboard
## Next steps
Create a site-scoped API key from the dashboard.
Code examples in cURL, Node.js, Python, and PHP.
How to safely retry failed requests.
Confirm your events are being received.
# Send Server-Side Events
Source: https://docs.scanova.io/conversion-tracking/server/send-events
Send purchase, sign-up, and lead conversion events from your backend to Scanova using the server events API. Code examples in cURL, Node.js, Python, and PHP.
Use the server events API to report conversions that happen on your server — purchases, confirmed sign-ups, leads, or any backend action you want to attribute to a QR Code scan.
## Endpoint
```
POST https://track.scanova.io/server-events
```
**Required headers:**
```http theme={null}
Content-Type: application/json
X-API-Key: YOUR_SITE_API_KEY
```
## Required fields
| Field | Description |
| - | - |
| `site_id` | Your tracking site ID from the dashboard |
| `event_name` | The conversion event name (e.g. `purchase`, `signup`, `lead`) |
| `scan_session_id` | The scan session ID from the user's browser. This links the conversion to the QR scan. |
## Examples
```bash theme={null}
curl -X POST "https://track.scanova.io/server-events" \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-d '{
"site_id": "YOUR_SITE_ID",
"event_name": "purchase",
"event_id": "550e8400-e29b-41d4-a716-446655440000",
"scan_session_id": "7ad26d4f-3181-4ef8-b6ca-b8f59499dd43",
"conversion_value": { "amount": 49.99, "currency": "USD" },
"properties": { "order_id": "ord_9876", "plan": "pro" }
}'
```
```javascript theme={null}
import crypto from 'node:crypto';
async function trackConversion({ scanSessionId, orderId, amount }) {
const response = await fetch('https://track.scanova.io/server-events', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.SCANOVA_API_KEY,
},
body: JSON.stringify({
site_id: process.env.SCANOVA_SITE_ID,
event_name: 'purchase',
event_id: crypto.randomUUID(), // generate once and persist for retries
scan_session_id: scanSessionId,
conversion_value: { amount, currency: 'USD' },
properties: { order_id: orderId },
}),
});
if (!response.ok) {
const error = await response.json();
throw new Error(`Tracking failed: ${response.status} — ${JSON.stringify(error)}`);
}
}
```
```python theme={null}
import os
import uuid
import requests
def track_conversion(scan_session_id: str, order_id: str, amount: float):
response = requests.post(
"https://track.scanova.io/server-events",
headers={
"Content-Type": "application/json",
"X-API-Key": os.environ["SCANOVA_API_KEY"],
},
json={
"site_id": os.environ["SCANOVA_SITE_ID"],
"event_name": "purchase",
"event_id": str(uuid.uuid4()), # generate once, persist for retries
"scan_session_id": scan_session_id,
"conversion_value": {"amount": amount, "currency": "USD"},
"properties": {"order_id": order_id},
},
timeout=10,
)
response.raise_for_status()
```
```php theme={null}
getenv('SCANOVA_SITE_ID'),
'event_name' => 'purchase',
'event_id' => sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
mt_rand(0, 0xffff), mt_rand(0, 0x0fff) | 0x4000,
mt_rand(0, 0x3fff) | 0x8000,
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)),
'scan_session_id' => $scanSessionId,
'conversion_value' => ['amount' => $amount, 'currency' => 'USD'],
'properties' => ['order_id' => $orderId],
]);
$ch = curl_init('https://track.scanova.io/server-events');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-API-Key: ' . getenv('SCANOVA_API_KEY'),
],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status !== 200) {
throw new RuntimeException("Tracking failed: {$status} — {$response}");
}
}
```
## Optional fields
| Field | Description |
| - | - |
| `event_id` | UUID for safe retries. Generate once and reuse on retry. If omitted, one is auto-generated. |
| `event_time` | ISO 8601 timestamp of when the event occurred. Defaults to time of receipt. Useful if you are sending events asynchronously. |
| `conversion_value` | `{ "amount": 49.99, "currency": "USD" }`. Currency must be a 3-letter ISO 4217 code. |
| `user_identifiers` | Hashed user identifiers: `email_hash`, `phone_hash`, `external_id`. Never send raw email or phone. |
| `properties` | Custom key-value object. Max 10 KB. No raw PII. |
| `consent` | `granted`, `denied`, or `pending`. Controls PII handling. |
## Passing scan\_session\_id from browser to server
The `scan_session_id` originates in the browser. You need to pass it from your frontend to your backend.
**Option 1: Hidden form field**
```html theme={null}
```
**Option 2: Include in API request body from frontend**
```javascript theme={null}
// _scnv is the SDK's internal localStorage key (structure may change in future SDK versions)
const stored = localStorage.getItem('_scnv');
const scanSessionId = stored ? JSON.parse(stored).sid : null;
await fetch('/api/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
cart: cartData,
scan_session_id: scanSessionId, // pass to your server
}),
});
```
## Sending a batch of events
Use the batch endpoint to send up to 100 events in a single request:
```
POST https://track.scanova.io/server-events/batch
```
```json theme={null}
{
"events": [
{
"site_id": "YOUR_SITE_ID",
"event_name": "purchase",
"scan_session_id": "7ad26d4f-...",
"conversion_value": { "amount": 49.99, "currency": "USD" }
},
{
"site_id": "YOUR_SITE_ID",
"event_name": "signup",
"scan_session_id": "3b5e1234-..."
}
]
}
```
The response includes a per-event accepted/rejected breakdown:
```json theme={null}
{
"accepted": 2,
"rejected": 0,
"results": [
{ "index": 0, "event_id": "...", "status": "accepted" },
{ "index": 1, "event_id": "...", "status": "accepted" }
]
}
```
## Next steps
* [Idempotency & Retries](/conversion-tracking/server/idempotency-retries) — how to safely retry failed requests
* [Verify Delivery](/conversion-tracking/server/verify) — confirm events are received
* [Single Event API Reference](/conversion-tracking/api/events-collect) — full endpoint specification
* [Batch Event API Reference](/conversion-tracking/api/events-batch) — batch endpoint specification
# Verify & Debug Server Events
Source: https://docs.scanova.io/conversion-tracking/server/verify
Confirm your server-side conversion events are being received, attributed correctly, and appearing in reports.
## Step 1: Send a test event
Send a single test event with a known `event_id` so you can trace it:
```bash theme={null}
curl -X POST "https://track.scanova.io/server-events" \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-d '{
"site_id": "YOUR_SITE_ID",
"event_name": "test_event",
"event_id": "00000000-0000-0000-0000-000000000001",
"scan_session_id": "00000000-0000-0000-0000-000000000002",
"properties": { "test": true }
}'
```
A successful response looks like:
```json theme={null}
{
"event_id": "00000000-0000-0000-0000-000000000001",
"status": "accepted"
}
```
## Step 2: Confirm it appears in the dashboard
1. Open the Scanova dashboard
2. Go to **Analytics → Conversion Tracking**
3. Open the tracking site
4. Your `test_event` should appear within 30–60 seconds
## Step 3: Test deduplication
Resend the same request with the same `event_id`:
```bash theme={null}
# Same request again
curl -X POST "https://track.scanova.io/server-events" \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-d '{
"site_id": "YOUR_SITE_ID",
"event_name": "test_event",
"event_id": "00000000-0000-0000-0000-000000000001",
"scan_session_id": "00000000-0000-0000-0000-000000000002"
}'
```
The second request will return `200` but the event will be marked as a duplicate and excluded from conversion counts. Confirm the event count in reports did not increase.
***
## Common errors and fixes
### `401` — Missing API key
The `X-API-Key` header is absent from the request.
```bash theme={null}
# Wrong — missing header
curl -X POST "https://track.scanova.io/server-events" -d '{...}'
# Correct
curl -X POST "https://track.scanova.io/server-events" \
-H "X-API-Key: YOUR_API_KEY" \
-d '{...}'
```
### `403` — Invalid key or unauthorized site
Either the API key is invalid, revoked, or the `site_id` in the payload does not match the site the key belongs to. Verify:
* The key was copied correctly (no trailing spaces or newline characters)
* The `site_id` in the request body matches exactly the site that generated the key
* The key has not been revoked in the dashboard
### `422` — Validation error
The payload failed schema validation. The response body includes details:
```json theme={null}
{
"detail": [
{
"loc": ["body", "scan_session_id"],
"msg": "field required",
"type": "value_error.missing"
}
]
}
```
Common causes:
* Missing required field (`site_id`, `event_name`, or `scan_session_id`)
* `scan_session_id` is not a valid UUID format
* `conversion_value.currency` is not a 3-letter ISO code
* `properties` object exceeds 10 KB
* Raw email address in `properties` (use `user_identifiers.email_hash` instead)
### `429` — Rate limit exceeded
You are sending more than 1,000 events per minute per API key. Back off and retry:
* Check the `Retry-After` header in the response
* Use the batch endpoint (`POST /server-events/batch`) to consolidate multiple events into fewer requests
### Event delivered but not appearing in reports
* Wait 60 seconds — there is a short processing delay
* Verify the `site_id` in your payload matches the tracking site you are viewing in the dashboard
* Check that the `scan_session_id` is a valid UUID — invalid format is rejected with `422`
* Confirm the `scan_session_id` you sent is a real scan session from a QR Code scan, not a test UUID — events with unresolvable sessions are not attributed and may not appear in reports
# Troubleshooting
Source: https://docs.scanova.io/conversion-tracking/troubleshooting
Fix common Scanova Conversion Tracking issues — no events appearing, CORS errors, 403 domain errors, missing attribution, and duplicate events.
## Browser SDK issues
### No network request to `/ct` appears
Check these in order:
1. **Snippet is in the wrong place** — it must be in ``, not `` or after ``
2. **`site_id` is missing** — confirm `scanova('init', 'YOUR_SITE_ID', ...)` has a real site ID, not a placeholder
3. **Script blocked by Content Security Policy (CSP)** — add `https://cdn.scanova.io` (script source) and `https://t.scanova.io` (connect source) to your CSP headers
4. **Browser extension blocking the request** — test in an incognito window with all extensions disabled
5. **`autoPageview: false` and no manual track call** — either enable `autoPageview` or add `scanova('track', 'pageview')`
6. **SDK loaded twice** — having both a direct HTML install and a GTM install on the same page causes issues. Remove one.
### `403 Domain not allowed`
The origin of your request is not in the tracking site's **Allowed Domains** list.
1. Go to **Analytics → Conversion Tracking**
2. Click on your tracking site → **Edit**
3. Add the exact hostname shown in your browser's address bar (e.g. `yoursite.com`, `www.yoursite.com`)
4. Save
`www.yoursite.com` and `yoursite.com` are treated as different domains. Add both if you use either.
The allowlist is checked against the request's `Origin` (falling back to `Referer`). A `curl` or server-side reproduction of a browser event therefore returns `403 Domain not allowed for this site` even when the domain is allowed, because neither header is sent. Add `-H "Origin: https://yoursite.com"` when reproducing a browser event by hand.
### `400` — Bad request
The `site_id` is invalid, empty, or the site has been deactivated in the dashboard. Verify:
* You are using the correct `site_id` for this tracking site
* The site is active (shown as enabled in the dashboard)
### Events appear in DevTools but not in the dashboard
* Wait up to 60 seconds — there is a short processing delay
* Ensure you are looking at the correct tracking site in the dashboard
* Verify `site_id` in the network request payload matches the site you are viewing
### Two events firing per page load
You have both `autoPageview: true` in your init options and a manual `scanova('track', 'page_view', ...)` call somewhere in your code. These produce two separate events. Remove one — see [Auto-Tracked Events](/conversion-tracking/browser/auto-tracking#page-view).
### No events at all for a specific visit
If no `/ct` request appears for a visit, the most likely cause is that the user arrived without a QR Code scan — there is no `?scnv=` parameter in the URL. The SDK checks for a `scan_session_id` before sending any event, and silently skips if it is absent.
Scanova Conversion Tracking only measures QR-scan-originated traffic. Direct or organic visits produce no events — this is by design.
To test attribution, visit your page with a `?scnv=` parameter to simulate a scan:
```
https://yoursite.com/?scnv=00000000-0000-0000-0000-000000000001
```
***
## Server-side event issues
### `401` — Missing API key
The `X-API-Key` header is not included in the request. See [Authentication](/conversion-tracking/api/authentication).
### `403` — Invalid key or wrong site
Either the key is invalid/revoked, or the `site_id` in your payload does not match the site that generated the key. Check:
* Copy the key again from the dashboard (keys are shown once — if you lost it, generate a new one)
* Verify `site_id` in your payload is the same site ID shown on the Conversion Tracking Sites list
* Confirm the key has not been revoked
### `422` — Validation error
The response body tells you exactly which field failed. Common causes:
| Cause | Fix |
| - | - |
| `scan_session_id` is not a UUID | Must be a valid UUID string |
| `event_name` is missing | Required field for server events |
| `properties` exceeds 10 KB | Reduce payload size |
| Raw email in `properties` | Hash it: `user_identifiers.email_hash` |
### Events delivered but not attributed to a QR Code
The `scan_session_id` you sent could not be linked to a QR scan in the Scanova database. This happens when:
* The `scan_session_id` was made up or invalid
* The user did not actually scan a QR Code — they arrived directly
* The scan session has expired
Confirm you are reading `scan_session_id` from the browser (via `localStorage._scnv` — an internal SDK storage key) and passing it to your server correctly. See [Send Server Events](/conversion-tracking/server/send-events#passing-scan_session_id-from-browser-to-server) for examples.
***
## Attribution issues
### Events show `null` for `qr_code_id`
This is an **orphan record** — the event arrived before the scan session data was fully propagated in the Scanova database. These records are automatically cleaned up after 24 hours if the session data never arrives, or backfilled once it does.
If you see many orphan records, check that:
* The `scan_session_id` in your events is a real scan session (not a test UUID)
* Your QR Codes are correctly linked to the tracking site in the dashboard
### Conversions showing for wrong QR Code
Your `scan_session_id` is not being read correctly — you may be reading a stale or incorrect value from `localStorage`. Confirm your frontend reads from `localStorage.getItem('_scnv')` (the SDK's internal storage key) and parses `.sid` from the JSON value. See [Send Server Events](/conversion-tracking/server/send-events#passing-scan_session_id-from-browser-to-server) for the full pattern.
***
## Performance issues
### `429` — Rate limit exceeded
You are hitting the rate limit:
* Browser events: 100 requests/min per IP
* Server events: 1,000 requests/min per API key
For server events, switch to the batch endpoint (`POST /server-events/batch`) to send up to 100 events per request, reducing your request rate by up to 100×.
For browser events, `429` during normal usage is rare. If you see it during load testing, space out your requests.
# API Authentication
Source: https://docs.scanova.io/conversion-tracking/api/authentication
How to authenticate server-side Conversion Tracking API requests using site-scoped API keys.
Server event endpoints (`POST /server-events` and `POST /server-events/batch`) require an API key. Browser events (`POST /ct`) are public and do not require authentication — they are validated by domain instead.
## API key header
Include your key in every server event request:
```http theme={null}
POST /server-events HTTP/1.1
Host: track.scanova.io
Content-Type: application/json
X-API-Key: YOUR_SITE_API_KEY
```
## Key scope
API keys are **site-scoped**. A key generated for Site A cannot be used to send events for Site B.
The `site_id` in your request body must match the site that issued the key. A mismatch returns `403 Forbidden`.
## Generating a key
Keys are created in the dashboard:
1. Go to **Analytics → Conversion Tracking → Setup Conversion Tracking**
2. Select **Tracking Setup** on your tracking site
3. Open the **Track via API** tab
4. Select **Add New Key** and name it
A site can hold **up to 2 active keys** at a time, so rotate an old key out when you add a new one.
Full instructions: [Generate an API Key](/conversion-tracking/server/generate-api-key-dashboard)
## Security rules
* **Never include your API key in frontend/browser code** — it would be publicly visible
* Store keys in environment variables or a secrets manager (AWS Secrets Manager, HashiCorp Vault, etc.)
* Create separate keys for production and staging environments
* Revoke keys immediately if they are accidentally exposed
## Error responses
| Status | Meaning |
| - | - |
| `401 Unauthorized` | `X-API-Key` header is missing |
| `403 Forbidden` | Key is invalid, revoked, or `site_id` does not match the key's site |
# Browser Events API — POST /ct
Source: https://docs.scanova.io/conversion-tracking/api/browser-events
Reference for the browser event collection endpoint. Used by the Scanova Browser SDK to send page views, clicks, and custom events from user browsers.
The `/ct` endpoint receives browser events sent by the Scanova Browser SDK. In most cases you do not call this endpoint directly — the SDK handles it. This page documents the request format for custom integrations or debugging.
**Base URL:** `https://t.scanova.io`
## Endpoint
```
POST https://t.scanova.io/ct
```
No authentication header is required. Requests are validated by checking the `Origin` or `Referer` header against the site's **Allowed Domains** list.
## Request
**Headers:**
```http theme={null}
Content-Type: application/json
Origin: https://yoursite.com
```
**Body:**
```json theme={null}
{
"event_id": "f9ac7db6-f900-4d8e-8918-c846834195a8",
"site_id": "YOUR_SITE_ID",
"event_type": "cta_click",
"scan_session_id": "7ad26d4f-3181-4ef8-b6ca-b8f59499dd43",
"web_session_id": "2d0c328a-01d0-4010-85f4-f327130d1bd4",
"visitor_id": "363fe851-7d8f-4090-902f-0f5a462829f5",
"page_url": "https://yoursite.com/pricing",
"referrer": "https://yoursite.com/",
"timestamp": "2026-05-13T10:00:00.000Z",
"device": {
"user_agent": "Mozilla/5.0 ...",
"screen_width": 1440,
"screen_height": 900,
"language": "en-US"
},
"metadata": {
"button_text": "Start Free Trial",
"section": "pricing"
}
}
```
## Fields
| Field | Type | Required | Description |
| - | - | - | - |
| `site_id` | string | Yes | Tracking site ID |
| `event_type` | string | Yes | Event name in snake\_case |
| `event_id` | UUID string | No | Deduplication ID. Auto-generated if omitted. |
| `scan_session_id` | UUID string | No | QR scan attribution ID |
| `web_session_id` | UUID string | No | Browser session ID (30-min timeout) |
| `visitor_id` | UUID string | No | Persistent visitor ID (1-year cookie) |
| `page_url` | string | No | Current page URL |
| `referrer` | string | No | Referring URL |
| `timestamp` | ISO 8601 | No | Event time. Defaults to receipt time. |
| `device` | object | No | User-agent, screen dimensions, language |
| `metadata` | object | No | Custom data. Max 10 KB, depth 5. |
| `consent` | string | No | `granted`, `denied`, or `pending` |
## Response
**Success (`200`):**
```json theme={null}
{
"event_id": "f9ac7db6-f900-4d8e-8918-c846834195a8"
}
```
**Error responses:**
| Status | Cause |
| - | - |
| `400` | Missing required field or invalid `site_id` |
| `403` | Request `Origin`/`Referer` not in the site's Allowed Domains |
| `413` | Payload exceeds size limit |
| `422` | Field validation error |
| `429` | Rate limit exceeded (100 requests/min per IP) |
## CORS
The `/ct` endpoint supports CORS. Browser requests from allowed domains are permitted with:
```
Access-Control-Allow-Origin:
Access-Control-Allow-Methods: POST, OPTIONS
```
Preflight (`OPTIONS`) requests return `204` with the appropriate CORS headers.
## Batch endpoint
To send multiple browser events in a single request:
```
POST https://t.scanova.io/collect/batch
```
Request body wraps an array:
```json theme={null}
{
"events": [
{ "site_id": "...", "event_type": "pageview", ... },
{ "site_id": "...", "event_type": "scroll", "metadata": { "scroll_depth": 25 } }
]
}
```
Up to 100 events per batch request.
# Conversion Analytics
Source: https://docs.scanova.io/conversion-tracking/api/ct-analytics
POST /analytics/ct/
Generate conversion analytics for selected QR Codes, with KPI, trend, event/site breakdown, and funnel outputs.
## Query Parameters
| Parameter | Type | Required | Description |
| :- | :- | :- | :- |
| `type` | `string` | Yes | Comma-separated sections to return. Supported: `count`, `trend`, `event_breakdown`, `site_breakdown`, `funnel`, `scan_overview`, `hybrid_funnel`, `summary`, `traffic`, `audience`, `behavior`, `qr_performance`, `raw`. |
| `from` | `string (YYYY-MM-DD)` | Yes | Inclusive start date. |
| `to` | `string (YYYY-MM-DD)` | Yes | Inclusive end date. |
| `all` | `boolean` | No | Include all QR Codes in scope. |
| `search` | `string` | No | Search value used with `all=true`. |
| `category` | `string` | No | Comma-separated category slugs. |
| `users` | `string` | No | Comma-separated user IDs. |
| `conversion_events` | `string` | No | Comma-separated conversion event keys. Default: `purchase,signup,lead,conversion`. |
| `funnel_steps` | `string` | No | Ordered comma-separated event keys for funnel calculation. |
## Request Body
| Field | Type | Required | Description |
| :- | :- | :- | :- |
| `filter_by` | `string` | Yes | One of: `qrid`, `id`, `tags`. |
| `q` | `array[string]` | Yes | Values corresponding to `filter_by`. |
## Example Request
```bash theme={null}
curl -X POST "https://api.scanova.io/analytics/ct/?type=count,trend,event_breakdown,site_breakdown,funnel&from=2026-03-03&to=2026-03-10&search=trac&conversion_events=purchase,signup,lead,conversion&funnel_steps=pageview,click,purchase" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"filter_by": "qrid",
"q": ["Q20a18d4af3e947a1"]
}'
```
## Example Response
```json theme={null}
{
"conversion": {
"meta": {
"warnings": [],
"unlinked_qrs": []
},
"count": {
"total_events": 12,
"total_conversions": 12,
"unique_sessions": 2,
"conversion_rate": 600.0,
"revenue": 0.0,
"primary_currency": null,
"currency_warning": null,
"period": {
"start": "2026-02-08",
"end": "2026-03-10"
}
},
"trend": [
{
"date": "2026-02-26",
"unique_sessions": 1,
"conversions": 7,
"events": 7,
"conversion_rate": 700.0
},
{
"date": "2026-03-02",
"unique_sessions": 1,
"conversions": 5,
"events": 5,
"conversion_rate": 500.0
}
],
"event_breakdown": [
{
"event": "scroll",
"total_events": 7,
"unique_sessions": 2,
"conversions": 7
},
{
"event": "click",
"total_events": 3,
"unique_sessions": 1,
"conversions": 3
},
{
"event": "pageview",
"total_events": 2,
"unique_sessions": 2,
"conversions": 2
}
],
"site_breakdown": [
{
"site": {
"site_id": "pID5CJUPr6VgP53FJLCeNZpk9tQwQi0JpPmECQoxAEeTzrtzoQxCOAkSxrCQb3c4",
"name": "Sample Site 1"
},
"total_events": 12,
"unique_sessions": 2,
"conversions": 12,
"conversion_rate": 600.0
}
],
"funnel": [
{
"step": "pageview",
"sessions": 2,
"drop_off_percent": 0
},
{
"step": "click",
"sessions": 1,
"drop_off_percent": 50.0
},
{
"step": "scroll",
"sessions": 1,
"drop_off_percent": 0.0
}
],
"hybrid_funnel": [
{
"step": "scan",
"sessions": 17,
"drop_off_percent": 0
},
{
"step": "pageview",
"sessions": 2,
"drop_off_percent": 88.24
},
{
"step": "click",
"sessions": 1,
"drop_off_percent": 50.0
},
{
"step": "scroll",
"sessions": 1,
"drop_off_percent": 0.0
}
]
},
"scan_overview": {
"total": 17,
"elevation": {
"percent": 100,
"previous": 0,
"type": "+",
"period": {
"start": "2026-01-08T18:07:00Z",
"end": "2026-02-07T18:07:00Z"
}
},
"unique": 2,
"unique_elevation": {
"percent": 100,
"previous": 0,
"type": "+",
"period": {
"start": "2026-01-08T18:07:00Z",
"end": "2026-02-07T18:07:00Z"
}
},
"scan_qr": 0,
"scan_url": 17
}
}
```
# Export Conversion Analytics
Source: https://docs.scanova.io/conversion-tracking/api/ct-export
POST /analytics/ct/export/
Export aggregated conversion analytics in CSV/XLS/XLSX formats.
## Query Parameters
| Parameter | Type | Required | Description |
| :- | :- | :- | :- |
| `type` | `string` | No | Comma-separated sections. Typical: `count,trend,event_breakdown,site_breakdown,funnel,scan_overview,hybrid_funnel`. |
| `from` | `string (YYYY-MM-DD)` | Yes | Inclusive start date. |
| `to` | `string (YYYY-MM-DD)` | Yes | Inclusive end date. |
| `file_format` | `string` | Yes | One of: `csv`, `xls`, `xlsx`. |
| `sheet` | `string` | No | CSV-only selected sheet key. Examples: `count`, `trend`, `event_breakdown`, `site_breakdown`, `funnel`, `hybrid_funnel`, `scan_overview`. |
| `all` | `boolean` | No | Include all QR Codes in scope. |
| `search` | `string` | No | Search value used with `all=true`. |
| `category` | `string` | No | Comma-separated category slugs. |
| `users` | `string` | No | Comma-separated user IDs. |
| `conversion_events` | `string` | No | Comma-separated conversion event keys. |
| `funnel_steps` | `string` | No | Ordered comma-separated event keys for funnel calculation. |
## Request Body
| Field | Type | Required | Description |
| :- | :- | :- | :- |
| `filter_by` | `string` | Yes | One of: `qrid`, `id`, `tags`. |
| `q` | `array[string]` | Yes | Values corresponding to `filter_by`. |
## Example
```bash theme={null}
curl -X POST "https://api.scanova.io/analytics/ct/export/?type=count,trend,event_breakdown,site_breakdown,funnel,scan_overview,hybrid_funnel&from=2026-03-03&to=2026-03-10&file_format=xlsx" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"filter_by": "qrid",
"q": ["Q20a18d4af3e947a1"]
}'
```
# Export Raw Conversion Events
Source: https://docs.scanova.io/conversion-tracking/api/ct-raw
POST /analytics/ct/raw/
Export row-level conversion events in CSV/XLS/XLSX formats.
## Query Parameters
| Parameter | Type | Required | Description |
| :- | :- | :- | :- |
| `from` | `string (YYYY-MM-DD)` | Yes | Inclusive start date. |
| `to` | `string (YYYY-MM-DD)` | Yes | Inclusive end date. |
| `file_format` | `string` | Yes | One of: `csv`, `xls`, `xlsx`. |
| `all` | `boolean` | No | Include all QR Codes in scope. |
| `search` | `string` | No | Search value used with `all=true`. |
| `category` | `string` | No | Comma-separated category slugs. |
| `users` | `string` | No | Comma-separated user IDs. |
| `conversion_events` | `string` | No | Comma-separated conversion event keys. |
| `funnel_steps` | `string` | No | Ordered comma-separated event keys. |
## Request Body
| Field | Type | Required | Description |
| :- | :- | :- | :- |
| `filter_by` | `string` | Yes | One of: `qrid`, `id`, `tags`. |
| `q` | `array[string]` | Yes | Values corresponding to `filter_by`. |
## Example
```bash theme={null}
curl -X POST "https://api.scanova.io/analytics/ct/raw/?from=2026-03-03&to=2026-03-10&file_format=csv" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"filter_by": "qrid",
"q": ["Q20a18d4af3e947a1"]
}'
```
# Errors & Rate Limits
Source: https://docs.scanova.io/conversion-tracking/api/errors-rate-limits
HTTP status codes, error response format, rate limits, and error handling guidance for Scanova Conversion Tracking APIs.
## HTTP status codes
| Code | Meaning | Action |
| - | - | - |
| `200` | Event accepted | No action needed |
| `400` | Bad request — missing field, invalid format, or inactive `site_id` | Fix the request payload |
| `401` | Missing `X-API-Key` header | Add the `X-API-Key` header |
| `403` | Invalid key, revoked key, or `site_id`/key mismatch | Check your API key and `site_id` |
| `413` | Payload too large | Reduce payload size or split into smaller batches |
| `422` | Validation error — see response body for details | Fix the specific field error |
| `429` | Rate limit exceeded | Back off and retry after the `Retry-After` header value |
| `500` / `502` / `503` | Server error | Retry with exponential backoff |
## Error response format
All error responses return a JSON body with details:
```json theme={null}
{
"detail": [
{
"loc": ["body", "scan_session_id"],
"msg": "field required",
"type": "value_error.missing"
}
]
}
```
For `400` errors with a single message:
```json theme={null}
{
"detail": "site_id not found or inactive"
}
```
## Rate limits
| Endpoint | Limit | Scope |
| - | - | - |
| `POST /ct` (browser events) | 100 requests / min | Per IP address |
| `POST /collect/batch` | 100 requests / min | Per IP address |
| `POST /server-events` | 1,000 requests / min | Per API key |
| `POST /server-events/batch` | 1,000 requests / min | Per API key |
Short bursts above the limit are handled with a small grace window. Design your clients to back off on `429` rather than relying on burst tolerance.
## Error handling pattern
### For server events
```javascript theme={null}
async function sendEvent(payload) {
const response = await fetch('https://track.scanova.io/server-events', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.SCANOVA_API_KEY,
},
body: JSON.stringify(payload),
});
if (response.ok) return await response.json();
const error = await response.json().catch(() => null);
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get('Retry-After') ?? '60', 10);
throw new RetryableError(`Rate limited — retry after ${retryAfter}s`, retryAfter);
}
if (response.status >= 500) {
throw new RetryableError(`Server error ${response.status}`);
}
// 400, 401, 403, 422 — do not retry, fix the request
throw new PermanentError(`Request failed ${response.status}: ${JSON.stringify(error)}`);
}
```
### Retry logic
Only retry on transient errors. Stop immediately on permanent errors.
| Retry? | Status codes |
| - | - |
| Yes — retry with backoff | `429`, `500`, `502`, `503`, `504`, network timeout |
| No — fix the request | `400`, `401`, `403`, `413`, `422` |
See [Idempotency & Retries](/conversion-tracking/server/idempotency-retries) for a full retry implementation with exponential backoff.
## Common `422` causes
| Error | Fix |
| - | - |
| `scan_session_id` invalid format | Must be a valid UUID (e.g. `7ad26d4f-3181-4ef8-b6ca-b8f59499dd43`) |
| `event_name` missing | Required for server events |
| `conversion_value.currency` invalid | Must be a 3-letter ISO 4217 code (e.g. `USD`, `EUR`, `GBP`) |
| `properties` too large | Keep under 10 KB |
| Raw email in `properties` | Use `user_identifiers.email_hash` with a SHA-256 hash instead |
# Batch Server Events API — POST /server-events/batch
Source: https://docs.scanova.io/conversion-tracking/api/events-batch
POST /server-events/batch
Send up to 100 server-side conversion events in a single authenticated request. Recommended for queued delivery and high-volume backends.
Use the batch endpoint when you have multiple events to send — it reduces HTTP overhead and is the preferred approach for worker-based or queue-driven architectures.
Use the site-scoped API key generated from your tracking site dashboard: **Analytics → Conversion Tracking → Setup Conversion Tracking**, then **Tracking Setup → Track via API** on your site.
See [Send Server Events](/conversion-tracking/server/send-events#sending-a-batch-of-events) for a full batch request example.
# Server Events API — POST /server-events
Source: https://docs.scanova.io/conversion-tracking/api/events-collect
POST /server-events
Send a single authenticated server-side conversion event — purchase, signup, lead, or any backend action — attributed to a QR Code scan.
Use this endpoint to report a single backend conversion event. For sending multiple events in one request, use [POST /server-events/batch](/conversion-tracking/api/events-batch).
Generate the required `X-API-Key` from your tracking site: **Analytics → Conversion Tracking → Setup Conversion Tracking**, then **Tracking Setup → Track via API** on your site.
See [Send Server Events](/conversion-tracking/server/send-events) for code examples in Node.js, Python, PHP, and cURL.
# Auto-Tracked Events
Source: https://docs.scanova.io/conversion-tracking/browser/auto-tracking
What the Scanova Browser SDK tracks automatically — page views, clicks, scroll depth, and form submissions. See the exact event type, metadata, and payload for each.
When you enable auto-tracking options in `scanova('init', ...)`, the SDK captures user actions automatically with no additional code. This page shows exactly what each auto-tracking feature sends.
## Page view
**Enabled by:** `autoPageview: true`
Fires once per page load, as soon as the SDK initialises.
**Event type:** `pageview`
**Metadata captured:** *(none — page context is in the top-level fields)*
**Full payload example:**
```json theme={null}
{
"event_id": "f9ac7db6-f900-4d8e-8918-c846834195a8",
"event_type": "pageview",
"site_id": "YOUR_SITE_ID",
"scan_session_id": "7ad26d4f-3181-4ef8-b6ca-b8f59499dd43",
"web_session_id": "2d0c328a-01d0-4010-85f4-f327130d1bd4",
"visitor_id": "363fe851-7d8f-4090-902f-0f5a462829f5",
"page_url": "https://yoursite.com/?scnv=7ad26d4f-3181-4ef8-b6ca-b8f59499dd43",
"referrer": "https://google.com",
"timestamp": "2026-05-13T10:00:00.000Z",
"device": {
"user_agent": "Mozilla/5.0 ...",
"screen_width": 1440,
"screen_height": 900,
"language": "en-US"
},
"metadata": {}
}
```
**Important:** `autoPageview` fires on SDK initialisation. If you also call `scanova('track', 'page_view', ...)` manually, you will send two events per page load. Use one or the other.
**Deduplication:** The SDK guards against firing `pageview` twice for the same URL within a single SDK lifecycle. If a user navigates away and back to the same URL in a traditional multi-page site, the second load re-initialises the SDK and fires normally. In a SPA where the URL changes client-side, the guard means returning to a previously visited route does **not** re-fire the pageview — you must handle this manually. See the [SPA guide](/conversion-tracking/browser/spa-frameworks).
***
## Click
**Enabled by:** `autoClicks: true`
Fires when a user clicks on a link (``), button (`