Overview

Matters CSV Upload

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

Endpoints

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

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

Header row

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):

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

Column reference

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.

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.

Billing Attorney Email

string

Yes

Must exactly match the email of a registered Perform member in your firm.

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

  • 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

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.

  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

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

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)

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

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

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

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 minimal valid file with two rows:

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

Validate:

Upload:

Common pitfalls

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), or rows were collapsed by duplicate-row deduplication.

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.

Last updated