TypeScript SDK
@originalpictures/sdk is a typed client for the public API. It is generated from the same
contract as the API reference. It covers all 109 operations and can call the 107 consumer
operations. The two provider-ingress callbacks, for the Clerk and Stripe webhooks, are typed but
deliberately not callable.
- ES modules only
- Runs anywhere with a standard
fetch: Node.js and browsers - One runtime dependency,
@noble/hashes
Configure a client
Section titled “Configure a client”import { OriginalPicturesClient } from "@originalpictures/sdk";
const client = new OriginalPicturesClient({ baseUrl: process.env.OP_API_BASE_URL ?? "https://dev-api.originalpictures.com", apiKey: process.env.OP_API_KEY, timeoutMs: 30_000,});baseUrl can include a path prefix. A base URL with a query string or fragment throws
ConfigurationError before any request is sent. Never ship an API key to a browser. In browsers,
use the client only with a backend that holds the key.
Calling operations
Section titled “Calling operations”Every executable operation is a method on client.operations, named by its operationId.
Examples are createUpload, completeUpload, acceptSigningJob, getJob, createVerification,
and listRecipes. Each method takes { body, parameters, headers, signal } and returns the typed
response. client.request(operationId, options) is the same call made by name.
Sign a file (Node.js)
Section titled “Sign a file (Node.js)”import { createReadStream, readFileSync, statSync } from "node:fs";import { createHash } from "node:crypto";import { idempotencyKey, poll, signJobTerminal } from "@originalpictures/sdk";
const path = "photo.jpg";const sha256 = createHash("sha256").update(readFileSync(path)).digest("hex");const byteLength = statSync(path).size;
const upload = await client.operations.createUpload({ body: { byte_length: byteLength, sha256, declared_media_type: "image/jpeg", mode: "single" },});// Sends exactly the issued headers, never your API key, and checks length and SHA-256.await client.uploadNodeStream(upload.upload!.url, createReadStream(path), { byteLength, sha256, headers: upload.upload!.headers as Record<string, string>,});
await client.operations.completeUpload({ body: {}, parameters: { upload_id: upload.upload_id }, headers: { "Idempotency-Key": idempotencyKey() },});
const accepted = await client.operations.acceptSigningJob({ body: { asset_id: upload.asset_id, recipe_id: "rcp_system_sign_only" }, headers: { "Idempotency-Key": idempotencyKey() },});const job = await poll( (signal) => client.operations.getJob({ parameters: { job_id: accepted.job_id }, signal }), signJobTerminal, { timeoutMs: 300_000 },);console.log(job.state, job.result);Choose a mode and a watermark strength
Section titled “Choose a mode and a watermark strength”The three signing modes are rcp_system_sign (C2PA only), rcp_system_watermark_then_sign_unfingerprinted
(watermark, no fingerprint) and rcp_system_watermark_then_sign (watermark and fingerprints); see
Choose a mode. watermark_strength is subtle, balanced (the default)
or robust:
const subtle = await client.operations.acceptSigningJob({ body: { asset_id: upload.asset_id, recipe_id: "rcp_system_watermark_then_sign_unfingerprinted", watermark_strength: "subtle", }, headers: { "Idempotency-Key": idempotencyKey() },});A key whose signing profile names a default recipe may omit
recipe_id; its other defaults fill the fields a request omits.
In browsers, use uploadBlob instead of uploadNodeStream.
signJobTerminal fails closed on inconsistent snapshots. For example, a completed job must
carry ready vfy_… evidence. For verifications, use createVerification with
verificationJobTerminal. A vjob_… ID is only a pollable job, never evidence.
Polling
Section titled “Polling”poll(fetch, done, { timeoutMs, intervalMs, maxAttempts, signal }) backs off from intervalMs
up to 5 seconds. Your fetch callback receives an AbortSignal. A deadline or caller cancellation
settles the poll immediately and aborts that signal, so the underlying request is released.
Downloads
Section titled “Downloads”client.download(url, { byteLength, sha256, sink }) streams into a staged sink. sink.commit()
runs only after the exact length and SHA-256 match. On any failure, sink.abort() runs and the
sink is never committed.
Webhooks
Section titled “Webhooks”verifyWebhook(rawBody, { secret, eventId, timestamp, signature }) checks a delivery against the
exact raw bytes. See Webhooks.
Errors
Section titled “Errors”API failures throw subclasses of ApiError: AuthenticationError, PermissionDeniedError,
NotFoundError, ConflictError, ValidationError, RateLimitError, and
ServiceUnavailableError. Each carries statusCode, code, requestId, retryable,
fieldDetails, and documentationUrl. Transport problems throw TransportError or
DeadlineExceededError, and integrity failures throw IntegrityError. All of them derive from
OriginalPicturesError. See Errors.
