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

# Cases

> Submit documents and manage cases.

A **case** represents a single candidate or evaluation subject and is identified by your
`externalId`. Each time you submit documents for the same `externalId`, a new **upload** is
added to the existing case. Credentials (extracted documents) accumulate across all uploads.

***

## Create a case / add an upload

```
POST /partner/v1/cases
Content-Type: multipart/form-data
```

If the `externalId` already exists for your organization, the files are added as a new upload
to the existing case. If not, a new case is created.

### Request fields

<ParamField body="files" type="file[]" required>
  One or more document files. Accepted formats: PDF, PNG, JPG, JPEG, WebP, ZIP.
  Up to 20 files per request. Max 30 MB per file, 40 MB total payload.
  Files inside a ZIP that are not of a supported type will appear in the upload's `files[]`
  list but will not be processed. PDFs are limited to 130 pages.
</ParamField>

<ParamField body="externalId" type="string" required>
  Your reference for this case: the candidate ID, application number, or any identifier
  that maps to a person in your system. Unique per organization. Subsequent submissions
  with the same `externalId` append to the existing case.
</ParamField>

<ParamField body="features" type="string (JSON array)">
  Which capabilities to run on this upload. Valid values: `"extraction"`, `"forensics"`,
  `"translation"`. Defaults to all three when omitted.

  ```
  features=["extraction","forensics"]
  ```
</ParamField>

<ParamField body="classify" type="boolean" default="true">
  Whether to run AI document classification. When `true` (default), TruEnroll analyses the
  uploaded files and splits them into credentials, one per education qualification
  identified. When `false`, the requested features run directly against the raw uploaded
  files instead of classified credentials. Use this only if you know the document type in advance.
</ParamField>

<ParamField body="metadata" type="object (JSON)">
  Arbitrary key-value pairs attached to the case. Values must be strings, numbers, or booleans.

  ```
  metadata={"applicationCycle":"2025","region":"APAC"}
  ```
</ParamField>

### Optional headers

<ParamField header="Idempotency-Key" type="string">
  Makes the upload safe to retry. If a request fails or times out, resend it with the same
  `Idempotency-Key` and an identical payload, and TruEnroll returns the original upload instead
  of creating a duplicate. Reusing a key with a *different* payload returns `409 Conflict`.
</ParamField>

<Info>
  A successful submission returns **`202 Accepted`**. The files are accepted and processing
  runs asynchronously. Watch [webhooks](/partner-api/webhooks) or poll `GET /cases/{id}` for results.
</Info>

### Example

```bash theme={null}
curl -X POST https://api.truenroll.com/partner/v1/cases \
  -H "x-api-key: $TRUENROLL_API_KEY" \
  -F "files=@transcript.pdf" \
  -F "externalId=applicant-9876" \
  -F 'features=["extraction","forensics","translation"]'
```

### Response fields

<ResponseField name="success" type="boolean">
  Always `true` on a successful response.
</ResponseField>

<ResponseField name="message" type="string">
  Human-readable confirmation, e.g. `"Upload accepted"`.
</ResponseField>

<ResponseField name="data" type="object">
  <Expandable title="data">
    <ResponseField name="caseId" type="string">
      TruEnroll's unique ID for the case. Use this to fetch results and check status.
    </ResponseField>

    <ResponseField name="externalId" type="string">
      The `externalId` you provided at submission.
    </ResponseField>

    <ResponseField name="upload" type="object">
      Details of the upload that was just created.

      <Expandable title="upload">
        <ResponseField name="uploadId" type="string">
          Unique ID for this upload. Each `POST /cases` call produces a new upload.
        </ResponseField>

        <ResponseField name="status" type="string">
          Aggregate processing status for this upload. One of `queued`, `processing`, `completed`, `failed`.
        </ResponseField>

        <ResponseField name="classify" type="boolean">
          Whether classification was requested for this upload.
        </ResponseField>

        <ResponseField name="features" type="object[]">
          The features enabled for this upload, each with its own status.

          <Expandable title="features[]">
            <ResponseField name="name" type="string">Feature name: `extraction`, `forensics`, or `translation`.</ResponseField>
            <ResponseField name="status" type="string">Per-feature status: `queued`, `processing`, `completed`, `failed`, or `skipped`.</ResponseField>
          </Expandable>
        </ResponseField>

        <ResponseField name="files" type="object[]">
          The source files included in this upload.

          <Expandable title="files[]">
            <ResponseField name="fileId" type="string">Unique ID for this file within the upload.</ResponseField>
            <ResponseField name="fileName" type="string">Original filename as uploaded.</ResponseField>
          </Expandable>
        </ResponseField>

        <ResponseField name="createdAt" type="string (ISO 8601)">Timestamp when the upload was created.</ResponseField>
        <ResponseField name="updatedAt" type="string (ISO 8601)">Timestamp when the upload was last updated.</ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>

```json theme={null}
{
  "success": true,
  "message": "Upload accepted",
  "meta": {},
  "data": {
    "caseId": "6850abc123def456ghi789",
    "externalId": "applicant-9876",
    "upload": {
      "uploadId": "6850def456ghi789abc123",
      "status": "processing",
      "classify": true,
      "features": [
        { "name": "extraction", "status": "processing" },
        { "name": "forensics", "status": "processing" },
        { "name": "translation", "status": "processing" }
      ],
      "files": [
        { "fileId": "file_abc", "fileName": "transcript.pdf" }
      ],
      "createdAt": "2025-06-22T10:00:00Z",
      "updatedAt": "2025-06-22T10:00:00Z"
    }
  }
}
```

***

## Get a case

```
GET /partner/v1/cases/{id}
```

Returns the full case including all uploads and their credentials.

### Path parameters

