Skip to main content

originalpictures-sdk: Python SDK for C2PA Content Credentials, AI watermarking and verification

originalpictures-sdk is the official, typed Python client for the Original Pictures API. Use it to sign images, audio, video and documents with C2PA Content Credentials, add an invisible watermark and perceptual fingerprints, and verify what a file declares about its origin.

It covers all 128 operations in the public API contract with generated types. Its synchronous and asynchronous clients execute the 126 consumer operations; the two provider-ingress operations (Clerk and Stripe callbacks) stay typed but are deliberately non-callable, because their signatures cover exact raw bytes that a JSON client must not re-encode.

from originalpictures import Client

with Client(base_url="https://api.originalpictures.com", api_key="opk_...") as client:
    me = client.get_current_user()
    capabilities = client.get_capabilities()

What it does

  • C2PA signing. Every signing job writes a C2PA manifest (Content Credentials) into the file, or beside it for sidecar formats, following the C2PA Technical Specification 2.4. The manifest records the file's declared origin: who signed it, with which tool, and what actions were declared.
  • Invisible watermark. Optionally embeds an invisible watermark before signing, so a copy whose metadata was stripped can still be matched to your record.
  • Perceptual fingerprinting. Optionally keeps server-side fingerprints of the signed output for recovery by similarity.
  • AI-generation declaration. Records that a generative model produced the file (c2pa.created with an IPTC digital source type such as trainedAlgorithmicMedia). This is the kind of machine-readable marking that EU AI Act Article 50(2) and the California AI Transparency Act (SB 942, as amended by AB 853) describe.
  • Verification. Checks Content Credentials, the certificate chain and your account's records, and reports the file's declared origin with reason codes. It is never a judgement about the content itself.
  • Public verify pages. verify_public() checks a file against an account's public verify page without an API key.
  • Webhooks. verify_webhook() checks Standard Webhooks signatures on raw request bodies.

Why use it

  • Fully typed (py.typed): every request body, parameter and response is a TypedDict, so mypy and Pyright catch a misspelled field before the request is sent.
  • Safe transfers: upload_file and download_file stream bytes and finish only after the expected length and SHA-256 match. API authorization is never forwarded to upload or download URLs.
  • Bounded by default: response size limits, request timeouts, and polling helpers with a wall deadline and cooperative cancellation.
  • Stable, typed errors (AuthenticationError, RateLimitError, ConflictError and others) carrying the API's reason code and request_id.
  • One dependency: httpx.

Install

pip install originalpictures-sdk

Or with uv:

uv add originalpictures-sdk

Requires Python 3.12 or later.

Quickstart: sign a file

Get an API key from the console (keys start with opk_). Upload the bytes, then accept a signing job for the asset.

import hashlib
import os
from pathlib import Path

from originalpictures import Client, idempotency_key, poll, sign_job_terminal

path = Path("photo.jpg")
digest = hashlib.sha256(path.read_bytes()).hexdigest()

with Client(
    base_url="https://api.originalpictures.com",
    api_key=os.environ["ORIGINALPICTURES_API_KEY"],
) as client:
    upload = client.create_upload(
        body={
            "byte_length": path.stat().st_size,
            "declared_media_type": "image/jpeg",
            "mode": "single",
            "sha256": digest,
        }
    )
    target = upload.get("upload")
    if target is None:
        raise RuntimeError("a single-mode upload returns one presigned request")
    client.upload_file(target["url"], path, sha256=digest, headers=target["headers"])
    client.complete_upload(
        body={},
        parameters={"upload_id": upload["upload_id"]},
        headers={"Idempotency-Key": idempotency_key()},
    )

    # C2PA only: no watermark and no fingerprint.
    job = client.accept_signing_job(
        body={"asset_id": upload["asset_id"], "watermark": False, "fingerprint": False},
        headers={"Idempotency-Key": idempotency_key()},
    )
    signed = poll(
        lambda: client.get_job(parameters={"job_id": job["job_id"]}),
        sign_job_terminal,
        timeout=300,
    )
    print(signed["state"], signed["recipe_id"])

sign_job_terminal is the completion test for job_... Sign snapshots. It fails closed on identifier or readiness drift: a completed Sign result must carry a ready vfy_... evidence ID. Use verification_job_terminal for vjob_... public Verify jobs; a vjob_... is only a pollable job and never immutable evidence.

Choose what a signing job adds

Every job signs with C2PA. Two flags choose what else it does (an omitted flag means false):

