Api Authentication

Authentication and scopes

Every request carries the key as a Bearer token:

Authorization: Bearer bskc_a1b2c3d4_yourSecretHere

Bearer only. There is deliberately no ?api_key= form: query strings end up in server logs, proxy logs and Referer headers, which is not where a credential belongs.

Scopes

Scope Can do Cannot do
read Analytics, stored suggestions, prompt export Change anything, spend AI credit
write Everything above, plus drafts, scheduling, and the AI actions —

The AI actions need write even though they feel like reads. They spend your own provider credit, and "my read-only key ran up an Anthropic bill" is a worse surprise than "my read-only key would not rewrite a post". The prompt-export routes, which cost nothing, work fine with a read key.

Response shape

Success:

{"status": "success", "message": "...", "data": {"...": "..."}}

Error:

{"status": "error", "error": "insufficient_scope", "message": "..."}

The error values are stable, so code can branch on them.

Code HTTP Meaning
unauthorized 401 Key missing, malformed, revoked or expired
api_access_required 403 Account is not on the paid plan
insufficient_scope 403 Read-only key used for a write
feature_required 403 Plan lacks a feature the route needs
no_ai_key 403 No Anthropic/OpenAI key configured
confirmation_required 400 Publishing needs {"confirm": true}
bluesky_not_connected 409 No Bluesky credentials stored
scheduled_in_past 409 The requested time has passed
rate_limited 429 Slow down; see Retry-After
bad_request 400 Something in the request is wrong
not_found 404 No such route or resource

Rate limits

Three separate per-key buckets over a sliding one-hour window:

Bucket Limit Covers
general 600 every request
AI 60 the two routes that call an LLM
write 120 routes that change state or call Bluesky

Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Window. A 429 also carries Retry-After.

The AI bucket is not a spend cap — your own provider key pays, and your provider has its own limits. It is there to stop an agent stuck in a loop from running up a bill overnight.

GET /me/

The first call anything should make. It reports which account the key drives, what it may do, and what is configured:

{"status": "success", "data": {
  "handle": "alice.bsky.social",
  "timezone": "Europe/Lisbon",
  "scope": "write",
  "features": {"scheduling": true, "api_access": true},
  "ai_key": {"configured": true, "provider": "anthropic", "is_valid": true},
  "rate_limits": {"default": {"limit": 600, "remaining": 598, "window_seconds": 3600}}
}}

timezone matters: it is how a scheduled time without an offset gets interpreted. Read it before scheduling anything.