Developers

The urlz.sh API

A small REST API to create and manage short links, pull click analytics, and generate QR codes from your own code. JSON over HTTPS, authenticated with a Bearer API key.

Base URL — all endpoints are relative to this.
https://urlz.sh/api/v1

Authentication

Every request is authenticated with an API key passed as a Bearer token in the Authorization header. Create and manage keys under Settings → API Keys. A key is shown in full only once at creation — store it somewhere safe.

Authorization: Bearer urlz_sk_YOUR_KEY

Quickstart

Create your first short link. Replace urlz_sk_YOUR_KEY with a key from Settings → API Keys.

curl -X POST https://urlz.sh/api/v1/links \
  -H "Authorization: Bearer urlz_sk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/a/very/long/url"}'

Response 200 OK:

{
  "id": 1234,
  "code": "Ab3xKp",
  "shortUrl": "https://urlz.sh/Ab3xKp",
  "longUrl": "https://example.com/a/very/long/url",
  "title": null,
  "description": null,
  "tags": null,
  "clicks": 0,
  "createdAt": "2026-06-06T12:00:00.000Z",
  "expiresAt": null,
  "isActive": true,
  "domainId": "default_domain",
  "domainHostname": "urlz.sh"
}

The numeric id in the response is what you pass as {id} to the per-link endpoints below (not the short code).

Endpoints

All endpoints live under https://urlz.sh/api/v1 and return JSON — except GET /links/{id}/qr, whose png and svg formats return a binary image. Timestamps are ISO 8601 (UTC).

POST/linksscope: write

Create a short link.

ParameterTypeRequiredDescription
urlstringyesThe destination URL to shorten.
customSlugstringnoUse a specific code instead of a random one (must be available on the domain).
titlestringnoOptional label (max 100 chars).
descriptionstringnoOptional description (max 500 chars).
tagsstringnoOptional tags string (max 200 chars).
expiresAtstring (ISO 8601)noWhen the link should stop working.
domainIdstringnoA custom domain you own (Business plan). Defaults to urlz.sh.
utmSource / utmMedium / utmCampaign / utmContent / utmTermstringnoUTM params appended to the destination (max 200 each).
curl -X POST https://urlz.sh/api/v1/links \
  -H "Authorization: Bearer urlz_sk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/page",
    "customSlug": "launch",
    "title": "Launch link",
    "utmSource": "hn",
    "utmMedium": "social"
  }'
{
  "id": 1235,
  "code": "launch",
  "shortUrl": "https://urlz.sh/launch",
  "longUrl": "https://example.com/page?utm_source=hn&utm_medium=social",
  "title": "Launch link",
  "description": null,
  "tags": null,
  "clicks": 0,
  "createdAt": "2026-06-06T12:00:00.000Z",
  "expiresAt": null,
  "isActive": true,
  "domainId": "default_domain",
  "domainHostname": "urlz.sh"
}
GET/linksscope: read

List your links, newest first, paginated.

ParameterTypeRequiredDescription
pageintegernoPage number (default 1).
limitintegernoItems per page, 1–100 (default 20).
searchstringnoMatch against URL, title, description, or code.
statusactive | disabled | expired | allnoFilter by status (default all).
curl "https://urlz.sh/api/v1/links?limit=2&status=active" \
  -H "Authorization: Bearer urlz_sk_YOUR_KEY"
{
  "links": [
    {
      "id": 1235,
      "code": "launch",
      "shortUrl": "https://urlz.sh/launch",
      "longUrl": "https://example.com/page",
      "title": "Launch link",
      "description": null,
      "tags": null,
      "clicks": 42,
      "createdAt": "2026-06-06T12:00:00.000Z",
      "expiresAt": null,
      "isActive": true,
      "isExpired": false
    }
  ],
  "pagination": { "page": 1, "limit": 2, "total": 1, "pages": 1 }
}
GET/links/{id}scope: read

Get one link by its numeric id.

curl https://urlz.sh/api/v1/links/1235 \
  -H "Authorization: Bearer urlz_sk_YOUR_KEY"
{
  "id": 1235,
  "code": "launch",
  "shortUrl": "https://urlz.sh/launch",
  "longUrl": "https://example.com/page",
  "title": "Launch link",
  "description": null,
  "tags": null,
  "clicks": 42,
  "createdAt": "2026-06-06T12:00:00.000Z",
  "updatedAt": "2026-06-06T12:05:00.000Z",
  "expiresAt": null,
  "lastAccessedAt": "2026-06-06T13:00:00.000Z",
  "isActive": true,
  "isExpired": false
}
PATCH/links/{id}scope: write

Update a link. Send only the fields you want to change.

