API authentication
The LinkSnap API uses token authentication. Every authenticated request carries an Authorization header: ApiKey <key> for a workspace API key (Bearer <key> works too), Bearer <token> for a Huudis access token. There's no HMAC request signing, no signed payloads, no header shuffling.
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 lsk_live_xxxxxxxxxxxxxxxx. Attach it to every request:
curl https://linksnap.forjio.com/api/v1/links \
-H "Authorization: ApiKey lsk_live_xxxxxxxxxxxxxxxx"
That's it. The key carries the full permission set of its workspace (no per-key scopes yet) and works on every customer route. API keys don't expire; revoke from the portal when you no longer need them.
A few routes are for a signed-in person only, and refuse a key with 403 PERSON_ONLY: minting and revoking API keys, deleting the account, changing the password or profile, and creating, switching or renaming workspaces and managing their members (LinkSnap makes those changes in Huudis with the person's own sign-in). The API reference marks each of them.
Treat the key like a password. Anyone with
lsk_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=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=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.forjio.com/api/v1/links \
-H "Authorization: Bearer eyJhbGc..."
LinkSnap verifies the token's signature against Huudis's published keys and accepts it when it was minted for LinkSnap — its aud is linksnap-cli (this flow) or linksnap (the web sign-in); another product's token is refused with 401. It then serves the request in the person's workspace (see Workspace selection). A person who has never opened LinkSnap gets their Huudis workspaces set up on their first call, as a first browser sign-in does.
The token is the person, so it also works on the routes that refuse an API key with 403 PERSON_ONLY (minting keys, changing the profile or password, managing workspaces and members).
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=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 workspace they joined first by default. To target a different one:
X-Account-Id: acc_01H...
…on every request (X-Workspace-Id is read too). A workspace the person isn't a member of is ignored, and the request falls back to the default. GET /api/v1/workspaces lists the person's workspaces and their ids.
Common errors
401 UNAUTHORIZED
The credential is missing or was refused; the message says which: Missing authorization header, Invalid or revoked API key, or Invalid or expired token (an access token that is expired, was minted for another product, or isn't Huudis's). Check that you've included ApiKey or Bearer (with a space) before it. An expired access token: refresh it and retry.
403 PERSON_ONLY
An API key on a route only a signed-in person may call. Use the person's access token.
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.