Shrtr
Sign in Sign up
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

A machine-readable OpenAPI 3.1 specification is available at https://shrtr.top/openapi.json — import it into Postman, Swagger UI, or your code generator of choice.

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-09-12T05:40:29+00:00"
}

Rate limits

POST /api/v1/shorten carries rate-limit headers for its per-minute budget so you can back off proactively. Every 429 carries Retry-After, which is the authoritative signal to retry from — and the one you must actually honour, see below.

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

The daily ceiling counts requests, not successful links — a rejected destination or a taken alias still spends one. It is independent of the web form's budget, so a scripted integration and a browser sharing one address never compete. Need more than that a day? See the plans — that is what accounts are for.

Response headers

X-RateLimit-Limit:     30          # the per-minute budget, and only that one
X-RateLimit-Remaining: 29
X-RateLimit-Reset:     1776935000
Retry-After:           47          # only on 429

The X-RateLimit-* trio always describes the per-minute 30/min budget. A 429 raised by a different bucket — the daily ceiling, the alias cap, or the per-destination limiter — carries Retry-After alone (changed 2026-08-04; those three previously echoed the per-minute numbers, which describe a budget that is not what refused). Drive retries from Retry-After, with one caveat worth coding for: on the per-destination limiter it can be many hours, and that bucket is keyed on the destination domain, so a different destination is very likely accepted immediately. Read the detail field to tell the cases apart.

On the per-minute and per-hour buckets, IPv6 callers are grouped per /64 rather than per /128, matching the web form's policy. The daily ceiling keys the full address.

Honouring Retry-After is not merely good manners. Once the daily ceiling is reached every further attempt answers 429, and a client that keeps retrying through them — even at a modest fixed interval of a few seconds — is eventually blocked at the network layer, which returns no HTTP response at all. So an integration that suddenly sees connection failures rather than 429s has usually been retrying too hard. Wait out the interval the header gives you and this never applies.

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.

Frequently asked questions

How is the Shrtr API versioned?

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

What HTTP status codes does the API return?

201 on a created short link, 200 on stats and health, 400 for malformed input, 404 for unknown codes, 409 for taken aliases, 422 for validation failures, 429 when rate-limited, and 503 on rare short-code collisions.

Can I call the API directly from a browser?

Yes. Access-Control-Allow-Origin is open on /api/v1/* and OPTIONS preflight is answered with HTTP 204 and a one-day Max-Age. The endpoints are anonymous, so no cookies or credentials are involved.