Members

List organization members and change their role, project access, and state.

The organization directory and membership management, with the portal's guards. Invitations are on their own page: Invites.

EndpointScopeToken owner's role
GET /membersmembers:readAny member
GET /members/{id}members:readAdmin
PATCH /members/{id}members:writeAdmin
PUT /members/{id}/projectsmembers:writeAdmin
POST /members/{id}/disablemembers:writeAdmin
POST /members/{id}/enablemembers:writeAdmin
DELETE /members/{id}members:writeAdmin
DELETE /members/{id}/sessionsmembers:writeAdmin
GET /members/removedmembers:readAdmin
POST /members/removed/{id}/restoremembers:writeAdmin

members:write is a sensitive scope. Every route except the list needs an owner who is currently an organization Admin; a Member's token gets 403 insufficient_scope.

People join through invitations, which are sent from the portal; accepting one is the invitee's step.

Python examples share this setup:

import os
import uuid
import requests

BASE = "https://portal.<domain>/api/v1/mgmt"
s = requests.Session()
s.headers["Authorization"] = f"Bearer {os.environ['TF_PAT']}"

def idem():
    return {"Idempotency-Key": str(uuid.uuid4())}

The member object

FieldTypeNotes
idstringThe member's user id.
emailstring
namestring | null
rolestringadmin or member.
statusstringactive or disabled.
email_verifiedboolean
created_atstringWhen they joined.
last_login_atstring | null
key_countintegerAPI keys this member created.
project_idsstring[]Projects the member belongs to. Always present on single-member responses; list rows may omit it. Admins can access every project regardless.

This is the full object an Admin token owner gets. A Member token owner listing members gets only each member's identity; see List members.

{
  "id": "8e7d6c5b-4a39-4281-9f0e-1d2c3b4a5968",
  "email": "[email protected]",
  "name": "Dana Okafor",
  "role": "admin",
  "status": "active",
  "email_verified": true,
  "created_at": "2026-03-02T09:41:17Z",
  "last_login_at": "2026-09-24T08:12:40Z",
  "key_count": 3,
  "project_ids": []
}

List members

GET /api/v1/mgmt/members

Newest first. Paginated with limit and cursor.

The rows depend on the token owner's current role:

  • Admin: the full member object.
  • Member: only each member's identity: id, email, name, role and created_at. Member state (status, email_verified, last_login_at, key_count) is admin-only.
{
  "id": "8e7d6c5b-4a39-4281-9f0e-1d2c3b4a5968",
  "email": "[email protected]",
  "name": "Dana Okafor",
  "role": "admin",
  "created_at": "2026-03-02T09:41:17Z"
}
Query parameterNotes
roleadmin or member. Any other value → 422 validation_failed.
statusactive or disabled. Any other value → 422 validation_failed. Admin only: a Member's token gets 403 insufficient_scope.
curl -G https://portal.<domain>/api/v1/mgmt/members \
  -H "Authorization: Bearer $TF_PAT" \
  -d role=admin

Retrieve a member

GET /api/v1/mgmt/members/{id}
curl https://portal.<domain>/api/v1/mgmt/members/8e7d6c5b-4a39-4281-9f0e-1d2c3b4a5968 \
  -H "Authorization: Bearer $TF_PAT"

Change a member's role or name

PATCH /api/v1/mgmt/members/{id}
Body fieldTypeNotes
rolestringadmin or member.
namestringDisplay name.

Send at least one field. Returns 200 with the member object. The last active admin cannot be demoted (409 last_admin), and the organization owner's role cannot be changed (409 owner_protected). A demotion also narrows the member's personal access tokens.

curl -X PATCH https://portal.<domain>/api/v1/mgmt/members/8e7d6c5b-4a39-4281-9f0e-1d2c3b4a5968 \
  -H "Authorization: Bearer $TF_PAT" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 5f6a7b8c-9d0e-4f1a-8b2c-3d4e5f6a7b8c" \
  -d '{"role": "member"}'

Set a member's project access

PUT /api/v1/mgmt/members/{id}/projects

Replaces the set of projects the member belongs to. Returns 200 with the member object.

Body fieldTypeRequiredNotes
project_idsstring[]yesThe full set. [] removes all project access. A project outside the organization → 400 invalid_request, param: "project_ids".
curl -X PUT https://portal.<domain>/api/v1/mgmt/members/8e7d6c5b-4a39-4281-9f0e-1d2c3b4a5968/projects \
  -H "Authorization: Bearer $TF_PAT" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6a7b8c9d-0e1f-4a2b-9c3d-4e5f6a7b8c9d" \
  -d '{"project_ids": ["5b3e9a41-0c7d-4e2f-8a16-9d4b7c2e1f08"]}'

Disable a member

POST /api/v1/mgmt/members/{id}/disable

Reversible containment. The member can no longer sign in, is signed out of every browser session, and every personal access token they created is revoked. API keys they created keep working — they are project assets. No body. Returns 200 with the member object (status: "disabled").

The last active admin cannot be disabled (409 last_admin), and you cannot disable yourself (409 cannot_act_on_self).

curl -X POST https://portal.<domain>/api/v1/mgmt/members/8e7d6c5b-4a39-4281-9f0e-1d2c3b4a5968/disable \
  -H "Authorization: Bearer $TF_PAT" \
  -H "Idempotency-Key: incident-4711-disable-member"

Warning

