# Complete and submit client intake

Complete an existing client intake: save answers, attach documents, and submit it to the receiving Portal, or email the form to the client to finish. Start with the intake returned by [business creation](https://docs.staging.levr.ai/guides/source-client-intake.md) or an existing intake you can access.

**Before you start:** your key needs `client_intake_applications:write` with access to this intake. This permission covers reading, editing, and submitting. Emailing the form separately requires `client_intake_sharing:write`. Saving answers alone does not submit the intake.

Every request uses `Authorization: Bearer <api-key>`. JSON writes use `Content-Type: application/json`. Give each answer write, file upload, and submission its own `Idempotency-Key`; retain the exact request and key for retries. Examples use abbreviated responses and illustrative IDs. Use returned URLs and question keys from the live contract.

Read the application's detail URL to find the links for this workflow:

```http
GET /v1/client-intake-applications/ciappl_example/
```

**200 OK — relevant fields**

```json
{
  "application": {
    "id": "ciappl_example",
    "status": { "key": "pre_submitted" },
    "contract_url": "https://YOUR_API_HOST/v1/client-intake-applications/ciappl_example/contract/",
    "answers_url": "https://YOUR_API_HOST/v1/client-intake-applications/ciappl_example/answers/",
    "submission_readiness_url": "https://YOUR_API_HOST/v1/client-intake-applications/ciappl_example/submission-readiness/",
    "submit_url": "https://YOUR_API_HOST/v1/client-intake-applications/ciappl_example/submit/"
  }
}
```

## 1. Read the questions and save answers

Read the application’s `contract_url`. It describes this intake's questions, accepted values, and available operations.

```http
GET /v1/client-intake-applications/ciappl_example/contract/
```

**Response — example question, abbreviated**

```json
{
  "questions": [
    {
      "block_key": "request",
      "label": "Funding request",
      "fields": [
        {
          "field_key": "amount",
          "value_schema": { "type": "string" }
        }
      ]
    }
  ]
}
```

The keys above are examples, not standard fields present in every intake. Match your data to the actual questions and their `value_schema`.

Read the application's `answers_url` to obtain the current values and its `ETag` header. Suppose that header is `ETag: "0"`. Save the amount using that exact ETag:

```http
PATCH /v1/client-intake-applications/ciappl_example/answers/
If-Match: "0"
Idempotency-Key: website-lead-12345-amount-1
```

```json
{
  "updates": [
    { "block_key": "request", "field_key": "amount", "value": "250000" }
  ]
}
```

**200 OK — relevant fields; response header `ETag: "1"`**

```json
{
  "answers_revision": 1,
  "updated_answers": [
    { "block_key": "request", "field_key": "amount", "value": "250000" }
  ]
}
```

When reading answers, pass a non-null `next_cursor` as the next request’s `cursor` query parameter until you have read every relevant page.

To correct the amount later, PATCH the same keys with the new value, the latest answers ETag, and a new idempotency key. Save only known facts. For repeated rows such as owners, POST to the contract's `operations.repeatable_items_url` with the group's `block_key`, the latest answers ETag, and a new idempotency key. Retain the returned `item.item_id`, then PATCH that URL with `item_id` and `updates`. Use the group and field keys from your contract.

## 2. Attach a supporting file

If the contract contains a file question, request an upload URL at its `operations.file_uploads_url`. For example, an intake might ask for a bank statement under the block key `bank_statement`:

```http
POST /v1/client-intake-applications/ciappl_example/answers/files/
```

```json
{ "block_key": "bank_statement" }
```

**201 Created — relevant fields**

```json
{
  "upload_url": "https://YOUR_API_HOST/v1/client-intake-applications/ciappl_example/answers/files/upl_example/",
  "multipart_field": "file"
}
```

Send the file bytes to that `upload_url` as one multipart field named `file`. Use the current answers ETag, respect the returned expiry and size limit, and let your HTTP client generate the multipart boundary. The short command here illustrates the binary upload; the other examples use JSON.

```sh
curl -X POST '<upload_url>' \
  -H 'Authorization: Bearer <api-key>' \
  -H 'If-Match: "1"' \
  -H 'Idempotency-Key: website-lead-12345-statement-1' \
  -F 'file=@bank-statement.pdf'
```

**201 Created — relevant fields; response header `ETag: "2"`**

```json
{ "answers_revision": 2 }
```

The upload attaches the file to its question. Do not add a file URL to the answer JSON. Read `operations.files_url` for attachment metadata; for a file inside a repeated row, include its `item_id` when requesting the upload capability.

After a connection failure, retry the original upload URL, file, key, and ETag to recover the result. Request a new upload URL only after a confirmed expiry rejection. On `412`, read the latest answers and reconcile changes before starting a new write attempt.

## 3. Complete and submit the intake

Your integration can finish the intake, or the client can complete it in a branded web form. Both use the same saved answers and files.

### Let the client finish

With `client_intake_sharing:write`, email the client a secure link to fill in the remaining answers, upload documents, and submit:

```http
POST /v1/client-intake-applications/ciappl_example/share/
Idempotency-Key: website-lead-12345-share
```

```json
{
  "recipients": [{ "email": "maya@example.com" }],
  "message": "Please complete and submit your intake form."
}
```

**201 Created — relevant fields**

```json
{
  "grants": [{ "email": "maya@example.com", "access_mode": "edit" }],
  "delivery_state": "queued"
}
```

The request queues an email containing the link. These intake links grant editing access and expire after 30 days. `queued` does not confirm delivery. Follow the submission webhook below to know when the client finishes.

Retry the original request and idempotency key after a lost response. To resend access deliberately, use a new key: this refreshes the invitation and revokes the recipient's previous sessions, so they should use the newest email.

### Submit through your integration

With `client_intake_applications:write`, complete the remaining questions, then GET the application's `submission_readiness_url`:

```http
GET /v1/client-intake-applications/ciappl_example/submission-readiness/
```

**Ready response — relevant fields**

```json
{
  "has_blocking_issues": false,
  "blocking_issue_count": 0,
  "issues": [],
  "submission_token": "<returned-submission-token>"
}
```

Readiness checks both the form and whether its current status permits submission. If there are blocking issues, `submission_token` is null. Correct the reported issues and check readiness again. A ready response supplies a token for the application and answers you just checked. Pass it unchanged to `submit_url`:

```http
POST /v1/client-intake-applications/ciappl_example/submit/
Idempotency-Key: intake-example-submit-1
```

```json
{ "submission_token": "<returned-submission-token>" }
```

**200 OK — relevant fields**

```json
{
  "application": {
    "id": "ciappl_example",
    "status": { "key": "submitted" },
    "submitted_at": "2026-10-15T16:30:00Z"
  }
}
```

If the application or answers change after the readiness check, submission returns `412`. Review the changes, check readiness again, and send the new token with a new idempotency key. After a connection failure, retry the original token and key to recover the result. Submission uses the token instead of an `If-Match` header; answer edits still use ETags.

Submission validates the answers again, records the submission, and notifies the receiving team according to the Portal's notification settings. If the Portal has enabled stage synchronization and mapped a stage to submitted intakes, submission moves the business to that stage. It also starts automated intake preparation when applicable. That background work can continue after the submission response; a successful submit does not mean downstream preparation is finished.

In the receiving Portal, open **Businesses → the business → Client intake** to review the submitted form and attachments.

## 4. Follow subsequent updates

Subscribe to `client_intake_application.submitted` and `client_intake_application.status_changed` using the [webhook setup guide](https://docs.staging.levr.ai/rest-api-reference/webhooks/). Set up the subscription before handing off or submitting the form if you need to capture that first event. Verify signatures, deduplicate deliveries, and reread the application to get its current state. Add `client_intake_application.updated` if you also need to follow saved answer changes before submission.

The Portal and shared form manage their own live websocket updates. Your API integration uses webhooks or REST polling; it does not need a websocket connection to follow the client's progress.

Without webhooks, poll the application detail URL at an appropriate interval. Your import is complete when the intended answers and files are saved and `application.status.key` is `submitted`; later review belongs to the receiving team.

## If a step fails

| Response | Next action |
| --- | --- |
| `401`, `403`, or record not found | Check the environment, ID, key permissions, and source scope. Read-only access uses `client_intake_applications:read`; editing and submitting require `client_intake_applications:write`. |
| `412` | Reread the affected resource and reconcile concurrent changes. For submission, check readiness again after reviewing the latest application and answers. |
| `409` on a retry | Reuse a key only for the identical command, including its precondition. |
| Validation error | Correct the named field against the live contract; for submission, inspect readiness and complete the missing answers. |

Keep the application ID after any failure and resume the same intake.
