REST API reference
Everything you can do with links, QR codes and stats over HTTPS and JSON.
Basics
| Base URL | https://app.qodeveo.com |
| Format | JSON request and response bodies (Content-Type: application/json) |
| Authentication | Authorization: Bearer shk_... on every request |
| Rate limit | 600 requests per minute per key. Over the limit you get 429 and a Retry-After header. |
| Scope | A key only sees and changes links in its own workspace. |
Key access
Read only keys can make GET requests. Read & write keys can also create, update and delete. A key must also be allowed for the REST API (see the overview).
Endpoints
| Method | Path | What it does | Key access |
|---|---|---|---|
GET | /api/v1/usage | Plan, links created this month and limits | Read |
GET | /api/v1/groups | Your custom country groups | Read |
GET | /api/v1/links | List links (50 per page, optional search) | Read |
POST | /api/v1/links | Create a link | Write |
POST | /api/v1/links/bulk | Create many links in one request | Write |
GET | /api/v1/links/{id} | Get one link | Read |
PATCH | /api/v1/links/{id} | Update a link | Write |
DELETE | /api/v1/links/{id} | Delete a link | Write |
GET | /api/v1/links/{id}/stats | Clicks, visitors and breakdowns | Read |
GET | /api/v1/links/{id}/qr.svg | The link's QR code as SVG (or .png) | Read |
Usage
GET /api/v1/usage shows your plan and what you've used this month. Check it before a large batch.
{
"plan": "…",
"links_used": 0,
"links_limit": 1000,
"destination_edits_used": 0,
"destination_edits_limit": 0,
"resets_at": "2026-12-01T00:00:00Z",
"bulk_max_links": 0,
"stats_retention_days": 30
}links_used counts links created this month. Each of those links has a QR code, so there is no separate QR count. Scans and redirects are not counted. bulk_max_links is the largest bulk request your plan accepts.
List links
GET /api/v1/links?q=&page=1
Returns 50 links per page. q filters by search text. The response has data (an array of links), page, per_page and total.
Create a link
POST /api/v1/links returns 201 Created with the link.
| Field | Required | Description |
|---|---|---|
url | Yes | The destination web address. |
title | No | A label to help you find the link later. |
code | No | A custom short code. If it is already taken you get 409. Leave it out to get a generated code. |
domain | No | The hostname of one of your verified custom domains. Leave it out to use the default domain. |
expires_at | No | When the link stops working, as an ISO 8601 date-time such as 2026-12-31T23:59:59Z. |
rules | No | Smart regional redirects (see below). Up to 10 rules per link. |
curl -X POST https://app.qodeveo.com/api/v1/links \
-H "Authorization: Bearer shk_..." \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/spring-menu", "title": "Spring menu"}'{
"id": "0b7e3f0c-…",
"code": "a1b2c3",
"short_url": "https://…/a1b2c3",
"url": "https://example.com/spring-menu",
"title": "Spring menu",
"active": true,
"expires_at": null,
"created_at": "2026-11-03T14:05:00Z",
"updated_at": "2026-11-03T14:05:00Z"
}A destination that is on the phishing blocklist, or that redirects to one, is refused with 422.
Smart regional redirects
Rules send visitors to a different destination based on where they are. A rule has a url and either a country or a group:
| Field | Description |
|---|---|
type | country (the default) or group. |
country | A country code, for country rules. |
region | Optional region within the country. |
group | The id or name of one of your custom groups (see GET /api/v1/groups), for group rules. |
url | Where matching visitors are sent. |
{
"url": "https://example.com/menu",
"title": "Menu",
"rules": [
{ "type": "country", "country": "CA", "url": "https://example.com/ca/menu" },
{ "type": "group", "group": "Europe", "url": "https://example.com/eu/menu" }
]
}Create links in bulk
POST /api/v1/links/bulk creates up to your plan's bulk limit in one request (bulk_max_links in the usage response). Each entry in links takes the same fields as a single create.
{
"links": [
{ "url": "https://example.com/product-a", "title": "Product A" },
{ "url": "https://example.com/product-b", "title": "Product B" },
{ "url": "not a url" }
]
}{
"created": 2,
"failed": 1,
"results": [
{ "index": 0, "link": { "id": "…", "short_url": "https://…", "url": "https://example.com/product-a", … } },
{ "index": 1, "link": { "id": "…", "short_url": "https://…", "url": "https://example.com/product-b", … } },
{ "index": 2, "error": "…the reason this row was rejected…" }
]
}The response is 200 OK. Results come back in the order you sent them, each with either a link or an error. A bad row doesn't stop the others, so always check failed and each row.
Get a link
GET /api/v1/links/{id} returns one link.
Update a link
PATCH /api/v1/links/{id} changes only the fields you send.
| Field | Description |
|---|---|
url | A new destination. |
title | A new title. |
active | true or false to turn the link on or off. |
expires_at | A new expiry date-time. |
clear_expiry | true to remove an expiry. |
rules | Replaces the link's redirect rules. |
Changing url or rules counts against your monthly destination edits (shown in the usage response). When the limit is reached you get 403.
Delete a link
DELETE /api/v1/links/{id} returns 204 No Content. Deleting a link doesn't necessarily give back its monthly allowance.
Link stats
GET /api/v1/links/{id}/stats?range=30d
range is 7d, 30d or 90d, limited by your plan's stats retention (30 days on monthly plans, 90 days on annual plans).
{
"range": "30d",
"clicks": 0,
"unique_visitors": 0,
"qr_scans": 0,
"daily": [ { "date": "2026-11-01", "clicks": 0, "unique_visitors": 0 } ],
"breakdowns": { "…": [ { "label": "…", "clicks": 0 } ] },
"smart_redirect_results": [ { "rule": "…", "sent_to": "…", "clicks": 0 } ]
}Clicks, unique visitors and QR scans are returned as totals and as daily counts, with breakdowns by dimensions such as country and device. smart_redirect_results appears when the link has redirect rules.
QR code
GET /api/v1/links/{id}/qr.svg or qr.png returns the link's QR code using its saved style.
| Parameter | Description |
|---|---|
size | Image size. |
fg, bg | Foreground and background colors. |
module_style, eye_style, frame | Shape and frame options. |
margin | Quiet-zone margin around the code. |
ec_level | Error correction level. |
logo | Whether to use the workspace logo. |
download=1 | Sends the image as a file download. |
Parameters replace the saved style for that one request. For printing tips, see how to make a QR code that scans reliably.
Errors
Errors are JSON with a message, and the HTTP status tells you the kind of problem.
{ "error": "this API key is read-only" }| Status | Meaning |
|---|---|
400 | The request body isn't valid JSON. |
401 | The key is missing, invalid or revoked. |
403 | The key is read-only, isn't allowed for the REST API, or you've reached your monthly link or destination-edit limit. |
404 | No such link in this workspace. |
409 | The custom short code is already taken. |
422 | A field isn't valid, or the destination is blocked. |
429 | Rate limit exceeded. Wait for the Retry-After period. |
500 | Something went wrong on our side. Try again, and contact support if it continues. |
Questions? Contact us.
1,000 New Links. 1,000 QR Codes. Every Month.
Every short link includes its own QR code, with a custom domain, team collaboration, smart regional routing and developer automation on one simple price.
