Skip to main content

Authentication

Site Health accepts two kinds of Bearer credentials. Both are sent in the standard Authorization header:

Authorization: Bearer <token>
CredentialPrefixLifetimeIssued byUsed for
API keywsh_Never expires (revocable)User (Settings UI)REST API + manual MCP client setup
OAuth access tokenat_30 days (refreshable)Authorization ServerClaude.ai + DCR-capable MCP clients
warning

Tokens grant full read access to every site in the issuing user's account. Treat them like passwords.

API keys (wsh_…)

How to create one

  1. Sign in at https://sitehealth.octagramlabs.com.
  2. Open Settings → API Keys.
  3. Click Create key, optionally name it (e.g. github-actions).
  4. Copy the raw key immediately — Site Health shows it exactly once. After that only the prefix (wsh_a1b2c3…) is visible.

Under the hood, generateApiKey() produces a 64-hex-char random suffix (wsh_ + 32 random bytes). The key is SHA-256 hashed before storing in Postgres — the plaintext value never touches disk.

Revoking

In the same UI, click Revoke next to a key. Revocation is immediate:

DELETE /api/settings/api-keys?id={apiKeyId}

Programmatic creation (session required)

POST /api/settings/api-keys
Content-Type: application/json
Cookie: session=…

{ "name": "github-actions" }

Response 201:

{
"id": "d1c2b3a4-…",
"keyPrefix": "wsh_a1b2c3d4",
"name": "github-actions",
"createdAt": "2026-04-18T12:00:00.000Z",
"rawKey": "wsh_a1b2c3d4e5f6…"
}
tip

The rawKey field is only present on creation. Save it to your secret store immediately.

Example request with an API key

curl -X POST https://sitehealth.octagramlabs.com/api/external/budget-check \
-H "Authorization: Bearer wsh_a1b2c3d4e5f6…" \
-H "Content-Type: application/json" \
-d '{ "siteId": "11111111-2222-3333-4444-555555555555" }'

OAuth access tokens (at_…)

Issued by Site Health's OAuth 2.1 Authorization Server at POST /api/oauth/token after a Claude.ai (or any DCR-capable) client completes the authorization code + PKCE flow. See OAuth reference for the full flow.

  • Lifetime: 30 days (ACCESS_TOKEN_TTL_MS).
  • Refresh: yes — use the returned refresh_token with grant_type=refresh_token (90-day lifetime).
  • Revoke: POST /api/oauth/revoke with { "token": "at_…" }.
  • Scopes: currently only mcp:read.

Tokens are base64url-encoded 32-byte random strings (at_ + 43 chars). Like API keys, they are SHA-256 hashed at rest.

Example request with an OAuth token

curl -X POST https://sitehealth.octagramlabs.com/api/mcp \
-H "Authorization: Bearer at_eyJ0eXAi…" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

Where each credential is accepted

Endpointwsh_at_Session cookie
POST /api/external/budget-checkyesnono
POST /api/mcpyesyesno
GET /api/sites/{siteId}/exportnonoyes
POST /api/settings/api-keysnonoyes
/.well-known/oauth-*no (public)
POST /oauth/* token endpointsper OAuth spec

Rate limits

EndpointLimitScope
Scan-trigger endpoints (manual/API scan)5 per hourPer user
Everything else (read endpoints, MCP tools, OAuth)Unlimited

When a scan limit is exceeded Site Health returns:

HTTP/1.1 429 Too Many Requests
Content-Type: application/json

{ "error": "Scan rate limit exceeded. Try again in 42 minutes." }

Scan deduplication is enforced separately: if a scan for the same site is already queued or running, createAndEnqueueScan() throws SCAN_ALREADY_ACTIVE and the caller gets a 409.

Security notes

  • Never commit a raw wsh_ key to version control. GitHub's secret scanning does detect the prefix, but prevention beats detection.
  • Use short-lived OAuth tokens for anything user-facing. API keys are for server-to-server where rotation is infrequent and audited.
  • Site Health logs only the keyPrefix (first 12 chars) on successful authentication. The raw key is never logged.
  • All traffic is HTTPS-only (HSTS is enforced at the Vercel edge).

Troubleshooting 401 Unauthorized

  1. Verify the Authorization: Bearer … header is present and formatted exactly (single space after Bearer).
  2. Confirm the token has the right prefix (wsh_ for API keys, at_ for OAuth).
  3. Check you haven't revoked the key in Settings or let an OAuth token expire past 30 days.
  4. For MCP 401 responses, the WWW-Authenticate header points at /.well-known/oauth-protected-resource — DCR-aware clients will re-register automatically.

Next: OAuth flow →