Overview

Members CSV Import

Reference for the file format accepted by the members CSV import endpoint. Use alongside the OpenAPI spec for full request/response schemas.

Endpoints

Method
Path
Purpose

POST

/members/import-csv?product={Product}

Creates new members and updates existing members from CSV input. Returns created and updated counts, or a detailed error response. Rejects all rows if any single row contains errors.

GET

/members/example-csv/{product}/{numRows}

Downloads a sample CSV pre-populated with example rows appropriate for your org's office locations, SSO configuration, etc. numRows is capped at 1000.

GET

/members/import-instructions.pdf?product={Product}

Downloads a PDF reference guide for the import flow. Recruit/University variants omit the Performance-only columns.

Both write paths accept multipart/form-data with the CSV provided in the file part. The product query parameter is required on import-csv and is one of the values defined by the Product enum in the OpenAPI spec (e.g. PERFORM, RECRUIT).

Authorization

  • product=PERFORM requires the Perform Admin role.

  • All other products require the Admin role.

File-level requirements

Requirement
Value

Encoding

UTF-8. An optional UTF-8 BOM is accepted and stripped.

Max file size

10 MB

Line endings

LF or CRLF

Header row

Required. Must include at least the three required columns (see Header rules).

There is no enforced row-count cap, but very large files should respect the 10 MB size limit.

Header rules

Column matching is case-insensitive and order-independent. Header rules are otherwise strict:

  • Required headers must be present. First Name, Last Name, and Email must all appear in the header row. Missing any of them rejects the file with MISSING_REQUIRED_COLUMNS.

  • Unknown headers reject the file. Any header not in the recognized column list below causes a UNEXPECTED_COLUMNS file error. There is no "ignore extras" behavior — clean up your column names before submitting.

  • Which columns you include matters for updates. When updating an existing member, the presence of a column header (independent of whether its cells are blank) controls whether that field is preserved or cleared. See Update semantics below.

Recognized headers (case-insensitive)

Column reference

All required-ness, type, and constraint information below reflects the actual validator behavior. A few items here differ from the inline description text on the endpoint — when in doubt, this document is the source of truth.

Column
Type
Required
Constraints
Example

First Name

string

Yes

Must not contain: backslash, carriage return, newline, <, >, `

,"`, or backtick.

Last Name

string

Yes

Same disallowed-character set as First Name.

Smith

Email

string

Yes

Must be a well-formed email address. Must be unique across rows in the file (case-insensitive).

Employee ID

string

No

Treated as an opaque identifier. When provided, must be unique across rows in the file (case-insensitive). Not required to be numeric.

E-1042

Job Title

string

No

None.

Senior Associate

Level

string

No

Must match one of the org's configured member levels (case-insensitive). Only persisted when product=PERFORM — silently ignored for other products.

Senior

Office City

string

No

When provided, exactly one of Office State or Office Country must also be provided. See Office location rules.

New York

Office State (US Only)

string

No

Mutually exclusive with Office Country. Must accompany Office City when set.

NY

Office Country (Non-US Only)

string

No

Mutually exclusive with Office State. Must accompany Office City when set.

United Kingdom

Department

string

No

When provided, must match one of the org's configured departments.

Litigation

Practice Area

string

No

When provided, must match one of the org's configured practice areas.

Corporate

Law School

string

No

When provided, must match one of the configured law schools. See Education writes for upsert behavior.

Columbia Law School

Graduation Year

string

No

4 digits, must be within 200 years of the current year. Only persisted when product=PERFORM — silently ignored for other products.

2018

Effective Class Year

string

No

4 digits, must be within 200 years of the current year. Only persisted when product=PERFORM — silently ignored for other products.

2020

Start Date

date

No

Format yyyy-MM-dd.

2025-06-03

Role

enum

No

ADMIN or MEMBER (case-sensitive). If omitted or blank on a new member, defaults to MEMBER.

MEMBER

SSO ID

string

No

Required only when your org uses SSO and is not configured to use email-as-SSO-ID. Otherwise ignored. Specific validation depends on your org's SSO configuration.

jsmith

Use MFA

boolean string

No

One of (case-insensitive): true, t, 1, yes, y, false, f, 0, no, n.

true

Bio Link

URL

No

Must be a syntactically valid URL.

https://firm.com/bio/jsmith

Work Arrangement

enum

No

One of: REMOTE, HYBRID, IN_PERSON (case-sensitive).

HYBRID

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

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

Office location rules

The Office City / Office State / Office Country triad is validated as a unit. All four combinations below are valid:

