# Hovercode API

> Create and manage QR codes, PDF codes, short links, landing pages, forms, and GS1 Digital Link codes programmatically.

Base URL: `https://hovercode.com`

Human-readable docs: https://hovercode.com/api/

**Prefer working from an AI assistant?** Hovercode also has an [MCP server](https://hovercode.com/mcp/docs) so tools like Claude and Cursor can create and manage all of this conversationally.

---

## Introduction

Hovercode's API lets you create and update [dynamic QR codes](https://hovercode.com/blog/static-vs-dynamic-qr-codes/), PDF codes, short links, hosted landing pages, forms, and GS1 Digital Link codes programmatically. It's ideal for creating codes in bulk or adding QR/link features to your own product.

You need access to the business plan to use the API, but you can test it for free — see [pricing](https://hovercode.com/pricing/).

**Note:** You can't call this API from the browser — that would expose your API key. Call it from a back-end (PHP, Node.js, Python, Ruby, etc.).

---

## Authentication

Every request must include an `Authorization` header with your API token:

```
Authorization: Token YOUR-TOKEN
```

Find your token in your account settings while logged in. Keep it private. Your workspace ID is also in your account settings — many endpoints require it as a field named `workspace`.

---

## QR codes

### Create a QR code

`POST https://hovercode.com/api/v2/hovercode/create/`

Generates a QR code. Returns the QR code as an SVG string by default; set `generate_png: true` to also get `.png` and `.svg` file URLs (slower). `qr_type` defaults to `Link`; use `Text` for plain text, or see the vCard and GS1 sections for those types.

**Parameters**

| Name | Required | Description |
|---|---|---|
| `workspace` | required | Your workspace ID |
| `qr_data` | required (Link/Text) | For `qr_type=Link`: a valid URL. For `qr_type=Text`: any plain text |
| `qr_type` | optional | `Link` (default), `Text`, `vCard`, or `GS1` |
| `dynamic` | optional | `false` by default. Set `true` for a dynamic QR code |
| `display_name` | optional | Internal name for organising codes |
| `domain` | optional | Custom domain for dynamic codes |
| `generate_png` | optional | `true` also returns `.png` and `.svg` file URLs |
| `gps_tracking` | optional | Enables GPS tracking for dynamic codes |
| `error_correction` | optional | `L`, `M`, `Q`, or `H` |
| `size` | optional | Width in pixels. Defaults to `220` |
| `logo_url` | optional | URL to an image to embed as a logo |
| `template` | optional | ID of a design template saved in your workspace. Applies its full design (colors, pattern, eye style, frame, text, logo); fields passed explicitly override the template. Get IDs from List templates |
| `logo_round` | optional | Force logo into a circle shape |
| `primary_color` | optional | Hex color (with `#`). Defaults to `#111111` |
| `background_color` | optional | Hex color (with `#`). Transparent by default |
| `pattern` | optional | `Original` (default), `Circles`, `Squares`, `Diamonds`, `Triangles` |
| `eye_style` | optional | `Square` (default), `Rounded`, `Drop`, `Leaf` |
| `frame` | optional | `border`, `border-small`, `border-large`, `square`, `speech-bubble`, `speech-bubble-above`, `card`, `card-above`, `text-frame`, `round-frame`, `circle-viewfinder`, `solid-spin`, `burst`, `scattered-lines`, `polkadot`, `swirl` |
| `has_border` | optional | For frames with a border option |
| `text` | optional | For frames with a text option |

```python
import requests

data = {
    "workspace": "YOUR-WORKSPACE-ID",
    "qr_data": "https://twitter.com/hovercodeHQ",
    "primary_color": "#1DA1F2"
}

response = requests.post(
    "https://hovercode.com/api/v2/hovercode/create/",
    headers={"Authorization": "Token YOUR-TOKEN"},
    json=data,
    timeout=10,
)
```

### Create a vCard QR code

`POST https://hovercode.com/api/v2/hovercode/create/`

Set `qr_type` to `vCard` and pass a nested `vcard` object (instead of `qr_data`). A dynamic vCard (`dynamic: true`) is editable later; a static one (default) encodes the contact directly. `first_name` is required; all other vCard fields (`last_name`, `company_name`, `position`, `email`, `mobile_number`, `phone_number`, `website`, `street`, `city`, `state`, `country`, `post_code`, `description`) are optional.

```python
import requests

data = {
    "workspace": "YOUR-WORKSPACE-ID",
    "qr_type": "vCard",
    "dynamic": True,
    "vcard": {
        "first_name": "Ada",
        "last_name": "Lovelace",
        "email": "ada@example.com"
    }
}

response = requests.post(
    "https://hovercode.com/api/v2/hovercode/create/",
    headers={"Authorization": "Token YOUR-TOKEN"},
    json=data,
    timeout=10,
)
```

### List QR codes

`GET https://hovercode.com/api/v2/workspace/{WORKSPACE-ID}/hovercodes/`

Paginated (50 per page). Add `?q=` to search links, display names, shortlink URLs, and tags.

### List templates

`GET https://hovercode.com/api/v2/workspace/{WORKSPACE-ID}/templates/`

Returns the QR design templates saved in your workspace (created in the dashboard), paginated 50 per page. Each result includes `id`, `title`, `primary_color`, `background_color`, `pattern`, `eye_style`, `has_border`, `text`, `frame`, `has_logo`, and a `preview` image URL. Pass a template's `id` as the `template` field on the create endpoint to apply its design.

### Get a single QR code

`GET https://hovercode.com/api/v2/hovercode/{QR-CODE-ID}/`

### Get QR code tracking activity

`GET https://hovercode.com/api/v2/hovercode/{QR-CODE-ID}/activity/`

Paginated (max 200 via `page_size`). Each result: `qr_code_id`, `time_utc`, `time_timezone_aware`, `location`, `device`, `scanner_id`, `id`.

### Get scan analytics

`GET https://hovercode.com/api/v2/hovercode/{QR-CODE-ID}/analytics/`

Aggregated stats for one code over a period (default 30 days; add `?days=` up to 365): `total_scans`, `unique_scans`, `scans_by_day`, and top `top_countries`, `top_cities`, `top_os`, `top_browsers`. For the whole workspace, use `GET https://hovercode.com/api/v2/workspace/{WORKSPACE-ID}/analytics/` — same shape, plus `top_codes`.

### Download images

`GET https://hovercode.com/api/v2/hovercode/{QR-CODE-ID}/download/`

Returns print-ready image URLs: `png_url` (high-resolution PNG) and `svg_url` (vector SVG — best for large sizes/print). Images are generated on first request.

### Update a QR code

`POST https://hovercode.com/api/v2/hovercode/{QR-CODE-ID}/update/`

Change `display_name`, `qr_data` (Link type only), or `gps_tracking`. Returns the same shape as GET.

### Update a QR code's design

`PATCH https://hovercode.com/api/v2/hovercode/{QR-CODE-ID}/design/`

Restyle an existing code **in place** — the code, its destination, and its scan history are unchanged, so an already-printed code keeps working. The QR is re-rendered and the new `svg` is returned. Accepts any of: `primary_color`, `background_color`, `pattern`, `eye_style`, `frame`, `has_border`, `text`, `text_secondary`, `error_correction`, `logo_url`, `logo_round`, and `remove_logo` (set `true` to clear the logo). Same values as the create endpoint.

```python
import requests

response = requests.patch(
    "https://hovercode.com/api/v2/hovercode/{QR-CODE-ID}/design/",
    headers={"Authorization": "Token YOUR-TOKEN"},
    json={"primary_color": "#1DA1F2", "pattern": "Circles", "eye_style": "Rounded"},
    timeout=10,
)
```

### Add tags to a QR code

`POST https://hovercode.com/api/v2/hovercode/{QR-CODE-ID}/tags/add/`

### Delete a QR code

`DELETE https://hovercode.com/api/v2/hovercode/{QR-CODE-ID}/delete/`

Returns `204` on success.

---

## PDF codes

Host a PDF and get a dynamic QR code that points at it — for menus, flyers, tickets, and similar. Because it's dynamic you can swap the PDF later without reprinting the code, and scans are tracked. PDF codes are dynamic, so they count toward your plan's dynamic-code limit.

### Create a PDF code

`POST https://hovercode.com/api/v2/pdf/create/`

Provide the PDF as **either** `pdf_url` (Hovercode fetches it) **or** `pdf_base64` (the file's base64-encoded bytes). Returns the QR code plus its shareable short link and the hosted PDF URL.

**Parameters**

| Name | Required | Description |
|---|---|---|
| `workspace` | required | Your workspace ID |
| `pdf_url` | required* | A URL Hovercode fetches the PDF from. *Provide this or `pdf_base64` |
| `pdf_base64` | required* | Base64-encoded PDF bytes (a `data:` URI prefix is allowed), max 20 MB. *Provide this or `pdf_url` |
| `filename` | optional | Name for the stored file, e.g. `menu.pdf` |
| `display_name` | optional | Internal name for organising codes |
| `domain` | optional | A custom short-link domain available to your workspace |

```python
import requests

data = {
    "workspace": "YOUR-WORKSPACE-ID",
    "pdf_url": "https://example.com/menu.pdf",
    "filename": "menu.pdf"
}

response = requests.post(
    "https://hovercode.com/api/v2/pdf/create/",
    headers={"Authorization": "Token YOUR-TOKEN"},
    json=data,
    timeout=30,
)
```

The response is the standard QR code shape (`id`, `qr_data`, `shortlink_url`, `dynamic`, `svg`, …) plus `pdf_file_url` for the raw hosted file.

---

## Short links

A short link is a dynamic redirect with an associated QR code (the SVG is generated immediately; the `.png` is generated on first request).

### Create a short link

`POST https://hovercode.com/api/v2/link/create/`

**Parameters**

| Name | Required | Description |
|---|---|---|
| `workspace` | required | Your workspace ID |
| `link` | required | The destination URL |
| `slug` | optional | Custom slug (letters, numbers, hyphens). Unique per domain; not a reserved word. Auto-generated if omitted |
| `domain` | optional | One of the short link domains available to your workspace |
| `display_name` | optional | Internal organising name |
| `gps` | optional | Enable GPS location tracking |

```python
import requests

data = {
    "workspace": "YOUR-WORKSPACE-ID",
    "link": "https://twitter.com/hovercodeHQ",
    "slug": "hovercode-twitter"
}

response = requests.post(
    "https://hovercode.com/api/v2/link/create/",
    headers={"Authorization": "Token YOUR-TOKEN"},
    json=data,
    timeout=10,
)
```

### Get short links

`GET https://hovercode.com/api/v2/workspace/{WORKSPACE-ID}/links/`

Paginated. Add `?q=` to search destinations, display names, and slugs.

### Get a short link

`GET https://hovercode.com/api/v2/link/{LINK-ID}/`

### Update a short link

`PATCH https://hovercode.com/api/v2/link/{LINK-ID}/update/`

Update `link`, `display_name`, or `gps`.

### Delete a short link

`DELETE https://hovercode.com/api/v2/link/{LINK-ID}/delete/`

Returns `204`.

### Short link QR image

`GET https://hovercode.com/api/v2/link/{LINK-ID}/qr/?format=png`

`?format=svg` returns the SVG inline; `?format=png` (default) redirects to a generated `.png`.

### Short link activity

`GET https://hovercode.com/api/v2/link/{LINK-ID}/activity/`

Same paginated format as QR code activity.

---

## Landing pages

A landing page (micro "link-in-bio" page) is a hosted page with a title and a list of link buttons, plus its own short URL and QR code. Pages count toward your plan's page limit.

### Create a landing page

`POST https://hovercode.com/api/v2/page/create/`

Creates and publishes a page in one call, and returns its live URL and QR code.

**Parameters**

| Name | Required | Description |
|---|---|---|
| `workspace` | required | Your workspace ID |
| `title` | required | The page heading |
| `links` | optional | An ordered array of link buttons. Each: `url` (required), `title`, `type` (e.g. `Website` (default), `Instagram`, `YouTube`) |
| `description` | optional | Text shown under the title |
| `bg_color` | optional | Hex background color |
| `text_color` | optional | Hex text color |
| `domain` | optional | A custom page domain available to your workspace |
| `slug` | optional | Custom URL slug; auto-generated if omitted |

```python
import requests

data = {
    "workspace": "YOUR-WORKSPACE-ID",
    "title": "My Links",
    "links": [
        {"url": "https://example.com", "title": "Website"},
        {"url": "https://instagram.com/example", "title": "Instagram", "type": "Instagram"}
    ]
}

response = requests.post(
    "https://hovercode.com/api/v2/page/create/",
    headers={"Authorization": "Token YOUR-TOKEN"},
    json=data,
    timeout=10,
)
```

Response: `id`, `title`, `description`, `status`, `share_url` (the live page URL), `slug`, `qr_id`, `svg` (the QR), `links`, `created`.

### Get a landing page

`GET https://hovercode.com/api/v2/page/{PAGE-ID}/`

### List landing pages

`GET https://hovercode.com/api/v2/workspace/{WORKSPACE-ID}/pages/`

Returns the workspace's pages, newest first.

---

## Forms

Forms collect submissions through a shareable link or QR code. Building a form is a three-step flow: **create** the form, **save** its fields, then **publish** it — publishing mints the short link and QR code and makes the form live. You can then read submissions. Publishing creates a dynamic code, so it counts toward your dynamic-code limit.

### Create a form

`POST https://hovercode.com/api/v2/form/create/`

**Parameters**

| Name | Required | Description |
|---|---|---|
| `workspace` | required | Your workspace ID |
| `title` | required | The form title |
| `description` | optional | Shown above the fields |

Returns the form, including its `id`.

### Save fields

`POST https://hovercode.com/api/v2/form/{FORM-ID}/save/`

Saves the form's fields (and optional form-level settings) in one atomic call. Pass a `fields` array in display order. **New fields must be given an `id` that starts with `temp-`** (existing fields keep their real `id`); any field left out of the array is removed.

Each field: `field_type` (`text`, `email`, `phone`, `number`, `textarea`, `select`, `radio`, `checkbox`, `date`, `time`, `url`, `rating`, `scale`, `yes_no`, `image`), `label` (required), and optionally `required`, `placeholder`, and `options` (for `select`/`radio`/`checkbox`, as `[{"value": …, "label": …}]`). `image` (respondent photo/image upload) is available on paid plans — publishing a form containing one requires an active subscription.

The same call also accepts form-level settings: `title`, `description`, `redirect_url`, `thank_you_message`, and **branding** — `bg_color`, `text_color`, `button_color`, `button_text_color` (hex). To **edit** an existing form later, send the fields again including each field's real `id` to keep it (omit a field to remove it, leave `id` off a new one), then re-publish.

### Set a form's logo

`POST https://hovercode.com/api/v2/form/{FORM-ID}/logo-url/`

Set the form's logo from an image URL — `{"logo_url": "https://…/logo.png"}` (JPEG/PNG/GIF/WebP, max 2 MB). The JSON alternative to the multipart logo upload.

```python
import requests

fields = {
    "fields": [
        {"id": "temp-0", "field_type": "text", "label": "Name", "required": True},
        {"id": "temp-1", "field_type": "email", "label": "Email", "required": True},
        {"id": "temp-2", "field_type": "select", "label": "Shirt size",
         "options": [{"value": "S", "label": "S"}, {"value": "M", "label": "M"}]}
    ]
}

response = requests.post(
    "https://hovercode.com/api/v2/form/{FORM-ID}/save/",
    headers={"Authorization": "Token YOUR-TOKEN"},
    json=fields,
    timeout=10,
)
```

### Publish a form

`POST https://hovercode.com/api/v2/form/{FORM-ID}/publish/`

Snapshots the current fields as the live form, creates the short link and QR code, and sets the form to `Published`. Optional body: `domain` and `slug` (as with short links). Returns the form including `shortlink_url`, `qr_code_svg`, and (on first publish) `share_url`.

### Close or reopen a form

`POST https://hovercode.com/api/v2/form/{FORM-ID}/close/` stops accepting responses; `POST .../reopen/` resumes.

### List forms

`GET https://hovercode.com/api/v2/forms/?workspace={WORKSPACE-ID}`

The workspace's forms (newest first). `?workspace=` is required. Add `?q=` to search by title. Paginated (`?page=`, `?page_size=` up to 100).

### List responses

`GET https://hovercode.com/api/v2/forms/{FORM-ID}/responses/`

A form's submissions, newest first. Each response includes its per-field answers.

---

## GS1 Digital Link

A GS1 Digital Link encodes one identifier (e.g. a GTIN) that resolves to a destination. The quickest way to make one is a single call to [Create a GS1 QR code](#create-a-gs1-qr-code) — pass an identifier and a destination.

The product/link endpoints below are for **advanced** use: managing an identifier's *multiple* link types (product info, instructions, recalls…) over time, and reusing one identifier across several codes. You don't need them for a basic GS1 QR. Resolution is handled by Hovercode's resolver.

### Create a GS1 product

`POST https://hovercode.com/api/v2/gs1/products/`

**Parameters**

| Name | Required | Description |
|---|---|---|
| `workspace` | required | Your workspace ID |
| `identifier_value` | required | The identifier value, e.g. a GTIN. Validated incl. GS1 check digit |
| `identifier_type` | optional | GS1 Application Identifier. One of: `01` (GTIN), `00` (SSCC), `414` (GLN), `417` (Party GLN), `253` (GDTI), `255` (GCN), `401` (GINC), `402` (GSIN), `8003` (GRAI), `8004` (GIAI), `8006` (ITIP), `8013` (GMN), `8017` (GSRN – Provider), `8018` (GSRN – Recipient). Defaults to `01` |
| `batch_lot`, `serial_number` | optional | Qualifiers added to the Digital Link path |
| `expiry_date` | optional | `YYMMDD` format (e.g. `261231`) |
| `destination_url` | optional | Seeds a default product information page (`gs1:pip`) link |

```python
import requests

data = {
    "workspace": "YOUR-WORKSPACE-ID",
    "identifier_type": "01",
    "identifier_value": "09506000134369",
    "destination_url": "https://example.com/product"
}

response = requests.post(
    "https://hovercode.com/api/v2/gs1/products/",
    headers={"Authorization": "Token YOUR-TOKEN"},
    json=data,
    timeout=10,
)
```

### List GS1 products

`GET https://hovercode.com/api/v2/workspace/{WORKSPACE-ID}/gs1/products/`

### Get, update or delete a GS1 product

`GET | PATCH | DELETE https://hovercode.com/api/v2/gs1/products/{PRODUCT-ID}/`

### Manage link types

`POST https://hovercode.com/api/v2/gs1/products/{PRODUCT-ID}/links/`

Each product resolves to one destination per link type. Exactly one is the default — setting a new default unsets the others, and deleting the default promotes another.

**Parameters**

| Name | Required | Description |
|---|---|---|
| `link_type` | required | One of: `gs1:pip`, `gs1:quickStartGuide`, `gs1:instructions`, `gs1:safetyInfo`, `gs1:recipeInfo`, `gs1:traceability`, `gs1:hasRetailers`, `gs1:recallStatus`, `gs1:review`, `gs1:ePIL`, `gs1:productSustainabilityInfo`, `gs1:promotion`, `gs1:masterData`, `gs1:smpc`, `gs1:certificationInfo`, `gs1:registerProduct` |
| `title` | required | Human-readable label |
| `destination_url` | required | Where this link type resolves to |
| `is_default`, `language`, `media_type` | optional | Default flag, language (e.g. `en`), media type (defaults to `text/html`) |

Update or delete a link with `PATCH | DELETE https://hovercode.com/api/v2/gs1/products/{PRODUCT-ID}/links/{LINK-ID}/`.

### Get the linkset

`GET https://hovercode.com/api/v2/gs1/products/{PRODUCT-ID}/linkset/`

Returns an [RFC 9264](https://www.rfc-editor.org/rfc/rfc9264.html) linkset (`application/linkset+json`) — a preview of what the resolver serves. Optional `?domain=` sets the anchor domain.

### Create a GS1 QR code

`POST https://hovercode.com/api/v2/hovercode/create/`

The quickest way to make a GS1 QR. Set `qr_type` to `GS1` and pass a `gs1_product` **object** with an `identifier_value` and a `destination_url` — this creates the GS1 product and the QR in one call. GS1 codes are always dynamic — the QR encodes the Digital Link URI, which the resolver redirects by link type.

```python
import requests

data = {
    "workspace": "YOUR-WORKSPACE-ID",
    "qr_type": "GS1",
    "gs1_product": {
        "identifier_value": "09506000134369",
        "destination_url": "https://example.com/product"
    }
}

response = requests.post(
    "https://hovercode.com/api/v2/hovercode/create/",
    headers={"Authorization": "Token YOUR-TOKEN"},
    json=data,
    timeout=10,
)
```

To reuse a product you already created (see the advanced endpoints above), pass its id as a string instead: `"gs1_product": "{PRODUCT-ID}"`.

---

## Webhooks

Enable webhooks from your workspace API settings (Business Plus plan). Every scan of a dynamic QR code or short link triggers a `POST` to your URL with `Content-Type: application/json` and an `x-signature` header. Verify `x-signature` against your webhook secret before processing.

```json
{
  "qr_code_id": "2fbb014a-4b5a-4ecd-95a3-p914d4aa167b",
  "time_utc": "2026-06-01 17:44:48.920050+00:00",
  "time_timezone_aware": "Jun. 1, 2026, 05:44 p.m.",
  "location": "London, England, United Kingdom",
  "device": "iPhone, iOS, Mobile Safari",
  "scanner_id": "5dd831a872687315f54a11fa62d089e66887647c67d5ad2cd89ebd3a38084bd3",
  "id": "0acb2379-c9e3-4245-a1e3-6e542cc02637"
}
```

---

## MCP server

Hovercode has a [Model Context Protocol](https://modelcontextprotocol.io/) server, so AI assistants like Claude and Cursor can create and manage QR codes, PDF codes, short links, pages, and forms conversationally — no API calls to write. Anything the assistant creates lands in your dashboard like any other code.

Endpoint: `https://hovercode.com/mcp/`

Connect it two ways:

- **OAuth** — in a client that supports remote MCP connectors, add `https://hovercode.com/mcp/` and approve access (you'll go through a normal Hovercode login). Nothing to copy.
- **API token** — for CLI/config clients, pass your token as a bearer token. In Claude Code:

```
claude mcp add --transport http hovercode https://hovercode.com/mcp/ \
  --header "Authorization: Bearer YOUR-TOKEN"
```

**Things you can ask for once it's connected:**

- "Point my printed *menu* QR code at the new PDF at this link" — retarget a dynamic code without reprinting it.
- "Make a form to collect name, email, and a rating, and give me a QR for the table."
- "Turn this PDF into a QR code for the flyer."
- "How did my pricing-page QR do this month, and where were people scanning it?"
- "Make a link-in-bio page with my Instagram, shop, and booking links, and a QR for the window."
- "Summarize this week's responses to my feedback form."

Building your own agent? This same guide is served as markdown at `https://hovercode.com/mcp/docs`.

---

## Support

This API is in active development — [send feedback or questions](https://hovercode.com/contact/) any time.