Revoked tokens stay revoked. Enabling the member again does not restore their personal access tokens — they mint new ones.

Enable a member

POST /api/v1/mgmt/members/{id}/enable

Restores a disabled member's sign-in. No body. Returns 200 with the member object (status: "active").

curl -X POST https://portal.<domain>/api/v1/mgmt/members/8e7d6c5b-4a39-4281-9f0e-1d2c3b4a5968/enable \
  -H "Authorization: Bearer $TF_PAT" \
  -H "Idempotency-Key: 7b8c9d0e-1f2a-4b3c-8d4e-5f6a7b8c9d0e"

Remove a member

DELETE /api/v1/mgmt/members/{id}

Permanent. The member's account in this organization is deleted, and their personal access tokens are deleted with it. Past audit entries remain. The person is listed under removed members and can't rejoin through SSO automatic membership until you restore or invite them. Returns 204 No Content.

Body fieldTypeRequiredNotes
delete_keysbooleannotrue also revokes every API key the member created. Default false: their keys keep working.

The last active admin cannot be removed (409 last_admin), you cannot remove yourself (409 cannot_act_on_self), and the organization owner is protected (409 owner_protected).

curl -X DELETE https://portal.<domain>/api/v1/mgmt/members/8e7d6c5b-4a39-4281-9f0e-1d2c3b4a5968 \
  -H "Authorization: Bearer $TF_PAT" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 8c9d0e1f-2a3b-4c4d-9e5f-6a7b8c9d0e1f" \
  -d '{"delete_keys": true}'

Sign a member out everywhere

DELETE /api/v1/mgmt/members/{id}/sessions

Ends every sign-in session of the member, so each browser has to sign in again. It does not revoke their personal access tokens (disable the member for that). No body. Returns 204 No Content.

When the member has no active sign-in session to end, the call is 409 sessions_unavailable; if sign-out is temporarily unavailable, 502 upstream_unavailable — retry with the same Idempotency-Key.

curl -X DELETE https://portal.<domain>/api/v1/mgmt/members/8e7d6c5b-4a39-4281-9f0e-1d2c3b4a5968/sessions \
  -H "Authorization: Bearer $TF_PAT" \
  -H "Idempotency-Key: 9d0e1f2a-3b4c-4d5e-8f6a-7b8c9d0e1f2a"

List removed members

GET /api/v1/mgmt/members/removed

People an admin removed from the organization, newest removal first, in the standard list envelope (limit, cursor). A removed person can't rejoin through your SSO's automatic membership; restore or invite them to give access again.

FieldTypeNotes
idstringThe removal's id, used to restore.
emailstring
namestring | null
last_roleadmin | member | nullTheir role when removed; null if not recorded.
projects_recordedbooleanfalse for removals made before project access was recorded.
projectsarray of {id, name}Recorded projects that still exist.
removed_atstring (date-time)
removed_by{id, name, email} | nullThe admin who removed them.
invite_pendingbooleanA restore or invitation was sent and not accepted yet; they stay removed until they accept it.
curl https://portal.<domain>/api/v1/mgmt/members/removed \
  -H "Authorization: Bearer $TF_PAT"

Restore a removed member

POST /api/v1/mgmt/members/removed/{id}/restore

Gives the person access again with their previous role and the recorded projects that still exist (Member with no projects when nothing was recorded). No body. Removing someone deletes their sign-in, so they become a member again the next time they sign in: your organization's SSO lets them straight in, and any other sign-in method uses the link in the email they get ("You've been given access to <organization> again"). Until then they appear under GET /invites as invite_id, and they are still listed as removed (invite_pending: true): they stay removed until they accept. If you revoke the invitation or it expires, they stay removed and automatic membership never lets them back in.

{
  "status": "restored",
  "email": "[email protected]",
  "role": "member",
  "project_ids": ["3f2b1c0d-9e8f-4a7b-b6c5-d4e3f2a1b0c9"],
  "invite_id": "0a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
  "expires_at": "2026-10-14T10:00:00Z"
}

409 already_a_member when the person is a member again; 409 conflict when the invitation rules refuse the address (for example, your SSO only allows invitations at your own email domain).

curl -X POST https://portal.<domain>/api/v1/mgmt/members/removed/6f1c2b0a-3d4e-4f50-8a9b-0c1d2e3f4a5b/restore \
  -H "Authorization: Bearer $TF_PAT" \
  -H "Idempotency-Key: 1e2f3a4b-5c6d-4e7f-8a9b-0c1d2e3f4a5b"

Errors specific to members

StatusCodeWhen
400invalid_request (param set)Empty PATCH body; unknown field; unknown role; project_ids missing or naming a project outside the organization.
403insufficient_scopeToken lacks members:read/members:write, or the owner is not currently an Admin.
404not_foundMember absent or in another organization.
409last_adminDemoting, disabling or removing the organization's last active admin.
409owner_protectedChanging the role of, or removing, the organization owner.
409cannot_act_on_selfDisabling or removing yourself.
409sessions_unavailableDELETE /members/{id}/sessions for a member with no active sign-in session.
409already_a_memberRestoring someone who is a member again.
422unsupported_filter · validation_failedAn unknown query parameter, or a role/status filter value outside the list above.
{
  "error": {
    "message": "this change would leave the organization without an active admin",
    "type": "conflict_error",
    "code": "last_admin",
    "param": null,
    "request_id": "req_d5f16075d7984e1b99c537a18f7fc58a"
  }
}