You want Request fields
C2PA Content Credentials only watermark: false, fingerprint: false
An invisible watermark, then C2PA watermark: true
A watermark and a fingerprint, then C2PA watermark: true, fingerprint: true
A fingerprint, then C2PA fingerprint: true

Send both flags every time (only an older key with legacy defaults may leave them out). The job reports the exact recipe_id it ran. Integrations that already send a recipe_id keep working; a flag sent with it must agree with that recipe or the request is refused.

Examples

1. Add an invisible watermark and fingerprints

watermarked = client.accept_signing_job(
    body={"asset_id": asset_id, "watermark": True},
    headers={"Idempotency-Key": idempotency_key()},
)
fingerprinted = client.accept_signing_job(
    body={"asset_id": asset_id, "watermark": True, "fingerprint": True},
    headers={"Idempotency-Key": idempotency_key()},
)

Each job is a separate signing of the asset. A watermark request accepts only media types that the deployment supports for watermarking; client.get_capabilities() lists them.

2. Declare AI generation (EU AI Act Article 50, SB 942)

To declare that a generative model produced the file, add ai_declaration and keep "watermark": False. A watermark together with an AI declaration is refused with JOB_POLICY_REJECTED and detail_code AI_DECLARATION_REQUIRES_SIGN_ONLY.

job = client.accept_signing_job(
    body={
        "asset_id": asset_id,
        "watermark": False,
        "fingerprint": True,
        "ai_declaration": {
            "software_agent_name": "Studio Nova Imagine",
            "software_agent_version": "2.0",
            "digital_source_type": "trainedAlgorithmicMedia",
        },
    },
    headers={"Idempotency-Key": idempotency_key()},
)

3. Download the signed file with integrity checks

result = signed["result"]
if result is None:
    raise RuntimeError("the job did not publish an asset")
grant = client.create_asset_download(parameters={"asset_id": result["asset_id"]})
client.download_file(
    grant["url"],
    Path("photo.signed.jpg"),
    byte_length=grant["byte_length"],
    sha256=grant["sha256"],
    headers=grant["headers"],
)

The download URL is a short-lived bearer capability: keep it out of logs. The file is written to a temporary name and moved into place only after its length and SHA-256 match.

4. Check a file on a public verify page (no API key)

from originalpictures import verify_public

result = verify_public("photo.jpg", page="studio-nova")
print(result["content_credentials"]["status"], result["made_with"]["answer"])
print(result["statement"])

verify_public states what the file declares about its origin and whether the page owner's system has a record of it. Show the result's statement with it. A file may be at most 25 MB, and a running check is polled every 2 seconds. verify_public_async is the asynchronous form, fetch_verify_page("studio-nova") reads the page's public settings, and Client.verify_public(slug, file) does the same on an existing client. Each check is billed to the page owner.

5. Verify a webhook delivery

from originalpictures import RequestValidationError, verify_webhook


def handle(raw_body: bytes, headers: dict[str, str], secret: str) -> bool:
    try:
        verify_webhook(
            raw_body,
            secret=secret,  # the whsec_... value shown once when the endpoint was created
            event_id=headers["webhook-id"],
            timestamp=headers["webhook-timestamp"],
            signature=headers["webhook-signature"],
        )
    except RequestValidationError:
        return False
    return True

Pass the exact raw request bytes: parsing and re-serializing the JSON first invalidates the HMAC. Several space-delimited signatures are accepted during secret rotation.

6. Async client and error handling

import asyncio

from originalpictures import AsyncClient, RateLimitError


async def recent_assets() -> None:
    async with AsyncClient(
        base_url="https://api.originalpictures.com", api_key="opk_..."
    ) as client:
        try:
            page = await client.list_assets(parameters={"limit": 20})
        except RateLimitError as error:
            print("retry later", error.code, error.request_id)
            return
        for item in page["items"]:
            print(item["asset_id"])


asyncio.run(recent_assets())

Configuration

Setting Where Default
API origin Client(base_url=...) none; use https://api.originalpictures.com
API key Client(api_key=...) none; read it from your secret store
Request timeout Client(timeout=...) (seconds) 30.0
JSON response bound Client(max_response_bytes=...) DEFAULT_MAX_RESPONSE_BYTES (16 MiB)
Custom HTTP client Client(http_client=httpx.Client) a private httpx.Client
Public check origin verify_public(..., api_base=...) https://api.originalpictures.com