<ParamField path="id" type="string" required>
  TruEnroll case ID (`caseId`) returned when the case was created.
</ParamField>

### Response fields

<ResponseField name="data" type="object">
  <Expandable title="data">
    <ResponseField name="id" type="string">TruEnroll's unique case ID.</ResponseField>
    <ResponseField name="externalId" type="string">Your reference for this case.</ResponseField>

    <ResponseField name="status" type="string">
      Aggregate status across all uploads and features. One of `queued`, `processing`,
      `completed`, `failed`. A case is `completed` only when every upload and every
      enabled feature has reached a terminal state.
    </ResponseField>

    <ResponseField name="uploads" type="object[]">
      All uploads submitted for this case, one per `POST /cases` call.

      <Expandable title="uploads[]">
        <ResponseField name="uploadId" type="string">Unique ID for this upload.</ResponseField>
        <ResponseField name="status" type="string">Processing status: `queued`, `processing`, `completed`, `failed`.</ResponseField>
        <ResponseField name="classify" type="boolean">Whether classification was run on this upload.</ResponseField>

        <ResponseField name="features" type="object[]">
          Features enabled for this upload, each with its own status.

          <Expandable title="features[]">
            <ResponseField name="name" type="string">Feature name: `extraction`, `forensics`, or `translation`.</ResponseField>
            <ResponseField name="status" type="string">Per-feature status: `queued`, `processing`, `completed`, `failed`, or `skipped`.</ResponseField>
          </Expandable>
        </ResponseField>

        <ResponseField name="files" type="object[]">
          Source files included in this upload.

          <Expandable title="files[]">
            <ResponseField name="fileId" type="string">Unique file ID.</ResponseField>
            <ResponseField name="fileName" type="string">Original filename.</ResponseField>
          </Expandable>
        </ResponseField>

        <ResponseField name="createdAt" type="string (ISO 8601)">Upload creation timestamp.</ResponseField>
        <ResponseField name="updatedAt" type="string (ISO 8601)">Upload last-updated timestamp.</ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="credentials" type="object[]">
      Education credentials identified by classification across all uploads. One credential
      is produced per education qualification found. A single file can yield multiple credentials.

      <Expandable title="credentials[]">
        <ResponseField name="credentialId" type="string">Unique credential ID. Used to correlate extraction and forensics results.</ResponseField>
        <ResponseField name="uploadId" type="string">ID of the upload that produced this credential.</ResponseField>
        <ResponseField name="credentialType" type="string">Document category assigned by classification, e.g. `Academic Credentials`, `Healthcare & Licensure`. `null` if classification has not completed.</ResponseField>
        <ResponseField name="status" type="string">Processing status: `queued`, `processing`, `completed`, `failed`.</ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="createdAt" type="string (ISO 8601)">Case creation timestamp.</ResponseField>
    <ResponseField name="updatedAt" type="string (ISO 8601)">Case last-updated timestamp.</ResponseField>
  </Expandable>
</ResponseField>

```json theme={null}
{
  "success": true,
  "message": "Case retrieved",
  "meta": {},
  "data": {
    "id": "6850abc123def456ghi789",
    "externalId": "applicant-9876",
    "status": "processing",
    "createdAt": "2025-06-22T10:00:00Z",
    "updatedAt": "2025-06-22T10:05:00Z",
    "uploads": [
      {
        "uploadId": "6850def456ghi789abc123",
        "status": "completed",
        "classify": true,
        "features": [
          { "name": "extraction", "status": "completed" },
          { "name": "forensics", "status": "completed" },
          { "name": "translation", "status": "completed" }
        ],
        "files": [
          { "fileId": "file_abc", "fileName": "transcript.pdf" }
        ],
        "createdAt": "2025-06-22T10:00:00Z",
        "updatedAt": "2025-06-22T10:04:00Z"
      }
    ],
    "credentials": [
      {
        "credentialId": "6850ghi789abc123def456",
        "uploadId": "6850def456ghi789abc123",
        "credentialType": "Academic Credentials",
        "status": "completed"
      }
    ]
  }
}
```

***

## List cases

```
GET /partner/v1/cases
```

Returns all cases for your organization, newest first.

### Query parameters

<ParamField query="externalId" type="string">
  Filter by your external reference.
</ParamField>

<ParamField query="limit" type="number">
  Number of results per page. Default: `20`, max: `100`.
</ParamField>

<ParamField query="offset" type="number">
  Number of results to skip for pagination. Default: `0`.
</ParamField>

***

## Delete a case

```
DELETE /partner/v1/cases/{id}
```

Soft-deletes the case. It no longer appears in list results and subsequent `GET` requests return
`404`. A deleted case cannot receive new uploads. Submitting the same `externalId` after
deletion returns `409 Conflict`.

### Path parameters

<ParamField path="id" type="string" required>
  TruEnroll case ID.
</ParamField>

Returns `204 No Content` on success.

***

## Error codes

| Code  | Reason                                                                                                            |
| ----- | ----------------------------------------------------------------------------------------------------------------- |
| `202` | **Success.** Upload accepted for asynchronous processing                                                          |
| `400` | Missing files, unsupported format, a file below the 1 KB minimum, or invalid JSON                                 |
| `401` | No API key, or an invalid/expired key                                                                             |
| `403` | Key is not a partner key, key is inactive, or organization lacks a requested feature                              |
| `404` | Case not found or deleted                                                                                         |
| `409` | Submitting to a deleted case, a case still initializing, or reusing an `Idempotency-Key` with a different payload |
| `413` | Per-file (30 MB) or total payload (40 MB) size limit exceeded                                                     |

<Card title="Features & limits" icon="sliders" href="/partner-api/features-and-limits">
  Full details on upload constraints and feature capability requirements.
</Card>
