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_TOKEN

API key header

x-api-key: $AUTH_TOKEN

The dashboard signs in via POST /api/auth/login, which sets the session cookie. /r/:slug and /api/health are public.

Endpoints

GET/api/health Public

Health check and database status.

curl "$BASE/api/health"

Response 200

okboolean
database"connected" | "not_configured"
POST/api/auth/login Public

Exchange 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

tokenstring — the shared AUTH_TOKEN

Responses

200Sets auth_token cookie
401Invalid token
500AUTH_TOKEN not configured on the server
POST/api/auth/logout Public

Clears the session cookie.

curl -X POST "$BASE/api/auth/logout"
GET/api/links Requires auth

List 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

searchstring — optional, matches slug OR destination
limitnumber — default 50, max 200
offsetnumber — default 0

Response 200

linksLink[]
totalnumber — count matching the search filter
totalClicksnumber — sum of clicks across ALL links
POST/api/links Requires auth

Create 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

destinationstring (required) — http(s) URL
slugstring (optional) — /^[a-z0-9-]{1,64}$/

Responses

201{ link: Link }
400Invalid destination or slug
409slug already exists
GET/api/links/:id Requires auth

Get a single link by UUID.

curl -H "Authorization: Bearer $AUTH_TOKEN" \
  "$BASE/api/links/<id>"

Responses

200{ link: Link }
404Link not found
PATCH/api/links/:id Requires auth

Update 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

destinationstring (optional) — http(s) URL
slugstring (optional) — /^[a-z0-9-]{1,64}$/

Responses

200{ link: Link }
400No fields / invalid value
404Link not found
409slug already exists
DELETE/api/links/:id Requires auth

Delete a link. Returns 204 with an empty body.

curl -X DELETE "$BASE/api/links/<id>" \
  -H "Authorization: Bearer $AUTH_TOKEN"

Responses

204Deleted
404Link not found
GET/r/:slug Public

Redirect to the link's destination and atomically increment its click count.

curl -i "$BASE/r/launch"

Responses

302Redirects to destination
404Short 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 };