---
title: "Manifests"
description: "Attach JSON or C2PA/JUMBF manifests to an image, state a kind on each part, and read them back byte for byte."
published: 2026-09-21T23:43:52.722338+00:00
updated: 2026-09-21T23:43:52.722338+00:00
tags: ["c2pa", "manifests", "parallax"]
url: https://xiobjects.com/docs/xio/parallax/manifests
source: XI Objects
---

<!-- xion:doctype xion+markdown -->
<!-- xion:metadata
{
  "version": "1.0",
  "content_type": "application/xion\u002Bmarkdown",
  "source_type": "xi-content/doc",
  "generator": "xio-content-publisher/1.0.0",
  "generated": "2026-09-21T23:42:03.9673007\u002B00:00",
  "encoding": "utf-8",
  "render_intent": "markdown",
  "title": "Manifests",
  "slug": "xio/parallax/manifests",
  "copyright": "\u00A9 2026 XI Objects Inc"
}
-->

# Manifests

A manifest is a document you attach to an image. XI Parallax keeps it with the registration and hands it to the engine. It can be your own JSON, or a C2PA/JUMBF manifest.

An image carries zero or more manifests. Each one states its kind and its form.

## How to state one

Every manifest is its own multipart part, named `manifest[<kind>]`.

```bash
-F 'manifest[xi-manifest]={"caption":"a photo"};type=application/json'
-F "manifest[c2pa]=<manifest.jumbf;type=application/jumbf"
```

Two things are read off the part, and neither has a default.

**The kind** comes from the part name, between the brackets. `manifest[c2pa]` is a manifest of kind `c2pa`. A bare `manifest` part, or an empty `manifest[]`, states no kind and refuses the image it belongs to.

The kind must be 1 to 64 characters of `A-Z`, `a-z`, `0-9`, `.`, `_` or `-`. Anything else refuses the image.

XI Parallax validates the kind against no registry of its own. It carries the kind through to the engine exactly as you stated it. If the engine does not know the kind, that image's entry is refused at commit, carrying the engine's own refusal name. See [Errors and refusals](/docs/xio/parallax/errors-and-refusals).

**The form** comes from the part's own Content-Type.

| Content-Type | Form | Payload |
|--------------|------|---------|
| `application/json` | `json` | Must parse as a JSON object. |
| `application/jumbf` | `jumbf` | Opaque bytes. Not parsed. |
| `application/c2pa` | `jumbf` | Opaque bytes. Not parsed. |

Any other content type refuses the image. There is no default, not even for JSON.

## Where manifests go

| Route | Manifests |
|-------|-----------|
| `POST /registrations` | Zero or more `manifest[<kind>]` parts, all of which must come before the image part. |
| `POST /slots/{slotId}/uploads` | Zero or more `manifest[<kind>]` parts immediately before each `image` part. |
| `PUT /slots/{slotId}/entries/{imageHash}/manifests` | Zero or more `manifest[<kind>]` parts. This replaces the entry's whole list. |
| `POST /lookup/slots/{lookupSlotId}/queries` | None. A look-up request carries the image alone. A non-image part refuses the whole request. |
| `POST /lookup` | None, for the same reason. |

## Limits

| Limit | What happens |
|-------|---------------|
| Manifests per image | The image is refused with `An image carries at most N manifests.`, naming the cap. Weighed as parts arrive, so the extra parts are never read. |
| Bytes per manifest part | On `POST /registrations`, `413 Manifest too large`. On a slot upload, a per-image rejection reading `Manifest exceeds the size cap.` On the replace route, `413 Manifest too large`. |

The two caps compose: the manifest bytes one image can carry at once are at most the per-manifest cap times the per-image count cap.

## What comes back

The wire shape is the same everywhere manifests are returned.

| Field | Type | Description |
|-------|------|-------------|
| `type` | `string` | The kind exactly as you stated it on the part name. |
| `form` | `string` | `json` or `jumbf`. |
| `payload` | `object` or `string` | The bytes you posted. Inline JSON when `form` is `json`. Base64 when `form` is `jumbf`. |

