realpng
Docs

Check an image

Send an image by URL, as base64 or as an upload, and read the verdict, the score and the three lanes behind it.

Send an image by URL

Check an image by URL
curl -X POST "https://realpng.com/api/v1/audit" \
  -H "X-API-Key: $REALPNG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "image_url": "https://images.example.com/beach.jpg"
  }'
200 OK
{
  "verdict": "ai",
  "score": 91,
  "decided_by": "pixels",
  "lanes": {
    "pixels": {
      "verdict": "ai",
      "score": 91
    },
    "watermark": {
      "verdict": "none",
      "score": 3
    },
    "provenance": {
      "verdict": "nothing",
      "issuer": null
    }
  },
  "media": "image"
}

realpng fetches the URL itself, so it must be a public http or https address. Redirects are followed. A private address, a host that is slow to answer or a URL that answers with an error comes back as a 400, listed in errors.

Each call sends exactly one of image_url, image_base64 or image_upload_id. It needs an API key and spends credits from your plan.

Send a small file as base64

Check a small file as base64
curl -X POST "https://realpng.com/api/v1/audit" \
  -H "X-API-Key: $REALPNG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "image_base64": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAF0lEQVR4nGM4UREQteASg0bUtGd3TgAALTYG/9S1V3UAAAAASUVORK5CYII="
  }'

Send the bytes as plain base64 or as a data URL. The data: prefix is removed before decoding.

Base64 travels inside the request, so its size limit is lower than for a URL or an upload. Send anything bigger as an upload.

Send an uploaded file

Check an uploaded file
curl -X POST "https://realpng.com/api/v1/audit" \
  -H "X-API-Key: $REALPNG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "image_upload_id": "5d1f7a3c-2b8e-4f6a-9c0d-8e3b1a7f4c62"
  }'

Any file on disk can go through POST /uploads first. You PUT the bytes to a one-time URL, then send its id as image_upload_id. Upload a local file covers the full flow, including what a retry needs.

Read the verdict

A 200 carries these fields. Agents get the same body from the audit_image tool. See MCP tools.

FieldTypeWhat it holds
verdictstringai, real, uncertain or unavailable. The field to act on
scoreinteger or null0 to 100, higher reads more like AI. Null only when the verdict is unavailable
decided_bystringWhich evidence set the verdict
lanesobjectWhat each lane found on its own: pixels, watermark and provenance
mediastringimage, or video for a video
detailobjectOnly with detail=full. See get the full engine report

Verdict

ValueMeaningWhat to do
aiThe image reads as AI generated, or carries proof that it isHandle it as AI generated
realThe image reads as a photograph, or carries a signed camera captureHandle it as real, within the limits
uncertainThe evidence sits in between, or proof points both waysHave a person look, or treat it as unknown
unavailableNo lane found proof and the pixel lane could not read the imageRetry later

Score

The score orders images by how strongly they read as AI, from 0 to 100. Use it to sort or rank, such as reviewing the highest first.

Branch on verdict, not on the score. The cut points between real, uncertain and ai are internal and may change, so a threshold you pick today can drift. When proof decides, the score is pinned to a set value whatever the pixels said, as the next table shows.

What decided it

decided_byWhenVerdict and score
watermarkA SynthID watermark was foundai, score pinned near 100
provenanceA valid signed C2PA manifest was foundai near 100 for signed_ai, real near 0 for signed_capture
pixelsNo proof and nothing to weigh. Also the value when the verdict is unavailableThe pixel lane's own verdict and score
weighedNo proof, but an unsigned AI label or a faint watermark raised the pixel scoreThe raised score, which never reaches either end, and the verdict it falls in
conflictAI proof and camera proof at once, such as a watermark on a signed captureuncertain, score in the middle

A watermark decides even when the pixels are unsure:

{
  "verdict": "ai",
  "score": 99,
  "decided_by": "watermark",
  "lanes": {
    "pixels": { "verdict": "uncertain", "score": 38 },
    "watermark": { "verdict": "present", "score": 96 },
    "provenance": { "verdict": "nothing", "issuer": null }
  }
}

