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:
- Sign into linksnap.com.
- Go to Settings → API keys.
- Click Create API key.
- 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
- API overview — the endpoint surface.
- Auth overview — the user-facing side of the same OIDC flow.
- SDKs — let the SDK handle all of this.