LinkShrink API Documentation

Privacy-first URL shortener API. Completely free and open — no API keys, no accounts, no signup. Just make HTTP requests.

Base URL: https://linkshrink.dev/api/v1   |   OpenAPI spec: /openapi.json

No Authentication Required

Every endpoint is open with no authentication. No API key, no OAuth token, no header required. Rate limits apply per IP address (see Rate Limits).

POST /shorten

POST /api/v1/shorten

Shorten a URL. Returns the short link, a QR code URL, a preview URL, anonymous analytics endpoint, and a deletion token. Store the deletion token if you want to delete the link later.

Request Body

FieldTypeRequiredDescription
urlstring required The URL to shorten. Must be http or https. Private IPs and localhost are blocked.
customAliasstring optional Custom slug for the short URL. 3–20 chars, alphanumeric + hyphens only. Must be unique. Example: my-campaign
ttl_secondsinteger optional Link expiry in seconds. Min: 60 (1 minute). Max: 31536000 (365 days). Default: 2592000 (30 days).
passwordstring optional Protect the link with a password. 4–128 characters. Visitors must enter the password before being redirected. The password is hashed with bcrypt and never stored in plaintext.
private_statsboolean optional When true, click analytics are private. A statsToken is returned in the response and must be provided via ?token= query parameter when calling GET /stats/:code. Default: false.
force_previewboolean optional When true, visitors see a safe browsing preview page and must click "Proceed" to reach the destination. Prevents phishing abuse. Default: false.

Response Fields

FieldDescription
codeThe short code or custom alias
shortUrlThe full short URL (e.g. https://linkshrink.dev/abc1234)
previewUrlSafe preview page showing destination before redirect — no click counted
qrCodeURL to the QR code PNG for this link
analyticsURL to fetch anonymous click stats
deleteTokenSave this. 48-char hex token required to delete the link. Not stored in plaintext.
statsTokenOnly present when private_stats: true. 48-char hex token required to view click analytics. Not stored in plaintext.
passwordProtectedtrue if a password was set, false otherwise
privateStatstrue if analytics are private, false otherwise
forcePreviewtrue if visitors see a safe browsing preview page before redirect, false otherwise
createdAtISO timestamp of creation
expiresAtISO timestamp of expiry

Example Request

cURL
curl -X POST "https://linkshrink.dev/api/v1/shorten" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/very/long/path?utm_source=email",
    "customAlias": "my-campaign",
    "ttl_seconds": 86400
  }'

Example Response

201 Created
{
  "status": "success",
  "data": {
    "code": "my-campaign",
    "shortUrl": "https://linkshrink.dev/my-campaign",
    "previewUrl": "https://linkshrink.dev/preview/my-campaign",
    "qrCode": "https://linkshrink.dev/api/v1/qr/my-campaign.png",
    "analytics": "https://linkshrink.dev/api/v1/stats/my-campaign",
    "deleteToken": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6",
    "passwordProtected": false,
    "originalUrl": "https://example.com/very/long/path?utm_source=email",
    "createdAt": "2026-02-20T12:00:00.000Z",
    "expiresAt": "2026-02-21T12:00:00.000Z"
  }
}

POST /shorten/bulk

POST /api/v1/shorten/bulk

Shorten up to 50 URLs in a single request. Returns a 207 Multi-Status response with per-URL results. Rate limit: 5 requests per minute.

Request Body

FieldTypeRequiredDescription
urlsstring[] required Array of URLs to shorten. Maximum 50. Each must be a valid http/https URL.
ttl_secondsinteger optional TTL applied to all links in the batch.

Example Request

cURL
curl -X POST "https://linkshrink.dev/api/v1/shorten/bulk" \
  -H "Content-Type: application/json" \
  -d '{
    "urls": [
      "https://example.com/page-1",
      "https://example.com/page-2",
      "https://example.com/page-3"
    ],
    "ttl_seconds": 604800
  }'

Example Response

207 Multi-Status
{
  "status": "success",
  "data": [
    {
      "url": "https://example.com/page-1",
      "success": true,
      "data": { "code": "xKp2m4a", "shortUrl": "https://linkshrink.dev/xKp2m4a", ... }
    },
    {
      "url": "https://example.com/page-2",
      "success": true,
      "data": { "code": "rNq9jzL", "shortUrl": "https://linkshrink.dev/rNq9jzL", ... }
    }
  ]
}

