> ## Documentation Index
> Fetch the complete documentation index at: https://docs.agentvolumes.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Upload trust attachments for a release

> Two-phase upload lifecycle for attaching BOMs, provenance attestations, and signatures to a published release — create an intent, PUT bytes, then finalize.

Trust attachments are append-only records associated with a published release subject. You upload them through the same two-phase lifecycle used for release archives: create an upload intent, PUT the attachment bytes to the returned URL, then finalize so the bibliotheca can verify the digest, validate subject binding, and make the attachment available through trust discovery. Authentication is required for all trust upload operations.

## Endpoints

```http theme={null}
# Create upload intent — scopeless
POST /api/v1/volumes/{name}/{version}/trust/uploads

# Create upload intent — scoped
POST /api/v1/volumes/@{scope}/{name}/{version}/trust/uploads

# Finalize — scopeless
POST /api/v1/volumes/{name}/{version}/trust/uploads/{uploadId}/finalize

# Finalize — scoped
POST /api/v1/volumes/@{scope}/{name}/{version}/trust/uploads/{uploadId}/finalize
```

## Upload lifecycle

<Steps>
  <Step title="Create an upload intent">
    Send a `POST` to the trust uploads endpoint with the release subject, attachment category, format metadata, and declared digest. The bibliotheca returns an `uploadId` and opaque upload instructions.

    ```http theme={null}
    POST /api/v1/volumes/@acme/research-agent-pack/1.4.0/trust/uploads
    Authorization: Bearer <your-token>
    Content-Type: application/json
    Idempotency-Key: provenance-1.4.0-build-42

    {
      "subject": {
        "purl": "pkg:volume/%40acme/research-agent-pack@1.4.0",
        "integrity": "sha256:a3f2b8c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2"
      },
      "attachment": {
        "category": "provenance",
        "format": {
          "family": "slsa-provenance",
          "mediaType": "application/vnd.in-toto+json",
          "predicateType": "https://slsa.dev/provenance/v1"
        }
      },
      "declaredDigest": "sha256:c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5",
      "declaredSize": 2048
    }
    ```

    The `201 Created` response mirrors the schema-backed intent record, including the bound subject, attachment metadata,
    declared digest, upload instructions, expiration time, and intent state:

    ```json theme={null}
    {
      "uploadId": "tup_01HZ9QRTXK4J2M8SVWBN6P3Y5C",
      "subject": {
        "purl": "pkg:volume/%40acme/research-agent-pack@1.4.0",
        "integrity": "sha256:a3f2b8c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2"
      },
      "attachment": {
        "category": "provenance",
        "format": {
          "family": "slsa-provenance",
          "mediaType": "application/vnd.in-toto+json",
          "predicateType": "https://slsa.dev/provenance/v1"
        }
      },
      "declaredDigest": "sha256:c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5",
      "declaredSize": 2048,
      "upload": {
        "instructionType": "http-put",
        "url": "https://upload.example.com/trust-staging/tup_01HZ9QRTXK4J2M8SVWBN6P3Y5C",
        "method": "PUT"
      },
      "expiresAt": "2026-05-18T16:00:00Z",
      "state": "pending-upload"
    }
    ```
  </Step>

  <Step title="Upload the attachment bytes">
    PUT the raw attachment bytes to the `upload.url` returned in step 1. Include any headers specified in `upload.headers`.

    ```http theme={null}
    PUT https://upload.example.com/trust-staging/tup_01HZ9QRTXK4J2M8SVWBN6P3Y5C
    Content-Type: application/vnd.in-toto+json
    Content-Length: 2048

    <raw attachment bytes>
    ```
  </Step>

  <Step title="Finalize the upload">
    Call the finalize endpoint with the `uploadId`. The bibliotheca verifies the digest, validates subject binding, and makes the attachment available through trust discovery.

    ```http theme={null}
    POST /api/v1/volumes/@acme/research-agent-pack/1.4.0/trust/uploads/tup_01HZ9QRTXK4J2M8SVWBN6P3Y5C/finalize
    Authorization: Bearer <your-token>
    ```

    The `201 Created` response includes the finalized attachment identity, bound subject, artifact digest, status, and detail
    URL:

    ```json theme={null}
    {
      "uploadId": "tup_01HZ9QRTXK4J2M8SVWBN6P3Y5C",
      "attachmentId": "att_01HZ9QRTXK4J2M8SVWBN6P3Y5C",
      "subject": {
        "purl": "pkg:volume/%40acme/research-agent-pack@1.4.0",
        "integrity": "sha256:a3f2b8c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2"
      },
      "artifactDigest": "sha256:c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5",
      "artifactSize": 2048,
      "status": {
        "state": "active"
      },
      "detailUrl": "https://api.example.com/api/v1/volumes/@acme/research-agent-pack/1.4.0/trust/detail"
    }
    ```
  </Step>
