Team API
Your team is everyone who works inside your account besides you — admins, agents and read-only viewers — plus the invitations you have sent and the departments you organise them into. The Team API is the programmatic version of Settings → Team: add and remove people, set what each of them can see and do, send and chase invitations, and manage departments.
All endpoints below are relative to the base URL https://api.youraiconnector.com/v1. For the dashboard version of everything on this page, see Team Management.
Authentication: these endpoints need a signed-in person
This is the one part of the API that an API key cannot use. Every /team endpoint except the department ones has to be called with a Firebase ID token from a signed-in session:
Authorization: Bearer <Firebase ID token>
Send an API key instead and the request is rejected with a 401:
{
"success": false,
"error_code": 401,
"error": "This endpoint requires a Firebase ID token (Authorization: Bearer <token>)."
}
The reason is that these endpoints decide what to do based on who is signed in: your role, the ceiling on what you are allowed to grant someone else, and whether you are currently working inside another account. An API key is an integration, not a person, so there is nobody for those rules to apply to.
In practice that means the Team API is for a first-party app with a logged-in Your AI Connector user (see Authentication → Firebase ID token). A server-to-server integration cannot manage team members — there is no way to mint one of these tokens from outside the app.
The exception: the four department endpoints are ordinary API endpoints. They accept your API key exactly like the rest of the API, as well as a signed-in session.
Every response on this page follows the usual envelope: success: true plus the endpoint’s fields at the top level, or success: false with error and error_code when something goes wrong.
Roles and permissions
Every team member has one role, which sets their default access across 12 areas of the app. You can then override individual areas.
| Role | Value | Summary |
|---|---|---|
| Admin | admin |
Everything except the owner’s billing-level actions. |
| Editor | editor |
Can create and change things. Shown as Agent in the app. |
| Viewer | viewer |
Read-only. |
Each area is set to one of four levels: none (hidden), view (read-only), edit (create and change), full (including deleting).
| Area | Admin | Editor | Viewer |
|---|---|---|---|
campaigns |
full | edit | view |
contacts |
full | edit | view |
messages |
full | edit | view |
appointments |
full | edit | view |
settings |
edit | view | none |
billing |
edit | none | none |
team_management |
edit | none | none |
analytics |
full | view | view |
phone_numbers |
edit | none | none |
integrations |
edit | none | none |
faqs |
full | edit | view |
daily_summaries |
full | view | view |
To depart from the role’s defaults, send permission_overrides — an array of { "area": ..., "level": ... } objects. Each entry replaces the role’s default for that one area; everything you do not list keeps the role default.
"permission_overrides": [
{ "area": "analytics", "level": "full" },
{ "area": "billing", "level": "none" }
]
Who can call these endpoints
- The account owner can always do everything.
- A team member needs
team_managementatviewto read the roster and the invite list, and ateditto add, change, suspend, remove, invite, cancel or resend. Admins haveeditby default; editors and viewers havenone, so by default only admins can manage the team. - Nobody can grant access above their own. If you try to give someone a level you do not hold yourself — or to edit, suspend or remove someone whose access is already broader than yours — the request is refused with
403and a message naming the area.
The team member object
GET /team/members returns one of these per member:
| Field | Type | Description |
|---|---|---|
member_uid |
string | The member’s own user ID. This is the {memberUid} in the paths below. |
account_owner_uid |
string | The account they are a member of. |
member_email |
string | Their email address. |
member_display_name |
string | The name shown for them in the app. |
role |
string | admin, editor or viewer. |
permission_overrides |
array | Their per-area exceptions. [] when they are purely on role defaults. |
status |
string | active or suspended. |
auto_assign_enabled |
boolean | null | Whether new contacts can be auto-assigned to them. null means never changed, which behaves as true. |
created_by |
string | Who added them. |
created_at |
string | null | ISO 8601 timestamp. |
updated_at |
string | null | ISO 8601 timestamp. |
Removed members are not returned — the list is active and suspended members only.
Visibility limits are write-only here.
contact_scope,contact_scope_axesandsub_account_access(see Limiting what a member can see) can be set on create, update and invite, but this endpoint does not return them.
List team members
GET /team/members
Returns the roster plus your plan’s seat counts, so you can show “3 of 5 seats” and know when inviting is about to be refused.
cURL
curl "https://api.youraiconnector.com/v1/team/members" \
-H "Authorization: Bearer FIREBASE_ID_TOKEN"
JavaScript
const res = await fetch("https://api.youraiconnector.com/v1/team/members", {
headers: { Authorization: `Bearer ${idToken}` },
});
const { members, seat_limit, seats_used } = await res.json();
Python
import requests
res = requests.get(
"https://api.youraiconnector.com/v1/team/members",
headers={"Authorization": f"Bearer {id_token}"},
)
data = res.json()
Response
{
"success": true,
"members": [
{
"account_owner_uid": "owner_uid_123",
"member_uid": "uid_alice",
"member_email": "alice@example.com",
"member_display_name": "Alice Chen",
"role": "admin",
"permission_overrides": [],
"status": "active",
"auto_assign_enabled": true,
"created_by": "owner_uid_123",
"created_at": "2026-05-01T10:00:00.000Z",
"updated_at": "2026-06-02T09:15:00.000Z"
}
],
"seat_limit": 5,
"seats_used": 3
}
seat_limit is null when your plan has no seat cap. seats_used counts active members only — suspending or removing someone frees their seat immediately.
Add a team member directly
POST /team/members
Puts someone on your team straight away, without an invitation.
This sends no email. Nobody is told they were added, and if they did not already have a Your AI Connector login, the account created for them has no password, so they cannot sign in until they reset it. Use Send an invitation unless you have your own way of telling the person and getting them signed in.
Request fields
| Field | Required | Description |
|---|---|---|
email |
Yes | The teammate’s email address. |
display_name |
Yes | The name shown for them in the app. |
role |
Yes | admin, editor or viewer. |
permission_overrides |
No | Per-area exceptions to the role’s defaults. |
contact_scope |
No | all or assigned — see Limiting what a member can see. |
contact_scope_unassigned |
No | With assigned, also let them see contacts nobody owns yet. |
contact_scope_axes |
No | Limit them to named agents, channels or departments. |
sub_account_access |
No | Agencies only — which client sub-accounts they may open. |
cURL
curl -X POST "https://api.youraiconnector.com/v1/team/members" \
-H "Authorization: Bearer FIREBASE_ID_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"email": "sam@example.com",
"display_name": "Sam Rivera",
"role": "editor"
}'
JavaScript
const res = await fetch("https://api.youraiconnector.com/v1/team/members", {
method: "POST",
headers: {
Authorization: `Bearer ${idToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
email: "sam@example.com",
display_name: "Sam Rivera",
role: "editor",
}),
});
const { member_uid } = await res.json();
Response — 201 Created
{
"success": true,
"team_member_id": "owner_uid_123_uid_sam",
"member_uid": "uid_sam",
"message": "Team member created successfully."
}
| Status | When |
|---|---|
400 |
email, display_name or role is missing, the role is not one of the three, or you tried to add yourself. |
403 |
You do not have permission to manage the team, or you tried to grant access above your own. |
409 |
That person is already an active member of your team. |
429 |
Your plan’s team seats are full. |
Adding someone who was previously suspended or removed reinstates them rather than failing.
Update a team member
PATCH /team/members/{memberUid}
Changes a member’s role, permissions, visibility, client access, or whether they take part in automatic contact assignment. Send only the fields you want to change; anything you leave out keeps its current value.
Request fields
| Field | Description |
|---|---|
role |
admin, editor or viewer. |
permission_overrides |
Replaces their whole override list. Send [] to put them back on pure role defaults. |
status |
Only active is accepted, to bring a suspended member back. To suspend someone, use the suspend endpoint. |
auto_assign_enabled |
true or false. |
contact_scope |
all or assigned. |
contact_scope_unassigned |
true or false. |
contact_scope_axes |
See Limiting what a member can see. |
sub_account_access |
Agencies only. |
This is the only endpoint where
nullmeans “clear”. Sending"contact_scope": null,"contact_scope_axes": nullor"sub_account_access": nullremoves that limit entirely and puts the member back to seeing everything. On create and invite,nullsimply means “not supplied”.
cURL
curl -X PATCH "https://api.youraiconnector.com/v1/team/members/uid_sam" \
-H "Authorization: Bearer FIREBASE_ID_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"role": "admin",
"permission_overrides": [{ "area": "billing", "level": "none" }]
}'
Response
{
"success": true,
"message": "Team member updated successfully."
}
| Status | When |
|---|---|
400 |
An invalid status or auto_assign_enabled value, or you tried to reactivate a member who was removed (removed members must be re-invited). |
403 |
You do not have permission, or the change would edit or create access broader than your own. |
404 |
No such team member. |
Suspend a team member
POST /team/members/{memberUid}/suspend
Suspends someone: they keep their place on the team but lose access. Use this instead of removing when the pause is temporary — bring them back with PATCH /team/members/{memberUid} and {"status": "active"}.
cURL
curl -X POST "https://api.youraiconnector.com/v1/team/members/uid_sam/suspend" \
-H "Authorization: Bearer FIREBASE_ID_TOKEN"
Response
{
"success": true,
"message": "Team member suspended successfully."
}
A suspended member frees their seat, so you can invite someone else in their place. Their access ends when their current session token next refreshes, which can take up to an hour — remove them instead if you need it to be immediate.
| Status | When |
|---|---|
400 |
You tried to suspend the account owner, or a member who is already suspended or removed. |
403 |
Their access is broader than yours. |
404 |
No such team member. |
Remove a team member
DELETE /team/members/{memberUid}
Removes someone from your team and frees their seat. They are signed out and lose access to your account; their own login is untouched.
cURL
curl -X DELETE "https://api.youraiconnector.com/v1/team/members/uid_sam" \
-H "Authorization: Bearer FIREBASE_ID_TOKEN"
Response
{
"success": true,
"message": "Team member removed successfully."
}
Removal is permanent from your side: a removed member cannot be reactivated with the update endpoint — invite them again if you change your mind. Their email is also taken off your account’s notification list.
| Status | When |
|---|---|
400 |
You tried to remove the account owner. |
403 |
Their access is broader than yours. |
404 |
No such team member. |
Limiting what a member can see
Three optional fields, accepted on add, update and invite, decide how much of the account a person sees. They stack: a member limited on more than one is limited by all of them.
contact_scope — all (the default: every contact and conversation) or assigned (only the ones assigned to them). With assigned, add "contact_scope_unassigned": true to also let them see contacts nobody owns yet.
contact_scope_axes — limits them to named agents, channels or departments:
| Field | Type | Description |
|---|---|---|
agents |
string[] | Agent IDs. They only see chats routed to one of these agents. Max 200. |
channels |
string[] | Channel names — whatsapp, whatsapp_web, sms, instagram, instagram_private, messenger, facebook, chat_widget, telegram, line, viber, tiktok, imessage, email, linkedin, skool, custom, custom_channel. Max 200. |
departments |
string[] | Department IDs (see Departments). They only see leads filed under them. Max 200. |
include_unrouted |
boolean | With agents set, also show chats that no agent handles. Off by default. Ignored when agents is empty. |
include_undepartmented |
boolean | With departments set, also show chats that are in no department. Off by default. Ignored when departments is empty. |
Agent and department IDs are not checked when you save them — an ID that does not exist simply matches nothing, which shows up as an empty inbox rather than an error. Channel names are checked: an unrecognised one is rejected with 400.
None of these three can be set on the account owner — that request is refused with 400.
List invitations
GET /team/invites
The invitations you have sent, newest first, so you can see who has not accepted yet.
Query parameters
| Parameter | Required | Description |
|---|---|---|
status |
No | Only return invites in this state — pending, accepted, declined, cancelled or expired. |
cURL
curl "https://api.youraiconnector.com/v1/team/invites?status=pending" \
-H "Authorization: Bearer FIREBASE_ID_TOKEN"
Response
{
"success": true,
"invites": [
{
"id": "inv_abc123",
"account_owner_uid": "owner_uid_123",
"account_owner_display_name": "Acme Ltd",
"invitee_email": "sam@example.com",
"invitee_uid": null,
"role": "editor",
"permission_overrides": [],
"status": "pending",
"created_by": "owner_uid_123",
"created_at": "2026-06-10T12:00:00.000Z",
"expires_at": "2026-06-17T12:00:00.000Z",
"responded_at": null
}
]
}
The invitation token is never returned — it only ever exists in the email that was sent.
Send an invitation
POST /team/invites
Emails someone an invitation to join your team. This is the usual way to add a teammate: they click the link, sign in as themselves, and accept. If they do not have a Your AI Connector account yet, one is created for them and the email walks them through setting a password.
Request fields
| Field | Required | Description |
|---|---|---|
email |
Yes | Where to send the invitation. |
role |
Yes | admin, editor or viewer. |
permission_overrides |
No | Per-area exceptions, applied the moment they accept. |
contact_scope |
No | Applied when they accept. |
contact_scope_unassigned |
No | Applied when they accept. |
contact_scope_axes |
No | Applied when they accept. |
sub_account_access |
No | Agencies only. Applied when they accept. |
Setting the permissions up front means you do not have to edit the member afterwards — everything is copied onto their membership when they accept.
cURL
curl -X POST "https://api.youraiconnector.com/v1/team/invites" \
-H "Authorization: Bearer FIREBASE_ID_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "email": "sam@example.com", "role": "editor" }'
JavaScript
const res = await fetch("https://api.youraiconnector.com/v1/team/invites", {
method: "POST",
headers: {
Authorization: `Bearer ${idToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ email: "sam@example.com", role: "editor" }),
});
const { invite_id } = await res.json();
Response — 201 Created
{
"success": true,
"invite_id": "inv_abc123",
"message": "Team invite sent successfully."
}
Things to plan for
- Invitations expire after 7 days. An expired one can be resent, which starts a fresh 7 days.
- Pending invitations hold a seat. Unlike adding a member directly, the seat check here counts active members plus pending invitations, so an account with all seats spoken for is refused before the email goes out.
- 20 invitations per day, counted per account across both sending and resending.
| Status | When |
|---|---|
400 |
email is missing or the role is invalid. |
403 |
You do not have permission to manage the team, or you tried to grant access above your own. |
409 |
A pending invitation for that email already exists, or that person is already on your team. |
429 |
Your plan’s team seats are full, or you have hit the 20-invitations-a-day limit. The error message says which. |
Cancel an invitation
DELETE /team/invites/{inviteId}
Withdraws an invitation before it is accepted. The link in the email stops working.
cURL
curl -X DELETE "https://api.youraiconnector.com/v1/team/invites/inv_abc123" \
-H "Authorization: Bearer FIREBASE_ID_TOKEN"
Response
{
"success": true,
"message": "Team invite cancelled."
}
Both pending and expired invitations can be cancelled. An invitation that was already accepted, declined or cancelled returns 400; one that is not yours returns 403; an unknown ID returns 404.
Resend an invitation
POST /team/invites/{inviteId}/resend
Sends the invitation email again — for when it was missed or went to spam. Works on pending and expired invitations, and resets the expiry to 7 days from now.
cURL
curl -X POST "https://api.youraiconnector.com/v1/team/invites/inv_abc123/resend" \
-H "Authorization: Bearer FIREBASE_ID_TOKEN"
Response
{
"success": true,
"message": "Team invite resent successfully."
}
The new email carries a new link, and the old link keeps working too, so a person who finds the first email later is not stuck. Resending counts against the same 20-a-day limit as sending, and reviving an expired invitation re-checks your seats — a full plan is refused with 429.
Accept an invitation
POST /team/invites/accept
Accepts an invitation with the token from the invitation email, joining the signed-in person to that account’s team.
This is an act of your own identity. Sign in as yourself — it is deliberately refused with
403while you are working inside somebody else’s account.
Request fields
| Field | Required | Description |
|---|---|---|
invite_token |
Yes | The token from the invitation email link. |
cURL
curl -X POST "https://api.youraiconnector.com/v1/team/invites/accept" \
-H "Authorization: Bearer FIREBASE_ID_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "invite_token": "1f4c…" }'
Response
{
"success": true,
"team_member_id": "owner_uid_123_uid_sam",
"account_owner_uid": "owner_uid_123",
"message": "Team invite accepted successfully."
}
| Status | When |
|---|---|
400 |
invite_token is missing, or the invitation is for your own account. |
403 |
The session is working inside another account, or the invitation was sent to a different email address than the one you are signed in with. |
404 |
The invitation does not exist or has already been used. |
429 |
The account’s seats filled up between the invitation and your acceptance. |
504 |
The invitation has expired. Ask the sender to resend it. |
Decline an invitation
POST /team/invites/decline
Declines an invitation with the token from the email. Like accepting, this is an act of your own identity and is refused while you are working inside another account.
cURL
curl -X POST "https://api.youraiconnector.com/v1/team/invites/decline" \
-H "Authorization: Bearer FIREBASE_ID_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "invite_token": "1f4c…" }'
Response
{
"success": true,
"message": "Team invite declined."
}
Departments
A department is a named group of your team — Sales, Customer support, HR. It gives a lead an owner team, can claim new conversations on its own, and can be used to limit what a member sees.
These four endpoints take an API key. Unlike the rest of this page, they authenticate like every other endpoint in the API (see Authentication). A signed-in session works too: reading needs
contactsatview, and creating, changing or deleting needsteam_managementatedit.
The department object
| Field | Type | Description |
|---|---|---|
id |
string | The department’s ID. Use it in contact_scope_axes.departments and in the paths below. |
name |
string | What the team is called. Up to 60 characters, unique on the account. |
color |
string | null | Accent colour as #rrggbb, or null. |
member_uids |
string[] | The team members in this department. May include the account owner. |
auto_assign_enabled |
boolean | Whether a lead filed under this department is also handed to someone on it. false means the department works from a shared queue. |
routing_agents |
string[] | New conversations handled by these AI Agents are filed under this department automatically. Empty means no agent rule. |
routing_channels |
string[] | New conversations on these channels are filed here automatically. Empty means no channel rule. |
created_by |
string | null | Who created it. |
When both routing_agents and routing_channels are set, a conversation has to match both to be filed here — that is how you give one team “the support agent, but only on WhatsApp”.
An account can have up to 50 departments.
List departments
GET /team/departments
curl "https://api.youraiconnector.com/v1/team/departments?apiKey=YOUR_API_KEY"
Response
{
"success": true,
"departments": [
{
"id": "dep_abc123",
"name": "Sales",
"color": "#2f6fed",
"member_uids": ["uid_alice", "uid_bob"],
"auto_assign_enabled": true,
"routing_agents": [],
"routing_channels": ["whatsapp"],
"created_by": "owner_uid_123"
}
]
}
Create a department
POST /team/departments
Request fields
| Field | Required | Description |
|---|---|---|
name |
Yes | Up to 60 characters. Must not match an existing department. |
color |
No | #rrggbb hex, or null. |
member_uids |
No | Who is on it. Every UID must be the account owner or an active team member. |
auto_assign_enabled |
No | Defaults to true. |
routing_agents |
No | Agent IDs whose new chats land here. |
routing_channels |
No | Channel names whose new chats land here — same vocabulary as contact_scope_axes.channels. |
cURL
curl -X POST "https://api.youraiconnector.com/v1/team/departments?apiKey=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Sales",
"color": "#2f6fed",
"member_uids": ["uid_alice", "uid_bob"],
"routing_channels": ["whatsapp"]
}'
JavaScript
const res = await fetch("https://api.youraiconnector.com/v1/team/departments", {
method: "POST",
headers: {
"X-API-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Sales",
color: "#2f6fed",
member_uids: ["uid_alice", "uid_bob"],
routing_channels: ["whatsapp"],
}),
});
const { department } = await res.json();
Python
import requests
res = requests.post(
"https://api.youraiconnector.com/v1/team/departments",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"name": "Sales",
"color": "#2f6fed",
"member_uids": ["uid_alice", "uid_bob"],
"routing_channels": ["whatsapp"],
},
)
department = res.json()["department"]
Response — 201 Created
{
"success": true,
"department": {
"id": "dep_abc123",
"name": "Sales",
"color": "#2f6fed",
"member_uids": ["uid_alice", "uid_bob"],
"auto_assign_enabled": true,
"routing_agents": [],
"routing_channels": ["whatsapp"],
"created_by": "owner_uid_123"
}
}
| Status | When |
|---|---|
400 |
name is missing or too long, color is not #rrggbb, a channel name is not recognised, a listed UID is not an active member of this team, or you already have 50 departments. |
409 |
A department with that name already exists. |
Update a department
PATCH /team/departments/{departmentId}
Changes a department. Only the fields you send are changed.
curl -X PATCH "https://api.youraiconnector.com/v1/team/departments/dep_abc123?apiKey=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "member_uids": ["uid_alice"], "auto_assign_enabled": false }'
Response
{
"success": true,
"department": {
"id": "dep_abc123",
"name": "Sales",
"color": "#2f6fed",
"member_uids": ["uid_alice"],
"auto_assign_enabled": false,
"routing_agents": [],
"routing_channels": ["whatsapp"],
"created_by": "owner_uid_123"
}
}
Sending no recognised fields returns 400; an unknown department returns 404; a name that clashes with another department returns 409.
Delete a department
DELETE /team/departments/{departmentId}
curl -X DELETE "https://api.youraiconnector.com/v1/team/departments/dep_abc123?apiKey=YOUR_API_KEY"
Response
{
"success": true,
"deleted": "dep_abc123"
}
Deleting a department that someone is limited to is refused. The
400response names the members whose visibility is narrowed to it, so you can re-scope them first. That is deliberate: silently un-limiting them would hand them your whole customer base with nothing to show it happened.
Contacts filed under a deleted department are not rewritten — they simply stop showing a department, and the next time you file them it sticks.
Check your own permissions
GET /team/permissions
Returns what the signed-in person is allowed to do in the account they are currently working in. Use it to hide buttons a member cannot use, instead of letting them discover the limit through an error.
cURL
curl "https://api.youraiconnector.com/v1/team/permissions" \
-H "Authorization: Bearer FIREBASE_ID_TOKEN"
Response — the account owner
{
"success": true,
"role": "owner",
"is_team_mode": false,
"permissions": {
"campaigns": "full",
"contacts": "full",
"messages": "full",
"appointments": "full",
"settings": "full",
"billing": "full",
"team_management": "full",
"analytics": "full",
"phone_numbers": "full",
"integrations": "full",
"faqs": "full",
"daily_summaries": "full"
}
}
Response — a team member working inside an account
{
"success": true,
"role": "editor",
"is_team_mode": true,
"permissions": { "campaigns": "edit", "billing": "none", "…": "…" },
"member": {
"uid": "uid_sam",
"email": "sam@example.com",
"display_name": "Sam Rivera",
"account_owner_uid": "owner_uid_123"
}
}
role is owner when the signed-in person is the account owner; otherwise it is their team role. member is only present in team mode, and carries contact_scope, contact_scope_unassigned and contact_scope_axes when their membership has them.
Session tokens
Five endpoints mint a one-time sign-in token for switching between accounts. They all answer the same way:
{
"success": true,
"customToken": "eyJhbGciOi…"
}
The token is exchanged for a session with the Firebase client SDK. It is not an API key and cannot be sent as one, which is why these endpoints are only useful inside a first-party app.
| Endpoint | What it does | Body |
|---|---|---|
POST /team/tokens/team-member |
Lets a team member start working inside an account they belong to. | account_owner_uid (required) |
POST /team/tokens/return-from-team |
Takes them back out, to their own account. | — |
POST /team/tokens/assist |
Lets Your AI Connector staff open a customer’s account to help. Staff only. | customerUid |
POST /team/tokens/return-to-admin |
Ends an assist session and returns staff to their own account. | — |
POST /team/tokens/agency-assist |
Lets an agency open one of its client sub-accounts — or, called without one, return to the agency account. | subAccountUid (optional) |
Each refuses with 403 when the session is not entitled to it: not a member of that account, not staff, that sub-account is not on your agency or has not been granted to you, or the session is not currently in the mode the endpoint ends.
Assign a platform role
POST /team/users/{targetUid}/role
Sets a user’s platform role — User, Dev, Support or Agency. This is not team membership: it is what kind of Your AI Connector account somebody has.
This endpoint is restricted to Your AI Connector staff, and the last remaining Dev cannot be demoted. Listed for completeness; it is not part of managing your own team.
{
"success": true,
"targetUid": "uid_sam",
"role": "Agency",
"claimUpdated": true
}
| Status | When |
|---|---|
400 |
role is missing or not one of the four, or this would remove the last Dev. |
403 |
You are not staff, or the session is working inside another account. |
404 |
No such user. |
Team API errors
Team endpoints return the standard error envelope, always with error_code alongside the HTTP status:
{
"success": false,
"error_code": 403,
"error": "Cannot grant \"full\" access to \"billing\" — exceeds your own permissions."
}
| Status | When it happens on a team endpoint |
|---|---|
400 |
A required field is missing or invalid, or the action is not allowed in this state (reactivating a removed member, suspending the owner, deleting a department someone is limited to). |
401 |
You sent an API key to an endpoint that needs a signed-in person — see Authentication. |
403 |
You do not have team_management permission, the change exceeds your own access, or the action is refused while working inside another account. |
404 |
No such member, invitation, department or user. |
409 |
Already a team member, a pending invitation already exists, or a department by that name exists. |
429 |
Team seats are full, the 20-invitations-a-day limit is reached, or you hit the API rate limit. |
504 |
The invitation you tried to accept has expired. |
The shared codes every endpoint can return — 429 (rate limit) and 500 — are listed with retry guidance in Errors & Pagination.
Related
- Team Management — the same features in the dashboard, with screenshots.
- Authentication — how to send a Firebase ID token instead of an API key.
- Contacts API — the contacts a member’s visibility limits apply to.