TypeScript SDK
TypeScript SDK
The @originalpictures/sdk package is the official TypeScript client for the Original Pictures API.
- npm:
@originalpictures/sdk - Node: 18+
- Formats: ESM and CJS
- License: MIT
Installation
npm install @originalpictures/sdk# orpnpm add @originalpictures/sdk# oryarn add @originalpictures/sdkConfiguration
import { OriginalPicturesClient } from "@originalpictures/sdk";
// From environment variable (recommended)const client = new OriginalPicturesClient(); // reads OP_API_KEY
// Explicit keyconst client = new OriginalPicturesClient({ apiKey: "op_live_01j..." });
// Custom base URLconst client = new OriginalPicturesClient({ baseUrl: "https://api.originalpictures.com" });Set OP_API_KEY in your environment:
export OP_API_KEY=op_live_01j...TypeScript types
The SDK is written in TypeScript and exports all request/response types:
import type { SignResult, VerificationResult, LookupResult, Recipe, AuditEntry, VerificationStatus, // the 16-code closed enum} from "@originalpictures/sdk";Signing
import { OriginalPicturesClient } from "@originalpictures/sdk";
const client = new OriginalPicturesClient();
const result = await client.sign("output.png", { recipe: "image-genai-v1", metadata: { generator: "stable-diffusion-xl", model: "sdxl-1.0", },});
console.log(result.assetId); // "a_01j..."console.log(result.verifierUrl); // "https://v.originalpictures.com/v/a_01j..."console.log(result.manifestUri); // "https://m.originalpictures.com/manifests/..."Sign from Buffer or ReadableStream
import fs from "fs";
// From Bufferconst buffer = fs.readFileSync("output.png");const result = await client.sign(buffer, { recipe: "image-genai-v1", filename: "output.png",});
// From ReadableStreamconst stream = fs.createReadStream("large_video.mp4");const result = await client.sign(stream, { recipe: "video-genai-v1", filename: "large_video.mp4",});Large files (async)
Files > ~5 MB return a job. The SDK handles polling automatically:
const result = await client.sign("large_video.mp4", { recipe: "video-genai-v1", // SDK polls until done; default timeout: 300s timeout: 600,});Verifying
// By file pathconst result = await client.verify("received_image.jpg");
// By asset IDconst result = await client.verify({ assetId: "a_01j..." });
// By manifest URIconst result = await client.verify({ manifestUri: "https://m.originalpictures.com/manifests/a_01j.../manifest.c2pa",});
console.log(result.status); // "verified_manifest_and_watermark_match"console.log(result.confidence); // 0.97console.log(result.proves); // string[]console.log(result.doesNotProve); // string[]Type-safe status checks
import type { VerificationStatus } from "@originalpictures/sdk";
const result = await client.verify({ assetId: "a_01j..." });
switch (result.status as VerificationStatus) { case "verified_manifest_and_watermark_match": case "verified_manifest_intact": // proceed break; case "manifest_tampered": throw new Error(`Tampered: ${result.developerExplanation}`); case "no_provenance": // no record found; handle per policy break; default: console.warn("Unhandled state:", result.status);}Lookup
const result = await client.lookup("cropped_image.jpg");
for (const match of result.matches) { console.log(match.assetId, match.matchType, match.confidence);}Recipes
// Listconst { recipes } = await client.recipes.list();
// Getconst recipe = await client.recipes.get("image-genai-v1");
// Create (requires admin key)const created = await client.recipes.create(yamlString);Webhooks
import express from "express";
const app = express();app.use("/webhook", express.raw({ type: "application/json" }));
app.post("/webhook", (req, res) => { const signature = req.headers["x-op-signature"] as string;
const event = client.webhooks.constructEvent( req.body, signature, process.env.OP_WEBHOOK_SECRET!, );
if (event.type === "job.completed") { const assetId = event.data.assetId; // update your records }
res.sendStatus(200);});Error handling
import { OriginalPicturesAPIError, AuthError, QuotaExceededError, RateLimitError, SignerUnavailableError,} from "@originalpictures/sdk";
try { const result = await client.sign("image.png", { recipe: "image-genai-v1" });} catch (e) { if (e instanceof AuthError) { console.error("Invalid or revoked key"); } else if (e instanceof QuotaExceededError) { console.error("Monthly quota exceeded"); } else if (e instanceof RateLimitError) { console.error(`Rate limited; retry after ${e.retryAfter}s`); } else if (e instanceof SignerUnavailableError) { console.error("Signer unavailable after retries"); } else if (e instanceof OriginalPicturesAPIError) { console.error(`${e.code}: ${e.detail} (requestId=${e.requestId})`); } else { throw e; }}ESM and CJS
The package ships both ESM and CJS builds. With bundlers (Vite, webpack, esbuild) and Node.js with "type": "module", ESM is used automatically.
For CommonJS:
const { OriginalPicturesClient } = require("@originalpictures/sdk");Environment variables
| Variable | Description |
|---|---|
OP_API_KEY | API key |
OP_API_BASE | Override base URL (default: https://api.originalpictures.com) |
OP_TIMEOUT_MS | HTTP timeout in milliseconds (default: 60000) |
OP_MAX_RETRIES | Max retry attempts (default: 3) |