Magic Tools
AI TutorialsBy CooconAugust 16, 2026311 views7 min read

OpenAI Content Provenance API: C2PA and SynthID in One Call

The topic of content provenance has moved from standard documents into various products over the past year: Anthropic added watermarks and C2PA signatures to Claude's outputs, Google launched SynthID, and industry verification tools have gradually become more complete. OpenAI recently added its own piece to the puzzle—the Content Provenance API: a synchronous interface where uploading a file returns "whether this content carries OpenAI's provenance signals".

This article will break down the API details and field semantics clearly, focusing on a few points in the official documentation that are easily misinterpreted.

What the API Does: Dual-Signal Check

Upload an image or audio file to POST /v1/content_provenance_checks. The API checks two types of signals simultaneously:

Signal Applicable Media What It Checks Characteristics
C2PA Content Credentials Images Embedded signature metadata in the file (issuer, AI generation markers) Information-rich, but editing/forwarding/re-saving strips it
SynthID Images + Audio Watermarks directly embedded in pixels/waveforms Less information, but resilient to some transformations

The two signals—one fragile, one robust—complement each other. This is precisely the "metadata + pixel watermark dual-track" approach we mentioned in the "C2PA Content Credentials Verification Guide" realized as a product.

The call is synchronous: the response is the result, no need to create background tasks, poll, or use the Files API first. If you don't want to write code, the official web version is also available at openai.com/verify.

curl https://api.openai.com/v1/content_provenance_checks \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -F "file=@./example.png;type=image/png"

Python SDK (2.52.0+):

from openai import OpenAI

client = OpenAI()

with open("./example.png", "rb") as image:
    result = client.content_provenance_checks.create(
        file=("example.png", image, "image/png"),
    )

Return Structure and Field Semantics

A typical response for an image:

{
  "object": "content_provenance_check",
  "created_at": 1778000000,
  "results": [
    {
      "type": "c2pa",
      "outcome": "detected",
      "validation_state": "trusted",
      "issuer": "OpenAI OpCo, LLC",
      "model": "gpt-image",
      "generated_at": "2026-07-27T18:34:12Z"
    },
    {
      "type": "synthid",
      "outcome": "not_detected",
      "model": null,
      "generated_at": null
    }
  ]
}

Several design details are worth noting:

  • There is no top-level outcome. An image returns two results: C2PA + SynthID; audio only returns one: SynthID. Each must be interpreted independently; the API does not summarize "is/is not AI-generated" for you.
  • Inapplicable checks are omitted directly, rather than returning not_detected—when parsing, search by type, do not access by index.

C2PA Result: "detected" Criteria Are Stricter Than Expected

For outcome to be detected, three conditions must be met simultaneously: the manifest's validation_state is trusted or valid, the issuer is OpenAI, and the manifest contains an AI generation action. If any condition fails—a manifest issued by a third party, a manifest without an AI generation action, an invalid (signature verification failed) or not_present (no manifest) state—the result is not_detected.

This means an important interpretation technique: when outcome is not_detected, the issuer and validation_state fields may still contain information. For example, an image signed by Adobe Firefly will have an outcome of not_detected (not signed by OpenAI), but issuer will tell you who signed it. Looking only at the outcome misses this information.

The four-tier semantics of validation_state are consistent with c2pa-rs: trusted (signature valid and certificate trusted), valid (signature valid but certificate not on the trust list), invalid (verification failed, cannot be used as provenance evidence), not_present (no manifest). The difference between valid and invalid was explored with a real-world pitfall case in our C2PA guide—signature validity and issuer trust are two independent dimensions; do not mix them.

SynthID Result

The semantics are much simpler: detected means a supported watermark was identified; not_detected only means none was detected, it does not rule out the content being AI-generated or AI-modified. model and generated_at are provided if available, otherwise null.

Supported Formats and Limitations

  • Images: PNG, JPEG, WebP
  • Audio: MP3, Opus, AAC, FLAC, WAV, PCM; decoded duration ≤ 60 seconds. For Opus, set the media type to audio/ogg.
  • Single file ≤ 50 MiB, one file per request
  • Error Codes: 400 for unsupported/corrupted file format; 404 for organization lacking access; 429 for rate limit exceeded (includes Retry-After)
  • Rate limits are strict; officials state this is to prevent abuse. Higher quotas require a separate application and case-by-case approval.
  • Does not apply to Zero Data Retention—note this for scenarios with strict data residency requirements.

Boundaries Set by OpenAI (More Important Than the API Itself)

The documentation contains a usage guidance section with a very restrained attitude, worth quoting verbatim:

  1. It is not a general-purpose AI detector. It only recognizes OpenAI's own signals and cannot detect content generated by models from other companies—not_detected neither proves "content is human-written" nor "not AI-generated".
  2. The list of reasons for not_detected is long: metadata stripped, watermarks degraded by compression/cropping/screenshots/format conversion, content from an older model before signals were added, or the format itself unsupported. Verification with the original file yields more meaningful conclusions.
  3. Check the issuer before attribution. Detecting a C2PA manifest does not equal OpenAI generation; manifests from third parties should be attributed by issuer.
  4. High-risk scenarios require manual review; do not treat automated results as final judgment.
  5. Repeated querying to reverse-engineer, remove, or circumvent watermarks is explicitly prohibited.

