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

# Onboard a client and ingest documents

> Create a new client with initial documents, or add documents to an existing client, and track binder ingestion

Most workflows in Filed start the same way: a client exists, documents are
attached to that client, and Filed ingests them into the client's binder as a
background task. This recipe covers the two most common onboarding paths using
operations already documented on the [Clients](/apis/clients) reference page:

* **New client with initial documents**: upload files, then create the client
  with those upload IDs in one call.
* **Add documents to an existing client**: upload files, then attach them to a
  client that already exists.

Both paths end with the same step: poll the binder ingestion [task](/apis/tasks)
until it completes. Everything here uses a **`workspaceToken`** (see
[Authentication](/guides/authentication)) and goes to the single GraphQL
endpoint:

```
https://router.apps.filed.com/graphql
```

```mermaid theme={null}
flowchart LR
  A["Your files"] -->|"upload"| B["Upload IDs"]
  B -->|new client| C["createClient<br/>(input.uploadIds)"]
  B -->|existing client| D["addClientDocuments<br/>(input.uploadIds)"]
  C -->|"returns taskId"| E["Binder ingestion task"]
  D -->|"returns taskId"| E
  E -->|poll| F["COMPLETED"]
```

## 1. Upload the files

Filed does not accept file bytes over GraphQL. Upload each file to the upload
endpoint first and collect the **upload IDs** it returns. Both the resumable
(tus) and direct upload paths produce the same kind of upload ID, and both
attach to a client the same way. See [Uploading
documents](/guides/uploading-documents) for the full upload contract.

At the end of this step you have one upload ID per file, for example
`018f9c2a-7b1e-7c3d-9a4e-2f6b1c8d0e5a`.

## 2. New client with initial documents