```json
{
  "manifests": [
    { "type": "xi-manifest", "form": "json", "payload": { "caption": "carried as given" } },
    { "type": "c2pa", "form": "jumbf", "payload": "AAAAHGp1bWL/AIB/" }
  ]
}
```

The payload is your own bytes. A JSON payload is written back verbatim: the same key order, the same whitespace, the same escaping you posted. If you signed or digested your manifest body before posting it, that digest still verifies against what comes back.

XI Parallax converts between no forms. What you post in JSON comes back in JSON. What you post in JUMBF comes back in JUMBF.

Manifests come back on:

- `POST /registrations`, in the `manifests` field.
- A look-up match of your **own** registration, in each candidate's `manifests` field.

They never come back on a slot's manifest report. That report gives you `manifestsHash`, a digest of the stored list, so you can confirm the slot holds what you uploaded without the bodies travelling again.

Another account's manifests are never returned. A look-up match you do not own discloses no candidate at all, so none of its manifests reach you.

## A registered image's manifests never change

Once an image is registered, its manifests are fixed. There is no update route, and re-registering the same image is refused rather than accepted as an update.

To change them: [take the registration down](/docs/xio/parallax/take-down), then register the image again with the manifests you want.

Before commit is different. Inside an open slot you can change an entry's manifests as often as you like, either by re-uploading the same image bytes with new manifest parts or by calling `PUT /slots/{slotId}/entries/{imageHash}/manifests`. Both are whole list replaces: what the request carries is what the entry holds afterwards, and a request carrying no manifest part clears the list.

## Manifest refusals

Each of these refuses one image and leaves its siblings in the same request untouched.

| Sentence | Cause |
|----------|-------|
| `Manifest part states no kind; name it manifest[<kind>].` | The part is named `manifest` or `manifest[]`, or its bracket is unclosed. |
| `Manifest kind must be 1 to 64 characters of A-Z, a-z, 0-9, '.', '_' or '-'.` | The kind is too long or carries a character outside that set. The refusal never quotes your kind back. |
| `Manifest part declares an unsupported content type; it must be application/json, application/jumbf or application/c2pa.` | The part's Content-Type names no accepted form. |
| `Manifest exceeds the size cap.` | The part is past the per-manifest byte cap. |
| `Manifest is not a JSON object.` | A `application/json` part whose body is not a JSON object. |
| `An image carries at most N manifests.` | The image declared more manifests than the cap allows. |

