Members Bulk Remove
Overview
Reference for the bulk member-removal endpoint, which removes product access for many members in one call, identifying each member by email address. Use alongside the OpenAPI spec for full request/response schemas.
This is the counterpart to POST /members/import-csv (see Members CSV Import). The import endpoint creates and updates members from a CSV; this endpoint removes product access for members that already exist. It takes a JSON body — there is no CSV form of bulk removal.
Endpoint
DELETE
/members/bulk-remove
Removes access to one product for the listed members. Only members whose role for that product is MEMBER are affected; admins are skipped. Returns the number of members removed.
Content type is application/json. Unlike import-csv, the product is supplied in the request body rather than as a query parameter.
Authorization
product=PERFORMrequires the Perform Admin role.All other products require the Admin role.
Authentication is the same scheme as
/members/import-csv.
The endpoint is additionally gated by the user-list-members-bulk-delete feature flag. If the flag is not enabled for your org, every call returns 403 Forbidden and nothing is removed. Contact your Flo representative to have it enabled.
Request body
{
"product": "RECRUIT",
"emails": ["jane.smith@firm.com", "john.doe@firm.com"]
}product
enum
Yes
One of RECRUIT, PERFORM, UNI. The product whose access is being removed. Determines the required admin role (see Authorization).
emails
array of string
No
Email addresses of the members to remove. Omitting the field, or sending null or [], is a valid no-op that returns removedCount: 0.
There is no enforced cap on the number of emails, but the whole call runs in a single transaction — prefer batches of a few thousand over one very large request.
Matching semantics
Members are matched by email address, against the email stored on the member record in your org. Members in other orgs are never visible to the call.
The API performs no normalization on the values you send — no trimming, no case folding, no alias resolution. Send the exact email on file.
An email that matches nothing is silently ignored. It is not an error, and it does not appear in the response; it simply doesn't contribute to
removedCount. A call whose every email is unknown returns200 OKwithremovedCount: 0.Duplicate emails in the array are harmless — each member is removed at most once, so
removedCountcounts members, not array entries.
What gets removed — and what doesn't
The operation removes the member's access to the one product named in the request. It is not a member deletion.
The member's access to the requested product
Removed
The member's access to other products
Preserved
The member's user record, profile, and employment data
Preserved
Members whose role for the requested product is ADMIN
Skipped — not removed, not reported as an error
Because admins are skipped rather than rejected, including an admin's email in the list is safe: the rest of the batch still processes, and the admin's access is left intact. The only signal is that removedCount comes back lower than the number of emails you sent.
To remove an admin, first demote them to MEMBER (via import-csv with a Role column of MEMBER, or the editEmployerMemberAuthInfo GraphQL mutation), then re-run the bulk remove. To delete a member entirely rather than revoking one product, use the deleteMember GraphQL mutation — see Alternative: GraphQL mutations below.
Success response
200 OK with:
removedCount is the number of members whose product access was actually removed. It excludes skipped admins and unmatched emails, so it is the authoritative signal of what happened — always compare it against the number of emails you sent.
Error responses
401 Unauthorized
The request is unauthenticated.
403 Forbidden
Either the caller lacks the required role for the product (Perform Admin for PERFORM, Admin otherwise), or the user-list-members-bulk-delete feature flag is not enabled for your org.
There is no row-level error response and no partial-failure body — unlike import-csv, this endpoint has nothing to validate per email. An email is either matched (and removed) or ignored. The call is a single transaction: on any failure nothing is removed.
Recommended workflow
Determine the product you are removing access from, and confirm you hold the matching admin role.
Collect the exact email addresses on file for the members to remove.
DELETE /members/bulk-removewith the JSON body.On
200 OK, compareremovedCountagainst the number of emails sent. A shortfall means one or more emails didn't match a member, or matched an admin.If you need to know which emails didn't take effect, re-query the members (
membersByProduct/memberByIdin GraphQL) and diff against your list — the response body deliberately carries only a count.
Recipes
Recipe 1: offboard a batch of members from Recruit
Each of the three loses Recruit access. Any of them that is a Recruit admin, or whose email doesn't match a member, is skipped.
Recipe 2: remove Perform access but keep Recruit access
Requires the Perform Admin role. Jane keeps her Recruit access and her member record; only the Perform role is removed.
Recipe 3: dry-run-ish validation before a large batch
There is no dry-run mode. Send a single-email request for one representative member first, confirm removedCount is 1, then submit the rest of the batch.
Things to be careful about
This is not a member deletion. The member stays in your org with their other product access intact.
Admins are skipped silently. No error, no per-email report. Watch
removedCount.Email matching is exact and unnormalized. A trailing space, a different alias, or a stale address on your side produces a silent no-op for that entry.
Some HTTP clients drop request bodies on
DELETE. A few libraries and proxies strip the body from aDELETE, which arrives as an empty email list and returnsremovedCount: 0. If you get an unexplained zero, confirm your client actually sent the body.There is no dry-run mode and no undo. Re-granting access requires
import-csv, and re-granting fires a welcome email.The feature flag gate is per-org. A call that works for one org can return
403for another if the flag isn't enabled there.
Example: curl
Response:
Common pitfalls
removedCount is lower than the number of emails sent
One or more emails matched no member in your org, or matched a member whose role for that product is ADMIN (admins are skipped).
removedCount: 0 on a request you know is correct
The request body didn't arrive — some HTTP clients strip bodies from DELETE. Verify with a raw curl.
403 Forbidden on every call
The user-list-members-bulk-delete flag isn't enabled for your org, or you're targeting PERFORM without the Perform Admin role.
An admin wasn't removed
By design. Demote to MEMBER first, then re-run.
The member still appears in your other integrations
Only the one product's access was removed. The member record itself still exists — use deleteMember for a full deletion.
Member removed from the wrong product
product is in the request body, not a query parameter as it is on import-csv. Double-check the body field.
Last updated

