Skip to main content

Members

Tags: data, members, users

A Member is a person in a workspace — a patient or plan member, or a staff member who works with them. These endpoints manage the member roster, the authenticated member's own profile, per-member custom fields, and bulk import and export.

In the product: Members

Resources​

Request and response models used by the endpoints on this page.

BulkCreateMembersRequest​

FieldTypeRequiredDescription
membersArray<BulkMemberItem>✓
upsertBooleanIf true, update existing members matched by externalId/email/phone (default: False)

Example:

{
"members": [],
"upsert": false
}

BulkCreateMembersResponse​

FieldTypeRequiredDescription
conflictedCountIntegerDefault: 0
errorsArray
importedCountInteger✓
resultsArray<BulkMemberResult>✓
skippedCountIntegerDefault: 0
successBoolean✓
totalCountInteger✓

Example:

{
"importedCount": 0,
"totalCount": 0,
"success": false,
"results": [],
"conflictedCount": 0,
"skippedCount": 0
}

BulkMemberItem​

FieldTypeRequiredDescription
conflictResolution<enum ImportContactConflictPolicyDefault: fail
descriptionString
emailString
externalIdString
labelsArray
localeString
memberRoleIdInteger
nameString
notifyEmailBoolean
notifySmsBoolean
notifyVoiceBoolean
notifyWhatsappBoolean
phoneString
timezoneString

Example:

{
"conflictResolution": "fail"
}

BulkMemberResult​

FieldTypeRequiredDescription
conflictsArray<ImportContactConflict> | null
errorString
externalIdString
indexInteger✓
memberIdInteger
sharedContactsArrayDefault: []
statusLiteral['created', 'updated', 'failed', 'conflict', 'skipped']✓

Example:

{
"index": 0,
"status": "created",
"sharedContacts": []
}

CapabilityRequirementFinding​

FieldTypeRequiredDescription
kindLiteral['feature', 'member_scope']✓
slugString✓
subjectLiteral['org', 'workspace', 'member']✓

Example:

{
"kind": "feature",
"slug": "string",
"subject": "org"
}

CapabilityStatus​

FieldTypeRequiredDescription
enabledBoolean✓
missingArray<CapabilityRequirementFinding>✓
slugString✓

Example:

{
"slug": "string",
"enabled": false,
"missing": []
}

CreateMemberContactPointRequest​

FieldTypeRequiredDescription
capabilitiesArray
contactTypeLiteral['phone', 'email']✓
labelString
observedAtDateTime
priorityIntegerDefault: 0
purposeLiteral['general', 'notification', 'billing']Default: general
sharingLiteral['shared', 'individual', 'unknown']Default: unknown
valueString✓Phone or email value. Normalized server-side; PHI, never logged.

Example:

{
"contactType": "phone",
"value": "string",
"purpose": "general",
"priority": 0,
"sharing": "unknown"
}

CreateMemberFieldRequest​

FieldTypeRequiredDescription
keyString✓
serviceIdInteger
valueString✓

Example:

{
"key": "string",
"value": "string"
}

CreateMemberRelationshipRequest​

FieldTypeRequiredDescription
relatedMemberIdInteger✓
relatedRole<enum RelatedMemberRole✓

Example:

{
"relatedMemberId": 1,
"relatedRole": null
}

CreateWorkspaceMemberRequest​

FieldTypeRequiredDescription
accountEnabledBoolean
accountTosAcceptedBoolean
accountUuidString
dateOfBirthDate
descriptionString
emailString
externalIdString
isOrgOwnerBoolean
labelIdsArray
localeString
memberRoleIdInteger✓
memberTypeString
nameString
notifyEmailBoolean
notifySmsBoolean
notifyVoiceBoolean
notifyWhatsappBoolean
phoneString
releaseContactFromMemberIdInteger
timezoneString
titleString
tosAcceptedBoolean

Example:

{
"memberRoleId": 0
}

CurrentMemberResponse​

FieldTypeRequiredDescription
accountEnabledBoolean
accountTosAcceptedBoolean✓
accountUuidString✓
canLoginBooleanDefault: True
capabilitiesDictionary<String, CapabilityStatus>Per-member surface-capability decisions. slug → {enabled, missing}. Each capability composes workspace feature availability with this member's scope authorization. See lib/capabilities/surfaces.py for CapabilityStatus and CapabilityRequirementFinding definitions.
createdAtDateTime✓
dateOfBirthDate
descriptionString✓
emailString✓
emailVerifiedBooleanDefault: False
experimentGroupMemberExperimentGroupSummary | null
externalIdString✓
homeFolderIdInteger
idInteger✓
isArchivedBooleanDefault: False
isImportProtectedBooleanDefault: False
isOrgOwnerBoolean
isSuperuserBoolean
labelsArray<MemberLabelResponse>Default: []
localeString✓
memberRoleIdInteger✓
memberRoleNameString✓
memberRoleSystemKeyString
memberTypeStringDefault: member
nameString✓
notifyEmailBoolean✓
notifySmsBoolean✓
notifyVoiceBoolean✓
notifyWhatsappBooleanDefault: False
orgRoleString
phoneString✓
phoneTypeString✓
phoneVerifiedBooleanDefault: False
roleString✓
scopesArrayDefault: []
sharedContactPlacementsArray<SharedContactResponse>Default: []
timezoneString
titleString
tosAcceptedBoolean✓
updatedAtDateTime✓
usagePausedBooleanDefault: False
uuidString✓

