Dashboard
System Components
API Gateway
Cloudflare Worker
Signing Service
Database
SQLite at Edge
Loading system info...
Today
requests
This Week
requests
Success Rate
Avg Duration
successful requests
Signing Container
Activity (30 days)
By Format
Projects
| Project | Requests | Success | Errors | Signs | Verifies | Avg ms | Last Used |
|---|---|---|---|---|---|---|---|
Recent Errors
No errors
API Keys
Create API Key
Comma-separated. Leave empty to allow all origins (server-to-server).
Leave empty for no expiration
Revoke API Key
You are about to revoke the key for . This action cannot be undone. All API calls using this key will immediately fail.
Edit API Key
Project:
Comma-separated. Empty = allow all origins.
Empty = no expiration.
Key created successfully
Copy it now โ it won't be shown again.
| Project | Status | Expires | Requests | Success | Errors | Last Used | Created | |
|---|---|---|---|---|---|---|---|---|
|
Active
Revoked
|
Never |
|
Audit Log
| Time | Op | Format | Status | Project | Client IP | Size | Duration | Error | Request ID |
|---|---|---|---|---|---|---|---|---|---|
Integration Guide
Quick Start
Base URL: https://c2pa.mirrorstage.net
All endpoints use the /v1/ prefix.
Every request (except /v1/health) requires authentication:
Each project gets a dedicated API key. Create one from the page. The key is shown only once at creation.
Endpoints
/v1/health
No auth required
Check service status.
/v1/warmup
Optional
Pre-warms the signing container. Cold starts are handled transparently by the gateway, but this endpoint lets you pre-warm before batch operations for guaranteed low latency. Safe to call when already warm.
/v1/sign
Embeds a C2PA manifest into a media file. Returns the signed file as binary download.
Important: The file must be a valid, complete media file. Placeholders, empty files, or files <100 bytes will be rejected.
Request: multipart/form-data
| Field | Type | Required | Description |
|---|---|---|---|
| file | File | * | Media file (direct upload) |
| file_url | String | * | HTTPS URL to fetch file from (alternative to file) |
| manifest | String | Yes | JSON manifest (see below) |
| format | String | Yes | image/jpeg image/png image/webp image/tiff image/avif video/mp4 audio/mpeg audio/wav audio/flac audio/mp4 |
* Provide either file (upload) or file_url (remote fetch), not both. HTTPS only. Max file size is per-key (default 100MB, up to 200MB). If origin restrictions are set, the URL hostname must match an allowed origin.
Response 200
Content-Type: application/octet-streamโ signed file binaryX-Request-Idโ unique request ID (log this for debugging)X-Duration-Msโ processing time
/v1/verify
Reads and validates the C2PA manifest embedded in a signed file. Performs full validation: signature check, hash binding, certificate chain, OCSP revocation.
Send the signed file as Content-Type: application/octet-stream in the request body.
Validation States:
Invalidโ signature mismatch, tampered file, or broken manifestValidโ signature and hashes correct, but cert not on C2PA Trust ListTrustedโ fully verified with a trusted C2PA certificate
Error Codes (sign & verify)
| Code | Description |
|---|---|
| 400 | Invalid request (missing fields, bad JSON, unsupported format, magic bytes mismatch, no C2PA data on verify) |
| 401 | Missing or invalid API key |
| 413 | File too large (exceeds per-key limit, default 100MB, max 200MB). |
| 429 | Rate limit exceeded (per IP or per project). Includes Retry-After header. |
| 502 | Signing service error (rare โ cold starts are handled transparently by the gateway). |
Manifest JSON
The manifest defines provenance metadata embedded in the file. Use this template for AI-generated content (EU AI Act compliant):
Key fields:
claim_generatorโ identifies your application and versiontitleโ human-readable content titleaction: c2pa.createdโ indicates content was created (not edited)digitalSourceType: trainedAlgorithmicMediaโ IPTC code for AI-generated. Required for EU AI Act.stds.schema-org.CreativeWorkโ attribution metadata (author organization)
Code Examples
Sign an image
curl -X POST https://c2pa.mirrorstage.net/v1/sign \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "[email protected]" \ -F 'manifest={"claim_generator":"MirrorStage/1.0","title":"My Image","assertions":[{"label":"c2pa.actions","data":{"actions":[{"action":"c2pa.created","digitalSourceType":"http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia"}]}}]}' \ -F "format=image/jpeg" \ --output my-image-signed.jpg
Sign from a remote URL
curl -X POST https://c2pa.mirrorstage.net/v1/sign \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "file_url=https://cdn.example.com/my-image.jpg" \
-F 'manifest={"claim_generator":"MirrorStage/1.0","title":"My Image","assertions":[{"label":"c2pa.actions","data":{"actions":[{"action":"c2pa.created","digitalSourceType":"http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia"}]}}]}' \
-F "format=image/jpeg" \
--output my-image-signed.jpg
Verify a signed file
curl -X POST https://c2pa.mirrorstage.net/v1/verify \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/octet-stream" \ --data-binary @my-image-signed.jpg
const C2PA_URL = "https://c2pa.mirrorstage.net/v1"; const API_KEY = process.env.C2PA_API_KEY!; interface VerifyResult { validation_state: "Invalid" | "Valid" | "Trusted"; valid: boolean; trusted: boolean; manifest: Record<string, unknown> | null; signer: string | null; timestamp: string | null; validation_errors: string[]; validation_warnings: string[]; } function buildManifest(title: string) { return { claim_generator: "MirrorStage/1.0", title, assertions: [ { label: "c2pa.actions", data: { actions: [{ action: "c2pa.created", softwareAgent: { name: "MirrorStage", version: "1.0" }, digitalSourceType: "http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia", }] }, }, { label: "stds.schema-org.CreativeWork", data: { "@type": "CreativeWork", author: [{ "@type": "Organization", name: "MirrorStage" }] }, }, ], }; } async function signFile(file: Buffer, filename: string, format: string): Promise<Buffer> { const form = new FormData(); form.append("file", new Blob([file]), filename); form.append("manifest", JSON.stringify(buildManifest(filename))); form.append("format", format); const res = await fetch(`${C2PA_URL}/sign`, { method: "POST", headers: { Authorization: `Bearer ${API_KEY}` }, body: form, }); if (!res.ok) throw new Error(`Sign failed: ${res.status}`); return Buffer.from(await res.arrayBuffer()); } async function verifyFile(file: Buffer): Promise<VerifyResult> { const res = await fetch(`${C2PA_URL}/verify`, { method: "POST", headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/octet-stream" }, body: file, }); if (!res.ok) throw new Error(`Verify failed: ${res.status}`); return res.json(); }
import json, os, requests C2PA_URL = "https://c2pa.mirrorstage.net/v1" API_KEY = os.environ["C2PA_API_KEY"] HEADERS = {"Authorization": f"Bearer {API_KEY}"} def build_manifest(title="Generated Content"): return { "claim_generator": "MirrorStage/1.0", "title": title, "assertions": [ { "label": "c2pa.actions", "data": {"actions": [{ "action": "c2pa.created", "softwareAgent": {"name": "MirrorStage", "version": "1.0"}, "digitalSourceType": "http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia" }]} }, { "label": "stds.schema-org.CreativeWork", "data": {"@type": "CreativeWork", "author": [{"@type": "Organization", "name": "MirrorStage"}]} } ] } def sign_file(file_path: str, fmt: str, title: str = "Generated Content") -> bytes: with open(file_path, "rb") as f: resp = requests.post(f"{C2PA_URL}/sign", headers=HEADERS, files={"file": f}, data={"manifest": json.dumps(build_manifest(title)), "format": fmt}) resp.raise_for_status() return resp.content def verify_file(file_path: str) -> dict: with open(file_path, "rb") as f: resp = requests.post(f"{C2PA_URL}/verify", headers={**HEADERS, "Content-Type": "application/octet-stream"}, data=f.read()) resp.raise_for_status() return resp.json() # { validation_state, valid, trusted, manifest, ... } # Usage signed = sign_file("photo.jpg", "image/jpeg", "My AI Photo") open("photo_signed.jpg", "wb").write(signed) result = verify_file("photo_signed.jpg") print(f"State: {result['validation_state']}, Trusted: {result['trusted']}")
using System.Net.Http.Headers; using System.Text; using System.Text.Json; public class C2paClient { private readonly HttpClient _http; private const string BaseUrl = "https://c2pa.mirrorstage.net/v1"; public C2paClient(string apiKey) { _http = new HttpClient(); _http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey); } public async Task<byte[]> SignFileAsync( string filePath, string format, string title = "Generated Content") { var manifest = new { claim_generator = "MirrorStage/1.0", title, assertions = new object[] { new { label = "c2pa.actions", data = new { actions = new[] { new { action = "c2pa.created", softwareAgent = new { name = "MirrorStage", version = "1.0" }, digitalSourceType = "http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia" } } } }, new { label = "stds.schema-org.CreativeWork", data = (object)new Dictionary<string, object> { ["@type"] = "CreativeWork", ["author"] = new[] { new Dictionary<string, string> { ["@type"] = "Organization", ["name"] = "MirrorStage" } } } } } }; using var content = new MultipartFormDataContent(); content.Add(new ByteArrayContent(File.ReadAllBytes(filePath)), "file", Path.GetFileName(filePath)); content.Add(new StringContent( JsonSerializer.Serialize(manifest), Encoding.UTF8), "manifest"); content.Add(new StringContent(format), "format"); var resp = await _http.PostAsync($"{BaseUrl}/sign", content); resp.EnsureSuccessStatusCode(); return await resp.Content.ReadAsByteArrayAsync(); } public async Task<VerifyResult> VerifyFileAsync(string filePath) { var bytes = File.ReadAllBytes(filePath); var content = new ByteArrayContent(bytes); content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); var resp = await _http.PostAsync($"{BaseUrl}/verify", content); resp.EnsureSuccessStatusCode(); var json = await resp.Content.ReadAsStringAsync(); return JsonSerializer.Deserialize<VerifyResult>(json)!; } } public record VerifyResult( string validation_state, bool valid, bool trusted, JsonElement? manifest, string? signer, string? timestamp, string[] validation_errors, string[] validation_warnings ); // Usage var client = new C2paClient(Environment.GetEnvironmentVariable("C2PA_API_KEY")!); var signed = await client.SignFileAsync("photo.jpg", "image/jpeg", "My AI Photo"); File.WriteAllBytes("photo_signed.jpg", signed); var result = await client.VerifyFileAsync("photo_signed.jpg"); Console.WriteLine($"State: {result.validation_state}, Trusted: {result.trusted}");
Supported Formats
| Format | MIME Type | Extension |
|---|---|---|
| JPEG | image/jpeg | .jpg |
| PNG | image/png | .png |
| WebP | image/webp | .webp |
| TIFF | image/tiff | .tiff |
| AVIF | image/avif | .avif |
| MP4 Video | video/mp4 | .mp4 |
| MP3 | audio/mpeg | .mp3 |
| WAV | audio/wav | .wav |
| FLAC | audio/flac | .flac |
| M4A | audio/mp4 | .m4a |
Integration Checklist
- ☐ Store API key securely (env var / secrets manager, never in code)
- ☐ Sign every AI-generated media before delivering to end users
- ☐ Use correct
formatMIME type matching actual file - ☐ Include
digitalSourceType: trainedAlgorithmicMedia(EU AI Act) - ☐ Include both
c2pa.actionsandCreativeWorkassertions - ☐ Handle 429 (rate limit) with exponential backoff
- ☐ Handle 502 with retry (rare โ cold starts handled transparently, first request may take 10-20s)
- ☐ Log
X-Request-Idfrom responses for debugging - ☐ Verify files after signing as sanity check during development
- ☐ Test signed files at contentcredentials.org/verify
Key Rotation
API keys should be rotated periodically (recommended every 6 months) or immediately if compromised.
Create new key
Use the same project_id for audit log continuity. Set expiration and allowed origins.
Update your application
Replace the API key in your environment variables or secrets manager. Never hardcode keys.
Grace period (48h)
Both old and new keys work simultaneously. Monitor the Audit Log to confirm traffic moves to the new key.
Revoke old key
Once confirmed, revoke the old key from the page with a reason.
If a key is compromised: Revoke immediately, create a replacement, and contact [email protected]. Check the Audit Log for unauthorized usage.
Rate Limits
- 30 requests/minute per IP (configurable per key)
- Max file size: 100MB default (up to 200MB per key)
- First request after idle: 10-20s (cold start handled transparently)
- Use
/v1/warmupbefore bursts for guaranteed low latency
Notes
signingCredential.untrustedis expected during development (self-signed cert). Signature integrity is valid.- Production certs will be issued by Trufo TCA (C2PA Trust List)
- Verify signed files at contentcredentials.org/verify
C2PA Compliance & Documentation
This service implements the C2PA Content Credentials Specification v2.1 and is undergoing the C2PA Conformance Program for Generator Products.
Conformance Status
| Organization Validation (OV) | Approved |
| Product Validation (PV) | Pending |
| Certificate Authority | Trufo TCA |
| Signing Algorithm | PS256 (RSA) โ ES256 (ECDSA P-256) |
Operator
| Company | BitForFun SRL |
| Product | MirrorStage C2PA Gateway |
| Security Contact | [email protected] |
| Infrastructure | Cloudflare Workers + Containers |
Security & Compliance Documents
Security Architecture
System design, trust boundaries, deployment topology, TLS/encryption
Key Management Plan
Key generation, storage, rotation, destruction, access control
Certificate Management
CA chain, enrollment, renewal, revocation, TSA integration
Threat Model
STRIDE analysis, C2PA-specific threats, risk matrix, mitigations
Incident Response Plan
SEV levels, key compromise procedure, 72h notification SLA
Audit & Compliance
GDPR, EU AI Act, data retention, audit log schema
Full documentation available upon request to [email protected]. Required for C2PA Conformance Program Appendix C submission.
Verification Best Practices
Always verify after signing
Call /v1/verify on the signed file to confirm the manifest was embedded correctly.
Check validation_state in response
Valid = signature correct (expected with self-signed cert). Trusted = fully trusted chain (after Trufo cert integration).
Preserve the signed file intact
Any modification (resize, crop, re-encode) will invalidate the C2PA manifest. Serve the signed file as-is.
Report anomalies
If verify returns Invalid on a file you just signed, contact [email protected] immediately.
Release History
Loading changelog...
- •