Developers

ShrinkRL API

Shorten URLs, set custom slugs, read click analytics, and create links in bulk — over a simple JSON REST API. No API key. No auth. Just call it.

Base URL
https://api.shrinkrl.com
Format
JSON in, JSON out
Rate limit
1,000 requests/hour per IP
Short links
https://www.shrinkrl.com/<slug>

Overview

Every endpoint is open — no headers or keys required. A bulk request counts as a single call against the rate limit, no matter how many URLs it carries. High-volume partners can request a higher per-IP limit.

Heads up — metadata is async. When you create a link, the destination’s title/description/ image (used for rich social previews) are scraped in the background and are usually ready 1–3 seconds later. The create response returns og_image: null and metadata: {}; fetch the link again with GET /api/v1/urls/:slug a moment later to get them.

Create a short link

POST/api/v1/urls

Body — wrap fields in a url object. slug (custom, 3–20 chars of a–z, A–Z, 0–9, -_) and expires_at (ISO-8601) are optional.

Request
POST /api/v1/urls
Content-Type: application/json

{ "url": { "original_url": "https://example.com/a/very/long/page" } }
201 Created
{
  "short_url": "https://www.shrinkrl.com/kDgxDc",
  "original_url": "https://example.com/a/very/long/page",
  "og_image": null,
  "slug": "kDgxDc",
  "metadata": {}
}

Custom-slug collisions are idempotent

  • Same original_url200 with the existing link (safe to retry — no duplicate created).
  • Different original_url409 with the existing link in the body plus errors.

Read a link & its stats

GET/api/v1/urls/:slug

Returns the link plus metadata and click count. Does not count as a click — use this for dashboards and previews.

200 OK
{
  "short_url": "https://www.shrinkrl.com/kDgxDc",
  "original_url": "https://example.com/a/very/long/page",
  "og_image": "https://.../preview.png",
  "click_count": 42,
  "created_at": "2026-05-29T08:29:10.409Z",
  "slug": "kDgxDc",
  "metadata": {
    "title": "Example page title",
    "description": "A short description of the destination.",
    "image": "https://.../preview.png",
    "site_name": "Example.com"
  }
}
GET/api/v1/urls/:slug/data

Same payload, but counts as a click. Use it when a user actually opens the link.

DELETE/api/v1/urls/:slug

Deletes the link. Returns 204 No Content.

Bulk operations

POST/api/v1/urls/bulk_create

≤ 500 URLs are created immediately and returned in the response. > 500 are processed in the background and you get a bulk_id to poll.

Request
{ "urls": [
  { "original_url": "https://a.com" },
  { "original_url": "https://b.com", "slug": "launch-b" }
] }
≤500 → 200 Completed
{
  "status": "completed",
  "total": 2, "successful": 2, "failed": 0,
  "urls": [ { "short_url": "...", "slug": "...", ... } ],
  "errors": []
}
>500 → 202 Accepted
{
  "bulk_id": "uuid",
  "status": "processing",
  "check_status_url": "/api/v1/urls/bulk/uuid/status"
}
GET/api/v1/urls/bulk/:bulk_id/status

Poll until status is completed, then read urls / errors.

POST/api/v1/urls/bulk_data

Read up to 1,000 links at once.

Request
{ "slugs": ["abc123", "def456"] }

Redirects & health

GET/r/:slug

Server-side 302 redirect to the original URL (the click is tracked automatically). Sharing the short link https://www.shrinkrl.com/:slug works too.

GET/health

Returns { "status": "OK" } when the API is healthy.

Code examples

curl
curl -s -X POST https://api.shrinkrl.com/api/v1/urls \
  -H "Content-Type: application/json" \
  -d '{"url":{"original_url":"https://example.com"}}'
JavaScript / TypeScript
const API = "https://api.shrinkrl.com";

const res = await fetch(`${API}/api/v1/urls`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ url: { original_url: "https://example.com" } }),
});
const { short_url } = await res.json();

// metadata is async — fetch it a moment later (non-tracking endpoint):
const link = await (await fetch(`${API}/api/v1/urls/${slug}`)).json();
Python
import requests
API = "https://api.shrinkrl.com"

r = requests.post(f"{API}/api/v1/urls",
                  json={"url": {"original_url": "https://example.com"}})
r.raise_for_status()
short_url = r.json()["short_url"]