Skip to content
  • Follow System
  • English
  • 中文

Python SDK Quickstart

picora-sdk is the official Python SDK for the Picora API. Synchronous (requests-based), Python 3.9+.

Install

Terminal window
pip install picora-sdk # coming soon — not yet on PyPI

60-second example (API Key)

from picora_sdk import PicoraClient
picora = PicoraClient(api_key="sk_live_xxx")
# Whoami
me = picora.auth_me()
print(f"Logged in as {me['email']} ({me['plan']})")
# List collections
result = picora.collections.list(limit=10)
print(f"Found {len(result['items'])} collections")

OAuth Device Flow (CLI / headless)

from picora_sdk import start_device_flow, PicoraClient
flow = start_device_flow(
client_id="cli_xxx",
scopes=["collection.read", "episode.write"],
)
print(f"Visit {flow.verification_uri} and enter {flow.user_code}")
# Built-in RFC 8628 backoff
token = flow.poll()
picora = PicoraClient(oauth_token=token.access_token)

DeviceFlowToken fields:

  • access_token — pass to PicoraClient(oauth_token=...)
  • refresh_token — optional
  • expires_at — unix seconds (absolute)
  • scopes — list of actually granted scopes

Collections + Episodes

# Create or find a collection
series = picora.collections.create(
name="My TV Show",
slug="my-tv-show",
collection_type="tv_series",
allowed_resource_types=["video", "audio", "doc"],
)
# Add an episode
ep = picora.episodes.create(series["id"], sequence_no=1, title="EP01 — Pilot")
# Sync uploaded assets to the episode (idempotent + dedup)
result = picora.episodes.sync(
series["id"], ep["id"],
idempotency_key="comfy-batch-001",
assets=[
{"resource_type": "video", "resource_id": "vid_uploaded_xxx"},
{"resource_type": "doc", "resource_id": "doc_script_yyy"},
],
)
print(f"Applied: {result['appliedCount']} / Skipped: {result['skippedCount']} / Failed: {result['failedCount']}")

Note: the request body uses camelCase keys (matching the Picora REST API), but the Python wrapper accepts snake_case kwargs (resource_type, idempotency_key). Conversion is automatic.

Custom collection types (Pro+)

picora.collection_types.create(
name="My Podcast Series",
slug="my_podcast",
allowed_resource_types=["audio", "doc"],
icon="mic",
description="Audio drama with companion scripts",
)

Error handling

All SDK errors extend PicoraApiError:

from picora_sdk import PicoraClient, PicoraApiError, PicoraRateLimitError
try:
picora.collections.create(name="X", slug="taken")
except PicoraRateLimitError as exc:
# Auto-retry happens by default; only raised if all 3 retries exhausted
print(f"Rate limited; retry after {exc.retry_after_sec}s")
except PicoraApiError as exc:
if exc.code == "COLLECTION_SLUG_TAKEN":
# pick a different slug
pass
else:
raise

Token persistence

MemoryTokenStorage keeps the token only in-process. For CLI / desktop tools you usually want the token to survive restarts — use the built-in FileTokenStorage (v0.2.1+):

import time
from picora_sdk import start_device_flow, FileTokenStorage, PicoraClient
storage = FileTokenStorage() # defaults to ~/.picora/token.json
token = storage.get()
if not token or token.expires_at < time.time() + 60:
flow = start_device_flow(client_id="cli_xxx", scopes=["collection.read"])
print(f"Visit {flow.verification_uri} and enter {flow.user_code}")
token = flow.poll()
storage.put(token)
picora = PicoraClient(oauth_token=token.access_token)

Properties:

  • Default path ~/.picora/token.json; pass a custom path to the constructor to override
  • Atomic write (tmp file + rename), so a crash mid-write won’t leave half a JSON
  • POSIX chmod 0600 on the file (current user only); silently skipped on Windows
  • Read errors (missing file / corrupted JSON / missing fields) return None — let the caller fall through to a fresh Device Flow

Plaintext on disk is fine for dev, but production CLI tools should put refresh_token in the OS keychain (macOS Keychain / Windows Credential Manager / Linux libsecret). picora-sdk ships KeychainTokenStorage (v0.2.2+) with the same interface, backed by the keyring package (optional extra).

Terminal window
pip install 'picora-sdk[keychain]'
# or: pip install picora-sdk keyring
import time
from picora_sdk import start_device_flow, KeychainTokenStorage, PicoraClient
# service defaults to 'picora-sdk', account defaults to 'default'
storage = KeychainTokenStorage(account="cli_acme")
token = storage.get()
if not token or token.expires_at < time.time() + 60:
flow = start_device_flow(client_id="cli_acme", scopes=["collection.read"])
print(f"Visit {flow.verification_uri} and enter {flow.user_code}")
token = flow.poll()
storage.put(token)
picora = PicoraClient(oauth_token=token.access_token)

Properties:

  • Encrypted at rest by the OS (no plaintext on disk)

  • service namespace lets you isolate prod vs staging tokens

  • account lets one machine hold tokens for multiple Picora users / OAuth clients

  • keyring is an optional extra — importing KeychainTokenStorage works without it; the first get()/put()/clear() call raises a clear RuntimeError pointing to pip install picora-sdk[keychain]

  • Pass backend=... to inject a custom adapter (Vault / 1Password CLI / corporate secret store):

    KeychainTokenStorage(backend=my_keyring_adapter)

For other secret-store backends, implement the KeyringBackend Protocol:

from picora_sdk import KeyringBackend
class VaultBackend:
def get_password(self, service: str, username: str) -> str | None: ...
def set_password(self, service: str, username: str, password: str) -> None: ...
def delete_password(self, service: str, username: str) -> None: ...

Retry behavior

By default the client retries:

  • 429 — exponential backoff (1s / 2s / 4s, max 3 attempts; respects Retry-After header)
  • 5xx and network errors — 0.5s + 1.5s (max 2 attempts)

Disable: PicoraClient(api_key="...", retry=False)

Available namespaces (v0.2.2)

NamespaceMethods
client.collectionslist / get / create / delete
client.collection_typeslist / create / delete
client.episodeslist / create / sync
client.auth_me()Returns current user
start_device_flow(...)Top-level helper
MemoryTokenStorageIn-process token persistence
FileTokenStorageFile-backed persistence; default ~/.picora/token.json, atomic write + chmod 0600
KeychainTokenStorageOS keychain persistence (optional keyring extra); service / account namespacing

What’s next