Teams & Users
Manage teams (operator grouping + channel access), company members, and email invitations for new users. The group also includes the current-user block (GET /me): the profile with roles and permissions in the active company, availability status, and switching the active company. GET /v1/users lists the company's real (non-service) users. A separate block covers provisioning SIP telephony for operators (companies/current/members/telephony*, admin-gated by company_telephony.manage) and operator self-service (/v1/softphone/*, session/mobile token only).
Roles and membership
Roles are assigned at the company level (admin/manager/agent) via Spatie permissions in teams mode — one user can belong to multiple companies with different roles in each.
Invitations
The public, unauthenticated invitation-resolution steps (resolve/context/intent) are out of scope for this reference — see the invitations guide.
Current user
The current-user block (GET /me) returns the profile together with the caller's roles and permissions in the active company — the starting point for service-token consumers; companion endpoints update the profile, availability status, and active company (switching company returns the profile with the new company's roles and permissions).
The availability status (PUT /v1/me/availability) accepts exactly three writable values: online, away, offline (busy is a system-derived state and cannot be set through this endpoint).
Active company
The active company is stored in a single server-side field, current_company_id, on your account: it is shared across all of your sessions and API tokens (it is not scoped to an individual token), and company-scoped lists (for example GET /v1/conversations) and realtime channel authorization follow it.
So if the same user switches company in another tab, app, or token, every context follows — an embedded chat may suddenly show another company's conversations, and subscriptions to the previous (no-longer-active) company's channels start returning 403. Keep the company you read from and subscribe to in sync with current_company_id.
Notification preferences
GET /v1/me/notification-preferences returns a snapshot with four blocks: channels — boolean master switches per delivery channel (mail, push, whatsapp; a missing key defaults to true), categories — an object per notification category with enabled/label/controllable fields, plus push_triggers and quiet_hours (see below). The transactional category (verification codes, invitations, password resets) is always controllable: false and enabled: true — it cannot be turned off; the remaining categories (onboarding, trial, billing, win_back, engagement) can each be toggled independently.
PUT /v1/me/notification-preferences — a partial update (merge, not replace): send only the channels.*/categories.*/push_triggers.*/quiet_hours.* keys you are changing — every other key, including the other blocks entirely, keeps its current value. channels.whatsapp = false is the only way for a user to opt out of the WhatsApp channel for billing notifications (payment reminders, suspension notices, etc.) — this opt-out is required by Meta's policy for WhatsApp templates and must be honored by the sender.
Both endpoints also carry push_triggers — a per-push-type object of boolean switches: chat_message_new, conversation_assigned, call_missed, call_incoming, company_unassigned. All default to enabled except company_unassigned (a push about a conversation in the company having no assignee — opt-in so it does not flood every operator at once). call_incoming is effectively always delivered regardless of its stored value — an incoming call is a system notification that cannot be suppressed.
quiet_hours is a do-not-disturb window: { "enabled": bool, "start": "HH:MM"|null, "end": "HH:MM"|null }, defaulting to enabled: false with start/end as null. While the window is active, non-critical push notifications (e.g. conversation_assigned) are suppressed — except call_incoming, which quiet hours never affect. The time is interpreted in the user's timezone (users.timezone); if start is greater than end, the window is treated as overnight (wraps past midnight, e.g. 22:00–08:00).
Phone number verification
The profile phone number (users.phone) can be verified with a code delivered over WhatsApp: POST /v1/me/phone/send-otp sends a 6-digit code and POST /v1/me/phone/verify with a {"code": "123456"} body checks it. A verified number is reflected by the phone_verified field in GET /v1/me and is the address used for WhatsApp copies of billing notifications. Changing the number via POST /v1/me clears the verification.
send-otp does not accept a number in the request body — the code always goes to the saved profile number, so save the number first and request the code afterwards. A 200 response carries the masked phone and expires_in (code lifetime in seconds, currently 600). Possible errors: 422 — no number set, or it fails the E.164 format check; 429 — send/verify rate limits exceeded; 409 (verify only) — the number changed between sending and verifying, request a new code; 502 — the message could not be delivered; 503 — the platform-wide daily code-sending budget is exhausted.
Both endpoints are available to session authentication and mobile tokens only. Service accounts, client API tokens, and embed tokens receive 403 — verifying a number stays an action performed by a real user.
Operator telephony
Provisioning SIP telephony for an operator is the step that follows connecting a SIP channel (see the channel-connection guide, "SIP telephony (BYO trunk)" section). A company admin (the company_telephony.manage permission) enables telephony for a specific member via POST /v1/companies/current/members/{memberId}/telephony/enable, which provisions TWO SIP profiles at once: a browser profile agent-{id}-web (for the built-in WebRTC softphone in the AISAR apps, opaque password) and a native profile agent-{id} (for desk SIP phones and apps like Zoiper/MicroSIP/Linphone, typeable password). There is no separate toggle for the native profile — disable removes both profiles together.
The admin endpoints (companies/current/members/telephony*) are called with the company api_token, same as the rest of this group — a partner service account with the admin role (which unconditionally carries company_telephony.manage, see below) can provision operators programmatically. Self-service (/v1/softphone/*) is the opposite: those endpoints require the caller to BE that operator (session authentication or a mobile token — the same pattern as phone verification above), so a company api_token / service account gets 403 here — a service account never has a SIP profile of its own (member provisioning deliberately filters is_service_account = false). Partners also have a server-to-server way to enable telephony for operators without a user session — it is covered in the partner integration documentation.
company_telephony.manage is not a plan gate: since 2026-08-13, the admin role in ANY company unconditionally carries this permission (a backfill migration closed the gap for companies provisioned before the permission existed). The plan gate lives separately — the plan's telephony feature is required for an operator to actually USE calling (TelephonyAccess::availableTo), but not for an admin to manage operators, schemes and channels.
Both ways of reading the native profile's credentials — the admin's GET .../telephony/credentials and the operator's own GET /v1/softphone/external-credentials — go through the same builder (ExternalSipCredentialPresenter), so the response shape cannot drift apart: sip_username, sip_password, host, tcp_port (5060), tls_port (5061), default_transport: "TCP", codecs: ["opus", "PCMU", "PCMA"], media_encryption, registered (whether the SIP registration is live right now — a best-effort ARA check, treated as false on error) and provisioning_uri — a generic sip: URI (sip:user:pass@host:port;transport=tcp) for QR import into Zoiper / MicroSIP / Linphone / Grandstream Wave. Not usable with 3CX — its app only reads its own provisioning-QR format, so those fields must be entered by hand there. The browser (agent-{id}-web) password is opaque: the admin GET .../telephony/credentials never returns it — that endpoint only touches the native profile; POST .../telephony/reset-password (admin), by contrast, DOES return it — the response carries the freshly rotated browser profile's sip_username/sip_password (the same one-time-reveal principle as creating an API token), and silently rotates the native profile too without returning its new password in the same response — the operator sees their new native password via their own /softphone/external-credentials.
POST .../telephony/extension — the operator's internal extension, 3–4 digits (^\d{3,4}$), unique within the company; a blank value clears it. Requires telephony to already be enabled — otherwise 422.
Operator self-service (session/mobile token only): GET /v1/softphone/credentials — the operator's own browser credentials (consumed directly by the built-in WebRTC softphone, not meant for manual entry); POST /v1/softphone/reset-password — rotates them; GET /v1/softphone/external-credentials / POST /v1/softphone/external/reset-password — the same for the native profile, including the provisioning_uri for QR import; GET /v1/softphone/colleagues — telephony-enabled colleagues in the company (dialer directory) with extension and availability; POST /v1/softphone/internal-call — call a colleague by callee_user_id or internal extension; POST /v1/softphone/external-call — an outbound call over the company's SIP trunk to an external number, with an optional contact_id (resolved strictly within the company — a foreign/unknown id is 422, no silent fallback to number-based resolution). external-call shares the call-originate rate limit with POST /v1/calls/originate — 20 requests per minute per user; the same sip_outbound_gated behavior and IP-mode anti-abuse check as /calls/originate also apply here (see the Calls group).
Endpoints
| Method | Path | Summary |
|---|---|---|
| GET | /v1/teamsList teams | List teams |
| POST | /v1/teamsCreate a team | Create a team |
| GET | /v1/teams/{team}Get a team | Get a team |
| PATCH | /v1/teams/{team}Update a team | Update a team |
| DELETE | /v1/teams/{team}Delete a team | Delete a team |
| GET | /v1/companies/current/membersList current company members | List current company members |
| POST | /v1/companies/current/members/{memberId}/roleChange a member's role | Change a member's role |
| POST | /v1/companies/current/members/{memberId}/revokeRevoke a member's access | Revoke a member's access |
| GET | /v1/usersList current company users (excluding service accounts) | List current company users (excluding service accounts) |
| POST | /v1/invitationsSend an invitation (email + role) | Send an invitation (email + role) |
| POST | /v1/invitations/{guid}/acceptAccept an invitation | Accept an invitation |
| POST | /v1/invitations/{guid}/declineDecline an invitation | Decline an invitation |
| POST | /v1/invitations/{invitationId}/roleChange the role on a pending invitation | Change the role on a pending invitation |
| POST | /v1/invitations/{invitationId}/revokeRevoke an invitation | Revoke an invitation |
| DELETE | /v1/invitations/{invitationId}Delete an invitation | Delete an invitation |
| GET | /v1/meCurrent user profile (roles and permissions) | Current user profile (roles and permissions) |
| POST | /v1/meUpdate the profile | Update the profile |
| POST | /v1/me/passwordChange the password | Change the password |
| PUT | /v1/me/availabilitySet availability status (online, away, offline) | Set availability status (online, away, offline) |
| PUT | /v1/me/current-companySwitch the active company (single field shared by all sessions/tokens) | Switch the active company (single field shared by all sessions/tokens) |
| GET | /v1/me/notification-preferencesNotification preference snapshot: channels (mail/push/whatsapp) + categories + push triggers (push_triggers) + quiet hours (quiet_hours) | Notification preference snapshot: channels (mail/push/whatsapp) + categories + push triggers (push_triggers) + quiet hours (quiet_hours) |
| PUT | /v1/me/notification-preferencesPartially update preferences (channels.mail/push/whatsapp, categories.*, push_triggers.*, quiet_hours) | Partially update preferences (channels.mail/push/whatsapp, categories.*, push_triggers.*, quiet_hours) |
| POST | /v1/me/phone/send-otpSend a verification code over WhatsApp to the saved profile number | Send a verification code over WhatsApp to the saved profile number |
| POST | /v1/me/phone/verifyCheck the code and mark the profile number verified | Check the code and mark the profile number verified |
| GET | /v1/companies/current/members/telephonyList the company's real members with their telephony status (enabled, extension, whether the native profile is enabled and currently registered) — for the operators admin table. | List the company's real members with their telephony status (enabled, extension, whether the native profile is enabled and currently registered) — for the operators admin table. |
| POST | /v1/companies/current/members/{memberId}/telephony/enableEnable telephony for a member: provisions the browser (agent-{id}-web) and native (agent-{id}) SIP profiles at the same time. | Enable telephony for a member: provisions the browser (agent-{id}-web) and native (agent-{id}) SIP profiles at the same time. |
| POST | /v1/companies/current/members/{memberId}/telephony/disableDisable telephony for a member: removes both SIP profiles. | Disable telephony for a member: removes both SIP profiles. |
| GET | /v1/companies/current/members/{memberId}/telephony/credentialsA member's native SIP profile credentials for an admin (without logging in as them) — host/ports/codecs/provisioning_uri; the browser password is not returned. | A member's native SIP profile credentials for an admin (without logging in as them) — host/ports/codecs/provisioning_uri; the browser password is not returned. |
| POST | /v1/companies/current/members/{memberId}/telephony/reset-passwordRotate a member's SIP passwords: returns the new browser-profile password and silently rotates the native one too. | Rotate a member's SIP passwords: returns the new browser-profile password and silently rotates the native one too. |
| POST | /v1/companies/current/members/{memberId}/telephony/extensionAssign/clear a member's internal extension (3–4 digits, unique per company); requires telephony to be enabled. | Assign/clear a member's internal extension (3–4 digits, unique per company); requires telephony to be enabled. |
| GET | /v1/softphone/credentialsThe calling operator's own browser SIP profile credentials. Requires a session/mobile token — a company api_token gets 403. | The calling operator's own browser SIP profile credentials. Requires a session/mobile token — a company api_token gets 403. |
| POST | /v1/softphone/reset-passwordRotate the operator's own browser SIP password. | Rotate the operator's own browser SIP password. |
| GET | /v1/softphone/external-credentialsThe operator's own native SIP profile credentials (for a desk phone/Zoiper/MicroSIP) with a provisioning_uri for QR import. | The operator's own native SIP profile credentials (for a desk phone/Zoiper/MicroSIP) with a provisioning_uri for QR import. |
| POST | /v1/softphone/external/reset-passwordRotate the operator's own native SIP password; also rotates the browser one. | Rotate the operator's own native SIP password; also rotates the browser one. |
| GET | /v1/softphone/colleaguesTelephony-enabled colleagues in the company (dialer directory) with extension and availability. | Telephony-enabled colleagues in the company (dialer directory) with extension and availability. |
| POST | /v1/softphone/internal-callAn internal call to a colleague by callee_user_id or internal extension. | An internal call to a colleague by callee_user_id or internal extension. |
| POST | /v1/softphone/external-callAn outbound call to an external number over the company's SIP trunk; optional contact_id (strictly company-scoped); shares the call-originate limit — 20/min. | An outbound call to an external number over the company's SIP trunk; optional contact_id (strictly company-scoped); shares the call-originate limit — 20/min. |
Examples
Invite a new user
Request
curl -X POST https://api.aisar.app/v1/invitations \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"email": "new.agent@example.com",
"role": "agent"
}'Response
{
"data": {
"message": "Invitation sent.",
"invitation": {
"id": 77,
"guid": "b6e2a4d0-1234-4a90-9e21-5d3f0c8a9b11",
"invite_url": "https://my.aisar.app/invitation/b6e2a4d0-1234-4a90-9e21-5d3f0c8a9b11",
"email": "new.agent@example.com",
"role": "agent",
"expires_at": "2026-03-22T10:00:00.000000Z"
}
}
}Create a team
Request
curl -X POST https://api.aisar.app/v1/teams \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Support",
"description": "Customer success team",
"channel_access_mode": "custom",
"channel_ids": [7, 9],
"member_ids": [5, 6]
}'Response
{
"data": {
"message": "Team created.",
"team": {
"id": 3,
"company_id": 1,
"name": "Support",
"description": "Customer success team",
"channel_access": { "mode": "custom", "channel_ids": [7, 9] },
"member_ids": [5, 6],
"created_at": "2026-03-15T10:00:00.000000Z",
"updated_at": "2026-03-15T10:00:00.000000Z"
}
}
}Current user profile and permissions
Request
curl https://api.aisar.app/v1/me \
-H "Authorization: Bearer YOUR_API_TOKEN"Response
{
"data": {
"id": 12,
"name": "Aisha",
"lastname": "Nurlanova",
"email": "aisha@example.com",
"email_verified": true,
"phone": "+77000000001",
"phone_verified": true,
"photo": null,
"country_code": "KZ",
"language": "ru",
"timezone": "Asia/Almaty",
"current_company_id": 3,
"roles": ["admin"],
"permissions": ["company.view", "conversation.view", "message.send"],
"company": { "id": 3, "name": "Acme" },
"memberships": [
{ "company_id": 3, "company_name": "Acme", "roles": ["admin"], "status": "active" }
],
"pending_invitations": []
}
}Update the profile
Request
curl -X POST https://api.aisar.app/v1/me \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Aisha",
"lastname": "Nurlanova",
"phone": "+77000000001",
"country_code": "KZ",
"language": "ru",
"timezone": "Asia/Almaty"
}'Response
{
"data": {
"id": 12,
"name": "Aisha",
"lastname": "Nurlanova",
"email": "aisha@example.com",
"phone": "+77000000001",
"country_code": "KZ",
"language": "ru",
"timezone": "Asia/Almaty",
"current_company_id": 3
}
}Change the password
Request
curl -X POST https://api.aisar.app/v1/me/password \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"current_password": "YOUR_CURRENT_PASSWORD",
"password": "YOUR_NEW_PASSWORD",
"password_confirmation": "YOUR_NEW_PASSWORD"
}'Response
{
"data": {
"message": "Password updated."
}
}Update availability status
Request
curl -X PUT https://api.aisar.app/v1/me/availability \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"availability": "away"
}'Response
{
"status": "ok"
}Switch the active company
Request
curl -X PUT https://api.aisar.app/v1/me/current-company \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"company_id": 4
}'Response
{
"data": {
"id": 12,
"current_company_id": 4,
"roles": ["manager"],
"permissions": ["company.view", "conversation.view"]
}
}Company users
Request
curl https://api.aisar.app/v1/users \
-H "Authorization: Bearer YOUR_API_TOKEN"Response
{
"data": [
{
"id": 12,
"name": "Aisha",
"lastname": "Nurlanova",
"email": "aisha@example.com",
"availability": "online",
"last_login_at": "2026-03-20T09:14:00.000000Z"
},
{
"id": 13,
"name": "Damir",
"lastname": "Serik",
"email": "damir@example.com",
"availability": "away",
"last_login_at": "2026-03-19T18:02:00.000000Z"
}
]
}Notification preference snapshot
Request
curl https://api.aisar.app/v1/me/notification-preferences \
-H "Authorization: Bearer YOUR_API_TOKEN"Response
{
"data": {
"channels": {
"mail": true,
"push": true,
"whatsapp": true
},
"categories": {
"onboarding": { "enabled": true, "label": "Onboarding", "controllable": true },
"trial": { "enabled": true, "label": "Trial", "controllable": true },
"billing": { "enabled": true, "label": "Billing", "controllable": true },
"win_back": { "enabled": true, "label": "Win-back", "controllable": true },
"engagement": { "enabled": false, "label": "Engagement", "controllable": true },
"transactional": { "enabled": true, "label": "Transactional", "controllable": false }
},
"push_triggers": {
"chat_message_new": true,
"conversation_assigned": true,
"call_missed": true,
"call_incoming": true,
"company_unassigned": false
},
"quiet_hours": {
"enabled": false,
"start": null,
"end": null
}
}
}Opt out of the WhatsApp billing-notification channel
Request
curl -X PUT https://api.aisar.app/v1/me/notification-preferences \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"channels": {
"whatsapp": false
}
}'Response
{
"data": {
"channels": {
"mail": true,
"push": true,
"whatsapp": false
},
"categories": {
"onboarding": { "enabled": true, "label": "Onboarding", "controllable": true },
"trial": { "enabled": true, "label": "Trial", "controllable": true },
"billing": { "enabled": true, "label": "Billing", "controllable": true },
"win_back": { "enabled": true, "label": "Win-back", "controllable": true },
"engagement": { "enabled": false, "label": "Engagement", "controllable": true },
"transactional": { "enabled": true, "label": "Transactional", "controllable": false }
},
"push_triggers": {
"chat_message_new": true,
"conversation_assigned": true,
"call_missed": true,
"call_incoming": true,
"company_unassigned": false
},
"quiet_hours": {
"enabled": false,
"start": null,
"end": null
}
}
}Quiet hours and the unassigned-conversation push
Request
curl -X PUT https://api.aisar.app/v1/me/notification-preferences \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"push_triggers": {
"company_unassigned": true
},
"quiet_hours": {
"enabled": true,
"start": "22:00",
"end": "08:00"
}
}'Response
{
"data": {
"channels": {
"mail": true,
"push": true,
"whatsapp": true
},
"categories": {
"onboarding": { "enabled": true, "label": "Onboarding", "controllable": true },
"trial": { "enabled": true, "label": "Trial", "controllable": true },
"billing": { "enabled": true, "label": "Billing", "controllable": true },
"win_back": { "enabled": true, "label": "Win-back", "controllable": true },
"engagement": { "enabled": false, "label": "Engagement", "controllable": true },
"transactional": { "enabled": true, "label": "Transactional", "controllable": false }
},
"push_triggers": {
"chat_message_new": true,
"conversation_assigned": true,
"call_missed": true,
"call_incoming": true,
"company_unassigned": true
},
"quiet_hours": {
"enabled": true,
"start": "22:00",
"end": "08:00"
}
}
}List company operators
Request
curl https://api.aisar.app/v1/companies/current/members/telephony \
-H "Authorization: Bearer YOUR_API_TOKEN"Response
{
"data": [
{
"user_id": 12,
"name": "Aisha Nurlanova",
"email": "aisha@example.com",
"photo": null,
"telephony_enabled": true,
"extension": "101",
"external_enabled": true,
"external_registered": true
},
{
"user_id": 13,
"name": "Damir Serik",
"email": "damir@example.com",
"photo": null,
"telephony_enabled": false,
"extension": null,
"external_enabled": false,
"external_registered": false
}
]
}Enable telephony for a member
Request
curl -X POST https://api.aisar.app/v1/companies/current/members/13/telephony/enable \
-H "Authorization: Bearer YOUR_API_TOKEN"Response
{
"data": {
"message": "Telephony enabled.",
"telephony": {
"user_id": 13,
"sip_username": "agent-13-web",
"status": "active"
}
}
}Member's SIP credentials (admin view)
Request
curl https://api.aisar.app/v1/companies/current/members/13/telephony/credentials \
-H "Authorization: Bearer YOUR_API_TOKEN"Response
{
"data": {
"sip_username": "agent-13",
"sip_password": "Qw7Lm2VtRk4x",
"host": "sip.aisar.app",
"tcp_port": 5060,
"tls_port": 5061,
"default_transport": "TCP",
"codecs": ["opus", "PCMU", "PCMA"],
"media_encryption": "SRTP/SDES (over TLS)",
"registered": false,
"provisioning_uri": "sip:agent-13:Qw7Lm2VtRk4x@sip.aisar.app:5060;transport=tcp"
}
}