> ## 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.

# Create a Case via the API

> Open a case from your own code, set its owner and contacts, and write data into it.

Creating a case takes one API call. You choose where its fields come from — a case template, or an
inline definition in the same request — and the response returns the case `id` you use for
everything that follows.

This guide covers the most common path: instantiating a case template. Creating a case without a
template is covered under [Create a case without a template](#create-a-case-without-a-template).

## Before you start

You need:

* A **workspace API token** (`pnbx_…`). See [Authentication](/api-reference/authentication).
* The **id of a case template** in your workspace. Step 1 shows how to list them.
* Base URL: `https://connect.penbox.io/v1`.

The examples below use these values:

| Value            | Example                                      |
| ---------------- | -------------------------------------------- |
| Case template id | `3f8a1c42-9b7e-4d51-8f2a-6c1d0e5b7a93`       |
| Case id          | `c1d2e3f4-5a6b-47c8-9d0e-1f2a3b4c5d6e`       |
| Owner            | `claims@acme.com`, a member of the workspace |
| Contact          | Marie Dupont, the customer the case is about |

<Steps>
  <Step title="List your case templates">
    Read the template ids with
    [List Case Templates](/api-reference/case-templates/list-case-templates):

    ```bash theme={null}
    curl -X GET 'https://connect.penbox.io/v1/case_templates' \
      -H "Authorization: Bearer $PENBOX_API_TOKEN"
    ```

    Template ids are stable. Cache them per workspace rather than listing before every case.
  </Step>

  <Step title="Create the case">
    Pass the template id. The case inherits the template's fields, sections, statuses and steps.

    ```bash theme={null}
    curl -X POST 'https://connect.penbox.io/v1/cases' \
      -H "Authorization: Bearer $PENBOX_API_TOKEN" \
      -H 'Content-Type: application/json' \
      -d '{
        "template": { "id": "3f8a1c42-9b7e-4d51-8f2a-6c1d0e5b7a93" },
        "title": "Claim 2026-0042 - Dupont",
        "status": "in_progress",
        "reference": "CLAIM-2026-0042",
        "owner": { "email": "claims@acme.com" },
        "contacts": [
          {
            "key": "main",
            "given_name": "Marie",
            "family_name": "Dupont",
            "email": "marie.dupont@example.com",
            "locale": "fr"
          }
        ]
      }'
    ```

    **`title` is required.** `template`, `status`, `reference`, `owner` and `contacts` are optional;
    each optional field has an automatic behaviour when omitted — see
    [Defaults](#defaults-when-you-omit-a-field).

    **`owner` and `contacts` are different populations.** The owner is a **member** of the workspace,
    someone on your team who works the case. Contacts are the **customers** the case is about, and
    the people you ask documents from. A contact cannot own a case.

    The `key` of a contact is the role it fills, such as `main` or `billing`. Use the keys the
    template declares.

    Response, partial:

    ```json theme={null}
    {
      "id": "c1d2e3f4-5a6b-47c8-9d0e-1f2a3b4c5d6e",
      "workspace": { "id": "d5c9f2a1-4e7b-4c83-9f16-2b8a7e3d0c54" },
      "title": "Claim 2026-0042 - Dupont",
      "reference": "CLAIM-2026-0042",
      "status": "in_progress",
      "template": "Claim intake",
      "locale": "fr",
      "schema": {
        "data": [
          {
            "key": "signed_contract",
            "name": "Signed contract",
            "type": "file",
            "options": { "accept": ["application/pdf", "image/*"], "multiple": true }
          },
          {
            "key": "policy_number",
            "name": "Policy number",
            "type": "text"
          }
        ],
        "other": []
      }
    }
    ```

    Keep `id`. Every later call needs it.

    Three things to know when you parse this response:

    * `schema` is an **array in the request and an object in the response**, with the case fields
      under `schema.data`.
    * A field with no value **has no `value` key**. Test for the key rather than comparing to `null`.
    * `template` is returned as the template's **name**, not as the object you sent. Keep the id on
      your side if you need it later.

    The case takes the template's `locale`, which sets the language of everything a contact sees.
  </Step>

  <Step title="Write data into the case">
    Set any field of the schema with
    [Update Case Data](/api-reference/cases/update-case-data). One call accepts as many fields as you
    need:

    ```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": { "policy_number": "POL-2026-0042" } }'
    ```

    Penbox keeps the full history of each field. Sending a new value keeps the previous one
    retrievable under `history`, so you do not need to version case data yourself.

    Documents use a different sequence: upload the file first, then save its reference. See
    [Add a Document to a Case](/guides/upload-document-to-case).
  </Step>
</Steps>

## Create a case without a template

Declare the fields inline with `schema`. Each entry needs a `key`, a `name` and a `type`. An entry
with a `value` prefills the field; an entry without one creates it empty.

```bash theme={null}
curl -X POST 'https://connect.penbox.io/v1/cases' \
  -H "Authorization: Bearer $PENBOX_API_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
    "title": "Claim 2026-0042 - Dupont",
    "status": "in_progress",
    "schema": [
      { "key": "signed_contract", "name": "Signed contract", "type": "file" },
      { "key": "policy_number", "name": "Policy number", "type": "text", "value": "POL-2026-0042" }
    ]
  }'
```

The case then has no template, no statuses beyond the default ones, and no steps. Its `locale`
defaults to `en` unless you set it.

Field types and their options are listed in the [Data Schema](/cases/data_schema) reference.

## Choose the workspace

A token scoped to a single workspace needs nothing: the case is created there.

A token scoped to several workspaces, or a partner token, must name the target workspace **in the
request body**:

```json theme={null}
{
  "template": { "id": "3f8a1c42-9b7e-4d51-8f2a-6c1d0e5b7a93" },
  "title": "Claim 2026-0042 - Dupont",
  "workspace": { "id": "d5c9f2a1-4e7b-4c83-9f16-2b8a7e3d0c54" }
}
```

<Warning>
  Set the workspace in the body, not in the query string. This endpoint ignores a `workspace[id]`
  query parameter and answers `200`, having created the case in the token's own workspace. The
  `workspace[id]` query parameter is read on
  [Upload Attachment](/api-reference/attachments/upload-attachments), which makes the difference easy
  to miss.
</Warning>

A workspace outside the token's scope answers `404`.

## Defaults when you omit a field

| Field       | When omitted                                                                                                                                                                                                                                                                       |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `status`    | The case is created as `draft`. Send `"status": "in_progress"` to open it directly, or change it later with `POST /v1/cases/{id}` — the endpoint documented as [Archive Case](/api-reference/cases/archive-case). With a template, you can use the statuses the template declares. |
| `reference` | Penbox generates a short reference such as `U7TP18`. Send your own if you need to find the case from your system later; it cannot be inferred afterwards.                                                                                                                          |
| `locale`    | Taken from the template, or `en` without a template.                                                                                                                                                                                                                               |

## Contact fields in the schema

`schema.data` contains a field per contact role and identity attribute — `main_given_name`,
`main_family_name`, `main_email` for the `main` role. They carry a `contact` property naming what
they mirror, such as `"contact": "main.family_name"`, and they appear whether or not you send a
`contacts` array.

Read them if it is convenient, but do not write to them, and do not declare data fields of your own
for a contact's personal information. The values live on the contact record.

## Fields outside the schema

`POST /v1/cases/{id}/data` accepts any key. A key the schema does not declare is stored under
`schema.other` instead of `schema.data`, and it is not displayed as a case field.

Check your keys against the schema returned at creation if a value you sent does not appear on the
case.

## Related

<CardGroup cols={2}>
  <Card title="Add a Document to a Case" icon="paperclip" href="/guides/upload-document-to-case">
    Uploading a file and saving it on a case field
  </Card>

  <Card title="Create Case" icon="folder-plus" href="/api-reference/cases/create-case">
    The endpoint reference, with every parameter
  </Card>

  <Card title="Data Schema" icon="table-columns" href="/cases/data_schema">
    Every field type and its options
  </Card>

  <Card title="Update Case Data" icon="pen-to-square" href="/api-reference/cases/update-case-data">
    Writing values into case fields
  </Card>
</CardGroup>
