TypeScript SDK
@picora/sdk is the official, recommended way to integrate with Picora. Prefer it over hand-rolled fetch/curl calls: it is fully typed, auto-retries rate limits and transient errors, handles OAuth token refresh with rotation safety, and is contract-tested against the OpenAPI spec so it never drifts from the live API.
v0.4.0 covers 100% of the public Picora API — 236 operations across 34 namespaces, enforced in CI by a bidirectional OpenAPI coverage gate. There is no longer any resource you need to drop to raw fetch() for.
Working in another language? A Rust SDK is also available (a PC-focused subset, used by the Moraya desktop app). A Python SDK is in development. See Other languages below.
Requires Node.js ≥ 18 (native fetch). This release targets Node.js; browser / edge builds are planned.
Install
npm install @picora/sdk# orpnpm add @picora/sdk# orbun add @picora/sdkCreate a client
import { createPicoraClient } from '@picora/sdk'
const picora = createPicoraClient({ apiKey: process.env.PICORA_API_KEY, // sk_live_... created in center.picora.me/integration
baseUrl: 'https://api.picora.me', // default; switch to https://api.picora.cn for the China deployment timeout: 30_000, // ms; default 30s userAgent: 'MyApp/1.2', // SDK appends '@picora/sdk/<version>'})apiKey is the simplest auth mode. For OAuth access tokens, auto-refreshing OAuth sessions, and first-party JWT sessions, see Authentication — all four modes plug into the same createPicoraClient({ ... }).
First call
import { createPicoraClient, PicoraApiError } from '@picora/sdk'import { readFileSync } from 'node:fs'
const picora = createPicoraClient({ apiKey: process.env.PICORA_API_KEY })
// Upload an image → public CDN URLconst img = await picora.images.upload({ file: readFileSync('cat.jpg'), filename: 'cat.jpg', contentType: 'image/jpeg', tags: ['pets'], isPublic: true,})console.log(img.url) // https://media.picora.me/<nanoid>.jpg
// List — typed, cursor-paginatedconst page = await picora.images.list({ pageSize: 20 })for (const i of page.items) console.log(i.url)Namespaces (v0.4.0 — full public API)
| Namespace | Endpoints |
|---|---|
auth | register / login / OTP / SMS / Firebase / WeChat / refresh / verify / password reset / export tokens |
user | profile, usage, identities, avatar, password, account deletion |
apps, oauth | authorized apps; OAuth client registration / consent / device verify / revoke-all |
images | upload (multipart), list, batch ops, hash dedupe (exists), signed URLs, incremental syncState |
uploads | TUS 1.0 resumable uploads (create / append / status / capabilities / abort) |
videos, audio, media | video (async transcode), audio, unified media list / batch delete |
docs | Markdown docs CRUD, raw content, raw:batch, revisions (list / get / restore) |
kbs | knowledge bases CRUD, sync (batch ops), manifest (ETag / 304), tree delete, conflicts |
boards | teaching whiteboards (.boardraw) — upload, list, detail, raw scene, update, delete |
collections, collectionTypes, episodes | collections, episode CRUD + episodes.sync (idempotent asset sync) |
aigc | projects / episodes / contents / assets / batch jobs / templates / generate |
aiTools, credit, agreements | AI image toolkit, credit wallet, AIGC terms |
billing, campaigns | plans, checkout, orders, subscription; promo campaigns & coupons |
notifications, tickets | in-app notifications; support tickets |
domains, watermarkTemplates, storageTier | custom domains, watermark templates, storage tiering + bulk delete |
orgs, insights, migration, backup | organizations, analytics, migration jobs, backups |
publish, publishedPages, mcp, system | multi-platform publishing, published pages, MCP catalog/usage, health |
Auto-pagination over any cursor-paginated namespace:
import { paginateAll } from '@picora/sdk'
for await (const doc of paginateAll((p) => picora.docs.list(p), { limit: 100 })) { console.log(doc.id)}Escape hatch — call any endpoint (even a brand-new one) through the same retry / auth / decoding stack:
await picora.http.request({ method: 'GET', path: '/v1/user/me' })Error model
import { PicoraApiError, PicoraRateLimitError, PicoraNetworkError, PicoraReauthRequiredError,} from '@picora/sdk'
try { await picora.images.delete(id)} catch (err) { if (err instanceof PicoraRateLimitError) { console.warn(`Rate limited; retry in ${err.retryAfterSec}s`) // already auto-retried up to 3× } else if (err instanceof PicoraApiError && err.status === 404) { console.log('Image already deleted') } else if (err instanceof PicoraReauthRequiredError) { // OAuth/JWT session mode only: refresh token is terminal → re-run the authorization flow } else if (err instanceof PicoraNetworkError) { console.error('Network blip:', err.cause?.message) } else { throw err }}| HTTP | SDK class | Auto-retry |
|---|---|---|
| 200/201/204 | (success) | — |
| 400/401/403/404/422 | PicoraApiError | No |
| 401 (session modes) | refresh + retry once | terminal failure → PicoraReauthRequiredError |
| 429 | PicoraRateLimitError | Yes, up to 3× (1 s / 2 s / 4 s, honors Retry-After) |
| 500–504 | PicoraApiError | Yes, up to 2× (500 ms / 1500 ms) |
| Network failure | PicoraNetworkError | Yes, up to 2× (network errors only) |
| Timeout | PicoraNetworkError | No (AbortError, treat as user-cancelled) |
Non-idempotent calls (uploads, checkout, job creation, TUS append) disable auto-retry internally, so a retry can never double-charge or double-create. Pass { retry: false } on any call to opt out explicitly.
PicoraApiError exposes:
status— HTTP status codecode— Picora machine-readable error code (UNAUTHORIZED,QUOTA_EXCEEDED,RATE_LIMITED, …)message— human-readable, localized to the user’sAccept-Languagemeta— extra context (e.g.{ retryAfterSec: 30 }on rate limit,{ kbId: '…' }on KB-specific failures)requestId— value of theX-Request-Idresponse header for support correlation
SSR & test mocks
createPicoraClient({ fetch: customFetch }) accepts any fetch-compatible implementation. Use it to:
- Mock all HTTP calls in unit tests (the SDK never reaches
globalThis.fetchonce you inject one). - Wrap
fetchfor runtimes that need extra cookies / observability hooks.
Versioning
The SDK follows semver:
- Patch (
0.3.x) — bug fixes, new convenience methods, no breaking changes. - Minor (
0.x.0) — new namespaces, new optional client options. - Major (
x.0.0) — breaking type / signature changes (none planned before1.0).
Every release is published to npm with signed provenance (via GitHub Actions OIDC Trusted Publishing) so consumers can verify the artifact came from the zouwei/picora-sdk CI.
Provenance & deprecation policy
Once published, an SDK version cannot truly be unpublished. Picora’s policy is to issue a new patch + npm deprecate the bad version, never silent rewrite. If you ever see npm install @picora/sdk print a deprecation notice, upgrade to the suggested version — the deprecation message includes the reason.
Other languages
The TypeScript SDK above is the flagship (100% API coverage, on npm). Two more language SDKs live in the same zouwei/picora-sdk multi-language repo, versioned and released independently:
- Rust SDK — the
picora-sdkcrate, available now as a git-tag dependency (not yet on crates.io). It is a PC-focused subset — knowledge bases, document revisions, and user settings, plus the shared HTTP / auth / error core — built for the Moraya desktop app (Tauri), and grows toward fuller coverage as consumers need it. See the Rust quickstart. - Python SDK —
picora-sdk, in development (not yet on PyPI). It will mirror the TypeScript SDK’s full coverage. See the Python quickstart for the previewed API shape.
All SDKs implement the same public OpenAPI contract and enforce the same bidirectional coverage gate in CI, so none of them drift from the live API.