Overview
- Base URL:
https://urlf.ly/api/v1 - Responses are JSON. Send request bodies as JSON with
Content-Type: application/json, or form-encoded. - Every request needs an API key. A key acts on the links owned by the member who created it.
- Times are UTC, formatted
YYYY-MM-DD HH:MM:SS. - A request to
https://urlf.ly/api/v1itself needs no key and returns the API name and version.
Authentication
- Sign in and open API keys from your dashboard.
- Name the key and press Create key. The full key, starting
uf_, is shown once only. Store it somewhere safe. - Send it with every request, either as a Bearer token or in an
X-Api-Keyheader.
curl -H "Authorization: Bearer uf_YOUR_API_KEY" https://urlf.ly/api/v1/links curl -H "X-Api-Key: uf_YOUR_API_KEY" https://urlf.ly/api/v1/links You can hold up to 20 active keys and revoke any of them from the same page. Only a hash of each key is stored. A missing, wrong, or revoked key gets 401. After 30 failed attempts from one IP address within 10 minutes, further attempts get 429 until the window passes.
Rate limits
Each key can make 1,000 requests per hour. Once a key is accepted and the API is on its owner’s plan, responses carry X-RateLimit-Limit: 1000. Past the limit you get 429 with a Retry-After header giving the seconds to wait.
Request bodies are limited to 64 KB.
Endpoints
| Method and path | What it does |
|---|---|
GET /links | List your links, newest first |
POST /links | Create a link |
GET /links/{ref} | Get one link |
PATCH /links/{ref} | Change a link |
DELETE /links/{ref} | Delete a link |
GET /links/{ref}/stats | Click stats for a link |
Paths are relative to the base URL. {ref} is the link’s numeric id or its short code. A number is tried as an id first, then as a code. A link you do not own returns 404.
The link object
{
"id": 1234,
"code": "launch",
"short_url": "https://urlf.ly/launch",
"destination": "https://example.com/blog/launch-day",
"title": "Launch post",
"notes": null,
"status": "active",
"clicks": 0,
"created_at": "2026-11-09 18:00:00",
"updated_at": "2026-11-09 18:00:00",
"expires_at": null,
"encrypted": false
} status is active or disabled. short_url uses your branded domain when the link is on one. expires_at is null for a link that does not expire.
List links
GET /links?page=1&per_page=20. page starts at 1. per_page defaults to 20; the most is 100.
{
"items": [ { "id": 1234, "code": "launch", "...": "..." } ],
"page": 1,
"per_page": 20,
"total": 1
}
Create a link
curl -X POST https://urlf.ly/api/v1/links \
-H "Authorization: Bearer uf_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/blog/launch-day", "title": "Launch post"}' Returns 201:
{
"id": 1234,
"code": "aB3xY9",
"short_url": "https://urlf.ly/aB3xY9",
"destination": "https://example.com/blog/launch-day",
"created_at": "2026-11-09 18:00:00"
} If you already have an active link to exactly the same URL, that link is returned instead of a new one, even when you send an alias.
Fields for create and update
| Field | Meaning |
|---|---|
url | The destination, an http or https address. Required when creating. |
alias | Your own short code: 3 to 32 letters, numbers, _ or -. Taken codes return 409, reserved words 422. Without it a random code is made. Needs the Premium plan. On update, a different alias renames the link and the old code stops working (Premium plan). |
title | A label for the link, up to 200 characters. |
notes | Private notes, up to 2,000 characters. |
expiry | A number of days, or a future date such as 2026-12-31. 0 means never. When creating without it, the link does not expire. On update, leaving it out keeps the current expiry. |
tags | A list of strings, or one comma-separated string. Needs the Premium plan; ignored otherwise. |
max_clicks | Stop the link after this many counted clicks; 0 means no limit. Needs the Premium plan; ignored otherwise. |
password | Ask visitors for this password, 4 to 72 characters. Needs the Premium plan; ignored otherwise. An empty value leaves the current password alone; remove a password from the link’s Edit page. |
Other options, such as country routing or cloaking, are set on the dashboard; the API does not change them.
Get a link
curl -H "Authorization: Bearer uf_YOUR_API_KEY" https://urlf.ly/api/v1/links/launch Returns 200 with the link object.
Update a link
Send only the fields you want to change. Fields you leave out keep their current value. null clears title, notes, or tags.
curl -X PATCH https://urlf.ly/api/v1/links/launch \
-H "Authorization: Bearer uf_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/blog/launch-day-updated"}' Returns 200 with the updated link object.
Delete a link
curl -X DELETE -H "Authorization: Bearer uf_YOUR_API_KEY" https://urlf.ly/api/v1/links/launch Returns 204 with no body. The short link stops working.
Link stats
curl -H "Authorization: Bearer uf_YOUR_API_KEY" https://urlf.ly/api/v1/links/launch/stats {
"link_id": 1234,
"code": "launch",
"clicks_total": 2,
"unique_visitors": 2,
"qr_scans": 1,
"last_7_days": [
{ "day": "2026-11-03", "clicks": 0 },
"... one entry per day ...",
{ "day": "2026-11-09", "clicks": 2 }
],
"top_countries": [ { "code": "US", "label": "United States", "clicks": 2 } ],
"top_referrers": [ { "label": "news.example.org", "clicks": 1 } ]
} The numbers above are an example. clicks_total, unique_visitors, and qr_scans cover the life of the link. last_7_days has one entry per UTC day, today included. top_countries (up to 15) and top_referrers (up to 10) cover the same 7 days. Known bots are not counted.
Errors
Errors return JSON with a single error message, for example {"error": "url is required"}.
| Status | When |
|---|---|
400 | The body is not valid JSON. |
401 | Missing, wrong, or revoked key. Sent with WWW-Authenticate: Bearer. |
403 | The API is not on your plan, or is switched off. |
404 | Unknown path, or no link of yours with that id or code. |
405 | Wrong method for the path. The Allow header lists the right ones. |
409 | The alias is already taken. |
413 | The body is larger than 64 KB. |
422 | A field is missing or invalid, such as a bad URL or alias. |
429 | Rate limit reached, or too many failed key attempts. See Retry-After. |
500 | Something went wrong on our side. |
Webhooks
To be told when a link is created, changed, deleted, or clicked, add a webhook from your dashboard instead of polling the API. Webhooks: Premium plan.