The SDK reads no environment variables itself; the examples use ORIGINALPICTURES_API_KEY by convention, the same name the CLI and MCP server read. Do not put API keys in command arguments or logs. API base URLs may include a path prefix, which is preserved; query strings and fragments are rejected locally before any request or credential is sent.

poll_async cancels an in-flight coroutine at its wall deadline. poll runs each fetch in one of at most eight daemon workers and never queues work; when all are busy, new polls fail with PollingCapacityError. Give callbacks a finite I/O timeout no longer than the poll budget.

FAQ

Is this C2PA? Yes. Signing writes standard C2PA manifests (Content Credentials) that any C2PA-aware validator, such as c2patool, can read. Original Pictures is listed on the C2PA Conforming Products List as a generator product under the C2PA Conformance Program 0.2 (C2PA Technical Specification 2.4).

Is Original Pictures audited? Original Pictures Technologies, Inc. holds ISO/IEC 27001:2022 certification for its information security management system (GCAI, certificate GCAI-ISMS-PD91) and ISO/IEC 42001:2023 certification for its AI management system (TNV). It has a SOC 2 Type I report from Percilchofe CPA LLC as of 19 October 2026, and runs a GDPR compliance program. The certifications cover management systems, not this package. See Security and compliance.

Does it store my media? Files you upload are processed on Original Pictures' private servers, encrypted in transit and at rest, in isolated processing. Published outputs are kept for the retention your API key selects (1h, 24h, 72h or retain; new accounts default to 72 hours). Operator views show metadata only, and emergency access is audited. This is private server processing, not end-to-end encryption. See Retention and pricing.

What formats are supported? Embedded C2PA for JPEG, PNG, WebP, TIFF, AVIF, WAV, MP3, AAC (.m4a) and MP4 (H.264/AAC). Sign-only embedding for GIF, JPEG XL, SVG, DNG, HEIC/HEIF, QuickTime, AVI and FLAC. A sidecar .c2pa manifest for PDF, EPUB, DOCX, text, fonts and ML model files. Watermarks and fingerprints apply only to media types that can be decoded. client.get_capabilities() returns the live list; see Supported media.

Does a watermark replace C2PA? No. C2PA records declared origin in metadata; the watermark and fingerprints help find your record again when that metadata has been stripped. A file declared as AI-generated is signed without a watermark.

Does this make me meet the EU AI Act or SB 942? It produces machine-readable marking of the kind those laws describe. Whether your use meets an obligation is a legal question for your counsel; using the SDK does not by itself satisfy any law.

How much does it cost? See pricing. Signing jobs, watermarks, fingerprints and verifications are billable operations.

How do I get an API key? Sign in to the console and create one under API keys. Give each integration its own key with only the scopes it needs.

Licence

Licensed under the Apache License, Version 2.0; the full text is in the LICENSE file shipped with this package. Copyright © 2026 Original Pictures Technologies, Inc. The licence grants no rights to the Original Pictures name or logo.

Metadata

Release files for originalpictures-sdk 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for originalpictures-sdk 1.0.0
File Size Uploaded Provenance Actions
originalpictures_sdk-1.0.0.tar.gz 79.0 kB With provenance Details for originalpictures_sdk-1.0.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for originalpictures-sdk 1.0.0
File Interpreter ABI Platform Actions
originalpictures_sdk-1.0.0-py3-none-any.whl Python 3 none any Details for originalpictures_sdk-1.0.0-py3-none-any.whl

Total release size: 162.7 kB

Release files / originalpictures_sdk-1.0.0.tar.gz

Download URL originalpictures_sdk-1.0.0.tar.gz
Size 79.0 kB
Tags Source
SHA-256 checksum
How to use checksums
2c5b50b6e8bbb5e16a48bd87e62935ecff2a5b188e1479bbf1832a9caa3a8acd
BLAKE2b-256 checksum
How to use checksums
eebbbc41a60b32e89cfc313c8c525f8ed74d0114bdca4ea3e264c368e011c42a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 10, 2026.

Transparency log

Release files / originalpictures_sdk-1.0.0-py3-none-any.whl

Download URL originalpictures_sdk-1.0.0-py3-none-any.whl
Size 83.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
76094fa78a0eae4ecb4fcbe4b6b7005c2c75f073bb0d29ea06c6218cc7819b9e
BLAKE2b-256 checksum
How to use checksums
9eb1570f45b3575c24d14574b18ee5bd5e1377b9d74e3eb3351ccf6121dd2908
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 10, 2026.

Transparency log

Security for originalpictures-sdk 1.0.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for originalpictures_sdk-1.0.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 10, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page