Kin Shortener API
A minimal, public HTTP API for creating and resolving short links. One endpoint to shorten, one redirect to resolve — served from Cloudflare’s edge with no keys to manage.
https://kinshortner-api.gamedevinc6.workers.dev
https://kinshortner.pages.dev
Request bodies and success responses are JSON encoded in UTF-8. Every API response includes permissive CORS headers, so browser-based clients can call the API directly.
Copy the complete documentation as Markdown — API contract, Worker source, routing rules, and deployment steps — then hand it to an AI assistant to rebuild this stack.
Quick start #
Create your first short link in three steps. No installation or authentication is required.
-
Send the destination URL.
Make a
POSTrequest to/api/shortenwith a JSON body containing theurlfield. -
Read the short link.
A successful request returns
200 OKand a JSON object with ashortUrlfield. -
Share it.
Requests to the short link respond with
302 Foundand aLocationheader pointing at the destination.
curl -X POST "https://kinshortner-api.gamedevinc6.workers.dev/api/shorten" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/handbook/chapter-1"}'
const response = await fetch("https://kinshortner-api.gamedevinc6.workers.dev/api/shorten", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ url: "https://example.com/handbook/chapter-1" })
});
const { shortUrl } = await response.json();
console.log(shortUrl); // "https://kinshortner.pages.dev/a1b2c3"
import requests
response = requests.post(
"https://kinshortner-api.gamedevinc6.workers.dev/api/shorten",
json={"url": "https://example.com/handbook/chapter-1"},
timeout=20,
)
response.raise_for_status()
print(response.json()["shortUrl"])
Authentication #
The Kin Shortener API is public. It does not require an API key, OAuth token, or account, and there is nothing to configure before your first request.
Authorization headers are ignored. Do not send secrets to the API, and avoid
shortening URLs that contain personal or confidential information, since short links are
publicly resolvable.
Conventions #
- All traffic uses HTTPS; every route is available on the base URL shown above.
- Request and response bodies are UTF-8 JSON, except
404responses, which are plain text. - Send
Content-Type: application/jsonwith everyPOSTrequest. - Unknown fields in a request body are ignored; only
urlis read. - Short codes are six characters from
a–zand0–9, generated once per request, and are case-sensitive. - Each successful
POSTcreates a new code, even when the same URL was shortened before.
Create a short link #
Creates a new short code for a destination URL and returns the public short link. The link is
live immediately and resolves with a 302 redirect.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
url |
string | required |
The absolute destination URL, including the http:// or
https:// scheme. The value is stored as provided and is not normalized.
|
The API does not prepend a scheme or check that the destination is reachable. A value such as
example.com/page is accepted but produces a link that cannot resolve. Always send
a fully qualified URL, and validate input in your client before calling the API.
Request example
curl -X POST "https://kinshortner-api.gamedevinc6.workers.dev/api/shorten" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/handbook/chapter-1"}'
const response = await fetch("https://kinshortner-api.gamedevinc6.workers.dev/api/shorten", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
url: "https://example.com/handbook/chapter-1"
})
});
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
const { shortUrl } = await response.json();
console.log(shortUrl);
import requests
response = requests.post(
"https://kinshortner-api.gamedevinc6.workers.dev/api/shorten",
json={"url": "https://example.com/handbook/chapter-1"},
timeout=20,
)
response.raise_for_status()
short_url = response.json()["shortUrl"]
print(short_url)
Response
{
"shortUrl": "https://kinshortner.pages.dev/a1b2c3"
}
| Field | Type | Description |
|---|---|---|
shortUrl |
string | The absolute public short link. Six-character code appended to the short domain. |
Error responses use 400 Bad Request with a JSON body. See
Errors
for every case.
Try it
Send a real request from your browser. Responses are returned live from the production API.
Send a request to see a live response.
Resolve a code #
Resolves a short code with a standard 302 Found redirect. Browsers follow the
redirect automatically; API clients can read the destination from the Location
header. HEAD requests are supported.
| Path parameter | Type | Required | Description |
|---|---|---|---|
code |
string | required | The six-character short code, case-sensitive, with no trailing slash. |
Behaviour
- Responds with
302 Foundand aLocationheader containing the destination. - Codes are case-sensitive:
a1b2c3andA1B2C3are different. - A trailing slash (
/a1b2c3/) is not a valid code and returns404. - Query strings on the short link are not forwarded to the destination.
- Links expire 24 hours after creation; expired codes resolve as
404. - Unknown or expired codes return
404with a plain-text body.
Request example
curl -I "https://kinshortner-api.gamedevinc6.workers.dev/a1b2c3"
const response = await fetch(
"https://kinshortner-api.gamedevinc6.workers.dev/a1b2c3",
{ redirect: "manual" }
);
console.log(response.status); // 302
console.log(response.headers.get("location"));
import requests
response = requests.head(
"https://kinshortner-api.gamedevinc6.workers.dev/a1b2c3",
allow_redirects=False,
timeout=20,
)
print(response.status_code) # 302
print(response.headers["Location"])
Redirect response
HTTP/1.1 302 Found
Location: https://example.com/handbook/chapter-1
Content-Length: 0
Not found response
HTTP/1.1 404 Not Found
Content-Type: text/plain;charset=UTF-8
Kin Shortener - Link not found or expired, Links expire after 24 hours...
Errors #
APIs fail in predictable ways. The table below lists every response the API returns for invalid requests or missing links.
| Status | Content type | Body | Cause |
|---|---|---|---|
| 400 | application/json | {"error":"Missing URL"} |
Valid JSON was sent, but url was absent or empty. |
| 400 | application/json | {"error":"Invalid JSON payload"} |
The request body could not be parsed as JSON. |
| 404 | text/plain | Kin Shortener - Link not found or expired, Links expire after 24 hours... |
No link exists for the requested code, or its 24-hour lifetime has elapsed. |
| 5xx | text/plain | edge error page | An unexpected failure occurred at the edge. Retry with backoff. |
Error payloads
{
"error": "Missing URL"
}
{
"error": "Invalid JSON payload"
}
Check response.ok before reading a body. On 400 the body is JSON;
on 404 it is plain text. Branch on the Content-Type header instead
of guessing the shape.
CORS #
The API is callable from browsers without a proxy. Responses from /api/shorten —
including 400 errors — and preflight responses include the following headers.
| Header | Value |
|---|---|
Access-Control-Allow-Origin |
* |
Access-Control-Allow-Headers |
Content-Type |
Access-Control-Allow-Methods |
GET, POST, OPTIONS |
Preflight (OPTIONS) requests return 200 OK with an empty body.
Credentialed requests are not supported: the API never issues or reads cookies, so send
credentials: "omit" (the fetch default) and no Authorization header.
Redirect and 404 responses are served without CORS headers because they are meant
for top-level navigation. A browser fetch to a missing or expired code therefore
rejects as a network error; server-side clients still receive the plain-text body.
Limits #
- No API keys, accounts, or per-user quotas exist in v1.
- The API does not currently expose analytics, listing, or deletion endpoints.
- If a request fails with a
5xxstatus, retry with exponential backoff rather than immediately resending. - Short links should be treated as public: anyone with a code can resolve it.
- Links expire 24 hours after creation. There is no renewal or extension endpoint.
Reference implementation #
Kin Shortener runs as a single Cloudflare Worker backed by Workers KV. The routing core from the v1 Worker is reproduced below — small enough to audit in one pass, and reusable if you want to self-host the same API contract. The deployed Worker adds a 24-hour link lifetime and the longer not-found message documented above.
| Component | Implementation |
|---|---|
| Runtime | Cloudflare Workers, ES module syntax |
| Storage | Workers KV namespace bound as URL_MAP |
| Short domain | Set by the shortDomain constant inside the Worker |
| Routing | OPTIONS preflight · POST /api/shorten · GET /{code} |
Workers KV is eventually consistent, so a newly created link can take up to about a minute
to resolve in every region. Codes are generated with Math.random().toString(36),
which is not meant to be unguessable — never treat a code as an access token. In production,
each entry is written with a 24-hour expiration, so links stop resolving after that window.
Worker source
export default {
async fetch(request, env) {
const url = new URL(request.url);
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type",
};
if (request.method === "OPTIONS") {
return new Response(null, { headers: corsHeaders });
}
// 1. POST /api/shorten endpoint
if (request.method === "POST" && url.pathname === "/api/shorten") {
try {
const body = await request.json();
if (!body.url) {
return new Response(JSON.stringify({ error: "Missing URL" }), {
status: 400,
headers: { ...corsHeaders, "Content-Type": "application/json" }
});
}
const code = Math.random().toString(36).substring(2, 8);
await env.URL_MAP.put(code, body.url);
// HERE: Set your short URL domain to your Cloudflare Pages domain
const shortDomain = "https://kinshortner.pages.dev";
return new Response(JSON.stringify({ shortUrl: `${shortDomain}/${code}` }), {
headers: { ...corsHeaders, "Content-Type": "application/json" }
});
} catch (err) {
return new Response(JSON.stringify({ error: "Invalid JSON payload" }), {
status: 400,
headers: { ...corsHeaders, "Content-Type": "application/json" }
});
}
}
// 2. GET /:code endpoint (Redirects to original link)
const code = url.pathname.slice(1);
if (code) {
const longUrl = await env.URL_MAP.get(code);
if (longUrl) {
return Response.redirect(longUrl, 302);
}
}
return new Response("Kin Shortener - Link not found", { status: 404 });
}
};
To deploy your own instance, change shortDomain to your Pages domain, bind a KV
namespace named URL_MAP in your Wrangler configuration, and redeploy. No client
changes are required as long as the routes and response shapes stay the same.
Changelog #
POST /api/shorten creates links ·
GET /{code} redirects ·
CORS enabled for browser clients.
Links expire one day after creation and return the updated not-found message when resolved afterwards.