Create the client and pass the upload IDs in the same call.
[`createClient`](/apis/clients#create-a-client) takes a `CreateClientInput`
with the client's `name`, `externalId`, `returnType`, `taxYear`, and an optional
`uploadIds` list. When `uploadIds` is provided, Filed creates the client and
immediately starts a binder ingestion task for those documents.

```graphql theme={null}
mutation CreateClient($input: CreateClientInput!) {
  createClient(input: $input) {
    client {
      id
      name
      externalId
      status
      returnType
      taxYear
    }
    taskId
  }
}
```

```json theme={null}
{
  "input": {
    "name": "Jane Taxpayer",
    "externalId": "PMS-10432",
    "returnType": "F1040",
    "taxYear": 2025,
    "uploadIds": ["018f9c2a-7b1e-7c3d-9a4e-2f6b1c8d0e5a"]
  }
}
```

`CreateClientResult` returns the created `client` (read its `id` to use in
later steps) and a `taskId` for the binder ingestion task. `taskId` is `null`
when no `uploadIds` were supplied, so guard for that before polling.

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST https://router.apps.filed.com/graphql \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer YOUR_WORKSPACE_TOKEN" \
    -d '{
      "query": "mutation CreateClient($input: CreateClientInput!) { createClient(input: $input) { client { id name externalId status returnType taxYear } taskId } }",
      "variables": {
        "input": {
          "name": "Jane Taxpayer",
          "externalId": "PMS-10432",
          "returnType": "F1040",
          "taxYear": 2025,
          "uploadIds": ["018f9c2a-7b1e-7c3d-9a4e-2f6b1c8d0e5a"]
        }
      }
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json theme={null}
  {
    "data": {
      "createClient": {
        "client": {
          "id": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c",
          "name": "Jane Taxpayer",
          "externalId": "PMS-10432",
          "status": "active",
          "returnType": "F1040",
          "taxYear": 2025
        },
        "taskId": "018f9c2b-1a2b-7c3d-8e4f-5a6b7c8d9e0f"
      }
    }
  }
  ```
</ResponseExample>

Save the `client.id` (you need it for later workflows such as
[tax prep](/apis/tax-prep)) and the `taskId` (for the polling step below).

## 3. Add documents to an existing client

When the client already exists, attach new uploads with
[`addClientDocuments`](/apis/clients#add-documents-to-a-client). It takes an
`AddClientDocumentsInput` with the `clientId` and one or more `uploadIds`, and
returns a `taskId` for the binder ingestion task.

```graphql theme={null}
mutation AddClientDocuments($input: AddClientDocumentsInput!) {
  addClientDocuments(input: $input) {
    taskId
  }
}
```

```json theme={null}
{
  "input": {
    "clientId": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c",
    "uploadIds": ["018f9c2a-7b1e-7c3d-9a4e-2f6b1c8d0e5a"]
  }
}
```

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST https://router.apps.filed.com/graphql \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer YOUR_WORKSPACE_TOKEN" \
    -d '{
      "query": "mutation AddClientDocuments($input: AddClientDocumentsInput!) { addClientDocuments(input: $input) { taskId } }",
      "variables": {
        "input": {
          "clientId": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c",
          "uploadIds": ["018f9c2a-7b1e-7c3d-9a4e-2f6b1c8d0e5a"]
        }
      }
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json theme={null}
  {
    "data": {
      "addClientDocuments": {
        "taskId": "018f9c2b-1a2b-7c3d-8e4f-5a6b7c8d9e0f"
      }
    }
  }
  ```
</ResponseExample>

<Note>
  `addClientDocuments` always starts a fresh ingestion task for the supplied
  uploads. To retry a prior failed ingestion without re-uploading the files, use
  [`retriggerIngestion`](#re-run-ingestion-without-re-uploading) instead.
</Note>

### Re-run ingestion without re-uploading

When a client's binder ingestion task finished with `status: FAILED` and the
original files have not changed, you do not need to upload them again. Call
[`retriggerIngestion`](/apis/clients#re-run-binder-ingestion) with just the
`clientId` and Filed re-runs ingestion over the documents already attached to
the client.

```graphql theme={null}
mutation RetriggerIngestion($input: RetriggerIngestionInput!) {
  retriggerIngestion(input: $input) {
    taskId
  }
}
```

```json theme={null}
{
  "input": {
    "clientId": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c"
  }
}
```

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST https://router.apps.filed.com/graphql \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer YOUR_WORKSPACE_TOKEN" \
    -d '{
      "query": "mutation RetriggerIngestion($input: RetriggerIngestionInput!) { retriggerIngestion(input: $input) { taskId } }",
      "variables": {
        "input": {
          "clientId": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c"
        }
      }
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json theme={null}
  {
    "data": {
      "retriggerIngestion": {
        "taskId": "018f9c2b-1a2b-7c3d-8e4f-5a6b7c8d9e0f"
      }
    }
  }
  ```
</ResponseExample>

It returns a new `taskId` for the binder ingestion task, which you poll with
the same pattern as `addClientDocuments` (see [step 4](#4-poll-the-binder-ingestion-task)).

<Tip>
  Use `retriggerIngestion` when the files have not changed and you only need to
  retry the failed run. Use `addClientDocuments` with fresh upload IDs when the
  files themselves have changed, since `retriggerIngestion` only re-processes
  what is already attached to the client.
</Tip>

## 4. Poll the binder ingestion task

Both paths return a `taskId` for a `BINDER` task. Follow it to completion with
the [tasks API](/apis/tasks#check-a-single-tasks-status): list the client's
tasks narrowed to `type: BINDER`, read the entry whose `id` matches, and poll
on an interval until `status` is no longer `RUNNING`.

```graphql theme={null}
query ClientTaskStatus($clientId: ID!, $type: TaskType) {
  me {
    ... on WorkspaceUser {
      workspace {
        clients(filters: { ids: [$clientId] }) {
          tasks(type: $type) {
            id
            status
            startedAt
            completedAt
            errorMessage
            subTasks {
              type
              status
            }
          }
        }
      }
    }
  }
}
```

```json theme={null}
{
  "clientId": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c",
  "type": "BINDER"
}
```

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST https://router.apps.filed.com/graphql \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer YOUR_WORKSPACE_TOKEN" \
    -d '{
      "query": "query ClientTaskStatus($clientId: ID!, $type: TaskType) { me { ... on WorkspaceUser { workspace { clients(filters: { ids: [$clientId] }) { tasks(type: $type) { id status startedAt completedAt errorMessage subTasks { type status } } } } } } }",
      "variables": {
        "clientId": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c",
        "type": "BINDER"
      }
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json theme={null}
  {
    "data": {
      "me": {
        "workspace": {
          "clients": [
            {
              "tasks": [
                {
                  "id": "018f9c2b-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
                  "status": "COMPLETED",
                  "startedAt": "2026-07-04T09:15:00.000Z",
                  "completedAt": "2026-07-04T09:17:42.000Z",
                  "errorMessage": null,
                  "subTasks": [
                    { "type": "CONVERT_DOCUMENTS", "status": "COMPLETED" },
                    { "type": "CLASSIFY_SUBDOCS", "status": "COMPLETED" },
                    { "type": "EXTRACT_SUBDOCS", "status": "COMPLETED" },
                    { "type": "EXPORT_AND_INDEX", "status": "COMPLETED" }
                  ]
                }
              ]
            }
          ]
        }
      }
    }
  }
  ```
</ResponseExample>

<Tip>
  `COMPLETED` means the documents are filed in the binder. `FAILED` means it did
  not, and `errorMessage` explains why. `subTasks` show which stage is currently
  running, which is useful for progress UI while you poll.
</Tip>

## Next steps

Once ingestion is `COMPLETED`, the client's binder is ready. From here you can:

* Add more documents any time with
  [`addClientDocuments`](/apis/clients#add-documents-to-a-client).
* List and fetch the client through the [Clients API](/apis/clients).
* Follow any other background work with the [tasks API](/apis/tasks).
