Bulk upsert member locations

## Organization Locations - Bulk Upsert

Organization Locations - Bulk Upsert

Add or update sites on many members' accounts in one call.

Each location is applied to each target member, matched on external_id. A new external_id creates the location, an existing one updates it. Sending the same payload again returns unchanged for every row and writes nothing.

Location fields

FieldRequiredNotes
external_idyesYour id for the site, up to 100 characters. Unique per user.
nameyesUp to 50 characters
latitude, longitudeyesDecimal degrees, stored to 5 decimal places
warning_radiusnoMiles, 3 to 12. Default 8 on create
all_clear_countdownnoMinutes, 0 to 60
notificationsnoLightning notifications. Default true on create
hazardsnoPer-hazard switches, e.g. {"wbgt": {"enabled": false}}. Hazards left out use the defaults, the same as a location the user created

On an update, optional fields you leave out are not changed. The locations are ordinary locations on each user's account: they appear in the user's dashboard and app and alert the same way. A user's default location is never changed.

Targets

targets picks the members to apply the change to. Give exactly one of:

KeyMatches
all_memberstrue: every member
user_idsFLASH user ids
emailsMember email addresses, case-insensitive
member_external_idsYour own member ids, set with PATCH /organization/members/{user_id}/
tagsMembers carrying any of the tags, set with PATCH /organization/members/tags/

Members whose invitation is still pending are included. Their locations are saved but do not alert until they accept, and each of their rows carries the warning user_inactive.

Limits

  • Up to 1000 result rows per request (targets x locations). Split larger jobs into several requests. A larger request is refused with BATCH_TOO_LARGE and nothing is written.
  • Up to 30 locations per user. A create past that is an error row with code location_limit.

Results

The response has one row per target and location, in request order, plus a summary count. Each member's changes are written together. Rows for one member never depend on another member's.

statusMeaning
createdNew location on the user's account
updatedExisting location changed
unchangedAlready matched, nothing written
deletedLocation removed
skipped_user_deletedThe member deleted this location, so it was not recreated
errorNothing written for this row. See code

Error codes: not_a_member, location_limit, not_found, conflict (another request was changing the same user's locations; retry).

Warnings do not stop the write:

  • org_policy_applied:warning_radius / org_policy_applied:all_clear_countdown: the organization's setting replaced the value you sent.
  • notifications_outside_coverage: the site is outside the lightning coverage area, so its notifications are off.
  • user_inactive: the member has not accepted their invitation yet.
  • kept_user_edit:<field>: the member changed this field themselves, so their value was kept.
  • restored_user_deleted: a location the member deleted was recreated because you sent restore_user_deleted.

Member edits and deletions

Members can rename, move, switch notifications on or off, and delete locations you manage. Later calls respect that:

  • A location the member deleted is not recreated. Its row comes back as skipped_user_deleted. Send restore_user_deleted: true to recreate it anyway.
  • Fields the member changed (name, latitude, longitude, notifications) keep the member's value, with a kept_user_edit:<field> warning. Send override_user_edits: true to write yours and reset that tracking.

dry_run: true returns exactly what the call would do and writes nothing.

A request that fails validation is refused as a whole with INVALID_REQUEST, and details lists every problem by field and index. Nothing is written.

Authentication

Two ways to call these endpoints, both scoped to one organization:

  • Secret API key in the X-Flash-Api-Key header (fw_sk_...). The organization needs the Organization Locations API product. The key always acts on its own organization. Publishable keys are refused. This is the mode for software partners integrating on an organization's behalf.
  • User token (Authorization: Token ...) of an administrator of the organization.

Either way, only members of that organization can be read or changed. A user outside it is reported as not_a_member, whether or not the account exists.

POST
/organization/locations/bulk-upsert/

Authorization

X-Flash-Api-Key<token>

FLASH API key. Secret keys are prefixed fw_sk_live_ and must stay server-side; publishable keys are prefixed fw_pk_live_ and are safe in a public page. Obtain via POST /api-keys/.

In: header

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Rejects keys it does not declare, so a misspelt field fails instead of being ignored.

Response Body

application/json

application/json

application/json

application/json

application/json

curl -X POST "https://example.com/organization/locations/bulk-upsert/" \  -H "Content-Type: application/json" \  -d '{    "targets": {      "tags": [        "crew-north"      ]    },    "locations": [      {        "external_id": "crowther-site-118",        "name": "Lakeview Dr re-roof",        "latitude": 41.88,        "longitude": -87.63,        "warning_radius": 8,        "all_clear_countdown": 30,        "notifications": true      },      {        "external_id": "crowther-site-121",        "name": "Oak St warehouse",        "latitude": 41.9,        "longitude": -87.68,        "hazards": {          "wbgt": {            "enabled": false          }        }      }    ],    "dry_run": false  }'
{  "organization_id": 42,  "dry_run": false,  "summary": {    "created": 2,    "updated": 0,    "unchanged": 1,    "deleted": 0,    "skipped_user_deleted": 0,    "error": 1  },  "results": [    {      "user_id": 311,      "email": "dana@crowther.example",      "member_external_id": "crowther-emp-17",      "external_id": "crowther-site-118",      "location_id": 90211,      "status": "created",      "warnings": [        "org_policy_applied:warning_radius"      ]    },    {      "user_id": 311,      "email": "dana@crowther.example",      "member_external_id": "crowther-emp-17",      "external_id": "crowther-site-121",      "location_id": 90212,      "status": "created"    },    {      "user_id": 318,      "email": "lee@crowther.example",      "member_external_id": null,      "external_id": "crowther-site-118",      "location_id": 88001,      "status": "unchanged"    },    {      "user_id": 318,      "email": "lee@crowther.example",      "member_external_id": null,      "external_id": "crowther-site-121",      "location_id": null,      "status": "error",      "code": "location_limit",      "detail": "This user already has the maximum of 30 locations."    }  ]}

On this page