ParameterTypeRequiredDescription
longUrlstringnoNew destination URL.
titlestringnoMax 100 chars.
descriptionstringnoMax 500 chars.
tagsstringnoMax 200 chars.
isActivebooleannoEnable or disable the link.
expiresAtstring (ISO 8601) | nullnoSet or clear (null) the expiration.
curl -X PATCH https://urlz.sh/api/v1/links/1235 \
  -H "Authorization: Bearer urlz_sk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"isActive": false}'
{
  "id": 1235,
  "code": "launch",
  "shortUrl": "https://urlz.sh/launch",
  "longUrl": "https://example.com/page",
  "title": "Launch link",
  "description": null,
  "tags": null,
  "clicks": 42,
  "createdAt": "2026-06-06T12:00:00.000Z",
  "updatedAt": "2026-06-06T14:00:00.000Z",
  "expiresAt": null,
  "isActive": false,
  "isExpired": false
}
DELETE/links/{id}scope: delete

Permanently delete a link.

curl -X DELETE https://urlz.sh/api/v1/links/1235 \
  -H "Authorization: Bearer urlz_sk_YOUR_KEY"
{ "success": true, "message": "Link deleted successfully" }
GET/links/{id}/analyticsscope: read

Click analytics for a link over a time window.

ParameterTypeRequiredDescription
daysintegernoWindow length, 1–365 (default 30).
curl "https://urlz.sh/api/v1/links/1235/analytics?days=7" \
  -H "Authorization: Bearer urlz_sk_YOUR_KEY"
{
  "code": "launch",
  "shortUrl": "https://urlz.sh/launch",
  "longUrl": "https://example.com/page",
  "totalClicks": 42,
  "clicksInPeriod": 18,
  "uniqueClicks": 12,
  "createdAt": "2026-06-06T12:00:00.000Z",
  "expiresAt": null,
  "isActive": true,
  "period": {
    "days": 7,
    "from": "2026-05-30T12:00:00.000Z",
    "to": "2026-06-06T12:00:00.000Z"
  },
  "timeline": [
    { "date": "2026-06-05", "clicks": 6 },
    { "date": "2026-06-06", "clicks": 12 }
  ],
  "referrers": [
    { "source": "news.ycombinator.com", "clicks": 10 },
    { "source": "direct", "clicks": 8 }
  ],
  "devices": {
    "types": [{ "name": "mobile", "count": 11 }, { "name": "desktop", "count": 7 }],
    "browsers": [{ "name": "Chrome", "count": 14 }],
    "operatingSystems": [{ "name": "iOS", "count": 9 }]
  }
}
GET/links/{id}/qrscope: read

Generate a QR code for a link's short URL.

ParameterTypeRequiredDescription
formatpng | svg | base64noOutput format (default png).
sizeintegernoPixel size, 64–1024 (default 256).

png returns a binary image/png; svg returns image/svg+xml; base64 returns JSON with a data URL.

# Save a PNG to disk
curl "https://urlz.sh/api/v1/links/1235/qr?format=png&size=512" \
  -H "Authorization: Bearer urlz_sk_YOUR_KEY" \
  --output launch-qr.png

format=base64 response:

{
  "code": "launch",
  "shortUrl": "https://urlz.sh/launch",
  "qrCode": "data:image/png;base64,iVBORw0KGgoAAAAN…",
  "format": "base64",
  "size": 256
}

Rate limits

Requests are rate-limited per API key — 100 requests per minute by default. Every response includes the current limit state; check X-RateLimit-Limit for your key's exact ceiling.

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 96
X-RateLimit-Reset: 1780755780000

Exceeding the limit returns 429 with a Retry-After header (seconds):

{ "error": "Rate limit exceeded", "code": "RATE_LIMITED" }

Errors

Errors use standard HTTP status codes and a JSON body with a human-readable error and a stable code. Validation errors also include a details array.

{ "error": "Invalid API key", "code": "UNAUTHORIZED" }
StatuscodeWhen
400VALIDATION_ERRORRequest body or query failed validation (see details).
400INVALID_AUTH_METHODAPI key was passed as a query param instead of the header.
400INVALID_UTMA UTM parameter was invalid.
400SLUG_TAKENThe requested customSlug is already in use.
401UNAUTHORIZEDMissing, malformed, expired, or revoked API key.
403INSUFFICIENT_SCOPEThe key lacks the scope the endpoint requires.
403PLAN_REQUIREDFeature needs a higher plan (e.g. custom domains).
403LIMIT_REACHEDMonthly link limit for the plan was reached.
404NOT_FOUNDLink (or analytics) not found, or not owned by this key.
404DOMAIN_NOT_FOUNDThe specified domainId is not yours.
429RATE_LIMITEDPer-key rate limit exceeded.
500INTERNAL_ERROR / QR_ERRORUnexpected server error.

SDKs & spec

There's no official SDK or OpenAPI spec yet — the API is plain REST/JSON over HTTPS, so any HTTP client works (the curl examples above translate directly). Need something specific for your launch? Get in touch.

Ready to build?

Create a free account, generate an API key, and make your first call.

Get your API key