Documentation

The API is for the link owner. It is not a public service and there is no public registration. Keys are scoped and revocable.

Authentication

Send an API key as a bearer token. Keys are stored only as a salted hash, so the value is shown once at creation and cannot be recovered afterwards.

curl -H "Authorization: Bearer $GO_API_KEY" \
  https://go.arjav.is-cool.dev/api/v1/links

Scopes are explicit and are never implied: a key granted links:read cannot create, modify or delete anything. Owner allowlist membership is re-checked on every request, so removing an owner from configuration invalidates their keys immediately.

Endpoints

MethodPathScopeNotes
GET/api/v1/linkslinks:readList links owned by the authenticated owner.
POST/api/v1/linkslinks:writeCreate a link. Optional custom slug; reserved slugs are rejected.
GET/api/v1/links/{id}links:readFetch one link.
PATCH/api/v1/links/{id}links:writeUpdate destination, title, activation, expiry or tags.
DELETE/api/v1/links/{id}links:writeDelete a link and its dependent rows.
GET/api/v1/analyticsanalytics:readAggregate click counts over a date range.
GET/api/v1/experiments/{id}analytics:readVersioned config, results, a deterministic replay and a routing check.
GET/api/health—Liveness. Constant response. Safe to expose.
GET/api/ready—Readiness. Reports database reachability and configuration booleans.

Rate limits

Limits are counted in Postgres, so they hold across every deployed instance. A limited response carries 429, a Retry-After header and the remaining budget. If the counter store is unreachable, nonessential paths fail open rather than break.

Errors

Errors use a fixed JSON envelope and never include driver messages, stack traces or row contents.

{ "error": "Slug is already taken." }
  • 400Malformed request body, query or path parameter.
  • 401Missing, malformed or revoked API key.
  • 403Valid key without the required scope, or not an allowed owner.
  • 404No such resource, or one owned by a different owner.
  • 409Slug already taken.
  • 422Well-formed but invalid, for example an unusable destination URL.
  • 429Rate limited. See the Retry-After header.
  • 500Unexpected server error. Detail is never included in the response.

Short link behaviour

  • A live link returns 302 with Cache-Control: no-store, so the redirect is never cached and a changed destination takes effect immediately.
  • Unknown, disabled and expired slugs all return an indistinguishable 404, so the set of live links cannot be probed.
  • Only http and https destinations are ever returned, validated again at redirect time.
  • Analytics are written after the redirect is sent. A failure to record a click never delays or fails the redirect.

Further reading

  • Privacy — exactly what is and is not collected.
  • Terms — acceptable use, and the guarantees that are explicitly not offered.
  • The repository README covers local setup, environment variables and deployment; THREAT-MODEL.md and BACKUP-RECOVERY.md cover operational risk.