Shortnd Docs

Authentication

Exchange a long-lived API key for a 15-minute bearer token.

Shortnd public API auth is a two-step flow:

  1. Mint a long-lived API key in the dashboard (one-time per integration).
  2. Exchange the key for a short-lived bearer token (every 15 minutes, programmatically).

Every other request to /api/v1/* carries the bearer token.

Mint an API key

API keys are minted from the Shortnd dashboard.

  1. Sign in and open Settings → API Keys at shortnd.com/dashboard/settings/api-keys.
  2. Click Create API key.
  3. Give it a memorable name (e.g. Postman — local dev, prod ingestion).
  4. Pick the scopes the key needs. Defaults grant the read + write scopes most integrations need; trim them down for least-privilege keys.
  5. (Optional) Set an expires at date. Omit for non-expiring keys.
  6. Click Create. The secret (snd_live_xxx.yyy) is shown once — copy it immediately into your secret store. Shortnd cannot recover it later; rotate the key if you lose the secret.

Available scopes

ScopeWhat it allows
urls:read / urls:writeList, create, update, delete short URLs (single + bulk).
qr:read / qr:writeGenerate, list, update, delete QR codes; read scans.
domains:read / domains:writeList custom domains; add and verify new ones.
webhooks:read / webhooks:writeManage webhook subscriptions and inspect deliveries.
analytics:readRead org-wide and per-resource analytics.
usage:readRead API operational usage and billing usage.
api_keys:read / api_keys:writeManage other API keys (admin-only — typically dashboard-gated).

Rotate or revoke

The same dashboard page lets you rotate (mint a new secret on the same key id while invalidating the old one) and revoke (deactivate the key permanently).

API keys are tenant-scoped — every bearer token issued from a key inherits the key's organization. Cross-tenant access returns 404 / 403.

Required configuration

The public API is treated as always-on infrastructure.

  • PUBLIC_API_JWT_SECRET is required
  • REDIS_URL is required

If either is missing, the app should fail startup rather than serving /api/v1 with request-time auth errors.

1. Exchange the API key

Send the long-lived secret in the Authorization header as ApiKey (not Bearer):

curl -X POST https://shortnd.com/api/v1/auth/token \
  -H "Authorization: ApiKey $SHORTND_API_KEY"
const res = await fetch('https://shortnd.com/api/v1/auth/token', {
  method: 'POST',
  headers: {
    Authorization: `ApiKey ${process.env.SHORTND_API_KEY}`,
  },
});
const { data } = await res.json();
// data.accessToken — use as SHORTND_ACCESS_TOKEN for ~15 minutes
import os, requests

res = requests.post(
    "https://shortnd.com/api/v1/auth/token",
    headers={"Authorization": f"ApiKey {os.environ['SHORTND_API_KEY']}"},
)
access_token = res.json()["data"]["accessToken"]
req, _ := http.NewRequest("POST", "https://shortnd.com/api/v1/auth/token", nil)
req.Header.Set("Authorization", "ApiKey "+os.Getenv("SHORTND_API_KEY"))
http.DefaultClient.Do(req)
$ch = curl_init("https://shortnd.com/api/v1/auth/token");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => [
        "Authorization: ApiKey " . getenv("SHORTND_API_KEY"),
    ],
]);
curl_exec($ch);

Response:

{
  "success": true,
  "data": {
    "accessToken": "eyJ...",
    "tokenType": "Bearer",
    "expiresIn": 900,
    "organizationId": "org_uuid",
    "apiKeyId": "key_uuid",
    "scopes": ["urls:read", "urls:write", "domains:read"]
  }
}

Suggested env names:

VariableUsed as
SHORTND_API_KEYAuthorization: ApiKey … on /api/v1/auth/token only
SHORTND_ACCESS_TOKENAuthorization: Bearer … on every other /api/v1/* route

2. Use the bearer token

curl https://shortnd.com/api/v1/urls \
  -H "Authorization: Bearer $SHORTND_ACCESS_TOKEN"

The access token expires in 900 seconds. Re-exchange before expiry; do not pass the long-lived API key as Bearer — that is not a valid JWT and will fail.

Operational notes

Token exchange depends on both PostgreSQL and Redis.

  • Redis is used for centralized rate limiting.
  • PostgreSQL is used to resolve the API key record and organization linkage.

Expected failure modes:

  • 503 RATE_LIMIT_BACKEND_UNAVAILABLE: Redis-backed rate limiting is genuinely unavailable.
  • 500 SERVER_ERROR: operational backend issue such as PostgreSQL connection exhaustion.

The public API rate-limit layer now waits briefly for Redis readiness on cold start, so a momentary Redis connect race should not fail token exchange anymore.

Scope model

Current scopes:

  • urls:read
  • urls:write
  • analytics:read
  • domains:read
  • domains:write
  • webhooks:read
  • webhooks:write
  • usage:read
  • api_keys:read
  • api_keys:write

Session-managed key lifecycle

Tenant admins create, rotate, and revoke keys from a signed-in dashboard session through:

  • GET /api/v1/api-keys?organizationId=...
  • POST /api/v1/api-keys
  • POST /api/v1/api-keys/{id}/rotate
  • POST /api/v1/api-keys/{id}/revoke

Those endpoints are for dashboard UX and operational tooling. External vendors should use bearer-authenticated /api/v1 resource routes.