Read the lanes

Each lane keeps its own vocabulary, so the watermark lane reads present or none, never ai or real.

Pixels

A panel of trained pixel detectors reads the image. The lane reads pixels only and knows nothing about who made the file.

It averages the detectors on several overlapping crops and takes the median crop, so a caption or sticker over one part does not decide the verdict. An image too small to crop is read whole.

verdictMeaning
aiThe pixels read as AI generated
realThe pixels read as a photograph
uncertainThe pixels sit in between
unavailableToo few detectors returned a reading, and score is null

score is 0 to 100, or null.

What it cannot see. Detection is measured on original files. An ordinary JPEG re-encode makes it markedly weaker, so screenshots, social media copies and recompressed files are caught less often. Generators the panel was not tuned on are caught less reliably.

Watermark

The lane looks for SynthID, the invisible watermark Google embeds in images from its generators.

verdictMeaning
presentSynthID was found. This is proof of AI and decides the verdict
faintA weak trace. It raises the score but never decides on its own
noneNo SynthID was found. This does not mean the image is real
unavailableThe watermark model did not run, and score is null

score is the watermark model's own reading, 0 to 100, or null.

What it cannot see. Any watermark other than SynthID. An image from a generator that does not embed SynthID reads none.

Provenance

The lane reads the file's metadata, not its pixels. It uses no model and always answers, so it has no unavailable value.

verdictMeaningissuer
signed_aiA C2PA manifest that validates, from a known AI issuer or declaring an AI source. Proof of AIThe signer, such as OpenAI
signed_captureA C2PA manifest that validates and records a digital capture, such as from a signing camera. Proof of a real captureThe signer
ai_labelAn unsigned AI label: an AI digital source type in IPTC XMP, an unverified C2PA AI action, or generator settings in PNG textnull
nothingNone of the abovenull

An ai_label raises the score but never decides on its own. Anyone can add or remove unsigned metadata, so only a signature counts as proof. Stray bytes that happen to spell a generator's name are ignored.

What it cannot see. Most re-encodes, screenshots and social platforms strip metadata, so nothing is common and says nothing either way. A missing signature is not evidence of AI.

Handle an unavailable verdict

The verdict is unavailable when no lane found proof and the pixel lane could not read the image. With a watermark present or a signed manifest, the verdict is still decided even if the pixel lane is unavailable.

{
  "verdict": "unavailable",
  "score": null,
  "decided_by": "pixels",
  "lanes": {
    "pixels": { "verdict": "unavailable", "score": null },
    "watermark": { "verdict": "none", "score": 4 },
    "provenance": { "verdict": "nothing", "issuer": null }
  }
}

It is a normal 200, not an error, and it is charged as a completed check. Retry the same image later. The full engine report names the detector that dropped out.

The watermark lane can also answer unavailable on its own. The verdict then comes from the other lanes as usual.

An engine that is still warming up is different. It answers 503 before any lane runs, as described in errors.

Get the full engine report

Check with the full engine report
curl -X POST "https://realpng.com/api/v1/audit?detail=full" \
  -H "X-API-Key: $REALPNG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "image_url": "https://images.example.com/beach.jpg"
  }'

Add detail=full to the query to get detail beside the usual fields. It holds the raw engine report: each detector's own reading, the evidence per lane, the decoded image's size and format, and how long the engine took (engine_ms).

The shape of detail can change without notice. Read verdict, score, decided_by and lanes in code, and keep detail for debugging and support.

Know what a check cannot tell you

  • Only watermark, provenance and conflict in decided_by rest on proof. A pixels or weighed verdict is a statistical reading and can be wrong.
  • A real verdict from the pixels means the image does not read as AI. It does not prove a camera took it.
  • Each lane has blind spots of its own. Recompressed copies weaken the pixels, the watermark lane sees SynthID only, and most platforms strip metadata. Read the lanes has the detail.

Handle errors

Every error comes back as JSON with a stable code. Errors lists each one with its status and fix.

Next steps