POST /shorten/utm

POST /api/v1/shorten/utm

Build a UTM-tagged URL and shorten it in one step. Appends UTM campaign parameters to the destination URL, then creates a short link.

Request Body

ParameterTypeRequiredDescription
urlstringYesDestination URL
utm_sourcestringYesCampaign source (e.g. "google", "newsletter")
utm_mediumstringYesCampaign medium (e.g. "cpc", "email", "social")
utm_campaignstringYesCampaign name (e.g. "spring-sale")
utm_termstringNoPaid search keywords
utm_contentstringNoAd/link differentiation
customAliasstringNoCustom short code (3-20 chars)
ttl_secondsintegerNoExpiry in seconds (60-31536000)
force_previewbooleanNoWhen true, visitors see a safe browsing preview page and must click "Proceed" to reach the destination. Prevents phishing abuse.

Example Request

cURL
curl -X POST "https://linkshrink.dev/api/v1/shorten/utm" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/landing",
    "utm_source": "google",
    "utm_medium": "cpc",
    "utm_campaign": "spring-sale",
    "utm_term": "running+shoes",
    "utm_content": "hero-banner"
  }'

Example Response

201 Created
{
  "status": "success",
  "data": {
    "code": "abc1234",
    "shortUrl": "https://linkshrink.dev/abc1234",
    "originalUrl": "https://example.com/landing?utm_source=google&utm_medium=cpc&utm_campaign=spring-sale&utm_term=running%2Bshoes&utm_content=hero-banner",
    "taggedUrl": "https://example.com/landing?utm_source=google&utm_medium=cpc&utm_campaign=spring-sale&utm_term=running%2Bshoes&utm_content=hero-banner",
    "utm": {
      "source": "google",
      "medium": "cpc",
      "campaign": "spring-sale",
      "term": "running+shoes",
      "content": "hero-banner"
    },
    "qrCode": "https://linkshrink.dev/api/v1/qr/abc1234.png",
    "deleteToken": "a1b2c3d4..."
  }
}

GET /expand/:code

GET /api/v1/expand/:code

Look up the original URL for a short code. Note: this increments the click counter. To preview the destination without counting a click, use /preview/:code instead.

ParamTypeDescription
codepathThe short code or custom alias
cURL
curl "https://linkshrink.dev/api/v1/expand/my-campaign"

# Response:
# {
#   "status": "success",
#   "data": {
#     "code": "my-campaign",
#     "originalUrl": "https://example.com/very/long/path?utm_source=email"
#   }
# }

GET /stats/:code

GET /api/v1/stats/:code

Get anonymous click analytics for a short link. Privacy: no IP addresses, device types, or user identifiers are stored. Only aggregate counts are recorded.

The response includes a clicksByDay array with daily click counts, a referrers array with top referring domains, and a countries array with country-level geographic breakdown — all sorted by volume descending. Country data is derived from IP at click time using a local GeoIP database; the raw IP is never stored.

If the link was created with private_stats: true, you must provide the statsToken via the token query parameter. Returns 401 if the token is missing or invalid.

ParamTypeDescription
codepathThe short code or alias
tokenquery (optional)Stats token for links with private analytics. Required if the link was created with private_stats: true.
cURL
curl "https://linkshrink.dev/api/v1/stats/my-campaign"

# Response:
# {
#   "status": "success",
#   "data": {
#     "code": "my-campaign",
#     "originalUrl": "https://example.com/...",
#     "clicks": 142,
#     "clicksByDay": [
#       { "date": "2026-03-10", "clicks": 38 },
#       { "date": "2026-03-09", "clicks": 52 },
#       { "date": "2026-03-08", "clicks": 52 }
#     ],
#     "referrers": [
#       { "domain": "twitter.com", "clicks": 67 },
#       { "domain": "linkedin.com", "clicks": 45 },
#       { "domain": "github.com", "clicks": 30 }
#     ],
#     "countries": [
#       { "country": "US", "clicks": 58 },
#       { "country": "DE", "clicks": 34 },
#       { "country": "PL", "clicks": 28 },
#       { "country": "GB", "clicks": 22 }
#     ],
#     "createdAt": "2026-02-20T12:00:00.000Z",
#     "expiresAt": "2026-02-21T12:00:00.000Z",
#     "lastClicked": "2026-02-20T14:32:11.000Z"
#   }
# }

