Skip to content
  • Follow System
  • English
  • 中文

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

Terminal window
npm install @picora/sdk
# or
pnpm add @picora/sdk
# or
bun add @picora/sdk

Create 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 URL
const 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-paginated
const page = await picora.images.list({ pageSize: 20 })
for (const i of page.items) console.log(i.url)

Namespaces (v0.4.0 — full public API)

NamespaceEndpoints
authregister / login / OTP / SMS / Firebase / WeChat / refresh / verify / password reset / export tokens
userprofile, usage, identities, avatar, password, account deletion
apps, oauthauthorized apps; OAuth client registration / consent / device verify / revoke-all
imagesupload (multipart), list, batch ops, hash dedupe (exists), signed URLs, incremental syncState
uploadsTUS 1.0 resumable uploads (create / append / status / capabilities / abort)
videos, audio, mediavideo (async transcode), audio, unified media list / batch delete
docsMarkdown docs CRUD, raw content, raw:batch, revisions (list / get / restore)
kbsknowledge bases CRUD, sync (batch ops), manifest (ETag / 304), tree delete, conflicts
boardsteaching whiteboards (.boardraw) — upload, list, detail, raw scene, update, delete
collections, collectionTypes, episodescollections, episode CRUD + episodes.sync (idempotent asset sync)
aigcprojects / episodes / contents / assets / batch jobs / templates / generate
aiTools, credit, agreementsAI image toolkit, credit wallet, AIGC terms
billing, campaignsplans, checkout, orders, subscription; promo campaigns & coupons
notifications, ticketsin-app notifications; support tickets
domains, watermarkTemplates, storageTiercustom domains, watermark templates, storage tiering + bulk delete
orgs, insights, migration, backuporganizations, analytics, migration jobs, backups
publish, publishedPages, mcp, systemmulti-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
}
}
HTTPSDK classAuto-retry
200/201/204(success)
400/401/403/404/422PicoraApiErrorNo
401 (session modes)refresh + retry onceterminal failure → PicoraReauthRequiredError
429PicoraRateLimitErrorYes, up to 3× (1 s / 2 s / 4 s, honors Retry-After)
500–504PicoraApiErrorYes, up to 2× (500 ms / 1500 ms)
Network failurePicoraNetworkErrorYes, up to 2× (network errors only)
TimeoutPicoraNetworkErrorNo (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 code
  • code — Picora machine-readable error code (UNAUTHORIZED, QUOTA_EXCEEDED, RATE_LIMITED, …)
  • message — human-readable, localized to the user’s Accept-Language
  • meta — extra context (e.g. { retryAfterSec: 30 } on rate limit, { kbId: '…' } on KB-specific failures)
  • requestId — value of the X-Request-Id response 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.fetch once you inject one).
  • Wrap fetch for 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 before 1.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-sdk crate, 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 SDKpicora-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.