Media Signing API

Sign media with C2PA Content Credentials by using the DigiCert Content Trust Manager Media Signing API.

The Media Signing API signs a supported media file and embeds a C2PA-compliant manifest, returns it as a detached sidecar, or both. The response contains the asset and any sidecar as Base64 data, together with an identifier returned by the signing service.

Overview

Use the API to sign supported media files with a C2PA-compliant manifest. The resulting Content Credentials provide tamper-evident provenance information that helps a viewer verify who signed the content, which actions were declared, and whether the signed asset has changed.

The API also supports S/MIME Baseline Requirements (BR) certificates for CAWG identity assertions. A CAWG identity assertion adds verifiable creator, publisher, or contributor identity information to the Content Credentials. Please note that CAWG identity assertion signing is supported only in the production environment.

For an application integration that uses the native Media Signing Library, see Media Signing Library.

You can use the API to:

  • Generate and embed a minimal C2PA manifest.
  • Choose where the manifest is written: embedded in the asset, as a detached sidecar (.c2pa), or both.
  • Re-sign a sidecar-signed asset by supplying its previous sidecar.
  • Declare a new creation (AI-generated or declared digital source type) and add user-declared actions.
  • Add an AI disclosure and record AI training and data-use permissions.
  • Include available EXIF metadata and an external reference.
  • Add a CAWG identity assertion by using an S/MIME BR certificate credential.
  • Validate a detached sidecar against an asset.
  • Choose one request-authentication method: API key or mutual TLS, and use credentials issued for the same environment as the endpoint.
Choose one request-authentication method—API key or mutual TLS—and use credentials issued for the same environment as the endpoint.

Prerequisites

Before making your first request, make sure you have:

  • A DigiCert ONE account with Content Trust Manager enabled.

  • Your Content Trust Manager account ID.

  • One request-authentication method: an API key or a client authentication certificate for mutual TLS (mTLS).

  • Credentials created in the same production (one.digicert.com) or demo environment (demo.one.digicert.com) as the endpoint you call.

  • A validated organization associated with the account. The signing service records this organization’s name in the manifest; without one, signing fails.

  • A supported media file.

  • For CAWG identity assertion signing, an S/MIME BR certificate credential, its credential ID and its PIN.

The maximum supported file size is 80 MB per upload; your account might be configured with a lower limit. Validate the file size before sending the request. You can upload only one media file per request; sending more than one file part (or more than one previous_sidecar part) returns HTTP 400. A sidecar upload is limited to 16 MB.

The file part must include a filename. Its extension must be a supported extension (see Supported media types) and must match the actual file content, and the filename without its extension must be shorter than 64 characters.

Client authentication certificate

Use a client authentication certificate when your integration authenticates with mTLS. The certificate is presented during the TLS handshake; an API key is not sent on an mTLS request.

Create a client authentication certificate

  • Sign in to DigiCert ONE.

  • Select the user icon, select Admin profile, and then select Client authentication certificates.

  • Select Create client authentication certificate.

  • Enter a unique nickname using letters, numbers, spaces, dashes, or underscores.

  • Select an end date and optionally record it in your certificate-rotation process.

  • Select AES for Encryption.

  • Select SHA256 for Signature hash algorithm.

  • Select Generate certificate.

  • Copy the certificate password and store it securely, such as in your organization’s secrets-management system. You cannot retrieve the password later.

  • Download the certificate and store it securely. You cannot download the same certificate again.

Create and deploy a replacement before the certificate expires. Requests that continue to use an expired certificate cannot complete the mTLS handshake.

