# AI Image Provenance Check: C2PA, IPTC, AI Labels (`webify-lab/ai-image-provenance-check`) Actor

Scans the images on web pages (or direct image URLs) for AI provenance signals: C2PA Content Credentials, IPTC digitalSourceType, generator names in XMP/EXIF/PNG metadata and visible AI labels in alt text and captions. Reads only the metadata bytes. For EU AI Act Art. 50 audits.

- **URL**: https://apify.com/webify-lab/ai-image-provenance-check.md
- **Developed by:** [Batu T.](https://apify.com/webify-lab) (community)
- **Categories:** AI, SEO tools
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $7.00 / 1,000 page scanneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## AI Image Provenance Check: C2PA, IPTC & AI Labels on Websites

Find out which images on a website carry machine-readable AI markings and whether the page tells visitors about them. The Actor reads the provenance metadata of every image on the pages you give it (or of direct image URLs) and reports what it finds, image by image.

It downloads only the bytes that hold the metadata, using HTTP Range requests where the server supports them. In our tests that was one 64 KB request for most JPEG, PNG and WebP files, and up to about 400 KB for JPEGs with a large C2PA manifest, instead of the whole file.

### Why now

Article 50 of the EU AI Act applies from 2 August 2026. Providers of generative AI systems must mark synthetic images in a machine-readable, detectable way (Art. 50(2)); deployers must disclose deepfakes (Art. 50(4)). For generative AI systems placed on the market before 2 August 2026, the European Commission states that the Art. 50(2) marking obligation applies from 2 December 2026. Sources: European Commission FAQ "Transparency obligations under Article 50 of the AI Act" and the Council press release of 29 June 2026 on the AI Act simplification.

This Actor shows which images already carry such marks, which carry only weaker hints, and where an AI image appears without a visible label next to it.

### What is checked

| signal | where it is read from |
|---|---|
| C2PA / Content Credentials manifest | JPEG APP11 (JUMBF), PNG `caBX` chunk, WebP `C2PA` chunk, AVIF/HEIF `uuid` box. Decoded with the official Content Authenticity Initiative library (`@contentauth/c2pa-wasm`, MIT) |
| C2PA claim generator, signer, actions, digitalSourceType | from the decoded manifest, including ingredient manifests |
| IPTC digitalSourceType | XMP `Iptc4xmpExt:DigitalSourceType` with the IPTC NewsCodes vocabulary |
| IPTC AI fields | XMP `Iptc4xmpExt:AISystemUsed`, `AISystemVersionUsed` |
| Generator names | XMP `xmp:CreatorTool`, history `softwareAgent`, `photoshop:Credit`, EXIF `Software`, PNG text chunks of Stable Diffusion WebUI and ComfyUI |
| Visible AI label | alt text, title, `<figcaption>` and caption or credit elements next to the image (German and English wording such as "KI-generiert", "AI-generated", "generated with") |

No pixel analysis is done. The Actor reports marks that are present; it does not guess whether an unmarked image is AI-made.

### Verdicts

| verdict | meaning |
|---|---|
| `ai-marked` | C2PA or IPTC digitalSourceType says `trainedAlgorithmicMedia`, `compositeWithTrainedAlgorithmicMedia` or `compositeSynthetic` (created or edited with generative AI) |
| `likely-ai-hint` | no formal AI mark, but a known AI generator name in the metadata, an AI label in the page text, or `algorithmicMedia` |
| `no-signal` | none of the above found. Metadata may have been stripped by a CDN or CMS |

`aiWithoutVisibleLabel` is `true` when an image is `ai-marked` or `likely-ai-hint` from its metadata but no AI wording was found in alt text or caption.

### Output per image

| field | meaning |
|---|---|
| `pageUrl`, `imageUrl`, `element`, `alt` | where the image was found (`img`, `picture-source`, `css-background`, `direct`) |
| `format`, `width`, `height` | from the file header |
| `c2pa.present`, `c2pa.embedded` | manifest found in the file, or only a remote manifest link in XMP |
| `c2pa.generator`, `c2pa.issuer`, `c2pa.signedAt` | claim generator, certificate issuer and signing time |
| `c2pa.actions`, `c2pa.digitalSourceTypes` | actions such as `c2pa.created` and their IPTC source types |
| `c2pa.validation`, `c2pa.validationCodes` | `signature-ok` or `signature-issues` (metadata-only mode), or the library's `Valid`, `Invalid`, `Trusted` with full validation |
| `iptcDigitalSourceType`, `aiSystemUsed` | IPTC values from XMP |
| `generatorHints` | `{ generator, source, value }` for each AI tool name found |
| `textDisclosure` | `{ found, source, text }` |
| `verdict`, `reasons`, `aiWithoutVisibleLabel` | the result and why |
| `bytesFetched` | traffic used for this image |

One extra item per page (`type: page-summary`) counts images found, checked, skipped, `ai-marked`, `likely-ai-hint`, `no-signal`, with C2PA and without visible label, plus total bytes.

### Pricing

You pay per scanned page (`page-scanned`) and per direct image URL (`image-checked`). Pages or images that cannot be fetched are not charged.

### Limits

- Static HTML only. Images added by JavaScript after load, lazy-loaded galleries without `src`/`data-src` and images inside iframes are not seen.
- Many CDNs, CMS image pipelines and social networks strip metadata when resizing or converting. `no-signal` therefore does not mean "not AI". Images are requested like a browser (AVIF/WebP preferred); enable `requestOriginalFormat` to ask for the original file.
- Signature check without a trust list: the issuer is reported, but `signingCredential.untrusted` only means no trust list was configured, not that the signature is fake.
- In the default metadata-only mode the hash binding between manifest and pixels is not verified. Use full validation for that.
- PNG metadata after the image data and GIF, TIFF, SVG manifests are not read. C2PA in WebP and AVIF/HEIF is parsed per the C2PA reference implementation but was not yet seen on a live site during testing.
- This is a technical check, not legal advice.

Not affiliated with the C2PA, the Content Authenticity Initiative, Adobe, OpenAI, Google or IPTC.

### FAQ

**What am I charged for?** One `page-scanned` event per page that was loaded and scanned, with all its images included, and one `image-checked` event per direct image URL. Pages that fail or are disallowed by robots.txt come back as a `page-summary` item with an `error` and are not charged. Direct image URLs that cannot be read are not charged either.

**Does it detect AI images that carry no metadata?** No. There is no pixel analysis. The Actor only reports marks and labels that are present, so `no-signal` does not mean the image is not AI-made.

**Does it download every image completely?** No. It reads only the bytes that hold the metadata, with HTTP Range requests where the server supports them, up to `maxBytesPerImage` (default 2,000,000 bytes). Only `fullC2paValidation` downloads whole files that carry a C2PA manifest, to verify the hash over the image data.

**Do I need a proxy?** Usually not. With the default `auto` strategy requests go out directly, and a host is switched to the proxy only after it answers 403, 429 or 503 or the connection fails. `direct` never uses the proxy, `proxy` uses it for every request.

**Does it respect robots.txt?** Yes, by default pages that the site's robots.txt disallows for this bot (or for all bots) are skipped. Image files themselves are not checked against robots.txt. Set `respectRobotsTxt` to false to override.

### Use with AI agents (MCP)

This Actor works as a tool for Claude, ChatGPT, Cursor and other MCP clients through the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp). Add only this Actor:

```
https://mcp.apify.com?tools=webify-lab/ai-image-provenance-check
```

On first use the server signs you in with OAuth. Alternatively send the header `Authorization: Bearer <APIFY_TOKEN>`. Agents on the default Apify MCP setup can also find this Actor with `search-actors` and run it with `call-actor`. Pricing is the same as in Apify Console: you pay per result.

# Actor input Schema

## `pageUrls` (type: `array`):

Web pages to scan. Every <img> (largest srcset candidate) and <picture> source on the page is checked. Static HTML only: images injected later by JavaScript are not seen.

## `imageUrls` (type: `array`):

Image files to check directly (JPEG, PNG, WebP, AVIF, HEIC). Charged per image.

## `maxImagesPerPage` (type: `integer`):

Images checked per page, in page order.

## `minImageSize` (type: `integer`):

Images whose width and height (from HTML attributes or the file header) are both below this value are skipped as icons. 0 checks everything.

## `includePictureSources` (type: `boolean`):

Also check the alternative formats offered in <picture><source> (often WebP or AVIF). CDNs often strip metadata when converting, so the formats can differ.

## `includeCssBackgrounds` (type: `boolean`):

Also check url(...) images from inline style attributes and <style> blocks.

## `requestOriginalFormat` (type: `boolean`):

