Launching soon.

REST API reference

Everything you can do with links, QR codes and stats over HTTPS and JSON.

Launching soon. Qodeveo is not open for signups yet. These docs describe the API and MCP server as they will work once your account is active. Keys are created in the Qodeveo app, on the API page of your workspace.

Basics

Base URLhttps://app.qodeveo.com
FormatJSON request and response bodies (Content-Type: application/json)
AuthenticationAuthorization: Bearer shk_... on every request
Rate limit600 requests per minute per key. Over the limit you get 429 and a Retry-After header.
ScopeA 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

MethodPathWhat it doesKey access
GET/api/v1/usagePlan, links created this month and limitsRead
GET/api/v1/groupsYour custom country groupsRead
GET/api/v1/linksList links (50 per page, optional search)Read
POST/api/v1/linksCreate a linkWrite
POST/api/v1/links/bulkCreate many links in one requestWrite
GET/api/v1/links/{id}Get one linkRead
PATCH/api/v1/links/{id}Update a linkWrite
DELETE/api/v1/links/{id}Delete a linkWrite
GET/api/v1/links/{id}/statsClicks, visitors and breakdownsRead
GET/api/v1/links/{id}/qr.svgThe 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.

Response
{
  "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.

FieldRequiredDescription
urlYesThe destination web address.
titleNoA label to help you find the link later.
codeNoA custom short code. If it is already taken you get 409. Leave it out to get a generated code.
domainNoThe hostname of one of your verified custom domains. Leave it out to use the default domain.
expires_atNoWhen the link stops working, as an ISO 8601 date-time such as 2026-12-31T23:59:59Z.
rulesNoSmart regional redirects (see below). Up to 10 rules per link.
Request
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"}'
Response
{
  "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:

FieldDescription
typecountry (the default) or group.
countryA country code, for country rules.
regionOptional region within the country.
groupThe id or name of one of your custom groups (see GET /api/v1/groups), for group rules.
urlWhere matching visitors are sent.
Example body
{
  "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.

Request
{
  "links": [
    { "url": "https://example.com/product-a", "title": "Product A" },
    { "url": "https://example.com/product-b", "title": "Product B" },
    { "url": "not a url" }
  ]
}
Response
{
  "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.

FieldDescription
urlA new destination.
titleA new title.
activetrue or false to turn the link on or off.
expires_atA new expiry date-time.
clear_expirytrue to remove an expiry.
rulesReplaces 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).

Response (shape)
{
  "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.

ParameterDescription
sizeImage size.
fg, bgForeground and background colors.
module_style, eye_style, frameShape and frame options.
marginQuiet-zone margin around the code.
ec_levelError correction level.
logoWhether to use the workspace logo.
download=1Sends 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 body
{ "error": "this API key is read-only" }
StatusMeaning
400The request body isn't valid JSON.
401The key is missing, invalid or revoked.
403The key is read-only, isn't allowed for the REST API, or you've reached your monthly link or destination-edit limit.
404No such link in this workspace.
409The custom short code is already taken.
422A field isn't valid, or the destination is blocked.
429Rate limit exceeded. Wait for the Retry-After period.
500Something 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.

Join the waiting list

Qodeveo is launching soon. Leave your details and we will let you know when it opens.

Loading the form…