Introduction
Focus on your core proposition. We handle C2PA and CAWG. The
SSL Provenance API is fully managed REST API from SSL. It creates, signs and edits
C2PA manifests (Content Credentials) for your images, video and audio in a single API call. It can also attach a
CAWG identity assertion and your own custom assertions. Signing uses your SSL.com eSigner credentials. Private keys stay secure in SSL's HSMs, and every signature is timestamped by SSL's conformant C2PA Timestamp Authority.
Quick Start
Sign a JPEG and save the result:
curl -X POST https://mss.ssl.com/v1/c2pa/sign \
-H "Authorization: Bearer <your-access-token>" \
-H "C2PA-Certificate-Id: 983da863-ce7f-42b5-b227-c9076c7b34af" \
-H "Content-Type: image/jpeg" \
--data-binary @photo.jpg \
--output signed_photo.jpg
signed_photo.jpg is your original image with a signed C2PA manifest embedded.
Base URL
https://mss.ssl.com
All endpoints are versioned under
/v1/c2pa/.
Authentication
Every signing request needs two values from your SSL.com eSigner account:
| Value |
Header |
Description |
| Access token |
Authorization: Bearer <access_token> |
Your eSigner API access token. It authorizes the API to use your signing credential on your behalf. |
| Credential ID |
C2PA-Certificate-Id: <credential_id> |
The ID of the eSigner credential (certificate and key) used to sign the C2PA claim. |
The signing algorithm comes from the credential's key. Supported algorithms are ES256, ES384, ES512 (ECDSA) and PS256, PS384, PS512 (RSA-PSS).
Registering an application for an eSigner access token
Access tokens are issued to an application you register with SSL.com's eSigner CSC API. Registering takes about 15 minutes:
- Sign in. Go to login.ssl.com and sign in with your SSL.com account.
- Open Manage Applications. Select the Manage Applications menu by clicking the persona icon on the upper right.
- Create the application. Click Create Application.
- Fill in the form, then click Submit Details:
- Application Name: a unique name for your application.
- Redirect URIs: applications that integrate with eSigner on the web need a valid redirect URI back to your server. This URI will not be used for the Provenance API but it is required. Feel free to add anything you'd like.
- Confidential: check this box if your application runs on a server and can store the secret securely. Leave it unchecked for client-side applications, such as native mobile or browser apps.
- Scopes: enter
service.
- Save your credentials. You now have an Application ID and an Application Secret. Use them to request access tokens from the eSigner CSC API.
For screenshots and more detail, see
Register an Application with eSigner's CSC API.
Keep your Application Secret private. Never embed it in client-side code or commit it to source control.
What the API Adds to Your Asset
Each successful sign request adds one new, signed manifest to the asset. That manifest contains:
- Claim generator:
SSL Provenance API unless you request your own C2PA Claim Signing certificate (requires C2PA conformance, contact us for more information).
- A
c2pa.opened action: records that the asset was opened and signed by the SSL Provenance API.
- A parent ingredient: references the asset as it was received, including its hash, so the signed output can be traced back to the input.
- A thumbnail of the asset, for formats where one can be generated.
- A trusted timestamp (RFC 3161) on the claim signature from SSL.com's C2PA Timestamp Authority.
- Optional: a CAWG identity assertion and up to 16 custom assertions.
All of these are recorded as
created assertions, which means the signer takes responsibility for them.
Assets that already have Content Credentials
If the uploaded asset already carries a C2PA manifest, the API keeps its existing provenance. The new manifest is added to the existing manifest store, and the previous manifest is linked in as the parent ingredient. The asset's full history stays intact and verifiable.
Existing signatures are checked against the official
C2PA Trust List, so validators can report whether the earlier signer was trusted.
Supported Content Types
Pass one of the following MIME types to
/v1/c2pa/sign. Use the request's
Content-Type header for a
raw body, or the
asset part's
Content-Type for a
multipart request.
| Category |
Content-Type |
Format |
| Images |
image/jpeg |
JPEG |
|
image/png |
PNG |
|
image/gif |
GIF |
|
image/tiff |
TIFF |
|
image/webp |
WebP |
|
image/avif |
AVIF |
|
image/heic |
HEIC |
|
image/heif |
HEIF |
|
image/jxl |
JPEG XL (see notes) |
|
image/svg+xml |
SVG |
|
image/dng |
DNG |
|
image/x-adobe-dng |
Adobe DNG |
| Video |
video/mp4 |
MP4 |
|
video/quicktime |
QuickTime (MOV) |
|
video/x-m4v |
M4V |
|
video/avi |
AVI |
|
video/msvideo |
AVI (MS) |
|
video/x-msvideo |
AVI (x-ms) |
| Audio |
audio/mpeg |
MP3 |
|
audio/mp4 |
M4A |
|
audio/flac |
FLAC |
|
audio/wav |
WAV |
|
audio/wave |
WAV |
|
audio/x-wav |
WAV |
|
audio/vnd.wave |
WAV |
Parameters on the MIME type, such as
; charset=utf-8, are ignored.
Endpoints
Health Check
GET /v1/c2pa/health
Returns the service status. No authentication required.
Response
| Status |
Content-Type |
Body |
200 OK |
application/json |
{"status":"ok"} |
Example
curl https://mss.ssl.com/v1/c2pa/health
Sign Asset
POST /v1/c2pa/sign
Signs the asset and returns it with a C2PA manifest embedded.
This endpoint accepts two request formats, chosen by the request's
Content-Type:
| Format |
Request Content-Type |
Use it when |
| Raw body |
The asset's own MIME type, e.g. image/jpeg |
You only need standard signing, with or without a CAWG identity assertion. |
| Multipart form |
multipart/form-data |
You also want to embed custom assertions. |
Both formats support every feature except custom assertions, which require multipart.
Request Headers
| Header |
Required |
Description |
Authorization |
Yes |
Bearer <access_token>: your eSigner access token |
C2PA-Certificate-Id |
Yes |
eSigner credential ID used to sign the C2PA claim, if you use the SSL Provenance API certificate the credential ID is: 983da863-ce7f-42b5-b227-c9076c7b34af |
Content-Type |
Yes |
Raw body: the asset's MIME type (see Supported Content Types). Multipart: multipart/form-data; boundary=..., which most HTTP clients set for you. |
CAWG-Certificate-Id |
No |
eSigner credential ID used to sign a CAWG identity assertion. If present, the identity assertion is added. Can be the same as C2PA-Certificate-Id. |
CAWG-Role |
No |
One or more named actor roles, such as cawg.creator, for the identity assertion. Requires CAWG-Certificate-Id. To send several roles, repeat the header, separate values with commas, or both. |
Option A: Raw body
The request body is the asset's bytes, up to
50 MB. The
Content-Type header gives the asset's MIME type.
The request body is
multipart/form-data with these parts, up to
50 MB in total:
| Part |
Required |
Part Content-Type |
Description |
asset |
Yes |
The asset's MIME type, e.g. image/jpeg |
The asset's bytes. This part's Content-Type decides how the asset is signed, so it must be set. curl's -F "asset=@photo.jpg" infers it from the file extension. Many other clients don't, so set it yourself, for example -F "asset=@photo.jpg;type=image/jpeg". |
manifest_options |
No |
application/json |
A JSON document that customizes the manifest. See Custom Assertions. |
manifest_options schema:
{
"custom_assertions": [
{ "label": "com.example.my-assertion", "data": { "any": "json object" } }
]
}
| Field |
Type |
Description |
custom_assertions |
array |
Up to 16 custom assertions to embed. To add one assertion, send a one-element array. |
custom_assertions[].label |
string |
Required. A unique label in reverse-domain notation. See label rules. |
custom_assertions[].data |
object |
Required. Any JSON object, up to 8 KB when serialized. It is embedded exactly as sent. |
Response
| Status |
Condition |
Content-Type |
Body |
200 OK |
Success |
Same as the asset's Content-Type |
The signed asset with the C2PA manifest embedded |
4xx / 5xx |
Error |
application/json |
{"error":"<description>"}. See Errors. |
Examples
Sign a JPEG image with the SSL Provenance API Claim signing certificate:
curl -X POST https://mss.ssl.com/v1/c2pa/sign \
-H "Authorization: Bearer <your-access-token>" \
-H "C2PA-Certificate-Id: 983da863-ce7f-42b5-b227-c9076c7b34af" \
-H "Content-Type: image/jpeg" \
--data-binary @photo.jpg \
--output signed_photo.jpg
Sign an MP4 video:
curl -X POST https://mss.ssl.com/v1/c2pa/sign \
-H "Authorization: Bearer <your-access-token>" \
-H "C2PA-Certificate-Id: 983da863-ce7f-42b5-b227-c9076c7b34af" \
-H "Content-Type: video/mp4" \
--data-binary @video.mp4 \
--output signed_video.mp4
Sign a JPEG XL image:
curl -X POST https://mss.ssl.com/v1/c2pa/sign \
-H "Authorization: Bearer <your-access-token>" \
-H "C2PA-Certificate-Id: 983da863-ce7f-42b5-b227-c9076c7b34af" \
-H "Content-Type: image/jxl" \
--data-binary @image.jxl \
--output signed_image.jxl
Sign a JPEG and add a CAWG identity assertion that names the creator:
curl -X POST https://mss.ssl.com/v1/c2pa/sign \
-H "Authorization: Bearer <your-access-token>" \
-H "C2PA-Certificate-Id: 983da863-ce7f-42b5-b227-c9076c7b34af" \
-H "CAWG-Certificate-Id: <your-identity-credential-id>" \
-H "CAWG-Role: cawg.creator" \
-H "Content-Type: image/jpeg" \
--data-binary @photo.jpg \
--output signed_photo.jpg
Sign a JPEG and embed a custom assertion (multipart):
curl -X POST https://mss.ssl.com/v1/c2pa/sign \
-H "Authorization: Bearer <your-access-token>" \
-H "C2PA-Certificate-Id: 983da863-ce7f-42b5-b227-c9076c7b34af" \
-F "asset=@photo.jpg;type=image/jpeg" \
-F 'manifest_options={"custom_assertions":[{"label":"com.example.tracking","data":{"internal_id":"ACME-2026-004421"}}]};type=application/json' \
--output signed_photo.jpg
Combine all of them: a CAWG identity with two roles, plus two custom assertions:
curl -X POST https://mss.ssl.com/v1/c2pa/sign \
-H "Authorization: Bearer <your-access-token>" \
-H "C2PA-Certificate-Id: 983da863-ce7f-42b5-b227-c9076c7b34af" \
-H "CAWG-Certificate-Id: <your-identity-credential-id>" \
-H "CAWG-Role: cawg.creator,cawg.editor" \
-F "asset=@photo.jpg;type=image/jpeg" \
-F 'manifest_options={"custom_assertions":[
{"label":"com.example.tracking","data":{"internal_id":"ACME-2026-004421"}},
{"label":"com.example.billing","data":{"account_ref":"AR-9001"}}
]};type=application/json' \
--output signed_photo.jpg
CAWG Identity Assertions
A
CAWG identity assertion (
cawg.identity) lets a named actor, such as the photographer or author, sign a statement that binds their identity to the asset. This is separate from the C2PA claim signature, which identifies the organization or tool that produced the manifest.
To add one, you first need to add your own CAWG certificate, contact us to purchase and provision one. Then, send the
CAWG-Certificate-Id header with the eSigner credential for your CAWG certificate.
- Same or separate credential. Use a different credential from
C2PA-Certificate-Id when the creator's identity certificate isn't the publisher's claim-signing certificate. Use the same value to sign both with one credential.
- Signature format. The identity assertion is signed with an X.509 certificate (
cawg.x509.cose). It covers the manifest's hard binding to the asset and carries its own trusted timestamp.
- Created assertion. The identity assertion is always recorded in the manifest's
created_assertions, as the CAWG identity specification requires.
- Cost. Each request that adds a CAWG identity assertion makes one more signing operation and one more timestamp request. The manifest grows by about 20 KB.
Named actor roles
Use the
CAWG-Role header to describe the actor's
role, for example
cawg.creator or
cawg.editor. To send more than one role, you can:
- comma-separate them:
CAWG-Role: cawg.creator,cawg.editor
- repeat the header:
-H "CAWG-Role: cawg.creator" -H "CAWG-Role: cawg.editor"
- or do both.
Role values are embedded exactly as sent. Roles defined by the CAWG specification and your own custom role identifiers are both accepted.
CAWG-Role requires
CAWG-Certificate-Id. Without it, the request returns
400 Bad Request.
Custom Assertions
Custom assertions let you embed your own structured data in the signed manifest, such as an internal asset ID, a workflow reference or licensing metadata. Each one is a JSON object under a label you choose. It is covered by the manifest signature, so any change to it is detected.
Custom assertions require the
multipart request format, sent in the
manifest_options part.
Custom assertions are not confidential. Like every C2PA assertion, they are signed, so changes can be detected, but they are not encrypted. Anyone who reads the manifest can read them. If a value must stay private, encrypt it before you send it.
Label rules
Every
custom_assertions entry needs a
label that:
- is present, because there is no default label.
- uses reverse-domain notation under a domain you control, for example
com.example.my-assertion or io.acme.tracking-id. That means at least two dot-separated segments. Each segment starts with a letter or digit and contains only letters, digits, - or _.
- is 256 characters or fewer.
- is outside the reserved namespaces. Labels starting with
c2pa. or cawg. (any capitalization) are reserved for standard assertions.
- is unique within the request.
If an entry breaks any of these rules, the request returns
400 Bad Request with a message that names the rule.
Constraints
| Constraint |
Limit |
| Custom assertions per request |
16 |
Size of each entry's data (serialized JSON) |
8 KB |
Type of data |
JSON object (not an array, string or number) |
JPEG XL
A JPEG XL file can be a bare codestream or an ISOBMFF container. A C2PA manifest can only be embedded in the container form, so the API handles each one like this:
- Container input is signed as is.
- Bare codestream input is wrapped in a minimal JPEG XL container before signing. The image data is copied unchanged and is not re-encoded.
The signed output is always a JPEG XL container, and standard JPEG XL decoders read it.
M4A audio
audio/mp4 (M4A) files are handled as MP4 containers. The response keeps the
audio/mp4 content type.
Errors
All errors return a JSON body:
{ "error": "<human-readable description>" }
| Status |
Cause |
Example message |
400 Bad Request |
C2PA-Certificate-Id is missing or empty |
credential ID not present in request |
400 Bad Request |
CAWG-Role sent without CAWG-Certificate-Id |
CAWG-Role requires CAWG-Certificate-Id: cawg.creator |
400 Bad Request |
Multipart: the asset part has no Content-Type |
asset part must declare a Content-Type |
400 Bad Request |
Multipart: manifest_options is not valid JSON, has more than 16 entries, has an entry with a missing or invalid label, has a data value that isn't an object or is over 8 KB, or repeats a label |
Invalid manifest_options: <reason> |
400 Bad Request |
Multipart: the multipart body is malformed or over the size limit |
multipart error: <reason> |
413 Payload Too Large |
The raw body is over 50 MB |
|
415 Unsupported Media Type |
The asset's MIME type is not supported |
Unsupported media type: image/bmp |
500 Internal Server Error |
The credential couldn't be loaded: the access token is invalid or expired, or the credential ID is wrong or not available to this token |
No credential info: <reason> |
500 Internal Server Error |
Signing, timestamping or manifest generation failed, for example because the asset is corrupt or doesn't match its declared type |
<reason> |
Troubleshooting tips
- Make sure
Content-Type matches the file's real format. A PNG sent as image/jpeg fails during signing.
Limits
| Item |
Limit |
| Request size (raw body or whole multipart body) |
50 MB |
| Custom assertions per request |
16 |
Custom assertion data size |
8 KB each |
Custom assertion label length |
256 characters |
Verifying a Signed Asset
You can inspect and validate the Content Credentials on any signed asset with standard C2PA tools:
The output shows the
SSL Provenance API claim generator, the
c2pa.opened action, the parent ingredient, any CAWG identity assertion and custom assertions, and the signing certificate and timestamp.