Shrtr
Free · No API key · CORS-enabled

Shrtr API reference

A small JSON API for programmatic URL shortening. No signup, no API key, permissive CORS. Rate-limited per IP. Errors follow RFC 7807.

Base URL

https://shrtr.top/api/v1

Endpoints

POST /api/v1/shorten

Create a short link. Body: {"url": "…", "alias": "optional"}. Returns 201 with the created link representation.

GET /api/v1/stats/{code}

Fetch aggregate stats for a short link (clicks_count, last_clicked_at, created_at, enabled). Never returns per-click data.

GET /api/v1/health

Uptime probe. Returns {"status": "ok"}. Polling-friendly with Cache-Control: no-store.

Shorten a URL

Send a JSON POST with Content-Type: application/json. The alias field is optional.

curl

curl -sS -X POST 'https://shrtr.top/api/v1/shorten' \
  -H 'Content-Type: application/json' \
  -d '{"url":"https://example.com/some/long/path"}'

JavaScript (fetch)

const res = await fetch('https://shrtr.top/api/v1/shorten', {
  method: 'POST',
  headers: {'Content-Type': 'application/json'},
  body: JSON.stringify({url: 'https://example.com/some/long/path'}),
});
const data = await res.json();
console.log(data.short_url);

Python (requests)

import requests
res = requests.post(
    'https://shrtr.top/api/v1/shorten',
    json={'url': 'https://example.com/some/long/path'},
    timeout=5,
)
res.raise_for_status()
print(res.json()['short_url'])

Go (net/http)

body := strings.NewReader(`{"url":"https://example.com/some/long/path"}`)
req, _ := http.NewRequest("POST", "https://shrtr.top/api/v1/shorten", body)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)

Success response (201)

{
  "code": "aBc1234",
  "short_url": "https://shrtr.top/s/aBc1234",
  "original_url": "https://example.com/some/long/path",
  "created_at": "2026-04-23T12:03:18+00:00"
}

Rate limits

Every response (2xx and 4xx) carries rate-limit headers so you can back off proactively.

EndpointLimitWindow
POST /api/v1/shorten30 requestsper minute per IP
POST /api/v1/shorten with alias10 requestsper hour per IP
GET /api/v1/stats/{code}unlimited
GET /api/v1/healthunlimited

Response headers

X-RateLimit-Limit:     30
X-RateLimit-Remaining: 29
X-RateLimit-Reset:     1776935000
Retry-After:           47        # only on 429

IPv6 callers are bucketed per /64, not per /128, matching the web form's policy.

Errors (RFC 7807 problem+json)

Every non-2xx response has Content-Type: application/problem+json and the following shape:

{
  "type":   "about:blank",
  "title":  "Unprocessable Entity",
  "status": 422,
  "detail": "The URL scheme must be http or https.",
  "errors": {"url": ["This is not a valid URL."]}
}
StatusWhen
400malformed JSON, missing Content-Type: application/json, oversized body
404GET /api/v1/stats/{code} for a code that does not exist
409alias already taken
422validation failure (invalid URL, alias shape, reserved word)
429rate limit exceeded — respect Retry-After
503rare short-code collision; retry once with a different body

CORS

Access-Control-Allow-Origin: * on every /api/v1/* response. The API is anonymous and there are no cookies, so this is safe. You can call the endpoint directly from a browser without a proxy.

Preflight OPTIONS is handled without running the route handler and returns 204 with a Max-Age of one day.

Stability

The /api/v1/ prefix is stable. Breaking changes will ship under /api/v2/. Additive changes (new optional fields, new endpoints) may land at any time within v1 — parse JSON leniently.