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.