HTTP API for URL shortening
To create a short link, call /api/shorten with either GET (handy for calling directly from browser-side JS on another domain, CORS is enabled) or POST with a JSON body.
GET https://n2n.to/api/shorten?url=https%3A%2F%2Fexample.com%2Fsome%2Flong%2Fpath
The url query parameter must be URL-encoded (encodeURIComponent). Only http and https schemes are allowed, max length 2048 characters.
POST https://n2n.to/api/shorten
Content-Type: application/json
{"url": "https://example.com/some/long/path"}
{
"shortUrl": "https://n2n.to/fAb1",
"code": "Ab1",
"originalUrl": "https://example.com/some/long/path"
}
The short link https://n2n.to/f<code> responds with a 301 redirect to the original URL.
If the exact same URL was already shortened, the API returns the existing code instead of creating a new row. This makes repeated calls (e.g. accidental double form submits, retries, prefetching) safe and keeps the database from filling up with duplicates — which also makes the GET endpoint practically idempotent even though it technically creates data.
| Window | Limit | Scope |
|---|---|---|
| 1 minute | 20 requests | burst protection, per IP |
| 24 hours (sliding window) | 100 requests | daily quota, per IP |
Both limits apply only to link creation (GET/POST /api/shorten). Redirects (/f<code>) are not rate-limited.
Exceeding a limit returns HTTP 429 with a Retry-After header (seconds until reset). Invalid requests (400) do not consume quota. Responses that pass validation include:
X-RateLimit-Limit — the daily request limitX-RateLimit-Remaining — requests left in the current windowX-RateLimit-Reset — unix timestamp (seconds) when the limit resets| Status | Condition | Body |
|---|---|---|
| 400 | Missing URL or longer than 2048 characters | {"error": "URL is required"} |
| 400 | Invalid URL or non-http(s) scheme | {"error": "Invalid URL"} |
| 429 | Exceeded 20/min or 100/day limit | {"error": "...", "retryAfter": N} |
| 500 | Internal server error | {"error": "Failed to shorten URL"} |
/api/shorten sends Access-Control-Allow-Origin: *, so it can be called with fetch() directly from a browser on any domain — no backend proxy needed. Preflight OPTIONS requests are handled correctly.
curl "https://n2n.to/api/shorten?url=https%3A%2F%2Fexample.com"
curl -X POST https://n2n.to/api/shorten \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com"}'
This is the exact use case GET + CORS were added for: calling the API straight from a third-party site's frontend in response to user input.
async function shorten(longUrl) {
const res = await fetch(
'https://n2n.to/api/shorten?url=' + encodeURIComponent(longUrl)
);
if (!res.ok) {
const err = await res.json();
throw new Error(err.error);
}
const data = await res.json();
return data.shortUrl; // https://n2n.to/fAb1
}
shorten('https://example.com/very/long/link').then(console.log);
import requests
resp = requests.get(
"https://n2n.to/api/shorten",
params={"url": "https://example.com/very/long/link"},
)
resp.raise_for_status()
print(resp.json()["shortUrl"])