Link API
The API creates anonymous short links, returns their click statistics, changes their settings and deletes them. Requests and responses use JSON. CORS is enabled, so calls work from browser code as well.
Base address:
http://anonym.es/api/v1
Quick start
To create a short link, send the destination address:
curl -X POST http://anonym.es/api/v1/links \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/article"}'
No token is required for this call. Without one, the link is created as a guest link within the Free quota of your IP address.
With an account token the same call puts the link into your dashboard and counts it against your plan:
curl -X POST http://anonym.es/api/v1/links \
-H "Authorization: Bearer anon_YOUR_ACCOUNT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/article"}'
The response contains the short address, the link id and the token that manages this link. Keep the token: it is needed to read or delete the link later.
{
"ok": true,
"link": {
"id": 4821,
"short": "http://anonym.es/k7m2q",
"url": "https://example.com/article",
"status": "active",
"password": false,
"created_at": "2026-09-05T14:02:11Z",
"expires_at": null,
"max_hits": null,
"self_destruct": false,
"delay": null,
"no_countdown": false,
"adult": false,
"note": null,
"tags": [],
"clicks": 0,
"uniques": 0
},
"token": "3f9c1b7e2d4a4c0e9b8f7a6d5c4b3a21",
"left": 4
}
Fields of the link object
All endpoints that return a link use this structure. Timestamps are ISO 8601 in UTC. The clicks and uniques counters cover the entire lifetime of the link.
| Field | Type | What it does |
|---|---|---|
id | integer | Numeric id of the link, used by the other calls. |
short | string | The short address to share. |
url | string | null | The destination; null for a note-only link. |
status | string | active | expired | exhausted (visit cap reached) | flagged (destination on a safety list) | banned. |
password | boolean | Visitors must enter a password. |
created_at | string | ISO 8601, UTC. |
expires_at | string | null | When the link expires; null = no expiry. |
max_hits | integer | null | Visit cap; null = no cap. |
self_destruct | boolean | The link deletes itself, for good, when the cap or the lifetime is reached. |
delay | integer | null | Seconds on the forwarding page; null = the plan default, 0 = instant. |
no_countdown | boolean | The forwarding page shows no countdown and waits for a click on the address. |
adult | boolean | Visitors confirm being 18 or older before the forwarding page. |
note | string | null | The note shown on the forwarding page. |
tags | string[] | Tags, lowercase. |
clicks | integer | Visits for the whole life of the link. |
uniques | integer | Unique visitors (one per address per day) for the whole life of the link. |
Authorization
Authorized requests carry the token in the Authorization header. The X-API-Key header is accepted as an alternative. Tokens are never passed in the query string.
Authorization: Bearer YOUR_TOKEN
| Token | Where to get it | What it is for |
|---|---|---|
| Account token | In the dashboard | All links of the account, with the rights of the plan |
| Link token | Returned when a link is created | One specific link, with the rights of the Free plan |
An account token starts with anon_. A link token consists of 32 characters. Generating a new account token invalidates the previous one.
Example:
curl http://anonym.es/api/v1/me \ -H "Authorization: Bearer anon_YOUR_ACCOUNT_TOKEN"
Generate an account token in the dashboard
Endpoints
Request bodies may be sent as application/json or as application/x-www-form-urlencoded. Parameter names and error codes are identical across all endpoints.
| Method | Path | Token | Cost | What it does |
|---|---|---|---|---|
GET | /api/v1 | optional | 1 | About this API: version, documentation and schema addresses. |
POST | /api/v1/links | optional | 5 + 1/url | Create a link, or up to 50 at once. Without a token: as a guest, on the Free quota. Returns the link with its token. |
GET | /api/v1/names/check | optional | 2 | Is this custom name available? Suggestions when it is taken. |
GET | /api/v1/links | account | 2 | The account's links, newest first. |
GET | /api/v1/links/lookup | link or account | 1 | Find one of your links by its short address. |
GET | /api/v1/links/{id} | link or account | 1 | One link: settings, status, clicks and unique visitors for its whole life. Account tokens may add a statistics report. |
PATCH | /api/v1/links/{id} | link or account | 3 | Change settings. Only the fields sent change. Paid plans (a link token has Free rights, so it cannot edit). |
DELETE | /api/v1/links/{id} | link or account | 2 | Delete the link; the name becomes free again. |
GET | /api/v1/links/{id}/qr | link or account | 3 | The QR code of the short address as an image (PNG or SVG); needs a token, so it suits server-side use. |
GET | /api/v1/qr | optional | 3 | The QR code of a short address as an image, without a token: the address itself is the key. Made for <img> tags. |
GET | /api/v1/me | account | 1 | The account: plan, limits, quota left, alias domains, what the plan allows. |
GET | /api/v1/openapi.json | optional | 1 | This API as an OpenAPI 3.1 document. |
The cost is given in rate-limit units, see Rate limits.
Creating a link
POST /api/v1/links
The minimal request contains only the url field:
curl -X POST http://anonym.es/api/v1/links \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/article"}'
With an account token the link belongs to the account and counts against its plan. Without a token it is a guest link on the IP quota:
curl -X POST http://anonym.es/api/v1/links \
-H "Authorization: Bearer anon_YOUR_ACCOUNT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/article"}'
A successful request returns status 201 together with the link, its token and the number of links left in the current quota period.
Fields of the response:
| Field | Type | What it does |
|---|---|---|
link | object | The created link. |
token | string | The link token (32 characters): manages this link. |
left | integer | null | Links still available in the current quota period; null when the plan has no limit. |
qr | object | The QR code, present when qr=true was sent. |
batch | boolean | true when several addresses were sent. |
results | array | One entry per address, in the order sent: {ok: true, link, token, qr} or {ok: false, url, error, message, field}. |
Link with a custom name
The short address gets the name my-article if it is free and meets the rules below.
curl -X POST http://anonym.es/api/v1/links \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/article","name":"my-article"}'
Name rules:
- Latin letters, digits, hyphen and underscore. From 5 characters on the Free plan and from 3 on paid plans, up to 32. Case does not matter: Promo and promo are the same link.
- Page names, language codes and service words are reserved.
- Brand names and offensive words are refused. Names pass a word list and an automated review. The number of custom names per hour is limited by plan.
- A taken name returns status 409 with free alternatives in suggest.
- A subdomain name (sub: true, Max plan) is one DNS label: letters, digits and hyphens, no hyphen at either end, no underscore. Names such as www, mail or ns1 are reserved.
A name can be checked before the link is created:
curl "http://anonym.es/api/v1/names/check?name=my-article"
Fields of the check response:
| Field | Type | What it does |
|---|---|---|
available | boolean | true when the name can be used. |
name | string | The name that was checked. |
error | string | Why the name is not available: taken, reserved, too_short and the other name errors. |
message | string | The reason in words. |
field | string | Always name. |
suggest | array | Free alternatives when the name is taken. |
Several links at once
The urls field accepts up to 50 addresses, as a JSON array or as text with one address per line. Each address becomes its own link with its own token, and the other fields apply to all of them. A custom name is not available in a batch.
curl -X POST http://anonym.es/api/v1/links \
-H "Content-Type: application/json" \
-d '{"urls":["https://example.com/a","https://example.com/b","https://example.com/c"]}'
The response contains batch: true and a results array with one entry per address, in the order sent. A faulty address produces an entry with ok: false and the error code without stopping the rest.
Additional settings
curl -X POST http://anonym.es/api/v1/links \
-H "Authorization: Bearer anon_YOUR_ACCOUNT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/article",
"name": "my-article",
"expires_days": 30,
"max_hits": 1000,
"self_destruct": true,
"password": "secret",
"note": "Available until the end of the month",
"delay": 5,
"tags": "blog,promo",
"qr": true,
"qr_size": 300,
"qr_format": "png"
}'
Options marked as paid are ignored for guests and on the Free plan. The response reflects the settings the link actually received.
| Field | Type | What it does |
|---|---|---|
url | string, required | The destination. Or urls: several addresses, one per line or as a JSON array (up to 50); each gets its own link. |
name | string | Custom name for a single link (checked against the rules and the name filter). |
domain | string | One of the alias domains, random, or random-alias (random among the alias domains only). Paid plans; default is random: links never sit on the main domain. Free and guests: anonymes.click. |
sub | boolean | Make it name.alias instead of alias/name. Max plan, alias domains only. |
expires_days | integer | Lifetime in days. Paid plans. |
max_hits | integer | Visit cap. Paid plans. |
self_destruct | boolean | Delete the link for good when the visit cap or the lifetime is reached (needs max_hits or expires_days). |
password | string | Password visitors must enter. Paid plans. |
note | string | Text shown on the forwarding page. Paid plans; length by plan. |
note_only | boolean | The link opens the note itself, no destination. Pro+ and up. |
delay | integer | Seconds on the forwarding page, 0 = instant. Paid plans. |
no_countdown | boolean | No countdown on the forwarding page: no timer, no automatic forward, the visitor clicks the address. Paid plans. |
adult | boolean | Adult content (18+): visitors confirm their age on a page of its own before anything else is shown. Every plan. |
tags | string | Comma-separated tags (up to 5, 24 characters each). Account tokens on paid plans. |
qr | boolean | Include the QR code of the short address (base64) in the response. |
qr_size | integer | Side of the QR image in pixels, 100-1000 (default 300; PNG rounds down to whole modules). |
qr_format | string | png (default) or svg. |
qr_logo | boolean | false = plain code without the logo; paid plans only, otherwise ignored. |
Listing links
GET /api/v1/links
Requires an account token.
Requires an account token. Links are returned newest first.
curl "http://anonym.es/api/v1/links?page=1&per=50" \ -H "Authorization: Bearer anon_YOUR_ACCOUNT_TOKEN"
| Field | Type | What it does |
|---|---|---|
page | integer | Page number (default 1). |
per | integer | Links per page, 1-100 (default 50). |
q | string | Search in the short name, domain and destination (one flat list, up to 50). |
tag | string | Only links with this tag. |
Search and the tag filter can be combined:
curl "http://anonym.es/api/v1/links?q=example&tag=promo" \ -H "Authorization: Bearer anon_YOUR_ACCOUNT_TOKEN"
Fields of the response:
| Field | Type | What it does |
|---|---|---|
page | integer | Current page. |
per | integer | Links per page. |
total | integer | Number of links matched. |
pages | integer | Number of pages. |
links | array | Link objects, newest first. |
Finding a link by its short address
A link is addressed by its numeric id (the id field, for example 4821), not by the name in the short address. When only the short address is known, the id can be looked up with any token that manages the link:
curl --get http://anonym.es/api/v1/links/lookup \ -H "Authorization: Bearer YOUR_TOKEN" \ --data-urlencode "short=http://anonym.es/k7m2q"
Link details and statistics
GET /api/v1/links/{id}
Returns the settings of the link, its status, the number of visits and the number of unique visitors for its whole life:
curl http://anonym.es/api/v1/links/4821 \ -H "Authorization: Bearer YOUR_TOKEN"
With an account token, a visits report can be added with the stats parameter:
curl "http://anonym.es/api/v1/links/4821?stats=day" \ -H "Authorization: Bearer anon_YOUR_ACCOUNT_TOKEN"
| Value | Report period |
|---|---|
day | Last 30 days, by day |
week | Last 26 weeks, by ISO week |
month | Last 24 months, by month |
Fields of the response: stats
| Field | Type | What it does |
|---|---|---|
mode | string | day, week or month. |
from | string | First day of the report, YYYY-MM-DD. |
to | string | Last day of the report. |
series | array | One entry per bucket, oldest first: [from, to, visits, uniques]. |
summary | object | cur (the current bucket), prev (the previous one), avg (the average of the buckets before the current one), each with hits and uniq. |
dims | object | Breakdown for the range: ref, country, device, os, browser, hour, each a list of [value, visits]. |
Editing a link
PATCH /api/v1/links/{id}
Only the fields sent change:
curl -X PATCH http://anonym.es/api/v1/links/4821 \
-H "Authorization: Bearer anon_YOUR_ACCOUNT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"max_hits": 2000, "delay": 3, "tags": "blog,updated"}'
Editing requires a paid plan. A link token has the rights of the Free plan and cannot change settings.
| Field | Type | What it does |
|---|---|---|
url | string | New destination. Pro+ and up. |
name | string | New name (same rules as a custom name). |
expires_days | integer | New lifetime in days; empty = no expiry. |
max_hits | integer | New visit cap; empty = no cap. |
self_destruct | boolean | Delete for good when the cap or the lifetime is reached; false turns it off. |
password | string | New password; an empty string removes the password. |
note | string | New note; empty removes it (a note-only link keeps needing one). |
delay | integer | Seconds on the forwarding page; empty = the plan default. |
no_countdown | boolean | true = no countdown on the forwarding page; false turns the countdown back on. |
adult | boolean | true = age confirmation (18+) before the forwarding page; false removes it. |
tags | string | New comma-separated tag list; empty removes all tags. |
Clearing a value
- password, note and tags: an empty string removes the value.
- expires_days and max_hits: null, an empty string or 0 removes the limit. self_destruct turns off when neither a cap nor a lifetime is left.
- delay: null or an empty string restores the plan default. 0 means an instant redirect.
- url cannot be emptied, and a note-only link keeps needing a note.
curl -X PATCH http://anonym.es/api/v1/links/4821 \
-H "Authorization: Bearer anon_YOUR_ACCOUNT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"password": "", "note": "", "expires_days": null, "max_hits": null, "delay": 0}'
Deleting a link
DELETE /api/v1/links/{id}
curl -X DELETE http://anonym.es/api/v1/links/4821 \ -H "Authorization: Bearer YOUR_TOKEN"
After deletion the custom name becomes free again. The statistics are deleted together with the link.
Fields of the response:
| Field | Type | What it does |
|---|---|---|
deleted | integer | The id of the deleted link. |
QR code
For an HTML img tag, request the code by the short address. This endpoint needs no token: the short address itself is the key, and anyone who has it can generate the same code.
<img src="http://anonym.es/api/v1/qr?short=http%3A%2F%2Fanonym.es%2Fk7m2q&qr_size=400" alt="QR code" >
Server-side code can also request the image by id with a token, or receive it base64-encoded in JSON by adding qr=true to the create or details call:
curl "http://anonym.es/api/v1/links/4821/qr?qr_size=400&qr_format=svg" \ -H "Authorization: Bearer YOUR_TOKEN" -o qr.svg
Parameters of both image endpoints:
| Field | Type | What it does |
|---|---|---|
short | string, required | The short address, for example https://anonym.es/abc12. |
qr_size | integer | Side of the QR image in pixels, 100-1000 (default 300; PNG rounds down to whole modules). |
qr_format | string | png (default) or svg. |
qr_logo | boolean | false = plain code without the logo; paid plans only, otherwise ignored. |
The qr member in JSON responses:
| Field | Type | What it does |
|---|---|---|
format | string | png or svg. |
mime | string | image/png or image/svg+xml. |
size | integer | Requested side length in pixels. |
logo | boolean | Whether the logo is drawn in the center. |
base64 | string | The image, base64-encoded. |
Account details
GET /api/v1/me
Requires an account token. Returns the plan, its limits and the remaining quota:
curl http://anonym.es/api/v1/me \ -H "Authorization: Bearer anon_YOUR_ACCOUNT_TOKEN"
| Field | Type | What it does |
|---|---|---|
login | string | The account login. |
plan | string | Plan key: free, pro, pro_plus, max or enterprise. |
plan_label | string | The plan name as shown on the site. |
until | string | null | When the paid plan ends, ISO 8601; null on Free or without an end date. |
limits | object | links (per period), per (day or month), ai_hourly (custom names per hour), name_min (shortest custom name), note_max (note length), api_units (API budget per minute). |
left | integer | null | Links still available in the current quota period; null without a limit. |
aliases | array | The alias domains the plan may use. |
can | object | One boolean per option: password, ttl, edit, edit_url, tags, note, note_only, alias, sub, instant, qr_no_logo. |
Errors
Errors are returned in a single JSON format:
{
"ok": false,
"error": "taken",
"message": "This name is already taken.",
"field": "name",
"suggest": ["my-article-26", "my-my-article", "my-article-link"]
}
field contains the name of the faulty parameter, or null when the error does not concern a specific field. A taken name adds suggest, a paid option adds upgrade, quotas and rate limits add the Retry-After header.
| HTTP | Meaning |
|---|---|
400 | Error in the request parameters |
401 | Token missing or invalid |
403 | The plan or the token type does not allow this call |
404 | The link does not exist or belongs to someone else |
405 | Method not allowed for this path |
409 | The custom name is taken |
429 | Rate limit or quota exhausted. See Retry-After |
501 | PNG is not available on this server. Request svg |
All error codes
| Code | HTTP | Field | What it does |
|---|---|---|---|
bad_request | 400 | Malformed request. | |
bad_url | 400 | url | The address is missing or is not a valid http(s) URL. |
blocked | 400 | url | This destination cannot be linked. |
bad_name | 400 | name | This name is not allowed. |
dirty | 400 | name | This name is not allowed. |
brand | 400 | name | This name looks like a brand name and cannot be used. |
reserved | 400 | name | This name is reserved. |
too_short | 400 | name | The name is too short for your plan. |
sub_format | 400 | name | A subdomain name is 3 to 32 letters, digits or hyphens. |
sub_main | 400 | domain | Subdomain links exist on the alias domains only. |
name_batch | 400 | name | A custom name works for a single address, not for a batch. |
taken | 409 | name | This name is already taken. |
note_long | 400 | note | The note is longer than your plan allows. |
note_required | 400 | note | A note-only link needs a note. |
bad_tag | 400 | tags | Tags: up to 5 of them, each up to 24 characters. |
pro_only | 403 | This option needs a paid plan. | |
max_only | 403 | Subdomain links need the Max plan. | |
account_only | 403 | This call needs an account token. | |
limit | 429 | The link quota for this period is used up. | |
domain_limit | 429 | url | Today's limit for links to this site without an account is used up. |
ai_limit | 429 | name | Too many custom names this hour; try again later. |
rate_limited | 429 | Too many requests; slow down. | |
busy | 429 | Too many links being created at once; retry in a moment. | |
auth | 401 | Missing or invalid token. | |
not_found | 404 | No such link. | |
no_route | 404 | No such endpoint. | |
method | 405 | Method not allowed. | |
fetch | 500 | Could not create the link; try again. | |
qr_unavailable | 501 | qr_format | PNG QR codes are not available on this server; request svg. |
Rate limits
Each call costs units, and each plan has a budget of units per minute (see Plans and limits). A read costs 1 unit, a list page 2, a QR code 3, an edit 3, a deletion 2, a creation 5 plus 1 per address. Every response carries these headers:
| Header | Value |
|---|---|
X-RateLimit-Limit | Budget of units per minute |
X-RateLimit-Remaining | Units left in the current minute |
X-RateLimit-Reset | Seconds until the budget renews |
Retry-After | On 429: seconds to wait before retrying |
Within any 10-second window at most a quarter of the minute budget can be spent, with a minimum of 20 units. When the budget is exhausted, the API responds with status 429.
Plans and limits
The exact limits of each plan. Links are counted per day and IP address on Free, per month and account on paid plans. The last column lists the settings a plan can use.
| Plan | Links | Custom names per hour | Shortest name | Note length | Units a minute | Options |
|---|---|---|---|---|---|---|
| Free | 5 per day, per IP address | 5 | 5 | 0 | 60 | none |
| Pro | 500 per month | 10 | 3 | 300 | 150 | password, expires_days, max_hits, edit, tags, note, domain, delay, qr_logo |
| Pro+ | 2,000 per month | 50 | 3 | 2,000 | 400 | password, expires_days, max_hits, edit, edit url, tags, note, note_only, domain, delay, qr_logo |
| Max | 50,000 per month | 200 | 3 | 5,000 | 1,000 | password, expires_days, max_hits, edit, edit url, tags, note, note_only, domain, sub, delay, qr_logo |
| Enterprise | unlimited | unlimited | 3 | 5,000 | 2,500 | password, expires_days, max_hits, edit, edit url, tags, note, note_only, domain, sub, delay, qr_logo |
Without an account token, creation counts against the Free quota of the IP address: 5 links per day. Links to a destination domain registered less than a year ago share one guest cap of 5 a day, whoever makes them. At most 2 links per caller can be in creation at the same time.
OpenAPI specification
The complete technical description as an OpenAPI 3.1 document: http://anonym.es/api/v1/openapi.json
It contains schemas and examples of every request and response, the plan limits, the name rules and the error codes. It suits Swagger UI, Postman, Insomnia and client generators.