Example:

{
"id": 0,
"uuid": "string",
"role": "string",
"tosAccepted": false,
"accountTosAccepted": false,
"accountUuid": "string",
"createdAt": "2024-01-01T00:00:00Z",
"updatedAt": "2024-01-01T00:00:00Z",
"name": "string",
"description": "string",
"phone": "string",
"phoneType": "string",
"email": "string",
"externalId": "string",
"emailVerified": false,
"phoneVerified": false,
"notifySms": false,
"notifyEmail": false,
"notifyVoice": false,
"notifyWhatsapp": false,
"locale": "string",
"memberRoleId": 0,
"memberRoleName": "string",
"labels": [],
"memberType": "member",
"isArchived": false,
"isImportProtected": false,
"usagePaused": false,
"canLogin": true,
"sharedContactPlacements": [],
"scopes": []
}

DataRecordResponse​

FieldTypeRequiredDescription
accessModeInteger
createdAtDateTime✓
dataTypeIdInteger✓
externalIdString
fieldValuesDict[str, Any]✓
idInteger✓
memberIdInteger✓
memberNameString
retirementDataRecordRetirementResponse | null
updatedAtDateTime✓
uuidUUID✓

Example:

{
"id": 0,
"uuid": "00000000-0000-0000-0000-000000000000",
"dataTypeId": 0,
"memberId": 0,
"fieldValues": {},
"createdAt": "2024-01-01T00:00:00Z",
"updatedAt": "2024-01-01T00:00:00Z"
}

DataRecordRetirementResponse​

FieldTypeRequiredDescription
reasonLiteral['monday_item_archived', 'monday_item_deleted']✓
sourceLiteral['monday']Default: monday
statusLiteral['retired']Default: retired

Example:

{
"status": "retired",
"reason": "monday_item_archived",
"source": "monday"
}

DeleteMembershipResponse​

FieldTypeRequiredDescription
messageString✓

Example:

{
"message": "string"
}

EnumOption​

FieldTypeRequiredDescription
labelString✓Display label shown to users
valuestrintfloat
visibleWhenStringCEL expression that must return true for this option to be visible. Uses record.data.<field_slug> syntax to reference other field values. CONSTRAINT: Can only reference non-FUNCTION fields.

Example:

{
"label": "string",
"value": "string"
}

ImportContactConflict​

FieldTypeRequiredDescription
accountValueString
fieldLiteral['email', 'phone']✓
rowValueString✓

Example:

{
"field": "email",
"rowValue": "string"
}

MemberContactPointConfirmationResponse​

FieldTypeRequiredDescription
bindingUuidString
confirmedBoolean✓
confirmedAtDateTime
methodString
sourceString

Example:

{
"confirmed": false
}

MemberContactPointListResponse​

FieldTypeRequiredDescription
contactsArray<MemberContactPointResponse>✓
memberIdInteger✓

Example:

{
"memberId": 0,
"contacts": []
}

MemberContactPointResponse​

FieldTypeRequiredDescription
capabilitiesArray✓
confirmationMemberContactPointConfirmationResponse | null
contactTypeString✓
createdAtDateTime✓
isPrimaryBooleanDefault: False
labelString✓
memberIdInteger✓
observedAtDateTime✓
priorityInteger✓
purposeString✓
retiredAtDateTime✓
sharingString✓
sourceRecordIdString✓
sourceSystemString✓
updatedAtDateTime✓
uuidString✓
valueString✓

Example:

{
"uuid": "string",
"memberId": 0,
"contactType": "string",
"value": "string",
"label": "string",
"purpose": "string",
"priority": 0,
"sharing": "string",
"capabilities": [],
"sourceSystem": "string",
"sourceRecordId": "string",
"observedAt": "2024-01-01T00:00:00Z",
"retiredAt": "2024-01-01T00:00:00Z",
"createdAt": "2024-01-01T00:00:00Z",
"updatedAt": "2024-01-01T00:00:00Z",
"isPrimary": false
}

MemberCredentialResponse​

FieldTypeRequiredDescription
appConnectionIdInteger
appConnectionNameString
connectedAtString
createdAtString✓
displayNameString
emailVerifiedBooleanDefault: False
externalEmailString
externalUserIdString
externalUsernameString
iconString
iconUrlString
idInteger✓
isDefaultBooleanDefault: False
lastUsedAtString
providerString
uuidString

Example:

{
"id": 0,
"isDefault": false,
"emailVerified": false,
"createdAt": "string"
}

MemberExperimentGroupSummary​

FieldTypeRequiredDescription
groupIdInteger✓
groupNameString✓
groupSlugString✓
isControlBoolean✓

Example:

{
"groupId": 0,
"groupName": "string",
"groupSlug": "string",
"isControl": false
}

MemberFieldDefinitionListResponse​

FieldTypeRequiredDescription
itemsArray<MemberFieldDefinitionResponse>✓

Example:

{
"items": []
}

MemberFieldDefinitionResponse​

FieldTypeRequiredDescription
keyString✓
labelString✓
serviceNameString
serviceTypeString

Example:

{
"key": "string",
"label": "string"
}

MemberFieldListResponse​

