Team workspaces
A Driftstack team is one owner account plus the member accounts that joined it. Team members, invites and roles are managed in the Driftstack dashboard; the API does not manage membership. What the API does is let a member’s program work in the owner’s workspace.
Acting on behalf of an owner
Once a member is on a team, they can scope a request to the owner’s
resources by passing the X-Driftstack-Account header:
GET /v1/sessions
Authorization: Bearer ds_live_<member-key>
X-Driftstack-Account: acc_<owner-uuid>
The server validates that the caller is on the team referenced by the
header. The response shape is unchanged; resources are scoped to the
owner instead of the caller. The owner can read their own account id
with GET /v1/whoami (account_id) and share it with
the team.
Role gating:
- Read endpoints (GET) accept both
memberandadminroles. - Write endpoints (POST / PATCH / PUT / DELETE) require the
adminrole on the team. Thememberrole gets403. - Your key’s scopes still apply. The team role decides whose
resources you reach; the scopes on your own key decide what it may do
there, exactly as on your own account. For example, managing the
owner’s webhooks needs a key with
account_owner, so aread:sessionskey gets403even for a team admin. - Agent-session exception.
/v1/agent-sessionscontains AI transcripts and live-control state, so its collection and:idsurface requireadminfor both reads and writes. - API key list. In the owner’s workspace only an
adminis shown the owner’s API keys; amembergets403.
Endpoints that honor the header include:
/v1/sessions(GET / POST / DELETE) and/:id/{navigate,interact,wait,capture,state}/v1/agent-sessions(GET collection / POST create /:idreads and controls) — an admin member can list and operate the owner’s runs or launch the owner’s profile; each run scopes to the owner (counts against the owner’s cap and uses the owner’s profile data). Thememberrole gets403on this whole surface./v1/profiles(GET / POST / PATCH / DELETE) and the profile snapshots/v1/recipes— in a team owner’s workspace every recipe route, reads included, needs theadminrole/v1/webhooks(GET / POST / PATCH / DELETE),/:id/deliveries,/:id/rotate-secret,/:id/testand/v1/webhook-deliveries/:id/replay— amemberreads/:id/deliverieswithlast_response_excerptset tonull(the reply of the owner’s endpoint); an admin gets it/v1/usageand/v1/usage/series(GET)/v1/account/me/proxies(GET / POST / PUT / DELETE,/:id/test,/importand the/:id/change-ip…routes) — an admin reads and writes the owner’s saved proxies; amembermay only list them, as narrow rows (member_view: true, no address, user name or password flag). These routes need a key withaccount_owner./v1/account/me/credentials(GET) — either role lists the owner’s saved credentials: handle, label, websites,include_subdomainsandcreated_at, never a value. Saving and deleting stay with the owner; sent with the header, they are refused with400./v1/team/members(GET) — an admin lists the owner’s team members: the same list, with the same fields, the owner sees. Thememberrole gets403. Without the header it lists your own team’s members. Changed 2026-10-19: this route used to ignore the header. A program that sendsX-Driftstack-Accounton every request now gets the owner’s list as an admin, and403as a member or for an id it is not a member of (or a malformed one), or403 plan_endedwhen the owner’s plan has ended.GET /v1/team/invitesstill ignores the header.
GET /v1/whoami always describes the key that made the request, with or
without the header.
Your memberships
GET /v1/account/me lists every owner who added you as a member in
teams, and GET /v1/team/owners returns the same list as data. Neither
reads the X-Driftstack-Account header. Each entry has these fields:
| Field | Meaning |
|---|---|
owner_account_id |
The owner’s account id (acc_<uuid>): the value to send as X-Driftstack-Account. |
owner_email |
The owner’s email address (acc_<uuid> if it could not be loaded). |
owner_name |
The owner’s display name, or null if they never set one. |
role |
Your role with this owner: member or admin. |
membership_id |
Your membership (mem_<uuid>). |
team_id |
The owner’s team record (team_<uuid>), or null when the owner has none yet. |
team_name |
That record’s name, or null when there is no record or the name is blank. |
team_id and team_name are left out of an entry, rather than set to
null, when the server could not read them for that response. Treat a
missing field as “unknown right now”, and null as “the owner has no team
name”.