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
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
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
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 IDcolumn 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
rowNumberreflects the data-row position in your original file (1-indexed, header excluded), with empty rows preserving their slot.
Recommended workflow: validate, then upload
POST /matters-csv/validate-csvwith the file. Inspect the response.If
fileErroris null androwErrorsis empty, the file is ready to upload.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:
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)
fileError)"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
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
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

