Snugl
reference

API docs

The dashboard is a client of this same REST API — every endpoint below is what it calls.

Authentication

Programmatic calls authenticate with an X-API-Key header carrying a key shaped like sk_live_…. The browser dashboard authenticates the same routes with a session cookie instead (set at /api/auth/login or /signup) — either one works on every route below.

Every /api/* route lives on snugl.space, the same host your shortened links resolve on — which is why the request below and the short_url it returns name the same domain.

curl -X POST https://snugl.space/api/links \
  -H "X-API-Key: sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"target": "https://example.com/a-very-long-url"}'

# -> {"short_url":"https://snugl.space/x7Kbq2","code":"x7Kbq2", ...}

Error codes

Every error response, on every route, shares one shape:

{
  "error": "human-readable message"
}
StatusNameMeaning
400Bad RequestMalformed body, or an invalid target/alias/reset token
401UnauthorizedMissing or invalid `X-API-Key`, or no session cookie
402Payment RequiredA plan limit was hit (active-link cap, Growth/Premium-only feature)
403ForbiddenAuthenticated, but not allowed to perform this action
404Not FoundNo resource at that code/id
409ConflictAlias/code already taken, a reserved code, or email already registered
429Too Many RequestsThe public `GET /{code}` redirect rate limit was exceeded
500Internal Server ErrorUnexpected server or database failure
502Bad GatewayThe upstream billing provider returned an error
503Service UnavailableAn optional integration (billing, OAuth, email) is not configured on this deployment

Endpoints

MethodPathAuthPurpose
GET/api/healthpublicLiveness probe
POST/api/linkskey or sessionCreate a link
GET/api/linkskey or sessionList your links, newest first
GET/api/links/{code}key or sessionFetch one of your links
PATCH/api/links/{code}key or sessionUpdate target, permanent, or is_active
DELETE/api/links/{code}key or sessionDeactivate a link
GET/api/links/{code}/statskey or sessionDaily click series + all-time total
GET/api/billing/subscriptionkey or sessionCurrent plan and effective limits
POST/api/billing/checkoutkey or sessionHosted checkout URL for a paid plan
POST/api/billing/portalkey or sessionSigned customer-portal URL
POST/api/auth/signuppublicEmail/password signup, sets a session cookie
POST/api/auth/loginpublicEmail/password login, sets a session cookie
GET/api/auth/oauth/{provider}/startpublicRedirects to Google or Microsoft
GET/{code}public302 by default, 301 when permanent; plain 404 otherwise

Rate limits

GET /{code} — the public redirect route — is guarded per source IP by a token bucket: 10 requests/second sustained, with a burst of 20. Going over it answers 429 Too Many Requests. The authenticated /api/* surface is not rate limited by the API itself — it's low-volume by design.

Account limits

PlanActive linksAnalytics retentionCustom aliasesPrivate codes
Free1030 daysNoNo
Growth / PremiumUnlimited365 daysYesYes

A create that would exceed the active-link limit answers 402 Payment Required, naming the limit. Deactivating a link frees the slot back up.

Interactive reference