# Hovercode API

> Create and manage QR codes, short links, and GS1 Digital Link codes programmatically.

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

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

---

## Introduction

Hovercode's API lets you create and update [dynamic QR codes](https://hovercode.com/blog/static-vs-dynamic-qr-codes/), short links, 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 |
| `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.

### 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`.

### 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.

### 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.

---

## 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.

---

## 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"
}
```

---

## Support

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