Skip to content

API Keys

API Keys (sk_live_*) authenticate tool clients (PicGo / Moraya / your own scripts) to Picora’s API. They are not used for AI tools — those use OAuth via HTTP MCP (v0.14+) or npm install @picora/mcp for stdio (v0.13+).

Creating an API Key

  1. Open Settings → API Keys
  2. Click Create new key
  3. Give it a name (e.g., “PicGo on MacBook”, “Production blog server”)
  4. Pick scopes (default: all)
  5. Click Create
  6. Copy the displayed key now — it’s shown once and never again

The full key looks like sk_live_abcdef0123456789... (40 characters total: sk_live_ prefix + 32 nanoid chars).

Scopes

Each key carries scope flags that determine what it can do:

ScopeIncludes
images:readList images, get metadata
images:writeUpload images, update metadata
images:deleteDelete images
videos:read / videos:write / videos:deleteSame as images, for videos
audio:read / audio:write / audio:deleteFor audio
docs:read / docs:write / docs:deleteFor Markdown documents (v0.15+)
usage:readRead your own quota / usage

By default, keys get all *:read and *:write permissions but not *:delete — to reduce blast radius if a key leaks.

You can refine scopes per key:

PicGo Blog Key → images:write only (cannot list / delete)
CI Pipeline Key → images:write + docs:write
Read-only Audit Key → *:read scopes only

Using an API Key

In every API request:

Terminal window
curl -H "Authorization: Bearer sk_live_..." https://api.picora.me/v1/images

Tool clients embed the key in their config:

Listing keys

Settings → API Keys shows all your active keys with:

  • Name
  • Last 4 characters (e.g., ...4f3a)
  • Scopes granted
  • Last used timestamp
  • Created date

You cannot view the full key value after creation — only the suffix for identification.

Rotation

Best practice: rotate API Keys every 90 days, or immediately if you suspect compromise.

  1. Create a new key with the same name + same scopes
  2. Update your tool config to use the new key (test that it works)
  3. Once verified, click Revoke on the old key in the keys list

Revoked keys are immediately invalid — any in-flight requests using them get 401.

Revocation

Click Revoke on any active key. The token cache is purged within 5 seconds globally. The key cannot be recovered after revocation.

If you revoked the wrong key: create a new one with the same scopes. There’s no undo.

Security best practices

DO

  • ✅ Use separate keys per tool / environment — easier to rotate / revoke
  • Minimize scopes — only grant what the tool needs
  • ✅ Store keys in environment variables or a secret manager (not in source code)
  • ✅ Rotate every 90 days
  • ✅ Revoke immediately on suspected leak

DON’T

  • ❌ Commit keys to git (even private repos — they leak through forks, mirrors, CI logs)
  • ❌ Embed keys in client-side code (browsers, mobile apps) — they’re visible in network tab
  • ❌ Share keys via email / Slack / Discord — use a secret manager
  • ❌ Use a single “master key” for everything — separate concerns

If you accidentally committed a key to git

  1. Revoke the key immediately in Settings → API Keys
  2. Remove from git history (use git filter-branch or BFG Repo-Cleaner)
  3. Force-push the cleaned history (warn collaborators first)
  4. Create a new key for your tool

GitHub’s secret scanning will detect leaked Picora keys and alert us — we’ll auto-revoke and email you within 1 hour.

Programmatic key management

You can manage keys via API:

Terminal window
# Create a key (requires JWT, not a key — keys can't create keys for security)
POST /v1/api-keys
{
"name": "Backup script",
"scopes": ["images:write"]
}
# List
GET /v1/api-keys
# Revoke
DELETE /v1/api-keys/{keyId}

See API Reference — API Keys for full details.

Common issues

“401 Unauthorized” with valid-looking key — check the Authorization header format: must be Bearer prefix + key. Common mistake: missing Bearer or extra whitespace.

“403 Forbidden — missing scope” — your key doesn’t have the required scope. Create a new key with the right scopes; the old key cannot be edited (only revoked).

“Key works locally but not from production server” — likely IP-based rate limit (production server gets shared IP via load balancer). Check admin / observability for rate limit logs.