By default images are requested like a browser (AVIF and WebP preferred), so you see what visitors get. Some CDNs convert on the fly and drop metadata. Enable to send Accept: */* and receive the original file where the CDN allows it.

## `fullC2paValidation` (type: `boolean`):

By default only the metadata bytes are downloaded, so the manifest signature is checked but not the hash over the image data. Enable to download images that carry a C2PA manifest in full and validate the hash binding too. Costs more traffic.

## `maxBytesPerImage` (type: `integer`):

Safety limit for the metadata scan of one image. Also the limit for full validation downloads multiplied by 10.

## `maxPages` (type: `integer`):

Safety limit per run.

## `concurrency` (type: `integer`):

Pages processed at the same time. Each page checks up to 4 images in parallel.

## `respectRobotsTxt` (type: `boolean`):

Skip pages that the site's robots.txt disallows.

## `proxyStrategy` (type: `string`):

Automatic keeps costs low and only retries through the proxy after a 403, 429 or 503 response or a failed connection.

## `proxyConfiguration` (type: `object`):

Used when a site blocks direct requests.

## Actor input object example

```json
{
  "pageUrls": [
    "https://netzpolitik.org/2026/generative-ki-finger-weg-von-bildgeneratoren/",
    "https://commons.wikimedia.org/wiki/File:Logo_des_Ducky_Boys.png"
  ],
  "imageUrls": [
    "https://upload.wikimedia.org/wikipedia/commons/6/60/Logo_des_Ducky_Boys.png",
    "https://raw.githubusercontent.com/c2pa-org/public-testfiles/main/legacy/1.4/image/jpeg/truepic-20230212-camera.jpg"
  ],
  "maxImagesPerPage": 30,
  "minImageSize": 100,
  "includePictureSources": true,
  "includeCssBackgrounds": false,
  "requestOriginalFormat": false,
  "fullC2paValidation": false,
  "maxBytesPerImage": 2000000,
  "maxPages": 200,
  "concurrency": 3,
  "respectRobotsTxt": true,
  "proxyStrategy": "auto",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `results` (type: `string`):

One item per image (type image): pageUrl, imageUrl, element, alt, format, width, height, bytesFetched, c2pa {present, embedded, generator, issuer, signedAt, actions, digitalSourceTypes, validation, validationCodes, remoteManifestUrl}, iptcDigitalSourceType, aiSystemUsed, generatorHints, textDisclosure, verdict (ai-marked, likely-ai-hint, no-signal), reasons, aiWithoutVisibleLabel; plus one item per page (type page-summary) with counts; or error.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "pageUrls": [
        "https://netzpolitik.org/2026/generative-ki-finger-weg-von-bildgeneratoren/",
        "https://commons.wikimedia.org/wiki/File:Logo_des_Ducky_Boys.png"
    ],
    "imageUrls": [
        "https://upload.wikimedia.org/wikipedia/commons/6/60/Logo_des_Ducky_Boys.png",
        "https://raw.githubusercontent.com/c2pa-org/public-testfiles/main/legacy/1.4/image/jpeg/truepic-20230212-camera.jpg"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("webify-lab/ai-image-provenance-check").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "pageUrls": [
        "https://netzpolitik.org/2026/generative-ki-finger-weg-von-bildgeneratoren/",
        "https://commons.wikimedia.org/wiki/File:Logo_des_Ducky_Boys.png",
    ],
    "imageUrls": [
        "https://upload.wikimedia.org/wikipedia/commons/6/60/Logo_des_Ducky_Boys.png",
        "https://raw.githubusercontent.com/c2pa-org/public-testfiles/main/legacy/1.4/image/jpeg/truepic-20230212-camera.jpg",
    ],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("webify-lab/ai-image-provenance-check").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "pageUrls": [
    "https://netzpolitik.org/2026/generative-ki-finger-weg-von-bildgeneratoren/",
    "https://commons.wikimedia.org/wiki/File:Logo_des_Ducky_Boys.png"
  ],
  "imageUrls": [
    "https://upload.wikimedia.org/wikipedia/commons/6/60/Logo_des_Ducky_Boys.png",
    "https://raw.githubusercontent.com/c2pa-org/public-testfiles/main/legacy/1.4/image/jpeg/truepic-20230212-camera.jpg"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call webify-lab/ai-image-provenance-check --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,webify-lab/ai-image-provenance-check"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/pY7QfjSLhNoMhnKnc/builds/Y72Pkox19Jf0jX7R9/openapi.json