Configure the certificate in Postman

  • Open Settings > Certificates.

  • Set Host to clientauth.one.digicert.com for production, or clientauth.demo.one.digicert.com for demo. Enter only the hostname (do not include https:// or the API path).

  • Set Port to 443.

  • Upload your client authentication certificate to the PFX file field and enter its password in Passphrase.

API key

Use an API key when your integration does not use mTLS. Send the key in the x-api-key header and use the API-key endpoint. Do not attach a client authentication certificate to the same request.

Create or copy an API key

  • Sign in to Content Trust Manager in the environment your integration will call.

  • Select the user icon, and then select Admin profile.

  • Scroll to API Keys.

  • Generate a key or copy an existing key for your integration.

  • Store the API key securely, such as in your organization’s secrets-management system. Do not commit it to source control or include it in a shared Postman collection.

Authentication

MethodRequest configurationDo not send
API keyUse the API-key endpoint and send x-api-key in the header.A client authentication certificate.
mTLSUse the clientauth endpoint and present the client authentication certificate during TLS.The x-api-key header.

Environment scope API keys, account IDs, and client certificates must belong to the selected environment. A missing, invalid, expired, or environment-mismatched API key returns HTTP 401 with error wrong_token and error_description of Invalid session.

Do not send browser cookies The API does not require a Cookie header. Remove copied browser cookies from curl commands, Postman requests, and application code.

Permissions and feature Both /sign and /validate-sidecar require the signing permission and media signing enabled for the account. A caller without the signing permission receives HTTP 403 (access_denied). If media signing is not enabled for the account, the request returns HTTP 400 (invalid_request) with Media Signing Library is not enabled for your account. Please contact Support. An account_id that the caller cannot use returns HTTP 400 with Invalid account ID.

Endpoints

OperationProduction endpoint (API key)Production endpoint (mTLS)
Signhttps://one.digicert.com/documentmanager/api/c2pa/v1/signhttps://clientauth.one.digicert.com/documentmanager/api/c2pa/v1/sign
Validate sidecarhttps://one.digicert.com/documentmanager/api/c2pa/v1/validate-sidecarhttps://clientauth.one.digicert.com/documentmanager/api/c2pa/v1/validate-sidecar

For the demo environment, use the corresponding demo.one.digicert.com or clientauth.demo.one.digicert.com host. Keep the path unchanged and use demo-issued credentials.

Request parameters

Send a POST request as multipart/form-data. Send the signing options in one JSON part named request; the media file and optional previous sidecar are separate binary parts. Do not use separate legacy form fields such as accountId, role, or createdWithAi to supply signing options.

Headers

HeaderRequirementValue and guidance
AcceptRecommendedapplication/json
x-api-keyAPI-key requests onlyYour API key. Omit this header for mTLS requests.
Content-TypeGenerated by clientLet curl, Postman, or your SDK generate multipart/form-data with the correct boundary. Do not hard-code this header.

Body parts

PartTypeRequirementDescription
requestJSONRequiredJSON object with the signing options. See Request JSON. Set the part content type to application/json.
fileFileRequiredBinary media file to sign. See Supported media types.
previous_sidecarFile (.c2pa)OptionalDetached Manifest Store issued by an earlier signing of this asset. Use it only when the asset carries no embedded manifest; without it, a new provenance chain starts. Must be a non-empty .c2pa file of at most 16 MB.

Property names in the /sign request JSON are snake_case. Unknown fields in the defined request objects return HTTP 400; assertions.ai_disclosure.metadata is a free-form JSON object. Optional groups can be omitted entirely. Include each JSON property only once; duplicate properties are not rejected, and the last value is used.

Request JSON

The request part contains a JSON object with the signing options. Every signing request must include account_id and signing. You can also include the optional provenance and assertions objects.

FieldTypeRequirementDescription
account_idStringRequiredYour Content Trust Manager account ID for the selected environment (UUID format).
signingObjectRequiredHow the asset is signed and where the manifest is written.
provenanceObjectOptionalWhat happened to the content: creation and actions.
assertionsObjectOptionalAdditional assertions: AI disclosure, usage rights, external reference, EXIF.

The following example is a complete minimal request. Replace the example UUID with your account ID.

{
  "account_id": "11111111-1111-4111-8111-111111111111",
  "signing": {
    "manifest_output": "EMBEDDED",
    "roles": [
      "creator",
      "publisher"
    ]
  }
}

Save the complete JSON object as request.json and send it as the multipart request part with content type application/json. Attach the media separately as the file part.

Later examples labelled “request fragment” show only the options being explained. Merge those options into your complete JSON object; do not send a fragment by itself.

signing

FieldTypeRequirementDescription
manifest_outputEnumRequiredEMBEDDED, SIDECAR, or BOTH (case-insensitive). See Manifest placement.
rolesArray of stringsRequiredOne or more roles, one per element. Standard signing accepts creator, contributor, and publisher. CAWG identity assertion signing additionally accepts editor, producer, and sponsor. Values are matched ignoring case and surrounding whitespace. Any other value, or a CAWG-only role in a standard signing request, returns HTTP 400.
cawg_identityObjectOptionalInclude to request CAWG identity signing. Both credential_id and pin are then required.

What roles record In standard signing, each role records the name of your account’s validated organization in a cawg.metadata assertion as dc:creator, dc:contributor, or dc:publisher, together with a dc:description that names the signing service and the organization. Roles describe the organization, not an individual. In CAWG identity assertion signing, the roles are recorded in the CAWG identity assertion instead.

This example shows the signing portion of a request. Add it to your complete request alongside account_id. List each role as a separate string in the roles array.

Set manifest_output to BOTH to request an asset with an embedded manifest and a separate .c2pa manifest file. Use EMBEDDED for an embedded manifest only, or SIDECAR to return the asset unchanged with a separate manifest file.

{
  "signing": {
    "manifest_output": "BOTH",
    "roles": [
      "creator",
      "contributor",
      "publisher"
    ]
  }
}

signing.cawg_identity

FieldTypeRequirementDescription
credential_idStringRequiredIdentifier of the S/MIME BR certificate credential.
pinStringRequiredPIN for the credential. Never returned in a response.

provenance

FieldTypeRequirementDescription
creationObjectOptionalInclude to mark the asset as a new creation. type is required: AI_GENERATED or DECLARED (uppercase, exact match).
actionsArray of objectsOptionalUser-declared actions, in order. Each action name may appear only once.

creation, type AI_GENERATED: Content created with AI.

FieldTypeRequirementDescription
typeStringRequiredAI_GENERATED.
generatorObjectRequiredAI software that created the content. Provide name and, optionally, version. version is used only when name is present. A generator object without name is accepted, but your generator is not recorded; the c2pa.created action’s softwareAgent then names the DigiCert signing service instead.

creation, type DECLARED: A new creation that is not AI-generated.

FieldTypeRequirementDescription
typeStringRequiredDECLARED.
digital_source_typeStringOptionalOne of the accepted digital source type URIs, matched exactly. See Accepted digital source types. If omitted, the API uses http://cv.iptc.org/newscodes/digitalsourcetype/digitalCapture (.../digitalCreation for PDF).

actions[]

FieldTypeRequirementDescription
actionStringRequiredC2PA action name, for example c2pa.edited. See User-defined manifest actions.
software_agentObjectOptionalname (at most 256 characters) and optional version of the software that performed the action. Ignored when name is absent or blank.
whenStringOptionalRFC 3339 timestamp with a time zone, such as 2026-10-22T10:00:00Z, at most 128 characters.
digital_source_typeStringOptionalOne of the accepted digital source type URIs for this action, matched exactly. See Accepted digital source types.

Declared creation fragment Use this for a new non-AI creation; digital_source_type describes how the content originated. Omit creation when you are opening existing content rather than declaring a new creation. Existing Content Credentials take precedence over a new creation declaration.

{
  "provenance": {
    "creation": {
      "type": "DECLARED",
      "digital_source_type": "http://cv.iptc.org/newscodes/digitalsourcetype/digitalCapture"
    }
  }
}

Accepted digital source types

provenance.creation.digital_source_type and provenance.actions[].digital_source_type accept only the following values, matched exactly. The IPTC values use http://; an https:// variant of the same URI returns HTTP 400.

ValueMeaning
http://cv.iptc.org/newscodes/digitalsourcetype/digitalCaptureDigital capture
http://cv.iptc.org/newscodes/digitalsourcetype/computationalCaptureComputational capture
http://cv.iptc.org/newscodes/digitalsourcetype/negativeFilmNegative film
http://cv.iptc.org/newscodes/digitalsourcetype/positiveFilmPositive film
http://cv.iptc.org/newscodes/digitalsourcetype/printPrint
http://cv.iptc.org/newscodes/digitalsourcetype/humanEditsHuman edits
http://cv.iptc.org/newscodes/digitalsourcetype/algorithmicallyEnhancedAlgorithmically enhanced
http://cv.iptc.org/newscodes/digitalsourcetype/digitalCreationDigital creation
http://cv.iptc.org/newscodes/digitalsourcetype/dataDrivenMediaData-driven media
http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMediaTrained algorithmic media (generative AI)
http://cv.iptc.org/newscodes/digitalsourcetype/algorithmicMediaAlgorithmic media
http://cv.iptc.org/newscodes/digitalsourcetype/screenCaptureScreen capture
http://cv.iptc.org/newscodes/digitalsourcetype/virtualRecordingVirtual recording
http://cv.iptc.org/newscodes/digitalsourcetype/compositeComposite
http://cv.iptc.org/newscodes/digitalsourcetype/compositeCaptureComposite capture
http://cv.iptc.org/newscodes/digitalsourcetype/compositeSyntheticComposite synthetic
http://cv.iptc.org/newscodes/digitalsourcetype/compositeWithTrainedAlgorithmicMediaComposite with trained algorithmic media (AI edit)
http://c2pa.org/digitalsourcetype/emptyEmpty (C2PA)
http://c2pa.org/digitalsourcetype/trainedAlgorithmicDataTrained algorithmic data (C2PA)

assertions

FieldTypeDescription
ai_disclosureObjectAI disclosure. model_type is required and must be one of the accepted C2PA model-type identifiers (see AI creation disclosure). Optional fields: model_name, model_identifier, content_profile.human_oversight_level (fully_autonomous, prompt_guided, or human_validated; lowercase, exact match), scientific_domains (array of arXiv category identifiers such as cs.AI), and metadata (a free-form JSON object).
usage_rightsObjectOptional keys ai_inference, generative_ai_training, non_generative_ai_training, and data_mining_and_analytics. Each is an object with permission (allowed, prohibited, or constrained; lowercase, exact match) and constraints_info (required with constrained; not accepted otherwise).
external_referenceObjecturi: an absolute https:// or http:// URL with a host (HTTPS recommended). content_type: an IANA media type. Both fields are required when the object is present.
exifObjectextract_from_asset: set to JSON true to request supported source EXIF fields in a c2pa.metadata assertion (JPEG, TIFF, WebP, HEIF, and HEIC only).

Boolean values Send JSON true or false without quotes.

Supported media types

Send a supported media file with matching bytes and extension. The service identifies the format from the filename extension and the file content; the extension must be one of those listed below. JPEG XL files must use the container format; bare JPEG XL codestreams are not accepted.

CategoryMedia typeExtensions
Still imageimage/jpeg.jpg, .jpeg
Still imageimage/png.png
Still imageimage/x-adobe-dng.dng
Still imageimage/heic, image/heif.heic, .heif
Still imageimage/tiff.tif, .tiff
Still imageimage/webp.webp
Still imageimage/svg+xml.svg
Still imageimage/gif.gif
Still imageimage/jxl.jxl
Videovideo/mp4.mp4
Videovideo/avi (video/x-msvideo).avi
Videovideo/quicktime.mov
Audioaudio/wav.wav
Audioaudio/mp4.m4a
Audioaudio/flac.flac
Audioaudio/mpeg.mp3
Documentapplication/pdf.pdf

Set the file part content type to the file’s media type. The response asset.mime_type repeats the content type you send (or application/octet-stream when none is sent). Some clients, including curl, send application/octet-stream for less common extensions; append ;type=<media-type> to the curl --form value to set it explicitly.

Manifest behavior

Generate a minimal manifest

When you submit request with only account_id and signing (roles and manifest_output), plus file, the API generates a minimal C2PA-compliant manifest.

Manifest placement

signing.manifest_output controls where the Manifest Store is written.

ValueSigned assetSidecar
EMBEDDEDContains the embedded manifest.Not returned.
SIDECARReturned unchanged (modified_from_input is false).Returned as a detached .c2pa file.
BOTHContains the embedded manifest.Returned as a detached .c2pa file.

Read manifest_location The response field manifest_location reports the placement achieved. The API does not silently change the requested placement: if it cannot be honored, the request fails. In particular, SIDECAR for an asset that already contains an embedded manifest returns HTTP 400, because sidecar-only output would discard the existing provenance; request EMBEDDED or BOTH instead.

Add signing metadata

The API combines the optional provenance and assertions values with a system-managed action based on the input state.

InputSystem-managed actionResult
provenance.creation presentc2pa.createdThe manifest records a newly created asset.
No provenance.creationc2pa.openedThe source is represented as an ingredient.
File already has Content Credentialsc2pa.openedExisting provenance wins, even when provenance.creation is present.
Sidecar-signed asset with previous_sidecarc2pa.openedThe previous manifest chain is carried forward.

Existing Content Credentials must validate Before signing, the API validates any Content Credentials already embedded in the file. If they are invalid (for example, the file was modified after it was signed), or their signing credential is expired, revoked, invalid, or not trusted by the service, the request returns HTTP 400 and the file is not signed. This can apply to files signed by other C2PA tools.

User-defined manifest actions

Use provenance.actions to declare transformations performed on the asset. Send an array of action objects. This replaces the earlier additionalActions object keyed by action name.

The following action names can be declared:

c2pa.edited, c2pa.edited.metadata, c2pa.resized, c2pa.resized.proportional, c2pa.cropped, c2pa.filtered, c2pa.converted, c2pa.transcoded, c2pa.trimmed, c2pa.addedText, c2pa.adjustedColor, c2pa.changedSpeed, c2pa.deleted, c2pa.drawing, c2pa.dubbed, c2pa.enhanced, c2pa.mastered, c2pa.orientation, c2pa.published, c2pa.remixed, c2pa.repackaged, and c2pa.unknown.

The API manages c2pa.created, c2pa.opened, and c2pa.placed; do not send them. c2pa.translated, c2pa.mixed, and c2pa.removed require parameters that this API does not accept, so they are rejected. Unknown, deprecated, or unsupported action names return HTTP 400. Each action name can appear only once per request.

FieldDescription
software_agentWritten to the action’s softwareAgent as a structured object with name and, when supplied, version.
whenRFC 3339 timestamp written to the action’s time. If omitted, the API inserts the current UTC time.
digital_source_typeDigital source type for the action. If omitted when existing content is opened (c2pa.opened), c2pa.edited, c2pa.resized, and c2pa.filtered default to humanEdits, and other actions default to digitalCapture (digitalCreation for PDF). If omitted on a new creation (c2pa.created), every action defaults to digitalCapture (digitalCreation for PDF). Set it explicitly on each action to record the intended value. Content edited with AI is expressed as c2pa.edited with the compositeWithTrainedAlgorithmicMedia source type.

Example provenance.actions array

Request fragment These entries describe edits already performed by your application. The signing API records the history; it does not resize or edit the uploaded media. Use a different supported action name for each entry. when is the time of that operation, including its UTC offset.

{
  "provenance": {
    "actions": [
      {
        "action": "c2pa.edited",
        "software_agent": {
          "name": "Example Editor",
          "version": "1.0"
        },
        "when": "2026-11-04T12:30:00Z",
        "digital_source_type": "http://cv.iptc.org/newscodes/digitalsourcetype/humanEdits"
      },
      {
        "action": "c2pa.resized",
        "when": "2026-11-04T12:31:00Z"
      }
    ]
  }
}

allActionsIncluded

The API always emits allActionsIncluded in the c2pa.actions.v2 assertion. The value is true when the manifest contains the default c2pa.opened action, an accepted user-declared action, or an AI-generated c2pa.created action. It is false when the manifest contains only the default non-AI c2pa.created action.

allActionsIncluded is generated in the output assertion. Do not add it to the signing request.

AI usage and permissions

AI creation disclosure

Set provenance.creation with type AI_GENERATED and a generator only for new content created with AI, on content that does not already contain Content Credentials. This replaces createdWithAi. An AI-generated creation is described by its generator and uses the Generative AI digital source type; it carries no separate digital_source_type. A request with type AI_GENERATED and no generator returns HTTP 400. If the file already has Content Credentials, the API records c2pa.opened and ignores the creation disclosure.

To disclose model details, add assertions.ai_disclosure. Set model_type to one of the following C2PA 2.4 model-type identifiers (exact match): c2pa.types.model, c2pa.types.model.caffe, c2pa.types.model.caffe2, c2pa.types.model.catboost, c2pa.types.model.coreml, c2pa.types.model.flax, c2pa.types.model.huggingface.transformers, c2pa.types.model.jax, c2pa.types.model.keras, c2pa.types.model.lightgbm, c2pa.types.model.ml_net, c2pa.types.model.mxnet, c2pa.types.model.onnx, c2pa.types.model.openvino, c2pa.types.model.openvino.parameter, c2pa.types.model.openvino.topology, c2pa.types.model.paddle, c2pa.types.model.pytorch, c2pa.types.model.sklearn, c2pa.types.model.tensorflow, c2pa.types.model.tensorrt, c2pa.types.model.tflite, c2pa.types.model.torchscript, or c2pa.types.model.xgboost. Any other value, including the category generative, returns HTTP 400.

AI model disclosure

Use provenance.creation to declare that new media was created with AI. Use assertions.ai_disclosure to describe the model and oversight behind that media. These objects serve different purposes and can appear in the same request. The generator identifies the creation application; model_name identifies the AI model.

Complete AI disclosure request Save this as request.json, replace account_id, and attach the generated media as the file part. model_identifier can identify the model or model documentation. scientific_domains is an array of arXiv category identifiers (exact match, for example cs.AI, cs.CV, cs.LG, or stat.ML; see the arXiv category taxonomy); an unknown identifier returns HTTP 400. metadata can contain application-specific nested JSON.

{
  "account_id": "11111111-1111-4111-8111-111111111111",
  "signing": {
    "manifest_output": "EMBEDDED",
    "roles": [
      "creator",
      "publisher"
    ]
  },
  "provenance": {
    "creation": {
      "type": "AI_GENERATED",
      "generator": {
        "name": "Example Creative Studio",
        "version": "2.0"
      }
    }
  },
  "assertions": {
    "ai_disclosure": {
      "model_type": "c2pa.types.model.huggingface.transformers",
      "model_name": "Example Image Model",
      "model_identifier": "https://example.com/models/image-model",
      "content_profile": {
        "human_oversight_level": "prompt_guided"
      },
      "scientific_domains": [
        "cs.AI"
      ],
      "metadata": {
        "workflow": "campaign-imagery",
        "revision": 1
      }
    }
  }
}

human_oversight_level accepts fully_autonomous, prompt_guided, or human_validated. Select the value that describes the actual workflow. For AI edits to existing media, describe the edit in provenance.actions and use its digital_source_type rather than declaring the existing asset to be a new AI creation.

{
  "provenance": {
    "actions": [
      {
        "action": "c2pa.edited",
        "digital_source_type": "http://cv.iptc.org/newscodes/digitalsourcetype/compositeWithTrainedAlgorithmicMedia"
      }
    ]
  }
}

Request fields use snake_case. The generated c2pa.ai-disclosure assertion uses the C2PA field names, including modelType, modelName, modelIdentifier, contentProfile.humanOversightLevel, and scientificDomain. Do not copy the output assertion unchanged into the request.

AI training and data-use permissions

Set permissions under assertions.usage_rights. Each permission is case-sensitive and accepts allowed, prohibited, or constrained. The API writes them to a cawg.training-mining assertion.

Request valueManifest valueConstraint rule
allowedallowedconstraints_info is not accepted.
prohibitednotAllowedconstraints_info is not accepted.
constrainedconstrainedconstraints_info is required and describes the constraint.
Usage keyMeaning
ai_inferenceUse for AI inference.
generative_ai_trainingUse to train generative AI models.
non_generative_ai_trainingUse to train non-generative AI or ML models.
data_mining_and_analyticsUse for data mining and analytics.

constraints_info is validated Supplying constraints_info with any permission other than constrained, or omitting it with constrained, returns HTTP 400. Earlier versions silently ignored orphan constraint values.

Usage-rights request fragment Set each permission independently. Include constraints_info only with constrained, and describe the applicable conditions. These training and data-use preferences do not require CAWG identity assertion signing.

{
  "assertions": {
    "usage_rights": {
      "ai_inference": {
        "permission": "allowed"
      },
      "generative_ai_training": {
        "permission": "prohibited"
      },
      "non_generative_ai_training": {
        "permission": "constrained",
        "constraints_info": "Contact the publisher for a separate training licence."
      },
      "data_mining_and_analytics": {
        "permission": "prohibited"
      }
    }
  }
}

EXIF metadata ingestion

Set assertions.exif.extract_from_asset to true to include supported EXIF fields in a c2pa.metadata assertion. Extraction is available for JPEG, TIFF, WebP, HEIF, and HEIC files; for other formats the option has no effect. Fields can include capture timestamps, exposure, ISO speed, aperture, focal length, lens and camera details, and GPS location. Extraction does not copy every source tag; the assertion is omitted when no supported fields are found.

Privacy EXIF data can contain capture time, device, software, or GPS location information, and GPS fields present in the file are included. Confirm that the metadata is appropriate to publish before enabling ingestion.

EXIF request fragment The API reads supported metadata from the uploaded file; you do not supply EXIF values in this object. Set extract_from_asset to false, or omit exif, when you do not want extraction. Only supported tags present in the asset can be included; the result varies with its metadata.

{
  "assertions": {
    "exif": {
      "extract_from_asset": true
    }
  }
}

External references

Use assertions.external_reference with uri and content_type to add a c2pa.external-reference assertion. Both are required: omitting either returns HTTP 400. Use an absolute URL with an https:// (recommended) or http:// scheme and a host, such as https://www.example.com/resource, with an IANA media type such as application/json or text/html. The API validates the values but does not fetch the referenced resource.

External-reference request fragment uri identifies the related resource, and content_type identifies that resource’s media type. This adds a reference to the manifest; the referenced JSON is not supplied as another multipart file.

{
  "assertions": {
    "external_reference": {
      "uri": "https://example.com/media/publication-record.json",
      "content_type": "application/json"
    }
  }
}

CAWG identity assertion signing

Use the CAWG signing flow when Content Credentials must include verifiable creator, publisher, sponsor, editor, producer, or contributor identity information. Obtain an S/MIME BR certificate credential before sending the request. See References for ordering a certificate. CAWG identity assertion signing is supported only in the production environment.

FieldRequirementPurpose
signing.cawg_identity.credential_idRequired for CAWG signingIdentifies the S/MIME BR certificate credential.
signing.cawg_identity.pinRequired with credential_idAuthenticates use of the selected credential.
signing.rolesRequiredStandard roles plus editor, producer, and sponsor.

OTP removed The otp field is no longer part of the request. Do not send it; unknown properties return HTTP 400.

Send credential_id and pin together; sending cawg_identity without both returns HTTP 400. The credential must be an S/MIME BR credential issued for CAWG signing, and CAWG identity signing must be enabled for the account; otherwise the request returns HTTP 400. Store the PIN in a protected secret variable.

Examples

API key authentication

Minimal production request

curl --location 'https://one.digicert.com/documentmanager/api/c2pa/v1/sign' \
  --header 'Accept: application/json' \
  --header "x-api-key: ${DIGICERT_API_KEY}" \
  --form 'request={"account_id":"'"${DIGICERT_ACCOUNT_ID}"'","signing":{"manifest_output":"EMBEDDED","roles":["creator","publisher"]}};type=application/json' \
  --form 'file=@/absolute/path/to/sample.jpg'

mTLS authentication

Minimal production request with a client certificate

curl --location 'https://clientauth.one.digicert.com/documentmanager/api/c2pa/v1/sign' \
  --cert-type P12 \
  --cert "/secure/path/client-auth.p12:${DIGICERT_CERT_PASSPHRASE}" \
  --header 'Accept: application/json' \
  --form 'request={"account_id":"'"${DIGICERT_ACCOUNT_ID}"'","signing":{"manifest_output":"EMBEDDED","roles":["publisher"]}};type=application/json' \
  --form 'file=@/absolute/path/to/sample.jpg'

While curl runs, the expanded passphrase is visible to other users in the process list. On a shared host, put the cert and cert-type options in a curl --config file that only you can read.

Signing metadata

AI-created image with permissions, an edit action, and a sidecar

Save the JSON below as request.json, then send it as the request part.

{
  "account_id": "<account-id>",
  "signing": { "manifest_output": "BOTH", "roles": ["creator"] },
  "provenance": {
    "creation": {
      "type": "AI_GENERATED",
      "generator": { "name": "ImageGen", "version": "3.1" }
    },
    "actions": [
      { "action": "c2pa.edited",
        "software_agent": { "name": "Example Editor", "version": "1.0" },
        "digital_source_type": "http://cv.iptc.org/newscodes/digitalsourcetype/humanEdits" }
    ]
  },
  "assertions": {
    "usage_rights": {
      "generative_ai_training": {
        "permission": "constrained",
        "constraints_info": "Permitted only for internal evaluation."
      }
    }
  }
}
curl --location 'https://one.digicert.com/documentmanager/api/c2pa/v1/sign' \
  --header 'Accept: application/json' \
  --header "x-api-key: ${DIGICERT_API_KEY}" \
  --form 'request=<request.json;type=application/json' \
  --form 'file=@/absolute/path/to/new-image.jpg'

S/MIME signing for a CAWG identity assertion

CAWG signing with an S/MIME credential

{
  "account_id": "<account-id>",
  "signing": {
    "manifest_output": "EMBEDDED",
    "roles": ["publisher"],
    "cawg_identity": { "credential_id": "<credential-id>", "pin": "<pin>" }
  }
}

Python quickstart

Install requests with python3 -m pip install requests. Save a complete request from this guide as request.json and put sample.jpg beside the script. Set DIGICERT_ACCOUNT_ID and DIGICERT_API_KEY in your terminal. The script replaces the sample account ID, sends the JSON and JPEG as multipart parts, and decodes the returned artifacts into output/. For another format, change both the input filename and its MIME type.

import base64
import json
import os
from pathlib import Path
import requests

url = (
    "https://one.digicert.com"
    "/documentmanager/api/c2pa/v1/sign"
)
payload = json.loads(Path("request.json").read_text())
payload["account_id"] = os.environ["DIGICERT_ACCOUNT_ID"]
headers = {"x-api-key": os.environ["DIGICERT_API_KEY"]}
with Path("sample.jpg").open("rb") as media:
    response = requests.post(
        url, headers=headers,
        files={
            "request": (None, json.dumps(payload),
                        "application/json"),
            "file": ("sample.jpg", media, "image/jpeg"),
        },
        timeout=180, allow_redirects=False,
    )
if response.status_code != 200:
    raise RuntimeError(
        f"Signing failed: HTTP {response.status_code}: {response.text}"
    )
result = response.json()
if not result.get("asset", {}).get("encoded_content"):
    raise RuntimeError("Response does not contain the asset")
out = Path("output")
out.mkdir(exist_ok=True)
for key in ("asset", "sidecar"):
    artifact = result.get(key)
    if artifact and artifact.get("encoded_content"):
        data = base64.b64decode(
            artifact["encoded_content"], validate=True
        )
        name = Path(artifact["file_name"]).name
        if name in ("", ".", ".."):
            raise RuntimeError("Invalid artifact filename")
        (out / name).write_bytes(data)
print("Manifest placement:", result["manifest_location"])

The response carries the artifacts as Base64, which is about one third larger than the binary file. For large media, allow for a large response body and a long read timeout.

Re-sign a sidecar-signed asset

Save the following complete request as request.json and replace account_id. Attach the unchanged asset and its matching previous .c2pa file using the command below. previous_sidecar is for an asset without an embedded manifest. The previous sidecar must match the uploaded asset bytes; otherwise the request returns HTTP 400. Do not re-encode the file before signing. Because SIDECAR output returns the asset unchanged, keep the same asset bytes and use the newly returned sidecar for the next re-sign.

{
  "account_id": "11111111-1111-4111-8111-111111111111",
  "signing": {
    "manifest_output": "SIDECAR",
    "roles": [
      "creator",
      "publisher"
    ]
  }
}
curl --location 'https://one.digicert.com/documentmanager/api/c2pa/v1/sign' \
  --header "x-api-key: ${DIGICERT_API_KEY}" \
  --form 'request=<request.json;type=application/json' \
  --form 'file=@/absolute/path/to/image.jpg' \
  --form 'previous_sidecar=@/absolute/path/to/previous.c2pa'

These examples omit an explicit Content-Type header. curl generates the required multipart boundary from the --form fields.

Validate a sidecar

The validation endpoint uses a smaller request object containing only account_id. The file part contains the asset and the sidecar part contains the detached manifest. Signing options and assertions are not validation inputs.

{
  "account_id": "11111111-1111-4111-8111-111111111111"
}
curl --location 'https://one.digicert.com/documentmanager/api/c2pa/v1/validate-sidecar' \
  --header 'Accept: application/json' \
  --header "x-api-key: ${DIGICERT_API_KEY}" \
  --form 'request={"account_id":"'"${DIGICERT_ACCOUNT_ID}"'"};type=application/json' \
  --form 'file=@/absolute/path/to/image.jpg' \
  --form 'sidecar=@/absolute/path/to/manifest.c2pa'

Decode the signed media

Save the API response as response.json, and then decode asset.encoded_content (and sidecar.encoded_content, when present). Use the file_name values from the response if you want to keep the returned names.

jq -r '.asset.encoded_content' response.json | base64 --decode > signed-output.jpg
if jq -e '.sidecar.encoded_content' response.json > /dev/null; then
  jq -r '.sidecar.encoded_content' response.json | base64 --decode > manifest.c2pa
fi

Response parameters

The response contains separate asset and sidecar artifact objects. It does not contain file_name, mime_type, content_size, encoded_signed_content, or manifest_store at the top level. To locate the active C2PA manifest, read manifest_store.active_manifest from a successful sidecar-validation response, or inspect the signed artifact with a C2PA reader. Use that label to select the manifest from manifest_store.manifests. Do not use the signing response manifest_id as the manifest-store lookup key.

FieldTypeDescription
manifest_locationStringPlacement achieved: EMBEDDED, SIDECAR, or BOTH.
assetObjectThe returned asset. Always present.
sidecarObjectThe detached Manifest Store. Present when manifest_location is SIDECAR or BOTH.
manifest_idStringOpaque identifier returned by the signing service. It is separate from the active C2PA manifest label. Read that label from the artifact or from manifest_store.active_manifest in a successful sidecar-validation response.

asset and sidecar objects

FieldTypeDescription
file_nameStringOutput filename. The asset name is your uploaded filename prefixed with signed-, for example signed-sample.jpg. The sidecar name uses the same stem with the .c2pa extension, for example signed-sample.c2pa.
mime_typeStringapplication/c2pa for a sidecar. For the asset, the content type of the uploaded file part (application/octet-stream if none was sent).
content_sizeIntegerSize of the artifact in bytes.
modified_from_inputBooleantrue when the bytes differ from what you uploaded. Always false for a sidecar.
encoded_contentStringBase64-encoded artifact bytes. Decode before saving.

Success response shape

{
  "manifest_location": "BOTH",
  "asset": {
    "file_name": "signed-sample.jpg",
    "mime_type": "image/jpeg",
    "content_size": 1832874,
    "modified_from_input": true,
    "encoded_content": "<base64-data>"
  },
  "sidecar": {
    "file_name": "signed-sample.c2pa",
    "mime_type": "application/c2pa",
    "content_size": 48211,
    "modified_from_input": false,
    "encoded_content": "<base64-data>"
  },
  "manifest_id": "b37e74fd-ff0b-4cdd-8656-3b5b020675f3"
}

Require HTTP 200 and asset.encoded_content before treating a request as successful.

Validate sidecar response

A sidecar that is evaluated and found invalid is reported with HTTP 200 and valid set to false. HTTP errors are reserved for requests that could not be processed.

FieldTypeDescription
validBooleantrue only when the signature and the hard binding to the asset check out.
validation_stateStringPresent only when valid is true. trusted: the signing credential chains to the trust list. valid: signature and binding are correct but trust was not established (not an endorsement of the signer). untrusted: returned only when the public trust-list check is skipped by configuration.
manifest_storeObjectThe full manifest store as a JSON object. Present only when valid is true.
failure_codeStringPresent only when valid is false. One of sidecar.unreadable, sidecar.noActiveManifest, sidecar.untrusted (the signature and binding are correct, but the signing credential is not on the service’s trust list), sidecar.invalid, cawg.identity.unverified (a CAWG identity assertion is present but its signature could not be verified), or a C2PA validation status code. Hard-binding mismatches (the sidecar does not match the asset bytes) are reported as assertion.dataHash.mismatch, assertion.bmffHash.mismatch, assertion.boxesHash.mismatch, or assertion.boxesHash.unknownBox. Other C2PA codes include claimSignature.mismatch, signingCredential.ocsp.revoked, signingCredential.expired, and signingCredential.invalid.
failure_reasonStringPresent only when valid is false. A fixed, human-readable message chosen by failure_code. Use failure_code for programmatic handling.

For /validate-sidecar, send a request part with content type application/json containing account_id, plus one file part and one sidecar part. All three parts are required. The sidecar must be a non-empty file whose name ends in .c2pa. Its part content type must be application/c2pa, application/x-c2pa-manifest-store, or application/octet-stream (or absent). A sidecar is limited to 16 MB.

Checking a validation result A successful HTTP exchange does not establish that the manifest is valid. In application code, check valid after checking the HTTP status. Read validation_state and manifest_store only when valid is true; otherwise report failure_code and failure_reason. The returned manifest_store can be several megabytes.

import json
import os
from pathlib import Path
import requests

url = (
    "https://one.digicert.com"
    "/documentmanager/api/c2pa/v1/validate-sidecar"
)
headers = {"x-api-key": os.environ["DIGICERT_API_KEY"]}
request_part = json.dumps({"account_id": os.environ["DIGICERT_ACCOUNT_ID"]})
with Path("image.jpg").open("rb") as asset, \
        Path("manifest.c2pa").open("rb") as sidecar:
    response = requests.post(
        url, headers=headers,
        files={
            "request": (None, request_part, "application/json"),
            "file": ("image.jpg", asset, "image/jpeg"),
            "sidecar": ("manifest.c2pa", sidecar, "application/c2pa"),
        },
        timeout=180, allow_redirects=False,
    )
response.raise_for_status()
result = response.json()
if not result["valid"]:
    raise RuntimeError(
        f"{result['failure_code']}: {result['failure_reason']}"
    )
store = result["manifest_store"]
active_label = store["active_manifest"]
active_manifest = store["manifests"][active_label]
print("Validation state:", result["validation_state"])

Errors and troubleshooting

HTTP response codes

HTTPErrorMeaningWhat your application should do
200—The request was processed. For sidecar validation, inspect valid to determine the result.Decode signing artifacts. For sidecar validation, check valid and, when false, failure_code.
400invalid_requestA required field, value, role, action, URI, vocabulary value, unknown /sign JSON property, or JSON payload is invalid; the filename is missing, too long, or does not match the content; a file or previous_sidecar upload is duplicated; SIDECAR was requested for an asset that already has an embedded manifest; the file’s existing Content Credentials fail validation; a previous_sidecar does not match the file; the account_id cannot be used; or media signing or CAWG identity signing is not enabled for the account.Correct the request. Do not retry it unchanged. If a feature is not enabled, contact Support.
401wrong_token or unauthorized_clientThe API key is missing, invalid, expired, or belongs to a different environment, or no user session could be established for the request.Use a valid key and account ID from the selected environment. Handle every 401 the same way, regardless of the error value.
403access_deniedThe caller lacks the signing permission.Use credentials with signing permission for the account.
413invalid_requestThe upload exceeds the file, sidecar, or request size limit.Reduce the size and retry.
415unsupported_media_typeA file or sidecar type is unsupported, a sidecar is not a .c2pa file, or the JSON request part uses an unsupported content type.Use a supported media type and sidecar extension; set the request part content type to application/json.
429concurrent_transactionToo many signing requests for the account.Retry with bounded exponential backoff and jitter.
500server_errorAn unexpected server condition occurred.Retry safely after a delay; contact Support if it persists.
502server_errorThe signing service or certificate retrieval failed, or no validated organization is associated with the account.Retry after a delay. If it persists, confirm the account has a validated organization, and then contact Support.
503temporarily_unavailableThe service is unavailable because of maintenance or high load.Retry later and check DigiCert system status.
504server_errorAn upstream operation did not complete in time.Retry later.

Every error response has the same shape: an error code from the table and a human-readable error_description. Use the HTTP status and error for programmatic handling; error_description text can change.

401 response

{
  "error": "wrong_token",
  "error_description": "Invalid session."
}

429 response

{
  "error": "concurrent_transaction",
  "error_description": "Too many signing requests for this account. Try again later."
}

Troubleshooting tips

  • Authentication failure: Confirm the correct host for the authentication method and environment; for API keys, confirm x-api-key is present and that the key and account_id belong to the endpoint environment; for mTLS, confirm the Postman Host contains only the hostname, Port is 443, and the PFX passphrase is correct.
  • Request format error: Send the options as one JSON part named request with content type application/json. Do not send the removed form fields (accountId, role, additionalActions, createdWithAi, isNewCreation, otp, and so on).
  • Unknown property error: Check the spelling and snake_case field names in the /sign request JSON. Use the documented structured fields; ai_disclosure.metadata accepts a free-form JSON object.
  • CAWG signing error: Confirm signing.cawg_identity contains both credential_id and pin, that the credential is an S/MIME BR credential for CAWG signing, and that roles are valid for the signing mode.
  • File error: Confirm the file exists, uses a supported extension that matches its content, has a filename shorter than 64 characters (excluding the extension), and that only one file part is sent.
  • Role error: Send roles as an array, one per element, for example "roles": ["creator", "publisher"]. Standard signing accepts only creator, contributor, and publisher.
  • Actions error: Send provenance.actions as an array, use only the declarable action names, use each action name once, use when (not time), and do not send c2pa.created, c2pa.opened, or c2pa.placed.
  • Vocabulary error: digital_source_type, ai_disclosure.model_type, and ai_disclosure.scientific_domains accept only the listed values, matched exactly. Use the http:// form of IPTC digital source type URIs.
  • Usage permission error: Use lowercase allowed, prohibited, or constrained. Send constraints_info with constrained, and only with constrained.
  • External-reference error: Send an absolute https:// (recommended) or http:// URL with a host, together with a valid IANA media type.
  • Unexpected c2pa.opened: Check whether the input already contains Content Credentials. Existing provenance takes precedence over provenance.creation.
  • Existing Content Credentials rejected: A 400 for a file that already contains Content Credentials means those credentials did not validate—for example, the file was modified after it was signed, or the signing credential is expired, revoked, or not trusted by the service. Sign a copy whose credentials validate, or the original file without Content Credentials.
  • Previous sidecar rejected: A 400 with previous_sidecar means the sidecar does not match the uploaded bytes. Send the exact asset bytes the sidecar was issued for.
  • Unexpected digital source type on an action: Set digital_source_type on each action. Omitted values use the defaults described in User-defined manifest actions, which differ for new creations and opened content.
  • Unexpected manifest placement: Read manifest_location in the response. If SIDECAR fails with HTTP 400, check whether the asset already contains an embedded manifest.
  • Multipart error: Remove a manually set Content-Type header and allow your HTTP client to generate the boundary.

What changed

AreaChange
Request formatOptions are sent in one JSON request part, grouped into signing, provenance, and assertions. Property names are snake_case; unknown fields in the defined /sign JSON objects return HTTP 400.
Field mappingaccountId → account_id; role (comma-separated) → signing.roles (array); credentialId/pin → signing.cawg_identity; isNewCreation/createdWithAi/digitalSourceType → provenance.creation; editedWithAi → a provenance.actions entry for c2pa.edited with the compositeWithTrainedAlgorithmicMedia digital source type; additionalActions object → provenance.actions array (time → when, software/version → software_agent); permission fields and corresponding ConstraintsInfo fields → assertions.usage_rights.<key>.permission/constraints_info; externalReferenceUri/externalReferenceContentType → assertions.external_reference; includeExifMetadata → assertions.exif.extract_from_asset; aiDisclosureModelType/aiDisclosureModelName/aiDisclosureModelIdentifier/aiDisclosureHumanOversightLevel → assertions.ai_disclosure.model_type/model_name/model_identifier/content_profile.human_oversight_level; aiDisclosureScientificDomain (string) → assertions.ai_disclosure.scientific_domains (array); aiDisclosureMetadata (JSON string) → assertions.ai_disclosure.metadata (JSON object).
Removedotp, hashAlgo, signAlgo, signAlgoParams, and the translator role. sponsor, editor, and producer remain CAWG-only roles.
Newsigning.manifest_output (required), sidecar output, previous_sidecar, per-action digital_source_type, and the /validate-sidecar endpoint.
ResponseTop-level file_name, mime_type, content_size, encoded_signed_content, and manifest_store replaced by manifest_location, asset, sidecar, and manifest_id.
ValidationOrphan constraints_info values, a constrained permission without constraints_info, and a content type without an external-reference URI now return HTTP 400.

References