Skip to content

Python SDK

llms.txtAsk agent

The originalpictures-sdk distribution (import name originalpictures) 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.

  • Python 3.12
  • Dependency: httpx
  • Sync (Client) and async (AsyncClient) clients
import os
from originalpictures import Client
client = Client(
base_url=os.environ.get("OP_API_BASE_URL", "https://dev-api.originalpictures.com"),
api_key=os.environ["OP_API_KEY"],
)

base_url can include a path prefix. A base URL with a query string or fragment is rejected before any request is sent. The client does not read environment variables by itself: pass the key explicitly, and never put it in command arguments or logs.

Every contract operation is a method named after its operationId in snake case. Examples are create_upload, complete_upload, accept_signing_job, get_job, create_verification, get_verification, and list_recipes. Methods take keyword arguments body=, parameters= (path and query), and headers=, and return typed dictionaries.

import hashlib
from pathlib import Path
from originalpictures import idempotency_key, poll, sign_job_terminal
path = Path("photo.jpg")
sha256 = hashlib.sha256(path.read_bytes()).hexdigest()
upload = client.create_upload(
body={
"byte_length": path.stat().st_size,
"sha256": sha256,
"declared_media_type": "image/jpeg",
"mode": "single",
"filename": path.name,
}
)
# Sends exactly the issued headers, never your API key, and checks length and SHA-256.
client.upload_file(upload["upload"]["url"], path, sha256=sha256, headers=upload["upload"]["headers"])
completed = client.complete_upload(
body={},
parameters={"upload_id": upload["upload_id"]},
headers={"Idempotency-Key": idempotency_key()},
)
job = client.accept_signing_job(
body={"asset_id": upload["asset_id"], "recipe_id": "rcp_system_sign_only"},
headers={"Idempotency-Key": idempotency_key()},
)
job = poll(
lambda: client.get_job(parameters={"job_id": job["job_id"]}),
sign_job_terminal,
timeout=300,
)
print(job["state"], job["result"])

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:

job = client.accept_signing_job(
body={
"asset_id": upload["asset_id"],
"recipe_id": "rcp_system_watermark_then_sign_unfingerprinted",
"watermark_strength": "subtle",
},
headers={"Idempotency-Key": idempotency_key()},
)

A key whose signing profile names a default recipe may omit recipe_id; its other defaults fill the fields a request omits.

sign_job_terminal fails closed on inconsistent snapshots. For example, a completed job must carry ready vfy_… evidence. For verifications, use create_verification with verification_job_terminal. A vjob_… ID is only a pollable job, never evidence.

poll enforces a wall-clock deadline, a bounded number of attempts, and backoff from interval up to max_interval. It can be cancelled cooperatively through cancelled=. Synchronous polls run on at most eight worker slots and fail with PollingCapacityError when every slot is busy. poll_async cancels the in-flight coroutine at its deadline.

  • upload_file and upload_stream PUT bytes to an API-issued URL. They verify length and SHA-256 and never forward API authorization.
  • download_file and download_stream write to a staged temporary file. The file replaces the destination only after its length and SHA-256 match the values from POST /v1/assets/{asset_id}/download.

verify_webhook(body, secret=…, event_id=…, timestamp=…, signature=…) checks a delivery against the exact raw body. See Webhooks.

API failures raise subclasses of ApiError: AuthenticationError, PermissionDeniedError, NotFoundError, ConflictError, RequestValidationError, RateLimitError, and ServiceUnavailableError. Each carries the response’s reason code, request_id, and retryable flag. Transport problems raise TransportError or DeadlineExceededError, and integrity failures raise IntegrityError. All of them derive from OriginalPicturesError. See Errors.