Team and access
Who can use this feature?
- An admin on Pro or Plus can create the calling API key. These Public API routes require explicit scopes; legacy unscoped keys are refused.
- Member writes, every API-key management route, and both AI-client routes also check that the key's creator is currently an active admin in the same shop. Member reads do not perform this additional admin check.
- Key management checks the current Pro / Plus entitlement. Invitations and activation also enforce the shop's team plan and seat allowance.
All paths use the Public API base URL. Send X-Api-Key on every request. Send Content-Type: application/json when the table shows a JSON body, including the AI-client DELETE request. The caller's key determines the shop; a body-supplied shop ID does not switch stores.
Authority and limits
The acting admin is the member recorded when the calling key was created. The API reloads that member and requires active: true and role: "admin" in the same shop. A missing creator, a removed or inactive member, or a changed role returns 403 KEY_OWNER_NOT_ADMIN. A stored active: null does not pass this check. Possessing a management scope alone is not enough.
This check applies to POST /members, member PATCH/status/delete/resend/notification writes, all four /api-keys operations, and both /ai-clients operations. It does not apply to GET /members, GET /members/{id}, or GET /members/{id}/notifications.
| Operation | Additional limit |
|---|---|
POST /members | 20 invitations per hour per shop |
PATCH /members/{id}/notifications | 30 updates per hour per calling API key |
POST /api-keys | 10 creations per hour per shop; at most 5 active keys in the shop |
The hourly limits are additional fixed-window counters. The shared Public API limits still apply: 120 requests/minute per key and 300/minute per source IP. POST /members/{id}/resend-invite uses those shared limits; it does not share the 20-invitation hourly counter. A successful invite or resend means the send operation completed, not that the recipient has opened or accepted the invitation.
Read members
All three reads take no body and return 200. Use a key created on Pro / Plus with the scope in the table. members:write implies members:read, but does not grant conversations:read for the list.
| Method | Path | Scope | Request example | Response example |
|---|---|---|---|---|
| GET | /members | conversations:read | No body | {"data":[]} |
| GET | /members/{id} | members:read | id=member_123; no body | {"data":{"id":"member_123","email":"[email protected]","role":"staff","active":true}} |
| GET | /members/{id}/notifications | members:read | id=member_123; no body | {"data":{"pushTopics":[],"emailTopics":[],"forwardTopicEmails":[],"enabledSoundOpt":"first"}} |
Member examples are excerpts. Member reads return the allowlisted fields id, email, firstName, lastName, fullName, avatarUrl, role, active, enableAwayMode, and createdAt when stored. Pending invitations can have active: null. They do not return passwords, authentication IDs, device tokens, or a complete permissions map. A missing or foreign member returns 404 NOT_FOUND.
Notification reads return pushTopics, emailTopics, forwardTopicEmails, enabledSoundOpt, topicEmail, and verifiedTopicEmail when stored. Device push tokens are not returned.
Invite and resend
Both routes require members:write, a key created on Pro / Plus, and the acting-admin check. Invitations require the current team entitlement or purchased additional seats and an available seat.
| Method | Path | JSON request | Success response |
|---|---|---|---|
| POST | /members | {"email":"[email protected]","firstName":"Example","lastName":"Teammate","permissions":{"inbox.conversations":"view"}} | 201 {"success":true,"id":"member_123"} |
| POST | /members/{id}/resend-invite | No body | 200 {"success":true} |
email must be valid and is lowercased after validation; remove surrounding whitespace before sending. firstName and lastName are optional strings. permissions is optional and uses the catalog below. The new record is always role: "staff", with pending activation (active: null); sending role, active, or an authentication ID does not grant those fields.
Pending invitations and active human members occupy seats when inviting. The limit is the plan's seats plus purchased additional seats. The invitation link expires after 24 hours; resend to issue a new link. Reinviting an existing email through POST returns 409 MEMBER_EXISTS, including when that member is still pending. Use resend instead. Resending to an already active member returns 409 ALREADY_ACTIVE; a missing or foreign member returns 404 NOT_FOUND.
Invalid email, name types or permissions return 400 INVALID_PARAMS. Missing team entitlement returns 403 PLAN_REQUIRED; a full seat allowance returns 409 MEMBER_LIMIT_EXCEEDED. Mail failures are server failures and can occur after the pending member has been saved. Read the member list before retrying creation.
curl -X POST 'https://app.chatty.net/v1/members' \
-H 'X-Api-Key: sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
-H 'Content-Type: application/json' \
-d '{"email":"[email protected]","firstName":"Example","lastName":"Teammate"}'Edit, activate or remove staff
These routes require members:write, a key created on Pro / Plus, and the acting-admin check. They reject every admin target with 403 ADMIN_TARGET_FORBIDDEN, not only the last admin. The acting admin therefore cannot use them to edit, deactivate or delete themselves. Role changes and ownership transfer are not supported by these routes.
| Method | Path | JSON request | Success response |
|---|---|---|---|
| PATCH | /members/{id} | {"firstName":"Updated","enableAwayMode":true} | 200 {"success":true} |
| POST | /members/{id}/status | {"active":false} | 200 {"success":true} |
| DELETE | /members/{id} | No body | 200 {"success":true} |
PATCH accepts these fields only:
| Fields | Accepted values |
|---|---|
firstName, lastName, fullName, storeAlias | Strings |
enabledAIResolvedReport, enabledChatPerformanceReport, enableAwayMode | Booleans |
unresolvedReportRecipients, performanceReportRecipients | Up to 10 valid email addresses; an array or comma-separated string. Stored as a comma-separated string |
permissions | A permission map using the catalog below |
Unsupported fields, including email, avatarUrl, role, and active, are ignored by profile PATCH. If no allowed field remains, it returns 400 INVALID_PARAMS. Invalid allowed values also return 400 INVALID_PARAMS. Changing names does not imply that every other name field is recomputed; send the intended fields explicitly.
The status body requires a boolean active. Activating a member rechecks the active-seat allowance and can return 409 MEMBER_LIMIT_EXCEEDED. Deactivation revokes that member's OAuth connections and Firebase sessions and disables their push delivery. It does not delete the member or their conversations.
Deletion revokes sessions before removing the staff record. It then attempts to reassign at most 500 open conversations active within the previous 30 days, or queues them for assignment when nobody is eligible. Reassignment is best effort: a failure is logged after removal and does not undo the deletion or turn it into a failed response. Do not assume all historical assignments were migrated. Missing or foreign targets return 404 NOT_FOUND.
Member permission catalog
These UI permissions are separate from API-key scopes. Only an admin may change permissions. These public staff-edit routes reject the caller and every other admin target with 403 ADMIN_TARGET_FORBIDDEN; admins retain full access. A permissions object replaces the stored map; send the full intended map. Missing entries follow the permission catalog's defaults or fallback rules, so omission does not mean none.
| Allowed levels | Permission keys |
|---|---|
none, view, edit | settings.appearance, settings.availability, settings.shortcuts, settings.satisfaction, settings.reports, settings.connectedStores, settings.channels.email, settings.channels.facebook, settings.channels.whatsapp, settings.channels.integrations, settings.teamMembers, settings.billing, inbox.conversations, inbox.contacts |
none, view, edit | ai.training.knowledgeBase, ai.training.catalog, ai.training.knowledgeFiles, ai.training.shopMemory, ai.behavior.instructions, ai.behavior.skills, ai.behavior.scenarios, ai.testing, ai.settings |
none, view, edit | ticket.tickets, ticket.contacts, ticket.helpcenter, ticket.ai, ticket.improvements, ticket.automation, ticket.settings |
none, edit | inbox.conversations.delete, inbox.contacts.export, ticket.tickets.delete, ticket.messages |
none, view | ticket.reports |
An unknown permission key or unsupported level returns 400 INVALID_PARAMS. Retired unknown keys already present on the target may be dropped during validation instead. Assigning a member permission does not create a corresponding public API endpoint or API-key scope.
Update notification preferences
PATCH /members/{id}/notifications requires members:write, a key created on Pro / Plus, and the acting-admin check. Unlike staff profile/status/delete operations, notification preferences can target an admin, including the acting admin.
| Field | Meaning |
|---|---|
pushTopics | Array of topic strings. Supplying it for a member whose active is not true returns 409 MEMBER_INACTIVE, even for an empty array |
emailTopics | Array of topic strings; may be edited for an inactive member |
enabledSoundOpt | all, first, or none |
topicEmail | Valid notification email address. A changed address can require email verification |
forwardTopicEmails | Email array or comma-separated string, normalized to an array. Changed values allow at most 10 valid addresses; [] clears forwards |
Allowed topic names for both arrays:
conversation-assigned
new-message-assigned
conversation-mentioned
conversation-assigned-others
new-message-assigned-others
conversation-assigned-chatbot
new-message-unassigned
new-unassigned-from-ai
agent-unassigned
no-repliesUnknown topic strings are filtered out; non-string array items are rejected. pushTokens, token platform data, and verifiedTopicEmail are not writable here. Unsupported fields are ignored; if no notification field remains, the response is 400 INVALID_PARAMS. Invalid email, sound option or array types also return 400 INVALID_PARAMS.
curl -X PATCH 'https://app.chatty.net/v1/members/member_123/notifications' \
-H 'X-Api-Key: sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
-H 'Content-Type: application/json' \
-d '{"emailTopics":["conversation-assigned"],"enabledSoundOpt":"first","topicEmail":"[email protected]"}'Success is 200 {"success":true}. Updating topicEmail does not verify it; delivery retains the previously verified address until verification completes. The write can send a verification email and sync conversation notification settings. A missing or foreign member returns 404 NOT_FOUND.
Manage API keys
All four routes require api_keys:write, the acting-admin check, and a current Pro / Plus plan. Grant api_keys:write to the calling key through Settings → General → Manage keys; it cannot be delegated by these endpoints.
| Method | Path | Request | Success response |
|---|---|---|---|
| GET | /api-keys | No body | 200 {"data":[]} |
| POST | /api-keys | {"title":"CRM reader","scopes":["customers:read"]} | 201 {"data":{"id":"key_123","title":"CRM reader","maskedKey":"sk_xxxx...xxxx","scopes":["customers:read"],"apiVersion":"2026-08-17","secret":"sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}} |
| PATCH | /api-keys/{id} | {"scopes":["customers:read"]} | 200 {"success":true,"id":"key_123"} |
| DELETE | /api-keys/{id} | No body | 200 {"success":true,"id":"key_123"} |
GET lists active keys only. Its metadata fields are id, maskedKey, title, scopes, apiVersion, and createdAt when stored. It can include legacy keys, but these cannot be changed or revoked through the public routes. Use the dashboard for them.
For POST, title is a nonblank string of at most 50 characters before trimming. scopes must be an array of known scope strings when supplied; omitted scopes default to [], and duplicate scopes are removed. The new key keeps the calling key's creator identity and the current API version. Creation returns the secret once; createdAt is not included in that creation response. Store the secret immediately. GET, PATCH and DELETE cannot recover it.
The requested scopes must fit within the caller's effective access. For example, a caller with customers:write can create a key with customers:read. It cannot grant an unrelated permission. Requests containing api_keys:write return 400 INVALID_PARAMS even if the caller has that scope.
PATCH requires scopes and replaces them; it does not change the title, secret or pinned version. Before PATCH or DELETE, the target's current scopes must also fit within the caller's access. A broader target returns 403 FORBIDDEN even if the proposed replacement would be narrower. Missing, foreign, or already revoked targets return 404 NOT_FOUND; legacy targets return 403 FORBIDDEN.
Clearing scopes on an existing key revokes it. PATCH with {"scopes":[]} revokes the target. By contrast, POST with empty or omitted scopes creates an active key with no granted scopes. The calling key cannot PATCH or DELETE itself: both return 409 CANNOT_REVOKE_SELF.
Keys have no automatic expiry or expiry field in these requests. Revocation disables future authenticated requests while keeping the key's identity for audit records. Replacement uses create → switch the integration → revoke the old key, within the 5-active-key limit. Losing the current key-management plan returns 403 PLAN_REQUIRED; exceeding the active-key limit returns 409 KEY_LIMIT_REACHED.
List and disconnect AI clients
Both routes require members:write, a key created on Pro / Plus, and the acting-admin check. They manage existing merchant OAuth connections, not API keys or new OAuth registrations.
| Method | Path | Request | Success response |
|---|---|---|---|
| GET | /ai-clients | No body | 200 {"data":{"serverUrl":"https://app.chatty.net/mcp","connections":[]}} |
| DELETE | /ai-clients | {"clientId":"client_123","memberId":"member_123"} | 200 {"success":true} |
GET groups unrevoked grants by the exact pair of client and connecting member. Each group includes clientId, clientName, clientHost, connectedByMemberId, connectedByName, createdAt, lastUsedAt, and scopes when available. scopes contains description objects (value, label, description), not bare strings. Groups combine unique scopes, the earliest creation time and latest use time. No access tokens, refresh tokens, or internal connection IDs are returned.
To disconnect, copy clientId and connectedByMemberId from one group; send the latter as body field memberId. Both must be nonblank strings. They are treated as opaque identifiers, so preserve them exactly rather than URL-normalizing, trimming or constructing them. Do not send connectedByName or an internal connection ID.
The DELETE revokes all matching active grants for that client/member pair within the calling key's shop. A valid pair with no matching grants still returns 200 {"success":true}; it is not a 404. A missing or malformed identifier returns 400 INVALID_PARAMS. Repeating the same disconnect is safe. Reconnecting requires the normal OAuth consent flow.
Shared errors
Errors from validation and guards use this shape:
{"error":{"code":"KEY_OWNER_NOT_ADMIN","message":"The API key owner must be an active shop admin"}}| HTTP | Code | Meaning |
|---|---|---|
| 400 | INVALID_PARAMS | Invalid body, field type, permission level, delegated scope or client identifier |
| 400 | INVALID_API_VERSION | Invalid version header |
| 401 | UNAUTHENTICATED / INVALID_API_KEY | Missing, unknown or revoked calling key |
| 403 | FORBIDDEN | Missing route scope or target/requested key scopes exceed caller access |
| 403 | KEY_OWNER_NOT_ADMIN | Key creator is not currently an active admin in this shop |
| 403 | PLAN_REQUIRED | Current key-management or team entitlement is missing |
| 403 | ADMIN_TARGET_FORBIDDEN | Staff-management request targets an admin |
| 404 | NOT_FOUND | Missing or foreign member/key; revoked key targets are also missing |
| 409 | MEMBER_EXISTS | Invite email already belongs to a member in this shop |
| 409 | MEMBER_LIMIT_EXCEEDED | Invite or activation exceeds the seat allowance |
| 409 | ALREADY_ACTIVE | An active member cannot receive a resend invitation through this route |
| 409 | MEMBER_INACTIVE | Push topics were supplied for an inactive/pending member |
| 409 | KEY_LIMIT_REACHED | The shop already has 5 active keys |
| 409 | CANNOT_REVOKE_SELF | PATCH/DELETE targets the calling key |
| 429 | RATE_LIMITED | Shared API/IP or operation-specific hourly budget exceeded |
Unexpected service or mail failures may be server errors rather than this coded envelope. A write may already have saved data before a later mail or audit step fails; inspect current state before retrying. These writes record the authenticated key and acting admin, but do not provide a general idempotency key.
Chatty Help Center