For the complete documentation index, see llms.txt. This page is also available as Markdown.

Overview

Matter API

The Matter API ingests a CSV of matter entries into a Perform review cycle. Each row in the file represents one attorney's work on a matter for a client; entries with the same (Matter ID, Client Name) share a single Client Matter parent (its Matter Name and Description).

There are two operations — upsert and validate — each with a recommended endpoint and a deprecated legacy endpoint:

Operation
Recommended
Deprecated

Write to a review cycle

POST /matters-csv/upsert

POST /matters-csv/upload-csv

Pre-flight validation

POST /matters-csv/validate-upsert

POST /matters-csv/validate-csv

Use /upsert and its pre-flight /validate-upsert. The legacy endpoints are retained for backwards compatibility only and behave differently in ways that make retries and re-uploads unsafe (see Choosing the right endpoint).

Pair the validation endpoint with the write endpoint it targets: /validate-upsert mirrors the rules /upsert enforces (including in-file duplicate detection), while /validate-csv mirrors the older /upload-csv rules and will not catch upsert-key duplicates within a file.


Choosing the right endpoint

POST /matters-csv/upsert (recommended)

POST /matters-csv/upload-csv (deprecated)

Idempotency

Safe to retry. Uploading the same CSV twice produces the same end state.

Not idempotent in the useful sense — rows whose Matter ID already exists on the cycle are silently skipped, so a corrected re-upload is a no-op for those rows.

Uniqueness key

(Matter ID, Client Name, Attorney Email) — one entry per attorney's work on a client matter.

(Matter ID, Matter Name, Attorney Email, Billing Attorney Email, Client Name) — the identifier the endpoint uses to decide whether a row already exists on the target review cycle.

Existing matches

Non-key fields (Matter Name, Billing Attorney Email, Start Date, End Date, Hours Billed, Description) are updated in place. The entry's id is preserved, so review-cycle assignments referencing the entry survive. Rows already matching the stored entry are no-ops and are not counted in updated.

Silently skipped. Corrections to Start Date, End Date, Hours Billed, or Description cannot be applied via re-upload — the existing entry is left untouched.

Shared Client Matter fields

Matter Name and Description refresh on the shared (Matter ID, Client Name) parent, updating every entry beneath it.

Not refreshed.

Duplicates within one file

Rejected. Two rows sharing the upsert key (Matter ID, Client Name, Attorney Email) fail validation regardless of whether other fields agree.

Exact-duplicate rows (every field identical) are silently collapsed to one entry. Rows sharing (Matter ID, Client Name, Attorney Email) but differing on non-key fields are not detected at the CSV validation layer.

Rows disagreeing on Client Matter fields

Rejected. Two rows with the same (Matter ID, Client Name) that disagree on Matter Name or Description fail validation.

Cross-row check is limited to Matter Name across rows sharing a Matter ID (regardless of Client Name) — disagreement rejects the file. Description is never compared across rows; Client Name is not compared across rows.

Response body

JSON: { created, updated }.

Empty on success.

Row-count limit

10,000 rows.

10,000 rows.

Companion pre-flight

POST /matters-csv/validate-upsert

POST /matters-csv/validate-csv


CSV file format

Applies to all four endpoints.

  • Encoding: UTF-8. A leading BOM is tolerated and stripped on read. Line endings may be LF or CRLF.

  • Size limit: file must be less than 10 MB.

  • Row limit: at most 10,000 data rows.

  • Header row: required. Column names must include all nine columns below. Matching is case-insensitive and column order does not matter — columns may appear in any order.

  • Escaping: field values containing a comma, double quote, or newline must be wrapped in double quotes. Embedded double quotes are escaped by doubling. Example — a Description of Smith, Jones & Associates must be written as "Smith, Jones & Associates".

  • Whitespace: leading and trailing whitespace in field values is trimmed. Whitespace-only values are treated as absent — persisted as null for optional columns; rejected as missing for required columns.

  • Blank rows: fully blank rows are ignored.

Columns

Each subsequent row corresponds to one matter entry.

Column
Type
Required
Rule

Matter ID

String

Required