# For links with private_stats:
curl "https://linkshrink.dev/api/v1/stats/my-campaign?token=YOUR_STATS_TOKEN"

# 401 if token missing/wrong:
# { "error": "Unauthorized", "message": "This link has private analytics..." }

GET /stats/:code/export

GET /api/v1/stats/:code/export

Export click analytics as CSV or JSON with optional date range filtering. Perfect for importing into spreadsheets, dashboards, or reporting tools. Respects private_stats tokens.

ParamTypeDescription
codepathThe short code or alias
formatquery (optional)csv or json. Default: json
fromquery (optional)Start date filter (inclusive), YYYY-MM-DD
toquery (optional)End date filter (inclusive), YYYY-MM-DD
tokenquery (optional)Stats token for links with private analytics
cURL
# Export as JSON (default)
curl "https://linkshrink.dev/api/v1/stats/my-campaign/export"

# Export as CSV (opens in Excel/Sheets)
curl "https://linkshrink.dev/api/v1/stats/my-campaign/export?format=csv" -o analytics.csv

# Filter by date range
curl "https://linkshrink.dev/api/v1/stats/my-campaign/export?from=2026-03-01&to=2026-03-14"

# JSON Response:
# {
#   "status": "success",
#   "data": {
#     "code": "my-campaign",
#     "originalUrl": "https://example.com/...",
#     "from": "2026-03-01",
#     "to": "2026-03-14",
#     "totalClicks": 142,
#     "clicksByDay": [
#       { "date": "2026-03-01", "clicks": 12 },
#       { "date": "2026-03-02", "clicks": 28 },
#       ...
#     ]
#   }
# }

# CSV Response:
# date,clicks
# 2026-03-01,12
# 2026-03-02,28
# ...

POST /stats/batch

POST /api/v1/stats/batch

Get click statistics for multiple short codes in a single request. Returns 207 Multi-Status with per-code results. Maximum 50 codes per request. Supports tokens object for links with private analytics.

FieldTypeDescription
codesstring[] (required)Array of short codes to query (max 50)
tokensobject (optional)Map of { "code": "statsToken" } for private-stats links
cURL
curl -X POST "https://linkshrink.dev/api/v1/stats/batch" \
  -H "Content-Type: application/json" \
  -d '{
    "codes": ["my-campaign", "launch-day", "newsletter-42"],
    "tokens": { "my-campaign": "optional-stats-token" }
  }'

# Response (207 Multi-Status):
# {
#   "status": "success",
#   "data": [
#     {
#       "code": "my-campaign",
#       "success": true,
#       "data": { "clicks": 142, "clicksByDay": [...], ... }
#     },
#     {
#       "code": "launch-day",
#       "success": true,
#       "data": { "clicks": 89, "clicksByDay": [...], ... }
#     },
#     {
#       "code": "newsletter-42",
#       "success": false,
#       "error": "Short URL not found or expired"
#     }
#   ]
# }

GET /qr/:code.png & /qr/:code.svg

GET /api/v1/qr/:code.png
GET /api/v1/qr/:code.svg

Returns a QR code for the short URL in PNG or SVG format. Supports custom foreground and background colors for branded QR codes. Response is cached for 24 hours.

ParamTypeDescription
codepathThe short code or alias
sizequery (optional)QR image size in pixels. Min: 64, Max: 1024, Default: 512
fgquery (optional)Foreground (dark modules) hex color, e.g. 000000 or #ff00aa. Default: #0a0a0f
bgquery (optional)Background hex color, e.g. ffffff or #00f0ff. Default: #00f0ff
cURL
# Default PNG (512×512px, cyberpunk colors)
curl "https://linkshrink.dev/api/v1/qr/my-campaign.png" --output qr.png

# Custom size (256px)
curl "https://linkshrink.dev/api/v1/qr/my-campaign.png?size=256" --output qr-small.png

# SVG format (scalable, print-ready)
curl "https://linkshrink.dev/api/v1/qr/my-campaign.svg" --output qr.svg

# Custom colors — black on white
curl "https://linkshrink.dev/api/v1/qr/my-campaign.png?fg=000000&bg=ffffff" --output qr-bw.png

