Developer docs
API Reference
Create, manage, and redirect short links programmatically.
Base URL
All examples use $BASE as your deployment origin — e.g. https://your-app.up.railway.app or http://localhost:3000.
BASE="https://your-app.up.railway.app"Authentication
Every /api/links route requires one of the following. Constant-time comparison, and the cookie is httpOnly + Secure.
Bearer token
Authorization: Bearer $AUTH_TOKENAPI key header
x-api-key: $AUTH_TOKENThe dashboard signs in via POST /api/auth/login, which sets the session cookie. /r/:slug and /api/health are public.
Endpoints
/api/health PublicHealth check and database status.
curl "$BASE/api/health"Response 200
| ok | boolean |
| database | "connected" | "not_configured" |
/api/auth/login PublicExchange the API token for an httpOnly session cookie (used by the dashboard sign-in).
curl -X POST "$BASE/api/auth/login" \
-H "Content-Type: application/json" \
-d '{"token":"$AUTH_TOKEN"}'Body
| token | string — the shared AUTH_TOKEN |
Responses
| 200 | Sets auth_token cookie |
| 401 | Invalid token |
| 500 | AUTH_TOKEN not configured on the server |
/api/auth/logout PublicClears the session cookie.
curl -X POST "$BASE/api/auth/logout"/api/links Requires authList links. `search` matches slug or destination (case-insensitive).
curl -H "Authorization: Bearer $AUTH_TOKEN" \
"$BASE/api/links?search=launch&limit=50&offset=0"Query params
| search | string — optional, matches slug OR destination |
| limit | number — default 50, max 200 |
| offset | number — default 0 |
Response 200
| links | Link[] |
| total | number — count matching the search filter |
| totalClicks | number — sum of clicks across ALL links |
/api/links Requires authCreate a short link. Omit `slug` to auto-generate a 6-char random one.
curl -X POST "$BASE/api/links" \
-H "Authorization: Bearer $AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{"slug":"launch","destination":"https://example.com"}'Body
| destination | string (required) — http(s) URL |
| slug | string (optional) — /^[a-z0-9-]{1,64}$/ |
Responses
| 201 | { link: Link } |
| 400 | Invalid destination or slug |
| 409 | slug already exists |
/api/links/:id Requires authGet a single link by UUID.
curl -H "Authorization: Bearer $AUTH_TOKEN" \
"$BASE/api/links/<id>"Responses
| 200 | { link: Link } |
| 404 | Link not found |
/api/links/:id Requires authUpdate a link's slug and/or destination.
curl -X PATCH "$BASE/api/links/<id>" \
-H "Authorization: Bearer $AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{"destination":"https://example.com/new"}'Body
| destination | string (optional) — http(s) URL |
| slug | string (optional) — /^[a-z0-9-]{1,64}$/ |
Responses
| 200 | { link: Link } |
| 400 | No fields / invalid value |
| 404 | Link not found |
| 409 | slug already exists |
/api/links/:id Requires authDelete a link. Returns 204 with an empty body.
curl -X DELETE "$BASE/api/links/<id>" \
-H "Authorization: Bearer $AUTH_TOKEN"Responses
| 204 | Deleted |
| 404 | Link not found |
/r/:slug PublicRedirect to the link's destination and atomically increment its click count.
curl -i "$BASE/r/launch"Responses
| 302 | Redirects to destination |
| 404 | Short link not found |
Types
type Link = {
id: string; // UUID
slug: string; // lowercase [a-z0-9-], max 64
destination: string; // http(s) URL
clicks: number;
created_at: string; // ISO 8601
updated_at: string; // ISO 8601
};type LinksResponse = {
links: Link[];
total: number;
totalClicks: number;
};
type ApiError = { error: string };