Together with Client Name, identifies the Client Matter this entry rolls up to. Multiple rows may share the same (Matter ID, Client Name) — one row per attorney working the matter. When they do, all Client Matter fields (Matter Name, Description) must be identical across those rows.

Matter Name

String

Required

Max 255 characters. A Client Matter field: must be identical across all rows sharing the same (Matter ID, Client Name).

Attorney Email

String

Required

Must match an existing Perform member's email.

Billing Attorney Email

String

Required

Must match an existing Perform member's email.

Start Date

Date

Optional

When present, format must be M/d/yyyy (e.g. 6/30/2025). Blank persists as null.

End Date

Date

Optional

When present, format must be M/d/yyyy (e.g. 10/5/2025). When both dates are present, End Date must be on or after Start Date. Blank persists as null.

Hours Billed

Integer or Float

Required

Between 0 and 999999.99 inclusive. Values are written in US format: . as the decimal separator, and an optional , as a thousands separator (e.g. 1,234.56). European-style formatting (1.234,56) is not accepted.

Client Name

String

Required

Max 255 characters. Together with Matter ID, identifies the Client Matter this entry rolls up to.

Description

String

Optional

Max 500 characters. This is the matter's description, not a description of the individual attorney's work on that matter. A Client Matter field: must be identical across all rows sharing the same (Matter ID, Client Name).


POST /matters-csv/upsert

Upserts a matter entry for each row in the CSV. Recommended.

Uniqueness of a matter entry within a review cycle is by the combined (Matter ID, Client Name, Attorney Email) key.

Request

  • Content-Type: multipart/form-data

  • Query parameter:

    • reviewCycleId (UUID, required) — the review cycle to associate the upserted entries with.

  • Form parts:

    • file (required) — CSV file, format as above.

Behavior

  • If a matter entry with the composite key already exists in the cycle, its non-key fields are updated in place when they differ from the row. Rows whose non-key fields already match the stored entry are no-ops and are not counted in updated. The entry's id is preserved, so any existing review-cycle assignments referencing the entry survive.

  • If no such entry exists, a new one is created.

  • Multiple entries under the same (Matter ID, Client Name) (different Attorney Emails) are supported — they share a single Client Matter parent whose canonical Matter Name and Description refresh on each upsert.

Idempotency

  • Uploading the same CSV twice produces the same end state — safe to retry.

  • Non-key fields updated on the entry: Matter Name, Billing Attorney Email, Start Date, End Date, Hours Billed, Description.

  • Matter Name and Description also refresh on the shared Client Matter parent; all existing entries under the same (Matter ID, Client Name) see the update.

Validation

All rows must pass validation or the entire upload is rejected — no partial commits.

Rows are rejected when:

  • Any field-level validation fails (see per-column rules above).

  • Two rows share the same upsert key (Matter ID, Client Name, Attorney Email). Each matter entry must appear in the CSV at most once.

  • Two rows share the same Client Matter (Matter ID, Client Name) but disagree on any Client Matter field (Matter Name or Description).

For pre-flight validation without committing, use POST /matters-csv/validate-upsert — it returns per-row, per-column error details.

Responses

200 OK — matter entries upserted successfully.

Field
Type
Description

created

integer

Number of matter entries newly created by this upsert.

updated

integer

Number of existing matter entries whose stored fields were changed by this upsert. Rows whose non-key fields already matched the stored entry are not counted.

400 Bad Request — the CSV was rejected. Causes:

  • Unsupported file format or empty file.

  • File exceeds 10 MB or contains more than 10,000 rows.

  • One or more rows failed field-level validation.

  • Two rows share the same upsert key (Matter ID, Client Name, Attorney Email).

  • Two rows share the same Client Matter (Matter ID, Client Name) but disagree on any Client Matter field (Matter Name, Description).

Use POST /matters-csv/validate-upsert to identify the specific rows and columns with errors.

401 Unauthorized / 403 Forbidden — see Authentication.

404 Not Found — no review cycle exists for the given reviewCycleId.


POST /matters-csv/validate-upsert

Pre-flight-validates a matter entry CSV for the upsert endpoint. Does not persist any data.

