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.
https://urlz.sh/api/v1Authentication
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- • Keys look like
urlz_sk_followed by 32 characters. - • Each key has scopes:
read,write, anddelete. Each endpoint below lists the scope it requires; a key missing that scope gets403 INSUFFICIENT_SCOPE. - • Keys created before scopes existed have
full_access(all scopes). - • Passing the key as a query parameter (
?api_key=…) is rejected with400— always use the header. - • A request scoped to your key only ever sees and affects links owned by that key's account.
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).
/linksscope: writeCreate a short link.
| Parameter | Type | Required | Description |
|---|---|---|---|
| url | string | yes | The destination URL to shorten. |
| customSlug | string | no | Use a specific code instead of a random one (must be available on the domain). |
| title | string | no | Optional label (max 100 chars). |
| description | string | no | Optional description (max 500 chars). |
| tags | string | no | Optional tags string (max 200 chars). |
| expiresAt | string (ISO 8601) | no | When the link should stop working. |
| domainId | string | no | A custom domain you own (Business plan). Defaults to urlz.sh. |
| utmSource / utmMedium / utmCampaign / utmContent / utmTerm | string | no | UTM 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"
}/linksscope: readList your links, newest first, paginated.
| Parameter | Type | Required | Description |
|---|---|---|---|
| page | integer | no | Page number (default 1). |
| limit | integer | no | Items per page, 1–100 (default 20). |
| search | string | no | Match against URL, title, description, or code. |
| status | active | disabled | expired | all | no | Filter 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 }
}/links/{id}scope: readGet 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
}/links/{id}scope: writeUpdate a link. Send only the fields you want to change.
| Parameter | Type | Required | Description |
|---|---|---|---|
| longUrl | string | no | New destination URL. |
| title | string | no | Max 100 chars. |
| description | string | no | Max 500 chars. |
| tags | string | no | Max 200 chars. |
| isActive | boolean | no | Enable or disable the link. |
| expiresAt | string (ISO 8601) | null | no | Set 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
}/links/{id}scope: deletePermanently 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" }/links/{id}/analyticsscope: readClick analytics for a link over a time window.
| Parameter | Type | Required | Description |
|---|---|---|---|
| days | integer | no | Window 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 }]
}
}/links/{id}/qrscope: readGenerate a QR code for a link's short URL.
| Parameter | Type | Required | Description |
|---|---|---|---|
| format | png | svg | base64 | no | Output format (default png). |
| size | integer | no | Pixel 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.pngformat=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: 1780755780000Exceeding 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" }| Status | code | When |
|---|---|---|
| 400 | VALIDATION_ERROR | Request body or query failed validation (see details). |
| 400 | INVALID_AUTH_METHOD | API key was passed as a query param instead of the header. |
| 400 | INVALID_UTM | A UTM parameter was invalid. |
| 400 | SLUG_TAKEN | The requested customSlug is already in use. |
| 401 | UNAUTHORIZED | Missing, malformed, expired, or revoked API key. |
| 403 | INSUFFICIENT_SCOPE | The key lacks the scope the endpoint requires. |
| 403 | PLAN_REQUIRED | Feature needs a higher plan (e.g. custom domains). |
| 403 | LIMIT_REACHED | Monthly link limit for the plan was reached. |
| 404 | NOT_FOUND | Link (or analytics) not found, or not owned by this key. |
| 404 | DOMAIN_NOT_FOUND | The specified domainId is not yours. |
| 429 | RATE_LIMITED | Per-key rate limit exceeded. |
| 500 | INTERNAL_ERROR / QR_ERROR | Unexpected 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