> For the complete documentation index, see [llms.txt](https://docs.joinflo.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.joinflo.com/flo-apis/matter-api/overview.md).

# Overview

## Matters CSV Upload <a href="#matters-csv-upload" id="matters-csv-upload"></a>

Reference for the file format accepted by the matters CSV endpoints. Use alongside the OpenAPI spec for full request/response schemas.

### Endpoints <a href="#endpoints" id="endpoints"></a>

| Method | Path                                           | Purpose                                                                                                |
| ------ | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `POST` | `/matters-csv/validate-csv`                    | Returns row- and column-level errors without persisting. Use this first to catch issues before upload. |
| `POST` | `/matters-csv/upload-csv?reviewCycleId={UUID}` | Creates one matter per valid row. Rejects the entire file if any row fails validation.                 |

Both endpoints accept `multipart/form-data` with the CSV provided in the `file` part. `upload-csv` additionally requires the target review cycle as a query parameter.

### File-level requirements <a href="#file-level-requirements" id="file-level-requirements"></a>

| Requirement   | Value                                                  |
| ------------- | ------------------------------------------------------ |
| Encoding      | UTF-8. An optional UTF-8 BOM is accepted and stripped. |
| Max file size | 10 MB                                                  |
| Max data rows | 10,000 (after empty-row removal and deduplication)     |
| Min data rows | 1                                                      |
| Line endings  | LF or CRLF                                             |

A file exceeding the size limit, with zero data rows, or with more than 10,000 data rows is rejected with a file-level error (see [Validation response](http://localhost:63342/markdownPreview/1186859008/markdown-preview-index-18t8v9hgjsna3pu0afqb60edgr.html#validation-response)).

### Header row <a href="#header-row" id="header-row"></a>

The first line must be a header row. Column names are matched **by name, case-insensitively**. Column **order does not matter**. Extra unrecognized columns are ignored.

Recognized header names (case-insensitive):

```
Matter ID, Matter Name, Attorney Email, Billing Attorney Email,
Start Date, End Date, Hours Billed, Client Name, Description
```

A missing required column makes every row fail validation on that column.

### Column reference <a href="#column-reference" id="column-reference"></a>

| Column                     | Type   | Required | Constraints                                                                                                                                                                                                                                                                                                           | Example                     |
| -------------------------- | ------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- |
| **Matter ID**              | string | Yes      | 1–255 characters. Treated as an opaque identifier (need not be numeric). The same ID may appear on multiple rows only when every row uses the same **Matter Name** — see [Cross-row rules](http://localhost:63342/markdownPreview/1186859008/markdown-preview-index-18t8v9hgjsna3pu0afqb60edgr.html#cross-row-rules). | `M-1042`                    |
| **Matter Name**            | string | Yes      | 1–255 characters                                                                                                                                                                                                                                                                                                      | `Acme v. Beta`              |
| **Attorney Email**         | string | Yes      | Must exactly match the email of a registered Perform member in your firm. No fuzzy or alias matching.                                                                                                                                                                                                                 | `j.smith@firm.com`          |
| **Billing Attorney Email** | string | Yes      | Must exactly match the email of a registered Perform member in your firm.                                                                                                                                                                                                                                             | `b.jones@firm.com`          |
| **Hours Billed**           | number | Yes      | `0` ≤ value ≤ `999999.99`. Period (`.`) decimal separator. No thousands separators. Leading zeros allowed (`008.5`).                                                                                                                                                                                                  | `8.5`                       |
| **Start Date**             | date   | No       | Format `M/d/yyyy`. One- or two-digit month and day both accepted (`6/30/2025` or `06/30/2025`). Four-digit year required. If both dates are present, must be on or before **End Date**.                                                                                                                               | `6/30/2025`                 |
| **End Date**               | date   | No       | Format `M/d/yyyy`. If both dates are present, must be on or after **Start Date**.                                                                                                                                                                                                                                     | `12/31/2025`                |
| **Client Name**            | string | No       | Up to 255 characters                                                                                                                                                                                                                                                                                                  | `Acme Inc.`                 |
| **Description**            | string | No       | Up to 500 characters                                                                                                                                                                                                                                                                                                  | `Q4 estate-planning matter` |

#### Field handling rules that apply to every column <a href="#field-handling-rules-that-apply-to-every-column" id="field-handling-rules-that-apply-to-every-column"></a>

* **Leading and trailing whitespace is trimmed** from every value before validation.
* A field whose content is whitespace-only is treated as **missing** (`null`). Required-field rules still apply.
* Embedded commas, double quotes, and newlines in a value must be CSV-escaped per RFC 4180 (wrap the value in double quotes; double up internal `"`).

### Cross-row rules <a href="#cross-row-rules" id="cross-row-rules"></a>

These rules apply across rows within the same file:

* **Repeated Matter ID with mismatched name.** A Matter ID may appear on multiple rows, but every occurrence must use the same Matter Name. Different names → every involved row is flagged with a `Matter ID` column error.
* **Exact-duplicate rows are silently deduplicated** before upload. Two rows are considered duplicates when all of the following match: Matter ID, Matter Name, Attorney Email, Billing Attorney Email, Hours Billed, Client Name, Description. **Start Date and End Date are not part of the duplicate key** — two otherwise-identical rows with different dates collapse into one.
* **Empty rows are silently skipped.** They do not contribute to the 10,000 row limit and they do not shift the row numbers reported in validation errors — error `rowNumber` reflects the data-row position in your original file (1-indexed, header excluded), with empty rows preserving their slot.

### Recommended workflow: validate, then upload <a href="#recommended-workflow-validate-then-upload" id="recommended-workflow-validate-then-upload"></a>

1. `POST /matters-csv/validate-csv` with the file. Inspect the response.
2. If `fileError` is null and `rowErrors` is empty, the file is ready to upload.
3. `POST /matters-csv/upload-csv?reviewCycleId={UUID}` with the same file.

Validating first is strongly recommended: `upload-csv` is **all-or-nothing**, so any single bad row aborts the entire upload, but `upload-csv` itself returns no per-row diagnostics.

### Validation response <a href="#validation-response" id="validation-response"></a>

`/validate-csv` returns `200 OK` with this JSON shape regardless of whether the file passed or failed:

```json
{
  "numValidRows": 5,
  "rowErrors": [
    { "rowNumber": 3, "columnsWithErrors": ["Matter Name", "Attorney Email"] },
    { "rowNumber": 7, "columnsWithErrors": ["Start Date", "End Date"] }
  ],
  "fileError": null
}
```

| Field          | Meaning                                                                                                                              |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `numValidRows` | Number of data rows that passed all column- and cross-row checks.                                                                    |
| `rowErrors`    | One entry per data row that failed. Each lists the column header names that failed for that row. Empty array if every row passed.    |
| `fileError`    | Populated when the file itself is unusable. When non-null, the rest of the response is not meaningful. Possible values listed below. |

#### File-level errors (`fileError`) <a href="#file-level-errors-fileerror" id="file-level-errors-fileerror"></a>

| Value                 | Meaning                                                                                      |
| --------------------- | -------------------------------------------------------------------------------------------- |
| `"Invalid file size"` | Missing file, empty file, or file larger than 10 MB.                                         |
| `"Invalid row count"` | Zero data rows after empty-row removal and deduplication, **or** more than 10,000 data rows. |
| `"Invalid file"`      | Parse failure (e.g. malformed CSV, unreadable encoding).                                     |

#### Per-column errors <a href="#per-column-errors" id="per-column-errors"></a>

When a row appears in `rowErrors`, every column header that failed for that row is listed in `columnsWithErrors`. A column appears in the list when any of these conditions is true:

* The column is required and the value is missing or whitespace-only.
* The value exceeds the column's maximum length.
* The value cannot be parsed in the column's required format (date, number).
* A numeric value is outside the allowed range.
* The email does not match a registered Perform member.
* For `Start Date` / `End Date`: when both dates are present and Start is after End, **both** column names are included in the row's error list.
* For `Matter ID`: when the same ID appears on multiple rows with different Matter Names.

The response does not distinguish which of these reasons caused a given column to fail — the column simply appears in the list.

### Upload behavior <a href="#upload-behavior" id="upload-behavior"></a>

| Outcome                                                    | HTTP status        | Body          |
| ---------------------------------------------------------- | ------------------ | ------------- |
| All rows valid and at least one row was inserted           | `200 OK`           | empty         |
| Any row failed validation, or the file itself was unusable | `400 Bad Request`  | error message |
| `reviewCycleId` does not exist                             | `404 Not Found`    | error message |
| Unauthenticated                                            | `401 Unauthorized` | —             |
| Caller is not a Perform admin                              | `403 Forbidden`    | —             |

#### Idempotency note <a href="#idempotency-note" id="idempotency-note"></a>

`upload-csv` **silently skips matters whose Matter ID already exists on the target review cycle**. They are not updated and not reported. If your integration retries an upload, previously-inserted rows will be skipped on the retry and only new Matter IDs will be created.

If you need to detect skipped rows, query the review cycle's matters after the upload and compare against the IDs you submitted.

### Sample CSV <a href="#sample-csv" id="sample-csv"></a>

A minimal valid file with two rows:

```csv
Matter ID,Matter Name,Attorney Email,Billing Attorney Email,Start Date,End Date,Hours Billed,Client Name,Description
M-1042,Acme v. Beta,j.smith@firm.com,b.jones@firm.com,6/30/2025,12/31/2025,127.5,Acme Inc.,Q4 estate-planning matter
M-1043,Gamma Acquisition,j.smith@firm.com,b.jones@firm.com,,,8,Gamma Holdings,
```

The second row demonstrates that **Start Date**, **End Date**, and **Description** are optional (empty between the commas) and **Client Name** may also be empty.

### Example: curl <a href="#example-curl" id="example-curl"></a>

Validate:

```bash
curl -X POST 'https://<host>/matters-csv/validate-csv' \
  -H 'Authorization: Bearer <token>' \
  -F 'file=@matters.csv'
```

Upload:

```bash
curl -X POST 'https://<host>/matters-csv/upload-csv?reviewCycleId=00000000-0000-0000-0000-000000000000' \
  -H 'Authorization: Bearer <token>' \
  -F 'file=@matters.csv'
```

### Common pitfalls <a href="#common-pitfalls" id="common-pitfalls"></a>

| Symptom                                                          | Likely cause                                                                                                                                                                                                                                                                                                                                                                                               |
| ---------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Every row's `Attorney Email` (or `Billing Attorney Email`) fails | The email is not registered as a Perform member in your firm, or the casing/spelling does not exactly match the member record.                                                                                                                                                                                                                                                                             |
| `Start Date` and `End Date` both flagged on the same row         | Either one of them is in the wrong format, or Start is after End.                                                                                                                                                                                                                                                                                                                                          |
| `Matter ID` flagged on rows that look individually fine          | Same Matter ID appears elsewhere in the file with a different Matter Name. Make every occurrence use the same name.                                                                                                                                                                                                                                                                                        |
| Upload returns `400` with no row-level detail                    | Use `/validate-csv` first — `/upload-csv` does not return per-row errors.                                                                                                                                                                                                                                                                                                                                  |
| Some rows missing after a successful upload                      | Matter IDs already on the target review cycle are silently skipped (see [Idempotency note](http://localhost:63342/markdownPreview/1186859008/markdown-preview-index-18t8v9hgjsna3pu0afqb60edgr.html#idempotency-note)), or rows were collapsed by [duplicate-row deduplication](http://localhost:63342/markdownPreview/1186859008/markdown-preview-index-18t8v9hgjsna3pu0afqb60edgr.html#cross-row-rules). |
| `Hours Billed` flagged on a value that looks numeric             | Check for thousands separators (`1,250`) or comma decimal separators (`8,5`). Only `.` is accepted as the decimal point.                                                                                                                                                                                                                                                                                   |
| File rejected with `"Invalid row count"` despite having rows     | Either the file is empty after deduplication, or it exceeds 10,000 data rows. Split larger files.                                                                                                                                                                                                                                                                                                          |

<br>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.joinflo.com/flo-apis/matter-api/overview.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
