Skip to content
AISARAISAR
REST API

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:0008: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

MethodPath
GET/v1/teams

List teams

POST/v1/teams

Create a team

GET/v1/teams/{team}

Get a team

PATCH/v1/teams/{team}

Update a team

DELETE/v1/teams/{team}

Delete a team

GET/v1/companies/current/members

List current company members

POST/v1/companies/current/members/{memberId}/role

Change a member's role

POST/v1/companies/current/members/{memberId}/revoke

Revoke a member's access

GET/v1/users

List current company users (excluding service accounts)

POST/v1/invitations

Send an invitation (email + role)

POST/v1/invitations/{guid}/accept

Accept an invitation

POST/v1/invitations/{guid}/decline

Decline an invitation

POST/v1/invitations/{invitationId}/role

Change the role on a pending invitation

POST/v1/invitations/{invitationId}/revoke

Revoke an invitation

DELETE/v1/invitations/{invitationId}

Delete an invitation

GET/v1/me

Current user profile (roles and permissions)

POST/v1/me

Update the profile

POST/v1/me/password

Change the password

PUT/v1/me/availability

Set availability status (online, away, offline)

PUT/v1/me/current-company

Switch the active company (single field shared by all sessions/tokens)

GET/v1/me/notification-preferences

Notification preference snapshot: channels (mail/push/whatsapp) + categories + push triggers (push_triggers) + quiet hours (quiet_hours)

PUT/v1/me/notification-preferences

Partially update preferences (channels.mail/push/whatsapp, categories.*, push_triggers.*, quiet_hours)

POST/v1/me/phone/send-otp

Send a verification code over WhatsApp to the saved profile number

POST/v1/me/phone/verify

Check the code and mark the profile number verified

GET/v1/companies/current/members/telephony

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/enable

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/disable

Disable telephony for a member: removes both SIP profiles.

GET/v1/companies/current/members/{memberId}/telephony/credentials

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-password

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/extension

Assign/clear a member's internal extension (3–4 digits, unique per company); requires telephony to be enabled.

GET/v1/softphone/credentials

The calling operator's own browser SIP profile credentials. Requires a session/mobile token — a company api_token gets 403.

POST/v1/softphone/reset-password

Rotate the operator's own browser SIP password.

GET/v1/softphone/external-credentials

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-password

Rotate the operator's own native SIP password; also rotates the browser one.

GET/v1/softphone/colleagues

Telephony-enabled colleagues in the company (dialer directory) with extension and availability.

POST/v1/softphone/internal-call

An internal call to a colleague by callee_user_id or internal extension.

POST/v1/softphone/external-call

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

bash
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

json
{
  "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

bash
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

json
{
  "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

bash
curl https://api.aisar.app/v1/me \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Response

json
{
  "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

bash
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

json
{
  "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

bash
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

json
{
  "data": {
    "message": "Password updated."
  }
}

Update availability status

Request

bash
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

json
{
  "status": "ok"
}

Switch the active company

Request

bash
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

json
{
  "data": {
    "id": 12,
    "current_company_id": 4,
    "roles": ["manager"],
    "permissions": ["company.view", "conversation.view"]
  }
}

Company users

Request

bash
curl https://api.aisar.app/v1/users \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Response

json
{
  "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

bash
curl https://api.aisar.app/v1/me/notification-preferences \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Response

json
{
  "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

bash
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

json
{
  "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

bash
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

json
{
  "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

bash
curl https://api.aisar.app/v1/companies/current/members/telephony \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Response

json
{
  "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

bash
curl -X POST https://api.aisar.app/v1/companies/current/members/13/telephony/enable \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Response

json
{
  "data": {
    "message": "Telephony enabled.",
    "telephony": {
      "user_id": 13,
      "sip_username": "agent-13-web",
      "status": "active"
    }
  }
}

Member's SIP credentials (admin view)

Request

bash
curl https://api.aisar.app/v1/companies/current/members/13/telephony/credentials \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Response

json
{
  "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"
  }
}