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

# Add a Document to a Case

> Upload a file, save its reference in a case field, and retrieve it later.

Adding a document to a case takes two API calls: upload the file, then save its reference in a case
field. Uploading alone does not attach anything to the case — the second call is what places the
document on it.

Check these three points before you build on this sequence:

* Uploading requires a **workspace API token** (`pnbx_…`). A user token obtained through OAuth is
  rejected on the upload endpoint.
* **Downloads have a lower size limit than uploads**: 10 GB up, 50 MB back. Check the download limit
  before uploading documents you need to retrieve through the API.
* Saving a document on a field **does not run Document Intelligence**. Extraction on this path is an
  explicit call — see [Extract data from the document](#extract-data-from-the-document).

## Before you start

You need:

* A workspace API token (`pnbx_…`). See [Authentication](/api-reference/authentication).
* A case with a **file field**, and its id. See
  [Create a Case via the API](/guides/create-case-via-api).
* Base URL: `https://connect.penbox.io/v1`.

The examples below use these values:

| Value          | Example                                |
| -------------- | -------------------------------------- |
| Case id        | `c1d2e3f4-5a6b-47c8-9d0e-1f2a3b4c5d6e` |
| File field key | `signed_contract`                      |
| Local file     | `signed-contract.pdf`                  |

<Steps>
  <Step title="Upload the file">
    Send the file as `multipart/form-data`:

    ```bash theme={null}
    curl -X POST 'https://connect.penbox.io/v1/attachments' \
      -H "Authorization: Bearer $PENBOX_API_TOKEN" \
      -F 'file=@./signed-contract.pdf;type=application/pdf'
    ```

    Response `201`, partial:

    ```json theme={null}
    {
      "id": "7b3e5f10-2c48-4a91-b6d3-8e5f2a1c9d47",
      "name": "signed-contract.pdf",
      "type": "application/pdf"
    }
    ```

    Keep `id`, `name` and `type`. The next call needs all three.

    The full response also returns the uploaded file itself, base64-encoded, in a `data` field. Read
    the three fields you need and discard the rest, and size your HTTP client's response buffer for
    the file you are sending rather than for the fields you keep.

    The response does not carry the file size. Take it from the file on disk if you want to store
    one.
  </Step>

  <Step title="Save the reference in the case field">
    Write the reference into `signed_contract`. The same call sets any other field, so send the
    scalar values you already have in the same request:

    ```bash theme={null}
    curl -X POST 'https://connect.penbox.io/v1/cases/c1d2e3f4-5a6b-47c8-9d0e-1f2a3b4c5d6e/data' \
      -H "Authorization: Bearer $PENBOX_API_TOKEN" \
      -H 'Content-Type: application/json' \
      -d '{
        "data": {
          "signed_contract": {
            "id": "7b3e5f10-2c48-4a91-b6d3-8e5f2a1c9d47",
            "name": "signed-contract.pdf",
            "type": "application/pdf",
            "size": 48213
          },
          "policy_number": "POL-2026-0042"
        }
      }'
    ```

    `id`, `name` and `type` are the reference. `size` is optional.

    <Warning>
      Always send the complete `{ id, name, type }` reference. The endpoint does not validate this
      value, so a bare id string, or an id that does not exist, can be saved successfully and then
      fail to display or download.
    </Warning>

    Response `200`, partial:

    ```json theme={null}
    {
      "schema": {
        "data": [
          {
            "key": "signed_contract",
            "name": "Signed contract",
            "type": "file",
            "value": {
              "id": "7b3e5f10-2c48-4a91-b6d3-8e5f2a1c9d47",
              "name": "signed-contract.pdf",
              "type": "application/pdf",
              "size": 48213
            }
          }
        ]
      }
    }
    ```
  </Step>

  <Step title="Retrieve the document">
    `GET /v1/cases/{id}` returns the reference under
    `schema.data[] | select(.key == "signed_contract") | .value`.

    To get the file itself, call the attachment endpoint. It returns JSON with the content
    base64-encoded in `data`:

    ```bash theme={null}
    curl -sS 'https://connect.penbox.io/v1/attachments/7b3e5f10-2c48-4a91-b6d3-8e5f2a1c9d47' \
      -H "Authorization: Bearer $PENBOX_API_TOKEN" \
      | jq -r .data | base64 -d > signed-contract.pdf
    ```

    <Warning>
      This response is limited to 50 MB. A larger document returns `400` and cannot currently be
      retrieved through the API. Keep documents you need to download under 50 MB, or deliver them to
      your recipients by another route.
    </Warning>
  </Step>
</Steps>

## Upload as JSON instead of multipart

Send the file base64-encoded in `data`:

```bash theme={null}
curl -X POST 'https://connect.penbox.io/v1/attachments' \
  -H "Authorization: Bearer $PENBOX_API_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "signed-contract.pdf",
    "data": "JVBERi0xLjQKMSAwIG9iajw8L1R5cGUvQ2F0YWxvZy9QYWdlcyAyIDAgUj4+ZW5kb2JqCg=="
  }'
```

Two differences from multipart:

* The MIME type comes from the **extension of `name`**. A `type` field in the body is not used, and a
  name without an extension produces `application/octet-stream`. Use multipart when the MIME type
  matters.
* Base64 inflates the payload by about a third, and the 10 GB limit applies to what is transmitted.
  The largest file you can send this way is roughly 7.5 GB.

## Save several files on one field

A field configured with `multiple: true` takes an array of references:

```json theme={null}
{
  "data": {
    "signed_contract": [
      { "id": "7b3e5f10-2c48-4a91-b6d3-8e5f2a1c9d47", "name": "signed-contract.pdf", "type": "application/pdf" },
      { "id": "1a9c8d72-6b04-4e35-8c7f-3d2e1b0a5f68", "name": "annex.pdf", "type": "application/pdf" }
    ]
  }
}
```

The array replaces the previous value. Include the files already on the field to keep them.

## Upload before the case exists

An attachment does not need a case, so you can upload first and pass the reference as a field value
when you [create the case](/guides/create-case-via-api):

```json theme={null}
{
  "title": "Claim 2026-0042 - Dupont",
  "schema": [
    {
      "key": "signed_contract",
      "name": "Signed contract",
      "type": "file",
      "value": {
        "id": "7b3e5f10-2c48-4a91-b6d3-8e5f2a1c9d47",
        "name": "signed-contract.pdf",
        "type": "application/pdf"
      }
    }
  ]
}
```

## Extract data from the document

The automatic extraction configured on a file field runs when a person uploads the document in the
app or in a form. Saving a reference through the API does not trigger it.

To extract from a document uploaded through the API, call
[`POST /v1/document_intelligence`](/api-reference/document-intelligence/create-document-intelligence)
with the same reference, then write the values it returns with a second `POST /v1/cases/{id}/data`.

```json theme={null}
{
  "attachments": [
    {
      "id": "7b3e5f10-2c48-4a91-b6d3-8e5f2a1c9d47",
      "name": "signed-contract.pdf",
      "type": "application/pdf"
    }
  ],
  "template": { "id": "6d0b4e93-1f52-4a86-b3c7-9e8d5a2f1b04" }
}
```

## The whole sequence in one script

```bash theme={null}
#!/usr/bin/env bash
set -euo pipefail

API="https://connect.penbox.io/v1"
AUTH="Authorization: Bearer $PENBOX_API_TOKEN"
CASE_ID="c1d2e3f4-5a6b-47c8-9d0e-1f2a3b4c5d6e"
DOCUMENT="./signed-contract.pdf"

# 1. Upload the file, keeping only the reference fields.
#    The size comes from the file: the response does not carry it.
REFERENCE=$(curl -sS --fail-with-body -X POST "$API/attachments" -H "$AUTH" \
  -F "file=@$DOCUMENT;type=application/pdf" \
  | jq -ce --argjson size "$(wc -c < "$DOCUMENT")" '{id, name, type, size: $size}')

# 2. Save the reference on the case field.
curl -sS --fail-with-body -X POST "$API/cases/$CASE_ID/data" -H "$AUTH" \
  -H 'Content-Type: application/json' \
  -d '{
    "data": {
      "signed_contract": '"$REFERENCE"',
      "policy_number": "POL-2026-0042"
    }
  }' | jq '.schema.data[] | select(.key == "signed_contract")'
```

## Limits and behaviours

|                        |                                                                                                                                                                                                                                                            |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Token type**         | Uploading requires a workspace API token (`pnbx_…`). A user token obtained through OAuth is rejected with `403` on `POST /v1/attachments`. The other calls in this guide accept either kind.                                                               |
| **Upload size**        | 10 GB per upload. On the JSON form the limit applies to the transmitted, base64-inflated payload, so roughly 7.5 GB of file.                                                                                                                               |
| **Download size**      | 50 MB per attachment.                                                                                                                                                                                                                                      |
| **Who can download**   | The attachment belongs to the workspace, not to the case. Your API token and workspace admins can download it. Other members, and the contacts filling a form on the case, cannot. Send the file as a step attachment if a recipient needs to download it. |
| **`scope`**            | The `scope` body field is not applied: every upload is created with the access rules above. Do not use it to make a document more, or less, confidential.                                                                                                  |
| **Case document list** | A referenced attachment appears in the case field, not in the case's own document list.                                                                                                                                                                    |

## Related

<CardGroup cols={2}>
  <Card title="Create a Case via the API" icon="folder-plus" href="/guides/create-case-via-api">
    Opening the case this document goes on
  </Card>

  <Card title="Upload Attachment" icon="upload" href="/api-reference/attachments/upload-attachments">
    The endpoint reference, with every parameter
  </Card>

  <Card title="File Field" icon="file" href="/cases/data_schema/file">
    Configuring a file field in a case template
  </Card>

  <Card title="Document Intelligence" icon="sparkles" href="/api-reference/document-intelligence/create-document-intelligence">
    Extracting structured data from a document
  </Card>
</CardGroup>