FieldTypeRequiredDescription
itemsArray<MemberFieldResponse>✓
totalInteger✓

Example:

{
"items": [],
"total": 0
}

MemberFieldResponse​

FieldTypeRequiredDescription
createdAtDateTime✓
idInteger✓
keyString✓
memberIdInteger✓
serviceIdInteger✓
serviceNameString✓
serviceTypeString✓
updatedAtDateTime✓
valueString✓

Example:

{
"id": 0,
"memberId": 0,
"key": "string",
"value": "string",
"serviceId": 0,
"serviceName": "string",
"serviceType": "string",
"createdAt": "2024-01-01T00:00:00Z",
"updatedAt": "2024-01-01T00:00:00Z"
}

MemberImportConflictInfo​

FieldTypeRequiredDescription
codeString✓
conflictsArray<ImportContactConflict>Default: []
fieldsArray✓

Example:

{
"code": "string",
"fields": [],
"conflicts": []
}

MemberImportIdentitySnapshot​

FieldTypeRequiredDescription
dateOfBirthDate
nameString

Example:

{}

MemberImportMatchInfo​

FieldTypeRequiredDescription
matchTypeLiteral['id', 'uuid', 'external_id', 'email', 'phone', 'account_email', 'account_phone']✓
memberIdInteger✓
memberUuidString✓

Example:

{
"matchType": "id",
"memberId": 0,
"memberUuid": "string"
}

MemberImportPreviewResponse​

FieldTypeRequiredDescription
previewPlanTokenString
rowsArray<MemberImportPreviewRow>✓
summaryMemberImportPreviewSummary✓
totalRowsInteger✓

Example:

{
"totalRows": 0,
"summary": null,
"rows": []
}

MemberImportPreviewRow​

FieldTypeRequiredDescription
actionLiteral['create', 'update', 'unchanged', 'conflict', 'skipped', 'error']✓
conflictMemberImportConflictInfo | null
errorString
existingIdentityMemberImportIdentitySnapshot | null
incomingIdentityMemberImportIdentitySnapshot | null
matchMemberImportMatchInfo | null
proposedChangesArrayDefault: []
rowInteger✓
sharedContactsArrayDefault: []

Example:

{
"row": 0,
"action": "create",
"proposedChanges": [],
"sharedContacts": []
}

MemberImportPreviewSummary​

FieldTypeRequiredDescription
conflictIntegerDefault: 0
createIntegerDefault: 0
errorIntegerDefault: 0
skippedIntegerDefault: 0
unchangedIntegerDefault: 0
updateIntegerDefault: 0

Example:

{
"create": 0,
"update": 0,
"unchanged": 0,
"conflict": 0,
"skipped": 0,
"error": 0
}

MemberImportRunItemResponse​

FieldTypeRequiredDescription
detailString
indexInteger✓
memberIdInteger
reasonCodeStringDefault: ``
statusString✓

Example:

{
"index": 0,
"status": "string",
"reasonCode": ""
}

MemberImportRunSummaryResponse​

FieldTypeRequiredDescription
completedAtString
conflictedIntegerDefault: 0
createdIntegerDefault: 0
failedIntegerDefault: 0
runIdString✓
skippedIntegerDefault: 0
startedAtString✓
statusString✓
terminalErrorString
totalIntegerDefault: 0
unchangedIntegerDefault: 0
updatedIntegerDefault: 0

Example:

{
"runId": "string",
"startedAt": "string",
"status": "string",
"total": 0,
"created": 0,
"updated": 0,
"unchanged": 0,
"conflicted": 0,
"skipped": 0,
"failed": 0
}

MemberLabelResponse​

FieldTypeRequiredDescription
colorString✓
descriptionString✓
idInteger✓
nameString✓
slugString✓
uuidString✓

Example:

{
"id": 0,
"uuid": "string",
"name": "string",
"slug": "string",
"color": "string",
"description": "string"
}

MemberRelationshipResponse​

FieldTypeRequiredDescription
createdAtDateTime✓
grantsChartAccessBoolean✓
idInteger✓
kind<enum MemberRelationshipKind✓
relatedMemberIdInteger✓
relatedMemberNameString
relatedRole<enum RelatedMemberRole✓
updatedAtDateTime✓
uuidString✓

Example:

{
"id": 0,
"uuid": "string",
"kind": null,
"relatedRole": null,
"relatedMemberId": 0,
"grantsChartAccess": false,
"createdAt": "2024-01-01T00:00:00Z",
"updatedAt": "2024-01-01T00:00:00Z"
}

MyDataTypeResponse​

FieldTypeRequiredDescription
canCreateBoolean✓
descriptionString
fieldsArray<SchemaField>✓
idInteger✓
isCollectionBoolean✓
nameString✓
slugString✓
uuidString✓

Example:

{
"id": 0,
"uuid": "string",
"slug": "string",
"name": "string",
"isCollection": false,
"fields": [],
"canCreate": false
}

PaginatedResponse[DataRecordResponse]​

FieldTypeRequiredDescription
itemsArray<DataRecordResponse>✓
pageInteger✓
pageSizeInteger✓
totalInteger✓
totalPagesInteger✓

Example:

{
"items": [],
"total": 0,
"page": 0,
"pageSize": 0,
"totalPages": 0
}

PaginatedResponse[MemberRelationshipResponse]​