# Branded colors — SVG with custom hex
curl "https://linkshrink.dev/api/v1/qr/my-campaign.svg?fg=ff00aa&bg=0a0a0f" --output qr-brand.svg

# Use directly in an <img> tag (PNG or SVG):
# <img src="https://linkshrink.dev/api/v1/qr/my-campaign.svg" alt="QR Code">

DELETE /:code

DELETE /api/v1/:code

Permanently delete a short link. Requires the delete_token returned when the link was created. After deletion the link returns 404.

Save the deleteToken from the creation response — it is the only way to delete the link. The token is stored as a hash and cannot be recovered if lost.
ParamTypeDescription
codepathThe short code or alias to delete
delete_tokenquery (required)The deletion token from the creation response
cURL
curl -X DELETE \
  "https://linkshrink.dev/api/v1/my-campaign?delete_token=a1b2c3d4e5f6..."

# Success response:
# {
#   "status": "success",
#   "message": "Short URL deleted successfully",
#   "data": { "code": "my-campaign" }
# }

# Error (missing/wrong token):
# HTTP 401
# {
#   "error": "Unauthorized",
#   "message": "A delete_token is required to delete this link",
#   "hint": "Use the delete_token returned when the link was created"
# }

POST /verify/:code

POST /api/v1/verify/:code

Verify the password for a password-protected short URL. Returns the original URL on success. For non-password-protected links, returns the URL directly without requiring a password.

Request Body

FieldTypeRequiredDescription
passwordstring required The password set when the link was created

Example

cURL
curl -X POST "https://linkshrink.dev/api/v1/verify/my-secret-link" \
  -H "Content-Type: application/json" \
  -d '{"password": "s3cret"}'

# Success — 200:
# { "status": "success", "data": { "code": "my-secret-link", "originalUrl": "https://example.com/secret" } }

# Wrong password — 401:
# { "error": "Invalid password", "message": "The provided password is incorrect." }

GET /preview/:code

GET /preview/:code

Shows the destination URL in a safety page before redirecting. Does not increment the click counter. Useful for sharing links where recipients want to verify the destination before visiting. Password-protected links show the password form instead.

Example: https://linkshrink.dev/preview/my-campaign shows a page with the destination URL and a "Proceed to site" button.

GET /unshorten

GET /api/v1/unshorten

Resolve any shortened URL to its final destination by following redirect chains. Works with any URL shortener — Bitly, TinyURL, t.co, goo.gl, and custom domains. Follows up to 10 redirect hops and returns the full redirect chain.

ParameterTypeRequiredDescription
urlstring (query)YesThe shortened URL to resolve (URL-encoded)
cURL — unshorten a Bitly link
curl "https://linkshrink.dev/api/v1/unshorten?url=https%3A%2F%2Fbit.ly%2Fexample"
Response — 200 OK
{
  "status": "success",
  "data": {
    "originalUrl": "https://bit.ly/example",
    "destinationUrl": "https://example.com/final-page",
    "redirectChain": [
      { "url": "https://bit.ly/example", "statusCode": 301 }
    ],
    "redirectCount": 1
  }
}

Response Fields

FieldTypeDescription
originalUrlstringThe input URL you provided
destinationUrlstringThe final destination URL after following all redirects
redirectChainarrayEach redirect hop with url and statusCode
redirectCountnumberNumber of redirects followed

Response Format

All API responses are JSON. Successful responses include "status": "success". Error responses include "error" and "message" fields.

Success — 201 Created
{
  "status": "success",
  "data": { ... }
}
Error — 400 Bad Request
{
  "error": "Failed to shorten URL",
  "message": "This alias is already taken"
}

Error Codes

StatusMeaning
400Bad Request — Missing or invalid input (URL, alias format, TTL out of range)
401Unauthorized — Missing or invalid delete_token or statsToken
404Not Found — Short code doesn't exist or has expired
429Too Many Requests — Rate limit exceeded (see below)
500Internal Server Error — Something went wrong on our side

Rate Limits

Rate limits are per IP address per minute.

EndpointLimitWindow
POST /shorten20 requests1 minute
POST /shorten/bulk5 requests1 minute
GET /stats/:code, GET /stats/:code/export, POST /stats/batch60 requests1 minute
GET /expand/:code, /qr/:code.png, /qr/:code.svg, DELETE /:code100 requests1 minute
GET /unshorten30 requests1 minute

