API authentication

The LinkSnap API uses Bearer-token authentication. Every authenticated request carries an Authorization: Bearer <token> header. There's no HMAC request signing, no signed payloads, no header shuffling — same scheme as standard OAuth.

This page covers:

  • The two ways to get a token (API key or OIDC device flow).
  • How to attach it to a request.
  • How to refresh access tokens.
  • Common errors.

If you're using a SDK or the CLI, token attachment and refresh happen automatically. The details below matter when you're calling the raw API with curl, or when you're implementing your own integration.

Option 1: API key

The simplest auth model. Mint a workspace-scoped API key in the portal:

  1. Sign into linksnap.com.
  2. Go to Settings → API keys.
  3. Click Create API key.
  4. Give it a label. Copy the secret immediately — we don't show it again.

The key looks like lk_live_xxxxxxxxxxxxxxxx. Attach it to every request:

curl https://linksnap.com/api/v1/links \
  -H "Authorization: Bearer lk_live_xxxxxxxxxxxxxxxx"

That's it. The key carries the full permission set of its workspace (no per-key scopes yet). API keys don't expire; revoke from the portal when you no longer need them.

Treat the key like a password. Anyone with lk_live_… can read and modify every link, QR code, and domain in the workspace. Use a secret manager. Don't commit it.

Option 2: OIDC device flow (for the CLI, or any headless tool)

For interactive sessions where a user is at the terminal, use the OIDC device authorization flow (RFC 8628). This is what linksnap auth login runs under the hood.

Step 1: Request a device code

curl -X POST https://huudis.com/api/v1/oidc/device/authorize \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "client_id=oc_linksnap_cli&scope=openid+profile+email+offline_access"

Response:

{
  "device_code": "abc123...",
  "user_code": "BPLR-RXJS",
  "verification_uri": "https://huudis.com/device",
  "verification_uri_complete": "https://huudis.com/device?user_code=BPLR-RXJS",
  "expires_in": 600,
  "interval": 5
}

Step 2: Show the code to the user

Print the user_code and open verification_uri_complete in their browser. They sign into Huudis (or are already signed in) and approve the request.

Step 3: Poll for the token

curl -X POST https://huudis.com/api/v1/oidc/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=urn:ietf:params:oauth:grant-type:device_code&device_code=abc123...&client_id=oc_linksnap_cli"

Poll at interval seconds. While the user hasn't approved yet, you'll get 400 authorization_pending — keep polling. When they approve:

{
  "access_token": "eyJhbGc...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "eyJhbGc...",
  "scope": "openid profile email offline_access",
  "id_token": "eyJhbGc..."
}

Step 4: Use the access token

curl https://linksnap.com/api/v1/links \
  -H "Authorization: Bearer eyJhbGc..."

LinkSnap's backend validates the token against Huudis, resolves the user, picks their default workspace, and serves the response.

Refreshing access tokens

Access tokens last one hour. Use the refresh_token to mint new ones:

curl -X POST https://huudis.com/api/v1/oidc/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token&refresh_token=eyJhbGc...&client_id=oc_linksnap_cli"

Response is the same shape as the initial token grant. Refresh tokens rotate on every use — the response includes a new refresh_token; the old one is invalidated. If Huudis sees the same refresh token presented twice, it treats it as a stolen-token signal and revokes the whole token family.

Single-flight refresh. If your code can run two refreshes concurrently (e.g. a polling browser, parallel API workers), wrap refresh in a mutex or single-flight cache. Two concurrent refreshes with the same token will both succeed once but trigger token-family revocation on the next call. This is a recurring footgun across the Forjio family — LinkSnap's frontend handles it in huudis.ts; if you're rolling your own, copy that pattern.

Workspace selection

API keys are workspace-scoped — the key encodes the workspace. No header needed.

OIDC bearer tokens are user-scoped — the user can belong to several workspaces. The backend picks the user's "current" workspace (last-active) by default. To target a different one:

X-Workspace-Id: ws_01H...

…on every request. The current workspace can also be changed for the user via POST /api/v1/workspaces/switch.

Common errors

401 UNAUTHENTICATED

Missing or invalid Authorization header. Check that you've included Bearer (with a space) before the token.

401 TOKEN_EXPIRED

Your access token is past its expires_in. Refresh it.

403 FORBIDDEN

The token is valid but you don't have permission for this resource — usually a workspace mismatch (token's workspace differs from the path's resource workspace).

429 TOO_MANY_REQUESTS

You've hit the rate limit. Wait Retry-After seconds and retry. The auth endpoints have a tighter limit (10/min/IP) than the main API.

Best practices

  • Use API keys for servers, OIDC for humans. API keys are durable and easy to rotate; OIDC tokens are short-lived and tied to a real identity.
  • Don't share API keys across services. One service, one key. Easier to rotate when a service is compromised.
  • Rotate on offboarding. When a teammate leaves, revoke their API keys and remove them from the workspace. Their Huudis sessions also drop.
  • Single-flight refresh. Said it twice. Do it.

Next