← n2n.to

HTTP API for URL shortening

Quick start

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 /api/shorten

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.

Encoding matters most when the link carries a # fragment: left unencoded, the browser strips everything after it and the server never sees it. Encoded, the fragment round-trips and the short link redirects to it intact.

/api is an alias for /api/shorten and behaves identically.

POST /api/shorten

POST https://n2n.to/api/shorten
Content-Type: application/json

{"url": "https://example.com/some/long/path"}

Response (both methods)

{
  "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.

Deduplication

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.

Rate limits

WindowLimitScope
1 minute20 requestsburst protection, per IP
24 hours (sliding window)100 requestsdaily 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:

Error codes

StatusConditionBody
400Missing URL or longer than 2048 characters{"error": "URL is required"}
400Invalid URL or non-http(s) scheme{"error": "Invalid URL"}
404Unknown endpoint under /api{"error": "Unknown endpoint", "endpoints": [...]}
429Exceeded 20/min or 100/day limit{"error": "...", "retryAfter": N}
500Internal server error{"error": "Failed to shorten URL"}

CORS

Every path under /api 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, and even a mistyped endpoint answers with CORS headers so the browser reports a readable 404 instead of an opaque CORS failure.

WebMCP

In a browser that supports WebMCP, this site registers a shorten_url tool on document.modelContext. An AI agent driving the page can call it directly instead of filling in the form. It takes a single url argument and returns the short link; the same URL always yields the same link.

The tool is same-origin only — no exposedTo origins are granted — and it goes through the same endpoint and the same rate limits described above. It never writes to the clipboard, unlike the form on the home page.

Chrome ships this behind an origin trial (Chrome 149-156). Browsers without it are unaffected: the script feature-detects and does nothing.

Examples

curl (GET)

curl "https://n2n.to/api/shorten?url=https%3A%2F%2Fexample.com"

curl (POST)

curl -X POST https://n2n.to/api/shorten \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com"}'

JavaScript (client-side app in the browser)

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);

Python

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"])