Dynamic QR code API
Automate your QR code creation or add QR code features to your product
02
03
04
05
06
requests.post( 'https://hovercode.com/', headers={'Authorization': 'Token YOUR-TOKEN'}, json=data, timeout=10 )
Introduction
Hovercode's API lets you create and update dynamic QR codes, PDF codes, short links, landing pages, forms, and GS1 Digital Link codes programmatically. It's ideal for creating QR codes in bulk or adding QR/link features to your own product.
You need the business plan to use the API, but you can test it for free (pricing).
Note: calls must be made from a back-end — never the browser — so your token isn't exposed.
Want to drive all of this from an AI assistant like Claude or Cursor? Hovercode has an MCP server that exposes these same capabilities as conversational tools.
Base URL
https://hovercode.com/api/v2/
AUTH Authentication
The API uses token authentication. Send your token in the Authorization header on every request:
Authorization: Token YOUR-TOKEN
You can find your token in your settings area while logged in. Keep it secret — anyone with it can use your credits.
QR codes
Generate styled QR codes. The same create endpoint produces every QR type via the qr_type field.
POST Create a QR code
https://hovercode.com/api/v2/hovercode/create/
Returns the QR code as an SVG string by default. Set generate_png to 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.
| Paramater name | Required | Description |
|---|---|---|
| workspace | Required | Every account has a workspace ID. You can find yours with your API token in your settings area |
| qr_data | Required | When using the default qr_type of "Link" this has to be a valid URL. With the type "Text" this can be any plain text |
| qr_type | Defaults to "Link" | This defaults to "Link" and can only currently be "Link or "Text". "Text" QR codes are plain text and can only be static (not dynamic) |
| dynamic | Defaults to false | Your QR code is static by default. Set this to true to make it a dynamic QR code. |
| display_name | Not required | You can optionally add a display name to your QR codes so they are easier to organise in your Hovercode dashboard (the display_name isn't customer facting) |
| domain | Not required (defaults to the default domain from your workspace) | [Only applies to dynamic QR codes, has effect on static codes] If you have multiple custom domains linked to your workspace, you can specify which you want to use here. |
| generate_png | Not required (defaults to false) | Set this to true to include a .png and .svg QR code in your response. This slows down the response. Without this set to true, the QR code is only returned as an SVG string. You can retrieve the .png or .svg file in future requests even if this has not been sert to true. |
| gps_tracking | Not required (defaults to false) | This is to enable the GPS tracking feature for the QR code. It's only for dynamic codes. More details. |
| error_correction | Not required (defaults to 'Q' without a logo or 'H' with a logo) | Use this to set the error correction of your QR code. Options are L, M, Q, or H |
| size | Not required (defaults to 220) | Sets the size of the QR code in pixels. Defaults to 220. The height is set automatically based on the width and the frame (the same if it's a square frame) |
| logo_url | Not required | Optionally add a url to an image to use it as a logo in your QR code. Don't use a massive image file and stick with pngs or jpegs |
| template | Not required | The ID of a design template saved in your workspace. Applies the template's full design — colors, pattern, eye style, frame, text and logo — to the new QR code. Any design field you also pass explicitly overrides the template's value. Get template IDs from the Get templates endpoint |
| logo_round | Not required | When creating a QR code with a logo, you can set this to True to force the logo into a circle shape |
| primary_color | Not required (defaults to #111111) | Change the color of your QR code by adding a valid HEX color code (including the '#') |
| background_color | Not required | Change the color of your QR code background by adding a valid HEX color code (including the '#'). By default, the QR code has no background color set (it's transparent). |
| pattern | Not required (defaults to "Original") | This refers to the shape of the pattern in your QR code. The default is "Original" and the options are: Original, Circles, Squares, Diamonds, Triangles |
| eye_style | Not required (defaults to "Square") | This sets the style of the "eyes" on the three corners of the QR code. "Square" is the default and the other options are: Rounded, Drop, and Leaf |
| frame | Not required | By default your generated QR code will have no frame. The frames available through the API are the same ones you can use at hovercode.com. They are: 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, and swirl |
| has_border | Not required | Some frames have a "border" option, which is false by default. If you are using a frame that has a border option you want to use, set this to true. This has no effect on QR codes using no frame or a frame with no border option. |
| text | Not required | Some frames have a "text" option, which is empty by default. If you are using a frame that has a text option you want to use, set the text here. Depending on the frame, it will have a max length. This has no effect on QR codes using no frame or a frame with no text option. |
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
)
<?php
$data = array(
"workspace" => "YOUR-WORKSPACE-ID",
"qr_data" => "https://twitter.com/hovercodeHQ",
"primary_color" => "#1DA1F2"
);
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://hovercode.com/api/v2/hovercode/create/',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => json_encode($data),
CURLOPT_HTTPHEADER => array(
'Authorization: Token YOUR-TOKEN',
'Content-Type: application/json'
),
));
$response = curl_exec($curl);
curl_close($curl);
?>
curl -X POST \
'https://hovercode.com/api/v2/hovercode/create/' \
-H 'Authorization: Token YOUR-TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"workspace": "YOUR-WORKSPACE-ID",
"qr_data": "https://twitter.com/hovercodeHQ",
"primary_color": "#1DA1F2"
}'
const axios = require('axios');
const data = {
workspace: 'YOUR-WORKSPACE-ID',
qr_data: 'https://twitter.com/hovercodeHQ',
primary_color: '#1DA1F2'
};
axios.post('https://hovercode.com/api/v2/hovercode/create/', data, {
headers: {
Authorization: 'Token YOUR-TOKEN'
},
timeout: 10000
})
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error(error);
});
require 'net/http'
require 'uri'
require 'json'
uri = URI('https://hovercode.com/api/v2/hovercode/create/')
data = {
"workspace" => "YOUR-WORKSPACE-ID",
"qr_data" => "https://twitter.com/hovercodeHQ",
"primary_color" => "#1DA1F2"
}
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.read_timeout = 10
request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Token YOUR-TOKEN'
request.content_type = 'application/json'
request.body = data.to_json
response = http.request(request)
puts response.body
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
public class Main {
public static void main(String[] args) {
HttpClient client = HttpClient.newBuilder()
.build();
String jsonData = new StringBuilder()
.append("{")
.append("\"workspace\":\"YOUR-WORKSPACE-ID\",")
.append("\"qr_data\":\"https://twitter.com/hovercodeHQ\",")
.append("\"primary_color\":\"#1DA1F2\"")
.append("}")
.toString();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://hovercode.com/api/v2/hovercode/create/"))
.timeout(Duration.ofSeconds(10))
.header("Authorization", "Token YOUR-TOKEN")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonData))
.build();
try {
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
} catch (Exception e) {
e.printStackTrace();
}
}
}
This is the resulting QR code:
POST Create a vCard QR code
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 (set dynamic to true) is editable later; a static one (the default)
encodes the contact directly. first_name is required; all other vCard fields are optional. (Profile photos aren't supported via the API yet.)
import requests
data = {
"workspace": "YOUR-WORKSPACE-ID",
"qr_type": "vCard",
"dynamic": True,
"vcard": {
"first_name": "Ada",
"last_name": "Lovelace",
"company_name": "Analytical Engines",
"email": "ada@example.com",
"mobile_number": "+1 555 0100"
}
}
response = requests.post(
'https://hovercode.com/api/v2/hovercode/create/',
headers={'Authorization': 'Token YOUR-TOKEN'},
json=data,
timeout=10
)
<?php
$data = array(
"workspace" => "YOUR-WORKSPACE-ID",
"qr_type" => "vCard",
"dynamic" => true,
"vcard" => array(
"first_name" => "Ada",
"last_name" => "Lovelace",
"company_name" => "Analytical Engines",
"email" => "ada@example.com",
"mobile_number" => "+1 555 0100"
)
);
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://hovercode.com/api/v2/hovercode/create/',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => json_encode($data),
CURLOPT_HTTPHEADER => array(
'Authorization: Token YOUR-TOKEN',
'Content-Type: application/json'
),
));
$response = curl_exec($curl);
curl_close($curl);
?>
curl -X POST \
'https://hovercode.com/api/v2/hovercode/create/' \
-H 'Authorization: Token YOUR-TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"workspace": "YOUR-WORKSPACE-ID",
"qr_type": "vCard",
"dynamic": true,
"vcard": {
"first_name": "Ada",
"last_name": "Lovelace",
"company_name": "Analytical Engines",
"email": "ada@example.com",
"mobile_number": "+1 555 0100"
}
}'
const axios = require('axios');
const data = {
workspace: 'YOUR-WORKSPACE-ID',
qr_type: 'vCard',
dynamic: true,
vcard: {
first_name: 'Ada',
last_name: 'Lovelace',
company_name: 'Analytical Engines',
email: 'ada@example.com',
mobile_number: '+1 555 0100'
}
};
axios.post('https://hovercode.com/api/v2/hovercode/create/', data, {
headers: {
Authorization: 'Token YOUR-TOKEN'
},
timeout: 10000
})
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error(error);
});
require 'net/http'
require 'uri'
require 'json'
uri = URI('https://hovercode.com/api/v2/hovercode/create/')
data = {
"workspace" => "YOUR-WORKSPACE-ID",
"qr_type" => "vCard",
"dynamic" => true,
"vcard" => {
"first_name" => "Ada",
"last_name" => "Lovelace",
"company_name" => "Analytical Engines",
"email" => "ada@example.com",
"mobile_number" => "+1 555 0100"
}
}
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.read_timeout = 10
request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Token YOUR-TOKEN'
request.content_type = 'application/json'
request.body = data.to_json
response = http.request(request)
puts response.body
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
public class Main {
public static void main(String[] args) {
HttpClient client = HttpClient.newBuilder()
.build();
String jsonData = new StringBuilder()
.append("{")
.append("\"workspace\":\"YOUR-WORKSPACE-ID\",")
.append("\"qr_type\":\"vCard\",")
.append("\"dynamic\":true,")
.append("\"vcard\":{")
.append("\"first_name\":\"Ada\",")
.append("\"last_name\":\"Lovelace\",")
.append("\"company_name\":\"Analytical Engines\",")
.append("\"email\":\"ada@example.com\",")
.append("\"mobile_number\":\"+1 555 0100\"")
.append("}")
.append("}")
.toString();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://hovercode.com/api/v2/hovercode/create/"))
.timeout(Duration.ofSeconds(10))
.header("Authorization", "Token YOUR-TOKEN")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonData))
.build();
try {
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
} catch (Exception e) {
e.printStackTrace();
}
}
}
The response echoes the contact back under a vcard key, with the usual QR fields.
GET Get QR codes
https://hovercode.com/api/v2/workspace/WORKSPACE-ID/hovercodes/
Returns your workspace's QR codes, paginated 50 per page. Add ?q= to search links, display names, shortlink URLs and tags.
import requests
response = requests.get(
'https://hovercode.com/api/v2/workspace/YOUR-WORKSPACE-ID/hovercodes/',
headers={'Authorization': 'Token YOUR-TOKEN'},
timeout=10
)
<?php
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://hovercode.com/api/v2/workspace/YOUR-WORKSPACE-ID/hovercodes/',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => array(
'Authorization: Token YOUR-TOKEN'
),
));
$response = curl_exec($curl);
curl_close($curl);
echo $response;
?>
curl -X GET \
'https://hovercode.com/api/v2/workspace/YOUR-WORKSPACE-ID/hovercodes/' \
-H 'Authorization: Token YOUR-TOKEN'
const axios = require('axios');
axios.get('https://hovercode.com/api/v2/workspace/YOUR-WORKSPACE-ID/hovercodes/', {
headers: {
Authorization: 'Token YOUR-TOKEN'
},
timeout: 10000
})
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error(error);
});
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class GetQRCodeActivity {
public static void main(String[] args) {
try {
String workspaceId = "YOUR-WORKSPACE-ID";
String token = "YOUR_TOKEN";
URL url = new URL("https://hovercode.com/api/v2/workspace/" + workspaceId + "/hovercodes/");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Authorization", "Token " + token);
try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"))) {
StringBuilder response = new StringBuilder();
String responseLine = null;
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
System.out.println(response.toString());
}
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
require 'net/http'
require 'uri'
uri = URI.parse('https://hovercode.com/api/v2/workspace/YOUR-WORKSPACE-ID/hovercodes/')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri.path)
request['Authorization'] = 'Token YOUR-TOKEN'
response = http.request(request)
puts response.read_body
GET Get templates
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.
Pass a template's id as the template field when creating a QR code to apply its full design —
colors, pattern, eye style, frame, text and logo — without re-uploading anything. Any design field you set
explicitly in the create request overrides the template's value.
import requests
response = requests.get(
'https://hovercode.com/api/v2/workspace/YOUR-WORKSPACE-ID/templates/',
headers={'Authorization': 'Token YOUR-TOKEN'},
timeout=10
)
<?php
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://hovercode.com/api/v2/workspace/YOUR-WORKSPACE-ID/templates/',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => array(
'Authorization: Token YOUR-TOKEN'
),
));
$response = curl_exec($curl);
curl_close($curl);
echo $response;
?>
curl -X GET \
'https://hovercode.com/api/v2/workspace/YOUR-WORKSPACE-ID/templates/' \
-H 'Authorization: Token YOUR-TOKEN'
const axios = require('axios');
axios.get('https://hovercode.com/api/v2/workspace/YOUR-WORKSPACE-ID/templates/', {
headers: {
Authorization: 'Token YOUR-TOKEN'
},
timeout: 10000
})
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error(error);
});
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class GetTemplates {
public static void main(String[] args) {
try {
String workspaceId = "YOUR-WORKSPACE-ID";
String token = "YOUR_TOKEN";
URL url = new URL("https://hovercode.com/api/v2/workspace/" + workspaceId + "/templates/");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Authorization", "Token " + token);
try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"))) {
StringBuilder response = new StringBuilder();
String responseLine = null;
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
System.out.println(response.toString());
}
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
require 'net/http'
require 'uri'
uri = URI.parse('https://hovercode.com/api/v2/workspace/YOUR-WORKSPACE-ID/templates/')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri.path)
request['Authorization'] = 'Token YOUR-TOKEN'
response = http.request(request)
puts response.read_body
GET Get a single QR code
https://hovercode.com/api/v2/hovercode/QR-CODE-ID/
Retrieve a previously created QR code, including its .png and .svg file URLs once they've been generated.
import requests
response = requests.get(
'https://hovercode.com/api/v2/hovercode/QR-CODE-ID/',
headers={'Authorization': 'Token YOUR-TOKEN'},
timeout=10
)
<?php
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://hovercode.com/api/v2/hovercode/QR-CODE-ID/',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => array(
'Authorization: Token YOUR-TOKEN'
),
));
$response = curl_exec($curl);
curl_close($curl);
echo $response;
?>
curl -X GET \
'https://hovercode.com/api/v2/hovercode/QR-CODE-ID/' \
-H 'Authorization: Token YOUR-TOKEN'
const axios = require('axios');
axios.get('https://hovercode.com/api/v2/hovercode/QR-CODE-ID/', {
headers: {
Authorization: 'Token YOUR-TOKEN'
},
timeout: 10000
})
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error(error);
});
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class GetQRCode {
public static void main(String[] args) {
try {
String qrCodeId = "YOUR_QR_CODE_ID";
String token = "YOUR_TOKEN";
URL url = new URL("https://hovercode.com/api/v2/hovercode/" + qrCodeId + "/");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Authorization", "Token " + token);
try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"))) {
StringBuilder response = new StringBuilder();
String responseLine = null;
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
System.out.println(response.toString());
}
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
require 'net/http'
require 'uri'
uri = URI.parse('https://hovercode.com/api/v2/hovercode/QR-CODE-ID/')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri.path)
request['Authorization'] = 'Token YOUR-TOKEN'
response = http.request(request)
puts response.read_body
GET Get QR code activity
https://hovercode.com/api/v2/hovercode/QR-CODE-ID/activity/
Tracking activity for a QR code, paginated (up to 200 per page via page_size).
import requests
response = requests.get(
'https://hovercode.com/api/v2/hovercode/QR-CODE-ID/activity/',
headers={'Authorization': 'Token YOUR-TOKEN'},
timeout=10
)
<?php
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://hovercode.com/api/v2/hovercode/QR-CODE-ID/activity/',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => array(
'Authorization: Token YOUR-TOKEN'
),
));
$response = curl_exec($curl);
curl_close($curl);
echo $response;
?>
curl -X GET \
'https://hovercode.com/api/v2/hovercode/QR-CODE-ID/activity/' \
-H 'Authorization: Token YOUR-TOKEN'
const axios = require('axios');
axios.get('https://hovercode.com/api/v2/hovercode/QR-CODE-ID/activity/', {
headers: {
Authorization: 'Token YOUR-TOKEN'
},
timeout: 10000
})
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error(error);
});
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class GetQRCodeActivity {
public static void main(String[] args) {
try {
String qrCodeId = "YOUR_QR_CODE_ID";
String token = "YOUR_TOKEN";
URL url = new URL("https://hovercode.com/api/v2/hovercode/" + qrCodeId + "/activity/");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Authorization", "Token " + token);
try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"))) {
StringBuilder response = new StringBuilder();
String responseLine = null;
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
System.out.println(response.toString());
}
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
require 'net/http'
require 'uri'
uri = URI.parse('https://hovercode.com/api/v2/hovercode/QR-CODE-ID/activity/')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri.path)
request['Authorization'] = 'Token YOUR-TOKEN'
response = http.request(request)
puts response.read_body
GET Get scan analytics
https://hovercode.com/api/v2/hovercode/QR-CODE-ID/analytics/
Aggregated stats for one code over a period — add ?days= (default 30, max 365). Returns
total_scans, unique_scans, scans_by_day, and top
top_countries, top_cities, top_os, top_browsers. For the
whole workspace use GET /api/v2/workspace/WORKSPACE-ID/analytics/ —
same shape plus top_codes.
GET Download images
https://hovercode.com/api/v2/hovercode/QR-CODE-ID/download/
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.
PATCH Update a QR code
https://hovercode.com/api/v2/hovercode/QR-CODE-ID/update/
Change the display_name or, for dynamic codes, the qr_data (scan destination). Also toggles gps_tracking.
import requests
data = {
"qr_data": "https://twitter.com/ramykhuffash",
"display_name": "hi"
}
response = requests.put(
'https://hovercode.com/api/v2/hovercode/QR-CODE-ID/update/',
headers={'Authorization': 'Token YOUR-TOKEN'},
json=data,
timeout=10
)
<?php
$data = array(
"qr_data" => "https://twitter.com/ramykhuffash",
"display_name" => "hi"
);
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://hovercode.com/api/v2/hovercode/QR-CODE-ID/update/',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_POSTFIELDS => json_encode($data),
CURLOPT_HTTPHEADER => array(
'Authorization: Token YOUR-TOKEN',
'Content-Type: application/json'
),
));
$response = curl_exec($curl);
curl_close($curl);
echo $response;
?>
curl -X PUT \
'https://hovercode.com/api/v2/hovercode/QR-CODE-ID/update/' \
-H 'Authorization: Token YOUR-TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"qr_data": "https://twitter.com/ramykhuffash",
"display_name": "hi"
}'
const axios = require('axios');
const data = {
qr_data: 'https://twitter.com/ramykhuffash',
display_name: 'hi'
};
axios.put('https://hovercode.com/api/v2/hovercode/QR-CODE-ID/update/', data, {
headers: {
Authorization: 'Token YOUR-TOKEN'
},
timeout: 10000
})
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error(error);
});
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.OutputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class UpdateQRCode {
public static void main(String[] args) {
try {
String qrCodeId = "YOUR_QR_CODE_ID";
String token = "YOUR_TOKEN";
URL url = new URL("https://hovercode.com/api/v2/hovercode/" + qrCodeId + "/update/");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("PUT");
conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestProperty("Authorization", "Token " + token);
conn.setDoOutput(true);
String jsonInputString = "{\"qr_data\": \"https://twitter.com/ramykhuffash\", \"display_name\": \"hi\"}";
try (OutputStream os = conn.getOutputStream()) {
byte[] input = jsonInputString.getBytes("utf-8");
os.write(input, 0, input.length);
}
try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"))) {
StringBuilder response = new StringBuilder();
String responseLine = null;
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
System.out.println(response.toString());
}
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
require 'net/http'
require 'uri'
require 'json'
uri = URI.parse('https://hovercode.com/api/v2/hovercode/QR-CODE-ID/update/')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Put.new(uri.path)
request['Content-Type'] = 'application/json'
request['Authorization'] = 'Token YOUR-TOKEN'
request.body = { "qr_data": "https://twitter.com/ramykhuffash", "display_name": "hi" }.to_json
response = http.request(request)
puts response.read_body
PATCH Update a QR code's design
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) — the same values as the create endpoint.
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
)
curl -X PATCH \
'https://hovercode.com/api/v2/hovercode/QR-CODE-ID/design/' \
-H 'Authorization: Token YOUR-TOKEN' \
-H 'Content-Type: application/json' \
-d '{"primary_color": "#1DA1F2", "pattern": "Circles", "eye_style": "Rounded"}'
POST Add tags to a QR code
https://hovercode.com/api/v2/hovercode/QR-CODE-ID/tags/add/
Add tag names or IDs to a QR code. New tag names are created if they don't exist.
import requests
data = [
{"title": "my tag"},
{"title": "my second tag"}
]
response = requests.post(
'https://hovercode.com/api/v2/hovercode/QR-CODE-ID/tags/add/',
headers={'Authorization': 'Token YOUR-TOKEN'},
json=data,
timeout=10
)
<?php
$data = array(
array("title" => "my tag"),
array("title" => "my second tag")
);
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://hovercode.com/api/v2/hovercode/QR-CODE-ID/tags/add/',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => json_encode($data),
CURLOPT_HTTPHEADER => array(
'Authorization: Token YOUR-TOKEN',
'Content-Type: application/json'
),
));
$response = curl_exec($curl);
curl_close($curl);
?>
curl -X POST \
'https://hovercode.com/api/v2/hovercode/QR-CODE-ID/tags/add/' \
-H 'Authorization: Token YOUR-TOKEN' \
-H 'Content-Type: application/json' \
-d '[
{"title": "my tag"},
{"title": "my second tag"}
]'
const axios = require('axios');
const data = [
{ title: 'my tag' },
{ title: 'my second tag' }
];
axios.post('https://hovercode.com/api/v2/hovercode/QR-CODE-ID/tags/add/', data, {
headers: {
Authorization: 'Token YOUR-TOKEN'
},
timeout: 10000
})
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error(error);
});
require 'net/http'
require 'uri'
require 'json'
uri = URI('https://hovercode.com/api/v2/hovercode/YOUR-QR-CODE-ID/tags/add/')
data = [
{"title" => "my tag"},
{"title" => "my second tag"}
]
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.read_timeout = 10
request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Token YOUR-TOKEN'
request.content_type = 'application/json'
request.body = data.to_json
response = http.request(request)
puts response.body
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
public class Main {
public static void main(String[] args) {
HttpClient client = HttpClient.newBuilder()
.build();
String jsonData = "[{\"title\":\"my tag\"}, {\"title\":\"my second tag\"}]";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://hovercode.com/api/v2/hovercode/YOUR-QR-CODE-ID/tags/add/"))
.timeout(Duration.ofSeconds(10))
.header("Authorization", "Token YOUR-TOKEN")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonData))
.build();
try {
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
} catch (Exception e) {
e.printStackTrace();
}
}
}
DELETE Delete a QR code
https://hovercode.com/api/v2/hovercode/QR-CODE-ID/delete/
Permanently deletes the QR code. Returns 204 on success.
import requests
response = requests.delete(
'https://hovercode.com/api/v2/hovercode/QR-CODE-ID/delete/',
headers={'Authorization': 'Token YOUR-TOKEN'},
timeout=10
)
<?php
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://hovercode.com/api/v2/hovercode/QR-CODE-ID/delete/',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => array(
'Authorization: Token YOUR-TOKEN'
),
));
$response = curl_exec($curl);
curl_close($curl);
echo $response;
?>
curl -X DELETE \
'https://hovercode.com/api/v2/hovercode/QR-CODE-ID/delete/' \
-H 'Authorization: Token YOUR-TOKEN' \
-H 'Content-Type: application/json' \
-d ''
const axios = require('axios');
axios.delete('https://hovercode.com/api/v2/hovercode/QR-CODE-ID/delete/', {
headers: {
Authorization: 'Token YOUR-TOKEN'
},
timeout: 10000
})
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error(error);
});
require 'net/http'
require 'uri'
uri = URI('https://hovercode.com/api/v2/hovercode/YOUR-QR-CODE-ID/delete/')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.read_timeout = 10
request = Net::HTTP::Delete.new(uri)
request['Authorization'] = 'Token YOUR-TOKEN'
response = http.request(request)
puts response.body
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpRequest.BodyPublishers;
import java.time.Duration;
public class Main {
public static void main(String[] args) {
HttpClient client = HttpClient.newBuilder()
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://hovercode.com/api/v2/hovercode/YOUR-QR-CODE-ID/delete/"))
.timeout(Duration.ofSeconds(10))
.header("Authorization", "Token YOUR-TOKEN")
.DELETE()
.build();
try {
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
} catch (Exception e) {
e.printStackTrace();
}
}
}
PDF codes
Host a PDF and get a dynamic QR code that points at it — for menus, flyers, tickets and the like. Because it's dynamic you can swap the PDF later without reprinting the code, and scans are tracked. PDF codes count toward your plan's dynamic-code limit.
POST Create a PDF code
https://hovercode.com/api/v2/pdf/create/
Provide the PDF as either pdf_url (Hovercode fetches it) or pdf_base64 (the file's base64 bytes).
| Parameter | Required | Description |
|---|---|---|
| workspace | Required | Your workspace ID. |
| pdf_url | Either/or | A URL Hovercode fetches the PDF from. |
| pdf_base64 | Either/or | Base64-encoded PDF bytes (a data: URI prefix is allowed), max 20 MB. |
| filename | Optional | Name for the stored file, e.g. menu.pdf. |
| display_name | Optional | Internal organising name. |
| domain | Optional | A custom short-link domain available to your workspace. |
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
)
curl -X POST \
'https://hovercode.com/api/v2/pdf/create/' \
-H 'Authorization: Token YOUR-TOKEN' \
-H 'Content-Type: application/json' \
-d '{"workspace": "YOUR-WORKSPACE-ID", "pdf_url": "https://example.com/menu.pdf", "filename": "menu.pdf"}'
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
Short links are the same primitive that powers dynamic QR codes, without the visual. A short link has a QR code too — its SVG is generated immediately, while the .png is only created when you request the QR image, keeping creation fast.
POST Create a short link
https://hovercode.com/api/v2/link/create/
| Parameter | Required | Description |
|---|---|---|
| workspace | Required | Your workspace ID. |
| link | Required | The destination URL. |
| slug | Optional | Custom slug (letters, numbers, hyphens). Must be unique for the domain and 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 (not customer-facing). |
| gps | Optional | Enable GPS location tracking. |
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
)
<?php
$data = array(
"workspace" => "YOUR-WORKSPACE-ID",
"link" => "https://twitter.com/hovercodeHQ",
"slug" => "hovercode-twitter"
);
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://hovercode.com/api/v2/link/create/',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => json_encode($data),
CURLOPT_HTTPHEADER => array(
'Authorization: Token YOUR-TOKEN',
'Content-Type: application/json'
),
));
$response = curl_exec($curl);
curl_close($curl);
?>
curl -X POST \
'https://hovercode.com/api/v2/link/create/' \
-H 'Authorization: Token YOUR-TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"workspace": "YOUR-WORKSPACE-ID",
"link": "https://twitter.com/hovercodeHQ",
"slug": "hovercode-twitter"
}'
const axios = require('axios');
const data = {
workspace: 'YOUR-WORKSPACE-ID',
link: 'https://twitter.com/hovercodeHQ',
slug: 'hovercode-twitter'
};
axios.post('https://hovercode.com/api/v2/link/create/', data, {
headers: {
Authorization: 'Token YOUR-TOKEN'
},
timeout: 10000
})
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error(error);
});
require 'net/http'
require 'uri'
require 'json'
uri = URI('https://hovercode.com/api/v2/link/create/')
data = {
"workspace" => "YOUR-WORKSPACE-ID",
"link" => "https://twitter.com/hovercodeHQ",
"slug" => "hovercode-twitter"
}
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.read_timeout = 10
request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Token YOUR-TOKEN'
request.content_type = 'application/json'
request.body = data.to_json
response = http.request(request)
puts response.body
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
public class Main {
public static void main(String[] args) {
HttpClient client = HttpClient.newBuilder()
.build();
String jsonData = new StringBuilder()
.append("{")
.append("\"workspace\":\"YOUR-WORKSPACE-ID\",")
.append("\"link\":\"https://twitter.com/hovercodeHQ\",")
.append("\"slug\":\"hovercode-twitter\"")
.append("}")
.toString();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://hovercode.com/api/v2/link/create/"))
.timeout(Duration.ofSeconds(10))
.header("Authorization", "Token YOUR-TOKEN")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonData))
.build();
try {
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
} catch (Exception e) {
e.printStackTrace();
}
}
}
Response:
{
"id": "b1d3f5a7-0d77-4828-95e7-1c27c603c5c9",
"link": "https://twitter.com/hovercodeHQ",
"link_type": "Link",
"display_name": null,
"short_url": "https://hov.to/hovercode-twitter",
"slug": "hovercode-twitter",
"domain": "hov.to",
"total_clicks": 0,
"unique_clicks": 0,
"gps": false,
"svg": "<svg xmlns=\"http://www.w3.org/2000/svg...",
"png": null,
"created": "2026-06-01T15:02:26.343134Z"
}
GET Get short links
https://hovercode.com/api/v2/workspace/WORKSPACE-ID/links/
Your workspace's short links, paginated. Add ?q= to search destinations, display names and slugs.
import requests
response = requests.get(
'https://hovercode.com/api/v2/workspace/YOUR-WORKSPACE-ID/links/',
headers={'Authorization': 'Token YOUR-TOKEN'},
timeout=10
)
<?php
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://hovercode.com/api/v2/workspace/YOUR-WORKSPACE-ID/links/',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => array(
'Authorization: Token YOUR-TOKEN'
),
));
$response = curl_exec($curl);
curl_close($curl);
echo $response;
?>
curl -X GET \
'https://hovercode.com/api/v2/workspace/YOUR-WORKSPACE-ID/links/' \
-H 'Authorization: Token YOUR-TOKEN'
const axios = require('axios');
axios.get('https://hovercode.com/api/v2/workspace/YOUR-WORKSPACE-ID/links/', {
headers: {
Authorization: 'Token YOUR-TOKEN'
},
timeout: 10000
})
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error(error);
});
require 'net/http'
require 'uri'
uri = URI.parse('https://hovercode.com/api/v2/workspace/YOUR-WORKSPACE-ID/links/')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri.path)
request['Authorization'] = 'Token YOUR-TOKEN'
response = http.request(request)
puts response.read_body
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class ListLinks {
public static void main(String[] args) {
try {
String workspaceToken = "YOUR-WORKSPACE-ID";
String token = "YOUR_TOKEN";
URL url = new URL("https://hovercode.com/api/v2/workspace/" + workspaceToken + "/links/");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Authorization", "Token " + token);
try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"))) {
StringBuilder response = new StringBuilder();
String responseLine = null;
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
System.out.println(response.toString());
}
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
GET Get a short link
https://hovercode.com/api/v2/link/LINK-ID/
Returns the same fields as the create response.
import requests
response = requests.get(
'https://hovercode.com/api/v2/link/LINK-ID/',
headers={'Authorization': 'Token YOUR-TOKEN'},
timeout=10
)
<?php
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://hovercode.com/api/v2/link/LINK-ID/',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => array(
'Authorization: Token YOUR-TOKEN'
),
));
$response = curl_exec($curl);
curl_close($curl);
echo $response;
?>
curl -X GET \
'https://hovercode.com/api/v2/link/LINK-ID/' \
-H 'Authorization: Token YOUR-TOKEN'
const axios = require('axios');
axios.get('https://hovercode.com/api/v2/link/LINK-ID/', {
headers: {
Authorization: 'Token YOUR-TOKEN'
},
timeout: 10000
})
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error(error);
});
require 'net/http'
require 'uri'
uri = URI.parse('https://hovercode.com/api/v2/link/LINK-ID/')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri.path)
request['Authorization'] = 'Token YOUR-TOKEN'
response = http.request(request)
puts response.read_body
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class GetLink {
public static void main(String[] args) {
try {
String linkId = "YOUR_LINK_ID";
String token = "YOUR_TOKEN";
URL url = new URL("https://hovercode.com/api/v2/link/" + linkId + "/");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Authorization", "Token " + token);
try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"))) {
StringBuilder response = new StringBuilder();
String responseLine = null;
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
System.out.println(response.toString());
}
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
PATCH Update a short link
https://hovercode.com/api/v2/link/LINK-ID/update/
Update the destination link, the display_name, or toggle gps. The QR keeps working.
import requests
data = {
"link": "https://twitter.com/ramykhuffash",
"display_name": "Updated"
}
response = requests.patch(
'https://hovercode.com/api/v2/link/LINK-ID/update/',
headers={'Authorization': 'Token YOUR-TOKEN'},
json=data,
timeout=10
)
<?php
$data = array(
"link" => "https://twitter.com/ramykhuffash",
"display_name" => "Updated"
);
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://hovercode.com/api/v2/link/LINK-ID/update/',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_POSTFIELDS => json_encode($data),
CURLOPT_HTTPHEADER => array(
'Authorization: Token YOUR-TOKEN',
'Content-Type: application/json'
),
));
$response = curl_exec($curl);
curl_close($curl);
echo $response;
?>
curl -X PATCH \
'https://hovercode.com/api/v2/link/LINK-ID/update/' \
-H 'Authorization: Token YOUR-TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"link": "https://twitter.com/ramykhuffash",
"display_name": "Updated"
}'
const axios = require('axios');
const data = {
link: 'https://twitter.com/ramykhuffash',
display_name: 'Updated'
};
axios.patch('https://hovercode.com/api/v2/link/LINK-ID/update/', data, {
headers: {
Authorization: 'Token YOUR-TOKEN'
},
timeout: 10000
})
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error(error);
});
require 'net/http'
require 'uri'
require 'json'
uri = URI.parse('https://hovercode.com/api/v2/link/LINK-ID/update/')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Patch.new(uri.path)
request['Content-Type'] = 'application/json'
request['Authorization'] = 'Token YOUR-TOKEN'
request.body = { "link": "https://twitter.com/ramykhuffash", "display_name": "Updated" }.to_json
response = http.request(request)
puts response.read_body
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
public class Main {
public static void main(String[] args) {
HttpClient client = HttpClient.newBuilder()
.build();
String jsonData = new StringBuilder()
.append("{")
.append("\"link\":\"https://twitter.com/ramykhuffash\",")
.append("\"display_name\":\"Updated\"")
.append("}")
.toString();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://hovercode.com/api/v2/link/LINK-ID/update/"))
.timeout(Duration.ofSeconds(10))
.header("Authorization", "Token YOUR-TOKEN")
.header("Content-Type", "application/json")
.method("PATCH", HttpRequest.BodyPublishers.ofString(jsonData))
.build();
try {
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
} catch (Exception e) {
e.printStackTrace();
}
}
}
DELETE Delete a short link
https://hovercode.com/api/v2/link/LINK-ID/delete/
Returns 204. The link stops redirecting.
import requests
response = requests.delete(
'https://hovercode.com/api/v2/link/LINK-ID/delete/',
headers={'Authorization': 'Token YOUR-TOKEN'},
timeout=10
)
<?php
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://hovercode.com/api/v2/link/LINK-ID/delete/',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => array(
'Authorization: Token YOUR-TOKEN'
),
));
$response = curl_exec($curl);
curl_close($curl);
echo $response;
?>
curl -X DELETE \
'https://hovercode.com/api/v2/link/LINK-ID/delete/' \
-H 'Authorization: Token YOUR-TOKEN' \
-H 'Content-Type: application/json' \
-d ''
const axios = require('axios');
axios.delete('https://hovercode.com/api/v2/link/LINK-ID/delete/', {
headers: {
Authorization: 'Token YOUR-TOKEN'
},
timeout: 10000
})
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error(error);
});
require 'net/http'
require 'uri'
uri = URI('https://hovercode.com/api/v2/link/YOUR-LINK-ID/delete/')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.read_timeout = 10
request = Net::HTTP::Delete.new(uri)
request['Authorization'] = 'Token YOUR-TOKEN'
response = http.request(request)
puts response.body
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpRequest.BodyPublishers;
import java.time.Duration;
public class Main {
public static void main(String[] args) {
HttpClient client = HttpClient.newBuilder()
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://hovercode.com/api/v2/link/YOUR-LINK-ID/delete/"))
.timeout(Duration.ofSeconds(10))
.header("Authorization", "Token YOUR-TOKEN")
.DELETE()
.build();
try {
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
} catch (Exception e) {
e.printStackTrace();
}
}
}
GET Short link QR image
https://hovercode.com/api/v2/link/LINK-ID/qr/?format=png
Returns the QR image. ?format=svg returns the SVG inline; ?format=png (default) redirects to a generated .png (created on first request).
import requests
response = requests.get(
'https://hovercode.com/api/v2/link/LINK-ID/qr/?format=png',
headers={'Authorization': 'Token YOUR-TOKEN'},
timeout=10
)
open('qr.png', 'wb').write(response.content)
<?php
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://hovercode.com/api/v2/link/LINK-ID/qr/?format=png',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => array(
'Authorization: Token YOUR-TOKEN'
),
));
$response = curl_exec($curl);
curl_close($curl);
echo $response;
?>
curl -X GET \
'https://hovercode.com/api/v2/link/LINK-ID/qr/?format=png' \
-H 'Authorization: Token YOUR-TOKEN'
const axios = require('axios');
axios.get('https://hovercode.com/api/v2/link/LINK-ID/qr/?format=png', {
headers: {
Authorization: 'Token YOUR-TOKEN'
},
timeout: 10000
})
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error(error);
});
require 'net/http'
require 'uri'
uri = URI.parse('https://hovercode.com/api/v2/link/LINK-ID/qr/?format=png')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Token YOUR-TOKEN'
response = http.request(request)
puts response.read_body
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class GetLinkQR {
public static void main(String[] args) {
try {
String linkId = "YOUR_LINK_ID";
String token = "YOUR_TOKEN";
URL url = new URL("https://hovercode.com/api/v2/link/" + linkId + "/qr/?format=png");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Authorization", "Token " + token);
try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"))) {
StringBuilder response = new StringBuilder();
String responseLine = null;
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
System.out.println(response.toString());
}
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
GET Short link activity
https://hovercode.com/api/v2/link/LINK-ID/activity/
Click/scan activity, same paginated format as QR code activity.
import requests
response = requests.get(
'https://hovercode.com/api/v2/link/LINK-ID/activity/',
headers={'Authorization': 'Token YOUR-TOKEN'},
timeout=10
)
<?php
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://hovercode.com/api/v2/link/LINK-ID/activity/',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => array(
'Authorization: Token YOUR-TOKEN'
),
));
$response = curl_exec($curl);
curl_close($curl);
echo $response;
?>
curl -X GET \
'https://hovercode.com/api/v2/link/LINK-ID/activity/' \
-H 'Authorization: Token YOUR-TOKEN'
const axios = require('axios');
axios.get('https://hovercode.com/api/v2/link/LINK-ID/activity/', {
headers: {
Authorization: 'Token YOUR-TOKEN'
},
timeout: 10000
})
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error(error);
});
require 'net/http'
require 'uri'
uri = URI.parse('https://hovercode.com/api/v2/link/LINK-ID/activity/')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri.path)
request['Authorization'] = 'Token YOUR-TOKEN'
response = http.request(request)
puts response.read_body
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class GetLinkActivity {
public static void main(String[] args) {
try {
String linkId = "YOUR_LINK_ID";
String token = "YOUR_TOKEN";
URL url = new URL("https://hovercode.com/api/v2/link/" + linkId + "/activity/");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Authorization", "Token " + token);
try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"))) {
StringBuilder response = new StringBuilder();
String responseLine = null;
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
System.out.println(response.toString());
}
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
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.
POST Create a page
https://hovercode.com/api/v2/page/create/
Creates and publishes a page in one call, and returns its live URL and QR code.
| Parameter | Required | Description |
|---|---|---|
| workspace | Required | Your workspace ID. |
| title | Required | The page heading. |
| links | Optional | 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. |
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
)
curl -X POST \
'https://hovercode.com/api/v2/page/create/' \
-H 'Authorization: Token YOUR-TOKEN' \
-H 'Content-Type: application/json' \
-d '{"workspace": "YOUR-WORKSPACE-ID", "title": "My Links", "links": [{"url": "https://example.com", "title": "Website"}]}'
Response:
{
"id": "b1d3f5a7-0d77-4828-95e7-1c27c603c5c9",
"title": "My Links",
"status": "Published",
"share_url": "https://micro.page/my-links",
"slug": "my-links",
"qr_id": "0acb2379-c9e3-4245-a1e3-6e542cc02637",
"svg": "<svg xmlns=\"http://www.w3.org/2000/svg...",
"links": [
{ "title": "Website", "url": "https://example.com", "type": "Website" }
],
"created": "2026-06-01T15:02:26.343134Z"
}
GET Get a page
https://hovercode.com/api/v2/page/PAGE-ID/
GET List pages
https://hovercode.com/api/v2/workspace/WORKSPACE-ID/pages/
The workspace's landing 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. List endpoints are paginated; add
?page= and ?page_size= (max 100).
POST Create a form
https://hovercode.com/api/v2/form/create/
| Parameter | Required | Description |
|---|---|---|
| workspace | Required | Your workspace ID. |
| title | Required | The form title. |
| description | Optional | Shown above the fields. |
import requests
response = requests.post(
'https://hovercode.com/api/v2/form/create/',
headers={'Authorization': 'Token YOUR-TOKEN'},
json={"workspace": "YOUR-WORKSPACE-ID", "title": "Newsletter signup"},
timeout=10
)
form_id = response.json()["id"]
curl -X POST \
'https://hovercode.com/api/v2/form/create/' \
-H 'Authorization: Token YOUR-TOKEN' \
-H 'Content-Type: application/json' \
-d '{"workspace": "YOUR-WORKSPACE-ID", "title": "Newsletter signup"}'
Returns the form, including its id.
POST Save fields
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.
| Field key | Required | Description |
|---|---|---|
| field_type | Required | text, email, phone, number, textarea, select, radio, checkbox, date, time, url, rating, scale, yes_no, or image (respondent photo upload — paid plans only). |
| label | Required | The field's question/label. |
| required | Optional | Whether an answer is required. |
| options | Optional | For select/radio/checkbox, as [{"value": …, "label": …}]. |
import requests
payload = {"fields": [
{"id": "temp-0", "field_type": "text", "label": "Name", "required": True},
{"id": "temp-1", "field_type": "email", "label": "Email", "required": True}
]}
response = requests.post(
f'https://hovercode.com/api/v2/form/{form_id}/save/',
headers={'Authorization': 'Token YOUR-TOKEN'},
json=payload,
timeout=10
)
curl -X POST \
'https://hovercode.com/api/v2/form/FORM-ID/save/' \
-H 'Authorization: Token YOUR-TOKEN' \
-H 'Content-Type: application/json' \
-d '{"fields": [{"id": "temp-0", "field_type": "text", "label": "Name", "required": true}]}'
POST Publish a form
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.
To later stop or resume responses, use POST .../close/ and POST .../reopen/.
import requests
response = requests.post(
f'https://hovercode.com/api/v2/form/{form_id}/publish/',
headers={'Authorization': 'Token YOUR-TOKEN'},
json={}, # or {"domain": "...", "slug": "..."}
timeout=10
)
data = response.json()
share_url = data["shortlink_url"]
curl -X POST \
'https://hovercode.com/api/v2/form/FORM-ID/publish/' \
-H 'Authorization: Token YOUR-TOKEN' \
-H 'Content-Type: application/json' \
-d '{}'
GET List forms
https://hovercode.com/api/v2/forms/?workspace=WORKSPACE-ID
A workspace's forms, newest first. ?workspace=WORKSPACE-ID is
required — listing is always scoped to one workspace. Add ?q= to search by title.
import requests
response = requests.get(
'https://hovercode.com/api/v2/forms/?workspace=YOUR-WORKSPACE-ID',
headers={'Authorization': 'Token YOUR-TOKEN'},
timeout=10
)
<?php
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://hovercode.com/api/v2/forms/?workspace=YOUR-WORKSPACE-ID',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => array(
'Authorization: Token YOUR-TOKEN'
),
));
$response = curl_exec($curl);
curl_close($curl);
echo $response;
?>
curl -X GET \
'https://hovercode.com/api/v2/forms/?workspace=YOUR-WORKSPACE-ID' \
-H 'Authorization: Token YOUR-TOKEN'
const axios = require('axios');
axios.get('https://hovercode.com/api/v2/forms/?workspace=YOUR-WORKSPACE-ID', {
headers: {
Authorization: 'Token YOUR-TOKEN'
},
timeout: 10000
})
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error(error);
});
require 'net/http'
require 'uri'
uri = URI.parse('https://hovercode.com/api/v2/forms/?workspace=YOUR-WORKSPACE-ID')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri.request_uri)
request['Authorization'] = 'Token YOUR-TOKEN'
response = http.request(request)
puts response.read_body
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class ListForms {
public static void main(String[] args) {
try {
String token = "YOUR_TOKEN";
URL url = new URL("https://hovercode.com/api/v2/forms/?workspace=YOUR-WORKSPACE-ID");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Authorization", "Token " + token);
try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"))) {
StringBuilder response = new StringBuilder();
String responseLine = null;
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
System.out.println(response.toString());
}
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Response:
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": "b1d3f5a7-0d77-4828-95e7-1c27c603c5c9",
"title": "Newsletter signup",
"status": "Published",
"response_count": 128,
"share_url": "https://hov.to/newsletter",
"workspace": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"created": "2026-05-20T09:14:02.114820Z",
"modified": "2026-06-01T12:00:00.000000Z"
}
]
}
When you have no forms (or none match the filter), the response is {"message": "No results found", "results": []}.
GET List responses
https://hovercode.com/api/v2/forms/FORM-ID/responses/
A form's submissions, newest first. Each response includes its per-field answers.
import requests
response = requests.get(
'https://hovercode.com/api/v2/forms/FORM-ID/responses/',
headers={'Authorization': 'Token YOUR-TOKEN'},
timeout=10
)
<?php
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://hovercode.com/api/v2/forms/FORM-ID/responses/',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => array(
'Authorization: Token YOUR-TOKEN'
),
));
$response = curl_exec($curl);
curl_close($curl);
echo $response;
?>
curl -X GET \
'https://hovercode.com/api/v2/forms/FORM-ID/responses/' \
-H 'Authorization: Token YOUR-TOKEN'
const axios = require('axios');
axios.get('https://hovercode.com/api/v2/forms/FORM-ID/responses/', {
headers: {
Authorization: 'Token YOUR-TOKEN'
},
timeout: 10000
})
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error(error);
});
require 'net/http'
require 'uri'
uri = URI.parse('https://hovercode.com/api/v2/forms/FORM-ID/responses/')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri.path)
request['Authorization'] = 'Token YOUR-TOKEN'
response = http.request(request)
puts response.read_body
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class ListFormResponses {
public static void main(String[] args) {
try {
String formId = "FORM-ID";
String token = "YOUR_TOKEN";
URL url = new URL("https://hovercode.com/api/v2/forms/" + formId + "/responses/");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Authorization", "Token " + token);
try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"))) {
StringBuilder response = new StringBuilder();
String responseLine = null;
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
System.out.println(response.toString());
}
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Response:
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": "f0a1c2d3-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
"submitted_at": "2026-06-02T18:30:00.000000Z",
"is_complete": true,
"completion_time_seconds": 42,
"field_responses": [
{ "field_id": "a1b2c3d4-...", "field_label": "Name", "field_type": "text", "value": "Ada Lovelace" },
{ "field_id": "c3d4e5f6-...", "field_label": "Email", "field_type": "email", "value": "ada@example.com" }
]
}
]
}
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 the QR create endpoint — pass an identifier and a destination and you get a working GS1 QR back.
The 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.
An identifier is unique per (domain, identifier, batch/serial) — the domain is set automatically and can't be changed (it's part of the
scannable URL). Registering the same identifier again updates it rather than creating a duplicate.
POST Create a GS1 product
https://hovercode.com/api/v2/gs1/products/
| Parameter | Required | Description |
|---|---|---|
| workspace | Required | Your workspace ID. |
| identifier_value | Required | The identifier value, e.g. a GTIN. Validated (incl. GS1 check digit). |
| identifier_type | Defaults to "01" | The 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). |
| 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. |
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
)
<?php
$data = array(
"workspace" => "YOUR-WORKSPACE-ID",
"identifier_type" => "01",
"identifier_value" => "09506000134369",
"destination_url" => "https://example.com/product"
);
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://hovercode.com/api/v2/gs1/products/',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => json_encode($data),
CURLOPT_HTTPHEADER => array(
'Authorization: Token YOUR-TOKEN',
'Content-Type: application/json'
),
));
$response = curl_exec($curl);
curl_close($curl);
?>
curl -X POST \
'https://hovercode.com/api/v2/gs1/products/' \
-H 'Authorization: Token YOUR-TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"workspace": "YOUR-WORKSPACE-ID",
"identifier_type": "01",
"identifier_value": "09506000134369",
"destination_url": "https://example.com/product"
}'
const axios = require('axios');
const data = {
workspace: 'YOUR-WORKSPACE-ID',
identifier_type: '01',
identifier_value: '09506000134369',
destination_url: 'https://example.com/product'
};
axios.post('https://hovercode.com/api/v2/gs1/products/', data, {
headers: {
Authorization: 'Token YOUR-TOKEN'
},
timeout: 10000
})
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error(error);
});
require 'net/http'
require 'uri'
require 'json'
uri = URI('https://hovercode.com/api/v2/gs1/products/')
data = {
"workspace" => "YOUR-WORKSPACE-ID",
"identifier_type" => "01",
"identifier_value" => "09506000134369",
"destination_url" => "https://example.com/product"
}
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.read_timeout = 10
request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Token YOUR-TOKEN'
request.content_type = 'application/json'
request.body = data.to_json
response = http.request(request)
puts response.body
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
public class Main {
public static void main(String[] args) {
HttpClient client = HttpClient.newBuilder()
.build();
String jsonData = new StringBuilder()
.append("{")
.append("\"workspace\":\"YOUR-WORKSPACE-ID\",")
.append("\"identifier_type\":\"01\",")
.append("\"identifier_value\":\"09506000134369\",")
.append("\"destination_url\":\"https://example.com/product\"")
.append("}")
.toString();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://hovercode.com/api/v2/gs1/products/"))
.timeout(Duration.ofSeconds(10))
.header("Authorization", "Token YOUR-TOKEN")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonData))
.build();
try {
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
} catch (Exception e) {
e.printStackTrace();
}
}
}
Returns the product (201) with its Digital Link path and link types:
{
"id": "7f1e2d3c-0d77-4828-95e7-1c27c603c5c9",
"identifier_type": "01",
"identifier_value": "09506000134369",
"batch_lot": null,
"serial_number": null,
"expiry_date": null,
"identifier_label": "GTIN",
"digital_link_path": "/01/09506000134369",
"link_types": [
{
"id": "a0b1c2d3-1111-2222-3333-444455556666",
"link_type": "gs1:pip",
"title": "Product information page",
"destination_url": "https://example.com/product",
"is_default": true,
"language": null,
"media_type": "text/html"
}
],
"created": "2026-06-01T15:02:26.343134Z"
}
GET List GS1 products
https://hovercode.com/api/v2/workspace/WORKSPACE-ID/gs1/products/
Your workspace's GS1 products (paginated), each with its nested link types.
import requests
response = requests.get(
'https://hovercode.com/api/v2/workspace/YOUR-WORKSPACE-ID/gs1/products/',
headers={'Authorization': 'Token YOUR-TOKEN'},
timeout=10
)
<?php
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://hovercode.com/api/v2/workspace/YOUR-WORKSPACE-ID/gs1/products/',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => array(
'Authorization: Token YOUR-TOKEN'
),
));
$response = curl_exec($curl);
curl_close($curl);
echo $response;
?>
curl -X GET \
'https://hovercode.com/api/v2/workspace/YOUR-WORKSPACE-ID/gs1/products/' \
-H 'Authorization: Token YOUR-TOKEN'
const axios = require('axios');
axios.get('https://hovercode.com/api/v2/workspace/YOUR-WORKSPACE-ID/gs1/products/', {
headers: {
Authorization: 'Token YOUR-TOKEN'
},
timeout: 10000
})
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error(error);
});
require 'net/http'
require 'uri'
uri = URI.parse('https://hovercode.com/api/v2/workspace/YOUR-WORKSPACE-ID/gs1/products/')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri.path)
request['Authorization'] = 'Token YOUR-TOKEN'
response = http.request(request)
puts response.read_body
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class ListGS1Products {
public static void main(String[] args) {
try {
String workspaceToken = "YOUR-WORKSPACE-ID";
String token = "YOUR_TOKEN";
URL url = new URL("https://hovercode.com/api/v2/workspace/" + workspaceToken + "/gs1/products/");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Authorization", "Token " + token);
try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"))) {
StringBuilder response = new StringBuilder();
String responseLine = null;
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
System.out.println(response.toString());
}
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
GET PATCH DELETE A GS1 product
https://hovercode.com/api/v2/gs1/products/PRODUCT-ID/
Retrieve, update the identifier/qualifiers, or delete (204) a product. The example below is a GET; PATCH and DELETE use the same URL.
import requests
response = requests.get(
'https://hovercode.com/api/v2/gs1/products/PRODUCT-ID/',
headers={'Authorization': 'Token YOUR-TOKEN'},
timeout=10
)
<?php
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://hovercode.com/api/v2/gs1/products/PRODUCT-ID/',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => array(
'Authorization: Token YOUR-TOKEN'
),
));
$response = curl_exec($curl);
curl_close($curl);
echo $response;
?>
curl -X GET \
'https://hovercode.com/api/v2/gs1/products/PRODUCT-ID/' \
-H 'Authorization: Token YOUR-TOKEN'
const axios = require('axios');
axios.get('https://hovercode.com/api/v2/gs1/products/PRODUCT-ID/', {
headers: {
Authorization: 'Token YOUR-TOKEN'
},
timeout: 10000
})
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error(error);
});
require 'net/http'
require 'uri'
uri = URI.parse('https://hovercode.com/api/v2/gs1/products/PRODUCT-ID/')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri.path)
request['Authorization'] = 'Token YOUR-TOKEN'
response = http.request(request)
puts response.read_body
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class GetGS1Product {
public static void main(String[] args) {
try {
String productId = "YOUR_PRODUCT_ID";
String token = "YOUR_TOKEN";
URL url = new URL("https://hovercode.com/api/v2/gs1/products/" + productId + "/");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Authorization", "Token " + token);
try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"))) {
StringBuilder response = new StringBuilder();
String responseLine = null;
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
System.out.println(response.toString());
}
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
POST Manage link types
https://hovercode.com/api/v2/gs1/products/PRODUCT-ID/links/
Each product resolves to one destination per link type (e.g. gs1:pip, gs1:instructions).
Exactly one is the default — setting a new default unsets the others, and deleting the default promotes another.
| Parameter | Required | Description |
|---|---|---|
| link_type | Required | One of:
gs1:pip (product info page), gs1:quickStartGuide, gs1:instructions,
gs1:safetyInfo, gs1:recipeInfo, gs1:traceability, gs1:hasRetailers,
gs1:recallStatus, gs1:review, gs1:ePIL (patient leaflet),
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). |
import requests
data = {
"link_type": "gs1:instructions",
"title": "Setup guide",
"destination_url": "https://example.com/setup"
}
response = requests.post(
'https://hovercode.com/api/v2/gs1/products/PRODUCT-ID/links/',
headers={'Authorization': 'Token YOUR-TOKEN'},
json=data,
timeout=10
)
<?php
$data = array(
"link_type" => "gs1:instructions",
"title" => "Setup guide",
"destination_url" => "https://example.com/setup"
);
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://hovercode.com/api/v2/gs1/products/PRODUCT-ID/links/',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => json_encode($data),
CURLOPT_HTTPHEADER => array(
'Authorization: Token YOUR-TOKEN',
'Content-Type: application/json'
),
));
$response = curl_exec($curl);
curl_close($curl);
?>
curl -X POST \
'https://hovercode.com/api/v2/gs1/products/PRODUCT-ID/links/' \
-H 'Authorization: Token YOUR-TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"link_type": "gs1:instructions",
"title": "Setup guide",
"destination_url": "https://example.com/setup"
}'
const axios = require('axios');
const data = {
link_type: 'gs1:instructions',
title: 'Setup guide',
destination_url: 'https://example.com/setup'
};
axios.post('https://hovercode.com/api/v2/gs1/products/PRODUCT-ID/links/', data, {
headers: {
Authorization: 'Token YOUR-TOKEN'
},
timeout: 10000
})
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error(error);
});
require 'net/http'
require 'uri'
require 'json'
uri = URI('https://hovercode.com/api/v2/gs1/products/PRODUCT-ID/links/')
data = {
"link_type" => "gs1:instructions",
"title" => "Setup guide",
"destination_url" => "https://example.com/setup"
}
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.read_timeout = 10
request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Token YOUR-TOKEN'
request.content_type = 'application/json'
request.body = data.to_json
response = http.request(request)
puts response.body
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
public class Main {
public static void main(String[] args) {
HttpClient client = HttpClient.newBuilder()
.build();
String jsonData = new StringBuilder()
.append("{")
.append("\"link_type\":\"gs1:instructions\",")
.append("\"title\":\"Setup guide\",")
.append("\"destination_url\":\"https://example.com/setup\"")
.append("}")
.toString();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://hovercode.com/api/v2/gs1/products/PRODUCT-ID/links/"))
.timeout(Duration.ofSeconds(10))
.header("Authorization", "Token YOUR-TOKEN")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonData))
.build();
try {
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
} catch (Exception e) {
e.printStackTrace();
}
}
}
Update or delete a link with PATCH / DELETE /api/v2/gs1/products/PRODUCT-ID/links/LINK-ID/.
GET Get the linkset
https://hovercode.com/api/v2/gs1/products/PRODUCT-ID/linkset/
An RFC 9264 linkset (application/linkset+json) — a preview of what the resolver serves. Optional ?domain= sets the anchor domain.
import requests
response = requests.get(
'https://hovercode.com/api/v2/gs1/products/PRODUCT-ID/linkset/',
headers={'Authorization': 'Token YOUR-TOKEN'},
timeout=10
)
<?php
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://hovercode.com/api/v2/gs1/products/PRODUCT-ID/linkset/',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => array(
'Authorization: Token YOUR-TOKEN'
),
));
$response = curl_exec($curl);
curl_close($curl);
echo $response;
?>
curl -X GET \
'https://hovercode.com/api/v2/gs1/products/PRODUCT-ID/linkset/' \
-H 'Authorization: Token YOUR-TOKEN'
const axios = require('axios');
axios.get('https://hovercode.com/api/v2/gs1/products/PRODUCT-ID/linkset/', {
headers: {
Authorization: 'Token YOUR-TOKEN'
},
timeout: 10000
})
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error(error);
});
require 'net/http'
require 'uri'
uri = URI.parse('https://hovercode.com/api/v2/gs1/products/PRODUCT-ID/linkset/')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri.path)
request['Authorization'] = 'Token YOUR-TOKEN'
response = http.request(request)
puts response.read_body
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class GetGS1Linkset {
public static void main(String[] args) {
try {
String productId = "YOUR_PRODUCT_ID";
String token = "YOUR_TOKEN";
URL url = new URL("https://hovercode.com/api/v2/gs1/products/" + productId + "/linkset/");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Authorization", "Token " + token);
try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"))) {
StringBuilder response = new StringBuilder();
String responseLine = null;
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
System.out.println(response.toString());
}
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Response:
{
"linkset": [
{
"anchor": "https://hov.to/01/09506000134369",
"gs1:pip": [
{"href": "https://example.com/product", "title": "Product information page", "type": "text/html"}
],
"gs1:instructions": [
{"href": "https://example.com/setup", "title": "Setup guide", "type": "text/html"}
]
}
]
}
POST Create a GS1 QR code
https://hovercode.com/api/v2/hovercode/create/
Set qr_type to GS1 and pass a gs1_product object with an
identifier_value (e.g. a GTIN) 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.
Already created a product via the endpoints above? Pass its id as a string instead — "gs1_product": "PRODUCT-ID" — to reuse it.
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
)
<?php
$data = array(
"workspace" => "YOUR-WORKSPACE-ID",
"qr_type" => "GS1",
"gs1_product" => array(
"identifier_value" => "09506000134369",
"destination_url" => "https://example.com/product"
)
);
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://hovercode.com/api/v2/hovercode/create/',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => json_encode($data),
CURLOPT_HTTPHEADER => array(
'Authorization: Token YOUR-TOKEN',
'Content-Type: application/json'
),
));
$response = curl_exec($curl);
curl_close($curl);
?>
curl -X POST \
'https://hovercode.com/api/v2/hovercode/create/' \
-H 'Authorization: Token YOUR-TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"workspace": "YOUR-WORKSPACE-ID",
"qr_type": "GS1",
"gs1_product": {
"identifier_value": "09506000134369",
"destination_url": "https://example.com/product"
}
}'
const axios = require('axios');
const data = {
workspace: 'YOUR-WORKSPACE-ID',
qr_type: 'GS1',
gs1_product: {
identifier_value: '09506000134369',
destination_url: 'https://example.com/product'
}
};
axios.post('https://hovercode.com/api/v2/hovercode/create/', data, {
headers: {
Authorization: 'Token YOUR-TOKEN'
},
timeout: 10000
})
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error(error);
});
require 'net/http'
require 'uri'
require 'json'
uri = URI('https://hovercode.com/api/v2/hovercode/create/')
data = {
"workspace" => "YOUR-WORKSPACE-ID",
"qr_type" => "GS1",
"gs1_product" => {
"identifier_value" => "09506000134369",
"destination_url" => "https://example.com/product"
}
}
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.read_timeout = 10
request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Token YOUR-TOKEN'
request.content_type = 'application/json'
request.body = data.to_json
response = http.request(request)
puts response.body
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
public class Main {
public static void main(String[] args) {
HttpClient client = HttpClient.newBuilder()
.build();
String jsonData = new StringBuilder()
.append("{")
.append("\"workspace\":\"YOUR-WORKSPACE-ID\",")
.append("\"qr_type\":\"GS1\",")
.append("\"gs1_product\":{")
.append("\"identifier_value\":\"09506000134369\",")
.append("\"destination_url\":\"https://example.com/product\"")
.append("}")
.append("}")
.toString();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://hovercode.com/api/v2/hovercode/create/"))
.timeout(Duration.ofSeconds(10))
.header("Authorization", "Token YOUR-TOKEN")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonData))
.build();
try {
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
} catch (Exception e) {
e.printStackTrace();
}
}
}
The response includes a gs1_product summary and the Digital Link URI as qr_data.
Webhooks
Enable webhooks in your workspace API settings to receive a POST every time a dynamic QR code or short link is scanned. Available on the Business Plus plan.
Each request is application/json with an x-signature header. Verify it against your webhook secret, then return 200.
import hmac
import hashlib
import json
from django.http import JsonResponse
def hc_webhook_view(request):
webhook_secret = [YOUR WEBHOOK SECRET]
raw_payload = request.body
received_signature = request.headers.get('X-Signature')
expected_signature = hmac.new(SECRET_KEY.encode(), raw_payload, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected_signature, received_signature):
return JsonResponse({'error': 'Invalid signature'}, status=400)
payload_data = json.loads(raw_payload)
# Do what you need to do
return JsonResponse({'message': 'Webhook received successfully'}, status=200)
<?php
function hc_webhook_view() {
$webhook_secret = '[YOUR WEBHOOK SECRET]';
$raw_payload = file_get_contents('php://input');
$received_signature = $_SERVER['HTTP_X_SIGNATURE'];
$expected_signature = hash_hmac('sha256', $raw_payload, $webhook_secret);
if (!hash_equals($expected_signature, $received_signature)) {
http_response_code(400);
echo json_encode(['error' => 'Invalid signature']);
return;
}
$payload_data = json_decode($raw_payload, true);
// Do what you need to do
http_response_code(200);
echo json_encode(['message' => 'Webhook received successfully']);
}
// Call the function to handle the webhook
hc_webhook_view();
?>
As this is a for receiving a webhook, there is no cURL version. Try selecting a different language
const express = require('express');
const crypto = require('crypto');
const bodyParser = require('body-parser');
const app = express();
const port = 3000;
const SECRET_KEY = '[YOUR WEBHOOK SECRET]';
// Middleware to parse JSON request body
app.use(bodyParser.json());
app.post('/hc_webhook', (req, res) => {
const raw_payload = JSON.stringify(req.body);
const received_signature = req.headers['x-signature'];
const expected_signature = crypto.createHmac('sha256', SECRET_KEY)
.update(raw_payload)
.digest('hex');
if (received_signature !== expected_signature) {
return res.status(400).json({ error: 'Invalid signature' });
}
// Process payload_data
const payload_data = req.body;
// Do what you need to do
return res.status(200).json({ message: 'Webhook received successfully' });
});
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.springframework.http.HttpStatus;
@RestController
public class WebhookController {
private static final String SECRET_KEY = "YOUR_WEBHOOK_SECRET";
@PostMapping("/hc_webhook")
public String hcWebhookView(@RequestBody String rawPayload, @RequestHeader("X-Signature") String receivedSignature) throws IOException {
try {
Mac hmacSha256 = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(SECRET_KEY.getBytes(), "HmacSHA256");
hmacSha256.init(secretKey);
byte[] expectedSignature = hmacSha256.doFinal(rawPayload.getBytes());
if (!java.util.Arrays.equals(expectedSignature, receivedSignature.getBytes())) {
return "{\"error\": \"Invalid signature\"}";
}
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
e.printStackTrace();
return "{\"error\": \"Internal server error\"}";
}
// Parse payload_data JSON and do what you need to do
return "{\"message\": \"Webhook received successfully\"}";
}
}
require 'sinatra'
require 'json'
require 'openssl'
SECRET_KEY = "YOUR_WEBHOOK_SECRET"
post '/hc_webhook' do
request.body.rewind
raw_payload = request.body.read
received_signature = request.env['HTTP_X_SIGNATURE']
hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), SECRET_KEY, raw_payload)
if hmac != received_signature
return { error: 'Invalid signature' }.to_json
end
# Parse payload_data JSON and do what you need to do
return { message: 'Webhook received successfully' }.to_json
end
The payload is activity data:
{
"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 server, so AI assistants like Claude and Cursor can use everything above — QR codes, PDF codes, short links, landing pages, and forms — conversationally, without writing API calls. Anything the assistant creates lands in your dashboard like any other code.
CONNECT Connect a client
https://hovercode.com/mcp/
There are two ways to connect, depending on your client:
1. OAuth (recommended for connectors). In Claude, ChatGPT, or any client that supports remote MCP connectors, add the server URL https://hovercode.com/mcp/ and approve access — you'll go through a normal Hovercode login. Nothing to copy.
2. API token (for CLI / config clients). Pass your API token (from the Authentication section above) as a bearer token.
Claude Code:
claude mcp add --transport http hovercode https://hovercode.com/mcp/ \
--header "Authorization: Bearer YOUR-TOKEN"
Config-file clients (Claude Desktop, Cursor, …):
{
"mcpServers": {
"hovercode": {
"type": "http",
"url": "https://hovercode.com/mcp/",
"headers": { "Authorization": "Bearer YOUR-TOKEN" }
}
}
}
Building your own agent? The machine-readable connect guide is served as markdown at https://hovercode.com/mcp/docs, and discovery metadata lives at /.well-known/mcp/server-card.json.
What the assistant can do
| Area | Capabilities |
|---|---|
| QR codes | Create (full design control + templates), create in bulk, restyle in place, retarget a dynamic code, read scan analytics, download print-ready PNG/SVG, search, delete. |
| PDF codes | Host a PDF (by URL) and get a dynamic QR + short link for it. |
| Short links | Create with a custom slug/domain, retarget, read click stats. |
| Landing pages | Create a link-in-bio page with a title and links, get its URL + QR. |
| Forms | Build a form from a description, publish it, get a share link + QR, and read/summarize responses. |
Things you can ask for
A few of the more useful things once it's connected:
- “Point my printed menu QR code at the new PDF at this link” — retarget a dynamic code without reprinting it.
- “Restyle my event QR to our brand colors with rounded eyes” — change the design in place, same code.
- “Make a form that asks for a name and email, then redirects to go.co on submit.”
- “Switch to my Acme workspace.”
- “Turn this PDF into a QR code for the flyer.”
- “How did my QR codes do this month, and where were people scanning them?”
- “Make a link-in-bio page with my Instagram, shop, and booking links, and a QR for the window.”
- “Create dynamic QR codes for these 12 product URLs in our brand colors.”
- “Give me a print-ready PNG of my event QR.”
- “Summarize this week's responses to my feedback form.”
This is a preview of the new docs. Send us feedback on the structure or anything missing.