API Reference
The MarkMyAI API adds four layers of verifiable provenance to AI-generated images: invisible watermark, C2PA publisher signature, audit trail with fingerprint recovery, and blockchain anchoring on Polygon.
Base URL
https://www.markmyai.com/api
Protocol
HTTPS only
Format
JSON (application/json)
Auth
Bearer token
Beta: The API is fully functional. C2PA manifests use test certificates — ContentCredentials.org will show "Unknown Signer". Production certificates will be issued before public launch.
Authentication
All API requests require a Bearer token in the Authorization header. Get your key from the Dashboard.
curl -X POST https://www.markmyai.com/api/v1/mark \
-H "Authorization: Bearer mk_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"image_url": "https://example.com/image.jpg"}'API keys are prefixed with mk_live_.
Keys are shown once at generation time. Store them securely — we only save a hash.
Never expose your key in client-side code or public repositories.
Rate Limits
| Plan | Requests / second | Marks / month |
|---|---|---|
| Free | 2 / sec | 20 |
| Starter | 5 / sec | 200 |
| Business | 20 / sec | 2,000 |
| Enterprise | 50 / sec | Custom |
/v1/markMark an Image
Creates an official publisher version of an AI image with four protection layers: invisible watermark (pixel-level), C2PA publisher signature, audit trail with fingerprint, and blockchain anchor. Returns a permanent verify URL.
Request Body
| Field | Type | Description |
|---|---|---|
image_url* | string | Publicly accessible URL of the image to mark. JPEG, PNG or WebP. Max 12 MB. |
ai_model | string | Name of the AI model used to generate the image (e.g. 'dall-e-3', 'midjourney-v6'). |
creator | string | Name of the organization or individual publishing the image. |
purpose | string | Intended use of the image (e.g. 'marketing', 'editorial', 'social'). |
wm_variant | string | Watermark variant: 'standard' (default, best all-round) or 'crop-resilient' (optimized for heavy cropping, e.g. social media thumbnails). |
curl -X POST https://www.markmyai.com/api/v1/mark \
-H "Authorization: Bearer mk_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"image_url": "https://example.com/ai-generated.jpg",
"ai_model": "dall-e-3",
"creator": "Acme Corp",
"purpose": "marketing"
}'Response
{
"status": "success",
"mark_id": "mk_a3f9b2e4d1",
"marked_image_url": "https://... (fresh signed delivery URL)",
"delivery_channel": "api",
"file_expires_at": "2026-03-11T10:30:00.000Z",
"file_retention_days": 1,
"verify_url": "https://markmyai.com/api/verify/mk_a3f9b2e4d1",
"layers": {
"watermark": {
"status": "embedded",
"type": "trustmark",
"note": "Invisible watermark in pixel data, designed to survive common transformations"
},
"c2pa_embedded": {
"status": "signed",
"signer": "MarkMyAI (Beta)",
"standard": "C2PA v2"
},
"audit_log": {
"status": "recorded",
"entry_hash": "a7f3c...",
"fingerprint": "f0e1d2c3..."
},
"blockchain_anchor": {
"status": "pending",
"chain": "polygon",
"note": "Anchoring in progress. Confirmation typically within 30 seconds."
}
},
"image": {
"sha256": "3a9f...",
"fingerprint": "f0e1d2c3...",
"format": "jpeg",
"width": 1024,
"height": 1024,
"size_bytes": 245760
},
"timestamp": "2026-03-06T10:30:00.000Z",
"request_id": "req_TAVQgHELicx6"
}Important: publish the marked version
The marked_image_url is a short-lived signed URL for immediate delivery. Store the marked image right away. API delivery files are retained for 24 hours. If you need a fresh URL during that window, call GET /v1/download/:mark_id. The verify_url is permanent.
/v1/download/:mark_idGet a Fresh Download URL
Returns a fresh signed download URL for the marked delivery file that belongs to your API key. Use this if the original marked_image_url has expired but the delivery file is still within its retention window.
curl https://www.markmyai.com/api/v1/download/mk_a3f9b2e4d1 \
-H "Authorization: Bearer mk_live_xxxxxxxxxxxx"Response
{
"status": "success",
"mark_id": "mk_a3f9b2e4d1",
"download_url": "https://... (signed URL, valid 1h)",
"expires_in_seconds": 3600
}If the file has expired
{
"error": "Marked file no longer available",
"code": "delivery_file_expired",
"mark_id": "mk_a3f9b2e4d1",
"verify_url": "https://markmyai.com/api/verify/mk_a3f9b2e4d1",
"proof_pdf_url": "https://markmyai.com/api/proof-pdf/mk_a3f9b2e4d1",
"proof_json_url": "https://markmyai.com/api/proof/mk_a3f9b2e4d1"
}Retention policy
API delivery files are retained for 24 hours. After that, this endpoint returns 410 Gone. The proof record, manifest, verify URL, Proof PDF and Proof JSON remain available.
/v1/detectDetect a Mark
Checks whether an image was previously processed by MarkMyAI. Uses four detection methods: C2PA metadata, invisible watermark, exact hash match, and perceptual fingerprint. Can recover a proof path even after compression, resizing, or metadata stripping.
Request Body
| Field | Type | Description |
|---|---|---|
image_url* | string | URL of the image to check. JPEG, PNG or WebP. |
curl -X POST https://www.markmyai.com/api/v1/detect \
-H "Authorization: Bearer mk_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"image_url": "https://example.com/suspected-ai-image.jpg"}'Response
{
"status": "success",
"is_marked": true,
"query_fingerprint": "f0e1d2c3...",
"c2pa": {
"embedded": true,
"data": { "...full C2PA manifest JSON..." }
},
"watermark": {
"found": true,
"crc_valid": true,
"match_mark_id": "mk_a3f9b2e4d1",
"match_token": "a3f9b2e4d1",
"payload_hash": "7c3e...",
"encoding": "BCH_SUPER",
"decode_ms": 4200
},
"database_match": {
"found": true,
"method": "c2pa_embedded",
"mark_id": "mk_a3f9b2e4d1",
"verify_url": "https://markmyai.com/api/verify/mk_a3f9b2e4d1",
"audit_record": {
"creator": "Acme Corp",
"ai_model": "dall-e-3",
"marked_at": "2026-03-06T10:30:00.000Z"
}
},
"fingerprint_matches": [
{
"mark_id": "mk_a3f9b2e4d1",
"verify_url": "https://markmyai.com/api/verify/mk_a3f9b2e4d1",
"creator": "Acme Corp",
"ai_model": "dall-e-3",
"marked_at": "2026-03-06T10:30:00.000Z",
"similarity": "100%"
}
],
"recovery_paths": {
"c2pa": "Embedded C2PA manifest found",
"watermark": "Invisible watermark recovered — linked to mk_a3f9b2e4d1",
"fingerprint": "Fingerprint match: https://markmyai.com/api/verify/mk_a3f9b2e4d1",
"watermark_verify_url": null
},
"image": {
"fingerprint": "f0e1d2c3..."
},
"request_id": "req_TAVQgHELicx6"
}Detection Logic
The endpoint runs all four detection methods in parallel. is_marked is true when at least one of the following succeeds:
c2pa_embeddedEmbedded publisher proof found in image metadata
watermarkInvisible watermark detected and CRC validated
sha256_exactExact pixel match in proof history
fingerprint_fuzzyPerceptual match after common transforms
Response Fields
| Field | Description |
|---|---|
is_marked | Overall verdict. True if any of the four detection layers returned a positive signal. |
c2pa.embedded | Whether a valid C2PA manifest was found and cryptographically validated using the @contentauth/c2pa-node reader. |
c2pa.data | Full parsed C2PA manifest JSON (signer, claim generator, actions, ingredients). Null if no C2PA found. |
watermark.found | Whether a TrustMark invisible watermark was detected in the pixel data. |
watermark.crc_valid | Whether the extracted watermark payload passes CRC integrity check. A valid CRC confirms the watermark is genuine. |
watermark.match_mark_id | The mark_id recovered from the watermark payload, if identifiable. Links back to the original proof chain. |
watermark.encoding | Watermark encoding scheme used (BCH_SUPER for current marks, BCH_5 for legacy). |
watermark.decode_ms | Time in milliseconds to decode the watermark. Typical: 3–8 seconds depending on image size. |
database_match | Best matching record from the audit trail, found via perceptual fingerprint. Includes mark_id, verify_url, creator, and AI model. |
fingerprint_matches | Up to 5 closest fingerprint matches from the audit log, sorted by similarity. Each includes a verify URL and similarity percentage. |
recovery_paths | Human-readable summary of which layers succeeded and how to follow the proof chain for each. |
Performance note
Watermark decoding adds 3–8 seconds to the response time. The endpoint has a maxDuration of 60 seconds. If you only need C2PA + fingerprint without watermark, the response is typically under 2 seconds.
/api/check-watermarkCheck Watermark, C2PA & Fingerprint
Public endpoint used by the free checker. Accepts an image as multipart form data and runs three detection methods: invisible watermark recovery (TrustMark), C2PA extraction (via c2pa-node with regex fallback), and perceptual fingerprint matching against the audit trail. Fingerprint matching is especially important for CMS-resized or re-encoded renditions where the watermark may have been lost. No authentication required.
Request (multipart/form-data)
| Field | Type | Description |
|---|---|---|
image* | file | The image file to analyze. JPEG, PNG, or WebP. Max 20 MB. |
mark_id | string | Optional mark ID to verify against (enables targeted watermark decoding). |
curl -X POST https://www.markmyai.com/api/check-watermark \
-F "image=@photo.jpg" \
-F "mark_id=mk_a3f9b2e4d1"Response
{
"watermark_found": true,
"crc_valid": true,
"match_mark_id": "mk_a3f9b2e4d1",
"match_token": "a3f9b2e4d1",
"payload_hash": "7c3e...",
"token_hex": "a3f9b2e4d1",
"version": "1",
"variant": "standard",
"encoding": "BCH_SUPER",
"decode_ms": 4200,
"c2pa_found": true,
"c2pa_claim_generator": "OpenAI DALL·E 3",
"c2pa_signer": "OpenAI, Inc.",
"c2pa_title": null,
"fingerprint_match": {
"mark_id": "mk_a3f9b2e4d1",
"similarity": "98%",
"creator": "Acme Corp",
"ai_model": "dall-e-3",
"verify_url": "https://markmyai.com/api/verify/mk_a3f9b2e4d1",
"marked_at": "2026-03-06T10:30:00.000Z"
},
"query_fingerprint": "f0e1d2c3..."
}C2PA Fields
The endpoint extracts C2PA metadata server-side using the @contentauth/c2pa-node Reader for proper CBOR/JUMBF parsing. If the Reader cannot process the file (e.g. truncated manifests), it falls back to regex pattern matching on the raw binary data. This two-layer approach reliably recovers generator and signer information from images produced by OpenAI, Adobe, Google, Midjourney, and other C2PA-enabled platforms.
| Field | Description |
|---|---|
c2pa_found | Whether C2PA / JUMBF / Content Credentials markers were detected in the file. |
c2pa_claim_generator | The tool or platform that generated the image (e.g. "OpenAI DALL·E 3", "Adobe Firefly", "MarkMyAI"). Parsed from the C2PA manifest via c2pa-node Reader, with regex fallback. Null if not identifiable. |
c2pa_signer | The organization that signed the C2PA manifest, extracted from the X.509 certificate or signature_info. Null if not identifiable. |
c2pa_title | The title field from the C2PA manifest, if present. |
Fingerprint Matching
In addition to watermark and C2PA analysis, the endpoint computes a perceptual fingerprint of the uploaded image and compares it against the MarkMyAI audit trail. If a match is found (Hamming distance < 10), the response includes the best match with its mark_id, which is then used as an effectiveToken for targeted watermark decoding. This allows recovery of provenance even when the invisible watermark has been destroyed by aggressive resizing, re-encoding, or screenshot capture.
| Field | Description |
|---|---|
fingerprint_match | Best matching record from the audit trail. null if no similar image was found. |
fingerprint_match.mark_id | The mark_id of the best matching audit log entry. |
fingerprint_match.similarity | Similarity percentage between the uploaded image and the matched record (e.g. "98%"). |
fingerprint_match.creator | The creator who originally marked the image. |
fingerprint_match.ai_model | The AI model declared when the image was originally marked. |
fingerprint_match.verify_url | Direct link to the full proof record for the matched mark. |
fingerprint_match.marked_at | ISO timestamp of when the matched image was originally marked. |
query_fingerprint | The perceptual fingerprint computed for the uploaded image (hex string). Useful for debugging. |
Recovery via fingerprint
When the watermark cannot be decoded (e.g. image was resized by a CMS, converted to a different format, or heavily compressed), the fingerprint match provides a fallback path to recover the full proof chain. The public checker at markmyai.com/check shows this as "Recovered Provenance" with a link to the original publisher proof.
/api/verify/:mark_idVerify a Mark
Public endpoint. Returns the publisher proof record for a given mark ID, including provenance, audit data, and blockchain state when available. No authentication required.
curl https://markmyai.com/api/verify/mk_a3f9b2e4d1Response
{
"mark_id": "mk_a3f9b2e4d1",
"status": "verified",
"proof_level": "verified_provenance",
"proof_level_description": "Core provenance signals found and verified.",
"proof_pdf_url": "https://markmyai.com/api/proof-pdf/mk_a3f9b2e4d1",
"proof_json_url": "https://markmyai.com/api/proof/mk_a3f9b2e4d1",
"provenance": {
"creator": "Acme Corp",
"ai_model": "dall-e-3",
"remote_manifest": "https://markmyai.com/api/verify/mk_a3f9b2e4d1"
},
"image": {
"fingerprint": "f0e1d2c3...",
"sha256": "3a9f...",
"watermark_embedded": true
},
"audit": {
"entry_hash": "a7f3c...",
"previous_hash": "6b2d1..."
},
"blockchain": {
"status": "anchored",
"anchor_hash": "0xa3f9...",
"tx_hash": "0x9bc4...",
"chain": "polygon",
"anchored_at": "2026-03-06T10:30:25.000Z",
"explorer_url": "https://polygonscan.com/tx/0x9bc4..."
},
"delivery": {
"channel": "api",
"retention_days": 1,
"file_expires_at": "2026-03-11T10:30:00.000Z",
"file_available": false,
"storage_deleted_at": "2026-03-11T10:31:02.000Z"
},
"marked_at": "2026-03-06T10:30:00.000Z"
}Proof Levels
The proof_level field provides a human-readable assessment:
| Level | Meaning |
|---|---|
verified_provenance | Core provenance signals found and verified. Origin traceable with high confidence to a publishing organization. |
recovered_provenance | Embedded metadata was stripped, but recovery signals (invisible watermark, audit trail) connect this image to a verified proof chain. |
no_verifiable_provenance | No reliable provenance signals found through MarkMyAI. |
Proof Export
Every verified mark has downloadable proof in two formats:
Proof PDF — for humans (legal, compliance, print):
curl https://markmyai.com/api/proof-pdf/mk_a3f9b2e4d1 -o proof.pdfProof JSON — for systems (DAMs, archives, automation):
curl https://markmyai.com/api/proof/mk_a3f9b2e4d1 -o proof.jsonBoth formats are self-contained: they include the blockchain anchor strings (on-chain pseudonymized + plaintext) and instructions for independent verification without MarkMyAI servers.
Recommended: Store the Proof JSON as a sidecar file alongside the image (image.jpg + image.proof.json) for searchable, machine-readable proof archival.
Errors
All errors return a JSON body with an error field and a request_id for debugging.
| Status | Code | Meaning |
|---|---|---|
| 401 | Unauthorized | Missing or invalid API key |
| 400 | Bad Request | Missing required field (e.g. image_url) |
| 410 | Gone | Temporary delivery file has expired; use proof links instead |
| 413 | Payload Too Large | Image exceeds 12 MB limit |
| 422 | Unprocessable | Invalid image format or image too large (>10k×10k px) |
| 429 | Rate Limited | Too many requests or monthly limit reached |
| 504 | Timeout | Image download took too long (>5s) |
| 500 | Server Error | Internal error — contact support with request_id |
Code Examples
Mark an image in your language of choice.
Python
import requests
API_KEY = "mk_live_xxxxxxxxxxxx"
BASE_URL = "https://www.markmyai.com/api"
def mark_image(image_url: str, ai_model: str, creator: str):
response = requests.post(
f"{BASE_URL}/v1/mark",
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"image_url": image_url,
"ai_model": ai_model,
"creator": creator,
}
)
response.raise_for_status()
data = response.json()
print(f"Marked! verify_url: {data['verify_url']}")
return data
result = mark_image(
image_url="https://example.com/my-ai-image.jpg",
ai_model="dall-e-3",
creator="My Company"
)
# Detect: C2PA + Watermark + Fingerprint + DB in one call
def detect_image(image_url: str):
response = requests.post(
f"{BASE_URL}/v1/detect",
headers={"Authorization": f"Bearer {API_KEY}"},
json={"image_url": image_url}
)
response.raise_for_status()
data = response.json()
print(f"Marked: {data['is_marked']}")
if data["watermark"]["found"]:
print(f" Watermark: CRC valid={data['watermark']['crc_valid']}")
if data["c2pa"]["embedded"]:
print(f" C2PA: embedded manifest found")
if data["fingerprint_matches"]:
print(f" DB match: {data['fingerprint_matches'][0]['mark_id']}")
return data
detect_image("https://example.com/suspicious-image.jpg")
# Free checker: watermark + C2PA + fingerprint (no auth)
def check_image(file_path: str):
with open(file_path, "rb") as f:
response = requests.post(
f"{BASE_URL.replace('/api', '')}/api/check-watermark",
files={"image": f}
)
response.raise_for_status()
data = response.json()
if data.get("fingerprint_match"):
print(f" Fingerprint match: {data['fingerprint_match']['mark_id']}")
return data
check_image("photo.jpg")Node.js
const API_KEY = "mk_live_xxxxxxxxxxxx";
async function markImage(imageUrl, aiModel, creator) {
const res = await fetch("https://www.markmyai.com/api/v1/mark", {
method: "POST",
headers: {
"Authorization": `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ image_url: imageUrl, ai_model: aiModel, creator }),
});
if (!res.ok) throw new Error(`API error: ${res.status}`);
const data = await res.json();
console.log("Marked! verify_url:", data.verify_url);
return data;
}
await markImage(
"https://example.com/my-ai-image.jpg",
"dall-e-3",
"My Company"
);
// Detect: C2PA + Watermark + Fingerprint + DB in one call
async function detectImage(imageUrl) {
const res = await fetch("https://www.markmyai.com/api/v1/detect", {
method: "POST",
headers: {
"Authorization": `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ image_url: imageUrl }),
});
if (!res.ok) throw new Error(`API error: ${res.status}`);
const data = await res.json();
console.log("Marked:", data.is_marked);
console.log("Watermark:", data.watermark);
console.log("C2PA:", data.c2pa.embedded);
console.log("DB matches:", data.fingerprint_matches.length);
return data;
}
await detectImage("https://example.com/suspicious-image.jpg");
// Free checker: watermark + C2PA + fingerprint (no auth)
async function checkImage(file) {
const form = new FormData();
form.append("image", file);
const res = await fetch("https://www.markmyai.com/api/check-watermark", {
method: "POST",
body: form,
});
if (!res.ok) throw new Error(`API error: ${res.status}`);
const data = await res.json();
if (data.fingerprint_match) {
console.log("Fingerprint match:", data.fingerprint_match.mark_id);
}
return data;
}cURL
# Mark an image
curl -X POST https://www.markmyai.com/api/v1/mark \
-H "Authorization: Bearer mk_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"image_url": "https://example.com/image.jpg",
"ai_model": "dall-e-3",
"creator": "My Company"
}' > response.json
# Detect: check all four layers at once
curl -X POST https://www.markmyai.com/api/v1/detect \
-H "Authorization: Bearer mk_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"image_url": "https://example.com/suspicious-image.jpg"}'
# Free checker: watermark + C2PA + fingerprint (no auth needed)
curl -X POST https://www.markmyai.com/api/check-watermark \
-F "image=@photo.jpg"
# Request a fresh download URL later
curl https://www.markmyai.com/api/v1/download/mk_a3f9b2e4d1 \
-H "Authorization: Bearer mk_live_xxxxxxxxxxxx"
# Verify a mark (no auth needed)
curl https://markmyai.com/api/verify/mk_a3f9b2e4d1PHP
<?php
$apiKey = "mk_live_xxxxxxxxxxxx";
$response = file_get_contents("https://www.markmyai.com/api/v1/mark", false,
stream_context_create([
"http" => [
"method" => "POST",
"header" => [
"Authorization: Bearer {$apiKey}",
"Content-Type: application/json",
],
"content" => json_encode([
"image_url" => "https://example.com/image.jpg",
"ai_model" => "dall-e-3",
"creator" => "My Company",
]),
],
])
);
$data = json_decode($response, true);
echo "verify_url: " . $data["verify_url"];Ready to add verifiable provenance to your AI images?