City
State
Country
Valid?

Yes (no office set)

set

set

Yes (US office)

set

set

Yes (non-US office)

Yes

Anything else produces a ROW_VALUE_CONFLICT or EMPTY_REQUIRED_VALUE row error:

  • Office State or Office Country without Office City → conflict on the offending field.

  • Office State and Office Country both set on the same row → conflict on both.

  • Office City set with neither State nor Country → missing-required on both State and Country.

Beyond format-correctness, the resulting office location must match one of your org's configured offices (INVALID_LIST_SELECTION if it does not).

Discovering valid list values

Five CSV columns require values that match data configured for your org or maintained globally by Flo. Use the endpoints below to retrieve the current set of valid values before building your CSV. All listing endpoints require admin authentication using the same scheme as /members/import-csv.

CSV column
Endpoint
Scope
Returns

Law School

GET /lawSchoolsList.csv

Global (same for every org)

CSV with the full list of recognized law schools. A snapshot is also embedded below at Law School values for convenience.

Practice Area

GET /practiceAreas/current (JSON), or GET /practiceAreasList.csv (CSV)

Global static list + your org's custom list, combined

Every practice area that will pass validation for your org, each tagged with its type (static vs. org-custom). The static list alone is available at GET /practiceAreasStaticList; your org's custom list alone is available at GET /firmPracticeAreasList.

Department

GET /departments (JSON), or GET /departments.csv (CSV)

Per-org

Your org's configured departments.

Office City / State / Country

GET /offices (JSON)

Per-org

Your org's configured office locations, each as a (city, state, country) triple. Populate the three Office columns in your CSV to exactly match one of these triples — see Office location rules for the format rules within a row.

Level

GET /org-member-levels (JSON)

Per-org (Performance only)

Your org's configured member levels. Returned only when your org has the member-levels feature enabled. Non-Performance imports ignore the Level column regardless of the list.

Law School values

Snapshot as of 2026-06-02. ~330 recognized law schools, sorted case-insensitively. Re-fetch from GET /lawSchoolsList.csv for the latest set — additions happen periodically.

Click to expand: full law school list (sorted case-insensitively)

Notes on using these endpoints

  • Lists can change between imports. New law schools are added over time; orgs can add/edit their own departments, offices, practice areas, and member levels. Re-fetch before each large import rather than caching long-term.

  • Comparison in member-import validation is case-insensitive but otherwise exact. Trailing whitespace in your CSV is trimmed before comparison; internal whitespace is not normalized — "New York" (two spaces) will not match "New York".

  • Practice-area availability is feature-flag gated. Your org's custom practice areas only count as valid values when the custom-practice-areas feature is enabled for your org. GET /practiceAreas/current returns the actual merged set that will pass validation, so always prefer it over the static-only endpoint for pre-import checks.

  • /officesStaticList exists but is not the right list for imports. It returns a global suggestion list used by the office-creation UI; member-import validation runs against your org's configured offices from GET /offices.

Fixed-enum columns (no endpoint needed)

The following columns accept a small fixed set of values that doesn't vary per org. The accepted values are listed directly in the Column reference and reproduced here for convenience:

Column
Accepted values

Role

ADMIN, MEMBER (case-sensitive). Defaults to MEMBER on new rows when blank.

Work Arrangement

REMOTE, HYBRID, IN_PERSON (case-sensitive).

Use MFA

true, t, 1, yes, y, false, f, 0, no, n (case-insensitive).

Cross-row rules

  • Duplicate Email across rowsDUPLICATE_VALUE on every involved row (case-insensitive comparison).

  • Duplicate Employee ID across rowsDUPLICATE_VALUE on every involved row (case-insensitive comparison). Empty Employee IDs are not considered duplicates of each other.

There is no separate "validate" endpoint — /members/import-csv performs validation and the write in a single transaction:

  1. Build the CSV. To get a template tailored to your org (with valid office locations, SSO id format, etc.), GET /members/example-csv/{product}/{numRows}.

  2. POST /members/import-csv?product={Product} with the file.

  3. On 200 OK, parse createdCount and updatedCount from the response body.

  4. On 400 Bad Request, parse the validation result body to determine whether the failure was file-level (fileError) or row-level (rowColumnErrors). Fix and retry; the call is rejected as a whole, so partial state is never written.

Update semantics

Members are matched by Email (case-insensitive). A row with an Email that matches an existing member in your org becomes an update; otherwise it becomes a create.

Updates: present-column rule

For an existing member, the presence of a column header in your file controls whether that field gets touched:

Situation
Effect on the existing field