Validates against the same rules /upsert enforces — including in-file uniqueness on (Matter ID, Client Name, Attorney Email) and Client Matter field agreement across rows sharing the same (Matter ID, Client Name).

Request

  • Content-Type: multipart/form-data

  • Form parts:

    • file (required) — CSV file, format as above.

No reviewCycleId is required — validation is against the file's structure and rules, not against an existing cycle's contents.

Responses

200 OK — validation performed. Response includes row numbers and column names that contain errors.

Field
Type
Description

numValidRows

integer

Number of rows in the CSV that pass all validation checks.

rowErrors

array

Details for each row containing errors. Empty if no rows have errors.

rowErrors[].rowNumber

integer

The row number containing the error.

rowErrors[].columnsWithErrors

array of string

Column names that failed validation. Email columns fail validation if the address is not associated with an existing Perform member account.

fileError

object, nullable

Present when a file-level constraint is violated. See File error object. null when there is no file-level error.

401 Unauthorized / 403 Forbidden — see Authentication.


File error object

When either validation endpoint (/validate-upsert or /validate-csv) reports a file-level error, the fileError field carries this shape:

Field
Type
Description

type

string (enum)

One of the four error types below.

rowNumber

integer, nullable

Populated only for COLUMN_COUNT_MISMATCH — the 1-based number of the offending data row. null for the other types.

description

string

Human-readable description of the error.

Error types:

  • INVALID_FILE_SIZE — the file is empty, or larger than 10 MB.

  • INVALID_ROW_COUNT — the file has zero data rows, or more than 10,000.

  • COLUMN_COUNT_MISMATCH — a data row has a different field count than the header row (typically caused by an unescaped comma inside a field value). rowNumber identifies the offending row.

  • UNREADABLE_FILE — the file could not be parsed for another reason (malformed CSV, I/O read failure).

Example:


Deprecated endpoints

These endpoints are retained for backwards compatibility. Migrate callers to /upsert and /validate-upsert.

POST /matters-csv/upload-csv (deprecated)

Use POST /matters-csv/upsert instead for idempotent behavior. This endpoint silently skips rows that already exist in the review cycle.

Creates a new matter entry for each row in the CSV. Rejects all rows if any single row contains errors.

  • Content-Type: multipart/form-data

  • Query parameter: reviewCycleId (UUID, required) — the review cycle to associate the uploaded matters with.

  • Form parts: file (required) — CSV file, format as above.

Behavioral differences from /upsert:

  • Rows whose full identifier (Matter ID, Matter Name, Attorney Email, Billing Attorney Email, Client Name) matches an existing matter entry on the target review cycle are silently skipped. Rows differing on any of those five fields are treated as new entries.

  • Exact-duplicate rows within one file are silently collapsed to one entry. Rows sharing (Matter ID, Client Name, Attorney Email) but differing on non-key fields are not detected at the CSV validation layer.

  • Cross-row check across the file is limited to Matter Name — rows sharing a Matter ID must agree on Matter Name or the file is rejected. Description and Client Name are not compared across rows.

  • No response body on success.

Responses:

  • 200 OK — matters created successfully (no response body).

  • 400 Bad Request — invalid CSV file format, or CSV file contains one or more invalid rows. Use /validate-csv for row-level error details.

  • 401 Unauthorized / 403 Forbidden — see Authentication.

  • 404 Not Found — review cycle not found.

POST /matters-csv/validate-csv (deprecated)

Use POST /matters-csv/validate-upsert instead.

Pre-flight-validates a matter entry CSV for the /upload-csv endpoint. Does not persist any data.

Validation rules differ from /validate-upsert: this endpoint validates against the /upload-csv rules only — it does not flag duplicate upsert keys within a file, and it does not check that rows sharing (Matter ID, Client Name) agree on Description. Do not use this endpoint as a pre-flight for /upsert.

Request — same shape as /validate-upsert.

Responses — same shape as /validate-upsert.


Authentication

All four endpoints require an authenticated Perform administrator. Unauthenticated requests return 401 Unauthorized; authenticated non-admin callers return 403 Forbidden.

Last updated