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
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=PERFORMrequires the Perform Admin role.All other products require the Admin role.
File-level requirements
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, andEmailmust all appear in the header row. Missing any of them rejects the file withMISSING_REQUIRED_COLUMNS.Unknown headers reject the file. Any header not in the recognized column list below causes a
UNEXPECTED_COLUMNSfile 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.
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
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:
—
—
—
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.
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.
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-areasfeature is enabled for your org.GET /practiceAreas/currentreturns the actual merged set that will pass validation, so always prefer it over the static-only endpoint for pre-import checks./officesStaticListexists 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 fromGET /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:
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 rows →
DUPLICATE_VALUEon every involved row (case-insensitive comparison).Duplicate Employee ID across rows →
DUPLICATE_VALUEon every involved row (case-insensitive comparison). Empty Employee IDs are not considered duplicates of each other.
Recommended workflow
There is no separate "validate" endpoint — /members/import-csv performs validation and the write in a single transaction:
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}.POST /members/import-csv?product={Product}with the file.On
200 OK, parsecreatedCountandupdatedCountfrom the response body.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:
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
Build a CSV containing only the columns whose values you want to change, plus the three always-required columns (
First Name,Last Name,Email).Include one row per member to update. Match by Email (case-insensitive).
POST /members/import-csv?product={Product}with the file.On
200 OK,updatedCountreflects how many existing members were touched. Any row whose email did not match an existing member is created instead and counts towardcreatedCount.
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 SchoolandGraduation Yearon 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, andEffective Class Yearare dropped at parse time when the request specifies a non-Performance product. To update those, targetproduct=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
memberByIdquery (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:
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)
fileError.type)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)
rowColumnErrors[].type)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
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