On `POST /registrations` and the replace route these are answered as a `400 Invalid manifest` problem, or `413 Manifest too large` for the size cap. On a slot upload they are answered per image, in `rejectionReason`.
<!-- xion:trust
{
  "v": 1,
  "canon_v": 1,
  "ctx": "xiobjects.com/content",
  "hash_blake3_hex": "37fd8a5732dd7983e42daa7e09a56c0a5379aad1503162f54ae9bb4637972d25",
  "hash_sha256_hex": null,
  "sig_alg": "ed25519",
  "sig_b64": "jbVQ1Ou--l24N3dYSeYiq1d6a9F3o96lS-M1F_C9USL08TNrUXiIn6pPPuN3PG8gVnmgVzRfQ4c3CMTsHIp4BA",
  "pubkey_b64": "VrV0rqYGnIqutEwPTr11jfrjH9wmtkOlL68wL0W6ed0",
  "x509_chain_pem": [
    "-----BEGIN CERTIFICATE-----\nMIIB9DCCAaagAwIBAgIQbmGqXUijS3XuRUHDuVME8jAFBgMrZXAwLjEsMCoGA1UE\nAwwjWEkgT2JqZWN0cyBJbmMgQ29udHJvbCBJbnRlcm1lZGlhdGUwHhcNMjYwOTIx\nMjMzNjU1WhcNMjYxMDIxMjMzNjU1WjBLMR4wHAYDVQQDDBV4aW8tY29udGVudC1w\ndWJsaXNoZXIxFzAVBgNVBAoMDlhJIE9iamVjdHMgSW5jMRAwDgYDVQQLDAdDb250\nZW50MCowBQYDK2VwAyEAVrV0rqYGnIqutEwPTr11jfrjH9wmtkOlL68wL0W6ed2j\ngbwwgbkwDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYI\nKwYBBQUHAyQwZQYDVR0jBF4wXIAUOym3mFmw/qs1fgKrujCkxhrTk7KhLqQsMCox\nKDAmBgNVBAMMH0luc3RpdHV0ZSBvZiBQcm92ZW5hbmNlIFJvb3QgQ0GCFFJgN/ix\nQn72H6h3T5lEr9f8lJQFMB0GA1UdDgQWBBTnptRqwN8T\u002B5J0zUSRl65iscaPUzAF\nBgMrZXADQQBTzG1wSuUk70ymEN3Mj6XxsS1c5egjDoy\u002BW/V2kko5c2a1Cs7c/kiD\n6H2y9z1DNSH5qjzLZcm9JKPN0mjCy8MO\n-----END CERTIFICATE-----\n",
    "-----BEGIN CERTIFICATE-----\nMIIByDCCAXqgAwIBAgIUUmA3\u002BLFCfvYfqHdPmUSv1/yUlAUwBQYDK2VwMCoxKDAm\nBgNVBAMMH0luc3RpdHV0ZSBvZiBQcm92ZW5hbmNlIFJvb3QgQ0EwHhcNMjUxMTAy\nMDMxNzEyWhcNMzAxMTAxMDMxNzEyWjAuMSwwKgYDVQQDDCNYSSBPYmplY3RzIElu\nYyBDb250cm9sIEludGVybWVkaWF0ZTAqMAUGAytlcAMhAFSS/pggSRmTcAMko7uc\nATH8OHgxVymd5mBFlPXbJkgio4GtMIGqMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYD\nVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBQ7KbeYWbD\u002BqzV\u002BAqu6MKTGGtOTsjBlBgNV\nHSMEXjBcgBQAZRTDswSVORu\u002BkUOKX6WvrOvmQKEupCwwKjEoMCYGA1UEAwwfSW5z\ndGl0dXRlIG9mIFByb3ZlbmFuY2UgUm9vdCBDQYIUJqoJlpiSFg\u002B7W5IJLMrLttgR\nQp4wBQYDK2VwA0EA5FOht7YOsVRPp/FOKMQ\u002B3Mo9JxrvGR3ylKWAWNm6OUV7N3DB\nI9cD62wU5I0d0EKDBy0CX9DnoqUyxv5yguraAA==\n-----END CERTIFICATE-----\n",
    "-----BEGIN CERTIFICATE-----\nMIIBaTCCARugAwIBAgIUJqoJlpiSFg\u002B7W5IJLMrLttgRQp4wBQYDK2VwMCoxKDAm\nBgNVBAMMH0luc3RpdHV0ZSBvZiBQcm92ZW5hbmNlIFJvb3QgQ0EwHhcNMjUxMTAy\nMDMwNTEyWhcNMzUxMDMxMDMwNTEyWjAqMSgwJgYDVQQDDB9JbnN0aXR1dGUgb2Yg\nUHJvdmVuYW5jZSBSb290IENBMCowBQYDK2VwAyEAEWNZl\u002Br3IC7\u002BgBh90Yo1kWk1\npZCVzVuFdFT7qBBU8W2jUzBRMB0GA1UdDgQWBBQAZRTDswSVORu\u002BkUOKX6WvrOvm\nQDAfBgNVHSMEGDAWgBQAZRTDswSVORu\u002BkUOKX6WvrOvmQDAPBgNVHRMBAf8EBTAD\nAQH/MAUGAytlcANBAO6QeydOFNrN75qNyftggYudsxMyl4w9qWkSdZ6hlhrRcbSr\niG9Si0kbrIJOwYB/LTBU0RM4Rl\u002Bo9PM3Qp0mPwo=\n-----END CERTIFICATE-----\n"
  ],
  "key_id": "_A4EpsXvfBfVEGu5deK65yN4EjBJsUpgkdoS25zPEFk",
  "created_at": "2026-09-21T23:42:03Z"
}
-->