---
title: "Remove a colleague (preview, then confirm)"
method: DELETE
path: "/api/v1/workspaces/{workspaceId}/members/{memberId}"
tags: ["Team"]
---

# Remove a colleague (preview, then confirm)

`DELETE /api/v1/workspaces/{workspaceId}/members/{memberId}`

## Request

- Base URL: `https://www.poster.ly`
- URL: `https://www.poster.ly/api/v1/workspaces/{workspaceId}/members/{memberId}`
- Auth: HTTP bearer

Owner or admin. Revokes their API keys bound to this workspace, then frees the seat. You cannot remove yourself or the owner, and only the owner removes an admin. Pass `confirm=true&preview_id=...` as query parameters to act.
Two steps. Without `confirm: true` the call returns 200 with `requires_confirmation`, a plain-language `preview` and a short-lived `preview_id`, and changes nothing. Show the preview to the user; after they approve, repeat the call with `confirm: true` and the `preview_id`. If anything changed in between, the confirm answers 409 `preview_stale` and changes nothing (a safeguard for the user, not a security control: every rule is checked again on confirm).
Access: an API key that a person granted "Manage team" in /dashboard/api (members:read and members:write). Browser sessions are refused with `api_key_required`; the scopes can never be requested through POST /api/v1/api-keys. ChatGPT, Claude and other OAuth connections hold both scopes implicitly (when their grant includes accounts:write) but may call this endpoint only while the person is an owner or admin of the workspace; otherwise 403 `team_management_requires_admin`. A key bound to another workspace, or a caller who is not a member, gets 404; a caller whose seat was removed gets 403 `workspace_access_revoked`.

## Path parameters

- `workspaceId` string, uuid, required
- `memberId` string, uuid, required

## Query parameters

- `confirm` boolean
- `preview_id` string

## Response `200`

Preview (nothing changed) or the removed member.

- union
  - WorkspaceMemberPreviewResponse: Returned when confirm is not true. Nothing was written and no email was sent.
    - `requires_confirmation` true
    - `preview` object
      - `action` 'invite' | 'reinvite' | 'resend_invite' | 'add_member' | 'update_member' | 'update_invite' | 'remove_member' | 'revoke_invite' | 'set_api_access'
      - `summary` string: Plain-language description to show the user before confirming.
      - `email` string, nullable
      - `role` string, nullable
      - `previous_role` string, nullable
      - `brand_ids` string[], nullable
      - `previous_brand_ids` string[], nullable
      - `sends_email` boolean
      - `counts_toward_seats` boolean
      - `seats_used` integer, nullable
      - `seat_limit` integer, nullable: -1 means no limit.
      - `api_access` boolean: set_api_access only: the new setting.
      - `previous_api_access` boolean, nullable: set_api_access only: the setting before this change.
    - `preview_id` string: Short-lived (15 minutes). Pass back with confirm: true.
    - `preview_expires_at` string, date-time
    - `next_step` string
  - WorkspaceMemberChangeResponse
    - `status` 'invited' | 'invite_updated' | 'invite_resent' | 'already_invited' | 'added' | 'updated' | 'removed' | 'revoked'
    - `invite` WorkspaceInvite: A pending invite. Never includes the invite link or token.
      - `id` string, uuid: Invite ID (use in /invites/{inviteId}).
      - `email` string
      - `role` 'admin' | 'publisher' | 'editor' | 'viewer'
      - `status` 'pending' | 'revoked' | 'accepted' | 'expired'
      - `brand_ids` string[]
      - `expires_at` string, date-time, nullable
      - `created_at` string, date-time, nullable
    - `member` WorkspaceMember
      - `id` string, uuid: Member ID (use in /members/{memberId}).
      - `user_id` string, nullable
      - `email` string, nullable
      - `name` string, nullable
      - `role` 'owner' | 'admin' | 'publisher' | 'editor' | 'viewer'
      - `status` 'active' | 'invited' | 'removed'
      - `brand_ids` string[]: Brands this seat is limited to. Empty means every brand. Missing when a brand-limited caller cannot see this seat assignment.
      - `api_access` boolean: Whether this teammate can use the workspace API plan from their own API keys and AI apps. Off by default; always true for the owner, whose own add-on decides.
      - `api_usage` object: Owners and admins only: live API keys and AI app connections that can act in this workspace for this member.
        - `key_count` integer
        - `last_used_at` string, date-time, nullable
        - `connected_apps` string[]
      - `joined_at` string, date-time, nullable
      - `created_at` string, date-time, nullable
    - `email_sent` boolean
    - `owner_notified` boolean: True when the owner alert email about this change was sent.
    - `message` string

## Other responses

- `400`: Validation error
- `401`: Invalid or missing API key
- `403`: Request is authenticated but not allowed
- `404`: Resource not found
- `409`: Resource is not in a usable state for this request
- `429`: Too many requests
- `500`: The member API keys could not be revoked, so the seat was kept.

---

[API](https://skmtc.dev/poster/apis/posterly-api.md) · [All operations](https://skmtc.dev/poster/apis/posterly-api/llms.txt) · [OpenAPI document](https://skmtc.dev/poster/apis/posterly-api/revisions/60be02a61f95?raw)
