Authentication
Picora supports two authentication modes:
| Mode | Carrier | Best for |
|---|---|---|
| API Key | Authorization: Bearer sk_live_… | Server-side scripts, MCP tools, desktop apps (PicGo / Moraya) |
| OAuth 2.1 | Authorization: Bearer <access_token> | Third-party web apps acting on a user’s behalf, first-party SSO |
Both ride the same Authorization header — the format alone tells the server which path to take.
API Key (recommended for tools and scripts)
API Keys are 40-character secrets with the prefix sk_live_. Created in center.picora.me/integration, they are per-user, revocable, and scoped so you can delegate the minimum permission your tool needs.
v2 fine-grained scopes (v0.31+)
| Scope | Allows |
|---|---|
media.read | List / download / read metadata of images / videos / audio |
media.write | Upload + edit metadata |
media.delete | Permanent deletion (irreversible — pick carefully) |
kb.read | List KBs, fetch manifests, read documents |
kb.write | Create / update / delete documents inside a KB |
account.read | Read profile (email, nickname, plan) |
usage.read | Read storage / bandwidth quota counters |
Two presets in the UI cover most tools:
- AI / MCP read-only —
media.read+kb.read+account.read+usage.read - Creator (read + write, no delete) — adds
media.write+kb.write
Legacy v1 scopes
Keys created before v0.31 use three coarse tiers (read / read_write / read_write_delete). They continue to work — the server expands them transparently:
| v1 | Equivalent v2 set |
|---|---|
read | media.read, kb.read, account.read, usage.read |
read_write | + media.write, kb.write |
read_write_delete | + media.delete |
You can keep using your existing v1 Keys; new Keys created in the dashboard are v2.
Storage
- Servers store only the SHA-256 hash of your Key. The plaintext is shown once at creation; copy it immediately.
- Revoking a Key in the dashboard takes effect within seconds (a
revoked:{keyHash}flag is written to a global cache, beating the 1-hour token cache TTL).
OAuth 2.1 + PKCE
For web apps acting on a user’s behalf, use OAuth Authorization Code grant + PKCE:
- Register your app at
POST /oauth/register(RFC 7591 dynamic registration). New apps land inpendingand need allowlist approval — the four built-in clients (Claude Desktop, Cursor, Continue, Claude.ai) are auto-approved. - Direct the user to
GET /oauth/authorize?client_id=…&redirect_uri=…&scope=…&code_challenge=…&code_challenge_method=S256&state=…. - Picora redirects to
https://center.picora.me/oauth/consentfor the human consent step. - After approval Picora redirects to your
redirect_uriwith?code=…&state=…. - Exchange the code at
POST /oauth/tokenwithcode_verifierto get anaccess_token(RS256 JWT, 1 h) plus a rotatingrefresh_token(90 d).
First-party SSO (v0.30+)
Moraya Web is a Picora first-party application. When a logged-in Picora user reaches its consent URL, the consent UI is skipped and the authorization code is signed directly — provided the user’s plan satisfies the app’s required_plan gate. The OAuth dance otherwise follows the standard PKCE flow.
If you build a first-party Picora client, we’ll seed it into the oauth_clients table during deployment with is_first_party = 1 — no UI banner is shown to your users.
OIDC
When the requested scope contains openid, the token endpoint additionally returns an id_token (RS256 JWT). The discovery documents are at:
/.well-known/oauth-authorization-server/.well-known/openid-configuration/.well-known/jwks.json— public RS256 verification keys, cached 10 min
Common pitfalls
- Mixing modes — passing
apiKeyandoauthTokentocreatePicoraClient()throws at construction; pick one. - Using a v1 Key with
media.deleteonly — there is no v1 scope that maps to delete-only; either upgrade the Key to v2 (recreate) or keep the broaderread_write_deletev1 tier. - CORS in browsers — only
picora.me,picora.cn,web.moraya.app,center.picora.*, andlocalhost:5173/:4321are allowed origins for credentialed Web requests. Tools sendingAuthorization: Bearer sk_live_…getAccess-Control-Allow-Origin: *automatically (no Cookie risk). - Refresh-token replay — if the OAuth server detects a previously-rotated refresh token being reused, the entire token chain for that client is revoked and the user receives an email alert. Always use the freshest token your refresh call returned.