This straightforwardness aligns with Anthropic's stance in Claude's watermark documentation (see "What is Claude's Invisible Watermark"): provenance signals are one-way weak evidence—detection confirms a specific fact, while non-detection confirms nothing. The industry is remarkably uniform in not exaggerating on this point.

When to Use It, When Not To

Suitable for: Adding a layer of definitive evidence for "suspected AI images/audio" in content moderation pipelines, image verification in newsrooms, platforms labeling AI-generated content, and trust & safety workflows.

Not suitable/unnecessary for:

  • Determining whether any arbitrary content is AI-generated—it only covers OpenAI signals, and there is currently no reliable universal answer to this question.
  • Only wanting to see the C2PA credentials of an image—no API key or rate quota needed; use a browser-based local verification tool in ten seconds: MagicTools C2PA Verifier runs signature verification locally via WASM, files are not uploaded, and it works for any issuer—OpenAI, Claude, Adobe, or cameras.
  • Checking text watermarks—this API only accepts images and audio. For the current state of text watermarking (character-level steganography can be checked; statistical watermarks await official detectors), see "AI Text Invisible Watermark Detection Guide".

Common Questions FAQ

Can the Content Provenance API detect images generated by Midjourney, Claude, or Stable Diffusion?

No, it cannot return detected. The API only recognizes OpenAI's own C2PA signatures and watermark signals. However, for images with third-party C2PA manifests, the issuer and validation_state in the response will still describe that manifest—you can know "this was signed by Adobe", just not with a detected outcome.

Does this API cost money? How do I get access?

The endpoint is available to authorized organizations, with strict rate limits. Higher quotas require an application via the official form and case-by-case approval. For specific billing, refer to OpenAI's official pricing page. Note that it does not apply to the Zero Data Retention policy.

Can not_detected prove an image is not AI-generated?

No. Metadata stripping, watermark degradation, products from older models, or generation by other vendors' models will all result in not_detected. Officials explicitly state it is "evidence of non-detection", not "proof of human creation".

What's the difference in the response for images versus audio?

Images return two results: C2PA and SynthID; audio only returns one: SynthID. The API omits inapplicable checks; parse by matching the type field, do not assume a specific array length.

Is there a way to verify without writing code?

Yes, there are two: OpenAI's official web version at openai.com/verify (checks OpenAI's dual signals), and the MagicTools C2PA Verifier (browser-based local verification, works for any issuer, files not uploaded). The latter is faster for routine image checks; the former is for when SynthID determination is needed.

Related Articles

Dev Breakfast · 2026-10-01

Today's headline: The Same Prompt: Why Some People's Output Looks Like a Designer Made It. Plus 4 more: Pi.dev: From "No MCP" to Shoving MCP into the Kernel; Gemini 4 Argon Is Priced at $2: Cybersecurity Teams Get It First; and more.

daily-intelOct 1, 20268 min
4

Dev Breakfast · 2026-09-30

Today's headline: Anthropic Prospectus: Revenue Increased 12 Times, Loss of 42 Billion. Plus 4 more: 0.8B Model Trained at Home: Choose One from 254 Options in 28 ms; 7 ESP32-S3 Chips Chained Together to Run a 0.5B 1.58-bit Model; and more.

daily-intelSep 30, 20268 min
63
Claude Code install errors, reproduced: EACCES, a 600s mirror stall, Node 20 silently getting an old version, a region-block install.sh, and the native installer removing your npm copy

Claude Code install errors, reproduced: EACCES, a 600s mirror stall, Node 20 silently getting an old version, a region-block install.sh, and the native installer removing your npm copy

I reproduced every Claude Code install failure I could on macOS: 15 verbatim errors, each with wall time and exit code. npm -g into /usr/local fails with EACCES, exit 243. A cache dir that is merely 0555 gets blamed on root-owned files, with sudo chown advice. From Beijing, npmmirror took 147s and then >600s, npmjs 11-12s (2 samples each). On Node 20, an unpinned install silently lands on 2.1.197. Fetching claude.ai/install.sh from a blocked region gives curl exit 0 and a 447 KB HTML page. The native installer runs npm uninstall -g on your npm copy without saying so; it removed mine.

claude-codetroubleshooting+5
pitfallsSep 29, 202611 min
77

Dev Breakfast · 2026-09-29

Today's headline: Adding 'Do not guess' cuts hallucination rate from 71% to 20%. Plus 4 more: Go's import path tied to GitHub: how much code changes when switching hosting; Sonnet 5.5 released: Terminal-Bench jumps from 10.3% to 70.6%; and more.

daily-intelSep 29, 20267 min
216

Published by Magic Tools