When rate limited the API returns 429 with a standard Retry-After HTTP header indicating the number of seconds to wait before retrying. Wait for the specified duration and retry.

cURL Examples

terminal
# Shorten (with custom alias and 7-day TTL)
curl -X POST "https://linkshrink.dev/api/v1/shorten" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com","customAlias":"my-link","ttl_seconds":604800}'

# Get click stats
curl "https://linkshrink.dev/api/v1/stats/my-link"

# Download QR code (PNG, 512px)
curl "https://linkshrink.dev/api/v1/qr/my-link.png" --output qr.png

# Download SVG QR with custom brand colors
curl "https://linkshrink.dev/api/v1/qr/my-link.svg?fg=ff00aa&bg=ffffff" --output qr.svg

# Delete (requires deleteToken from creation response)
curl -X DELETE "https://linkshrink.dev/api/v1/my-link?delete_token=<token>"

# Bulk shorten 3 URLs
curl -X POST "https://linkshrink.dev/api/v1/shorten/bulk" \
  -H "Content-Type: application/json" \
  -d '{"urls":["https://example.com/1","https://example.com/2","https://example.com/3"]}'

JavaScript

app.js
// Shorten a URL
const res = await fetch('https://linkshrink.dev/api/v1/shorten', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    url: 'https://example.com/very/long/path',
    customAlias: 'my-link',   // optional
    ttl_seconds: 86400         // optional — 1 day
  })
});
const { data } = await res.json();

console.log(data.shortUrl);    // https://linkshrink.dev/my-link
console.log(data.previewUrl);  // https://linkshrink.dev/preview/my-link
console.log(data.qrCode);      // QR code PNG URL
console.log(data.deleteToken); // Save this!

// Get click count
const stats = await fetch(`https://linkshrink.dev/api/v1/stats/${data.code}`);
const { data: s } = await stats.json();
console.log(s.clicks); // 42

// Delete the link
await fetch(
  `https://linkshrink.dev/api/v1/${data.code}?delete_token=${data.deleteToken}`,
  { method: 'DELETE' }
);

Python

main.py
import requests

BASE = "https://linkshrink.dev/api/v1"

# Shorten a URL
response = requests.post(f"{BASE}/shorten", json={
    "url": "https://example.com/very/long/path",
    "customAlias": "my-link",  # optional
    "ttl_seconds": 86400        # optional — 1 day
})
data = response.json()["data"]

print(data["shortUrl"])    # https://linkshrink.dev/my-link
print(data["deleteToken"]) # save this

# Get click stats
stats = requests.get(f"{BASE}/stats/{data['code']}").json()["data"]
print(stats["clicks"])  # 42

# Download QR code (PNG)
qr = requests.get(f"{BASE}/qr/{data['code']}.png?size=256")
with open("qr.png", "wb") as f:
    f.write(qr.content)

# Download branded SVG QR code
svg = requests.get(f"{BASE}/qr/{data['code']}.svg?fg=ff00aa&bg=ffffff")
with open("qr.svg", "w") as f:
    f.write(svg.text)

# Delete the link
requests.delete(
    f"{BASE}/{data['code']}",
    params={"delete_token": data["deleteToken"]}
)

# Bulk shorten
bulk = requests.post(f"{BASE}/shorten/bulk", json={
    "urls": ["https://example.com/1", "https://example.com/2"],
    "ttl_seconds": 604800
}).json()
for item in bulk["data"]:
    print(item["data"]["shortUrl"] if item["success"] else item["error"])

MCP Integration

LinkShrink supports the Model Context Protocol (MCP), letting AI assistants like Claude, Cursor, and VS Code Copilot use LinkShrink tools directly from the chat window.

Installation

claude_desktop_config.json
{
  "mcpServers": {
    "linkshrink": {
      "command": "npx",
      "args": ["-y", "linkshrink-mcp"]
    }
  }
}

Available Tools

ToolDescriptionParameters
shorten_url Shorten a long URL url (required), customAlias (optional)
expand_url Expand a short code to its original URL code (required)
url_stats Get anonymous click statistics code (required)
Part of the SoftVoyagers Ecosystem