Column header absent from the CSV

Existing value is preserved.

Column header present, cell non-blank

Existing value is replaced with the cell value.

Column header present, cell blank

Existing value is cleared.

This lets you do precise partial updates: include only the columns you want to change.

Exceptions and special cases:

  • First Name, Last Name, Email are always required regardless. Their headers must be present and their cells non-blank on every row.

  • Office City / Office State / Office Country are treated as a single unit. If none of the three column headers are present, the existing office location is preserved. If any one of them is present, all three are taken from the CSV (with the usual triad rules).

  • Level, Graduation Year, Effective Class Year are removed from the present-columns set on non-Performance imports — even if you include those headers, the columns will be treated as absent for update purposes (preserved, not cleared).

  • SSO ID and Use MFA are also context-merged before SSO/MFA validation runs — if only one of the two columns is present, the absent column's value is taken from the existing user record before validation, so you can update one without restating the other.

Education writes (Law School, Graduation Year)

Education-background writes on existing members are create-only: if the member already has any education record, additional ones are skipped. Use the dedicated profile-edit flows to modify or replace existing education records — the CSV will not overwrite them.

Creates

For new members, every required field on the row must pass validation. The default Role is MEMBER if Role is blank or absent. Welcome-email side effects fire per row.

Updating a member after creation

There is no separate "update" endpoint for individual members on the REST API — updates are performed by submitting CSV rows whose Email matches an existing member. The same POST /members/import-csv endpoint handles both create and update; the Update semantics section above describes the underlying rules. This section shows how to apply them in practice.

The general recipe

  1. Build a CSV containing only the columns whose values you want to change, plus the three always-required columns (First Name, Last Name, Email).

  2. Include one row per member to update. Match by Email (case-insensitive).

  3. POST /members/import-csv?product={Product} with the file.

  4. On 200 OK, updatedCount reflects how many existing members were touched. Any row whose email did not match an existing member is created instead and counts toward createdCount.

Remember: a column header present with a blank cell is interpreted as "clear this field," while an absent header is "preserve this field." This is the single most common source of accidental data loss — be deliberate about which columns you include in your update file.

Recipe 1: change one or two fields, leave the rest untouched

To update only the job title and work arrangement for an existing member, include exactly those columns alongside the required identifiers:

Everything else — office location, department, practice area, law school, employee ID, levels, dates, role, SSO settings — is preserved because those columns are absent from the file.

Recipe 2: clear a field

Include the column header but leave the cell empty. To remove a member's bio link:

The bio link is set to empty on the existing record. All other fields are preserved.

Recipe 3: move a member to a new office

The office triad (Office City, Office State (US Only), Office Country (Non-US Only)) is treated as a unit. Include all three columns to be explicit about the new location:

Omit all three columns to preserve the existing office.

Recipe 4: promote a member to admin (or demote)

Include the Role column. The role applies to the product the upload is targeted at (?product=…).

Recipe 5: grant an existing member access to an additional product

Submit the CSV against the new product. The import treats a row whose email matches an existing member but who doesn't yet have the target product role as a "grant product access" operation. A welcome email is sent for the newly-granted product.

Things to be careful about

  • Match is by email only. If your records use a member's old email and the email on file has changed, you'll create a new member instead of updating the old one. To change a member's email, use the GraphQL path described below — there is no email-change column in the CSV.

  • Education records are create-only. Setting Law School and Graduation Year on a re-upload will not overwrite existing education records. To change a member's law school after creation, contact Flo support.

  • Performance-only columns are silently ignored on non-Performance uploads. Level, Graduation Year, and Effective Class Year are dropped at parse time when the request specifies a non-Performance product. To update those, target product=PERFORM.

  • Welcome emails fire when a member is granted a new product role. If your re-upload is granting access to a previously-untouched product, expect notification side-effects (Recipe 5). If it's purely a field-update on a product the member already has, no email fires.

  • There is no separate dry-run mode. Plan your column selection before submitting — the change is committed when the request succeeds. If your integration is risk-averse, send a single-row file first for a representative member, verify the result with a memberById query (GraphQL) or by visual inspection, then submit the rest in batch.

Alternative: GraphQL mutations for per-member updates

For integrations that need finer-grained control (single-member updates, partial updates without constructing a CSV, post-create email changes, deletions) the v2 GraphQL endpoint at POST /graphql exposes the following mutations:

Mutation
What it does

editEmployerMemberInfo

Update first/last name, employment (office, department, practice area, level, start date, effective class year), employee ID, bio link, law school, graduation year, work arrangement, job title.