</Steps>

## Upload intent request fields

<ParamField body="subject" type="object" required>
  The release subject this attachment is bound to. Must match the release's published identity exactly.

  <Expandable title="subject fields">
    <ParamField body="subject.purl" type="string" required>
      Canonical `pkg:volume/...@version` logical identity of the release.
    </ParamField>

    <ParamField body="subject.integrity" type="string" required>
      `sha256:<hex>` immutable content identity of the release. Must match the published `integrity` value.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="attachment" type="object" required>
  Metadata describing the trust attachment you are uploading.

  <Expandable title="attachment fields">
    <ParamField body="attachment.category" type="string" required>
      Trust artifact category: `bom`, `provenance`, `signature`, or `other`.
    </ParamField>

    <ParamField body="attachment.format" type="object" required>
      Format identity for this attachment.

      <Expandable title="format fields">
        <ParamField body="attachment.format.family" type="string" required>
          Format family: `cyclonedx`, `slsa-provenance`, `sigstore-bundle`, or another identifier.
        </ParamField>

        <ParamField body="attachment.format.mediaType" type="string" required>
          MIME type of the attachment bytes.
        </ParamField>

        <ParamField body="attachment.format.predicateType" type="string">
          Predicate URI for in-toto/SLSA-style artifacts.
        </ParamField>

        <ParamField body="attachment.format.profile" type="string">
          Optional profile identifier.
        </ParamField>

        <ParamField body="attachment.format.version" type="string">
          Optional format version identifier.
        </ParamField>
      </Expandable>
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="declaredDigest" type="string" required>
  `sha256:<hex>` digest of the attachment bytes you will upload. The bibliotheca verifies the uploaded bytes match this value during finalize.
</ParamField>

<ParamField body="declaredSize" type="integer">
  Optional declared byte length of the attachment.
</ParamField>

## Idempotency

Pass an `Idempotency-Key` header or a `idempotencyKey` body field (not both, or they must match exactly) to make the intent creation idempotent.

```http theme={null}
POST /api/v1/volumes/@acme/research-agent-pack/1.4.0/trust/uploads
Authorization: Bearer <your-token>
Idempotency-Key: provenance-1.4.0-build-42
Content-Type: application/json

{ ... }
```

The header form is the preferred portable API surface. If you pass both the header and the body field, they must be byte-for-byte identical; otherwise the bibliotheca returns a `409 Conflict` with the `idempotency-conflict` problem type.

## Upload intent state lifecycle

| State            | Meaning                                |
| ---------------- | -------------------------------------- |
| `pending-upload` | Intent created; bytes not yet received |
| `uploading`      | Bytes transfer in progress             |
| `uploaded`       | Bytes received; ready to finalize      |
| `expired`        | Intent expired before finalization     |
| `failed`         | Upload failed                          |

## The `http-put` upload profile

Every write-capable v0.1 bibliotheca that supports trust attachment uploads must implement the `http-put` upload profile:

* `upload.instructionType = "http-put"`
* `upload.url` is the opaque byte-transfer target
* `upload.method` is `"PUT"` when present; default to `PUT` when omitted
* `upload.headers` contains headers you must include on the PUT request

If an upload intent advertises only unsupported profiles, fail locally with an unsupported-upload-profile diagnostic.

## What the bibliotheca verifies at finalize

During finalize, the bibliotheca:

1. Verifies the uploaded bytes match the `declaredDigest` (and `declaredSize` when provided)
2. Validates the release-subject binding — the attachment must bind to the intended release
3. Checks attachment metadata consistency
4. Confirms the caller remains authorized

A failed, expired, or conflicting upload will never silently create an active trust attachment.

## After finalization

After a successful finalize, the attachment appears in the trust detail view with `status.state = "active"`. From that point on, the attachment follows the same append-only lifecycle as all trust attachments — it can transition to `revoked`, `superseded`, or `invalid`, but it is never deleted.

<Note>
  Trust attachments are append-only. Previously published attachments are never silently replaced or
  deleted — they remain in the append-only record and are represented through status metadata
  changes.
</Note>
