---
title: "Change the role, brand access, or API access of a colleague (preview, then confirm)"
method: PATCH
path: "/api/v1/workspaces/{workspaceId}/members/{memberId}"
tags: ["Team"]
---

# Change the role, brand access, or API access of a colleague (preview, then confirm)

`PATCH /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

API access: send `api_access` (true or false) on its own to let this teammate use the workspace API plan from their own keys and AI apps, or to stop it. Off by default. The owner or an admin changes it for someone else, never for themselves (403 `cannot_change_own_access`); only the owner changes it for an admin. Turning it off stops their keys and AI app connections in this workspace on the next request and switches off their webhooks for it. The workspace owner is emailed.
Owner or admin. Same rules as the dashboard: nobody changes their own role, only the owner changes an admin seat, the owner seat never changes. Admin cannot be granted over the API. Moving a seat to admin is dashboard-only; brands are ignored for admins, who see every brand.
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`.
Limit: 30 role, brand, removal and revoke requests per workspace per 24 hours (previews count).

## Path parameters

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

## Request body

- UpdateWorkspaceSeatRequest: Send role, brand_ids, or both. Or, for a member only and on its own, api_access.
  - `role` 'publisher' | 'editor' | 'viewer'
  - `brand_ids` string[]: Replaces the brand limit. Empty means every brand. Omit to keep the current brands.
  - `api_access` boolean: Members only, in its own request. true lets the teammate use this workspace API plan from their own keys and AI apps; false turns it off, which also switches off their webhooks for this workspace. You cannot change your own (403 cannot_change_own_access); the owner row answers 403 owner_api_access_fixed.
  - `confirm` boolean
  - `preview_id` string

## Response `200`

Preview (nothing changed) or the updated 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

---

[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)