FieldTypeRequiredDescription
itemsArray<MemberRelationshipResponse>✓
pageInteger✓
pageSizeInteger✓
totalInteger✓
totalPagesInteger✓

Example:

{
"items": [],
"total": 0,
"page": 0,
"pageSize": 0,
"totalPages": 0
}

PaginatedResponse[WorkspaceMemberResponse]​

FieldTypeRequiredDescription
itemsArray<WorkspaceMemberResponse>✓
pageInteger✓
pageSizeInteger✓
totalInteger✓
totalPagesInteger✓

Example:

{
"items": [],
"total": 0,
"page": 0,
"pageSize": 0,
"totalPages": 0
}

SchemaField​

FieldTypeRequiredDescription
adminOnlyBooleanWhether only admins can see this field (default: False)
categoriesArray<Literal[personal, identifier, phi, credential]>Regulatory or content categories for audit and policy
celFunctionStringCEL expression that computes this field's value. CONSTRAINT: Can ONLY reference regular fields (TEXT, NUMBER, DATE, etc.). Cannot reference other FUNCTION fields. Syntax: record.data.<field_slug>. Example: 'record.data.score1 + record.data.score2'
defaultValueAnyDefault value for the field
descriptionStringOptional field description
fieldTypeUnion[Literal[enum, email, phone, text, longtext, number, integer, boolean, date, datetime, time_of_day, us_zip_code, array, object, function], DataFieldType]Field type (text, email, enum, etc.) (default: text)
nameString✓Display name for the field
optionsArray<EnumOption> | nullOptions for enum fields as {label, value} objects.
orderIntegerDeprecated: Display order is now determined by array position
referencedTypeIdIntegerReferenced DataType ID for reference fields
requiredBooleanWhether field is required (default: False)
sensitivityLiteral['public', 'internal', 'confidential', 'restricted', 'secret']Disclosure severity for audit and access policy (default: confidential)
shouldIndexBooleanWhether to index this field (default: False)
slugString✓Unique field identifier
validateCelStringCEL expression for cross-field validation. Context: 'value' (current field value), 'record.data.<field_slug>' (other fields). Returns: true if valid, or a string error message if invalid. CONSTRAINT: Can only reference non-FUNCTION fields. Example: 'value >= record.data.min_score' or 'value < record.data.min_score ? "Must be >= " + string(record.data.min_score) : true'

Example:

{
"slug": "string",
"name": "string",
"fieldType": "text",
"required": false,
"shouldIndex": false,
"adminOnly": false,
"sensitivity": "confidential"
}

SendPhoneConfirmationResponse​

FieldTypeRequiredDescription
statusString✓

Example:

{
"status": "string"
}

SharedContactResponse​

FieldTypeRequiredDescription
archivedBooleanDefault: False
fieldLiteral['phone', 'email']✓
memberIdInteger✓

Example:

{
"field": "phone",
"memberId": 0,
"archived": false
}

UpdateMemberContactPointRequest​

FieldTypeRequiredDescription
capabilitiesArray
contactTypeLiteral[phone, email]
labelString
observedAtDateTime
priorityInteger
purposeLiteral[general, notification, billing]
sharingLiteral[shared, individual, unknown]
valueString

Example:

{}

UpdateMemberFieldRequest​

FieldTypeRequiredDescription
valueString✓

Example:

{
"value": "string"
}

UpdateWorkspaceMemberRequest​

FieldTypeRequiredDescription
accountEnabledBoolean
accountTosAcceptedBoolean
dateOfBirthDate
descriptionString
emailString
externalIdString
isImportProtectedBoolean
isOrgOwnerBoolean
isSuperuserBoolean
labelIdsArray
localeString
memberRoleIdInteger
nameString
notifyEmailBoolean
notifySmsBoolean
notifyVoiceBoolean
notifyWhatsappBoolean
phoneString
releaseContactFromMemberIdInteger
timezoneString
titleString
tosAcceptedBoolean
usagePausedBoolean

Example:

{}

WorkspaceMemberResponse​

FieldTypeRequiredDescription
accountEnabledBoolean
accountTosAcceptedBoolean✓
accountUuidString✓
canLoginBooleanDefault: True
createdAtDateTime✓
dateOfBirthDate
descriptionString✓
emailString✓
emailVerifiedBooleanDefault: False
experimentGroupMemberExperimentGroupSummary | null
externalIdString✓
homeFolderIdInteger
idInteger✓
isArchivedBooleanDefault: False
isImportProtectedBooleanDefault: False
isOrgOwnerBoolean
isSuperuserBoolean
labelsArray<MemberLabelResponse>Default: []
localeString✓
memberRoleIdInteger✓
memberRoleNameString✓
memberRoleSystemKeyString
memberTypeStringDefault: member
nameString✓
notifyEmailBoolean✓
notifySmsBoolean✓
notifyVoiceBoolean✓
notifyWhatsappBooleanDefault: False
orgRoleString
phoneString✓
phoneTypeString✓
phoneVerifiedBooleanDefault: False
roleString✓
sharedContactPlacementsArray<SharedContactResponse>Default: []
timezoneString
titleString
tosAcceptedBoolean✓
updatedAtDateTime✓
usagePausedBooleanDefault: False
uuidString✓

Example:

{
"id": 0,
"uuid": "string",
"role": "string",
"tosAccepted": false,
"accountTosAccepted": false,
"accountUuid": "string",
"createdAt": "2024-01-01T00:00:00Z",
"updatedAt": "2024-01-01T00:00:00Z",
"name": "string",
"description": "string",
"phone": "string",
"phoneType": "string",
"email": "string",
"externalId": "string",
"emailVerified": false,
"phoneVerified": false,
"notifySms": false,
"notifyEmail": false,
"notifyVoice": false,
"notifyWhatsapp": false,
"locale": "string",
"memberRoleId": 0,
"memberRoleName": "string",
"labels": [],
"memberType": "member",
"isArchived": false,
"isImportProtected": false,
"usagePaused": false,
"canLogin": true,
"sharedContactPlacements": []
}

Endpoints​

List & activity

Create & manage

Current member (me)

Custom fields

Bulk operations

CSV import

Export

Contacts

Import Ndjson

My records (me)

Relationships

List & activity​

List and search the member roster, with message-activity summaries.

Members​

GET /api/v2/w/{workspace_uuid}/members

Description:

List all workspace members with filtering and pagination.

Authorization: Requires members:read scope

Parameters:

  • workspace_uuid (String)
  • memberFilterId (Integer)
  • query (String)
  • search (String)
  • role (String)
  • member_type (String)
  • member_types (String)
  • is_archived (Boolean)
  • is_manager (Boolean)
  • has_account (Boolean)
  • exclude_ids (String)
  • experimentId (Integer)
  • experimentGroupId (Integer)
  • experimentUnassigned (Boolean)
  • page (Integer) — min: 1
  • pageSize (Integer) — min: 1, max: 100
  • sortBy (MemberSortField)
  • sortOrder (SortOrder)

Response: See PaginatedResponse[WorkspaceMemberResponse]


Message Activity​

GET /api/v2/w/{workspace_uuid}/members/message-activity

Description:

Get message activity (count per day) for specified members.

Authorization: Requires members:read scope

Parameters:

  • workspace_uuid (String)
  • member_ids (String)
  • days (Integer) — min: 1, max: 90

Response: dict[int, list[int]]


Create & manage​

Create, fetch, update, archive, and delete Members.

Members​

POST /api/v2/w/{workspace_uuid}/members

Description:

Create a new workspace member.

Authorization: Requires members:write scope

Parameters:

Response: See WorkspaceMemberResponse


Update By External Id​

PUT /api/v2/w/{workspace_uuid}/members/by-external-id/{external_id}

Description:

Update a workspace member addressed by its external id.

The external-id twin of PUT /members/\{member_id}, for an integrator that keys on its own external_id and never stores our numeric member id. Resolution runs on the id route's own pre-write read session, and the write runs through the id route's own body — so the authorization, validation, and audit behaviour are identical, and an external-id address is not a weaker path to the same PHI.

Authorization: Requires members:write scope

Parameters:

Response: See WorkspaceMemberResponse


Archive​

POST /api/v2/w/{workspace_uuid}/members/by-external-id/{external_id}/archive

Description:

Archive a workspace member addressed by its external id.

Resolves the member through MembersService and delegates to the same MembersService.archive_member the id route calls, so the archived state, response body, and post-commit audit event are identical.

Authentication: Requires authenticated user

Parameters:

  • external_id (String)
  • _body (Object)

Response: See WorkspaceMemberResponse


Unarchive​

POST /api/v2/w/{workspace_uuid}/members/by-external-id/{external_id}/unarchive

Description:

Unarchive a member addressed by its external id.

The external-id twin of POST /members/\{member_id}/unarchive; it runs the same GLB membership invariant through MembersService.unarchive_member.

Authentication: Requires authenticated user

Parameters:

  • external_id (String)
  • _body (Object)

Response: See WorkspaceMemberResponse


By Uuid​

GET /api/v2/w/{workspace_uuid}/members/by-uuid/{uuid}

Description:

Get a workspace member by UUID.

The uuid-addressed twin of GET /members/\{member_id}, added for the Activity Spine webhook envelope, which addresses a member by uuid.

Resolution and authorization are the integer read's own path: the lookup runs in the same workspace-bound session and applies the same two gates — self-access, otherwise MEMBERS_READ. A uuid from another workspace cannot resolve at all (the session sees one workspace schema), and a caller without MEMBERS_READ probing another member's uuid gets the same 403 whether or not the uuid names a row, so the route is not an existence oracle.