editEmployerMemberAuthInfo

Update product roles (ADMIN ↔ MEMBER), SSO/MFA settings.

editUniversityMemberInfo

University-side counterpart: update first/last name.

addEmployerMemberProductUserRole

Grant an existing member access to an additional product (per-member equivalent of CSV Recipe 5).

revokeProductUserAccess

Remove a member's access to a product.

deleteMember

Delete a member entirely.

Each mutation takes a member id (the GraphQL ID returned at creation, or queryable via the memberById / membersByProduct queries). Authentication is the same as the CSV endpoint. Full request/response shapes are in the GraphQL schema at member.schema.graphql; the OpenAPI spec covers the REST surface area only. Reach out to your Flo contact if you need a GraphQL onboarding guide.

Error response shape

/members/import-csv returns 400 Bad Request with this JSON body on any validation failure:

Exactly one of fileError or rowColumnErrors is populated.

File-level errors (fileError.type)

Type
Meaning

EMPTY_FILE

The file is missing or empty.

FILE_SIZE_EXCEEDED

The file is larger than 10 MB.

MISSING_REQUIRED_COLUMNS

The header row is missing First Name, Last Name, or Email.

UNEXPECTED_COLUMNS

The header row contains a column not in the recognized list.

INVALID_FILE_FORMAT

Parse failure (malformed CSV, encoding issue, or an unexpected parser error).

Row-level error types (rowColumnErrors[].type)

Type
Triggers

EMPTY_REQUIRED_VALUE

A required field is missing, or an Office triad rule requires a value that wasn't provided.

INVALID_EMAIL

Email is not a well-formed address.

INVALID_FORMAT

First/Last Name contains a disallowed special character, or Use MFA isn't one of the accepted boolean strings.

INVALID_URL

Bio Link is not a syntactically valid URL.

INVALID_YEAR

Graduation Year / Effective Class Year is not 4 digits or is more than 200 years from the current year.

INVALID_DATE_FORMAT

Start Date isn't in yyyy-MM-dd.

DUPLICATE_VALUE

Email or Employee ID appears on two or more rows. The error message lists the colliding row numbers.

INVALID_LIST_SELECTION

A list field (Office Location, Practice Area, Law School, Department, or Level) doesn't match a known value for your org.

INVALID_SSO_ID

SSO ID failed your org's SSO validation rules.

ROW_VALUE_CONFLICT

The Office triad on this row is internally inconsistent.

EXISTING_USER_CONFLICT

A user conflict reported by the create path (e.g. an existing user in a different org).

UNKNOWN

An unmapped exception from the create/update path. Inspect the message field.

Success response

200 OK with:

createdCount is the number of new members created. updatedCount is the number of existing members updated.

Sample CSV

A minimal valid file using only the required columns:

A fuller file targeting product=PERFORM:

The second row demonstrates a non-US office (Office Country set, Office State empty) and an empty Bio Link.

Example: curl

Download a 5-row template for the Recruit product:

Import:

Common pitfalls

Symptom
Likely cause

UNEXPECTED_COLUMNS rejection

A header was spelled differently, has a typo, or includes a column from a different system. The match is case-insensitive but otherwise exact — Hire Date will be rejected because Start Date is the recognized name.

MISSING_REQUIRED_COLUMNS rejection

One of First Name / Last Name / Email is not in the header row.

Existing member's field unexpectedly cleared to blank

Your CSV included that column header with a blank cell. To preserve a field on an update, omit its column header from the file entirely.

Existing member's field unexpectedly unchanged

The reverse: you set a non-blank value but the column header was missing from your file.

All Office fields flagged

The Office triad rule was violated — see Office location rules.

Level / Graduation Year not persisted

product was not PERFORM. These three fields are silently ignored on non-Performance imports.

Law School / Graduation Year not applied to an existing member

Education writes are create-only — once a member has any education record, the CSV will not overwrite it.

DUPLICATE_VALUE on Employee ID across rows you thought were distinct

Comparison is case-insensitive — E-1042 and e-1042 collide.

INVALID_LIST_SELECTION on a value that exists in your UI

The list match is case-insensitive but otherwise exact and trims surrounding whitespace. Embedded extra whitespace ("New York") will not match "New York".

INVALID_FORMAT on a Name field

First or Last Name contained one of the disallowed characters: \, <, >, `

Members in the file aren't reflected as updates

Match is by Email (case-insensitive). If the CSV email differs from what's on file in any way other than case (extra dots, plus addressing, alias), the row becomes a create attempt, not an update.

Last updated