Skip to content

HTTP Routes

lnkify exposes several non-GraphQL HTTP endpoints served by the Express server.

GET /:slug — Redirect Handler

The primary redirect endpoint. When a visitor opens a shortlink URL, this handler:

  1. Looks up the slug — served from an in-memory slug→target cache when warm, otherwise from the database (the result is then cached)
  2. Validates that the target is an http(s):// URL (else 400 Invalid redirect target)
  3. If tracking is enabled: records the visitor's IP, user agent, and increments the hit count (asynchronously, non-blocking — fired on both cache hits and misses)
  4. Issues a 302 Found redirect to the target URL
  5. If the slug is not found: returns 404 with Target not found

Client IP Resolution

The client IP — used for both hit tracking and rate limiting — comes from a single trusted source: Express's req.ip. Behind a reverse proxy, set TRUST_PROXY=true so Express derives req.ip from the proxy's X-Forwarded-For and ignores spoofed values from untrusted hops. The handler no longer reads X-Forwarded-For/X-Real-IP directly. Stored IPs can be anonymized with IP_ANONYMIZE=true.

Rate Limiting

Redirects are rate-limited per IP at 600 req/min (configurable via RATELIMIT_REDIRECT_PER_MIN).

GET /healthz — Liveness

Unauthenticated liveness probe. Always returns 200 with {"status":"ok"} as long as the process is running — it touches no dependencies. Excluded from the redirect rate-limit bucket and the slug catch-all. Used by the Docker Compose server healthcheck.

GET /readyz — Readiness

Unauthenticated readiness probe. Runs a trivial SELECT 1 against Postgres and returns 200 {"status":"ok"} when the database is reachable, or 503 {"status":"error"} otherwise. Use this (not /healthz) to gate traffic on database availability.

GET /llms.txt — AI Discovery

Returns an AI agent discovery file describing available tools and endpoints.

  • Content-Type: text/markdown; charset=utf-8
  • Cache-Control: public, max-age=3600
  • Lists the MCP endpoint, tool names with descriptions, and the GraphQL endpoint
  • Links to docs for full reference

GET /llms-full.txt — Expanded AI Reference

Extended version of /llms.txt with additional details:

  • Same content type and caching headers
  • Full tool input schemas with field types and constraints
  • Worked examples for each tool

GET /:slug/qr.:format — QR Code

Returns a QR code image for a shortlink. :format is svg (default) or png. Optional query params control size and error-correction level. Rate-limited per IP. Used by the dashboard and the MCP get_qr_code tool.

GET /bio/:slug — Bio Page

Renders a public link-in-bio page (server-rendered HTML) for the given bio slug. Rate-limited per IP. See the Bio Pages guide.

POST /webhooks/stripe — Stripe Webhook

Receiver for Stripe billing events. Mounted with a raw body parser so Stripe's signature can be verified against STRIPE_WEBHOOK_SECRET. Only relevant when billing is configured; ignore it for a default self-host.

GET /internal/tls-check — On-Demand TLS Probe

Internal endpoint Caddy calls (ask) before issuing an on-demand certificate for a custom domain. When CADDY_TLS_CHECK_SECRET is set, it requires a matching X-Tls-Check-Secret header and fails closed otherwise. Not for public use.

GET / — Dashboard Redirect

The apex / on the server issues a 302 redirect to ${APP_URL}/dash. The dashboard single-page app itself is not served by the Express server — it is a separate static (nginx) container reached at app.lnkify.io. See Architecture.

Route Resolution Order

For self-hosters, the Express server's request routing priority is:

PriorityPathHandler
1POST /graphqlApollo Server
2POST/GET/DELETE /mcpMCP server (when MCP_ENABLED)
3/healthz, /readyzHealth probes
4/llms.txt, /llms-full.txtAI discovery handlers
5POST /webhooks/stripe, GET /internal/tls-checkBilling & TLS internal handlers
6/:slug/qr.:format, /bio/:slugQR + bio page handlers
7GET /302 → ${APP_URL}/dash
8* (everything else)Slug redirect handler

Architecture Note

These routes are served by the Express server container. The dashboard SPA (app) and the docs site (docs) are separate static containers. Caddy acts as the reverse proxy, routing each hostname to the correct container. For example:

  • lnkify.io/graphql → server container (GraphQL)
  • lnkify.io/<slug> → server container (redirect)
  • app.lnkify.io/ → app container (dashboard SPA)
  • mcp.lnkify.io/ → server container (MCP)
  • docs.lnkify.io/ → docs container (VitePress static site)

See Self-Hosting for deployment details.

Released under the MIT License.