API documentation

A small REST API over JSON. Every endpoint lives under https://tinylink.r3x.site/api/v1, takes and returns JSON, and authenticates with a bearer token.

Authentication

Create a key under API keys, then send it on every request. Keys are stored only as SHA-256 digests, so a lost key must be revoked and replaced.

Authorization: Bearer tl_live_xxxxxxxxxxxxxxxxxxxx

X-API-Key: <key> works too, if that is easier for your client.

Conventions

Endpoints

post/api/v1/links

Create a short link. Only url is required.

curl -X POST https://tinylink.r3x.site/api/v1/links \
  -H "Authorization: Bearer $TINYLINK_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/a/very/long/path",
    "slug": "launch",
    "title": "Spring launch",
    "password": "hunter2",
    "expires_at": "2026-12-31 23:59:00",
    "max_clicks": 500,
    "fetch_og": true,
    "utm": { "utm_source": "twitter", "utm_medium": "social" }
  }'
{
  "data": {
    "slug": "launch",
    "short_url": "https://r3x.site/launch",
    "target_url": "https://example.com/a/very/long/path",
    "qr": "https://tinylink.r3x.site/qr/launch.svg",
    "clicks": 0,
    "is_active": true,
    "protected": true,
    "expires_at": "2026-12-31 23:59:00",
    "max_clicks": 500,
    "utm": { "utm_source": "twitter", "utm_medium": "social" },
    "card": { "title": "Example", "description": "…", "image": "https://…" },
    "created_at": "2026-08-30 10:14:02"
  }
}
get/api/v1/links

List your links. Query parameters: page (default 1), limit (max 100), search.

curl "https://tinylink.r3x.site/api/v1/links?limit=10&search=launch" \
  -H "Authorization: Bearer $TINYLINK_KEY"
get/api/v1/links/{slug}

Fetch a single link.

patch/api/v1/links/{slug}

Update any subset of url, slug, title, password, expires_at, max_clicks, is_active, utm, and the og_* card fields. Send "password": "" to remove a password.

curl -X PATCH https://tinylink.r3x.site/api/v1/links/launch \
  -H "Authorization: Bearer $TINYLINK_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/new","is_active":false}'
delete/api/v1/links/{slug}

Delete a link and its click history. The slug becomes available again.

get/api/v1/links/{slug}/stats

Analytics for one link. ?days=N controls the timeline window (1–365, default 30).

{
  "data": {
    "slug": "launch",
    "summary":  { "total": 214, "humans": 198, "unique": 171, "bots": 16, "last": "…" },
    "timeline": [ { "day": "2026-08-01", "hits": 12 }, … ],
    "referrers": [ { "label": "twitter.com", "hits": 88 }, … ],
    "browsers":  [ … ], "devices": [ … ], "countries": [ … ]
  }
}
get/api/v1/pages
post/api/v1/pages
patch/api/v1/pages/{slug}

Manage bio pages. Send blocks as an ordered array to replace the page contents in one call.

curl -X POST https://tinylink.r3x.site/api/v1/pages \
  -H "Authorization: Bearer $TINYLINK_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "slug": "me",
    "title": "R3X",
    "bio": "Building things on the internet.",
    "theme": "midnight",
    "accent": "#6366f1",
    "blocks": [
      { "kind": "header", "label": "Projects" },
      { "kind": "link",   "label": "Portfolio", "url": "https://r3x.site", "icon": "🌐" },
      { "kind": "social", "label": "GitHub",    "url": "https://github.com/…", "icon": "🐙" }
    ]
  }'
get/api/v1/me

The authenticated account plus headline stats. Useful for verifying a key.

get/api/v1/health

Unauthenticated liveness check.

QR codes

Every link and bio page has a QR image, generated on the fly with no external service. scale accepts 2–20.

https://tinylink.r3x.site/qr/launch.svg?scale=8     # vector, ideal for print
https://tinylink.r3x.site/qr/launch.png?scale=12    # raster, ideal for slides
https://tinylink.r3x.site/qr?data=https://example.com   # ad-hoc, any URL

Errors

400 Bad request — invalid URL, taken slug, or malformed field
401 Missing or invalid API key
404 No such link or page (also returned for links you do not own)
405 Wrong HTTP method for that path
429 Rate limit exceeded — check the Retry-After header
500 Something broke on our side