@phi_audit("member") is deliberate: this is a PHI-bearing read path, and every PHI-bearing read path must be audited. The integer-id twin predated the decorator; it was re-classified in its own reviewed change (GH #27182) and now carries the same marker, so the two twins emit the same event and gate identically when PHI posture enforcement flips from shadow to enforce.

The audit subject is the numeric member id (via the resource_id callback), the same spelling the id-addressed member routes record, so "who read member N" returns both. The path segment is \{uuid} rather than \{member_uuid} on purpose: @phi_audit auto-records a \{resource_type}_uuid path parameter as the subject, and that channel cannot be overridden once the URL has named it.

Authentication: Requires workspace member

Parameters:

  • uuid (String)

Response: See WorkspaceMemberResponse


Delete Members​

DELETE /api/v2/w/{workspace_uuid}/members/{member_id}

Description:

Permanently delete a workspace member and all their associated data.

Important: Members must be archived first before deletion. Use the archive endpoint to safely remove a member from the workspace. Permanent deletion is a destructive operation that cannot be undone.

The force flag allows workspace managers to auto-archive before deletion. Without force, this endpoint is restricted to superusers for erasure scenarios.

reassign_to_id is accepted for query-shape compatibility and rejected with 422 when set: choosing a replacement Member is not authorization to widen that Member's access, so ownership must be changed through the owning resource's own authorized API first. A resource that still blocks erasure comes back as a 409 naming the blocking table.column.

Authorization: Requires members:admin scope

Parameters:

  • member_id (Integer)
  • reassign_to_id (Integer)
  • force (Boolean)

Response: See DeleteMembershipResponse


Members​

GET /api/v2/w/{workspace_uuid}/members/{member_id}

Description:

Get a workspace member by ID.

Self-access: Users can view their own member profile. Admin access: MEMBERS_READ scope required for viewing other members.

@phi_audit("member") classifies this read and emits the same PHI_RECORD_VIEWED event as its by-uuid twin below (GH #27182). The subject is the numeric member id, which the decorator reads straight off the member_id path parameter — no resource_id callback is needed here, and the twin's callback exists only because its path names a uuid.

The asymmetry this closes was not cosmetic: PHI posture classification is read off the @phi_audit marker (lib/middleware/phi_posture_seam.py), so while this route was unclassified the two twins would have diverged the moment enforcement flipped from shadow to enforce — one gated on credential posture, the other not.

Authentication: Requires workspace member

Parameters:

  • member_id (Integer)

Response: See WorkspaceMemberResponse


Update Members​

PUT /api/v2/w/{workspace_uuid}/members/{member_id}

Description:

Update a workspace member.

Authorization: Requires members:write scope

Parameters:

Response: See WorkspaceMemberResponse


Archive​

POST /api/v2/w/{workspace_uuid}/members/{member_id}/archive

Description:

Archive a workspace member instead of deleting them.

Authentication: Requires authenticated user

Parameters:

  • member_id (Integer)

Response: See WorkspaceMemberResponse


Phone Confirmation​

POST /api/v2/w/{workspace_uuid}/members/{member_id}/phone-confirmation

Description:

Text a member asking them to confirm their phone (member-attested trust).

Authorization: Requires members:write scope

Parameters:

  • member_id (Integer)

Response: See SendPhoneConfirmationResponse


Unarchive​

POST /api/v2/w/{workspace_uuid}/members/{member_id}/unarchive

Description:

Unarchive a previously archived workspace member.

For account-backed members this enforces the GLB membership invariant: the GLB workspace_memberships link must exist or be recreatable (the account is still in the workspace's org). When it isn't, unarchive_account_member_by_id raises ConflictError -> HTTP 409 via the global handler and the member stays archived.

Authentication: Requires authenticated user

Parameters:

  • member_id (Integer)

Response: See WorkspaceMemberResponse


Current member (me)​

The authenticated member's own profile and credentials.

Delete Me​

DELETE /api/v2/w/{workspace_uuid}/me

Description:

Leave a workspace.

Archives the workspace-level Member (so they no longer appear as active to others, while preserving assignments, chats, and audit history) and removes the account-to-workspace association so the user loses access.

Authentication: Requires workspace member

Response: See DeleteMembershipResponse


Me​

GET /api/v2/w/{workspace_uuid}/me

Description:

Get the current user's workspace member profile.

This is a self-access endpoint - no special scope required. Includes the effective scopes and org role that the API honors for this user.

Authentication: Requires workspace member

Response: See CurrentMemberResponse


Update Me​

PUT /api/v2/w/{workspace_uuid}/me

Description:

Update the current user's workspace member profile.

This is a self-access endpoint - no special scope required.

Authentication: Requires workspace member

Parameters:

Response: See WorkspaceMemberResponse


Credentials​

GET /api/v2/w/{workspace_uuid}/me/credentials

Description:

Get the current member's connected integration credentials (Discord, etc.).

This is a self-access endpoint - no special scope required.

Authentication: Requires workspace member

Response: List of MemberCredentialResponse


Delete Credentials​

DELETE /api/v2/w/{workspace_uuid}/me/credentials/{credential_id}

Description:

Delete/unlink a credential from the current member's account.

This is a self-access endpoint - no special scope required.

Authentication: Requires workspace member

Parameters:

  • credential_id (Integer)

Response: dict[str, str]


Custom fields​

Custom field definitions and per-member field values.

Member Field Definitions​

GET /api/v2/w/{workspace_uuid}/member-field-definitions

Description:

Get available field definitions for this workspace.

Returns field keys and labels from active app connections' managed field definitions, plus any distinct user-created field keys.

Authorization: Requires members:read scope

Response: See MemberFieldDefinitionListResponse


Fields​

GET /api/v2/w/{workspace_uuid}/members/{member_id}/fields

Description:

Get all fields for a member.

Returns all key-value fields associated with this member, including service-owned fields and placeholder entries for managed field definitions that don't have a value yet.

Authorization: Requires members:read scope

Parameters:

  • member_id (Integer)

Response: See MemberFieldListResponse


Fields​

POST /api/v2/w/{workspace_uuid}/members/{member_id}/fields

Description:

Create a new field for a member.

When serviceId is provided, creates a managed field owned by that app connection. The key must match a managed field definition declared by the connection. Otherwise creates a user-owned field.

Authorization: Requires members:write scope

Parameters:

Response: See MemberFieldResponse


Delete Fields​

DELETE /api/v2/w/{workspace_uuid}/members/{member_id}/fields/{field_id}

Description:

Delete a member field.

Only user-created fields (service_id is null) can be deleted. Service-owned fields are read-only.

Authorization: Requires members:write scope

Parameters:

  • member_id (Integer)
  • field_id (Integer)

Response: dict[str, str]


Update Fields​

PUT /api/v2/w/{workspace_uuid}/members/{member_id}/fields/{field_id}

Description:

Update a member field value.

Both user-created and service-owned (managed) fields can have their values updated. The app connection owns the field's existence; the admin owns the value.

Authorization: Requires members:write scope

Parameters:

Response: See MemberFieldResponse


Bulk operations​

Bulk member operations.

Bulk​

POST /api/v2/w/{workspace_uuid}/members/bulk

Description:

Bulk create or upsert workspace members.

Authorization: Requires members:write scope

Parameters:

Response: See BulkCreateMembersResponse


CSV import​

Preview and run a CSV member import.

Preview​

POST /api/v2/w/{workspace_uuid}/members/import/preview

Description:

Preview a Member CSV without changing records. Requires members:admin and a valid new_member_role_id. Returns row classifications and counts for creates, updates, unchanged rows, conflicts, skips and errors. Review contact and identity conflicts before submitting an import; the authorized preview may include existing and incoming values for comparison. Treat those values as sensitive and do not log them.

Authorization: Requires members:admin scope

Parameters:

  • file (UploadFile)
  • new_member_role_id (Integer)
  • resolutions (String)
  • auto_promote_unique_contacts (Boolean)

Response: See MemberImportPreviewResponse


Runs​

GET /api/v2/w/{workspace_uuid}/members/import/runs

Description:

List recent members import runs, newest first.

Authorization: Requires members:admin scope

Parameters:

  • limit (Integer) — min: 1, max: 100

Response: List of MemberImportRunSummaryResponse


Runs​

POST /api/v2/w/{workspace_uuid}/members/import/runs

Description:

Start a durable Member CSV import and stream progress as SSE events: import_run, import_item and done. The import continues if the stream disconnects. Use GET /members/import/runs for run summaries and GET /members/import/runs/{run_id}/items for row outcomes. Requires members:admin, a valid new_member_role_id and an uploaded CSV. Review the preview before choosing identity overwrites. Contact conflicts need an explicit per-row resolution; unresolved conflicts write nothing. A supplied resolutions entry takes precedence over that row's conflict_resolution CSV column. A concurrently running Member import returns 409. The preview is not a reservation: matching is checked again when the import runs.

Authentication: Public endpoint (no authentication required)

Parameters:

  • file (UploadFile)
  • new_member_role_id (Integer)
  • identity_conflict_policy (IdentityConflictPolicy)
  • resolutions (String)
  • auto_promote_unique_contacts (Boolean)

Response: StreamingResponse


Items​

GET /api/v2/w/{workspace_uuid}/members/import/runs/{run_id}/items

Description:

List per-row outcomes for a Member import run in this Workspace. Requires members:admin. Returns 404 when the run is not a Member import owned by this Workspace. Use limit to bound returned rows.

Authorization: Requires members:admin scope

Parameters:

  • run_id (String)
  • limit (Integer) — min: 1, max: 10000

Response: List of MemberImportRunItemResponse


Export​

Export the member roster.

Export​

POST /api/v2/w/{workspace_uuid}/members/export

Description:

Export members to CSV, matching the same filters the list view applies.

Authorization: Requires members:admin scope

Parameters:

  • selected_fields (Array)
  • selected_data_types (Array)
  • member_type (String)
  • member_types (String)
  • search (String)
  • memberFilterId (Integer)
  • query (String)
  • role (String)
  • is_archived (Boolean)

Response: StreamingResponse


Contacts​

Contacts​

GET /api/v2/w/{workspace_uuid}/members/{member_id}/contacts

Description:

The people in a workspace — roster, profiles, custom fields, and bulk import/export

List a Member's contact points. Self, or members:read.

Authentication: Requires workspace member

Parameters:

  • member_id (Integer)
  • include_retired (Boolean)

Response: See MemberContactPointListResponse


Contacts​

POST /api/v2/w/{workspace_uuid}/members/{member_id}/contacts

Description:

The people in a workspace — roster, profiles, custom fields, and bulk import/export

Add a contact point. Self, or members:write.

Authentication: Requires workspace member

Parameters:

Response: See MemberContactPointResponse


Delete Contacts​

DELETE /api/v2/w/{workspace_uuid}/members/{member_id}/contacts/{contact_uuid}

Description:

The people in a workspace — roster, profiles, custom fields, and bulk import/export

Retire a point (a state change; the row and provenance remain).

Authentication: Requires workspace member

Parameters:

  • member_id (Integer)
  • contact_uuid (String)
  • request (RetireMemberContactPointRequest)

Response: See MemberContactPointResponse


Update Contacts​

PUT /api/v2/w/{workspace_uuid}/members/{member_id}/contacts/{contact_uuid}

Description:

The people in a workspace — roster, profiles, custom fields, and bulk import/export

Change a point's value and/or annotations. Self, or members:write.

Authentication: Requires workspace member

Parameters:

Response: See MemberContactPointResponse


Promote​

POST /api/v2/w/{workspace_uuid}/members/{member_id}/contacts/{contact_uuid}/promote

Description:

The people in a workspace — roster, profiles, custom fields, and bulk import/export

Make a point the member's primary value.

Self, or members:admin when the target is another member: this is the one contact-point operation that writes the member's phone/email scalar, the gate PUT /members/\{id} carries for that field.

Refuses with 409 while another member holds the value as primary, or while another global Account holds it as its credential; with 400 for a retired point or a test/anonymous member. The previous primary is kept as a contact point.

The (empty) JSON body is a deliberate CSRF barrier: authToken defaults to SameSite=None, so a bodyless POST is forgeable by a cross-site HTML form; requiring JSON forces cross-origin callers into CORS preflight (GRA-6284).

Authentication: Requires workspace member

Parameters:

  • member_id (Integer)
  • contact_uuid (String)
  • _body (Object)

Response: See MemberContactPointResponse


Import Ndjson​

Import​

POST /api/v2/w/{workspace_uuid}/members/import

Description:

The people in a workspace — roster, profiles, custom fields, and bulk import/export

Import members from CSV file with streaming progress updates.

Contact conflicts (a row's email/phone disagreeing with the Account it matches) are resolved per ROW, not per request, over either transport:

  • the conflict_resolution CSV column (keep_account / skip), for programmatic callers that generate the file;
  • the resolutions form field, for the wizard — it reviews the read-only preview and cannot rebuild the CSV (identity-header aliasing is server-side), and it can also correct a value (replace_values), which no column can express.

The JSON field wins over the column for the same row: the column is a property of the uploaded file, the JSON is what the operator just decided. Rows with neither stream a conflict event and write nothing.

A resolution naming a row the file does not contain is a 400 (row numbers only — never values). See process_import for what a decision means when the row no longer conflicts at commit time.

identity_conflict_policy='overwrite_identity_fields' is intended to be sent only after an operator reviews a preview. Two known, accepted limits:

  • Preview→commit is not transactional (TOCTOU): the commit re-classifies each row against live DB state, so under the default skip policy a row that became a conflict after the preview is still skipped. Under overwrite the operator's prior approval is applied to current state, so an identity that changed between preview and commit is overwritten on the stale approval. Closing this would require optimistic locking / a preview token.
  • Preview-first is advisory, not enforced: overwrite requires only MEMBERS_ADMIN (which can already mutate identity via the member-update API) and every overwrite is audited; there is no backend gate requiring a prior preview call.

Authorization: Requires members:admin scope

Parameters:

  • file (UploadFile)
  • new_member_role_id (Integer)
  • identity_conflict_policy (IdentityConflictPolicy)
  • resolutions (String)
  • auto_promote_unique_contacts (Boolean)

Response: StreamingResponse


My records (me)​

The authenticated member's own DataTypes and records (self-access, no coarse scope).

Data Types​

GET /api/v2/w/{workspace_uuid}/me/data-types

Description:

List member-scoped DataTypes the current Member may use.

Self-access — no special scope required, mirroring GET /members/me. Admin-only Forms are listed only for records:admin, even to their owner; canCreate runs the same gate record creation enforces, so a client can offer an Add control only where a create would succeed.

Authentication: Requires workspace member

Response: List of MyDataTypeResponse


Records​

GET /api/v2/w/{workspace_uuid}/me/records

Description:

List the current Member's own records for one DataType.

Self-access — no special scope required. Records are hard-filtered to the session Member (member_id == self); there is no parameter that widens this. An admin-only Form answers 404 without records:admin, even for its owner, as the DataRecord policy decides.

Query Parameters:

  • data_type_id: DataType to list the Member's own records for
  • page (default 1) / page_size (default 50, max 100): pagination

Authentication: Requires workspace member

Parameters:

  • data_type_id (Integer) — min: 1
  • page (Integer) — min: 1
  • page_size (Integer) — min: 1, max: 100

Response: See PaginatedResponse[DataRecordResponse]


Relationships​

Relationships​

GET /api/v2/w/{workspace_uuid}/members/{member_id}/relationships

Description:

The people in a workspace — roster, profiles, custom fields, and bulk import/export

Authorization: Requires members:read scope

Parameters:

  • member_id (Integer)
  • page (Integer) — min: 1
  • pageSize (Integer) — min: 1, max: 100
  • kind (MemberRelationshipKind)

Response: See PaginatedResponse[MemberRelationshipResponse]


Relationships​

POST /api/v2/w/{workspace_uuid}/members/{member_id}/relationships

Description:

The people in a workspace — roster, profiles, custom fields, and bulk import/export

Authorization: Requires members:write scope

Parameters:

  • member_id (Integer)
  • request_body (CreateMemberRelationshipRequest)

Response: See MemberRelationshipResponse


Delete Relationships​

DELETE /api/v2/w/{workspace_uuid}/members/{member_id}/relationships/{relationship_id}

Description:

The people in a workspace — roster, profiles, custom fields, and bulk import/export

Authorization: Requires members:write scope

Parameters:

  • member_id (Integer)
  • relationship_id (Integer)

Response: See MemberRelationshipResponse