# Firetell Developer Documentation > Complete API reference and guides for building voice-powered applications with Firetell — VoIP, Cloud Communications, Call Center, SIP, and Voice AI. --- # Authentication The Firetell API supports two authentication methods, each designed for a different use case: | Method | Scheme | Use Case | | ------------- | --------------- | --------------------------------------------- | | **API Key** | `ApiKey sk-...` | Server-to-server calls from your backend | | **JWT Token** | `Bearer ` | Client-side calls from your web or mobile app | Both methods use the `Authorization` HTTP header. --- ## API Key Authentication (Server-side) Use API Keys to authenticate **server-to-server** requests from your backend. API keys start with the `sk-` prefix and are passed using the `ApiKey` scheme. ``` Authorization: ApiKey sk-your_api_key ``` :::danger **Do NOT use API Keys in client-side code.** API Keys grant **full access** to your workspace and must **never** be embedded in frontend applications — including JavaScript bundles, mobile apps, or any code that runs on the user's device. Attackers can easily extract keys from client-side code and use them to read, modify, or delete your workspace resources. For client-side authentication, use [JWT Tokens](#jwt-authentication-client-side) instead. ::: ### Quick Example ```bash curl -X GET "https://{workspace_id}.firetell.app/api/v1/phone-numbers" \ -H "Authorization: ApiKey sk-your_api_key" ``` ### Getting Your API Key 1. Log in to the [Firetell Console](https://console.firetell.com) 2. Select your workspace 3. Navigate to **Settings** → **API Keys** 4. Click **Create API Key** 5. Enter a descriptive name 6. Copy the generated key (starts with `sk-`) :::caution Your API Key is **only shown once** at creation time. Store it securely — you will not be able to retrieve it again. If lost, delete the key and create a new one. ::: :::tip Each workspace can have up to **100 API keys**. Use descriptive titles like "Production Server", "Staging", or "CRM Integration" to organize your keys. ::: ### Code Examples #### cURL ```bash curl -X GET "https://{workspace_id}.firetell.app/api/v1/phone-numbers" \ -H "Authorization: ApiKey sk-your_api_key" \ -H "Content-Type: application/json" ``` #### Node.js ```javascript const response = await fetch( "https://{workspace_id}.firetell.app/api/v1/phone-numbers", { headers: { Authorization: "ApiKey sk-your_api_key", "Content-Type": "application/json", }, }, ); const data = await response.json(); ``` #### Python ```python import requests response = requests.get( 'https://{workspace_id}.firetell.app/api/v1/phone-numbers', headers={ 'Authorization': 'ApiKey sk-your_api_key', 'Content-Type': 'application/json', }, ) data = response.json() ``` --- ## JWT Authentication (Client-side) Use JWT tokens to authenticate **client-side** requests from your web or mobile application. Your backend signs JWTs using the API Key secret (`sk-...`) with the **HS256** algorithm. ``` Authorization: Bearer eyJhbGciOiJIUzI1NiIs... ``` ### How It Works 1. Your **backend** signs a JWT using your API Key secret (`sk-...`) with HS256 2. Your backend returns the JWT to the **client** application 3. The client includes the JWT in all API requests ``` ┌──────────┐ ┌──────────────┐ │ Client │──(1)──▶ │ Your Backend │ │ (Web / │ │ Signs JWT │ │ Mobile) │◀──(2)── │ with sk-... │ │ │ └──────────────┘ │ │ │ │──(3)──────────────────────────▶ Firetell API │ │ Authorization: (Bearer JWT) └──────────┘ ``` 1. Client requests a token from your backend 2. Your backend signs a JWT locally using the API Key secret and returns it 3. Client calls Firetell API directly with `Bearer ` ### JWT Claims | Claim | Description | Example | | -------------- | -------------------------------------------------------------- | -------------------------- | | `iss` | API Key SID | `sid-your_api_key_sid` | | `sub` | Subject identifier (see Audience Types below) | `agent_123` or `user_abc` | | `aud` | Audience type: `agent-api` or `client-api` | `agent-api` | | `exp` | Expiration timestamp | 24 hours from now | | `domain` | Workspace domain (custom claim) | `yourcompany.firetell.app` | | `call_flow_id` | Visual Call Flow ID (custom claim, **required** for `client-api`) | `cf_abc123xyz789000` | #### Audience Types The `aud` claim determines what type of identity the token represents. Firetell supports two distinct identities: | Audience | `sub` requirement | Use case | | ------------ | ------------------------------------------------------------------------ | -------------------------------- | | `agent-api` | Must be the **username of an existing, active agent** in your workspace | Call center agents, support reps | | `client-api` | Can be **any identifier** you define (e.g. user ID, phone number, email) | End-user clients, in-app calling | --- ### Agent API vs Client API Understanding the difference between these two identities is key to building the right integration. #### Agent API (`agent-api`) Agents are **managed users** created by workspace admins via [Console](https://console.firetell.com) or the [Agents API](/docs/rest-api/workspace-api/agents). They represent real people in your organization (support reps, sales agents, operators). | Feature | Details | | ---------------------- | ----------------------------------------- | | **Created by** | Workspace admin (Console or API) | | **Username** | Fixed, unique within workspace | | **Quantity** | Limited by your workspace plan | | **PSTN calls** | ✅ Outbound and inbound via phone numbers | | **Call routing** | ✅ Queues, IVR, team-based routing | | **Internal calls** | ✅ Agent ↔ Agent, Agent ↔ Client | | **Call Center Portal** | ✅ Web, iOS, Android app | | **Presence** | ✅ Online/offline/busy state | | **Teams** | ✅ Assigned to teams for routing | :::info **Agent Limits** The number of agents you can create depends on your workspace plan. To view your current limits: **Console** → **Workspace Settings** → **Limits**. Need more agents? Upgrade your plan or contact [enterprise@firetell.com](mailto:enterprise@firetell.com). ::: :::note **Device Session Limit** For agents logging in via the official portal endpoint (`/auth/login`), each agent is allowed a maximum of **5 concurrent active device sessions** (e.g., 1 browser, 1 mobile app, 1 desktop app, etc.). If an agent logs in on a 6th device, the **oldest session is automatically evicted** (logged out) to enforce limits. *Note: If your backend signs JWT tokens manually (custom integration) without providing a `device_id` claim, this 5-device limit check is bypassed.* ::: #### Client API (`client-api`) Clients are **dynamic identities** — your backend signs a JWT with any `sub` value you choose. No pre-registration needed. Clients are designed for **in-app, internal VoIP calling** between your users. | Feature | Details | | ---------------------- | ----------------------------------------------- | | **Created by** | Your backend signs JWT with any username | | **Username** | Any identifier (user ID, phone, email, session) | | **Quantity** | Unlimited tokens (limit on active connections) | | **PSTN calls** | ❌ Internal VoIP only | | **Call routing** | ❌ Direct calls only | | **Internal calls** | ✅ Client ↔ Client, Client ↔ Agent | | **Call Center Portal** | ❌ Integrates into your own app | | **Presence** | ✅ Online/offline state | | **Teams** | ❌ Not applicable | :::caution **Concurrent Connection Limit** While you can generate and sign unlimited client JWT tokens (`client-api`), there is a default workspace limit of **1,000 concurrent active WebSocket signaling connections**. If your application requires higher concurrent limits, please contact [enterprise@firetell.com](mailto:enterprise@firetell.com) to increase the limit for your workspace. ::: #### Use Case Examples | Scenario | Identity | Flow | | ---------------------- | ------------ | --------------------------------------------------------- | | **Ride-hailing** | `client-api` | Driver app ↔ Customer app (in-app VoIP) | | **E-commerce support** | Both | Customer (`client-api`) → Call center agent (`agent-api`) | | **Telemedicine** | `client-api` | Patient app ↔ Doctor app | | **Customer support** | `agent-api` | Inbound PSTN call → IVR → Agent | | **Sales outreach** | `agent-api` | Agent → Outbound PSTN call to lead | | **In-app calling** | `client-api` | User A ↔ User B within your app | ``` ┌─────────────────────────────────────────────────────────┐ │ Your Application │ │ │ │ ┌──────────────┐ ┌──────────────┐ │ │ │ Client API │ ◄── VoIP ──► │ Client API │ │ │ │ (Driver) │ │ (Customer) │ │ │ └──────────────┘ └──────┬───────┘ │ │ │ │ │ VoIP │ │ │ ▼ │ │ ┌──────────────┐ │ │ │ Agent API │ │ │ │ (Support) │ │ │ └──────┬───────┘ │ │ │ │ └──────────────────────────────────────────┼───────────────┘ │ PSTN │ ▼ ┌─────────────┐ │ Phone │ │ Network │ └─────────────┘ ``` ### Quick Examples #### Agent API token ```javascript const jwt = require("jsonwebtoken"); const token = jwt.sign( { domain: "yourcompany.firetell.app" }, "sk-your_api_key_secret", { algorithm: "HS256", expiresIn: "24h", issuer: "sid-your_api_key_sid", subject: "agent_123", // Must be an existing, active agent username audience: "agent-api", }, ); ``` #### Client API token ```javascript const jwt = require("jsonwebtoken"); const token = jwt.sign( { domain: "yourcompany.firetell.app", call_flow_id: "cf_your_call_flow_id", // Required for client-api to route calls to a Call Flow }, "sk-your_api_key_secret", { algorithm: "HS256", expiresIn: "24h", issuer: "sid-your_api_key_sid", subject: "user_abc", // Any custom identifier you define audience: "client-api", }, ); ``` For detailed examples in Python, PHP, and more, see the [Client Authentication](/docs/sdks/javascript/authentication) guide. :::tip JWT tokens are designed for client-side usage and expire after a short period. Always sign tokens on your backend and pass them to the client — never expose your API Key secret to client-side code. ::: --- ## Workspace Scope Both API keys and JWT tokens are scoped to a single workspace. The workspace is determined by the subdomain in the request URL: ``` https://{workspace_id}.firetell.app/api/v1/... ^^^^^^^^^^^^^^ workspace scope ``` You cannot access resources from other workspaces with a single key or token. --- ## Choosing the Right Method | | API Key (`ApiKey`) | JWT Token (`Bearer`) | | ---------------- | ------------------------------------------ | ---------------------- | | **Where to use** | Backend / server | Frontend / client | | **Lifetime** | Long-lived (until revoked) | Short-lived (expires) | | **Security** | Must be kept secret | Safe to send to client | | **Access level** | Full workspace access | Scoped by JWT claims | | **Example** | REST API integrations, webhooks, cron jobs | WebRTC, real-time apps | :::warning **Never expose API Keys in client-side code.** API keys grant full access to your workspace. Use JWT tokens for any code that runs in the browser or on mobile devices. ::: :::tip **Use separate API keys for each authentication flow.** Create one key for server-to-server API calls (`ApiKey` scheme) and a different key for signing client JWT tokens (`Bearer` scheme). This makes it easy to identify which flow is affected if a key is compromised, and you can revoke a single key without disrupting the other. ::: --- ## Rate Limiting API requests are rate-limited to protect service quality: | Tier | Requests per minute | | ---------- | ------------------- | | Default | 100 | | Auth | 10 | | Heavy | 30 | | Enterprise | Custom | When rate limited, you'll receive a `429 Too Many Requests` response with a `Retry-After` header. Implement exponential backoff in your retry logic. See [Rate Limiting](/docs/rest-api/overview#rate-limiting) for full details. --- ## Error Responses ### Missing Authorization Header ```json { "statusCode": 401, "message": "Missing Authorization header" } ``` ### Unsupported Scheme ```json { "statusCode": 401, "message": "Unsupported authorization scheme \"Basic\". Use \"Bearer\" or \"ApiKey\"" } ``` ### Invalid API Key ```json { "statusCode": 401, "message": "Invalid API Key" } ``` ### Invalid or Expired JWT ```json { "statusCode": 401, "message": "Unauthorized" } ``` --- ## Security Best Practices - **Never expose API keys in client-side code** — Use them only on your backend server - **Use JWT tokens for clients** — Generate short-lived tokens for browser and mobile apps - **Rotate keys regularly** — Delete old keys and create new ones periodically - **Use separate keys per environment** — Different keys for development, staging, and production - **Store secrets securely** — Use environment variables or a secret manager, never hardcode in source code ## Next Steps - [Quickstart](/docs/getting-started/quickstart) — Make your first API call - [API Reference](/docs/rest-api/overview) — Explore all available endpoints --- # Introduction Welcome to the **Firetell Developer Platform**. Firetell provides a suite of cloud communications APIs that enable you to build voice-powered applications, automate call workflows, and integrate intelligent voice agents into your products. ## What is Firetell? Firetell is a **Communications Platform as a Service (CPaaS)** that offers: - **Cloud Phone Numbers** — Provision and manage virtual phone numbers worldwide - **Programmable Voice** — Make, receive, and control calls via API - **Smart IVR / Call Flows** — Build drag-and-drop interactive voice response systems - **SIP Trunking** — Connect your existing PBX infrastructure to the cloud - **Call Center** — Manage agents, teams, queues, and call routing - **Voice AI** — Deploy AI-powered voice agents for customer interactions - **Call Recording** — Record and store call audio for compliance and quality assurance - **Webhooks** — Receive real-time event notifications ## Who is this for? This documentation is designed for **developers** who want to integrate Firetell's capabilities into their applications using our REST APIs, webhooks, and SDKs. ## Base URL Each workspace has its own unique API domain. The base URL for all developer API requests follows this pattern: ``` https://{workspace_id}.firetell.app/api/v1 ``` Replace `{workspace_id}` with your workspace's subdomain. To find it: 1. Log in to [Firetell Console](https://console.firetell.com) 2. Select your workspace 3. Go to **Workspace Settings** → **General** 4. Copy the **Domain** value For example, if your domain is `yourcompany`, your base URL would be: ``` https://yourcompany.firetell.app/api/v1 ``` ## Workspaces & Regions When you create a workspace, you select a **region** that determines where your data is stored and API requests are routed. Each workspace gets a unique subdomain (`{workspace_id}.firetell.app`) that is DNS-resolved to the regional server cluster. ### How It Works ``` Create workspace "companyA" with region ASIA-SOUTH1 │ ▼ companyA.firetell.app → DNS → asia-south-1 cluster ├─ API servers ├─ Database ├─ Media servers ├─ Signaling servers └─ Storage servers ``` All API traffic, call data, recordings, and agent sessions for that workspace are processed and stored within the selected region. ### Available Regions | Region | ID | Location | | ----------------- | ---------------- | ------------- | | **ASIA-EAST1** | `asia-east-1` | Vietnam | | **ASIA-SOUTH1** | `asia-south-1` | Singapore | | **US-EAST1** | `us-east-1` | United States | | **EUROPE-NORTH1** | `europe-north-1` | France | :::warning The region **cannot be changed** after workspace creation. Choose the region closest to your users and agents for the lowest latency. ::: :::note **Need a custom domain?** Enterprise customers can use their own domain (e.g., `api.yourcompany.com`) instead of `*.firetell.app`. Contact [enterprise@firetell.com](mailto:enterprise@firetell.com) for details. ::: ## Call Center Portal Every workspace comes with a **ready-to-use Call Center Portal** that your agents can use immediately — no development required. ### Access Points | Platform | URL / App | Description | | ----------- | ------------------------------------------------------------------------- | -------------------------------- | | **Web** | `https://{workspace_id}.firetell.app` | Browser-based agent portal | | **iOS** | [App Store](https://apps.apple.com/app/firetell) | Native iOS app with VoIP push | | **Android** | [Google Play](https://play.google.com/store/apps/details?id=com.firetell) | Native Android app with FCM push | Agents log in with their username and password to make/receive calls, manage their availability, chat with team members, and more. ### Built on the Same API The Call Center Portal is built entirely on the **same public API** documented here. This means: - Everything the portal does, **your app can do too** - The portal serves as a **reference implementation** of the API - You can build your own custom agent experience using the same endpoints ### Open Source The Call Center Portal source code is available on GitHub: | Repository | Platform | Description | | ---------- | -------- | ----------- | | [firetell-call-center-web](https://github.com/firetellcom/firetell-call-center-web) | Web | Browser-based portal (Angular) | | [firetell-call-center-app](https://github.com/firetellcom/firetell-call-center-app) | iOS, Android | Mobile app (Flutter) | | — | Windows, macOS | Desktop app *(coming soon)* | You can fork and customize these apps to match your brand, add custom features, or use them as a starting point for your own agent application. ## Authentication Firetell APIs support authentication using either a server-side **API Key** or a client-side **JWT Token**. ### API Key (Server) Used for backend/server-to-server integrations. ```bash curl -X GET "https://{workspace_id}.firetell.app/api/v1/phone-numbers" \ -H "Authorization: ApiKey YOUR_API_KEY" ``` ### JWT Token (Client) Used for client-side applications (such as WebRTC or mobile SDKs) to authenticate agents or clients securely. ```bash curl -X GET "https://{workspace_id}.firetell.app/api/v1/phone-numbers" \ -H "Authorization: Bearer YOUR_JWT_TOKEN" ``` See the [Authentication](/docs/getting-started/authentication) guide and [Client Authentication](/docs/sdks/javascript/authentication) guide for more details. ## Next Steps - [Authentication](/docs/getting-started/authentication) — Learn how to authenticate API requests - [Quickstart](/docs/getting-started/quickstart) — Make your first API call - [API Reference](/docs/rest-api/overview) — Explore all available endpoints --- # Quickstart Get up and running with the Firetell API in just a few minutes. ## Prerequisites - A [Firetell Console](https://console.firetell.com) account - An API key (see [Authentication](/docs/getting-started/authentication)) - `curl` or any HTTP client ## Step 1: Find Your Workspace Domain Each workspace has its own API domain. To find yours: 1. Log in to [Firetell Console](https://console.firetell.com) 2. Select your workspace 3. Go to **Workspace Settings** → **General** 4. Note the **Domain** value (e.g., `yourcompany`) Your API base URL will be: ``` https://{workspace_id}.firetell.app/api/v1 ``` :::tip Replace `yourcompany` in all the examples below with your actual workspace domain. ::: ## Step 2: List Phone Numbers Verify your API key works by listing the phone numbers in your workspace: ```bash curl -X GET "https://{workspace_id}.firetell.app/api/v1/phone-numbers" \ -H "Authorization: ApiKey YOUR_API_KEY" ``` **Response:** ```json { "data": [ { "id": "pn_abc123", "number": "+84901234567", "type": "local", "status": "active" } ], "total": 1, "page": 1, "limit": 20 } ``` ## Step 3: Make a Call Initiate an outbound call: ```bash curl -X POST "https://{workspace_id}.firetell.app/api/v1/calls/make" \ -H "Authorization: ApiKey YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "from": "+84901234567", "to": "+84909876543" }' ``` ## What's Next? Now that you've made your first API call, explore the full capabilities: - [REST API Overview](/docs/rest-api/overview) — Learn about all available endpoints - [Call Flows](/docs/rest-api/workspace-api/call-flows) — Build automated call routing - [Webhooks](/docs/webhooks/overview) — Receive real-time event notifications - [Voice Agents](/docs/rest-api/workspace-api/voice-agents) — Integrate AI-powered voice bots --- # Account Self-service endpoints for agents to manage their own profile, teams, devices, and session. All endpoints require a valid **JWT token** with the `agent-api` audience (obtained via [Agent Login](/docs/rest-api/agent-api/auth#agent-login)). ## Endpoints | Method | Endpoint | Description | | ------- | ------------------------------------- | ------------------------------------- | | `GET` | `/me` | Get my profile | | `GET` | `/me/teams` | List my teams | | `GET` | `/me/teams/:id/agents` | List other agents in my team | | `PATCH` | `/me` | Update my display name | | `PATCH` | `/me/avatar` | Upload/change avatar | | `PATCH` | `/me/password` | Change my password | | `POST` | `/me/devices/voip-push-token` | Register VoIP push notification token | | `POST` | `/me/devices/notification-push-token` | Register notification push token | | `GET` | `/me/devices` | List my registered devices | | `POST` | `/me/logout` | Logout and remove device token | ## Agent Profile Object The profile object returned by `/me` endpoints: | Field | Type | Description | | -------------- | -------------- | ------------------------------------------------------------ | | `id` | string | Unique agent ID (prefixed with `ag_`) | | `username` | string | Agent login username | | `display_name` | string | Agent display name | | `email` | string \| null | Agent email address | | `avatar` | string \| null | Avatar image URL | | `state` | string | Real-time presence: `available`, `incall`, `busy`, `offline` | | `is_active` | boolean | Whether the agent account is active | | `workspace_id` | string | Workspace identifier | | `domain` | string | Agent's SIP domain | | `country_code` | string | ISO 3166-1 alpha-2 country code | | `created_at` | string | ISO 8601 creation timestamp | | `updated_at` | string | ISO 8601 last update timestamp | --- ## Get My Profile ``` GET /me ``` Retrieve the current authenticated agent's profile. ### Authentication Requires `Bearer` JWT with `agent-api` audience. ### Request ```bash curl -X GET "https://{workspace_id}.firetell.app/api/v1/me" \ -H "Authorization: Bearer YOUR_AGENT_JWT" ``` ### Response `200 OK` ```json { "id": "ag_7f3a2b1c9d4e5f6a8b0c1d2e", "username": "johndoe", "display_name": "John Doe", "email": "john@example.com", "avatar": "https://storage.firetell.app/avatars/ag_7f3a2b1c.jpg", "role": "member", "state": "available", "is_active": true, "workspace_id": "my_workspace", "domain": "my_workspace.app.firetell.com", "country_code": "VN", "created_at": "2026-01-15T08:30:00.000Z", "updated_at": "2026-07-13T09:00:00.000Z" } ``` ### Error Responses | Status | Description | | ------ | ------------------------------------------------ | | `403` | JWT audience is not `agent-api` | | `404` | Agent profile not found or account is not active | --- ## List My Teams ``` GET /me/teams ``` Retrieve a paginated list of teams that the current agent belongs to. ### Authentication Requires `Bearer` JWT with `agent-api` audience. ### Query Parameters | Parameter | Type | Default | Description | | --------- | ------ | ------- | --------------------------------- | | `page` | number | `1` | Page number (min: 1) | | `limit` | number | `10` | Items per page (min: 1, max: 100) | ### Request ```bash curl -X GET "https://{workspace_id}.firetell.app/api/v1/me/teams?page=1&limit=10" \ -H "Authorization: Bearer YOUR_AGENT_JWT" ``` ### Response `200 OK` ```json { "data": [ { "team_id": "te_a1b2c3d4e5f6g7h8", "team": { "id": "te_a1b2c3d4e5f6g7h8", "title": "Support Team", "workspace_id": "my_workspace", "agent_count": 5 } }, { "team_id": "te_x9y8z7w6v5u4t3s2", "team": { "id": "te_x9y8z7w6v5u4t3s2", "title": "Sales Team", "workspace_id": "my_workspace", "agent_count": 3 } } ], "meta": { "total": 2, "page": 1, "limit": 10, "total_pages": 1 } } ``` ### Item Fields | Field | Type | Description | | --------- | ------ | ------------------------------------- | | `team_id` | string | Team ID | | `team` | object | Team details (id, title, agent_count) | :::info Agent roles are no longer stored per-team. Each agent has a single workspace-level role (`agent`, `leader`, or `supervisor`) set by an admin. See [Roles & Permissions](/docs/rest-api/agent-api/roles-permissions) for details. ::: ### Error Responses | Status | Description | | ------ | ------------------------------------------------ | | `403` | JWT audience is not `agent-api` | | `404` | Agent profile not found or account is not active | --- ## List Team Agents ``` GET /me/teams/:id/agents ``` Retrieve a paginated list of other agents in a team that you belong to. The current agent is excluded from the results. This endpoint is designed for displaying teammate lists for messaging or calling. ### Authentication Requires `Bearer` JWT with `agent-api` audience. ### Path Parameters | Parameter | Type | Description | | --------- | ------ | ------------------------------------ | | `id` | string | Team ID (e.g. `te_a1b2c3d4e5f6g7h8`) | ### Query Parameters | Parameter | Type | Default | Description | | --------- | ------ | ------- | --------------------------------- | | `page` | number | `1` | Page number (min: 1) | | `limit` | number | `10` | Items per page (min: 1, max: 100) | ### Request ```bash curl -X GET "https://{workspace_id}.firetell.app/api/v1/me/teams/te_a1b2c3d4e5f6g7h8/agents?page=1&limit=10" \ -H "Authorization: Bearer YOUR_AGENT_JWT" ``` ### Response `200 OK` ```json { "data": [ { "username": "jane_smith", "display_name": "Jane Smith", "avatar": "https://storage.firetell.app/avatars/ag_x9y8z7.jpg", "state": "available", "role": "leader" }, { "username": "bob_wilson", "display_name": "Bob Wilson", "avatar": null, "state": "incall", "role": "member" }, { "username": "alice_chen", "display_name": "Alice Chen", "avatar": "https://storage.firetell.app/avatars/ag_m3n4o5.jpg", "state": "offline", "role": "member" } ], "meta": { "total": 4, "page": 1, "limit": 10, "total_pages": 1 } } ``` ### Item Fields | Field | Type | Description | | -------------- | -------------- | ---------------------------------------------------------------- | | `username` | string | Agent username | | `display_name` | string | Agent display name | | `avatar` | string \| null | Avatar URL | | `state` | string | Presence state: `available`, `incall`, `busy`, `offline` | | `role` | string | Agent's workspace-level role: `agent`, `leader`, or `supervisor` | :::info The `role` field reflects the agent's **account-level role**, not a team-specific role. Roles are assigned globally per agent by workspace admins. ::: ### Error Responses | Status | Description | | ------ | ------------------------------------------------ | | `403` | You are not a member of this team | | `404` | Agent profile not found or account is not active | --- ## Update My Profile ``` PATCH /me ``` Update the current agent's display name. Only `display_name` can be modified by the agent. Other fields like `username`, `email`, `is_active`, and `country_code` can only be changed by workspace admins via the [Agents API](/docs/rest-api/workspace-api/agents). ### Authentication Requires `Bearer` JWT with `agent-api` audience. ### Request Body | Field | Type | Description | | -------------- | ------ | ------------------------------------------------------- | | `display_name` | string | Display name (1–35 characters, letters and spaces only) | ### Request ```bash curl -X PATCH "https://{workspace_id}.firetell.app/api/v1/me" \ -H "Authorization: Bearer YOUR_AGENT_JWT" \ -H "Content-Type: application/json" \ -d '{ "display_name": "John D." }' ``` ### Response `200 OK` Returns the updated agent profile object. ### Error Responses | Status | Description | | ------ | ------------------------------------------------ | | `403` | JWT audience is not `agent-api` | | `404` | Agent profile not found or account is not active | --- ## Upload / Change Avatar ``` PATCH /me/avatar ``` Upload or replace the current agent's avatar image. The previous avatar (if any) is automatically deleted from storage. ### Authentication Requires `Bearer` JWT with `agent-api` audience. ### Request Body Send as `multipart/form-data` with a single file field: | Field | Type | Required | Description | | ------ | ---- | -------- | --------------------------------------------- | | `file` | file | ✅ | Image file (max 5MB, must be an image format) | ### Request ```bash curl -X PATCH "https://{workspace_id}.firetell.app/api/v1/me/avatar" \ -H "Authorization: Bearer YOUR_AGENT_JWT" \ -F "file=@/path/to/avatar.jpg" ``` ### Response `200 OK` Returns the updated agent profile object with the new `avatar` URL. ### Error Responses | Status | Description | | ------ | ------------------------------------------------ | | `400` | File is missing or not an image format | | `403` | JWT audience is not `agent-api` | | `404` | Agent profile not found or account is not active | --- ## Change My Password ``` PATCH /me/password ``` Change the current agent's login password. Unlike the admin [Change Password](/docs/rest-api/workspace-api/agents#change-agent-password) endpoint, this requires the **current password** for verification. ### Authentication Requires `Bearer` JWT with `agent-api` audience. ### Request Body | Field | Type | Required | Description | | ------------------ | ------ | -------- | ------------------------------ | | `current_password` | string | ✅ | Current (old) password | | `new_password` | string | ✅ | New password (6–50 characters) | ### Request ```bash curl -X PATCH "https://{workspace_id}.firetell.app/api/v1/me/password" \ -H "Authorization: Bearer YOUR_AGENT_JWT" \ -H "Content-Type: application/json" \ -d '{ "current_password": "oldPassword123", "new_password": "newSecurePassword456" }' ``` ### Response `200 OK` ```json { "message": "Password changed successfully" } ``` ### Error Responses | Status | Description | | ------ | ------------------------------------------------ | | `401` | Current password is incorrect | | `403` | JWT audience is not `agent-api` | | `404` | Agent profile not found or account is not active | :::caution After changing the password, the agent's existing JWT tokens remain valid until expiration. No active sessions are terminated. The new password takes effect on the next login. ::: --- ## Register VoIP Push Notification Token ``` POST /me/devices/voip-push-token ``` Register or update a **VoIP push notification token** for the current device. This enables the agent to receive **incoming call notifications** via VoIP Push (APNs VoIP / FCM) even when the app is in the background or the WebSocket connection is closed. If a device with the same `device_id` already exists, it will be updated with the new token and metadata. :::info This endpoint registers the **VoIP** push token used for incoming call pushes. To register a token for **regular notifications** (e.g., messages, alerts), use [`POST /me/devices/notification-push-token`](#register-notification-push-token) instead. The device record is created or updated (upserted by `device_id`). On **iOS**, VoIP push (APNs VoIP) and regular push (APNs standard) use **different tokens**. Both must be registered separately. On **Android/Web**, FCM handles both types — you may use the same FCM token for both endpoints. ::: ### Authentication Requires `Bearer` JWT with `agent-api` audience. ### Request Body | Field | Type | Required | Description | | -------------- | ------ | -------- | -------------------------------------------------------- | | `push_token` | string | ✅ | Push notification token (APNs VoIP token or FCM token) | | `device_id` | string | ✅ | Unique device identifier (e.g., IDFV on iOS, Android ID) | | `platform` | string | ✅ | Device platform: `ios`, `android`, or `web` | | `os_version` | string | | OS version (e.g., `"17.5.1"`, `"14"`) | | `app_version` | string | | Application version (e.g., `"1.2.0"`) | | `device_model` | string | | Device model (e.g., `"iPhone 15 Pro"`, `"Galaxy S24"`) | ### Request ```bash curl -X POST "https://{workspace_id}.firetell.app/api/v1/me/devices/voip-push-token" \ -H "Authorization: Bearer YOUR_AGENT_JWT" \ -H "Content-Type: application/json" \ -d '{ "push_token": "abc123...xyz", "device_id": "A1B2C3D4-E5F6-7890-ABCD-EF1234567890", "platform": "ios", "os_version": "17.5.1", "app_version": "1.2.0", "device_model": "iPhone 15 Pro" }' ``` ### Response `200 OK` ```json { "id": "dev_a1b2c3d4e5f6g7h8", "workspace_id": "my_workspace", "username": "johndoe", "push_token": "abc123...xyz", "device_id": "A1B2C3D4-E5F6-7890-ABCD-EF1234567890", "platform": "ios", "os_version": "17.5.1", "app_version": "1.2.0", "device_model": "iPhone 15 Pro", "voip_enabled": true, "notification_push_token": null, "last_active_at": "2026-07-13T10:00:00.000Z", "created_at": "2026-07-13T10:00:00.000Z", "updated_at": "2026-07-13T10:00:00.000Z" } ``` :::tip Call this endpoint every time the app launches or the push token refreshes to ensure the server always has the latest token. Stale tokens cannot receive push notifications. ::: --- ## Register Notification Push Token ``` POST /me/devices/notification-push-token ``` Register or update a **regular push notification token** for the current device. This enables the agent to receive **non-VoIP notifications** (e.g., chat messages, system alerts) via standard push (APNs / FCM). The device must already be registered via [`POST /me/devices/voip-push-token`](#register-voip-push-notification-token). :::info On **iOS**, the standard APNs token is **different** from the VoIP APNs token. Both must be registered separately. On **Android/Web**, FCM handles both VoIP and regular push — the same token can be used. ::: ### Authentication Requires `Bearer` JWT with `agent-api` audience. ### Request Body | Field | Type | Required | Description | | ------------------------- | ------ | -------- | -------------------------------------------------------- | | `notification_push_token` | string | ✅ | Regular push notification token (APNs standard or FCM) | | `device_id` | string | ✅ | Unique device identifier (same as used in `/me/devices`) | | `platform` | string | ✅ | Device platform: `ios`, `android`, or `web` | ### Request ```bash curl -X POST "https://{workspace_id}.firetell.app/api/v1/me/devices/notification-push-token" \ -H "Authorization: Bearer YOUR_AGENT_JWT" \ -H "Content-Type: application/json" \ -d '{ "notification_push_token": "def456...uvw", "device_id": "A1B2C3D4-E5F6-7890-ABCD-EF1234567890", "platform": "ios" }' ``` ### Response `200 OK` ```json { "id": "dev_a1b2c3d4e5f6g7h8", "workspace_id": "my_workspace", "username": "johndoe", "push_token": "abc123...xyz", "device_id": "A1B2C3D4-E5F6-7890-ABCD-EF1234567890", "platform": "ios", "os_version": "17.5.1", "app_version": "1.2.0", "device_model": "iPhone 15 Pro", "voip_enabled": true, "notification_push_token": "def456...uvw", "last_active_at": "2026-07-13T10:05:00.000Z", "created_at": "2026-07-13T10:00:00.000Z", "updated_at": "2026-07-13T10:05:00.000Z" } ``` ### Error Responses | Status | Description | | ------ | ---------------------------------------------------------------------------------------- | | `403` | JWT audience is not `agent-api` | | `404` | Device not found — register VoIP push token first via `POST /me/devices/voip-push-token` | :::tip Call this endpoint after registering the VoIP push token, or whenever the notification push token refreshes. On iOS, register both tokens on app launch: 1. `POST /me/devices/voip-push-token` — with the VoIP APNs token 2. `POST /me/devices/notification-push-token` — with the standard APNs token ::: --- ## List My Devices ``` GET /me/devices ``` List all devices with registered push notification tokens for the current agent. ### Authentication Requires `Bearer` JWT with `agent-api` audience. ### Response `200 OK` ```json [ { "id": "dev_a1b2c3d4e5f6g7h8", "workspace_id": "my_workspace", "username": "johndoe", "push_token": "abc123...xyz", "device_id": "A1B2C3D4-E5F6-7890-ABCD-EF1234567890", "platform": "ios", "os_version": "17.5.1", "app_version": "1.2.0", "device_model": "iPhone 15 Pro", "voip_enabled": true, "notification_push_token": "def456...uvw", "last_active_at": "2026-07-13T10:00:00.000Z" }, { "id": "dev_x9y8z7w6v5u4t3s2", "workspace_id": "my_workspace", "username": "johndoe", "push_token": "def456...uvw", "device_id": "android-device-id-123", "platform": "android", "os_version": "14", "app_version": "1.2.0", "device_model": "Samsung Galaxy S24", "voip_enabled": true, "notification_push_token": "def456...uvw", "last_active_at": "2026-07-12T08:00:00.000Z" } ] ``` --- ## Logout ``` POST /me/logout ``` Logout from a specific device. This removes the push notification token for the specified device, preventing it from receiving further call notifications. :::caution **Client applications MUST call this endpoint when the agent logs out.** If the agent simply closes the app without calling logout, the server will continue attempting to send push notifications to the stale token, which wastes resources and may cause the push provider (APNs/FCM) to throttle or revoke the token. Inactive device registrations are automatically cleaned up after **30 days** of inactivity as a safety net, but explicit logout is always preferred. ::: ### Authentication Requires `Bearer` JWT with `agent-api` audience. ### Request Body | Field | Type | Required | Description | | ----------- | ------ | -------- | --------------------------------------------------------- | | `device_id` | string | ✅ | The device identifier used during push token registration | ### Request ```bash curl -X POST "https://{workspace_id}.firetell.app/api/v1/me/logout" \ -H "Authorization: Bearer YOUR_AGENT_JWT" \ -H "Content-Type: application/json" \ -d '{ "device_id": "A1B2C3D4-E5F6-7890-ABCD-EF1234567890" }' ``` ### Response `200 OK` ```json { "message": "Logged out successfully" } ``` :::info This endpoint is **idempotent** — calling it multiple times with the same `device_id` always returns success, even if the device was already unregistered. ::: ### What happens on logout 1. The push notification token for the specified device is deleted 2. If the agent has no more devices with push tokens **and** no active WebSocket connections, their presence state transitions to `offline` 3. The JWT token itself remains valid until expiration — logout only removes the push notification registration --- # Address Books Organize your [Contacts](/docs/rest-api/agent-api/contacts) into named address books for better management. Address books act as folders — **every contact must belong to an address book**. When a new workspace is created, a system address book named **`Default`** is automatically initialized. If a contact is created without specifying an `address_book_id`, it is automatically assigned to the `Default` address book. ## Endpoints | Method | Endpoint | Description | | -------- | ------------------------------------ | ------------------------- | | `GET` | `/call-center/address-books` | List all address books | | `GET` | `/call-center/address-books/:id` | Get address book details | | `POST` | `/call-center/address-books` | Create a new address book | | `PUT` | `/call-center/address-books/:id` | Update an address book | | `DELETE` | `/call-center/address-books/:id` | Delete an address book | ## Authentication All endpoints require `Bearer` JWT with `agent-api` audience. ## Address Book Object | Field | Type | Description | | -------------- | ------- | ---------------------------------------------------------------------- | | `id` | string | Unique address book ID (prefixed `ab_`) | | `workspace_id` | string | Workspace identifier | | `title` | string | Address book name | | `description` | string | Optional description | | `is_default` | boolean | Whether this is the system default address book (cannot be deleted/modified) | | `owner_id` | string | ID of the owning agent or team (null for `everyone`) | | `owner_type` | string | Ownership scope: `agent`, `team`, or `everyone` | | `created_at` | string | ISO 8601 creation timestamp | | `updated_at` | string | ISO 8601 last update timestamp | --- ## List All Address Books ``` GET /call-center/address-books ``` Retrieve a paginated list of all address books accessible to the current agent. ### Query Parameters | Parameter | Type | Default | Description | | ------------ | ------ | ------- | --------------------------------- | | `page` | number | `1` | Page number (min: 1) | | `limit` | number | `20` | Items per page (min: 1, max: 100) | | `search` | string | — | Search by title (text search) | | `sort_field` | string | — | Sort by: `created_at`, `title` | | `sort_order` | string | `desc` | Sort order: `asc` or `desc` | ### Request ```bash curl -X GET "https://{workspace_id}.firetell.app/api/v1/call-center/address-books?page=1&limit=10" \ -H "Authorization: Bearer YOUR_AGENT_JWT" ``` ### Response `200 OK` ```json { "data": [ { "id": "ab_x1y2z3w4v5u6t7s8", "workspace_id": "my_workspace", "title": "VIP Customers", "description": "High-value customers with active contracts", "created_at": "2026-01-10T10:00:00.000Z", "updated_at": "2026-07-01T12:00:00.000Z" }, { "id": "ab_a9b8c7d6e5f4g3h2", "workspace_id": "my_workspace", "title": "Leads Q3 2026", "description": null, "created_at": "2026-07-01T08:00:00.000Z", "updated_at": "2026-07-01T08:00:00.000Z" } ], "meta": { "total": 2, "page": 1, "limit": 10, "total_pages": 1 } } ``` --- ## Get Address Book Details ``` GET /call-center/address-books/:id ``` Retrieve detailed information about a specific address book. ### Path Parameters | Parameter | Type | Description | | --------- | ------ | -------------------------------------- | | `id` | string | Address book ID (e.g., `ab_x1y2...`) | ### Request ```bash curl -X GET "https://{workspace_id}.firetell.app/api/v1/call-center/address-books/ab_x1y2z3w4v5u6t7s8" \ -H "Authorization: Bearer YOUR_AGENT_JWT" ``` ### Response `200 OK` Returns the full address book object. ### Error Responses | Status | Description | | ------ | ----------------------- | | `404` | Address book not found | --- ## Create an Address Book ``` POST /call-center/address-books ``` Create a new address book in the workspace. ### Request Body | Field | Type | Required | Description | | ------------- | ------ | -------- | ------------------------------------------------------------------- | | `title` | string | ✅ | Address book name (1–100 characters) | | `description` | string | | Optional description (max 500 chars) | | `owner_type` | string | | Ownership scope: `agent`, `team`, or `everyone` (defaults to `agent` in Agent API) | | `owner_id` | string | | ID of the owning team (required if `owner_type` is `team`). Automatically assigned to your agent ID if `owner_type` is `agent`. | ### Request ```bash curl -X POST "https://{workspace_id}.firetell.app/api/v1/call-center/address-books" \ -H "Authorization: Bearer YOUR_AGENT_JWT" \ -H "Content-Type: application/json" \ -d '{ "title": "Enterprise Clients", "description": "Contacts from enterprise accounts with dedicated support", "owner_type": "team", "owner_id": "tm_sales123" }' ``` ### Response `201 Created` ```json { "id": "ab_n3w1d2e3f4g5h6i7", "workspace_id": "my_workspace", "title": "Enterprise Clients", "description": "Contacts from enterprise accounts with dedicated support", "is_default": false, "owner_type": "team", "owner_id": "tm_sales123", "created_at": "2026-07-21T08:00:00.000Z", "updated_at": "2026-07-21T08:00:00.000Z" } ``` ### Error Responses | Status | Description | | ------ | --------------------------------------------------------------------------- | | `400` | Validation error | | `403` | Forbidden — Only leaders can create address books for a team or `everyone` | --- ## Update an Address Book ``` PUT /call-center/address-books/:id ``` Update an existing address book's title, description, or ownership settings. ### Path Parameters | Parameter | Type | Description | | --------- | ------ | ---------------- | | `id` | string | Address book ID | ### Request Body | Field | Type | Required | Description | | ------------- | ------ | -------- | ------------------------------------------------------------------- | | `title` | string | | Updated name (1–100 characters) | | `description` | string | | Updated description (max 500 chars) | | `owner_type` | string | | Ownership scope: `agent`, `team`, or `everyone` | | `owner_id` | string | | ID of the owning agent or team | ### Request ```bash curl -X PUT "https://{workspace_id}.firetell.app/api/v1/call-center/address-books/ab_x1y2z3w4v5u6t7s8" \ -H "Authorization: Bearer YOUR_AGENT_JWT" \ -H "Content-Type: application/json" \ -d '{ "title": "VIP Customers 2026", "description": "Updated address book for active VIP accounts in 2026", "owner_type": "team", "owner_id": "tm_support456" }' ``` ### Response `200 OK` Returns the updated address book object. ### Error Responses | Status | Description | | ------ | ------------------------------------------------------------------------------------------------------- | | `400` | Validation error or attempting to modify `Default` address book | | `403` | Forbidden — Only leaders can modify team/shared address books, or attempting to modify another agent's book | | `404` | Address book not found | --- ## Delete an Address Book ``` DELETE /call-center/address-books/:id ``` Permanently delete an address book. Contacts that belong to this book are **not deleted** — they are automatically reassigned to the system **`Default`** address book instead. ### Path Parameters | Parameter | Type | Description | | --------- | ------ | ---------------- | | `id` | string | Address book ID | ### Request ```bash curl -X DELETE "https://{workspace_id}.firetell.app/api/v1/call-center/address-books/ab_x1y2z3w4v5u6t7s8" \ -H "Authorization: Bearer YOUR_AGENT_JWT" ``` ### Response `200 OK` Returns the deleted address book object. ### Error Responses | Status | Description | | ------ | ------------------------------------------------------------------------------------------------------- | | `400` | Cannot delete system `Default` address book | | `403` | Forbidden — Only leaders can delete team/shared address books, or attempting to delete another agent's book | | `404` | Address book not found | :::info When a custom address book is deleted, all contacts that belonged to it will be automatically reassigned to the system **`Default`** address book. The contacts themselves are preserved. ::: --- # Authentication Authentication endpoints for agents to login, verify tokens, refresh sessions, and recover forgotten passwords. All endpoints in this section are **public** — no authentication required (except `/auth/verify` which validates a token). ## Endpoints | Method | Endpoint | Description | | ------ | ----------------------- | --------------------------------------- | | `POST` | `/auth/login` | Authenticate agent and get JWT | | `POST` | `/auth/verify` | Verify agent JWT token | | `POST` | `/auth/refresh` | Refresh access token | | `POST` | `/auth/check-username` | Check username and get masked email | | `POST` | `/auth/forgot-password` | Request a password reset email | | `POST` | `/auth/reset-password` | Reset password using a token from email | --- ## Agent Login ``` POST /auth/login ``` Authenticates an agent using their username and password. On successful authentication, returns an access token (JWT), refresh token, and basic agent info. ### Request Body | Field | Type | Required | Description | | ----------- | ------ | -------- | ------------------------------------------------------------------- | | `username` | string | ✅ | Agent username (3–30 characters) | | `password` | string | ✅ | Agent password (6–50 characters) | | `device_id` | string | ✅ | Unique device identifier (IDFV on iOS, Android ID, or UUID for web) | ### Request ```bash curl -X POST "https://{workspace_id}.firetell.app/api/v1/auth/login" \ -H "Content-Type: application/json" \ -d '{ "username": "agent_john", "password": "secretpassword", "device_id": "A1B2C3D4-E5F6-7890-ABCD-EF1234567890" }' ``` ### Response `200 OK` ```json { "tokens": { "access_token": "eyJhbGciOiJIUzI1NiIsIn...", "expires_at": 1783935600000, "refresh_token": "eyJhbGciOiJIUzI1NiIsIn..." }, "info": { "username": "agent_john", "domain": "yourcompany.firetell.app", "display_name": "John Doe" } } ``` ### Error Responses | Status | Description | | ------ | ------------------------------------------------ | | `400` | `workspace_id not found` or validation error | | `401` | Invalid credentials or agent account is inactive | --- ## Verify Agent JWT ``` POST /auth/verify ``` Verifies a previously generated JWT token and returns information about the token's subject (the authenticated agent or client). ### Request Body | Field | Type | Required | Description | | ------- | ------ | -------- | ----------------------- | | `token` | string | ✅ | The JWT token to verify | ### Request ```bash curl -X POST "https://{workspace_id}.firetell.app/api/v1/auth/verify" \ -H "Content-Type: application/json" \ -d '{ "token": "eyJhbGciOiJIUzI1NiIsIn..." }' ``` ### Response `200 OK` ```json { "username": "agent_john", "domain": "yourcompany.firetell.app", "workspace_id": "ws_123456", "display_name": "John Doe", "avatar": "https://storage.firetell.app/avatars/ag_123.jpg", "expires_at": 1783935600000 } ``` ### Error Responses | Status | Description | | ------ | ---------------------------------------- | | `401` | Missing token or invalid token signature | --- ## Refresh Token ``` POST /auth/refresh ``` Exchange a valid refresh token for a new access token and refresh token pair. This implements **token rotation** — each refresh token can only be used once, and a new pair is issued on every refresh. ### Token Lifecycle | Token | Lifetime | Purpose | | --------------- | -------- | -------------------------------------------- | | `access_token` | 1 day | Used for API access (Bearer header) | | `refresh_token` | 30 days | Used only at this endpoint to get new tokens | :::info Refresh tokens **cannot** be used as Bearer tokens for API access. The server will reject them with `401 Unauthorized`. ::: ### Request Body | Field | Type | Required | Description | | --------------- | ------ | -------- | ------------------------------------ | | `refresh_token` | string | ✅ | The refresh token from login/refresh | | `device_id` | string | ✅ | Same device ID used during login | ### Request ```bash curl -X POST "https://{workspace_id}.firetell.app/api/v1/auth/refresh" \ -H "Content-Type: application/json" \ -d '{ "refresh_token": "eyJhbGciOiJIUzI1NiIsIn...", "device_id": "A1B2C3D4-E5F6-7890-ABCD-EF1234567890" }' ``` ### Response `200 OK` ```json { "access_token": "eyJhbGciOiJIUzI1NiIsIn...", "refresh_token": "eyJhbGciOiJIUzI1NiIsIn...", "expires_at": 1784022000000 } ``` :::warning After refreshing, the **previous refresh token is no longer valid**. Always store and use the new `refresh_token` from the response. Reusing an old refresh token will be rejected as a potential token theft. ::: ### Error Responses | Status | Description | | ------ | ---------------------------------------------- | | `401` | Invalid, expired, or wrong type of token | | `401` | Refresh token has been revoked or already used | | `401` | Agent not found or inactive | | `400` | Missing workspace_id | --- ## Check Username ``` POST /auth/check-username ``` Check whether an agent username exists in the workspace and return the agent's **masked email address**. This helps agents who remember their username but have forgotten which email address is associated with their account. :::info This endpoint is **public** — no authentication required. ::: ### Request Body | Field | Type | Required | Description | | ---------- | ------ | -------- | -------------------------------- | | `username` | string | ✅ | Agent username (3–30 characters) | ### Request ```bash curl -X POST "https://{workspace_id}.firetell.app/api/v1/auth/check-username" \ -H "Content-Type: application/json" \ -d '{ "username": "agent_john" }' ``` ### Response `200 OK` ```json { "email": "ag*********@gmail.com" } ``` The email address is masked to protect privacy — only the first two characters of the local part are visible, and the domain remains intact. | Original email | Masked email | | --------------------------- | ----------------------- | | `your.agentemail@gmail.com` | `yo*********@gmail.com` | | `ab@company.com` | `a*@company.com` | | `a@example.com` | `*@example.com` | ### Error Responses | Status | Description | | ------ | ---------------------------------------------- | | `400` | `workspace_id not found` or validation error | | `404` | Agent not found or no email address configured | --- ## Forgot Password ``` POST /auth/forgot-password ``` Request a password reset email. If the `username` and `email` combination matches an active agent in the workspace, a reset email is sent with a link that expires in **30 minutes**. :::info This endpoint is **public** — no authentication required. ::: :::tip Security This endpoint always returns the same success message regardless of whether the agent exists or the email matches. This prevents attackers from enumerating valid accounts. ::: ### Request Body | Field | Type | Required | Description | | ---------- | ------ | -------- | -------------------------------- | | `username` | string | ✅ | Agent username (3–30 characters) | | `email` | string | ✅ | Agent's email address | ### Request ```bash curl -X POST "https://{workspace_id}.firetell.app/api/v1/auth/forgot-password" \ -H "Content-Type: application/json" \ -d '{ "username": "agent_john", "email": "john@company.com" }' ``` ### Response `200 OK` ```json { "message": "If the account exists, a reset email has been sent." } ``` ### Reset Email If the credentials match, the agent receives an email from **Firetell • Workspace Name** with: - Workspace name and portal URL - A "Reset Password" button linking to `https://{workspace_id}.firetell.app/auth/reset-password?token={JWT}` - The link expires in **30 minutes** ### Error Responses | Status | Description | | ------ | -------------------------------------------- | | `400` | `workspace_id not found` or validation error | --- ## Reset Password ``` POST /auth/reset-password ``` Reset an agent's password using the token received via the password reset email. The token is a JWT signed by the server with a 30-minute expiry. :::info This endpoint is **public** — no authentication required. The reset token itself serves as proof of identity. ::: ### Request Body | Field | Type | Required | Description | | -------------- | ------ | -------- | ------------------------------- | | `token` | string | ✅ | Reset token from the email link | | `new_password` | string | ✅ | New password (6–50 characters) | ### Request ```bash curl -X POST "https://{workspace_id}.firetell.app/api/v1/auth/reset-password" \ -H "Content-Type: application/json" \ -d '{ "token": "eyJhbGciOiJIUzI1NiIsIn...", "new_password": "myNewSecurePassword123" }' ``` ### Response `200 OK` ```json { "message": "Password has been reset successfully." } ``` ### Error Responses | Status | Description | | ------ | ---------------------------------------------- | | `400` | Invalid or expired reset token | | `400` | Agent not found or has been deleted | | `500` | Password reset is not configured on the server | --- ## Password Reset Flow The complete password reset flow involves three steps: ``` 1. Agent calls POST /auth/check-username → Receives masked email (e.g. "ag*****@gmail.com") → Now knows which email to use 2. Agent calls POST /auth/forgot-password with username + email → Server sends reset email to the agent's address → Agent clicks "Reset Password" link in email 3. Client extracts token from URL query parameter → Calls POST /auth/reset-password with token + new password → Password is updated, agent can log in with new credentials ``` :::warning Reset tokens are **single-purpose** JWT tokens — they can only be used to reset a password and cannot be used as access tokens for other API calls. ::: --- # Call History View the history of calls you have handled as an agent. These endpoints are scoped — you can only see calls where you were the assigned agent. :::info Data Scoping Only calls where `agent_id` matches the authenticated agent are returned. Workspace-wide call history and call events/logs are available to admins via the Workspace API. ::: ## Endpoints | Method | Endpoint | Description | | ------ | ------------------------------- | ---------------- | | `GET` | `/call-center/call-history` | List my calls | | `GET` | `/call-center/call-history/:id` | Get call details | ## Authentication All endpoints require `Bearer` JWT with `agent-api` audience. --- ## List My Calls ``` GET /call-center/call-history ``` Retrieve a paginated list of calls handled by the authenticated agent, sorted by most recent first. ### Query Parameters | Parameter | Type | Default | Description | | ------------ | ------ | ------- | --------------------------- | | `page` | number | `1` | Page number (min: 1) | | `limit` | number | `20` | Items per page (max: 100) | | `sort_order` | string | `desc` | Sort order: `asc` or `desc` | ### Request ```bash curl -X GET "https://{workspace_id}.firetell.app/api/v1/call-center/call-history?page=1&limit=10" \ -H "Authorization: Bearer YOUR_AGENT_JWT" ``` ### Response `200 OK` ```json { "data": [ { "id": "cl_a1b2c3d4e5f6g7h8i9j0k1", "workspace_id": "my_workspace", "direction": "inbound", "status": "completed", "type": "audio", "number": "84901234567", "client_number": "+84912345678", "from": { "name": null, "number": "+84912345678" }, "to": { "name": "Sales Line", "number": "84901234567" }, "agent_id": "ag_abc123", "source": "call_flow", "duration": 245, "call_flow_id": "cf_xyz789", "hangup_cause": "normal_clearing", "created_at": "2026-07-21T07:30:00.000Z", "answered_at": "2026-07-21T07:30:05.000Z", "ended_at": "2026-07-21T07:34:10.000Z" }, { "id": "cl_n3w1d2e3f4g5h6i7j8k9l0", "workspace_id": "my_workspace", "direction": "outbound", "status": "completed", "type": "audio", "number": "84901234567", "client_number": "+84987654321", "from": { "name": "Agent John", "number": "84901234567" }, "to": { "name": null, "number": "+84987654321" }, "agent_id": "ag_abc123", "source": "agent_app", "duration": 120, "hangup_cause": "normal_clearing", "created_at": "2026-07-21T06:15:00.000Z", "answered_at": "2026-07-21T06:15:08.000Z", "ended_at": "2026-07-21T06:17:08.000Z" } ], "meta": { "total": 42, "page": 1, "limit": 10, "total_pages": 5 } } ``` ### Call Object | Field | Type | Description | | --------------- | ------------ | --------------------------------------------------------------- | | `id` | string | Call ID | | `workspace_id` | string | Workspace identifier | | `direction` | string | `inbound`, `outbound`, or `internal` | | `status` | string | `created`, `started`, `active`, `completed`, `failed`, `missed` | | `type` | string | Call type: `audio` | | `number` | string | Workspace phone number used | | `client_number` | string | External party phone number | | `from` | object | Caller info (`name`, `number`) | | `to` | object | Callee info (`name`, `number`) | | `agent_id` | string | Handling agent ID | | `source` | string | Call origin: `client-api`, `agent-api`, `server-api` | | `duration` | number | Call duration in seconds | | `call_flow_id` | string\|null | Call flow ID if routed via a flow | | `hangup_cause` | string\|null | SIP hangup cause (e.g., `normal_clearing`) | | `created_at` | string | ISO 8601 creation timestamp | | `answered_at` | string\|null | ISO 8601 when call was answered | | `ended_at` | string\|null | ISO 8601 when call ended | --- ## Get Call Details ``` GET /call-center/call-history/:id ``` Retrieve detailed information about a specific call you handled. ### Path Parameters | Parameter | Type | Description | | --------- | ------ | ------------------------------ | | `id` | string | Call ID (e.g., `cl_a1b2c3...`) | ### Request ```bash curl -X GET "https://{workspace_id}.firetell.app/api/v1/call-center/call-history/cl_a1b2c3d4e5f6g7h8i9j0k1" \ -H "Authorization: Bearer YOUR_AGENT_JWT" ``` ### Response `200 OK` Returns the full call object. ### Error Responses | Status | Description | | ------ | ---------------------------------------- | | `404` | Call not found or you do not have access | --- # Call Supervision Real-time call supervision for supervisors. These endpoints allow supervisors to monitor, coach, and join active calls handled by agents in teams they supervise. :::caution **Supervisor Only** All endpoints on this page require the `supervisor` role in a team that the call's agent belongs to. See [Roles & Permissions](/docs/rest-api/agent-api/roles-permissions) for details. ::: ## Endpoints | Method | Endpoint | Description | | -------- | ----------------------------------------- | ------------------ | | `POST` | `/call-center/calls/:call_id/listen` | Silent monitor | | `POST` | `/call-center/calls/:call_id/whisper` | Coach agent | | `POST` | `/call-center/calls/:call_id/barge` | Join as 3-way call | | `DELETE` | `/call-center/calls/:call_id/supervision` | Stop supervision | ## Authentication All endpoints require `Bearer` JWT with `agent-api` audience and `supervisor` role. ## Supervision Modes | Mode | Supervisor hears | Agent hears supervisor | Caller hears supervisor | | ----------- | :--------------: | :--------------------: | :---------------------: | | **Listen** | ✅ | ❌ | ❌ | | **Whisper** | ✅ | ✅ | ❌ | | **Barge** | ✅ | ✅ | ✅ | --- ## Call Session Token (`call_token`) Every successful supervision call (`listen`, `whisper`, `barge`) returns a short-lived **`call_token`** (JWT valid for 15 minutes). This `call_token` is used by the Client SDK (or WebRTC app) to connect directly to the Signaling Server / WebRTC media stream with zero-latency authentication. ### `call_token` JWT Payload Structure ```json { "sub": "supervisor.jane", "aud": "call-session", "workspace_id": "ws_123456789", "call_id": "cl_a1b2c3d4e5f6", "mode": "listen", "fs_uuid": "f81d4fae-7dec-11d0-a765-00a0c91e6bf6", "exp": 1753114200 } ``` :::tip **Unified Call Token Architecture** The `call_token` architecture is unified across all call interactions: - **Participant / Inbound Push**: Included in VoIP Push payload data so mobile apps can connect immediately to WebSocket without extra HTTP roundtrips. - **Supervision**: Returned by supervision APIs to authorize WebRTC audio streams (`listen`, `whisper`, `barge`). ::: --- ## Silent Listen ``` POST /call-center/calls/:call_id/listen ``` Start silently monitoring an active call. Neither the agent nor the caller will know you are listening. ### Path Parameters | Parameter | Type | Description | | --------- | ------ | --------------------- | | `call_id` | string | ID of the active call | ### Request ```bash curl -X POST "https://{workspace_id}.firetell.app/api/v1/call-center/calls/cl_a1b2c3d4e5f6/listen" \ -H "Authorization: Bearer YOUR_SUPERVISOR_JWT" ``` ### Response `200 OK` ```json { "call_id": "cl_a1b2c3d4e5f6", "mode": "listen", "supervisor": "supervisor.jane", "status": "supervision_started", "call_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "expires_in": 900 } ``` --- ## Whisper (Coach) ``` POST /call-center/calls/:call_id/whisper ``` Start whispering to the agent. The agent can hear you, but the caller cannot. Useful for real-time coaching. ### Request ```bash curl -X POST "https://{workspace_id}.firetell.app/api/v1/call-center/calls/cl_a1b2c3d4e5f6/whisper" \ -H "Authorization: Bearer YOUR_SUPERVISOR_JWT" ``` ### Response `200 OK` ```json { "call_id": "cl_a1b2c3d4e5f6", "mode": "whisper", "supervisor": "supervisor.jane", "status": "supervision_started", "call_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "expires_in": 900 } ``` --- ## Barge-in (3-Way Call) ``` POST /call-center/calls/:call_id/barge ``` Join the call as a third participant. Both the agent and the caller can hear you. ### Request ```bash curl -X POST "https://{workspace_id}.firetell.app/api/v1/call-center/calls/cl_a1b2c3d4e5f6/barge" \ -H "Authorization: Bearer YOUR_SUPERVISOR_JWT" ``` ### Response `200 OK` ```json { "call_id": "cl_a1b2c3d4e5f6", "mode": "barge", "supervisor": "supervisor.jane", "status": "supervision_started", "call_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "expires_in": 900 } ``` --- ## Stop Supervision ``` DELETE /call-center/calls/:call_id/supervision ``` Stop any active supervision session on a call. ### Request ```bash curl -X DELETE "https://{workspace_id}.firetell.app/api/v1/call-center/calls/cl_a1b2c3d4e5f6/supervision" \ -H "Authorization: Bearer YOUR_SUPERVISOR_JWT" ``` ### Response `200 OK` ```json { "call_id": "cl_a1b2c3d4e5f6", "supervisor": "supervisor.jane", "status": "supervision_stopped" } ``` --- ## Call Transfer Any active agent can transfer an active call to another agent or department. ``` POST /call-center/calls/:call_id/transfer ``` ### Request Body | Field | Type | Required | Description | | ----------------- | ------ | -------- | -------------------------------------------------------------- | | `target_username` | string | ✅ | Username of the target agent to receive the transfer | | `team_id` | string | ❌ | Optional target team ID (auto-detected if omitted) | ### Request ```bash curl -X POST "https://{workspace_id}.firetell.app/api/v1/call-center/calls/cl_a1b2c3d4e5f6/transfer" \ -H "Authorization: Bearer YOUR_AGENT_JWT" \ -H "Content-Type: application/json" \ -d '{ "target_username": "agent.bob" }' ``` ### Response `200 OK` ```json { "call_id": "cl_a1b2c3d4e5f6", "status": "transfer_initiated", "from_agent_id": "ag_original123", "to_agent": { "id": "ag_target456", "username": "agent.bob" }, "transferred_by": "agent.jane" } ``` ### Error Responses | Status | Description | | ------ | -------------------------------------------------------------------- | | `400` | Target agent not found, not in specified team, or not available | | `404` | Call not found or not active | :::tip The target agent must have `available` presence state to receive a transfer. Check agent states first using `GET /call-center/teams/:team_id/agents/states`. ::: ## Error Responses | Status | Description | | ------ | ----------------------------------------------------------- | | `403` | Not a supervisor, or call agent is not in a supervised team | | `404` | Call not found or not active | --- # Outbound Calls & WebRTC Signaling Outbound calls are initiated via HTTP REST API (`POST /call-center/calls`). The API validates permissions and returns a short-lived **`call_token`** and `ws_url`. The client then opens a native WebSocket connection for WebRTC SDP signaling scoped exclusively to that call. --- ## Make Outbound Call ``` POST /call-center/calls ``` ### Request Body | Field | Type | Required | Description | | ------ | ------ | -------- | --------------------------------------------------------- | | `to` | string | ✅ | Recipient phone number or extension (e.g. `+84901234567`) | | `from` | string | ❌ | Outbound Caller ID phone number (e.g. `+842471000000`) | | `type` | string | ❌ | Call type: `audio` (default) or `video` | ### Request ```bash curl -X POST "https://{workspace_id}.firetell.app/api/v1/call-center/calls" \ -H "Authorization: Bearer YOUR_AGENT_JWT" \ -H "Content-Type: application/json" \ -d '{ "to": "+84901234567", "from": "+842471000000" }' ``` ### Response `201 Created` ```json { "call_id": "cl_a1b2c3d4e5f6g7h8i9j0k1", "status": "created", "call_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "ws_url": "wss://{workspace_id}.firetell.app/ws", "expires_in": 900 } ``` --- ## Native WebSocket Signaling Protocol Once `call_token` is received, open a native WebSocket to `ws_url`. :::important **3-Second Authentication Requirement** Upon opening the WebSocket connection, the client **must send the `session.connect` event within 3 seconds**, containing the `call_token`. Unauthenticated sockets are automatically terminated after 3 seconds. ::: ### 1. Handshake (`session.connect`) **Client Send:** ```json { "event": "session.connect", "data": { "token": "YOUR_CALL_TOKEN" } } ``` **Server Response:** ```json { "event": "session.connected", "data": { "session_id": "socket_uuid_123", "workspace_id": "ws_123456789", "username": "agent.john", "call_id": "cl_a1b2c3d4e5f6g7h8i9j0k1", "mode": "participant" } } ``` --- ### 2. WebRTC SDP Offer (`call.offer`) **Server Dispatch for Incoming Call:** ```json { "event": "call.offer", "data": { "call_id": "cl_a1b2c3d4e5f6g7h8i9j0k1", "from": "+84901234567", "from_name": "Nguyen Van A (VIP)", "to": "+842471000000", "sdp": { "type": "offer", "sdp": "v=0\r\no=- 123456789 2 IN IP4 127.0.0.1..." }, "is_transfer": false } } ``` **Client Send for Outbound Call:** ```json { "event": "call.offer", "data": { "call_id": "cl_a1b2c3d4e5f6g7h8i9j0k1", "sdp": { "type": "offer", "sdp": "v=0\r\no=- 123456789 2 IN IP4 127.0.0.1..." } } } ``` **Server Response:** ```json { "event": "call.offered", "data": { "call_id": "cl_a1b2c3d4e5f6g7h8i9j0k1" } } ``` --- ### 3. ICE Candidates (`call.candidate`) **Client Send:** ```json { "event": "call.candidate", "data": { "call_id": "cl_a1b2c3d4e5f6g7h8i9j0k1", "candidate": { "candidate": "candidate:1 1 UDP 2013266431 192.168.1.1 54321 typ host", "sdpMid": "0", "sdpMLineIndex": 0 } } } ``` :::info **ICE Candidate Signaling Note** Firetell Voice Platform uses **Full SDP Exchange (Non-Trickle ICE)** where ICE candidates (`a=candidate:...`) are bundled directly within the initial SDP offer (`call.offer`) and SDP answer (`call.answer`). Sending individual `call.candidate` events via WebSocket is supported for compatibility (acknowledged with `call.candidate_ack`), but bundling candidates directly in the SDP is the recommended and primary mechanism for media establishment. ::: --- ### 4. End Call (`call.hangup` / `call.ended`) **Client Send (`call.hangup`):** To hang up the call: ```json { "event": "call.hangup", "data": { "call_id": "cl_a1b2c3d4e5f6g7h8i9j0k1" } } ``` **Server Dispatch (`call.ended`):** Notifies the client that the call session has been terminated. ```json { "event": "call.ended", "data": { "call_id": "cl_a1b2c3d4e5f6g7h8i9j0k1", "reason": "hangup" } } ``` --- ### 5. Answer Call (`call.answer` / `call.answered`) **Client Send (`call.answer`):** Sent by the client to accept an incoming call, containing the client's WebRTC SDP answer. ```json { "event": "call.answer", "data": { "call_id": "cl_a1b2c3d4e5f6g7h8i9j0k1", "sdp": "v=0\r\no=- 123456789 2 IN IP4 127.0.0.1..." } } ``` **Server Dispatch (`call.answered`):** Notifies the client (e.g. for an outbound call) that the call has been answered. ```json { "event": "call.answered", "data": { "call_id": "cl_a1b2c3d4e5f6g7h8i9j0k1", "sdp": "v=0\r\no=- 123456789 2 IN IP4 127.0.0.1..." } } ``` --- ### 6. Reject Call (`call.reject` / `call.rejected`) **Client Send (`call.reject`):** Sent by the client to reject an incoming call. ```json { "event": "call.reject", "data": { "call_id": "cl_a1b2c3d4e5f6g7h8i9j0k1" } } ``` **Server Dispatch (`call.rejected`):** Notifies the client that the call was rejected. ```json { "event": "call.rejected", "data": { "call_id": "cl_a1b2c3d4e5f6g7h8i9j0k1", "reason": "rejected" } } ``` --- ### 7. Hold Call (`call.hold` / `call.held`) **Client Send (`call.hold`):** Sent by the client to place the active call on hold. ```json { "event": "call.hold", "data": { "call_id": "cl_a1b2c3d4e5f6g7h8i9j0k1", "sdp": { "type": "offer", "sdp": "v=0\r\no=- 123456789 2 IN IP4 127.0.0.1..." } } } ``` **Server Dispatch (`call.held`):** Notifies the client that the call was successfully held. ```json { "event": "call.held", "data": { "call_id": "cl_a1b2c3d4e5f6g7h8i9j0k1", "sdp": { "type": "answer", "sdp": "v=0\r\no=- 123456789 2 IN IP4 127.0.0.1..." } } } ``` --- ### 8. Resume Call (`call.unhold` / `call.unheld`) **Client Send (`call.unhold`):** Sent by the client to take the call off hold. ```json { "event": "call.unhold", "data": { "call_id": "cl_a1b2c3d4e5f6g7h8i9j0k1", "sdp": { "type": "offer", "sdp": "v=0\r\no=- 123456789 2 IN IP4 127.0.0.1..." } } } ``` **Server Dispatch (`call.unheld`):** Notifies the client that the call was successfully resumed. ```json { "event": "call.unheld", "data": { "call_id": "cl_a1b2c3d4e5f6g7h8i9j0k1", "sdp": { "type": "answer", "sdp": "v=0\r\no=- 123456789 2 IN IP4 127.0.0.1..." } } } ``` --- ### 9. Mute Call (`call.mute`) **Client Send:** Sent by the client to update the local microphone mute state. ```json { "event": "call.mute", "data": { "call_id": "cl_a1b2c3d4e5f6g7h8i9j0k1", "muted": true } } ``` --- ### 10. Send DTMF (`call.dtmf`) **Client Send:** Sent by the client to play DTMF tones on the call. ```json { "event": "call.dtmf", "data": { "call_id": "cl_a1b2c3d4e5f6g7h8i9j0k1", "digit": "5", "duration": 250 } } ``` --- ### 11. Transfer Call (`call.transfer` / `call.transferred`) **Client Send (`call.transfer`):** Sent by the client to transfer the call to another agent. ```json { "event": "call.transfer", "data": { "call_id": "cl_a1b2c3d4e5f6g7h8i9j0k1", "to": "agent.smith", "team_id": "te_123456" } } ``` **Server Dispatch (`call.transferred`):** Notifies the client that the call transfer is complete. ```json { "event": "call.transferred", "data": { "call_id": "cl_a1b2c3d4e5f6g7h8i9j0k1", "reason": "transferred" } } ``` --- ## Client-Initiated Call Flows (client-api) When integrating calling capabilities into end-user client applications (e.g., driver apps, customer support widgets) using **`client-api`** JWT tokens, calls are routed through a Visual Call Flow builder rather than being connected directly as normal operator/agent calls. ### Workflow & Routing 1. **JWT Verification:** The client-api JWT token **must** contain a valid `call_flow_id` claim in its payload. 2. **Call Initiation:** The client makes a `POST /call-center/calls` request. The API validates that the `call_flow_id` exists in the workspace and has `trigger_type` set to `'outbound'`. 3. **Internal VoIP Route:** Direct PSTN dialing is disabled for the `client-api` audience. The call behaves as an internal WebRTC VoIP call. 4. **Flow Execution:** When the client connects via WebSocket and initiates the call, Firetell automatically routes the call directly into the compiled Call Flow actions (e.g., executing an IVR, connecting the caller to a team, or routing to a specific agent). --- :::tip **Auto-Hangup on Disconnect** Closing or dropping the WebSocket connection automatically ends active calls associated with that `call_id`. ::: --- # Contacts Manage contacts within your workspace. Contacts store customer or lead information including phone numbers, emails, and custom metadata. They can be organized into [Address Books](/docs/rest-api/agent-api/address-books). :::info **Data Scoping** Agents can only see contacts within [Address Books](/docs/rest-api/agent-api/address-books) they have access to (books owned by them `owner_type: agent`, books owned by teams they belong to `owner_type: team`, or shared books `owner_type: everyone`). ::: ## Endpoints | Method | Endpoint | Description | | -------- | ----------------------------------- | ------------------------------- | | `GET` | `/call-center/contacts` | List all contacts | | `GET` | `/call-center/contacts/lookup` | Look up contact by phone number | | `GET` | `/call-center/contacts/:id` | Get contact details | | `POST` | `/call-center/contacts` | Create a new contact | | `POST` | `/call-center/contacts/assign-list` | Batch assign contacts to a list | | `PUT` | `/call-center/contacts/:id` | Update a contact | | `DELETE` | `/call-center/contacts/:id` | Delete a contact | ## Authentication All endpoints require `Bearer` JWT with `agent-api` audience. ## Contact Object | Field | Type | Description | | ------------------- | -------- | ----------------------------------------------------------------------- | | `id` | string | Unique contact ID (prefixed with `ct_`) | | `workspace_id` | string | Workspace identifier | | `address_book_id` | string | ID of the address book this contact belongs to (defaults to Default book if omitted) | | `first_name` | string | Contact first name | | `last_name` | string | Contact last name | | `company` | string | Company name | | `title` | string | Job title | | `emails` | object[] | List of email addresses (see [Email Object](#email-object)) | | `phone_numbers` | object[] | List of phone numbers (see [Phone Number Object](#phone-number-object)) | | `tags` | string[] | Tags for categorization | | `notes` | string | Free-text notes (max 1000 chars) | | `timezone` | string | Contact timezone (e.g., `"Asia/Ho_Chi_Minh"`) | | `language` | string | Preferred language (e.g., `"vi"`, `"en"`) | | `country_code` | string | ISO 3166-1 alpha-2 country code (e.g., `"VN"`) | | `assigned_agent_id` | string | Optional ID of the assigned agent responsible for this contact | | `source` | string | Origin of this contact (e.g., `"manual"`, `"import"`, `"call"`) | | `custom_fields` | object | Key-value pairs for custom data | | `created_at` | string | ISO 8601 creation timestamp | | `updated_at` | string | ISO 8601 last update timestamp | ### Phone Number Object | Field | Type | Description | | -------------- | ------- | ------------------------------------------------- | | `phone_number` | string | Phone number string (e.g., `"+84901234567"`) | | `type` | string | Type label: `mobile`, `work`, `home`, `fax`, etc. | | `is_primary` | boolean | Whether this is the primary phone number | ### Email Object | Field | Type | Description | | ------------ | ------- | ------------------------------------ | | `email` | string | Email address | | `type` | string | Type label: `work`, `personal`, etc. | | `is_primary` | boolean | Whether this is the primary email | --- ## List Contacts ``` GET /call-center/contacts ``` Retrieve a paginated list of all contacts in the workspace. ### Query Parameters | Parameter | Type | Default | Description | | ------------ | ------ | ------- | ----------------------------------------------------------- | | `page` | number | `1` | Page number (min: 1) | | `limit` | number | `20` | Items per page (min: 1, max: 100) | | `search` | string | — | Search by name, phone number, or email (text search) | | `sort_field` | string | — | Sort by: `created_at`, `first_name`, `last_name`, `company` | | `sort_order` | string | `desc` | Sort order: `asc` or `desc` | ### Request ```bash curl -X GET "https://{workspace_id}.firetell.app/api/v1/call-center/contacts?page=1&limit=10&search=john" \ -H "Authorization: Bearer YOUR_AGENT_JWT" ``` ### Response `200 OK` ```json { "data": [ { "id": "ct_a1b2c3d4e5f6g7h8i9j0k1", "workspace_id": "my_workspace", "address_book_id": "ab_x1y2z3w4v5u6t7s8", "first_name": "John", "last_name": "Doe", "company": "Acme Corp", "title": "CTO", "emails": [ { "email": "john@acme.com", "type": "work", "is_primary": true } ], "phone_numbers": [ { "phone_number": "+84901234567", "type": "mobile", "is_primary": true } ], "tags": ["vip", "enterprise"], "notes": "Key decision maker", "timezone": "Asia/Ho_Chi_Minh", "language": "en", "country_code": "VN", "owner_id": "ag_abc123", "owner_type": "agent", "source": "manual", "custom_fields": { "deal_size": "50000" }, "created_at": "2026-01-15T08:30:00.000Z", "updated_at": "2026-07-13T09:00:00.000Z" } ], "meta": { "total": 1, "page": 1, "limit": 10, "total_pages": 1 } } ``` --- ## Lookup Contact by Phone Number ``` GET /call-center/contacts/lookup ``` Find a contact by their phone number. The phone number is automatically normalized for matching, so formats like `+84901234567`, `0901234567`, or `84901234567` all match the same contact. ### Query Parameters | Parameter | Type | Required | Description | | --------- | ------ | -------- | ----------------------- | | `number` | string | ✅ | Phone number to look up | ### Request ```bash curl -X GET "https://{workspace_id}.firetell.app/api/v1/call-center/contacts/lookup?number=84901234567" \ -H "Authorization: Bearer YOUR_AGENT_JWT" ``` ### Response `200 OK` Returns the matching contact object, or `null` if no contact is found. ```json { "id": "ct_a1b2c3d4e5f6g7h8i9j0k1", "first_name": "John", "last_name": "Doe", "company": "Acme Corp", "phone_numbers": [ { "phone_number": "+84901234567", "type": "mobile", "is_primary": true } ] } ``` :::tip This endpoint is commonly used during incoming calls to display caller information. The call flow engine also uses it internally for the **Contact Lookup** node. ::: --- ## Get Contact Details ``` GET /call-center/contacts/:id ``` Retrieve detailed information about a specific contact. ### Path Parameters | Parameter | Type | Description | | --------- | ------ | --------------------------------- | | `id` | string | Contact ID (e.g., `ct_a1b2c3...`) | ### Request ```bash curl -X GET "https://{workspace_id}.firetell.app/api/v1/call-center/contacts/ct_a1b2c3d4e5f6g7h8i9j0k1" \ -H "Authorization: Bearer YOUR_AGENT_JWT" ``` ### Response `200 OK` Returns the full contact object. ### Error Responses | Status | Description | | ------ | ----------------- | | `404` | Contact not found | --- ## Create a Contact ``` POST /call-center/contacts ``` Create a new contact in the workspace. All fields except the basic identity fields are optional. ### Request Body | Field | Type | Required | Description | | ----------------- | -------------- | -------- | --------------------------------------------------------------- | | `first_name` | string | | First name (max 100 chars) | | `last_name` | string | | Last name (max 100 chars) | | `company` | string | | Company name (max 100 chars) | | `title` | string | | Job title (max 100 chars) | | `emails` | object[] | | Email addresses (see [Email Object](#email-object)) | | `phone_numbers` | object[] | | Phone numbers (see [Phone Number Object](#phone-number-object)) | | `tags` | string[] | | Tags for categorization | | `notes` | string | | Free-text notes (max 1000 chars) | | `timezone` | string | | Timezone identifier (max 50 chars) | | `language` | string | | Language code (max 10 chars) | | `country_code` | string | | ISO country code (max 10 chars) | | `address_book_id` | string | | Assign to an address book (defaults to Default book if omitted) | | `assigned_agent_id` | string | | Optional assigned agent responsible for this contact | | `source` | string | | Origin label (max 50 chars) | | `custom_fields` | object | | Key-value pairs for custom data | ### Request ```bash curl -X POST "https://{workspace_id}.firetell.app/api/v1/call-center/contacts" \ -H "Authorization: Bearer YOUR_AGENT_JWT" \ -H "Content-Type: application/json" \ -d '{ "first_name": "Jane", "last_name": "Smith", "company": "TechStart Inc.", "title": "Product Manager", "emails": [ { "email": "jane@techstart.io", "type": "work", "is_primary": true } ], "phone_numbers": [ { "phone_number": "+84912345678", "type": "mobile", "is_primary": true }, { "phone_number": "+84281234567", "type": "work" } ], "tags": ["lead", "tech"], "notes": "Met at TechConf 2026", "timezone": "Asia/Ho_Chi_Minh", "language": "vi", "country_code": "VN", "address_book_id": "ab_x1y2z3w4v5u6t7s8", "owner_type": "agent", "source": "manual" }' ``` ### Response `201 Created` Returns the created contact object. ```json { "id": "ct_n3w1d2e3f4g5h6i7j8k9l0", "workspace_id": "my_workspace", "first_name": "Jane", "last_name": "Smith", "company": "TechStart Inc.", "title": "Product Manager", "emails": [ { "email": "jane@techstart.io", "type": "work", "is_primary": true } ], "phone_numbers": [ { "phone_number": "+84912345678", "type": "mobile", "is_primary": true, "normalized_number": "84912345678" }, { "phone_number": "+84281234567", "type": "work", "normalized_number": "84281234567" } ], "tags": ["lead", "tech"], "notes": "Met at TechConf 2026", "address_book_id": "ab_x1y2z3w4v5u6t7s8", "owner_type": "agent", "source": "manual", "created_at": "2026-07-21T08:00:00.000Z", "updated_at": "2026-07-21T08:00:00.000Z" } ``` ### Error Responses | Status | Description | | ------ | ----------------------------------------------- | | `400` | Validation error or address book does not exist | :::info Phone numbers are automatically normalized for consistent lookup. The `normalized_number` field is generated server-side and used for the [Lookup](#lookup-contact-by-phone-number) endpoint. ::: --- ## Update a Contact ``` PUT /call-center/contacts/:id ``` Update an existing contact. All fields are optional — only include the fields you want to change. ### Path Parameters | Parameter | Type | Description | | --------- | ------ | ----------- | | `id` | string | Contact ID | ### Request Body Same fields as [Create a Contact](#create-a-contact). All fields are optional. ### Request ```bash curl -X PUT "https://{workspace_id}.firetell.app/api/v1/call-center/contacts/ct_a1b2c3d4e5f6g7h8i9j0k1" \ -H "Authorization: Bearer YOUR_AGENT_JWT" \ -H "Content-Type: application/json" \ -d '{ "company": "TechStart Global", "title": "VP of Product", "tags": ["vip", "enterprise", "tech"] }' ``` ### Response `200 OK` Returns the updated contact object. ### Error Responses | Status | Description | | ------ | ----------------------------------------------- | | `400` | Validation error or address book does not exist | | `404` | Contact not found | --- ## Delete a Contact ``` DELETE /call-center/contacts/:id ``` Permanently delete a contact from the workspace. This action cannot be undone. ### Path Parameters | Parameter | Type | Description | | --------- | ------ | ----------- | | `id` | string | Contact ID | ### Request ```bash curl -X DELETE "https://{workspace_id}.firetell.app/api/v1/call-center/contacts/ct_a1b2c3d4e5f6g7h8i9j0k1" \ -H "Authorization: Bearer YOUR_AGENT_JWT" ``` ### Response `200 OK` Returns the deleted contact object. ### Error Responses | Status | Description | | ------ | ----------------- | | `404` | Contact not found | :::caution Deleting a contact is **irreversible**. Consider updating the contact's `tags` or moving it to a different address book instead if you need to archive it. ::: --- ## Assign Contacts to an Address Book ``` POST /call-center/contacts/assign-list ``` Batch assign multiple contacts to an address book (or unassign by passing `null`). You can assign up to **50 contacts** per request. ### Request Body | Field | Type | Required | Description | | ----------------- | -------------- | -------- | ------------------------------------------------------------------------ | | `address_book_id` | string \| null | ✅ | Target address book ID. Pass `null` to unassign contacts from their book | | `contact_ids` | string[] | ✅ | Array of contact IDs to assign (min: 1, max: 50) | ### Request ```bash curl -X POST "https://{workspace_id}.firetell.app/api/v1/call-center/contacts/assign-list" \ -H "Authorization: Bearer YOUR_AGENT_JWT" \ -H "Content-Type: application/json" \ -d '{ "address_book_id": "ab_x1y2z3w4v5u6t7s8", "contact_ids": [ "ct_a1b2c3d4e5f6g7h8i9j0k1", "ct_n3w1d2e3f4g5h6i7j8k9l0", "ct_z9y8x7w6v5u4t3s2r1q0p9" ] }' ``` ### Response `200 OK` ```json { "matched": 3, "modified": 3 } ``` | Field | Type | Description | | ---------- | ------ | ----------------------------------------------------- | | `matched` | number | Number of contacts found that you have access to | | `modified` | number | Number of contacts actually updated (excludes no-ops) | :::tip If `matched` is less than the number of `contact_ids` you sent, it means some contacts don't exist or you don't have access to them. ::: ### Unassign Contacts To remove contacts from their current address book, pass `null` as the `address_book_id`: ```bash curl -X POST "https://{workspace_id}.firetell.app/api/v1/call-center/contacts/assign-list" \ -H "Authorization: Bearer YOUR_AGENT_JWT" \ -H "Content-Type: application/json" \ -d '{ "address_book_id": null, "contact_ids": ["ct_a1b2c3d4e5f6g7h8i9j0k1"] }' ``` ### Error Responses | Status | Description | | ------ | ------------------------------------------ | | `400` | Validation error or address book not found | --- # Phone Numbers View phone numbers available to the teams you belong to. These endpoints are **read-only** — phone number management (create, update, delete) is only available via the admin Workspace API. :::info Data Scoping Agents can only see phone numbers where: - `shared_teams_id` contains at least one of the agent's teams, **or** - `shared_teams_id` is empty (available to all agents) Only phone numbers with `active` status are returned. ::: ## Endpoints | Method | Endpoint | Description | | ------ | ------------------------------------- | ----------------------------------- | | `GET` | `/call-center/phone-numbers` | List phone numbers (scoped) | | `GET` | `/call-center/phone-numbers/:id` | Get phone number details | ## Authentication All endpoints require `Bearer` JWT with `agent-api` audience. --- ## List Phone Numbers ``` GET /call-center/phone-numbers ``` Retrieve a paginated list of phone numbers available to your teams. ### Query Parameters | Parameter | Type | Default | Description | | ------------ | ------ | ------- | ------------------------ | | `page` | number | `1` | Page number (min: 1) | | `limit` | number | `20` | Items per page (max: 100)| | `sort_order` | string | `desc` | Sort order: `asc` or `desc` | ### Request ```bash curl -X GET "https://{workspace_id}.firetell.app/api/v1/call-center/phone-numbers?page=1&limit=10" \ -H "Authorization: Bearer YOUR_AGENT_JWT" ``` ### Response `200 OK` ```json { "data": [ { "id": "pn_a1b2c3d4e5f6g7h8i9j0k1", "number": "84901234567", "country_code": "VN", "dial_code": "84", "status": "active", "capabilities": ["voice"], "enable_outbound": true, "shared_teams_id": ["tm_x1y2z3w4v5u6t7s8"], "created_at": "2026-01-10T10:00:00.000Z" }, { "id": "pn_n3w1d2e3f4g5h6i7j8k9l0", "number": "84281234567", "country_code": "VN", "dial_code": "84", "status": "active", "capabilities": ["voice"], "enable_outbound": false, "shared_teams_id": [], "created_at": "2026-03-15T08:00:00.000Z" } ], "meta": { "total": 3, "page": 1, "limit": 10, "total_pages": 1 } } ``` ### Phone Number Object | Field | Type | Description | | ------------------ | -------- | ---------------------------------------------------- | | `id` | string | Phone number ID (prefixed `pn_`) | | `number` | string | Full phone number (without `+`) | | `country_code` | string | ISO 3166-1 alpha-2 country code (e.g., `"VN"`) | | `dial_code` | string | Country calling code (e.g., `"84"`) | | `status` | string | Always `active` for call center endpoints | | `capabilities` | string[] | Supported features (e.g., `["voice"]`) | | `enable_outbound` | boolean | Whether outbound calls are enabled | | `shared_teams_id` | string[] | Teams that have access to this number | | `created_at` | string | ISO 8601 creation timestamp | --- ## Get Phone Number Details ``` GET /call-center/phone-numbers/:id ``` Retrieve details of a specific phone number you have access to. ### Path Parameters | Parameter | Type | Description | | --------- | ------ | ------------------------------------- | | `id` | string | Phone number ID (e.g., `pn_a1b2...`) | ### Request ```bash curl -X GET "https://{workspace_id}.firetell.app/api/v1/call-center/phone-numbers/pn_a1b2c3d4e5f6g7h8i9j0k1" \ -H "Authorization: Bearer YOUR_AGENT_JWT" ``` ### Response `200 OK` Returns the phone number object. ### Error Responses | Status | Description | | ------ | ------------------------------------------------ | | `404` | Phone number not found or you do not have access | :::tip Use the `enable_outbound` field to determine which phone numbers can be used as the caller ID when making outbound calls. Numbers with `enable_outbound: false` can only receive inbound calls. ::: --- # Realtime Events (SSE) Firetell provides a **Server-Sent Events (SSE)** stream for subscribing to real-time business events. Unlike WebSocket connections, SSE operates over standard HTTP/2 and natively handles automatic reconnections. :::info **Architecture Note** SSE is used for **server-to-client event observation** (agent presence, teammate status changes, team assignments, contact mutations, queue updates). Call audio and WebRTC signaling use short-lived per-call WebSockets with `call_token`. ::: ## Endpoints | Method | Endpoint | Content-Type | Description | | ------ | -------------------------------------------------- | ------------------- | ------------------------------ | | `GET` | `/stream?token=YOUR_AGENT_JWT` | `text/event-stream` | Subscribes to real-time events | | `PUT` | `/call-center/events/presence` | `application/json` | Set own presence (`busy` / `ready`) | ## Authentication Authentication is passed via the **`token` query parameter**: `?token=YOUR_AGENT_JWT`. :::info **Why Query Parameter?** The standard W3C browser `EventSource` API does not support custom HTTP headers (such as `Authorization: Bearer`). Passing the JWT via `?token=` query parameter allows native browser `EventSource` to authenticate seamlessly. ::: --- ## Subscribing via JavaScript `EventSource` ```javascript const agentJwt = "YOUR_AGENT_JWT"; const sseUrl = `https://{workspace_id}.firetell.app/stream?token=${encodeURIComponent(agentJwt)}`; const eventSource = new EventSource(sseUrl); // Incoming Call Ring Alert Event eventSource.addEventListener("call.ring", (e) => { const data = JSON.parse(e.data); console.log("Incoming call ring alert:", data.call_id); console.log("Caller:", data.from.name, data.from.number); console.log("WebSocket URL:", data.ws_url); console.log("Call Token:", data.call_token); // Connect WebSocket per call using ws_url and call_token const ws = new WebSocket(data.ws_url); ws.onopen = () => { ws.send(JSON.stringify({ event: "session.connect", data: { token: data.call_token } })); }; }); // Agent State Change Event eventSource.addEventListener("agent.state", (e) => { const data = JSON.parse(e.data); console.log("Teammate state changed:", data.username, data.state); }); // Contact Created Event eventSource.addEventListener("contact.created", (e) => { const eventData = JSON.parse(e.data); console.log("New contact created:", eventData.data); }); // Team Assigned Event eventSource.addEventListener("team.assigned", (e) => { const data = JSON.parse(e.data); console.log("Assigned to team:", data.team_id, "Role:", data.role); }); ``` --- ## Set Agent Presence Agents can set their own presence state to `busy` (Do Not Disturb) or `ready` (return to active state). :::info **`ready` is not a stored state** — it's a command that tells the system to revert the agent to their appropriate active state based on connectivity: - If the agent has an active SSE connection → `online` - If the agent has a registered VoIP push device → `available` - Otherwise → `offline` ::: ### Request ``` PUT /call-center/events/presence Authorization: Bearer YOUR_AGENT_JWT Content-Type: application/json ``` **Request Body:** | Field | Type | Required | Description | | ----- | ---- | -------- | ----------- | | `state` | `string` | ✅ | `busy` to set Do Not Disturb, `ready` to return to active state | ```json { "state": "busy" } ``` ### Response ```json { "username": "agent.jane", "state": "busy" } ``` **Unset busy (return to active):** ```json // Request { "state": "ready" } // Response — state determined by connectivity { "username": "agent.jane", "state": "online" } ``` --- ## Event Types ### `system.connected` Emitted immediately upon opening the SSE stream. Contains the list of Server-Sent Events (SSE) Pub/Sub channels subscribed for the agent (`ws:{workspace_id}:{username}`, `presence:{workspace_id}`, `workspace:{workspace_id}`, and `team:{workspace_id}:{team_id}` for all teams the agent belongs to). ```json { "event": "system.connected", "workspace_id": "ws_123456789", "username": "agent.jane", "subscribed_channels": [ "ws:ws_123456789:agent.jane", "presence:ws_123456789", "workspace:ws_123456789", "team:ws_123456789:tm_sales123" ], "timestamp": "2026-07-21T19:20:00.000Z" } ``` ### `system.ping` Emitted periodically every **15 seconds** to maintain stream activity and prevent intermediate proxies, NGINX, or load balancers from closing the connection due to idle timeouts. ```json { "event": "system.ping", "timestamp": "2026-07-21T19:20:15.000Z" } ``` ### `call.ring` Emitted when an incoming or transferred call is ringing for the agent. Triggered instantly over the SSE stream to allow the client application to display the Ringing UI Popup and establish a short-lived WebSocket connection to `ws_url` per call using the provided `call_token`. ```json { "event": "call.ring", "call_id": "call_1770000000000", "call_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "ws_url": "wss://ws_123456789.firetell.app/ws", "from": { "number": "+84901234567", "name": "Nguyen Van A" }, "to": { "number": "+842471000000", "name": "Support Team" }, "is_transfer": false, "timestamp": "2026-07-30T16:07:00.000Z" } ``` ### `agent.state` Emitted whenever an agent's presence state changes. State transitions happen automatically based on agent connectivity and call activity. | State | Description | | ----------- | ----------- | | `online` | Agent is actively connected via SSE event stream | | `available` | Agent is reachable via VoIP push notification (has registered device with push token) but not actively streaming SSE | | `incall` | Agent is currently in an active call (set when agent offers or answers a call, reverted when call ends) | | `busy` | Agent is busy (set manually or by supervisor) | | `offline` | Agent has no active connections or registered push devices | ```json { "event": "agent.state", "workspace_id": "ws_123456789", "username": "agent.jane", "state": "online", "timestamp": "2026-07-30T16:07:00.000Z" } ``` :::tip **State transition examples:** - Agent opens the app and connects SSE → `online` - Agent closes the app but has VoIP push token registered → `available` - Agent answers or makes a call → `incall` - Call ends while SSE is connected → `online` - Call ends while only push token exists → `available` - Agent logs out from all devices → `offline` ::: ### `agent.state.forced` Emitted when a team leader or supervisor force-changes an agent's state. Currently, supervisors can only force agents to `offline` state. Automatic states (`online`, `available`, `incall`) are managed by the system based on connectivity. ```json { "event": "agent.state.forced", "workspace_id": "ws_123456789", "id": "650000000000000000000001", "target_agent_id": "650000000000000000000001", "target_username": "agent.john", "new_state": "offline", "reason": "Shift ended", "forced_by": "supervisor.smith", "timestamp": "2026-07-30T18:00:00.000Z" } ``` ### `agent.created` / `agent.updated` / `agent.deleted` Emitted when a workspace agent account is created, updated, or deleted. ```json { "event": "agent.created", "workspace_id": "ws_123456789", "data": { "id": "650000000000000000000001", "username": "agent.john", "display_name": "John Doe", "email": "john@company.com", "role": "agent", "avatar": "https://cdn.firetell.com/avatars/agent_john.png", "country_code": "US", "is_active": true }, "timestamp": "2026-08-03T15:00:00.000Z" } ``` ### `contact.created` / `contact.updated` / `contact.deleted` Emitted when a contact is created, updated, or deleted within the workspace. ```json { "event": "contact.created", "workspace_id": "ws_123456789", "data": { "id": "ct_987654321", "display_name": "John Doe", "phone_numbers": [{ "phone_number": "+1234567890", "type": "mobile" }], "owner_type": "everyone" }, "timestamp": "2026-07-21T16:00:00.000Z" } ``` ### `team.assigned` / `team.unassigned` Emitted when an agent is assigned to or removed from a team. ```json { "event": "team.assigned", "workspace_id": "ws_123456789", "team_id": "tm_123456789", "agent_id": "ag_123456789", "role": "member", "timestamp": "2026-07-21T16:00:00.000Z" } ``` --- # Roles & Permissions Every agent in a workspace has a **role** that determines what they can do. The Firetell Call Center uses a 3-tier role system. ## Roles | Role | Description | |---|---| | **member** | Standard agent. Can make/receive calls, manage their own contacts, and view their own call history. | | **leader** | Team lead. Has all member permissions plus team-wide visibility and agent management capabilities. | | **supervisor** | Call center supervisor. Focuses on call quality assurance — real-time call supervision (listen, whisper, barge) plus team-wide visibility. | ## Permission Matrix | Feature | `member` | `leader` | `supervisor` | |---|:---:|:---:|:---:| | **Basic Operations** | | | | | View teams I belong to | ✅ | ✅ | ✅ | | View teammates + presence | ✅ | ✅ | ✅ | | CRUD my own contacts | ✅ | ✅ | ✅ | | View contacts in accessible lists (personal, team, shared) | ✅ | ✅ | ✅ | | Create / update / delete personal address books (`owner_type: agent`) | ✅ | ✅ | ✅ | | View phone numbers (scoped by team) | ✅ | ✅ | ✅ | | View my call history | ✅ | ✅ | ✅ | | Transfer calls between agents | ✅ | ✅ | ✅ | | **Team Management** | | | | | Create / update / delete team address books (`owner_type: team`) | ❌ | ✅ | ❌ | | Create / update / delete shared address books (`owner_type: everyone`) | ❌ | ✅ | ❌ | | View team-wide call history | ❌ | ✅ | ✅ | | View all team contacts | ❌ | ✅ | ✅ | | Assign / remove agents from teams | ❌ | ✅ | ❌ | | **Call Supervision** | | | | | Silent listen (monitor) | ❌ | ❌ | ✅ | | Whisper (coach agent) | ❌ | ❌ | ✅ | | Barge-in (3-way call) | ❌ | ❌ | ✅ | ## Setting Roles Roles are assigned when adding an agent to a team via the admin console or the Workspace API: ```bash PUT /teams/:team_id/agents/:agent_id ``` ```json { "role": "leader" } ``` Valid values: `member` (default), `leader`, `supervisor`. An agent has a **single workspace-level role** that applies across all teams they belong to. ::: ## Endpoint Access by Role ### All Agents (member, leader, supervisor) | Method | Endpoint | Description | |--------|----------|-------------| | `GET` | `/call-center/teams` | List my teams | | `GET` | `/call-center/teams/:id/agents` | List teammates | | `GET` | `/call-center/contacts` | List contacts in my accessible lists | | `POST` | `/call-center/contacts` | Create contact | | `PUT` | `/call-center/contacts/:id` | Update contact | | `DELETE` | `/call-center/contacts/:id` | Delete contact | | `GET` | `/call-center/address-books` | List accessible address books | | `GET` | `/call-center/address-books/:id` | Get address book details | | `POST` | `/call-center/address-books` | Create personal address book (`owner_type: agent`) | | `PUT` | `/call-center/address-books/:id` | Update personal address book | | `DELETE` | `/call-center/address-books/:id` | Delete personal address book | | `GET` | `/call-center/phone-numbers` | List phone numbers | | `GET` | `/call-center/call-history` | List my calls | | `POST` | `/call-center/calls/:call_id/transfer` | Transfer call | ### Leader Only | Method | Endpoint | Description | |--------|----------|-------------| | `POST` | `/call-center/address-books` | Create team/shared address book (`owner_type: team` or `everyone`) | | `PUT` | `/call-center/address-books/:id` | Update team/shared address book | | `DELETE` | `/call-center/address-books/:id` | Delete team/shared address book | | `GET` | `/call-center/teams/:team_id/call-history` | Team call history | | `GET` | `/call-center/teams/:team_id/call-history/:id` | Team call details | | `PUT` | `/call-center/teams/:id/agents/:agent_id` | Assign agent to team | | `DELETE` | `/call-center/teams/:id/agents/:agent_id` | Remove agent from team | | `GET` | `/call-center/teams/:team_id/contacts` | Team contacts | | `GET` | `/call-center/teams/:team_id/agents/states` | Team agent states | | `PUT` | `/call-center/teams/:team_id/agents/:username/state` | Force agent state | ### Leader & Supervisor | Method | Endpoint | Description | |--------|----------|-------------| | `GET` | `/call-center/teams/:team_id/call-history` | Team call history | | `GET` | `/call-center/teams/:team_id/call-history/:id` | Team call details | | `GET` | `/call-center/teams/:team_id/agents/states` | Team agent states | | `PUT` | `/call-center/teams/:team_id/agents/:username/state` | Force agent state | ### Supervisor Only | Method | Endpoint | Description | |--------|----------|-------------| | `POST` | `/call-center/calls/:call_id/listen` | Silent monitor | | `POST` | `/call-center/calls/:call_id/whisper` | Coach agent | | `POST` | `/call-center/calls/:call_id/barge` | Join as 3-way call | | `DELETE` | `/call-center/calls/:call_id/supervision` | Stop supervision | ## Error Responses When an agent tries to access an endpoint they don't have permission for: ```json { "statusCode": 403, "message": "This action requires one of the following roles: leader, supervisor", "error": "Forbidden" } ``` When an agent tries to access a team they don't belong to: ```json { "statusCode": 403, "message": "You are not a member of this team", "error": "Forbidden" } ``` --- # Teams View the teams you belong to and list your teammates. These endpoints are scoped — you can only see teams where you are a member. :::info Data Scoping Unlike the admin Teams API which returns all teams, the call center Teams API only returns teams where the authenticated agent is a member. ::: ## Endpoints | Method | Endpoint | Role Required | Description | | ------ | ----------------------------------------------------- | -------------------------- | --------------------------- | | `GET` | `/call-center/teams` | Any | List active workspace teams | | `GET` | `/call-center/teams/:id/agents` | Any | List teammates (if member) | | `PUT` | `/call-center/teams/:id/agents/:agent_id` | `leader` | Assign agent to team | | `DELETE` | `/call-center/teams/:id/agents/:agent_id` | `leader` | Remove agent from team | | `GET` | `/call-center/teams/:team_id/call-history` | `leader`, `supervisor` | Team call history | | `GET` | `/call-center/teams/:team_id/contacts` | `leader` | Team contacts | | `GET` | `/call-center/teams/:team_id/agents/states` | `leader`, `supervisor` | Team agent states | | `PUT` | `/call-center/teams/:team_id/agents/:username/state` | `leader`, `supervisor` | Force agent state | :::info Role checks (`leader`, `supervisor`) are based on the agent's **workspace-level role**, not a per-team assignment. Roles are managed by admins via the [Agents API](/docs/rest-api/workspace-api/agents). ::: ## Authentication All endpoints require `Bearer` JWT with `agent-api` audience. Some endpoints require specific team roles — see [Roles & Permissions](/docs/rest-api/agent-api/roles-permissions). --- ## List Active Workspace Teams ``` GET /call-center/teams ``` Retrieve a paginated list of all active teams in the workspace (useful for choosing target departments for call transfer). Note: To list only the teams you belong to, use `GET /me/teams`. ### Query Parameters | Parameter | Type | Default | Description | | --------- | ------ | ------- | ------------------------ | | `page` | number | `1` | Page number (min: 1) | | `limit` | number | `10` | Items per page (max: 100)| ### Request ```bash curl -X GET "https://{workspace_id}.firetell.app/api/v1/call-center/teams?page=1&limit=10" \ -H "Authorization: Bearer YOUR_AGENT_JWT" ``` ### Response `200 OK` ```json { "data": [ { "id": "tm_a1b2c3d4e5f6g7h8i9j0k1", "workspace_id": "my_workspace", "title": "Sales Team", "agent_count": 5, "created_at": "2026-01-10T10:00:00.000Z", "updated_at": "2026-07-01T12:00:00.000Z" }, { "id": "tm_x9y8z7w6v5u4t3s2r1q0p9", "workspace_id": "my_workspace", "title": "Support Team", "agent_count": 8, "created_at": "2026-02-15T08:00:00.000Z", "updated_at": "2026-06-20T09:30:00.000Z" } ], "meta": { "total": 2, "page": 1, "limit": 10, "total_pages": 1 } } ``` ### Team Object | Field | Type | Description | | -------------- | ------ | -------------------------------- | | `id` | string | Team ID | | `workspace_id` | string | Workspace identifier | | `title` | string | Team name | | `agent_count` | number | Number of agents in the team | | `created_at` | string | ISO 8601 creation timestamp | | `updated_at` | string | ISO 8601 last update timestamp | --- ## List Teammates ``` GET /call-center/teams/:id/agents ``` List agents in a specific team, excluding yourself. Includes real-time presence state. ### Path Parameters | Parameter | Type | Description | | --------- | ------ | ------------------------------------ | | `id` | string | Team ID (e.g., `tm_a1b2c3...`) | ### Query Parameters | Parameter | Type | Default | Description | | --------- | ------ | ------- | ------------------------ | | `page` | number | `1` | Page number (min: 1) | | `limit` | number | `10` | Items per page (max: 100)| ### Request ```bash curl -X GET "https://{workspace_id}.firetell.app/api/v1/call-center/teams/tm_a1b2c3d4e5f6g7h8i9j0k1/agents" \ -H "Authorization: Bearer YOUR_AGENT_JWT" ``` ### Response `200 OK` ```json { "data": [ { "username": "jane.smith", "display_name": "Jane Smith", "avatar": "https://storage.firetell.app/avatars/jane.jpg", "state": "available", "role": "leader" }, { "username": "bob.nguyen", "display_name": "Bob Nguyen", "avatar": null, "state": "on_call", "role": "member" }, { "username": "alice.tran", "display_name": "Alice Tran", "avatar": null, "state": "offline", "role": "member" } ], "meta": { "total": 4, "page": 1, "limit": 10, "total_pages": 1 } } ``` ### Teammate Object | Field | Type | Description | | -------------- | ------------ | -------------------------------------------------------- | | `username` | string | Agent username | | `display_name` | string | Display name | | `avatar` | string\|null | Avatar URL | | `state` | string | Real-time presence: `available`, `incall`, `busy`, `offline` | | `role` | string | Agent's workspace-level role: `agent`, `leader`, or `supervisor` | :::info The `role` field is the agent's **account-level role**, not a per-team assignment. Roles are managed globally by workspace admins. ::: ### Error Responses | Status | Description | | ------ | ----------------------------------- | | `403` | You are not a member of this team | | `404` | Agent not found | :::tip Use the `state` field to show real-time availability indicators in your call center UI. Agents with `available` state are ready to receive calls. ::: --- ## Assign Agent to Team ``` PUT /call-center/teams/:id/agents/:agent_id ``` Add an agent to a team. **Requires `leader` role.** ### Path Parameters | Parameter | Type | Description | | ---------- | ------ | --------------------------------------- | | `id` | string | Team ID (e.g., `tm_a1b2c3...`) | | `agent_id` | string | Agent ID to assign (e.g., `ag_x1y2...`) | ### Request ```bash curl -X PUT "https://{workspace_id}.firetell.app/api/v1/call-center/teams/tm_a1b2c3d4e5f6g7h8i9j0k1/agents/ag_x1y2z3w4v5u6t7s8" \ -H "Authorization: Bearer YOUR_AGENT_JWT" ``` ### Response `200 OK` Returns the created team-agent membership object. ### Error Responses | Status | Description | | ------ | ---------------------------------- | | `403` | Forbidden — requires `leader` role | | `404` | Team or agent not found | --- ## Remove Agent from Team ``` DELETE /call-center/teams/:id/agents/:agent_id ``` Remove an agent from a team. **Requires `leader` role.** ### Path Parameters | Parameter | Type | Description | | ---------- | ------ | --------------------------------------- | | `id` | string | Team ID (e.g., `tm_a1b2c3...`) | | `agent_id` | string | Agent ID to remove (e.g., `ag_x1y2...`) | ### Request ```bash curl -X DELETE "https://{workspace_id}.firetell.app/api/v1/call-center/teams/tm_a1b2c3d4e5f6g7h8i9j0k1/agents/ag_x1y2z3w4v5u6t7s8" \ -H "Authorization: Bearer YOUR_AGENT_JWT" ``` ### Response `200 OK` Returns the removed team-agent membership object. ### Error Responses | Status | Description | | ------ | ---------------------------------- | | `403` | Forbidden — requires `leader` role | | `404` | Team-agent membership not found | --- # Client API Overview & Authentication The **Client API** allows you to integrate external, third-party end-users, mobile apps, or custom web portals with the Firetell voice platform. Unlike the Agent API (which is designed for call center agents registered in the database), the Client API allows you to authenticate _any_ custom username on-the-fly using a signed JSON Web Token (JWT). --- ## 1. Authentication & JWT Payload To connect a client device via the SDK, your backend must generate and sign a JWT using the **Secret Key** of one of your workspace API Keys. The JWT must contain the following claims: | Claim | Type | Required | Description | | :------------- | :------- | :------- | :----------------------------------------------------------------------------- | | `iss` | `string` | **Yes** | The API Key SID (`sid-...`) used to sign the token. | | `aud` | `string` | **Yes** | Must be set to `client-api`. | | `sub` | `string` | **Yes** | Unique external username/ID (1-64 chars. Allowed: alphanumeric, `_`, `-`, `.`. No `@` or spaces). | | `domain` | `string` | **Yes** | Your workspace domain (e.g., `company.firetell.com`). | | `exp` | `number` | **Yes** | Expiration timestamp in seconds (Unix epoch time). | | `call_flow_id` | `string` | **Yes** | The outbound Call Flow ID that will handle outbound calls made by this client. | ### Payload Example ```json { "iss": "sid-650000000000000000000001", "aud": "client-api", "sub": "partner_customer_100", "domain": "acme.firetell.com", "exp": 1786022400, "call_flow_id": "cf_650000000000000000000100" } ``` :::important Subject (sub) Validation Rules To ensure secure and reliable connection handling and avoid formatting conflicts: - **Allowed characters:** Alphanumeric (`a-z`, `A-Z`, `0-9`), underscores (`_`), hyphens (`-`), and dots (`.`). - **Forbidden characters:** Special characters, whitespaces, and specifically the `@` symbol are strictly prohibited. - **Length:** The `sub` value must be between **1 and 64 characters** long. ::: --- ## 2. Dynamic Connection Quotas & Limits To protect the infrastructure from misuse or intentional DDoS attacks, the following limits are strictly enforced on Client API connections: - **Per-User Session Limit:** A single user ID (`sub`) can have a maximum of **5 concurrent active sessions** (e.g., open tabs or devices). - **Workspace Limit:** All Client API connections in a single workspace are capped at a total of **1000 concurrent active connections**. :::warning Requesting Limit Increases If your production application requires higher concurrent connection capacities, please contact our support team at [enterprise@firetell.com](mailto:enterprise@firetell.com) to request custom quotas. ::: --- ## 3. Real-Time Events (`call.ring`) Clients receive real-time updates (such as incoming call alerts) via a Server-Sent Events (SSE) stream at `/stream?token={JWT}`: - **Incoming Call Notification (`call.ring`):** When a call is routed to the client, the client device receives a `call.ring` event to trigger the ringer UI: ```json { "event": "call.ring", "call_id": "call_1770000000000", "call_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "ws_url": "wss://yourcompany.firetell.app/ws", "from": { "number": "agent_username", "name": "Your driver" }, "to": { "number": "partner_customer_100", "name": "Partner Customer Name" }, "timestamp": "2026-07-30T16:07:00.000Z" } ``` --- ## 4. Outbound Calling Rule (`call_flow_id`) When a client initiates an outbound call via the Client SDK (by calling the `makeCall` API), the following rules apply: - **Strict Call Flow Routing:** The call **must** be routed through the outbound Visual Call Flow identified by the `call_flow_id` claim in the client's JWT token. - **Pre-requisite:** The call flow assigned to `call_flow_id` must be pre-configured in the Workspace Portal and have its `trigger_type` set to `outbound`. - **Security & Control:** This restriction ensures that end-user calls are securely monitored, formatted, recorded (if configured), and filtered according to your business's outbound calling rules before routing to carriers or external DIDs. --- ## 5. Integrating with Client SDK Once you have generated a valid Client JWT on your backend, you can pass it to our official frontend Client SDK to initialize the real-time event stream, handle WebRTC audio/video connections, and manage the calling lifecycle on the device. To install the SDK, import it, and see complete usage guides, please refer to the [Client SDK Integration Guide](https://developers.firetell.com/docs/sdks/overview). --- ## 6. REST API Reference If you are implementing your own WebRTC signaling/client wrapper without using the official Client SDK, you can call the make call REST API directly using your Client JWT. ### Initiate Outbound Call Initiate an outbound WebRTC call and receive the signaling WebSocket session details. * **Endpoint:** `POST /api/v1/call-center/calls/make` * **Headers:** * `Authorization: Bearer ` * `Content-Type: application/json` #### Request Body ```json { "to": "agent_username", "from": "", "type": "audio" } ``` #### Response Example (`201 Created`) ```json { "call_id": "call_1770000000000", "ws_url": "wss://yourcompany.firetell.app/call-session", "call_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." } ``` After receiving this response, the client must connect to `ws_url` and send a `session.connect` event with the `call_token` within 3 seconds to complete signaling authentication. --- # REST API Overview The Firetell REST API allows you to programmatically manage your cloud communications infrastructure. ## Base URL Each workspace has its own API domain: ``` https://{workspace_id}.firetell.app/api/v1 ``` Replace `{workspace_id}` with your workspace's subdomain (found in **Console** → **Workspace Settings** → **General**). All endpoints are relative to this base URL. ## Request Format - All requests use **HTTPS** - Request bodies must be **JSON** with `Content-Type: application/json` - Authentication via `Authorization` header — `ApiKey sk-...` for Workspace API or `Bearer ` for Agent API (see [Authentication](/docs/getting-started/authentication)) ### Example Request ```bash curl -X GET "https://{workspace_id}.firetell.app/api/v1/phone-numbers" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" \ -H "Content-Type: application/json" ``` ## Response Format All responses return JSON. Successful responses include a `data` field: ```json { "data": [{ ... }], "meta": { "total": 100, "page": 1, "limit": 20, "total_pages": 5 } } ``` ## HTTP Status Codes | Code | Description | | ----- | ----------------------------------------- | | `200` | Success | | `201` | Resource created | | `400` | Bad request — invalid parameters | | `401` | Unauthorized — invalid or missing API key | | `403` | Forbidden — insufficient permissions | | `404` | Resource not found | | `429` | Rate limit exceeded | | `500` | Internal server error | ## Error Response Format When a request fails validation (HTTP `400`), the API returns a structured error body describing every invalid field: ```json { "statusCode": 400, "message": "name must not be empty", "error": "Bad Request", "errors": [ { "field": "name", "messages": ["name must not be empty", "name must be a string"] }, { "field": "address.city", "messages": ["city must be a string"] } ] } ``` | Field | Type | Description | | --- | --- | --- | | `message` | `string` | The first validation message — convenient for simple error display | | `errors` | `array` | Full list of all invalid fields | | `errors[].field` | `string` | Dot-notation path to the invalid field (e.g. `address.city` for nested objects) | | `errors[].messages` | `string[]` | All constraint violation messages for that field | :::tip Use `errors` to map validation feedback directly to form fields. The top-level `message` is kept for backward compatibility and quick display. ::: ## Pagination List endpoints support pagination with the following query parameters: | Parameter | Type | Default | Description | | --------- | ------ | ------- | ---------------------------- | | `page` | number | `1` | Page number | | `limit` | number | `20` | Items per page (max 100) | | `search` | string | — | Text search filter | | `sort` | string | — | Sort field | | `order` | string | `desc` | Sort order (`asc` or `desc`) | ## Rate Limiting All API endpoints are rate-limited to protect the platform and ensure fair usage. Rate limits are enforced per minute using a fixed-window counter backed by Firetell servers. ### Rate Limit Tiers | Tier | Limit | Endpoints | | ----------- | ----------- | ---------------------------------------------- | | **Default** | 100 req/min | All standard API endpoints | | **Auth** | 10 req/min | `/auth/login`, `/auth/verify`, `/auth/refresh` | | **Heavy** | 30 req/min | Upload, resource-intensive operations | Rate limits are applied **per API Key** when using API Key authentication, **per agent IP** when using agent JWT authentication (`agent-api`), or **per client identifier** when using client JWT authentication (`client-api`). :::note **Need higher limits?** Enterprise customers can request custom rate limits. Contact us at [enterprise@firetell.com](mailto:enterprise@firetell.com) with your workspace ID and use case. ::: ### Response Headers Every response includes rate limit information: | Header | Description | | ----------------------- | ----------------------------------------------- | | `X-RateLimit-Limit` | Maximum requests allowed in the window | | `X-RateLimit-Remaining` | Requests remaining in the current window | | `Retry-After` | Seconds to wait before retrying (only on `429`) | ### Rate Limit Exceeded (429) When the rate limit is exceeded, the API returns a `429 Too Many Requests` response: ```json { "statusCode": 429, "message": "Rate limit exceeded. Please try again later.", "retryAfter": 42 } ``` :::tip **Best Practices** - **Implement exponential backoff** when receiving `429` responses - **Cache responses** to reduce API calls - **Use webhooks** instead of polling for real-time updates - Monitor the `X-RateLimit-Remaining` header to preemptively slow down ::: ## Available Resources ### Workspace API Management endpoints for workspace administrators. Authenticated via `ApiKey`. | Resource | Description | | ----------------------------------------------------------- | ---------------------------- | | [Phone Numbers](/docs/rest-api/workspace-api/phone-numbers) | Manage virtual phone numbers | | [Call](/docs/rest-api/workspace-api/call) | Initiate and control calls | | [Call Flows](/docs/rest-api/workspace-api/call-flows) | Manage IVR call flows | | [Extensions](/docs/rest-api/workspace-api/extensions) | Manage internal extensions | | [SIP Trunks](/docs/rest-api/workspace-api/sip-trunks) | Manage SIP trunk connections | | [SIP Accounts](/docs/rest-api/workspace-api/sip-accounts) | Manage SIP accounts | | [Teams](/docs/rest-api/workspace-api/teams) | Manage agent teams | | [Agents](/docs/rest-api/workspace-api/agents) | Manage call center agents | | [Voice Agents](/docs/rest-api/workspace-api/voice-agents) | Manage AI voice agents | | [Audios](/docs/rest-api/workspace-api/audios) | Manage audio files | | [Webhooks](/docs/rest-api/workspace-api/webhooks) | Manage webhook endpoints | ### Agent API Call center portal endpoints for agents. Authenticated via agent JWT (`agent-api` audience). | Resource | Description | | ----------------------------------------------------------------- | ------------------------------------------------------------------- | | [Roles & Permissions](/docs/rest-api/agent-api/roles-permissions) | Team roles (member, leader, supervisor) & permission matrix | | [Authentication](/docs/rest-api/agent-api/auth) | Agent login, JWT verify, token refresh & password recovery | | [Account](/docs/rest-api/agent-api/account) | Profile, teams, avatar, devices & logout | | [Realtime Events](/docs/rest-api/agent-api/realtime-events) | Server-Sent Events (SSE) stream for presence, queue & notifications | | [Outbound Calls](/docs/rest-api/agent-api/calls) | Initiate outbound calls via REST & native WebSockets per-call | | [Contacts](/docs/rest-api/agent-api/contacts) | Manage contacts — create, search, lookup & delete | | [Address Books](/docs/rest-api/agent-api/address-books) | Organize contacts into named address books | | [Teams](/docs/rest-api/agent-api/teams) | View teams, teammates, agent states & team management | | [Phone Numbers](/docs/rest-api/agent-api/phone-numbers) | View phone numbers available to your teams | | [Call History](/docs/rest-api/agent-api/call-history) | View calls you handled as an agent | | [Call Supervision](/docs/rest-api/agent-api/call-supervision) | Listen, whisper, barge (supervisor) & call transfer (all agents) | ## Enterprise Services Need advanced capabilities beyond the standard platform? Firetell offers custom enterprise solutions: | Service | Description | | ---------------------------- | ----------------------------------------------------------------------------- | | **Custom Rate Limits** | Higher or tailored rate limits for your workload | | **Custom Base Domain** | Use your own domain instead of `*.firetell.app` (e.g., `api.yourcompany.com`) | | **Dedicated Infrastructure** | Isolated deployment for compliance and performance | | **Priority Support** | Dedicated technical support and SLA guarantees | To discuss enterprise requirements, contact us at [enterprise@firetell.com](mailto:enterprise@firetell.com). --- # Agents Agents are members assigned to handle calls in a call center setup. Each agent has login credentials to access the agent portal at `https://{workspace_id}.firetell.app`, where they can receive and make calls. The Agents API allows you to manage agent accounts, availability status, and routing preferences. ## Endpoints | Method | Endpoint | Description | | -------- | ---------------------- | ------------------------------ | | `GET` | `/agents` | List all agents | | `GET` | `/agents/:id` | Get agent details | | `GET` | `/agents/:id/teams` | List teams an agent belongs to | | `POST` | `/agents` | Create an agent | | `PATCH` | `/agents/:id` | Update agent settings | | `PATCH` | `/agents/:id/avatar` | Upload agent avatar | | `PATCH` | `/agents/:id/password` | Change agent password | | `DELETE` | `/agents/:id` | Delete an agent | ## The Agent Object | Field | Type | Description | | -------------- | -------------- | ------------------------------------------------- | | `id` | string | Unique agent identifier (prefixed with `ag_`) | | `username` | string | Agent login username (unique per workspace) | | `domain` | string | Agent's domain (e.g., `yourcompany.firetell.app`) | | `display_name` | string | Agent's display name | | `email` | string \| null | Agent's email address | | `avatar` | string \| null | URL to agent's avatar image | | `state` | string | Current availability state | | `is_active` | boolean | Whether the agent account is active | | `workspace_id` | string | Workspace identifier | | `country_code` | string | Two-letter ISO country code (e.g., `VN`, `US`) | | `created_at` | string | ISO 8601 creation timestamp | | `updated_at` | string | ISO 8601 last update timestamp | ### Agent States | State | Description | | ----------- | ------------------------------------------ | | `available` | Agent is online and ready to receive calls | | `incall` | Agent is currently on a call | | `busy` | Agent is online but not accepting calls | | `offline` | Agent is not logged in | :::info The `state` field is a **real-time presence** value powered by the presence system. It is **not stored in the database**. An agent is considered `available` if they are reachable via **any** channel — either a live WebSocket connection or a registered VoIP Push token (for mobile apps in background). An agent transitions to `offline` only when **all** reachability sources are removed (e.g., all WebSocket sessions closed **and** push token unregistered via [logout](/docs/rest-api/agent-api/account#logout)). ::: :::info The `password` field is never returned in API responses. ::: --- ## List Agents ``` GET /agents ``` Retrieve a paginated list of all agents in the workspace. ### Authentication Requires `ApiKey` with any scope. ### Query Parameters | Parameter | Type | Default | Description | | -------------- | ------- | ------------ | ----------------------------------------------------------------------------------- | | `page` | number | `1` | Page number (min: 1) | | `limit` | number | `10` | Items per page (min: 1, max: 100) | | `search` | string | — | Search by display name, email, or username | | `sort_field` | string | `created_at` | Sort field. Allowed: `created_at`, `display_name`, `email`, `username`, `is_active` | | `sort_order` | string | `desc` | Sort order: `asc` or `desc` | | `state` | string | — | Filter by agent state: `available`, `incall`, `busy`, `offline` | | `country_code` | string | — | Filter by country code (e.g., `VN`, `US`) | | `is_active` | boolean | — | Filter by active status | ### Request ```bash curl -X GET "https://{workspace_id}.firetell.app/api/v1/agents?page=1&limit=10&state=available" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" ``` ### Response ```json { "data": [ { "id": "ag_7f3a2b1c9d4e5f6a8b0c1d2e", "username": "johndoe", "domain": "yourcompany.firetell.app", "display_name": "John Doe", "email": "john@example.com", "avatar": "https://s3.amazonaws.com/bucket/yourcompany/avatars/abc123.webp", "state": "available", "is_active": true, "workspace_id": "yourcompany", "country_code": "US", "created_at": "2026-01-15T08:30:00.000Z", "updated_at": "2026-03-20T14:00:00.000Z" } ], "meta": { "total": 25, "page": 1, "limit": 10, "total_pages": 3 } } ``` --- ## Get Agent ``` GET /agents/:id ``` Retrieve details of a specific agent. ### Authentication Requires `ApiKey` with any scope. ### Path Parameters | Parameter | Type | Description | | --------- | ------ | -------------------------------------------------- | | `id` | string | The agent ID (e.g., `ag_7f3a2b1c9d4e5f6a8b0c1d2e`) | ### Request ```bash curl -X GET "https://{workspace_id}.firetell.app/api/v1/agents/ag_7f3a2b1c9d4e5f6a8b0c1d2e" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" ``` ### Response ```json { "id": "ag_7f3a2b1c9d4e5f6a8b0c1d2e", "username": "johndoe", "domain": "yourcompany.firetell.app", "display_name": "John Doe", "email": "john@example.com", "avatar": null, "state": "offline", "is_active": true, "workspace_id": "yourcompany", "country_code": "US", "created_at": "2026-01-15T08:30:00.000Z", "updated_at": "2026-01-15T08:30:00.000Z" } ``` ### Error Response ```json { "statusCode": 404, "message": "Agent not found", "error": "Not Found" } ``` --- ## List Agent Teams ``` GET /agents/:id/teams ``` Retrieve a paginated list of teams that a specific agent belongs to. ### Authentication Requires `ApiKey` with any scope. ### Path Parameters | Parameter | Type | Description | | --------- | ------ | ------------ | | `id` | string | The agent ID | ### Query Parameters | Parameter | Type | Default | Description | | ------------ | ------ | ------------ | --------------------------- | | `page` | number | `1` | Page number | | `limit` | number | `10` | Items per page (max: 100) | | `sort_field` | string | `created_at` | Sort field | | `sort_order` | string | `desc` | Sort order: `asc` or `desc` | ### Request ```bash curl -X GET "https://{workspace_id}.firetell.app/api/v1/agents/ag_7f3a2b1c9d4e5f6a8b0c1d2e/teams" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" ``` ### Response ```json { "data": [ { "id": "te_a1b2c3d4e5f6a7b8c9d0e1f2", "name": "Support Team", "workspace_id": "yourcompany", "agent_count": 5, "created_at": "2026-01-10T09:00:00.000Z", "updated_at": "2026-02-15T10:30:00.000Z" }, { "id": "te_b2c3d4e5f6a7b8c9d0e1f2a3", "name": "Sales Team", "workspace_id": "yourcompany", "agent_count": 8, "created_at": "2026-01-12T11:00:00.000Z", "updated_at": "2026-03-01T08:00:00.000Z" } ], "meta": { "total": 2, "page": 1, "limit": 10, "total_pages": 1 } } ``` --- ## Create Agent ``` POST /agents ``` Create a new agent in the workspace. The agent will be able to log in to the agent portal and receive calls. ### Authentication Requires `ApiKey` with `full` scope. ### Request Body | Field | Type | Required | Description | | -------------- | ------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `username` | string | ✅ | Login username. 3–30 characters, must start with a lowercase letter, only lowercase letters and numbers allowed. Must be unique per workspace. | | `password` | string | ✅ | Login password. 6–50 characters. | | `display_name` | string | ✅ | Agent display name. 3–50 characters. Only letters, spaces, and hyphens allowed. | | `email` | string | — | Email address. Must be unique per workspace. | | `avatar` | string | — | URL to avatar image. | | `country_code` | string | — | Two-letter ISO country code (e.g., `VN`). Defaults to workspace's country code. | | `is_active` | boolean | — | Whether the agent is active. Defaults to `true`. | ### Request ```bash curl -X POST "https://{workspace_id}.firetell.app/api/v1/agents" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "username": "janedoe", "password": "securePass123", "display_name": "Jane Doe", "email": "jane@example.com", "country_code": "VN" }' ``` ### Response ```json { "id": "ag_9a8b7c6d5e4f3a2b1c0d9e8f", "username": "janedoe", "domain": "yourcompany.firetell.app", "display_name": "Jane Doe", "email": "jane@example.com", "avatar": null, "state": "offline", "is_active": true, "workspace_id": "yourcompany", "country_code": "VN", "created_at": "2026-07-09T08:00:00.000Z", "updated_at": "2026-07-09T08:00:00.000Z" } ``` ### Error Responses **Duplicate username:** ```json { "statusCode": 409, "message": "username is exists", "error": "Conflict" } ``` **Duplicate email:** ```json { "statusCode": 409, "message": "email is exists", "error": "Conflict" } ``` **Invalid username format:** ```json { "statusCode": 400, "message": [ "Invalid username. Must start with a lowercase letter, contain only lowercase letters, numbers" ], "error": "Bad Request" } ``` --- ## Update Agent ``` PATCH /agents/:id ``` Update an existing agent's settings. Only include the fields you want to change. ### Authentication Requires `ApiKey` with `full` scope. ### Path Parameters | Parameter | Type | Description | | --------- | ------ | ------------ | | `id` | string | The agent ID | ### Request Body All fields are optional: | Field | Type | Description | | -------------- | ------- | --------------------------------------------------------------------------------------- | | `username` | string | New username. 3–30 characters, lowercase letters, numbers, and hyphens. Must be unique. | | `display_name` | string | New display name. 1–35 characters. Only letters, spaces, and hyphens. | | `email` | string | New email address. Must be unique per workspace. | | `avatar` | string | URL to new avatar image. | | `is_active` | boolean | Enable or disable the agent account. | | `country_code` | string | Two-letter ISO country code. | ### Request ```bash curl -X PATCH "https://{workspace_id}.firetell.app/api/v1/agents/ag_7f3a2b1c9d4e5f6a8b0c1d2e" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "display_name": "John D", "is_active": false }' ``` ### Response Returns the updated agent object: ```json { "id": "ag_7f3a2b1c9d4e5f6a8b0c1d2e", "username": "johndoe", "domain": "yourcompany.firetell.app", "display_name": "John D", "email": "john@example.com", "avatar": null, "state": "offline", "is_active": false, "workspace_id": "yourcompany", "country_code": "US", "created_at": "2026-01-15T08:30:00.000Z", "updated_at": "2026-07-09T09:15:00.000Z" } ``` ### Error Response ```json { "statusCode": 400, "message": "Agent not found or does not belong to this workspace", "error": "Bad Request" } ``` --- ## Upload Agent Avatar ``` PATCH /agents/:id/avatar ``` Upload or replace an agent's avatar image. The previous avatar (if any) will be automatically deleted from storage. ### Authentication Requires `ApiKey` with `full` scope. ### Request Body This endpoint uses `multipart/form-data` (not JSON). | Field | Type | Required | Description | | ------ | ---- | -------- | ------------------------------------------------------ | | `file` | file | ✅ | Image file (max **5 MB**). Must be an image MIME type. | ### Supported Formats | Format | MIME Type | | ------ | ------------ | | JPEG | `image/jpeg` | | PNG | `image/png` | | WebP | `image/webp` | | GIF | `image/gif` | ### Request ```bash curl -X PATCH "https://{workspace_id}.firetell.app/api/v1/agents/ag_7f3a2b1c9d4e5f6a8b0c1d2e/avatar" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" \ -F "file=@/path/to/avatar.jpg" ``` ### Response Returns the updated agent object with the new `avatar` URL: ```json { "id": "ag_7f3a2b1c9d4e5f6a8b0c1d2e", "username": "johndoe", "domain": "yourcompany.firetell.app", "display_name": "John Doe", "email": "john@example.com", "avatar": "https://s3.amazonaws.com/bucket/yourcompany/avatars/new-uuid.webp", "state": "available", "is_active": true, "workspace_id": "yourcompany", "country_code": "US", "created_at": "2026-01-15T08:30:00.000Z", "updated_at": "2026-07-09T10:00:00.000Z" } ``` ### Error Responses **Missing file:** ```json { "statusCode": 400, "message": "file is required", "error": "Bad Request" } ``` **Invalid file type:** ```json { "statusCode": 400, "message": "Only images accepted", "error": "Bad Request" } ``` --- ## Change Agent Password ``` PATCH /agents/:id/password ``` Change an agent's login password. ### Authentication Requires `ApiKey` with `full` scope. ### Path Parameters | Parameter | Type | Description | | --------- | ------ | ------------ | | `id` | string | The agent ID | ### Request Body | Field | Type | Required | Description | | ---------- | ------ | -------- | ------------------------------ | | `password` | string | ✅ | New password. 6–50 characters. | ### Request ```bash curl -X PATCH "https://{workspace_id}.firetell.app/api/v1/agents/ag_7f3a2b1c9d4e5f6a8b0c1d2e/password" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "password": "newSecurePass456" }' ``` ### Response Returns the agent object (prior to update): ```json { "id": "ag_7f3a2b1c9d4e5f6a8b0c1d2e", "username": "johndoe", "domain": "yourcompany.firetell.app", "display_name": "John Doe", "email": "john@example.com", "state": "offline", "is_active": true, "workspace_id": "yourcompany", "country_code": "US", "created_at": "2026-01-15T08:30:00.000Z", "updated_at": "2026-07-09T10:30:00.000Z" } ``` :::caution Changing an agent's password will not terminate active sessions. The agent will need to use the new password on their next login. ::: --- ## Delete Agent ``` DELETE /agents/:id ``` Permanently delete an agent from the workspace. The agent will be automatically removed from all teams they belong to, and team member counts will be updated accordingly. ### Authentication Requires `ApiKey` with `full` scope. :::caution This action is irreversible. The agent will lose access to the call center portal immediately. Any active calls will not be affected, but the agent will not be able to receive new calls. ::: ### Path Parameters | Parameter | Type | Description | | --------- | ------ | ------------ | | `id` | string | The agent ID | ### Request ```bash curl -X DELETE "https://{workspace_id}.firetell.app/api/v1/agents/ag_7f3a2b1c9d4e5f6a8b0c1d2e" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" ``` ### Response Returns the deleted agent object: ```json { "id": "ag_7f3a2b1c9d4e5f6a8b0c1d2e", "username": "johndoe", "domain": "yourcompany.firetell.app", "display_name": "John Doe", "email": "john@example.com", "avatar": null, "state": "offline", "is_active": true, "workspace_id": "yourcompany", "country_code": "US", "created_at": "2026-01-15T08:30:00.000Z", "updated_at": "2026-07-09T08:00:00.000Z" } ``` ### Error Response ```json { "statusCode": 400, "message": "Agent not found or does not belong to this workspace", "error": "Bad Request" } ``` --- # Audios Upload and manage audio files used for IVR prompts, hold music, voicemail greetings, and other call flow audio components. Firetell automatically processes uploaded audio into multiple sample rates optimized for telephony. ## Endpoints | Method | Endpoint | Description | | -------- | ------------- | ---------------------- | | `GET` | `/audios` | List all audio files | | `GET` | `/audios/:id` | Get audio file details | | `POST` | `/audios` | Upload an audio file | | `DELETE` | `/audios/:id` | Delete an audio file | ## The Audio Object | Field | Type | Description | | ------------- | ------ | --------------------------------------------------- | | `id` | string | Unique audio identifier (prefixed with `au_`) | | `title` | string | Display name of the audio file | | `url` | string | URL to the best quality audio sample | | `codec` | string | Audio codec (default: `pcm_mulaw`) | | `sample_rate` | number | Original sample rate in Hz (default: `8000`) | | `duration` | number | Duration in seconds | | `file_size` | number | File size in MB | | `mime_type` | string | MIME type (default: `audio/wav`) | | `samples` | array | Array of processed audio samples at different rates | | `created_at` | string | ISO 8601 creation timestamp | | `updated_at` | string | ISO 8601 last update timestamp | ### Audio Sample Object Each audio file is automatically converted into multiple sample rates. The `samples` array contains: | Field | Type | Description | | ---------- | ------ | -------------------------------------------------- | | `rate` | number | Sample rate in Hz (e.g., `8000`, `16000`, `48000`) | | `url` | string | URL to this sample variant | | `key` | string | Storage key identifier | | `size` | number | File size in MB | | `duration` | number | Duration in seconds | --- ## List Audios ``` GET /audios ``` Retrieve a paginated list of all audio files in the workspace. ### Authentication Requires `ApiKey` with any scope. ### Query Parameters | Parameter | Type | Default | Description | | ------------ | ------ | ------------ | ------------------------------------------------------------------- | | `page` | number | `1` | Page number (min: 1) | | `limit` | number | `10` | Items per page (min: 1, max: 100) | | `search` | string | — | Search audio files by title | | `sort_field` | string | `created_at` | Sort field. Allowed: `created_at`, `title`, `duration`, `file_size` | | `sort_order` | string | `desc` | Sort order: `asc` or `desc` | ### Request ```bash curl -X GET "https://{workspace_id}.firetell.app/api/v1/audios?page=1&limit=10&sort_field=created_at&sort_order=desc" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" ``` ### Response ```json { "data": [ { "id": "au_7f3a2b1c9d4e5f6a8b0c1d2e", "title": "Welcome Greeting", "url": "https://s3.amazonaws.com/bucket/workspace/audios/uuid/48000hz.wav", "codec": "pcm_mulaw", "sample_rate": 8000, "duration": 15, "file_size": 0.24, "mime_type": "audio/wav", "samples": [ { "rate": 48000, "url": "https://s3.amazonaws.com/bucket/workspace/audios/uuid/48000hz.wav", "key": "workspace/audios/uuid/48000hz.wav", "size": 1.44, "duration": 15 }, { "rate": 16000, "url": "https://s3.amazonaws.com/bucket/workspace/audios/uuid/16000hz.wav", "key": "workspace/audios/uuid/16000hz.wav", "size": 0.48, "duration": 15 }, { "rate": 8000, "url": "https://s3.amazonaws.com/bucket/workspace/audios/uuid/8000hz.wav", "key": "workspace/audios/uuid/8000hz.wav", "size": 0.24, "duration": 15 } ], "created_at": "2026-01-15T08:30:00.000Z", "updated_at": "2026-01-15T08:30:00.000Z" } ], "meta": { "total": 3, "page": 1, "limit": 10, "total_pages": 1 } } ``` --- ## Get Audio ``` GET /audios/:id ``` Retrieve details of a specific audio file. ### Authentication Requires `ApiKey` with any scope. ### Path Parameters | Parameter | Type | Description | | --------- | ------ | -------------------------------------------------- | | `id` | string | The audio ID (e.g., `au_7f3a2b1c9d4e5f6a8b0c1d2e`) | ### Request ```bash curl -X GET "https://{workspace_id}.firetell.app/api/v1/audios/au_7f3a2b1c9d4e5f6a8b0c1d2e" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" ``` ### Response ```json { "id": "au_7f3a2b1c9d4e5f6a8b0c1d2e", "title": "Hold Music - Jazz", "url": "https://s3.amazonaws.com/bucket/workspace/audios/uuid/48000hz.wav", "codec": "pcm_mulaw", "sample_rate": 8000, "duration": 120, "file_size": 1.92, "mime_type": "audio/wav", "samples": [ { "rate": 48000, "url": "https://s3.amazonaws.com/bucket/workspace/audios/uuid/48000hz.wav", "key": "workspace/audios/uuid/48000hz.wav", "size": 11.52, "duration": 120 }, { "rate": 16000, "url": "https://s3.amazonaws.com/bucket/workspace/audios/uuid/16000hz.wav", "key": "workspace/audios/uuid/16000hz.wav", "size": 3.84, "duration": 120 }, { "rate": 8000, "url": "https://s3.amazonaws.com/bucket/workspace/audios/uuid/8000hz.wav", "key": "workspace/audios/uuid/8000hz.wav", "size": 1.92, "duration": 120 } ], "created_at": "2026-02-10T14:22:00.000Z", "updated_at": "2026-02-10T14:22:00.000Z" } ``` ### Error Response ```json { "statusCode": 404, "message": "item not found", "error": "Not Found" } ``` --- ## Upload Audio ``` POST /audios ``` Upload a new audio file. The file is automatically processed and converted into multiple sample rates for optimal telephony playback. ### Authentication Requires `ApiKey` with `full` scope. :::caution Each workspace is limited to a maximum of **100 audio files**. Attempting to upload beyond this limit will return a `403 Forbidden` error. ::: ### Request Body This endpoint uses `multipart/form-data` (not JSON). | Field | Type | Required | Description | | ------- | ------ | -------- | -------------------------------------------- | | `file` | file | ✅ | Audio file to upload (max **3 MB**) | | `title` | string | ✅ | Display name for the audio (1–50 characters) | ### Supported Formats | Format | MIME Type | Notes | | ------ | ------------ | ---------------------------------------- | | WAV | `audio/wav` | Recommended — best quality for telephony | | MP3 | `audio/mpeg` | Widely compatible | | OGG | `audio/ogg` | Compressed alternative | Any file with a MIME type starting with `audio/` is accepted. ### Request ```bash curl -X POST "https://{workspace_id}.firetell.app/api/v1/audios" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" \ -F "file=@/path/to/welcome.wav" \ -F "title=Welcome Greeting" ``` ### Response ```json { "id": "au_9a8b7c6d5e4f3a2b1c0d9e8f", "title": "Welcome Greeting", "url": "https://s3.amazonaws.com/bucket/workspace/audios/uuid/48000hz.wav", "workspace_id": "yourcompany", "duration": 10, "file_size": 0.16, "samples": [ { "rate": 48000, "url": "https://s3.amazonaws.com/bucket/workspace/audios/uuid/48000hz.wav", "size": 0.96, "duration": 10, "key": "workspace/audios/uuid/48000hz.wav" }, { "rate": 16000, "url": "https://s3.amazonaws.com/bucket/workspace/audios/uuid/16000hz.wav", "size": 0.32, "duration": 10, "key": "workspace/audios/uuid/16000hz.wav" }, { "rate": 8000, "url": "https://s3.amazonaws.com/bucket/workspace/audios/uuid/8000hz.wav", "size": 0.16, "duration": 10, "key": "workspace/audios/uuid/8000hz.wav" } ], "created_at": "2026-07-09T08:00:00.000Z", "updated_at": "2026-07-09T08:00:00.000Z" } ``` ### Error Responses **Missing file:** ```json { "statusCode": 400, "message": "file is required", "error": "Bad Request" } ``` **Invalid file type:** ```json { "statusCode": 400, "message": "Only audio accepted", "error": "Bad Request" } ``` **File limit reached:** ```json { "statusCode": 403, "message": "You have reached the maximum limit of 100 files. Please contact support.", "error": "Forbidden" } ``` :::tip For best telephony quality, upload **WAV files** with a sample rate of **16000 Hz** or higher. Firetell will automatically generate optimized versions at 8000 Hz, 16000 Hz, and 48000 Hz. ::: --- ## Delete Audio ``` DELETE /audios/:id ``` Permanently delete an audio file and all its processed samples from storage. ### Authentication Requires `ApiKey` with `full` scope. :::caution This action is irreversible. The audio file and all associated sample rate variants will be permanently deleted. Ensure the audio is not actively used in any call flows before deleting. ::: ### Path Parameters | Parameter | Type | Description | | --------- | ------ | -------------------------------------------------- | | `id` | string | The audio ID (e.g., `au_7f3a2b1c9d4e5f6a8b0c1d2e`) | ### Request ```bash curl -X DELETE "https://{workspace_id}.firetell.app/api/v1/audios/au_7f3a2b1c9d4e5f6a8b0c1d2e" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" ``` ### Response Returns the deleted audio object: ```json { "id": "au_7f3a2b1c9d4e5f6a8b0c1d2e", "title": "Welcome Greeting", "url": "https://s3.amazonaws.com/bucket/workspace/audios/uuid/48000hz.wav", "codec": "pcm_mulaw", "sample_rate": 8000, "duration": 15, "file_size": 0.24, "mime_type": "audio/wav", "samples": [], "created_at": "2026-01-15T08:30:00.000Z", "updated_at": "2026-01-15T08:30:00.000Z" } ``` ### Error Response ```json { "statusCode": 404, "message": "item not found", "error": "Not Found" } ``` --- ## Audio Processing When you upload an audio file, Firetell automatically: 1. **Validates** the file format and sample rate 2. **Converts** the audio into multiple sample rate variants (8000 Hz, 16000 Hz, 48000 Hz) 3. **Stores** all variants in cloud storage for fast access 4. **Selects** the best quality sample as the primary URL The system intelligently selects the optimal audio variant during call playback based on the codec negotiated between endpoints, minimizing real-time transcoding and ensuring the highest quality audio delivery. :::info The minimum accepted sample rate is **8000 Hz**. Files with a lower sample rate will be rejected during upload. ::: --- # Call Flows Integration Call Flows define the automated routing logic for incoming and outgoing calls. They are built and managed visually using the graphical designer inside the **Firetell Console**. Since Call Flow management and structure manipulation are internal resources, no public REST API endpoints are exposed. Instead, developers can integrate custom database lookups and dynamic routing logic directly into their own servers using two key integration nodes: **Contact Lookup** and **JSON Call Action (JCA)**. --- ## Contact Lookup Node (`contact_lookup`) The **Contact Lookup** node allows Firetell to query your CRM, database, or API endpoint to resolve the caller's identity before routing the call. ### How it Works 1. **Internal Fallback (Optional)**: If the Request URL parameter is left empty, Firetell will automatically search the internal Firetell Call Center database for a contact matching the caller's phone number. 2. **Webhook Query**: If a Request URL is provided, Firetell issues an HTTP `POST` request to your configured webhook URL. The request body contains a JSON payload representing the caller: - `phone_number` (string): The caller's E.164 phone number. - `from_id` (string): The caller's E.164 phone number. - `from_number` (string): The caller's E.164 phone number. 3. Your server must respond with a `200 OK` JSON payload containing the contact's name. ### Expected JSON Response Firetell scans the response object for a display name using the following fields (in order of priority): - `display_name` - `name` - `fullName` - `display_title` - `title` Example response: ```json { "id": "customer_99182", "display_name": "Nguyen Van A", "email": "vana@example.com", "tier": "VIP" } ``` ### Telephony Behavior - **Caller ID Display Name**: If a name is found, Firetell injects it into the SIP headers. Softphones, WebRTC clients, and physical IP phones will display the contact's name instead of a raw phone number. - **Session Variable**: The raw JSON response is saved into the call's channel variable (`firetell_contact_data`). This data can be referenced later during the call flow or accessed inside subsequent JCA webhooks. --- ## JSON Call Action Node (`json_call_action`) The **JSON Call Action (JCA)** node allows your server to dynamically direct and control the call flow in real-time by returning a sequence of execution actions. ### How it Works 1. When a call reaches this node, Firetell sends an HTTP `POST` request to your configured backend webhook URL. 2. Firetell sends the call context (caller number, dialed destination, workspace ID, call flow ID, call ID, and contact lookup data) in the body. 3. Your server should process the business logic and respond with a `200 OK` containing a list of `actions` to execute next. ### HTTP POST Request Body ```json { "event": "call_flow.jca", "call_id": "call_e9a0c7eb1a744b55a0ee65", "workspace_id": "ws_abc123", "call_flow_id": "cf_xyz987", "caller_number": "84901234567", "destination_number": "842873000123", "contact_data": "{\"display_name\":\"Nguyen Van A\",\"tier\":\"VIP\"}" // Stringified response from preceding Contact Lookup node } ``` ### Expected Response Your server must return a JSON response containing an array of compiled call actions. Example response (Welcome user, and then bridge to an operator): ```json { "actions": [ { "action": "play", "params": { "audio_id": "au_welcome_vip", "answer_call": true, "continue_on_play": false } }, { "action": "connect_operator", "params": { "phone_number": "84909999999", "timeout": 45 } } ] } ``` --- ## Supported Webhook Actions The following action objects can be returned in the `actions` array: ### 1. Play Audio (`play`) Plays a pre-recorded audio file stored in the workspace cache. - `audio_id` (string, required): ID of the audio metadata resource. - `answer_call` (boolean, optional): If `false`, streams the audio as early media (SIP 183 Progress) without answering/billing the call. Default: `true`. - `continue_on_play` (boolean, optional): If `true`, runs the audio in the background and immediately moves to the next action. If `false`, blocks call flow until playback ends. Default: `false`. ### 2. Text to Speech (`tts`) Plays a text announcement using synthetic voice. - `text` (string, required): The text content to read (max 1000 characters). - `voice` (string, optional): Selected TTS voice identifier. - `speed` (number, optional): Playback speed rate. ### 3. Collect Digits (`ivr`) Waits for the caller to press DTMF keys. - `timeout` (number, optional): Time in seconds to wait for input. Default: `5`. - `max_digits` (number, optional): Maximum digits to collect (between 1 and 10). Default: `1`. ### 4. Connect Operator (`connect_operator`) Bridges the call to an external phone number. - `phone_number` (string, required): Destination E.164 phone number. - `timeout` (number, optional): Max ringing time in seconds before failover (max 300). Default: `60`. ### 5. Connect Agent (`connect_agent`) Routes the call to a registered Firetell agent. - `agent_id` (string, required): Unique agent ID. - `timeout` (number, optional): Max ringing time in seconds. Default: `30`. ### 6. Connect Team (`connect_team`) Bridges the call to a team queue. - `team_id` (string, required): Unique team ID. - `timeout` (number, optional): Ring timeout in seconds. Default: `30`. ### 7. Voicemail (`voicemail`) Records a voicemail message from the caller and uploads it to the S3 workspace cloud. - `greeting_audio_id` (string, optional): Audio ID to play before recording. If omitted, plays default greeting *"Please leave your message after the tone."* - `max_duration` (number, optional): Max message recording length in seconds. Default: `60`. - `beep` (boolean, optional): Play a tone beep sound before recording starts. Default: `true`. ### 8. Disconnect (`disconnect`) Ends the call session immediately. - `cause` (string, optional): SIP hangup cause (e.g. `NORMAL_CLEARING`). --- # Call The Call API allows you to initiate outbound server-to-server calls, control active calls with actions, and retrieve call history. ## Endpoints | Method | Endpoint | Description | | ------ | -------------------- | -------------------------------- | | `GET` | `/calls` | List call history | | `GET` | `/calls/:id/events` | Get call events timeline | | `POST` | `/calls/make` | Initiate an outbound call | | `PUT` | `/calls/:id/actions` | Update actions on an active call | ## List Call History ```http GET /calls?page=1&limit=20 ``` Returns paginated call history for your workspace. ### Query Parameters | Parameter | Type | Default | Description | | --------- | ------ | ------- | ---------------------- | | `page` | number | `1` | Page number | | `limit` | number | `20` | Items per page | | `search` | string | — | Search by phone number | ### Response ```json { "data": [ { "call_id": "call_abc123", "from": "+84901234567", "to": "+84909876543", "status": "completed", "direction": "outbound", "duration": 45, "created_at": "2026-07-09T10:00:00Z" } ], "meta": { "total": 100, "page": 1, "limit": 20, "total_pages": 5 } } ``` ## Make a Call Initiate an outbound server-to-server call. Requires `owner` or `editor` role. ```http POST /calls/make ``` ### Request Body | Field | Type | Required | Description | | --------------------- | ------ | -------- | ------------------------------------------------------- | | `to` | string | ✅ | Destination phone number (E.164) or extension | | `from` | string | — | Caller ID phone number (E.164). Uses default if omitted | | `call_flow_id` | string | — | Call flow to execute on answer | | `outbound_gateway_id` | string | — | Specific SIP trunk to route through | | `actions` | array | — | Actions to perform when the call connects | ### Call Actions Each action object has the following shape: | Field | Type | Required | Description | | ------------- | ------- | -------- | -------------------------------------------- | | `action` | string | ✅ | Action type (see below) | | `params` | object | — | Action-specific parameters | | `continue` | boolean | — | Whether to continue to the next action | | `auto_answer` | boolean | — | Auto-answer the call before executing action | ### Action Reference All server-to-server actions are unified with Call Flow builder actions for structural consistency. #### `play` — Audio Playback Play a pre-uploaded workspace audio file. | Param | Type | Required | Description | | ------------------ | ------- | -------- | ---------------------------------------------------------------------------------------- | | `audio_id` | string | ✅ | ID of the workspace audio asset to play | | `answer_call` | boolean | — | Whether to answer the call before playing. Default: `true` | | `continue_on_play` | boolean | — | If `true`, playback runs in background while the next action executes. Default: `false`. | #### `tts` — Text-to-Speech Play a synthesized speech message. | Param | Type | Required | Description | | ------- | ------ | -------- | -------------------------- | | `text` | string | ✅ | The text to read aloud | | `voice` | string | — | Synthetic voice identifier | | `speed` | number | — | Voice speaking speed rate | #### `ivr` — Wait for Digits Wait for the callee to press DTMF keys. | Param | Type | Required | Description | | ------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------ | | `timeout` | number | — | Time in seconds to wait for input. Default: `5` | | `max_digits` | number | — | Maximum digits to collect (between 1-10). Default: `1` | | `webhook_url` | string | ✅ | The POST destination URL where collected digits will be sent. | | `sid` | string | — | Inferred automatically from the request's authentication context (API Key). Partners do not need to provide this. | ##### Collected Callback Payload When DTMF collection completes (due to timeout, reaching max digits, or pressing `#`), Firetell dispatches a queued POST request to your `webhook_url` with the following JSON payload: ```json { "event": "call.ivr_collected", "data": { "workspace_id": "ws_xyz789", "call_id": "call_abc123", "digits": "123", "status": "completed" // "completed" or "timeout" } } ``` #### `connect_operator` — Connect External Phone Bridge the call to an external phone number. | Param | Type | Required | Description | | ------------------- | ------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | `phone_number` | string | ✅ | Destination E.164 phone number | | `from` | string | — | Custom DID phone number (E.164) used as caller ID. Automatically resolves the matching carrier gateway to prevent cross-carrier blockage. Default: Call DID | | `timeout` | number | — | Timeout in seconds for the trunk failover search. Default: `60` | | `ringback_audio_id` | string | — | Audio file ID to play as hold music while ringing | #### `connect_agent` — Connect Agent Bridge the call to a registered Firetell agent. | Param | Type | Required | Description | | ------------------- | ------ | -------- | ------------------------------------------------- | | `agent_id` | string | ✅ | Unique registered agent ID | | `timeout` | number | — | Timeout in seconds. Default: `30` | | `ringback_audio_id` | string | — | Audio file ID to play as hold music while ringing | #### `connect_team` — Connect Team Queue Bridge the call to a queue team. | Param | Type | Required | Description | | ------------------- | ------ | -------- | ------------------------------------------------- | | `team_id` | string | ✅ | Unique registered team ID | | `timeout` | number | — | Timeout in seconds. Default: `30` | | `ringback_audio_id` | string | — | Audio file ID to play as hold music while ringing | #### `voicemail` — Voicemail Recording Record a voicemail message from the caller and automatically upload it to Firetell Cloud Storage. | Param | Type | Required | Description | | ------------------- | ------- | -------- | ------------------------------------------------------------------------ | | `greeting_audio_id` | string | — | Pre-recorded greeting file ID. Fallbacks to default greeting if omitted. | | `max_duration` | number | — | Max recording length in seconds. Default: `60` | | `beep` | boolean | — | Whether to play a tone beep before starting recording. Default: `true` | #### `disconnect` / `hangup` — Hang Up Terminate the active call channel. Either name can be used interchangeably. | Param | Type | Required | Description | | ------- | ------ | -------- | ---------------------------------------------- | | `cause` | string | — | SIP hangup reason (default: `NORMAL_CLEARING`) | #### `recording` — Call Session Recording Record the entire call session and automatically upload the file to S3 cloud storage. :::caution This action will be automatically ignored and skipped if the workspace plan quota does not authorize call recording (specifically, `call_recording` for app-to-phone external calls, or `call_internal_recording` for app-to-app internal calls). In this case, the call flow will seamlessly proceed to the next action. ::: | Param | Type | Required | Description | | ------------- | ------- | -------- | ---------------------------------------------------------------------------------------------------------- | | `format` | string | — | Audio format: `wav` (default) or `mp3`. | | `stereo` | boolean | — | Whether to record caller and callee on separate stereo channels (left/right). Default: `false`. | | `answer_call` | boolean | — | Whether to answer the call immediately (`true`, default). Set to `false` to defer recording until bridged. | ## Use Cases & Examples ### 1. Simple Outbound Call — Connect to Agent Call an external number and connect directly to an agent when answered. ```bash curl -X POST "https://{workspace_id}.firetell.app/api/v1/calls/make" \ -H "Authorization: ApiKey YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "from": "+84901234567", "to": "+84909876543", "actions": [ { "action": "connect_agent", "params": { "agent_id": "agt_abc123", "timeout": 30 } } ] }' ``` ### 2. Outbound Call with Hold Music Call an external number and play hold music while the agent is ringing. The music stops automatically when the agent answers. ```bash curl -X POST "https://{workspace_id}.firetell.app/api/v1/calls/make" \ -H "Authorization: ApiKey YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "from": "+84901234567", "to": "+84909876543", "actions": [ { "action": "connect_agent", "params": { "agent_id": "agt_abc123", "timeout": 30, "ringback_audio_id": "aud_holdmusic" } } ] }' ``` :::tip The `ringback_audio_id` param works on `connect_agent`, `connect_team`, and `connect_operator`. Upload your hold music via the [Audio API](/docs/rest-api/workspace-api/audios) first to get the `audio_id`. ::: ### 3. Agent Cascade — Sequential Fallback Try agent 1 first. If they don't answer within 10 seconds, automatically try agent 2. If agent 2 also doesn't answer, route to the support team. ```bash curl -X POST "https://{workspace_id}.firetell.app/api/v1/calls/make" \ -H "Authorization: ApiKey YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "from": "+84901234567", "to": "+84909876543", "actions": [ { "action": "connect_agent", "params": { "agent_id": "agt_primary", "timeout": 10, "ringback_audio_id": "aud_holdmusic" } }, { "action": "connect_agent", "params": { "agent_id": "agt_backup", "timeout": 10, "ringback_audio_id": "aud_holdmusic" } }, { "action": "connect_team", "params": { "team_id": "team_support", "timeout": 30, "ringback_audio_id": "aud_holdmusic" } }, { "action": "voicemail", "params": { "greeting_audio_id": "aud_vm_greeting", "max_duration": 60 } } ] }' ``` :::info Actions execute **sequentially**. If a `connect_*` action fails (no answer, timeout, agent offline), the system automatically falls through to the next action in the array. ::: ### 4. Recorded Outbound Campaign Make an outbound call with recording enabled, play a TTS message, then hang up. Useful for automated notification campaigns. ```bash curl -X POST "https://{workspace_id}.firetell.app/api/v1/calls/make" \ -H "Authorization: ApiKey YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "from": "+84901234567", "to": "+84909876543", "actions": [ { "action": "recording", "params": { "format": "wav", "stereo": false } }, { "action": "tts", "params": { "text": "Hello, this is a unified notification from your system." } }, { "action": "disconnect" } ] }' ``` :::caution The `recording` action is automatically skipped if your workspace plan does not include the `call_recording` quota. The remaining actions will still execute normally. ::: ### 5. Mid-Call Action Update After a call is in `started` or `active` status, you can dynamically push new actions using the [Update Call Actions](#update-call-actions) endpoint. This is useful for interactive scenarios where your backend decides the next step based on external logic. ```bash # Step 1: Initiate the call curl -X POST "https://{workspace_id}.firetell.app/api/v1/calls/make" \ -H "Authorization: ApiKey YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "from": "+84901234567", "to": "+84909876543", "actions": [ { "action": "play", "params": { "audio_id": "aud_welcome" } } ] }' # Returns: { "data": { "call_id": "call_abc123", ... } } # Step 2: After your backend logic, push new actions to the active call curl -X PUT "https://{workspace_id}.firetell.app/api/v1/calls/call_abc123/actions" \ -H "Authorization: ApiKey YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "actions": [ { "action": "connect_team", "params": { "team_id": "team_sales", "timeout": 30, "ringback_audio_id": "aud_holdmusic" } } ] }' ``` ### Response All `POST /calls/make` requests return: ```json { "data": { "call_id": "call_abc123", "status": "created", "direction": "outbound", "from": { "name": "+84901234567", "number": "+84901234567" }, "to": { "name": "+84909876543", "number": "+84909876543" }, "job_id": "job_xyz789", "job_status": "queued" } } ``` ## Update Call Actions Update the actions on an active call. Requires `owner` or `editor` role. The call must be in `started` or `active` status, and the media channel must be ready. ```http PUT /calls/:id/actions ``` ### Request Body | Field | Type | Required | Description | | --------- | ----- | -------- | --------------------------------------------------------------------------------------------------------------------------- | | `actions` | array | ✅ | Array of call action objects (minimum 1). See [Action Reference](#action-reference) above for available actions and params. | ### Example Request ```bash curl -X PUT "https://{workspace_id}.firetell.app/api/v1/calls/call_abc123/actions" \ -H "Authorization: ApiKey YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "actions": [ { "action": "tts", "params": { "text": "Your order has been confirmed. Goodbye!" }, "continue": true }, { "action": "disconnect" } ] }' ``` ### Response ```json { "data": { "call_id": "call_abc123", "queued": true, "action_count": 2 } } ``` ## Get Call Events Retrieve the event timeline for a specific call, sorted chronologically. ```http GET /calls/:id/events?page=1&limit=50 ``` ### Response ```json { "data": [ { "id": "evt_abc123", "call_id": "call_abc123", "event": "call.created", "direction": "outbound", "sip_code": null, "created_at": "2026-07-09T10:00:00Z" }, { "id": "evt_abc124", "call_id": "call_abc123", "event": "call.answered", "direction": "outbound", "sip_code": 200, "created_at": "2026-07-09T10:00:05Z" }, { "id": "evt_abc125", "call_id": "call_abc123", "event": "call.bridged", "direction": null, "sip_code": null, "created_at": "2026-07-09T10:00:06Z" }, { "id": "evt_abc126", "call_id": "call_abc123", "event": "call.ended", "direction": "outbound", "sip_code": 200, "created_at": "2026-07-09T10:00:50Z" } ], "meta": { "total": 4, "page": 1, "limit": 50, "total_pages": 1 } } ``` ### Event Types | Event | Description | | ------------------------ | ---------------------------------- | | `call.created` | Call channel created | | `call.answered` | Call was answered | | `call.bridged` | Two call legs bridged together | | `call.ended` | Call hangup completed | | `call.destroyed` | Call channel destroyed | | `call.dtmf` | DTMF digit received | | `call.playback_started` | Audio playback started | | `call.playback_stopped` | Audio playback stopped | | `call.recording_started` | Call recording started | | `call.recording_ready` | Call recording completed and ready | --- # Extensions Extensions allow you to create 3 to 6-digit internal phone numbers (e.g. `1001`, `1002`) for agents, teams, SIP accounts, or voice AI agents within your workspace, enabling direct internal calling, IVR transfers, and call routing. ## Endpoints | Method | Endpoint | Description | | -------- | ----------------- | ---------------------- | | `GET` | `/extensions` | List all extensions | | `GET` | `/extensions/:id` | Get extension details | | `POST` | `/extensions` | Create an extension | | `PATCH` | `/extensions/:id` | Update an extension | | `DELETE` | `/extensions/:id` | Delete an extension | --- ## The Extension Object | Field | Type | Description | | ------------------ | -------- | -------------------------------------------------------------------- | | `id` | string | Unique extension identifier (prefixed with `ex_`) | | `workspace_id` | string | Workspace identifier | | `extension_number` | string | 3 to 6-digit internal extension number (e.g. `1001`, `1002`) | | `title` | string | Display label/name for the extension | | `destination` | string | Destination target (`agent`, `team`, `sip-account`, `voice-agent`) | | `agent_id` | string | Assigned agent ID (required if `destination: agent`) | | `team_id` | string | Assigned team ID (required if `destination: team`) | | `sip_account_id` | string | Assigned SIP account ID (required if `destination: sip-account`) | | `voice_agent_id` | string | Assigned voice agent ID (required if `destination: voice-agent`) | | `agent` | object | Populated agent object (when `destination: agent`) | | `team` | object | Populated team object (when `destination: team`) | | `sip_account` | object | Populated SIP account object (when `destination: sip-account`) | | `created_at` | string | ISO 8601 creation timestamp | | `updated_at` | string | ISO 8601 last update timestamp | --- ## List Extensions ``` GET /extensions ``` Retrieve a paginated list of extensions in the workspace. ### Query Parameters | Parameter | Type | Default | Description | | ------------ | ------ | ------------------ | -------------------------------------------------------- | | `page` | number | `1` | Page number | | `limit` | number | `10` | Items per page (max: 100) | | `search` | string | — | Search by extension title or number | | `sort_field` | string | `created_at` | Sort field (`extension_number`, `created_at`, `title`) | | `sort_order` | string | `desc` | Sort direction (`asc`, `desc`) | ### Response `200 OK` ```json { "data": [ { "id": "ex_987654321", "workspace_id": "ws_company", "extension_number": "1001", "title": "Sales Support Line", "destination": "agent", "agent_id": "ag_abc123", "agent": { "id": "ag_abc123", "username": "john_doe", "display_name": "John Doe", "email": "john@example.com", "avatar": "https://cdn.firetell.app/avatars/john.png" }, "created_at": "2026-07-21T14:45:00.000Z", "updated_at": "2026-07-21T14:45:00.000Z" }, { "id": "ex_123456789", "workspace_id": "ws_company", "extension_number": "1002", "title": "Support Queue", "destination": "team", "team_id": "team_xyz789", "team": { "id": "team_xyz789", "title": "Customer Support", "agent_count": 5 }, "created_at": "2026-07-21T14:50:00.000Z", "updated_at": "2026-07-21T14:50:00.000Z" } ], "meta": { "total": 2, "page": 1, "limit": 10, "total_pages": 1 } } ``` --- ## Get Extension Details ``` GET /extensions/:id ``` Retrieve details of a single extension by its ID. ### Response `200 OK` ```json { "id": "ex_987654321", "workspace_id": "ws_company", "extension_number": "1001", "title": "Sales Support Line", "destination": "agent", "agent_id": "ag_abc123", "agent": { "id": "ag_abc123", "username": "john_doe", "display_name": "John Doe", "email": "john@example.com" }, "created_at": "2026-07-21T14:45:00.000Z", "updated_at": "2026-07-21T14:45:00.000Z" } ``` --- ## Create Extension ``` POST /extensions ``` Create a new internal extension and assign its routing destination. ### Request Body ```json { "title": "Sales Support Line", "extension_number": "1001", "destination": "agent", "agent_id": "ag_abc123" } ``` ### Response `201 Created` ```json { "id": "ex_987654321", "workspace_id": "ws_company", "extension_number": "1001", "title": "Sales Support Line", "destination": "agent", "agent_id": "ag_abc123", "created_at": "2026-07-21T14:45:00.000Z", "updated_at": "2026-07-21T14:45:00.000Z" } ``` ### Error Responses - `400 Bad Request`: Invalid payload or missing required `agent_id`, `team_id`, or `sip_account_id` for the selected destination. - `409 Conflict`: The `extension_number` already exists within this workspace. --- ## Update Extension ``` PATCH /extensions/:id ``` Update an extension's number, title, or routing destination. ### Request Body ```json { "title": "Tier 1 Sales Support", "extension_number": "1005", "destination": "team", "team_id": "team_sales" } ``` ### Response `200 OK` ```json { "id": "ex_987654321", "workspace_id": "ws_company", "extension_number": "1005", "title": "Tier 1 Sales Support", "destination": "team", "team_id": "team_sales", "updated_at": "2026-07-21T15:00:00.000Z" } ``` --- ## Delete Extension ``` DELETE /extensions/:id ``` Permanently remove an extension. ### Response `200 OK` ```json { "id": "ex_987654321", "workspace_id": "ws_company", "extension_number": "1005", "title": "Tier 1 Sales Support", "destination": "team" } ``` --- # Phone Numbers Manage virtual phone numbers in your workspace. Phone numbers can be used for inbound/outbound calls, assigned to call flows, extensions, or SIP trunks. ## Endpoints | Method | Endpoint | Description | | -------- | ------------------------ | ---------------------------- | | `GET` | `/phone-numbers` | List all phone numbers | | `GET` | `/phone-numbers/:id` | Get phone number details | | `POST` | `/phone-numbers/connect` | Connect a new phone number | | `PATCH` | `/phone-numbers/:id` | Update phone number settings | | `DELETE` | `/phone-numbers/:id` | Release a phone number | ## Phone Number Object | Field | Type | Description | | --------------------- | -------------- | ---------------------------------------------------------------- | | `id` | string | Unique phone number ID (prefixed) | | `title` | string | Display name / label | | `number` | string | Full phone number with country calling code (e.g. `84901234567`) | | `country_code` | string | ISO 3166-1 alpha-2 country code (e.g. `VN`, `US`) | | `dial_code` | string | Country calling code (e.g. `84`, `1`) | | `status` | string | `pending`, `active`, or `inactive` | | `type` | string | `local`, `tollfree`, `mobile`, or `international` | | `capabilities` | string[] | Supported capabilities: `voice`, `sms` | | `provider` | string | Number provider: `firetell` or `sip_trunk` | | `enable_outbound` | boolean | Whether outbound calling is enabled | | `inbound_acl_id` | string | ID of the assigned [Inbound IP ACL](/docs/rest-api/workspace-api/sip-trunks#part-1-inbound-ip-access-control-lists-acls) for whitelisting inbound SIP traffic | | `outbound_gateway_id` | string | ID of the assigned [Outbound Gateway](/docs/rest-api/workspace-api/sip-trunks#part-2-outbound-gateways) for outbound trunk call routing | | `call_flow_id` | string \| null | ID of the assigned call flow (null if not assigned) | | `shared_teams_id` | string[] | IDs of teams this number is shared with | | `record_outbound` | boolean | Whether to record outbound calls | | `fee_per_month` | number | Monthly fee for this number | | `fee_per_mins_call` | number | Per-minute call fee | | `created_at` | string | ISO 8601 creation timestamp | | `updated_at` | string | ISO 8601 last update timestamp | --- ## List Phone Numbers ```bash GET /phone-numbers ``` Retrieves a paginated list of all phone numbers in the workspace. ### Authentication Requires `ApiKey` with any scope. ### Query Parameters | Parameter | Type | Default | Description | | ------------ | ------ | ------- | --------------------------------------- | | `page` | number | `1` | Page number (min: 1) | | `limit` | number | `20` | Items per page (min: 1, max: 100) | | `search` | string | — | Search by title or number (text search) | | `sort_field` | string | — | Sort by field: `created_at`, `title` | | `sort_order` | string | `desc` | Sort order: `asc` or `desc` | ### Example Request ```bash curl -X GET "https://{workspace_id}.firetell.app/api/v1/phone-numbers?page=1&limit=10&search=hotline" \ -H "Authorization: ApiKey YOUR_API_KEY" ``` ### Response `200 OK` ```json { "data": [ { "id": "pn_a1b2c3d4e5f6g7h8i9j0k1", "title": "Main Hotline", "number": "84901234567", "country_code": "VN", "dial_code": "84", "status": "active", "type": "local", "capabilities": ["voice"], "provider": "sip_trunk", "enable_outbound": true, "inbound_acl_id": "acl_abc123", "outbound_gateway_id": "gw_def456", "call_flow_id": "cf_ghi789", "shared_teams_id": [], "record_outbound": true, "fee_per_month": 0, "fee_per_mins_call": 0, "created_at": "2026-01-15T08:30:00.000Z", "updated_at": "2026-01-15T08:30:00.000Z" } ], "meta": { "total": 1, "page": 1, "limit": 10, "total_pages": 1 } } ``` --- ## Get Phone Number Details ```bash GET /phone-numbers/:id ``` Retrieve detailed information about a specific phone number, including populated ACL and gateway data. ### Authentication Requires `ApiKey` with `full` scope. ### Path Parameters | Parameter | Type | Description | | --------- | ------ | ------------------------------------- | | `id` | string | Phone number ID (e.g. `pn_a1b2c3...`) | ### Example Request ```bash curl -X GET "https://{workspace_id}.firetell.app/api/v1/phone-numbers/pn_a1b2c3d4e5f6g7h8i9j0k1" \ -H "Authorization: ApiKey YOUR_API_KEY" ``` ### Response `200 OK` ```json { "id": "pn_a1b2c3d4e5f6g7h8i9j0k1", "title": "Main Hotline", "number": "84901234567", "country_code": "VN", "dial_code": "84", "status": "active", "type": "local", "capabilities": ["voice"], "provider": "sip_trunk", "enable_outbound": true, "inbound_acl_id": "acl_abc123", "outbound_gateway_id": "gw_def456", "call_flow_id": "cf_ghi789", "shared_teams_id": [], "record_outbound": true, "fee_per_month": 0, "fee_per_mins_call": 0, "acl": { "id": "acl_abc123", "name": "SIP Trunk ACL", "workspace_id": "my_workspace" }, "gateway": { "id": "gw_def456", "name": "Primary Gateway", "workspace_id": "my_workspace" }, "created_at": "2026-01-15T08:30:00.000Z", "updated_at": "2026-01-15T08:30:00.000Z" } ``` ### Error Responses | Status | Description | | ------ | ----------------------------------------------------------- | | `404` | Phone number not found or does not belong to this workspace | --- ## Connect a Phone Number ```bash POST /phone-numbers/connect ``` Connect (provision) a new phone number to the workspace. The phone number will be created with `pending` status. The country calling code is automatically prepended to the number based on the `country_code`. ### Authentication Requires `ApiKey` with `full` scope. ### Request Body | Field | Type | Required | Description | | --------------------- | ------- | -------- | ----------------------------------------------------------------------- | | `title` | string | ✅ | Display name (1–36 characters) | | `number` | string | ✅ | Phone number **without** country code, digits only (6–14 chars) | | `country_code` | string | ✅ | ISO 3166-1 alpha-2 code (e.g. `VN`, `US`, `SG`) | | `inbound_acl_id` | string | ✅ | [Inbound ACL ID](/docs/rest-api/workspace-api/sip-trunks#part-1-inbound-ip-access-control-lists-acls) for whitelisting inbound SIP traffic | | `enable_outbound` | boolean | ✅ | Enable outbound calling | | `outbound_gateway_id` | string | Optional | [Outbound Gateway ID](/docs/rest-api/workspace-api/sip-trunks#part-2-outbound-gateways) for trunk routing (required if `enable_outbound` is `true`) | ### Example Request ```bash curl -X POST "https://{workspace_id}.firetell.app/api/v1/phone-numbers/connect" \ -H "Authorization: ApiKey YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "title": "Support Hotline", "number": "901234567", "country_code": "VN", "inbound_acl_id": "acl_abc123", "enable_outbound": true, "outbound_gateway_id": "gw_def456" }' ``` ### Response `201 Created` ```json { "id": "pn_x1y2z3w4v5u6t7s8r9q0p1", "title": "Support Hotline", "number": "84901234567", "country_code": "VN", "dial_code": "84", "status": "pending", "type": "local", "capabilities": ["voice"], "provider": "sip_trunk", "enable_outbound": true, "inbound_acl_id": "acl_abc123", "outbound_gateway_id": "gw_def456", "call_flow_id": null, "shared_teams_id": [], "record_outbound": true, "workspace_id": "my_workspace", "created_at": "2026-07-13T08:30:00.000Z", "updated_at": "2026-07-13T08:30:00.000Z" } ``` ### Error Responses | Status | Description | | ------ | ------------------------------------------------------- | | `400` | Validation error, invalid ACL ID, or invalid gateway ID | | `403` | Maximum limit of 100 phone numbers reached | | `409` | Phone number already exists in this workspace | :::tip The `number` field should contain **only digits** without the country calling code. For example, for a Vietnamese number `+84 901 234 567`, send `"number": "901234567"` with `"country_code": "VN"`. The system will automatically prepend `84`. ::: --- ## Update a Phone Number ```bash PATCH /phone-numbers/:id ``` Update settings for an existing phone number. All fields are optional — only include the fields you want to change. ### Authentication Requires `ApiKey` with `full` scope. ### Path Parameters | Parameter | Type | Description | | --------- | ------ | --------------- | | `id` | string | Phone number ID | ### Request Body | Field | Type | Description | | --------------------- | -------------- | --------------------------------------------- | | `title` | string | Display name (1–36 characters) | | `inbound_acl_id` | string | [Inbound ACL ID](/docs/rest-api/workspace-api/sip-trunks#part-1-inbound-ip-access-control-lists-acls) for whitelisting inbound SIP traffic | | `enable_outbound` | boolean | Enable/disable outbound calling | | `outbound_gateway_id` | string | [Outbound Gateway ID](/docs/rest-api/workspace-api/sip-trunks#part-2-outbound-gateways) for trunk routing | | `shared_teams_id` | string[] | Team IDs this number is shared with (max 100) | | `record_outbound` | boolean | Enable/disable outbound call recording | | `call_flow_id` | string \| null | Assign a call flow, or `null` to unassign | ### Example Request ```bash curl -X PATCH "https://{workspace_id}.firetell.app/api/v1/phone-numbers/pn_a1b2c3d4e5f6g7h8i9j0k1" \ -H "Authorization: ApiKey YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "title": "Updated Hotline", "call_flow_id": "cf_new789", "record_outbound": false }' ``` ### Response `200 OK` Returns the updated phone number object. ```json { "id": "pn_a1b2c3d4e5f6g7h8i9j0k1", "title": "Updated Hotline", "number": "84901234567", "status": "active", "call_flow_id": "cf_new789", "record_outbound": false, "updated_at": "2026-07-13T09:00:00.000Z" } ``` ### Error Responses | Status | Description | | ------ | ---------------------------- | | `400` | Invalid ACL ID or gateway ID | | `404` | Phone number not found | --- ## Delete a Phone Number ```bash DELETE /phone-numbers/:id ``` Release (delete) a phone number from the workspace. This action is permanent and cannot be undone. ### Authentication Requires `ApiKey` with `full` scope. ### Path Parameters | Parameter | Type | Description | | --------- | ------ | --------------- | | `id` | string | Phone number ID | ### Example Request ```bash curl -X DELETE "https://{workspace_id}.firetell.app/api/v1/phone-numbers/pn_a1b2c3d4e5f6g7h8i9j0k1" \ -H "Authorization: ApiKey YOUR_API_KEY" ``` ### Response `200 OK` Returns the deleted phone number object. ```json { "id": "pn_a1b2c3d4e5f6g7h8i9j0k1", "title": "Main Hotline", "number": "84901234567", "status": "active", "workspace_id": "my_workspace" } ``` ### Error Responses | Status | Description | | ------ | ----------------------------------------------------------- | | `404` | Phone number not found or does not belong to this workspace | :::caution Deleting a phone number is **irreversible**. Any call flows, extensions, or SIP trunks referencing this number will no longer route calls to it. Make sure to update or remove those references before deleting. ::: --- # SIP Accounts SIP Accounts are credentials used by SIP-compatible softphones, desk phones, or other devices to register with the Firetell platform. Each SIP account can optionally be linked to an outbound gateway (SIP Trunk) for making external calls. ## Endpoints | Method | Endpoint | Description | | -------- | ------------------- | --------------------- | | `GET` | `/sip-accounts` | List all SIP accounts | | `POST` | `/sip-accounts` | Create a SIP account | | `PATCH` | `/sip-accounts/:id` | Update a SIP account | | `DELETE` | `/sip-accounts/:id` | Delete a SIP account | ## The SIP Account Object | Field | Type | Description | | ---------------------------- | -------------- | ------------------------------------------------------------------ | | `id` | string | Unique SIP account identifier (prefixed with `si_`) | | `title` | string | Display name of the SIP account | | `username` | string | SIP registration username | | `workspace_id` | string | Workspace identifier | | `domain` | string | SIP domain for registration (e.g., `yourcompany.firetell.app`) | | `outbound_gateway_id` | string \| null | Linked outbound gateway (SIP Trunk) ID | | `outbound_gateway_uri` | string \| null | Outbound gateway SIP URI | | `outbound_gateway_port` | number \| null | Outbound gateway port | | `outbound_gateway_transport` | string \| null | Transport protocol: `udp`, `tcp`, or `tls` | | `outbound_prefix` | string \| null | Outbound dialing prefix | | `outbound_caller_id` | string \| null | Outbound caller ID override | | `created_at` | string | ISO 8601 creation timestamp | | `updated_at` | string | ISO 8601 last update timestamp | :::info The `password` field is never returned in API responses. SIP registration credentials are stored securely with HA1/HA1B digest hashing. ::: --- ## List SIP Accounts ``` GET /sip-accounts ``` Retrieve a paginated list of all SIP accounts in the workspace. ### Authentication Requires `ApiKey` with any scope. ### Query Parameters | Parameter | Type | Default | Description | | ------------ | ------ | ------------ | ---------------------------------------------- | | `page` | number | `1` | Page number (min: 1) | | `limit` | number | `10` | Items per page (min: 1, max: 100) | | `search` | string | — | Search by title or username (case-insensitive) | | `sort_field` | string | `created_at` | Sort field. Allowed: `created_at`, `title` | | `sort_order` | string | `desc` | Sort order: `asc` or `desc` | ### Request ```bash curl -X GET "https://{workspace_id}.firetell.app/api/v1/sip-accounts?page=1&limit=10" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" ``` ### Response ```json { "data": [ { "id": "si_7f3a2b1c9d4e5f6a8b0c1d2e", "title": "Reception Desk Phone", "username": "reception01", "workspace_id": "yourcompany", "domain": "yourcompany.firetell.app", "outbound_gateway_id": "gw_a1b2c3d4e5f6a7b8c9d0e1f2", "outbound_gateway_uri": "sip.carrier.com", "outbound_gateway_port": 5060, "outbound_gateway_transport": "udp", "outbound_prefix": null, "outbound_caller_id": "+18001234567", "created_at": "2026-01-15T08:30:00.000Z", "updated_at": "2026-03-20T14:00:00.000Z" }, { "id": "si_9a8b7c6d5e4f3a2b1c0d9e8f", "title": "Softphone - John", "username": "john01", "workspace_id": "yourcompany", "domain": "yourcompany.firetell.app", "outbound_gateway_id": null, "outbound_gateway_uri": null, "outbound_gateway_port": null, "outbound_gateway_transport": null, "outbound_prefix": null, "outbound_caller_id": null, "created_at": "2026-02-10T10:00:00.000Z", "updated_at": "2026-02-10T10:00:00.000Z" } ], "meta": { "total": 3, "page": 1, "limit": 10, "total_pages": 1 } } ``` --- ## Create SIP Account ``` POST /sip-accounts ``` Create a new SIP account. The system will automatically register the SIP subscriber credentials in the SIP registrar database. ### Authentication Requires `ApiKey` with `full` scope. ### Request Body | Field | Type | Required | Description | | --------------------- | ------ | -------- | ------------------------------------------------------------------------------------------------ | | `title` | string | ✅ | Display name. 1–36 characters. | | `username` | string | ✅ | SIP username. 3–30 characters, lowercase letters and numbers only. Must be unique per workspace. | | `password` | string | ✅ | SIP registration password. 6–50 characters. | | `outbound_gateway_id` | string | — | ID of a SIP Trunk gateway to use for outbound calls. Max 50 characters. | ### Request ```bash curl -X POST "https://{workspace_id}.firetell.app/api/v1/sip-accounts" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "title": "Conference Room Phone", "username": "confroom01", "password": "securePass123" }' ``` ### Response ```json { "id": "si_d4e5f6a7b8c9d0e1f2a3b4c5", "title": "Conference Room Phone", "username": "confroom01", "workspace_id": "yourcompany", "domain": "yourcompany.firetell.app", "outbound_gateway_id": null, "outbound_gateway_uri": null, "outbound_gateway_port": null, "outbound_gateway_transport": null, "outbound_prefix": null, "outbound_caller_id": null, "created_at": "2026-07-09T08:00:00.000Z", "updated_at": "2026-07-09T08:00:00.000Z" } ``` ### With Outbound Gateway ```bash curl -X POST "https://{workspace_id}.firetell.app/api/v1/sip-accounts" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "title": "Sales Desk Phone", "username": "sales01", "password": "securePass456", "outbound_gateway_id": "gw_a1b2c3d4e5f6a7b8c9d0e1f2" }' ``` ```json { "id": "si_e5f6a7b8c9d0e1f2a3b4c5d6", "title": "Sales Desk Phone", "username": "sales01", "workspace_id": "yourcompany", "domain": "yourcompany.firetell.app", "outbound_gateway_id": "gw_a1b2c3d4e5f6a7b8c9d0e1f2", "outbound_gateway_uri": "sip.carrier.com", "outbound_gateway_port": 5060, "outbound_gateway_transport": "udp", "outbound_prefix": null, "outbound_caller_id": "+18001234567", "created_at": "2026-07-09T08:00:00.000Z", "updated_at": "2026-07-09T08:00:00.000Z" } ``` ### Error Responses **Duplicate username:** ```json { "statusCode": 409, "message": "username confroom01 is exists", "error": "Conflict" } ``` **Invalid outbound gateway:** ```json { "statusCode": 400, "message": "outbound_gateway is not found: gw_invalid", "error": "Bad Request" } ``` **Gateway not configured:** ```json { "statusCode": 400, "message": "outbound_gateway target is not configured: gw_a1b2c3d4e5f6a7b8c9d0e1f2", "error": "Bad Request" } ``` :::tip **SIP Registration Details** After creating a SIP account, configure your SIP device with: - **SIP Server / Registrar:** The `domain` value from the response - **Username:** The `username` you provided - **Password:** The `password` you provided - **Transport:** UDP (default), TCP, or TLS ::: --- ## Update SIP Account ``` PATCH /sip-accounts/:id ``` Update an existing SIP account. Only include the fields you want to change. The `username` cannot be changed after creation. ### Authentication Requires `ApiKey` with `full` scope. ### Path Parameters | Parameter | Type | Description | | --------- | ------ | -------------------------------------------------------- | | `id` | string | The SIP account ID (e.g., `si_7f3a2b1c9d4e5f6a8b0c1d2e`) | ### Request Body All fields are optional: | Field | Type | Description | | --------------------- | -------------- | -------------------------------------------------------------- | | `title` | string | New display name. 1–36 characters. | | `password` | string | New SIP registration password. 6–50 characters. | | `outbound_gateway_id` | string \| null | New outbound gateway ID, or `null` to remove the gateway link. | ### Request ```bash curl -X PATCH "https://{workspace_id}.firetell.app/api/v1/sip-accounts/si_7f3a2b1c9d4e5f6a8b0c1d2e" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "title": "Main Reception Phone", "password": "newSecurePass789" }' ``` ### Response Returns the updated SIP account object: ```json { "id": "si_7f3a2b1c9d4e5f6a8b0c1d2e", "title": "Main Reception Phone", "username": "reception01", "workspace_id": "yourcompany", "domain": "yourcompany.firetell.app", "outbound_gateway_id": "gw_a1b2c3d4e5f6a7b8c9d0e1f2", "outbound_gateway_uri": "sip.carrier.com", "outbound_gateway_port": 5060, "outbound_gateway_transport": "udp", "outbound_prefix": null, "outbound_caller_id": "+18001234567", "created_at": "2026-01-15T08:30:00.000Z", "updated_at": "2026-07-09T09:30:00.000Z" } ``` ### Remove Outbound Gateway To disconnect a SIP account from its outbound gateway, set `outbound_gateway_id` to `null`: ```bash curl -X PATCH "https://{workspace_id}.firetell.app/api/v1/sip-accounts/si_7f3a2b1c9d4e5f6a8b0c1d2e" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "outbound_gateway_id": null }' ``` ### Error Response ```json { "statusCode": 404, "message": "Sip account not found or does not belong to this workspace", "error": "Not Found" } ``` --- ## Delete SIP Account ``` DELETE /sip-accounts/:id ``` Permanently delete a SIP account. The SIP subscriber credentials are also removed from the registrar database, immediately preventing the device from registering. ### Authentication Requires `ApiKey` with `full` scope. :::caution This action is irreversible. Any SIP devices registered with this account will be immediately disconnected and unable to make or receive calls. ::: ### Path Parameters | Parameter | Type | Description | | --------- | ------ | ------------------ | | `id` | string | The SIP account ID | ### Request ```bash curl -X DELETE "https://{workspace_id}.firetell.app/api/v1/sip-accounts/si_7f3a2b1c9d4e5f6a8b0c1d2e" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" ``` ### Response Returns the deleted SIP account object: ```json { "id": "si_7f3a2b1c9d4e5f6a8b0c1d2e", "title": "Reception Desk Phone", "username": "reception01", "workspace_id": "yourcompany", "domain": "yourcompany.firetell.app", "created_at": "2026-01-15T08:30:00.000Z", "updated_at": "2026-03-20T14:00:00.000Z" } ``` ### Error Response ```json { "statusCode": 404, "message": "Sip account not found or does not belong to this workspace", "error": "Not Found" } ``` --- # SIP Trunks SIP Trunks allow you to connect your existing IP PBX or telephony infrastructure to Firetell's cloud communications network for inbound and outbound voice traffic. SIP Trunking in Firetell consists of two primary resources: 1. **Inbound IP Access Control Lists (ACLs)**: Whitelist IP addresses or CIDR blocks allowed to send inbound SIP calls into Firetell. 2. **Outbound Gateways**: Configure outbound carrier SIP trunks, origination target URIs, protocols, and SIP authentication credentials. --- ## Resource Endpoints ### Inbound IP Access Control Lists (ACLs) | Method | Endpoint | Description | | -------- | --------------------- | -------------------------------- | | `GET` | `/sip-trunks/acls` | List all Inbound ACLs | | `GET` | `/sip-trunks/acls/:id` | Get Inbound ACL details | | `POST` | `/sip-trunks/acls` | Create an Inbound ACL | | `PATCH` | `/sip-trunks/acls/:id` | Update an Inbound ACL | | `DELETE` | `/sip-trunks/acls/:id` | Delete an Inbound ACL | ### Outbound Gateways | Method | Endpoint | Description | | -------- | ------------------------- | ----------------------------- | | `GET` | `/sip-trunks/gateways` | List all Outbound Gateways | | `GET` | `/sip-trunks/gateways/:id` | Get Outbound Gateway details | | `POST` | `/sip-trunks/gateways` | Create an Outbound Gateway | | `PATCH` | `/sip-trunks/gateways/:id` | Update an Outbound Gateway | | `DELETE` | `/sip-trunks/gateways/:id` | Delete an Outbound Gateway | --- # Part 1: Inbound IP Access Control Lists (ACLs) Inbound ACLs control which remote IP addresses or subnets are authorized to send inbound SIP traffic into your Firetell workspace. ## The ACL Object | Field | Type | Description | | ------------- | -------- | -------------------------------------------------------------- | | `id` | string | Unique ACL identifier (prefixed with `acl_`) | | `workspace_id`| string | Workspace identifier | | `title` | string | Descriptive label for this ACL | | `mode` | string | Filter mode (`ip` or `domain`) | | `allowed_ips` | array | List of authorized IPv4/IPv6 addresses or CIDR blocks (max: 5) | | `created_at` | string | ISO 8601 creation timestamp | | `updated_at` | string | ISO 8601 last update timestamp | --- ## List Inbound ACLs ``` GET /sip-trunks/acls ``` ### Query Parameters | Parameter | Type | Default | Description | | ------------ | ------ | ------------ | ------------------------------------------------- | | `page` | number | `1` | Page number | | `limit` | number | `10` | Items per page (max: 100) | | `search` | string | — | Search by ACL title | | `sort_field` | string | `created_at` | Sort field (`created_at`, `title`) | | `sort_order` | string | `desc` | Sort direction (`asc`, `desc`) | ### Response `200 OK` ```json { "data": [ { "id": "acl_123456789", "workspace_id": "ws_company", "title": "Primary PBX Server", "mode": "ip", "allowed_ips": ["118.69.123.45", "203.162.0.0/24"], "created_at": "2026-07-21T15:00:00.000Z", "updated_at": "2026-07-21T15:00:00.000Z" } ], "meta": { "total": 1, "page": 1, "limit": 10, "total_pages": 1 } } ``` --- ## Create Inbound ACL ``` POST /sip-trunks/acls ``` ### Request Body ```json { "title": "Backup Data Center PBX", "mode": "ip", "allowed_ips": ["14.225.10.12/32"] } ``` ### Response `201 Created` ```json { "id": "acl_987654321", "workspace_id": "ws_company", "title": "Backup Data Center PBX", "mode": "ip", "allowed_ips": ["14.225.10.12/32"], "created_at": "2026-07-21T15:15:00.000Z", "updated_at": "2026-07-21T15:15:00.000Z" } ``` --- ## Update Inbound ACL ``` PATCH /sip-trunks/acls/:id ``` ### Request Body ```json { "title": "HQ Asterisk PBX", "allowed_ips": ["118.69.123.45", "118.69.123.46"] } ``` ### Response `200 OK` ```json { "id": "acl_123456789", "workspace_id": "ws_company", "title": "HQ Asterisk PBX", "mode": "ip", "allowed_ips": ["118.69.123.45", "118.69.123.46"], "updated_at": "2026-07-21T15:20:00.000Z" } ``` --- ## Delete Inbound ACL ``` DELETE /sip-trunks/acls/:id ``` ### Response `200 OK` ```json { "id": "acl_123456789", "workspace_id": "ws_company", "title": "HQ Asterisk PBX" } ``` --- # Part 2: Outbound Gateways Outbound Gateways define carrier trunks and remote SIP endpoints for routing outbound calls from Firetell to external telco networks or private PBX systems. ## The Gateway Object | Field | Type | Description | | --------------------- | -------- | -------------------------------------------------------------------- | | `id` | string | Unique gateway identifier (prefixed with `gw_`) | | `workspace_id` | string | Workspace identifier | | `title` | string | Descriptive label for this gateway | | `outbound_caller_id` | string | Default caller ID number to present when placing calls via this trunk | | `outbound_uris` | array | Array of target SIP destination URIs (1 to 10 targets) | | `outbound_uris[].uri` | string | Destination IP or FQDN host (e.g. `sip.telco.com` or `1.2.3.4`) | | `outbound_uris[].port`| number | SIP port (`1` to `65535`, default `5060`) | | `outbound_uris[].protocol` | string | Transport protocol (`udp`, `tcp`, `tls`) | | `outbound_uris[].priority` | number | Routing priority order (`1` highest to `10`) | | `credential` | object | SIP Digest authentication credentials (if required by trunk provider)| | `credential.username` | string | SIP auth username | | `created_at` | string | ISO 8601 creation timestamp | | `updated_at` | string | ISO 8601 last update timestamp | --- ## List Outbound Gateways ``` GET /sip-trunks/gateways ``` ### Response `200 OK` ```json { "data": [ { "id": "gw_abc123456", "workspace_id": "ws_company", "title": "Viettel Carrier Trunk", "outbound_caller_id": "842471001234", "outbound_uris": [ { "uri": "10.0.1.50", "port": 5060, "protocol": "udp", "priority": 1 } ], "credential": { "username": "vt_trunk_user" }, "created_at": "2026-07-21T15:05:00.000Z", "updated_at": "2026-07-21T15:05:00.000Z" } ], "meta": { "total": 1, "page": 1, "limit": 10, "total_pages": 1 } } ``` --- ## Create Outbound Gateway ``` POST /sip-trunks/gateways ``` ### Request Body ```json { "title": "FPT Telecom Trunk", "outbound_caller_id": "842873009999", "outbound_uris": [ { "uri": "sip.fpttelecom.vn", "port": 5060, "protocol": "udp", "priority": 1 } ], "credential": { "username": "fpt_user", "password": "SecretPassword123" } } ``` ### Response `201 Created` ```json { "id": "gw_xyz789012", "workspace_id": "ws_company", "title": "FPT Telecom Trunk", "outbound_caller_id": "842873009999", "outbound_uris": [ { "uri": "sip.fpttelecom.vn", "port": 5060, "protocol": "udp", "priority": 1 } ], "created_at": "2026-07-21T15:25:00.000Z", "updated_at": "2026-07-21T15:25:00.000Z" } ``` --- ## Update Outbound Gateway ``` PATCH /sip-trunks/gateways/:id ``` ### Request Body ```json { "title": "FPT Telecom Primary Trunk", "outbound_uris": [ { "uri": "sip.fpttelecom.vn", "port": 5061, "protocol": "tls", "priority": 1 } ] } ``` ### Response `200 OK` ```json { "id": "gw_xyz789012", "workspace_id": "ws_company", "title": "FPT Telecom Primary Trunk", "outbound_uris": [ { "uri": "sip.fpttelecom.vn", "port": 5061, "protocol": "tls", "priority": 1 } ], "updated_at": "2026-07-21T15:30:00.000Z" } ``` --- ## Delete Outbound Gateway ``` DELETE /sip-trunks/gateways/:id ``` ### Response `200 OK` ```json { "id": "gw_xyz789012", "workspace_id": "ws_company", "title": "FPT Telecom Primary Trunk" } ``` --- # Teams Teams group agents together for call routing, queue management, and workload distribution. Each team can contain multiple agents, and each agent can belong to multiple teams. Team members can have a role of either `leader` or `member`. ## Endpoints | Method | Endpoint | Description | | -------- | ------------------------------ | ------------------------------ | | `GET` | `/teams` | List all teams | | `GET` | `/teams/:id` | Get team details | | `POST` | `/teams` | Create a team | | `PATCH` | `/teams/:id` | Update a team | | `DELETE` | `/teams/:id` | Delete a team | | `GET` | `/teams/:id/agents` | List agents in a team | | `PUT` | `/teams/:id/agents/:agent_id` | Add an agent to a team | | `DELETE` | `/teams/:id/agents/:agent_id` | Remove an agent from a team | | `GET` | `/teams/:id/suggestion-agents` | Suggest agents not in the team | ## The Team Object | Field | Type | Description | | -------------- | ------ | -------------------------------------------- | | `id` | string | Unique team identifier (prefixed with `te_`) | | `title` | string | Team name | | `workspace_id` | string | Workspace identifier | | `agent_count` | number | Number of agents currently in the team | | `created_at` | string | ISO 8601 creation timestamp | | `updated_at` | string | ISO 8601 last update timestamp | ### Team Agent Object When listing agents within a team, each item contains: | Field | Type | Description | | -------------- | ------ | ---------------------------------------------- | | `team_id` | string | The team ID | | `agent_id` | string | The agent ID | | `workspace_id` | string | Workspace identifier | | `role` | string | Agent's role in the team: `leader` or `member` | | `agent` | object | Populated agent details (see below) | | `created_at` | string | ISO 8601 timestamp when agent joined | | `updated_at` | string | ISO 8601 last update timestamp | The populated `agent` object includes: | Field | Type | Description | | -------------- | -------------- | ------------------ | | `id` | string | Agent ID | | `display_name` | string | Agent display name | | `avatar` | string \| null | Agent avatar URL | | `email` | string \| null | Agent email | | `username` | string | Agent username | | `country_code` | string | Agent country code | --- ## List Teams ``` GET /teams ``` Retrieve a paginated list of all teams in the workspace. ### Authentication Requires `ApiKey` with any scope. ### Query Parameters | Parameter | Type | Default | Description | | ------------ | ------ | ------------ | --------------------------------------------------------- | | `page` | number | `1` | Page number (min: 1) | | `limit` | number | `10` | Items per page (min: 1, max: 100) | | `search` | string | — | Search teams by title | | `sort_field` | string | `created_at` | Sort field. Allowed: `created_at`, `title`, `agent_count` | | `sort_order` | string | `desc` | Sort order: `asc` or `desc` | ### Request ```bash curl -X GET "https://{workspace_id}.firetell.app/api/v1/teams?page=1&limit=10&sort_field=agent_count&sort_order=desc" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" ``` ### Response ```json { "data": [ { "id": "te_a1b2c3d4e5f6a7b8c9d0e1f2", "title": "Support Team", "workspace_id": "yourcompany", "agent_count": 12, "created_at": "2026-01-10T09:00:00.000Z", "updated_at": "2026-03-20T14:00:00.000Z" }, { "id": "te_b2c3d4e5f6a7b8c9d0e1f2a3", "title": "Sales Team", "workspace_id": "yourcompany", "agent_count": 8, "created_at": "2026-01-12T11:00:00.000Z", "updated_at": "2026-02-15T10:30:00.000Z" } ], "meta": { "total": 5, "page": 1, "limit": 10, "total_pages": 1 } } ``` --- ## Get Team ``` GET /teams/:id ``` Retrieve details of a specific team. ### Authentication Requires `ApiKey` with any scope. ### Path Parameters | Parameter | Type | Description | | --------- | ------ | ------------------------------------------------- | | `id` | string | The team ID (e.g., `te_a1b2c3d4e5f6a7b8c9d0e1f2`) | ### Request ```bash curl -X GET "https://{workspace_id}.firetell.app/api/v1/teams/te_a1b2c3d4e5f6a7b8c9d0e1f2" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" ``` ### Response ```json { "id": "te_a1b2c3d4e5f6a7b8c9d0e1f2", "title": "Support Team", "workspace_id": "yourcompany", "agent_count": 12, "created_at": "2026-01-10T09:00:00.000Z", "updated_at": "2026-03-20T14:00:00.000Z" } ``` ### Error Response ```json { "statusCode": 404, "message": "Team not found or does not belong to this workspace", "error": "Not Found" } ``` --- ## Create Team ``` POST /teams ``` Create a new team in the workspace. ### Authentication Requires `ApiKey` with `full` scope. :::caution Each workspace is limited to a maximum of **100 teams**. Exceeding this limit will return a `402 Payment Required` error. ::: ### Request Body | Field | Type | Required | Description | | ------- | ------ | -------- | --------------------------- | | `title` | string | ✅ | Team name. 1–36 characters. | ### Request ```bash curl -X POST "https://{workspace_id}.firetell.app/api/v1/teams" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "title": "Technical Support" }' ``` ### Response ```json { "id": "te_c3d4e5f6a7b8c9d0e1f2a3b4", "title": "Technical Support", "workspace_id": "yourcompany", "agent_count": 0, "created_at": "2026-07-09T08:00:00.000Z", "updated_at": "2026-07-09T08:00:00.000Z" } ``` ### Error Response **Team limit reached:** ```json { "statusCode": 402, "message": "You have reached the maximum limit of 100 team. Please contact support" } ``` --- ## Update Team ``` PATCH /teams/:id ``` Update a team's title. ### Authentication Requires `ApiKey` with `full` scope. ### Path Parameters | Parameter | Type | Description | | --------- | ------ | ----------- | | `id` | string | The team ID | ### Request Body | Field | Type | Required | Description | | ------- | ------ | -------- | ------------------------------- | | `title` | string | ✅ | New team name. 1–36 characters. | ### Request ```bash curl -X PATCH "https://{workspace_id}.firetell.app/api/v1/teams/te_a1b2c3d4e5f6a7b8c9d0e1f2" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "title": "Premium Support" }' ``` ### Response Returns the updated team object: ```json { "id": "te_a1b2c3d4e5f6a7b8c9d0e1f2", "title": "Premium Support", "workspace_id": "yourcompany", "agent_count": 12, "created_at": "2026-01-10T09:00:00.000Z", "updated_at": "2026-07-09T09:30:00.000Z" } ``` ### Error Response ```json { "statusCode": 400, "message": "Team not found or does not belong to this workspace", "error": "Bad Request" } ``` --- ## Delete Team ``` DELETE /teams/:id ``` Permanently delete a team. All agent memberships in this team will be automatically removed. ### Authentication Requires `ApiKey` with `full` scope. :::caution This action is irreversible. All agents will be removed from the team, but the agent accounts themselves will not be affected. ::: ### Path Parameters | Parameter | Type | Description | | --------- | ------ | ----------- | | `id` | string | The team ID | ### Request ```bash curl -X DELETE "https://{workspace_id}.firetell.app/api/v1/teams/te_a1b2c3d4e5f6a7b8c9d0e1f2" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" ``` ### Response Returns the deleted team object: ```json { "id": "te_a1b2c3d4e5f6a7b8c9d0e1f2", "title": "Support Team", "workspace_id": "yourcompany", "agent_count": 12, "created_at": "2026-01-10T09:00:00.000Z", "updated_at": "2026-03-20T14:00:00.000Z" } ``` --- ## List Team Agents ``` GET /teams/:id/agents ``` Retrieve a paginated list of agents that belong to a specific team, including their role and populated agent details. ### Authentication Requires `ApiKey` with any scope. ### Path Parameters | Parameter | Type | Description | | --------- | ------ | ----------- | | `id` | string | The team ID | ### Query Parameters | Parameter | Type | Default | Description | | ------------ | ------ | ------------ | --------------------------- | | `page` | number | `1` | Page number | | `limit` | number | `10` | Items per page (max: 100) | | `sort_field` | string | `created_at` | Sort field | | `sort_order` | string | `desc` | Sort order: `asc` or `desc` | ### Request ```bash curl -X GET "https://{workspace_id}.firetell.app/api/v1/teams/te_a1b2c3d4e5f6a7b8c9d0e1f2/agents?page=1&limit=10" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" ``` ### Response ```json { "data": [ { "team_id": "te_a1b2c3d4e5f6a7b8c9d0e1f2", "agent_id": "ag_7f3a2b1c9d4e5f6a8b0c1d2e", "workspace_id": "yourcompany", "role": "leader", "agent": { "id": "ag_7f3a2b1c9d4e5f6a8b0c1d2e", "display_name": "John Doe", "avatar": "https://s3.amazonaws.com/bucket/yourcompany/avatars/abc123.webp", "email": "john@example.com", "username": "johndoe", "country_code": "US" }, "created_at": "2026-02-01T10:00:00.000Z", "updated_at": "2026-02-01T10:00:00.000Z" }, { "team_id": "te_a1b2c3d4e5f6a7b8c9d0e1f2", "agent_id": "ag_9a8b7c6d5e4f3a2b1c0d9e8f", "workspace_id": "yourcompany", "role": "member", "agent": { "id": "ag_9a8b7c6d5e4f3a2b1c0d9e8f", "display_name": "Jane Doe", "avatar": null, "email": "jane@example.com", "username": "janedoe", "country_code": "VN" }, "created_at": "2026-02-05T14:30:00.000Z", "updated_at": "2026-02-05T14:30:00.000Z" } ], "meta": { "total": 3, "page": 1, "limit": 10, "total_pages": 1 } } ``` --- ## Add Agent to Team ``` PUT /teams/:id/agents/:agent_id ``` Add an agent to a team. If the agent is already in the team, their role will be updated. The team's `agent_count` is automatically recalculated. ### Authentication Requires `ApiKey` with `full` scope. ### Path Parameters | Parameter | Type | Description | | ---------- | ------ | ------------------- | | `id` | string | The team ID | | `agent_id` | string | The agent ID to add | ### Request ```bash curl -X PUT "https://{workspace_id}.firetell.app/api/v1/teams/te_a1b2c3d4e5f6a7b8c9d0e1f2/agents/ag_7f3a2b1c9d4e5f6a8b0c1d2e" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" ``` ### Response Returns the team-agent membership with populated agent details: ```json { "team_id": "te_a1b2c3d4e5f6a7b8c9d0e1f2", "agent_id": "ag_7f3a2b1c9d4e5f6a8b0c1d2e", "workspace_id": "yourcompany", "role": "member", "agent": { "id": "ag_7f3a2b1c9d4e5f6a8b0c1d2e", "display_name": "John Doe", "avatar": null, "email": "john@example.com", "username": "johndoe", "country_code": "US" }, "created_at": "2026-07-09T08:00:00.000Z", "updated_at": "2026-07-09T08:00:00.000Z" } ``` :::tip This endpoint uses **upsert** behavior — if the agent is already in the team, the request will succeed and update the membership rather than returning an error. ::: ### Error Response ```json { "statusCode": 400, "message": "Agent not found or does not belong to this workspace", "error": "Bad Request" } ``` --- ## Remove Agent from Team ``` DELETE /teams/:id/agents/:agent_id ``` Remove an agent from a team. The team's `agent_count` is automatically recalculated. The agent account itself is not affected. ### Authentication Requires `ApiKey` with `full` scope. ### Path Parameters | Parameter | Type | Description | | ---------- | ------ | ---------------------- | | `id` | string | The team ID | | `agent_id` | string | The agent ID to remove | ### Request ```bash curl -X DELETE "https://{workspace_id}.firetell.app/api/v1/teams/te_a1b2c3d4e5f6a7b8c9d0e1f2/agents/ag_7f3a2b1c9d4e5f6a8b0c1d2e" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" ``` ### Response Returns the removed team-agent membership with populated agent details: ```json { "team_id": "te_a1b2c3d4e5f6a7b8c9d0e1f2", "agent_id": "ag_7f3a2b1c9d4e5f6a8b0c1d2e", "workspace_id": "yourcompany", "role": "member", "agent": { "id": "ag_7f3a2b1c9d4e5f6a8b0c1d2e", "display_name": "John Doe", "avatar": null, "email": "john@example.com", "username": "johndoe", "country_code": "US" }, "created_at": "2026-02-01T10:00:00.000Z", "updated_at": "2026-02-01T10:00:00.000Z" } ``` --- ## Suggest Agents ``` GET /teams/:id/suggestion-agents ``` Get a list of agents who are **not yet members** of the specified team. Useful for building "Add Agent" UI with search functionality. ### Authentication Requires `ApiKey` with any scope. ### Path Parameters | Parameter | Type | Description | | --------- | ------ | ----------- | | `id` | string | The team ID | ### Query Parameters | Parameter | Type | Description | | --------- | ------ | ------------------------------------------------ | | `search` | string | Search by agent display name, email, or username | ### Request ```bash curl -X GET "https://{workspace_id}.firetell.app/api/v1/teams/te_a1b2c3d4e5f6a7b8c9d0e1f2/suggestion-agents?search=john" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" ``` ### Response Returns up to 20 agents not in the team: ```json { "items": [ { "id": "ag_d4e5f6a7b8c9d0e1f2a3b4c5", "username": "johnsmith", "domain": "yourcompany.firetell.app", "display_name": "John Smith", "email": "johnsmith@example.com", "avatar": null, "state": "available", "is_active": true, "workspace_id": "yourcompany", "country_code": "US", "created_at": "2026-03-01T08:00:00.000Z", "updated_at": "2026-03-01T08:00:00.000Z" } ] } ``` :::info This endpoint returns a maximum of **20 results** and does not support pagination. Use the `search` parameter to narrow down results. ::: ### Error Response ```json { "statusCode": 404, "message": "Team not found or does not belong to this workspace", "error": "Not Found" } ``` --- # Voice Agents Voice Agents are AI-powered bots that handle customer calls autonomously using real-time speech processing. They integrate LLM models with text-to-speech and speech-to-text capabilities to have natural conversations with callers. Voice Agents use a **versioned settings** model — you can create multiple versions of your agent's configuration, test them, and publish the best one for production. ## Endpoints | Method | Endpoint | Description | | -------- | --------------------------------------------------- | ------------------------------------- | | `GET` | `/voice-agents` | List all voice agents | | `GET` | `/voice-agents/:id` | Get voice agent details with settings | | `POST` | `/voice-agents` | Create a voice agent | | `PATCH` | `/voice-agents/:id` | Update voice agent name | | `DELETE` | `/voice-agents/:id` | Delete a voice agent | | `GET` | `/voice-agents/:id/versions` | List all setting versions | | `PATCH` | `/voice-agents/:id/settings` | Update settings for a version | | `PATCH` | `/voice-agents/:id/publish` | Publish a settings version | | `POST` | `/voice-agents/:id/version` | Create a new settings version | | `DELETE` | `/voice-agents/:id/version/:version` | Delete a settings version | | `GET` | `/voice-agents/models` | List available AI models | | `GET` | `/voice-agents/voices` | List available TTS voices | | `POST` | `/voice-agents/generate-instructions` | Start AI instruction generation | | `POST` | `/voice-agents/generate-instructions/:conversation` | Continue instruction generation | | `POST` | `/voice-agents/optimize/:id` | Optimize existing instructions | ## The Voice Agent Object | Field | Type | Description | | ------------------- | ------ | -------------------------------------------------- | | `id` | string | Unique voice agent identifier | | `name` | string | Voice agent name | | `workspace_id` | string | Workspace identifier | | `country_code` | string | Country code inherited from workspace | | `published_version` | number | Currently published settings version | | `settings` | object | The settings object (included in detail responses) | | `created_at` | string | ISO 8601 creation timestamp | | `updated_at` | string | ISO 8601 last update timestamp | ### Voice Agent Settings Object | Field | Type | Description | | --------------------------- | -------------- | -------------------------------------- | | `id` | string | Unique settings record ID | | `voice_agent_id` | string | Parent voice agent ID | | `workspace_id` | string | Workspace identifier | | `model_id` | string | AI model ID (e.g., `gpt-realtime`) | | `voice_id` | string \| null | TTS voice ID | | `greeting_message` | string | Initial greeting when call connects | | `instructions` | string | System prompt / behavior instructions | | `temperature` | number | Model creativity (default: `0.8`) | | `enable_vad` | boolean | Voice Activity Detection enabled | | `enable_noise_reduction` | boolean | Background noise reduction enabled | | `max_call_duration_seconds` | number | Maximum call duration (0–1800 seconds) | | `tools_id` | string[] | IDs of integrated tools/functions | | `version` | number | Settings version number | | `created_at` | string | ISO 8601 creation timestamp | | `updated_at` | string | ISO 8601 last update timestamp | --- ## List Voice Agents ``` GET /voice-agents ``` Retrieve a paginated list of all voice agents in the workspace. ### Authentication Requires `ApiKey` with any scope. ### Query Parameters | Parameter | Type | Default | Description | | ------------ | ------ | ------------ | ----------------------------------------- | | `page` | number | `1` | Page number | | `limit` | number | `10` | Items per page (max: 100) | | `search` | string | — | Search by name | | `sort_field` | string | `created_at` | Sort field. Allowed: `created_at`, `name` | | `sort_order` | string | `desc` | Sort order: `asc` or `desc` | ### Request ```bash curl -X GET "https://{workspace_id}.firetell.app/api/v1/voice-agents?page=1&limit=10" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" ``` ### Response ```json { "data": [ { "id": "va_7f3a2b1c-9d4e-5f6a-8b0c-1d2e3f4a5b6c", "name": "Support Assistant", "workspace_id": "yourcompany", "country_code": "US", "published_version": 2, "created_at": "2026-01-15T08:30:00.000Z", "updated_at": "2026-03-20T14:00:00.000Z" } ], "meta": { "total": 3, "page": 1, "limit": 10, "total_pages": 1 } } ``` --- ## Get Voice Agent ``` GET /voice-agents/:id ``` Retrieve a voice agent with its settings. By default returns the latest settings version. Optionally specify a version number. ### Authentication Requires `ApiKey` with any scope. ### Path Parameters | Parameter | Type | Description | | --------- | ------ | ------------------ | | `id` | string | The voice agent ID | ### Query Parameters | Parameter | Type | Description | | --------- | ------ | ---------------------------------------------------------------------- | | `version` | number | Specific settings version to retrieve. Defaults to the latest version. | ### Request ```bash curl -X GET "https://{workspace_id}.firetell.app/api/v1/voice-agents/va_7f3a2b1c-9d4e-5f6a-8b0c-1d2e3f4a5b6c" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" ``` ### Response ```json { "id": "va_7f3a2b1c-9d4e-5f6a-8b0c-1d2e3f4a5b6c", "name": "Support Assistant", "workspace_id": "yourcompany", "country_code": "US", "published_version": 2, "created_at": "2026-01-15T08:30:00.000Z", "updated_at": "2026-03-20T14:00:00.000Z", "settings": { "id": "vas_a1b2c3d4e5f6a7b8c9d0e1f2", "voice_agent_id": "va_7f3a2b1c-9d4e-5f6a-8b0c-1d2e3f4a5b6c", "workspace_id": "yourcompany", "model_id": "gpt-realtime", "voice_id": "alloy", "greeting_message": "Thank you for calling Acme Corp. This is Support Assistant. How may I help you today?", "instructions": "You are a helpful customer support agent for Acme Corp...", "temperature": 0.8, "enable_vad": true, "enable_noise_reduction": true, "max_call_duration_seconds": 180, "tools_id": [], "version": 2, "created_at": "2026-02-01T10:00:00.000Z", "updated_at": "2026-03-20T14:00:00.000Z" } } ``` ### Error Response ```json { "statusCode": 404, "message": "Voice Agent not found", "error": "Not Found" } ``` --- ## Create Voice Agent ``` POST /voice-agents ``` Create a new voice agent with initial instructions. A default settings version (v1) is automatically created with sensible defaults. ### Authentication Requires `ApiKey` with `full` scope. :::caution Each workspace is limited to a maximum of **30 voice agents**. ::: ### Request Body | Field | Type | Required | Description | | -------------- | ------ | -------- | ----------------------------------------------------------- | | `name` | string | ✅ | Voice agent name. 3–30 characters. | | `instructions` | string | ✅ | System prompt / behavior instructions. 10–3,500 characters. | | `conversation` | string | — | AI conversation ID from instruction generation flow. | | `response_id` | string | — | AI response ID from instruction generation flow. | ### Default Settings When a voice agent is created, the following defaults are applied to the initial settings: | Setting | Default Value | | --------------------------- | -------------------------------------------------- | | `model_id` | `gpt-realtime` | | `voice_id` | `null` (system default) | | `temperature` | `0.8` | | `enable_vad` | `true` | | `enable_noise_reduction` | `true` | | `max_call_duration_seconds` | `180` (3 minutes) | | `greeting_message` | Auto-generated from workspace title and agent name | ### Request ```bash curl -X POST "https://{workspace_id}.firetell.app/api/v1/voice-agents" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Sales Bot", "instructions": "You are a sales assistant for Acme Corp. Help customers learn about our products and pricing. Always be professional and helpful. If a customer wants to speak with a human agent, transfer the call immediately." }' ``` ### Response ```json { "id": "va_d4e5f6a7-b8c9-d0e1-f2a3-b4c5d6e7f8a9", "name": "Sales Bot", "workspace_id": "yourcompany", "country_code": "US", "published_version": 1, "created_at": "2026-07-09T08:00:00.000Z", "updated_at": "2026-07-09T08:00:00.000Z" } ``` ### Error Response ```json { "statusCode": 403, "message": "You have reached the maximum limit of 30 voice agents. Please contact support", "error": "Forbidden" } ``` --- ## Update Voice Agent ``` PATCH /voice-agents/:id ``` Update a voice agent's name. To update settings (instructions, model, voice, etc.), use the [Update Settings](#update-voice-agent-settings) endpoint. ### Authentication Requires `ApiKey` with `full` scope. ### Request Body | Field | Type | Required | Description | | ------ | ------ | -------- | -------------------------- | | `name` | string | — | New name. 3–30 characters. | ### Request ```bash curl -X PATCH "https://{workspace_id}.firetell.app/api/v1/voice-agents/va_7f3a2b1c-9d4e-5f6a-8b0c-1d2e3f4a5b6c" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Premium Support Bot" }' ``` ### Response Returns the full voice agent object with current settings (same format as [Get Voice Agent](#get-voice-agent)). --- ## Delete Voice Agent ``` DELETE /voice-agents/:id ``` Permanently delete a voice agent and all its settings versions. ### Authentication Requires `ApiKey` with `full` scope. :::caution This action is irreversible. The voice agent and all its versioned settings will be permanently deleted. ::: ### Request ```bash curl -X DELETE "https://{workspace_id}.firetell.app/api/v1/voice-agents/va_7f3a2b1c-9d4e-5f6a-8b0c-1d2e3f4a5b6c" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" ``` ### Response Returns the deleted voice agent object. --- ## Settings Version Management Voice agents support **versioned settings**, allowing you to iterate on configurations and roll back if needed. ### List Versions ``` GET /voice-agents/:id/versions ``` List all settings versions for a voice agent, sorted by version number (newest first). ### Authentication Requires `ApiKey` with any scope. #### Request ```bash curl -X GET "https://{workspace_id}.firetell.app/api/v1/voice-agents/va_7f3a2b1c-9d4e-5f6a-8b0c-1d2e3f4a5b6c/versions" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" ``` #### Response ```json { "data": [ { "version": 3, "updated_at": "2026-07-09T09:00:00.000Z" }, { "version": 2, "updated_at": "2026-06-15T14:30:00.000Z" }, { "version": 1, "updated_at": "2026-01-15T08:30:00.000Z" } ], "meta": { "total": 3, "page": 1, "limit": 10, "total_pages": 1 } } ``` --- ### Update Voice Agent Settings ``` PATCH /voice-agents/:id/settings ``` Update the settings of a specific version. You must specify the `version` number of the settings you want to modify. ### Authentication Requires `ApiKey` with `full` scope. #### Request Body All fields are optional except `version`: | Field | Type | Required | Description | | --------------------------- | -------- | -------- | -------------------------------------------- | | `version` | number | ✅ | The settings version to update (min: 1) | | `greeting_message` | string | — | Initial greeting. Max 250 characters. | | `instructions` | string | — | System prompt. 10–3,500 characters. | | `model_id` | string | — | AI model ID. 1–100 characters. | | `voice_id` | string | — | TTS voice ID. 1–100 characters. | | `enable_vad` | boolean | — | Enable Voice Activity Detection | | `enable_noise_reduction` | boolean | — | Enable noise reduction | | `max_call_duration_seconds` | number | — | Max call duration. 0–1,800 seconds (30 min). | | `functions` | string[] | — | Array of tool/function IDs to enable | #### Request ```bash curl -X PATCH "https://{workspace_id}.firetell.app/api/v1/voice-agents/va_7f3a2b1c-9d4e-5f6a-8b0c-1d2e3f4a5b6c/settings" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "version": 2, "instructions": "Updated instructions for the voice agent...", "voice_id": "shimmer", "max_call_duration_seconds": 300, "enable_noise_reduction": true }' ``` #### Response Returns the full voice agent object with the updated settings version. --- ### Publish a Version ``` PATCH /voice-agents/:id/publish ``` Publish a settings version to make it the active configuration used for live calls. ### Authentication Requires `ApiKey` with `full` scope. #### Request Body | Field | Type | Required | Description | | --------- | ------ | -------- | -------------------------------------- | | `version` | number | ✅ | The version number to publish (min: 1) | #### Request ```bash curl -X PATCH "https://{workspace_id}.firetell.app/api/v1/voice-agents/va_7f3a2b1c-9d4e-5f6a-8b0c-1d2e3f4a5b6c/publish" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "version": 3 }' ``` #### Response Returns the full voice agent object with `published_version` updated. --- ### Create a New Version ``` POST /voice-agents/:id/version ``` Create a new settings version by cloning the currently published version. The new version gets the next sequential version number. ### Authentication Requires `ApiKey` with `full` scope. :::info - You must have at least one **published** version before creating a new version. - A maximum of **20 versions** are kept. Older versions are automatically cleaned up. ::: #### Request ```bash curl -X POST "https://{workspace_id}.firetell.app/api/v1/voice-agents/va_7f3a2b1c-9d4e-5f6a-8b0c-1d2e3f4a5b6c/version" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" ``` #### Response ```json { "version": 4, "updated_at": "2026-07-09T10:00:00.000Z" } ``` #### Error Responses ```json { "statusCode": 400, "message": "You must publish one version before creating another", "error": "Bad Request" } ``` --- ### Delete a Version ``` DELETE /voice-agents/:id/version/:version ``` Delete a specific settings version. You cannot delete the currently published version. ### Authentication Requires `ApiKey` with `full` scope. #### Request ```bash curl -X DELETE "https://{workspace_id}.firetell.app/api/v1/voice-agents/va_7f3a2b1c-9d4e-5f6a-8b0c-1d2e3f4a5b6c/version/3" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" ``` #### Response ```json { "version": 3, "updated_at": "2026-07-09T09:00:00.000Z" } ``` #### Error Response ```json { "statusCode": 400, "message": "Version has been Published, You cannot delete", "error": "Bad Request" } ``` --- ## AI-Powered Instruction Generation These endpoints use OpenAI to help you generate and refine voice agent instructions through a conversational flow. ### Start Instruction Generation ``` POST /voice-agents/generate-instructions ``` Start a new AI conversation to generate instructions based on a description of your desired voice agent behavior. ### Authentication Requires `ApiKey` with `full` scope. #### Request Body | Field | Type | Required | Description | | ------------- | ------ | -------- | ------------------------------------------------------------ | | `description` | string | ✅ | Description of the voice agent's purpose. 10–500 characters. | #### Request ```bash curl -X POST "https://{workspace_id}.firetell.app/api/v1/voice-agents/generate-instructions" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "description": "A customer support agent that helps users troubleshoot internet connectivity issues and can schedule a technician visit if needed." }' ``` #### Response ```json { "id": "conv_abc123", "metadata": { "user_id": "yourcompany", "company_name": "Acme Corp", "country_code": "US" } } ``` --- ### Continue Instruction Generation ``` POST /voice-agents/generate-instructions/:conversation ``` Continue the AI conversation to refine the generated instructions. ### Authentication Requires `ApiKey` with `full` scope. #### Path Parameters | Parameter | Type | Description | | -------------- | ------ | ------------------------------------------ | | `conversation` | string | The conversation ID from the previous step | #### Request Body | Field | Type | Required | Description | | ------------- | ------ | -------- | ------------------------------------------------ | | `description` | string | ✅ | Additional refinement prompt. 10–500 characters. | #### Request ```bash curl -X POST "https://{workspace_id}.firetell.app/api/v1/voice-agents/generate-instructions/conv_abc123" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "description": "Also add handling for billing inquiries and make the tone more casual and friendly." }' ``` #### Response ```json { "content": "You are a friendly customer support agent for Acme Corp...", "conversation": "conv_abc123", "response_id": "resp_xyz789" } ``` --- ### Optimize Instructions ``` POST /voice-agents/optimize/:id ``` Use AI to optimize and improve the existing instructions for a voice agent. ### Authentication Requires `ApiKey` with `full` scope. #### Request Body | Field | Type | Required | Description | | --------- | ------ | -------- | --------------------------------------------- | | `content` | string | — | What changes to optimize. Max 255 characters. | #### Request ```bash curl -X POST "https://{workspace_id}.firetell.app/api/v1/voice-agents/optimize/va_7f3a2b1c-9d4e-5f6a-8b0c-1d2e3f4a5b6c" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "content": "Make responses shorter and more concise" }' ``` #### Response ```json { "content": "Optimized instructions text...", "conversation": "conv_abc123", "response_id": "resp_def456" } ``` --- ## Catalog Endpoints ### List AI Models ``` GET /voice-agents/models ``` List available AI models that can be used for voice agents. ### Authentication Requires `ApiKey` with any scope. #### Request ```bash curl -X GET "https://{workspace_id}.firetell.app/api/v1/voice-agents/models" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" ``` --- ### List TTS Voices ``` GET /voice-agents/voices ``` List available text-to-speech voices, filtered by the workspace's country code. ### Authentication Requires `ApiKey` with any scope. #### Query Parameters | Parameter | Type | Description | | ---------- | ------ | -------------------------------------------- | | `model_id` | string | Filter voices by model. Max 100 characters. | | `provider` | string | Filter by voice provider. Max 50 characters. | | `page` | number | Page number | | `limit` | number | Items per page | | `search` | string | Search voices by name | #### Request ```bash curl -X GET "https://{workspace_id}.firetell.app/api/v1/voice-agents/voices?provider=openai" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" ``` --- # Webhooks Configure webhook endpoints to receive real-time HTTP `POST` notifications when events occur in your workspace — such as incoming calls, call state changes, call endings, and contact updates. For details on payload format, event types, retry policy, and signature verification, see the [Webhooks Overview](/docs/webhooks/overview). ## Endpoints | Method | Endpoint | Description | | -------- | -------------------------- | -------------------------- | | `GET` | `/webhooks` | List all webhook endpoints | | `GET` | `/webhooks/:id` | Get webhook details | | `POST` | `/webhooks` | Create a webhook endpoint | | `POST` | `/webhooks/:id/test-event` | Send a test event | | `PATCH` | `/webhooks/:id` | Update a webhook endpoint | | `DELETE` | `/webhooks/:id` | Delete a webhook endpoint | ## The Webhook Object | Field | Type | Description | | -------------- | -------- | --------------------------------------------------------------------- | | `id` | string | Unique webhook identifier (prefixed with `wh-`) | | `workspace_id` | string | Workspace identifier | | `title` | string | Display name for the webhook | | `url` | string | The URL that receives event notifications | | `events` | string[] | Array of event names this webhook is subscribed to | | `headers` | object[] | Custom HTTP headers sent with each delivery | | `is_active` | boolean | Whether the webhook is active | | `secret` | string | Signing secret for signature verification (only returned on creation) | | `created_at` | string | ISO 8601 creation timestamp | | `updated_at` | string | ISO 8601 last update timestamp | ### Webhook Header Object | Field | Type | Description | | ------- | ------ | ------------------------------- | | `key` | string | Header name (1–35 characters) | | `value` | string | Header value (1–255 characters) | ### Available Event Names For complete payload structures and JSON schemas for each event, see the [Webhook Event Catalog](/docs/webhooks/event-catalog). | Event | Description | | ---------------------- | ----------------------------------------------------------------- | | `call.created` | A new call has been initiated | | `call.answered` | A call was answered by an agent or user | | `call.ended` | A call has ended | | `call.recording_ready` | A call recording audio file has finished processing and is ready | | `contact.created` | A new contact was created in the workspace | | `contact.updated` | An existing contact was updated | | `contact.deleted` | A contact was deleted | | `agent.state` | An agent's presence state was changed (available, busy, away, etc) | | `agent.created` | A new workspace agent account was created | | `agent.updated` | An existing workspace agent account was updated | | `agent.deleted` | A workspace agent account was deleted | --- ## List Webhooks ``` GET /webhooks ``` Retrieve a paginated list of all webhook endpoints in the workspace. ### Authentication Requires `ApiKey` with any scope. ### Query Parameters | Parameter | Type | Default | Description | | ------------ | ------ | ------------ | --------------------------------- | | `page` | number | `1` | Page number (min: 1) | | `limit` | number | `10` | Items per page (min: 1, max: 100) | | `search` | string | — | Search by title | | `sort_field` | string | `created_at` | Sort field. Allowed: `created_at` | | `sort_order` | string | `desc` | Sort order: `asc` or `desc` | ### Request ```bash curl -X GET "https://{workspace_id}.firetell.app/api/v1/webhooks?page=1&limit=10" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" ``` ### Response ```json { "data": [ { "id": "wh-7f3a2b1c9d4e5f6a8b0c1d2e", "workspace_id": "yourcompany", "title": "Call Events - Production", "url": "https://api.example.com/webhooks/firetell", "events": ["call.created", "call.ended"], "headers": [ { "key": "X-Custom-Token", "value": "my-secret-token" } ], "is_active": true, "created_at": "2026-01-15T08:30:00.000Z", "updated_at": "2026-03-20T14:00:00.000Z" }, { "id": "wh-9a8b7c6d5e4f3a2b1c0d9e8f", "workspace_id": "yourcompany", "title": "Contact Sync", "url": "https://crm.example.com/hooks/firetell", "events": ["contact.created", "contact.updated", "contact.deleted"], "headers": [], "is_active": true, "created_at": "2026-02-10T10:00:00.000Z", "updated_at": "2026-02-10T10:00:00.000Z" } ], "meta": { "total": 2, "page": 1, "limit": 10, "total_pages": 1 } } ``` :::info The `secret` field is not included in list responses. It is only returned when creating a webhook. ::: --- ## Get Webhook ``` GET /webhooks/:id ``` Retrieve details of a specific webhook endpoint. ### Authentication Requires `ApiKey` with `full` scope. ### Path Parameters | Parameter | Type | Description | | --------- | ------ | ---------------------------------------------------- | | `id` | string | The webhook ID (e.g., `wh-7f3a2b1c9d4e5f6a8b0c1d2e`) | ### Request ```bash curl -X GET "https://{workspace_id}.firetell.app/api/v1/webhooks/wh-7f3a2b1c9d4e5f6a8b0c1d2e" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" ``` ### Response ```json { "id": "wh-7f3a2b1c9d4e5f6a8b0c1d2e", "workspace_id": "yourcompany", "title": "Call Events - Production", "url": "https://api.example.com/webhooks/firetell", "events": ["call.created", "call.ended"], "headers": [ { "key": "X-Custom-Token", "value": "my-secret-token" } ], "is_active": true, "created_at": "2026-01-15T08:30:00.000Z", "updated_at": "2026-03-20T14:00:00.000Z" } ``` ### Error Response ```json { "statusCode": 404, "message": "item not found", "error": "Not Found" } ``` --- ## Create Webhook ``` POST /webhooks ``` Create a new webhook endpoint. A signing secret is automatically generated and returned in the response — **save it securely**, as it will not be retrievable again. ### Authentication Requires `ApiKey` with `full` scope. :::caution Each workspace is limited to a maximum of **20 webhook endpoints**. Exceeding this limit will return a `403 Forbidden` error. ::: ### Request Body | Field | Type | Required | Description | | --------- | -------- | -------- | ------------------------------------------------------------------------------------ | | `title` | string | ✅ | Display name. 1–100 characters. | | `url` | string | ✅ | Webhook URL. Must be a valid HTTPS URL. Max 255 characters. | | `events` | string[] | ✅ | Array of event names to subscribe to. At least 1 event required. | | `headers` | object[] | — | Custom HTTP headers (max 3). Each with `key` (1–35 chars) and `value` (1–255 chars). | ### Request ```bash curl -X POST "https://{workspace_id}.firetell.app/api/v1/webhooks" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "title": "Call Events - Production", "url": "https://api.example.com/webhooks/firetell", "events": ["call.created", "call.answered", "call.ended"], "headers": [ { "key": "X-Custom-Token", "value": "my-secret-token" } ] }' ``` ### Response ```json { "id": "wh-d4e5f6a7b8c9d0e1f2a3b4c5", "workspace_id": "yourcompany", "title": "Call Events - Production", "url": "https://api.example.com/webhooks/firetell", "events": ["call.created", "call.answered", "call.ended"], "headers": [ { "key": "X-Custom-Token", "value": "my-secret-token" } ], "is_active": true, "secret": "whse-a1b2c3d4e5f6a7b8c9d0e1", "created_at": "2026-07-09T08:00:00.000Z", "updated_at": "2026-07-09T08:00:00.000Z" } ``` :::caution **Save the `secret` value immediately.** The secret (prefixed with `whse-`) is only returned once during creation. You will need it to verify webhook signatures using HMAC-SHA256. ::: ### Error Response **Webhook limit reached:** ```json { "statusCode": 403, "message": "We currently only support up to 20 webhooks with your plan. You've reached your limit.", "error": "Forbidden" } ``` --- ## Send Test Event ``` POST /webhooks/:id/test-event ``` Send a simulated test event to verify your webhook endpoint is working correctly. The test payload is sent immediately to the configured URL with a valid signature. ### Authentication Requires `ApiKey` with `full` scope. ### Path Parameters | Parameter | Type | Description | | --------- | ------ | -------------- | | `id` | string | The webhook ID | ### Request Body | Field | Type | Required | Description | | ------------ | ------ | -------- | ----------------------------------------------------------------------------------- | | `event_name` | string | ✅ | The event type to simulate. Must be one of the events the webhook is subscribed to. | ### Request ```bash curl -X POST "https://{workspace_id}.firetell.app/api/v1/webhooks/wh-7f3a2b1c9d4e5f6a8b0c1d2e/test-event" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "event_name": "call.created" }' ``` ### Success Response ```json { "success": true, "status": 200, "status_text": "OK", "latency_ms": 142, "message": "Webhook test event with respond success" } ``` ### Failure Responses **Endpoint returned non-success status:** ```json { "success": false, "status": 500, "status_text": "Internal Server Error", "latency_ms": 85, "error": "Internal Server Error", "message": "Webhook endpoint did not respond success. status >= 300" } ``` **Endpoint unreachable:** ```json { "success": false, "status": 0, "latency_ms": 0, "error": "ECONNREFUSED", "message": "Webhook endpoint did not respond" } ``` **Event not subscribed:** ```json { "statusCode": 400, "message": "This endpoint is not currently subscribed to this event", "error": "Bad Request" } ``` ### Test Payload Example When simulating a `call.created` event, the following test payload is sent to your URL: ```json { "event": "call.created", "data": { "workspace_id": "yourcompany", "call_id": "test_call_id_1720512000000", "direction": "inbound", "caller": { "name": "Caller_name", "number": "Caller_number" }, "callee": { "name": "Callee_name", "number": "Callee_number" }, "created_at": "2026-07-09T08:00:00.000Z" } } ``` :::tip Use the test event endpoint during development to verify your webhook handler is correctly parsing payloads and validating signatures before going live. ::: --- ## Update Webhook ``` PATCH /webhooks/:id ``` Update an existing webhook endpoint's configuration. All fields in the request body are required — this endpoint replaces the webhook configuration entirely. ### Authentication Requires `ApiKey` with `full` scope. ### Path Parameters | Parameter | Type | Description | | --------- | ------ | -------------- | | `id` | string | The webhook ID | ### Request Body | Field | Type | Required | Description | | --------- | -------- | -------- | ----------------------------------------------------------- | | `title` | string | ✅ | Display name. 1–100 characters. | | `url` | string | ✅ | Webhook URL. Must be a valid HTTPS URL. Max 255 characters. | | `events` | string[] | ✅ | Array of event names. At least 1 event required. | | `headers` | object[] | — | Custom HTTP headers (max 3). | ### Request ```bash curl -X PATCH "https://{workspace_id}.firetell.app/api/v1/webhooks/wh-7f3a2b1c9d4e5f6a8b0c1d2e" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "title": "Call Events - Updated", "url": "https://api.example.com/webhooks/firetell-v2", "events": ["call.created", "call.ended"], "headers": [] }' ``` ### Response Returns the updated webhook object: ```json { "id": "wh-7f3a2b1c9d4e5f6a8b0c1d2e", "workspace_id": "yourcompany", "title": "Call Events - Updated", "url": "https://api.example.com/webhooks/firetell-v2", "events": ["call.created", "call.ended"], "headers": [], "is_active": true, "created_at": "2026-01-15T08:30:00.000Z", "updated_at": "2026-07-09T09:30:00.000Z" } ``` :::info Updating a webhook does **not** regenerate the signing secret. The original secret remains valid. ::: ### Error Response ```json { "statusCode": 404, "message": "item not found", "error": "Not Found" } ``` --- ## Delete Webhook ``` DELETE /webhooks/:id ``` Permanently delete a webhook endpoint. Event notifications will immediately stop being delivered to this URL. ### Authentication Requires `ApiKey` with `full` scope. :::caution This action is irreversible. Any events that occur after deletion will not be delivered to this endpoint. ::: ### Path Parameters | Parameter | Type | Description | | --------- | ------ | -------------- | | `id` | string | The webhook ID | ### Request ```bash curl -X DELETE "https://{workspace_id}.firetell.app/api/v1/webhooks/wh-7f3a2b1c9d4e5f6a8b0c1d2e" \ -H "Authorization: ApiKey sk-YOUR_API_KEY" ``` ### Response Returns the deleted webhook object: ```json { "id": "wh-7f3a2b1c9d4e5f6a8b0c1d2e", "workspace_id": "yourcompany", "title": "Call Events - Production", "url": "https://api.example.com/webhooks/firetell", "events": ["call.created", "call.ended"], "headers": [], "is_active": true, "created_at": "2026-01-15T08:30:00.000Z", "updated_at": "2026-03-20T14:00:00.000Z" } ``` ### Error Response ```json { "statusCode": 404, "message": "item not found", "error": "Not Found" } ``` --- ## Webhook Delivery ### Signature Verification Every webhook delivery includes an `X-Webhook-Signature` header containing an HMAC-SHA256 signature of the request body, signed with your webhook's `secret`. ```javascript const crypto = require("crypto"); function verifyWebhookSignature(rawBody, signature, secret) { const expected = crypto .createHmac("sha256", secret) .update(rawBody) .digest("hex"); return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected)); } // In your Express handler: app.post("/webhooks/firetell", (req, res) => { const signature = req.headers["x-webhook-signature"]; const isValid = verifyWebhookSignature( JSON.stringify(req.body), signature, "whse-your-webhook-secret", ); if (!isValid) { return res.status(401).send("Invalid signature"); } // Process the event... res.status(200).send("OK"); }); ``` ### Delivery Headers Each webhook delivery includes the following headers: | Header | Description | | --------------------- | ------------------------------------------------------------------------------ | | `Content-Type` | Always `application/json` | | `X-Webhook-Signature` | HMAC-SHA256 signature of the request body | | `X-Powered-By` | Firetell service identifier | | `User-Agent` | Firetell webhook delivery agent (e.g., `region@firetell.shared.webhook/1.0.2`) | | Custom headers | Any headers you configured on the webhook endpoint | ### Timeout & Retries - **Timeout:** 5 seconds per delivery attempt - **Redirect:** Not followed (`maxRedirects: 0`) - Your endpoint must respond with a status code **below 300** to be considered successful - See [Webhooks Overview](/docs/webhooks/overview) for the retry policy --- # API Reference This document provides a detailed description of the classes, methods, events, and types exposed by `@firetell/firetell-client-sdk`. --- ## FiretellClient The `FiretellClient` manages session initialization, REST call dispatch, per-call Native WebSockets, and real-time background EventSource (SSE) stream subscriptions. ### Constructor ```typescript new FiretellClient(jwt: string, domain: string) ``` | Parameter | Type | Default | Description | | :-------- | :------- | :------ | :--------------------------------------------------------------------------- | | `jwt` | `string` | — | The Agent JWT authentication token. | | `domain` | `string` | — | Your full Firetell workspace API domain (e.g. `"yourcompany.firetell.app"`). | --- ### Properties #### `ready` - **Type:** `Promise` - **Description:** Resolves when the workspace metadata is fetched, the SSE event stream is established, and the agent session is ready. #### `connected` - **Type:** `boolean` - **Description:** Returns `true` if a call WebSocket or event stream is active. #### `activeCalls` - **Type:** `Map` - **Description:** A map of active call instances keyed by their `callId`. #### `isWebRTCSupport` - **Type:** `boolean` - **Description:** Returns `true` if the current environment supports WebRTC (specifically `RTCPeerConnection`). #### `sdkVersion` - **Type:** `string` - **Description:** The version string of the current SDK (`"1.0.0"`). --- ### Methods #### `makeCall()` - **Signature:** `makeCall(call: Call, sdp: RTCSessionDescription): Promise` - **Description:** Initiates an outbound call via HTTP REST (`POST /api/v1/call-center/calls`), receives a scoped `call_token`, and opens a native WebSocket for WebRTC signaling. #### `superviseCall()` - **Signature:** `superviseCall(callId: string, mode: "listen" | "whisper" | "barge"): Promise` - **Description:** Initiates call supervision for supervisors via HTTP REST, receives a scoped `call_token`, and connects to the WebRTC audio stream. #### `getSessionInfo()` - **Signature:** `getSessionInfo(): ISession | null` - **Description:** Returns the active session details if ready; otherwise returns `null`. #### `getDeviceId()` / `FiretellClient.getOrCreateDeviceId()` - **Signature:** `client.getDeviceId(storageKey?: string): string` / `FiretellClient.getOrCreateDeviceId(storageKey?: string): string` - **Description:** Generates or retrieves a unique persistent browser Device ID string stored in `localStorage` (e.g. `"web_550e8400-e29b-41d4-a716-446655440000"`). #### `logout()` - **Signature:** `logout(): void` - **Description:** Ends active calls, closes the SSE event stream, disconnects WebSockets, and clears session state. #### `destroy()` - **Signature:** `destroy(): void` - **Description:** Performs an immediate local cleanup of WebSockets, SSE streams, call objects, and event listeners. --- ### Client Events Subscribe to these events using `client.events.on(eventName, handler)`. | Event (Enum / String) | Payload | Description | | :--------------------------------------------------------- | :------------------------------------------------------ | :--------------------------------------------------------------------------------------------- | | `EClientEventName.SESSION` (`"session"`) | `ISession \| null` | Fired when a session is ready (payload is `ISession`) or destroyed (payload is `null`). | | `EClientEventName.ERROR` (`"error"`) | `{ code: number, message: string }` | Fired when an error occurs in the connection or signaling layer. | | `EClientEventName.CALL_OFFER` (`"call.offer"`) | `Call` | Fired when an incoming call offer is received. | | `EClientEventName.AGENT_STATE` (`"agent.state"`) | `{ event, workspace_id, data, timestamp }` | Fired via SSE when an agent's presence state changes (`available`, `busy`, `away`, `offline`). | | `EClientEventName.AGENT_STATE_FORCED` (`"agent.state.forced"`) | `{ event, workspace_id, data, timestamp }` | Fired via SSE when an agent's state is force-changed by a supervisor. | | `EClientEventName.AGENT_CREATED` (`"agent.created"`) | `{ event, workspace_id, data, timestamp }` | Fired via SSE when a new agent account is created in the workspace. | | `EClientEventName.AGENT_UPDATED` (`"agent.updated"`) | `{ event, workspace_id, data, timestamp }` | Fired via SSE when an agent account's details or avatar are updated. | | `EClientEventName.AGENT_DELETED` (`"agent.deleted"`) | `{ event, workspace_id, data, timestamp }` | Fired via SSE when an agent account is deleted. | | `EClientEventName.CONTACT_CREATED` (`"contact.created"`) | `{ event, workspace_id, data, timestamp }` | Fired via SSE when a new contact is created in the workspace. | | `EClientEventName.CONTACT_UPDATED` (`"contact.updated"`) | `{ event, workspace_id, data, timestamp }` | Fired via SSE when a contact is updated. | | `EClientEventName.CONTACT_DELETED` (`"contact.deleted"`) | `{ event, workspace_id, data, timestamp }` | Fired via SSE when a contact is deleted. | --- ## Call The `Call` class manages WebRTC media streams (`RTCPeerConnection`), local/remote audio/video tracks, and call control actions (hangup, answer, hold, mute, DTMF). ### Constructor ```typescript new Call(client: FiretellClient, options: CallOptions) ``` | Parameter | Type | Description | | :-------- | :--------------- | :--------------------------------------- | | `client` | `FiretellClient` | Active instance of `FiretellClient`. | | `options` | `CallOptions` | Configuration object for the call setup. | #### `CallOptions` Interface | Field | Type | Required | Description | | :----------- | :-------- | :------- | :------------------------------------------------------------- | | `to` | `string` | ✅ | Target phone number or agent extension. | | `from` | `string` | ❌ | Caller identity / number on incoming call offers. | | `number` | `string` | ❌ | Outbound Caller ID DID phone number. | | `isVideo` | `boolean` | ❌ | Set to `true` to request video media track. Default: `false`. | --- ### Call Methods #### `start()` - **Signature:** `start(): Promise` - **Description:** Starts an outbound call. Requests media permissions, gathers full ICE candidates, and calls `client.makeCall()`. #### `accept()` - **Signature:** `accept(): Promise` - **Description:** Accepts an incoming call offer. Sets up local media, generates SDP answer, and connects the call. #### `reject()` - **Signature:** `reject(): Promise` - **Description:** Rejects an incoming call offer and notifies the server. #### `hangup()` - **Signature:** `hangup(): Promise` - **Description:** Terminates the active call and closes the WebRTC peer connection and WebSocket. #### `sendMute()` - **Signature:** `sendMute(muted: boolean): void` - **Description:** Mutes or unmutes local audio tracks and notifies server. #### `sendDTMF()` - **Signature:** `sendDTMF(digit: string, duration?: number): Promise` - **Description:** Sends DTMF tones (`0-9`, `*`, `#`, `A-D`). --- ### Call Events Subscribe to these events on a `Call` instance using `call.on(eventName, handler)`. | Event (Enum / String) | Payload | Description | | :--------------------------------------------------------- | :------------------------------------------------------ | :--------------------------------------------------------------------------------------------- | | `ECallEventName.STATE` (`"state"`) | `{ state: ECallState, reason?: string, status?: number }` | Fired when the call's signaling state changes (`TRYING`, `RINGING`, `ACTIVE`, `ENDED`, etc.). | | `ECallEventName.MUTE` (`"mute"`) | `{ muted: boolean }` | Fired when the call is muted or unmuted. | | `ECallEventName.REMOTE_STREAM` (`"remote_stream"`) | `{ stream: MediaStream }` | Fired when the remote audio/video stream is successfully connected via WebRTC. | | `ECallEventName.ERROR` (`"error"`) | `Error` | Fired when an error occurs during WebRTC negotiation or media gathering. | #### `ECallState` Enum - `INITIATED`: Call is initializing. - `TRYING`: SIP 100 Trying received from the server. - `RINGING`: SIP 180/183 Ringing received. - `ACTIVE`: Media is connected and active (mapped from `ANSWERED`). - `HELD`: Call is put on hold. - `ENDED`: Call terminated normally. - `CANCEL`: Call was cancelled before answering. - `ERROR`: Call failed. --- # Client Authentication To authorize frontend WebRTC clients safely, your backend server must generate a temporary **Agent JWT Token** representing that agent, which is then passed to the frontend client to initialize the `FiretellClient`. > [!WARNING] > **NEVER hardcode your Master API Key (`sk-...`) in frontend client-side code.** > > Exposing your master API Key gives anyone full access to provision phone numbers, configure billing, and read call records for your entire workspace. --- ## Generating the Agent JWT Token (Backend) To generate the JWT token on your backend server using your Firetell API Key credentials, you must sign the token using the **HS256** algorithm with your API Key's secret (`sk-...`) as the signing key. ### How to Obtain API Key Credentials from Firetell Console To generate Agent JWT tokens, your backend needs both the **API Key SID** and the **API Key Secret**: 1. Log in to the [Firetell Console](https://console.firetell.com). 2. Select your workspace from the dashboard. 3. Navigate to **Workspace Settings** → **API Keys** in the left sidebar. 4. Click **Create API Key** (or select an existing key). 5. Copy both of the following credentials: - **API Key SID** (starts with `sid-`, e.g. `sid-your_api_key_sid`): The public key identifier, used as the `iss` (Issuer) claim in your JWT. - **API Key Secret** (starts with `sk-`, e.g. `sk-your_api_key_secret`): The secret key, used as the signing secret for HS256. :::caution The **API Key Secret** (`sk-...`) is **only shown once** at the time of creation. Store it securely on your backend server (e.g., in environment variables) and never share it or commit it to version control. ::: ### JWT Claims & Structure - **Algorithm:** `HS256` - **Signing Key (Secret):** Your Firetell API Key secret (starts with `sk-`, e.g. `sk-your_api_key_secret`) - **Standard Claims:** - `iss` (Issuer): Your API Key SID (starts with `sid-`, e.g. `sid-your_api_key_sid`) - `sub` (Subject): Depends on the audience type (see below) - `aud` (Audience): `'agent-api'` or `'client-api'` - `exp` (Expiration): The expiration timestamp (e.g. 24 hours from generation) - **Custom Claims:** - `domain`: Your full workspace domain (e.g. `"yourcompany.firetell.app"`) #### Audience Types | Audience | `sub` requirement | Use case | | --- | --- | --- | | `agent-api` | Must be the **username of an existing, active agent** in your workspace | WebRTC agent clients (call center agents, support reps) | | `client-api` | Can be **any identifier** you define (e.g. user ID, session ID) | End-user clients (customers, anonymous callers) | ### Code Examples #### Agent API Token Generate a token for a call center agent. The `sub` must match an existing, active agent username in your workspace. ##### Node.js (using `jsonwebtoken`) ```javascript const jwt = require("jsonwebtoken"); const apiKeySid = "sid-your_api_key_sid"; // From Firetell Console (API Key SID) const apiKeySecret = "sk-your_api_key_secret"; // From Firetell Console (API Key Secret) const workspaceDomain = "yourcompany.firetell.app"; // Your full workspace domain const agentUsername = "agent_123"; // Must be an existing, active agent username const token = jwt.sign( { domain: workspaceDomain, }, apiKeySecret, { algorithm: "HS256", expiresIn: "24h", issuer: apiKeySid, subject: agentUsername, audience: "agent-api", }, ); console.log(token); // Send this token to the frontend client SDK ``` ##### Python (using `PyJWT`) ```python import jwt from datetime import datetime, timedelta api_key_sid = 'sid-your_api_key_sid' api_key_secret = 'sk-your_api_key_secret' workspace_domain = 'yourcompany.firetell.app' agent_username = 'agent_123' # Must be an existing, active agent username payload = { 'domain': workspace_domain, 'iss': api_key_sid, 'sub': agent_username, 'aud': 'agent-api', 'exp': datetime.utcnow() + timedelta(hours=24) } token = jwt.encode(payload, api_key_secret, algorithm='HS256') print(token) # Send this token to the frontend client SDK ``` --- #### Client API Token Generate a token for an end-user client. The `sub` can be any custom identifier you define. ##### Node.js (using `jsonwebtoken`) ```javascript const jwt = require("jsonwebtoken"); const apiKeySid = "sid-your_api_key_sid"; const apiKeySecret = "sk-your_api_key_secret"; const workspaceDomain = "yourcompany.firetell.app"; const userId = "user_abc"; // Any custom identifier you define const token = jwt.sign( { domain: workspaceDomain, }, apiKeySecret, { algorithm: "HS256", expiresIn: "24h", issuer: apiKeySid, subject: userId, audience: "client-api", }, ); console.log(token); // Send this token to the frontend client ``` ##### Python (using `PyJWT`) ```python import jwt from datetime import datetime, timedelta api_key_sid = 'sid-your_api_key_sid' api_key_secret = 'sk-your_api_key_secret' workspace_domain = 'yourcompany.firetell.app' user_id = 'user_abc' # Any custom identifier you define payload = { 'domain': workspace_domain, 'iss': api_key_sid, 'sub': user_id, 'aud': 'client-api', 'exp': datetime.utcnow() + timedelta(hours=24) } token = jwt.encode(payload, api_key_secret, algorithm='HS256') print(token) # Send this token to the frontend client ``` #### PHP (using `firebase/php-jwt`) ```php $workspaceDomain, 'iss' => $apiKeySid, 'sub' => $agentUsername, 'aud' => 'agent-api', 'exp' => time() + (24 * 60 * 60) // e.g. token expires in 24 hours ]; $token = JWT::encode($payload, $apiKeySecret, 'HS256'); echo $token; // Send this token to the frontend client SDK ``` --- # Installation & Setup The `@firetell/firetell-client-sdk` library allows you to build WebRTC-based voice and video calling features directly in web browsers and Node.js environments. 🎮 **Live Interactive Demo**: [https://developers.firetell.com/firetell-client-sdk/example/](https://developers.firetell.com/firetell-client-sdk/example/) This SDK is a multi-format package supporting: - **ESM** (ECMAScript Modules) for modern browsers and bundlers (Vite, Webpack, etc.) - **CJS** (CommonJS) for Node.js backend integration - **IIFE** (Immediately Invoked Function Expression) for direct inclusion via script tags (CDN) --- ## 1. Install via npm If you are using a package manager (such as `npm`, `yarn`, or `pnpm`) in a bundled browser or Node.js environment, install the package: ```bash npm install @firetell/firetell-client-sdk ``` --- ## 2. Importing the SDK ### In Browser (ESM) If you are using modern JavaScript/TypeScript bundlers (e.g. Vite, React, Angular, Vue, Next.js): ```typescript import { FiretellClient, Call, ECallState, } from "@firetell/firetell-client-sdk"; // Initialize the client with the full domain name const client = new FiretellClient("YOUR_AGENT_JWT", "yourcompany.firetell.app"); ``` ### In Node.js (CommonJS) If you are running in a Node.js environment that uses CommonJS syntax: ```javascript const { FiretellClient } = require("@firetell/firetell-client-sdk"); // Initialize the client with the full domain name const client = new FiretellClient("YOUR_AGENT_JWT", "yourcompany.firetell.app"); ``` > [!NOTE] > **Node.js Environment Limitation:** > Node.js does not support WebRTC features (making or receiving calls) due to the lack of native browser APIs like `RTCPeerConnection` or `getUserMedia`. In Node.js environments, the SDK is solely used for connecting to the gateway to receive real-time events via WebSocket connection. --- ## 3. Direct Script Tag (IIFE via CDN) For simple HTML pages or legacy architectures where you do not use a build step or bundler, you can load the SDK directly from a CDN (such as jsDelivr or unpkg). Add this script tag to the `` or `` of your HTML document: ```html ``` --- ## ⚠️ Security Warning: Frontend Auth > [!WARNING] > **NEVER hardcode your Master API Key (`sk-...`) in frontend client-side code.** > > Exposing your master API Key gives anyone full access to provision phone numbers, configure billing, and read call records for your entire workspace. To authenticate frontend clients safely: 1. Keep your master API Key securely on your **backend server**. 2. When an agent logs into your application, your backend should generate or retrieve a temporary **Agent JWT Token** representing that agent. 3. Pass the **Agent JWT Token** to your frontend browser client. 4. Use that JWT token to initialize the `FiretellClient` WebRTC SDK. For a detailed walkthrough and code examples (Node.js, Python, PHP, ...) on how to generate the Agent JWT Token, refer to the [Client Authentication Guide](./authentication.md). --- ## System Requirements To use the WebRTC calling features: - **HTTPS or Localhost:** Web browsers require a secure origin (`https://` or `localhost`) to access microphone and camera devices (`navigator.mediaDevices.getUserMedia`). - **Browser Support:** Modern web browsers with WebRTC support (Google Chrome, Mozilla Firefox, Apple Safari, Microsoft Edge). - **Network Access:** Ensure WebSocket (`wss://`) traffic is allowed on your network to the Firetell signaling domains. - **Node.js Environment Limitation:** Node.js environments do not support native WebRTC APIs, meaning making or receiving audio/video calls is not supported. Use Node.js environments strictly for subscribing to real-time events. --- # Quickstart Guide This guide walks you through the core workflows of the Firetell Client SDK: connecting, listening for SSE events, initiating outbound calls via REST, accepting incoming calls, and controlling active call states. 🎮 **Try the Live Interactive Demo**: [https://developers.firetell.com/firetell-client-sdk/example/](https://developers.firetell.com/firetell-client-sdk/example/) --- ## Step 1: Initialize the Client To connect to the Firetell platform, create a new instance of `FiretellClient`. The constructor takes your agent's JWT token and the workspace domain. Wait for the `ready` promise to resolve before performing actions. ```typescript import { FiretellClient } from "@firetell/firetell-client-sdk"; const token = "your-agent-jwt-token"; const domain = "yourcompany.firetell.app"; // Workspace API domain const client = new FiretellClient(token, domain); try { // Wait for session initialization and SSE event stream setup const session = await client.ready; console.log("Successfully authenticated as:", session.username); } catch (error) { console.error("Initialization failed:", error); } ``` --- ## Step 2: Listen to Realtime Events (SSE) Register event listeners on the client's `events` emitter to respond to incoming calls or teammate status updates via Server-Sent Events (SSE). ```typescript import { EClientEventName, ICallRingParams, Call } from "@firetell/firetell-client-sdk"; // Handle signaling and socket errors client.events.on(EClientEventName.ERROR, (error) => { console.error(`Signaling error [Code ${error.code}]:`, error.message); }); // Track agent presence updates in the workspace via SSE client.events.on(EClientEventName.AGENT_STATE, (payload) => { const data = payload.data || payload; console.log(`Agent ${data.username || data.id} is now ${data.state}`); }); // Track agent lifecycle creation events via SSE client.events.on(EClientEventName.AGENT_CREATED, (payload) => { const data = payload.data || payload; console.log(`New agent account created: ${data.display_name || data.username} (${data.id})`); }); // 1. Instant Ring Alert via SSE (CALL_RING — Triggers Incoming Call Ringing UI) client.events.on(EClientEventName.CALL_RING, (ringData: ICallRingParams) => { console.log("Incoming call ring alert for call_id:", ringData.call_id); console.log("Caller (from):", ringData.from?.name, ringData.from?.number); // "Nguyen Van A", "+84901234567" console.log("Target (to):", ringData.to?.name, ringData.to?.number); // "Support Team", "+842471000000" // -> Display Ringing Screen & Ringtone Popup immediately! }); // 2. WebRTC Call Offer Ready (CALL_OFFER — WebRTC Call object ready for answer) client.events.on(EClientEventName.CALL_OFFER, (call: Call) => { console.log("Incoming WebRTC Call ready to answer:", call.callId); console.log("Caller:", call.from_name || call.from); }); ``` --- ## Step 3: Make an Outbound Call To start a new call, instantiate a `Call` object, subscribe to state/stream events, and invoke `start()`. The SDK automatically calls `POST /api/v1/call-center/calls` to obtain a `call_token`, then opens a per-call native WebSocket for WebRTC signaling. ```typescript import { Call, ECallState } from "@firetell/firetell-client-sdk"; // Create a call configuration const call = new Call(client, { to: "+84901234567", // Destination number or internal extension number: "84281234567", // Outbound Caller ID DID phone number isVideo: false, }); // 1. Monitor call lifecycle states call.on("state", ({ call_id, state, reason }) => { console.log(`Call state updated: ${state}`); // INITIATED → ANSWERED → ENDED if (["ENDED", "CANCEL", "ERROR"].includes(state)) { console.log(`Call terminated. Reason: ${reason || "Normal Hangup"}`); } }); // 2. Attach remote audio stream to HTML element call.on("remoteStream", (stream) => { if (stream) { const remoteAudio = document.getElementById("remoteAudio") as HTMLAudioElement; remoteAudio.srcObject = stream; remoteAudio.play(); } }); // 3. Initiate call await call.start(); ``` --- ## Step 4: Call Supervision (Supervisor Only) Supervisors can monitor, coach, or join active calls using `superviseCall()`: ```typescript // Modes: "listen" (silent), "whisper" (coach), "barge" (3-way) const supervision = await client.superviseCall("cl_123456789", "listen"); console.log("Supervision started:", supervision.status); ``` --- ## Step 5: Handling Concurrent Incoming Calls When an agent receives a 2nd call while already on an active call, choose one of two integration patterns: ### Option A: Call Waiting & Hold ```typescript let activeCall: Call | null = null; client.events.on("call.offer", async (incomingCall: Call) => { if (activeCall && activeCall.active) { // Show Call Waiting UI notification for incomingCall console.log(`Call Waiting from ${incomingCall.from} (${incomingCall.from_name})`); // When Agent clicks "Hold & Answer": await activeCall.hold(); await incomingCall.accept(); activeCall = incomingCall; } else { activeCall = incomingCall; } }); ``` ### Option B: Auto-Reject (Busy Policy) ```typescript client.events.on("call.offer", async (incomingCall: Call) => { if (client.activeCalls.size > 1) { // Reject 2nd call with Busy status (SIP 486) to trigger backend fallback branch await incomingCall.reject(); } }); ``` --- # SDKs Overview Firetell provides official client libraries and SDKs to simplify integration, handle WebRTC calling, and manage WebSocket communication within your applications. Using our SDKs ensures that best practices—such as automatic reconnects, authentication timeouts, and media track management—are handled for you out of the box. --- ## Available SDKs ### JavaScript / TypeScript SDK The `@firetell/firetell-client-sdk` library is compatible with browser environments (using ES Modules or loaded via a CDN script tag) for building WebRTC-based calling applications, and Node.js environments (using CommonJS) for receiving real-time events via WebSocket connection. It enables you to integrate real-time voice, video, and event features directly into your applications. - **Package:** [`@firetell/firetell-client-sdk`](https://www.npmjs.com/package/@firetell/firetell-client-sdk) - **Status:** Active / Stable - **Interactive Live Demo:** [https://developers.firetell.com/firetell-client-sdk/example/](https://developers.firetell.com/firetell-client-sdk/example/) - **Guides:** [Installation & Setup](/docs/sdks/javascript/installation) • [Client Authentication](/docs/sdks/javascript/authentication) • [Quickstart Guide](/docs/sdks/javascript/quickstart) • [API Reference](/docs/sdks/javascript/api-reference) --- ## Handling Concurrent Incoming Calls When multiple incoming calls arrive simultaneously or an incoming call arrives while an agent is currently on an active call, Firetell supports two standard frontend integration patterns: ### Pattern A: Call Waiting & Hold (Multi-Call Management) Allow the agent to receive call waiting notifications, put the current call on hold, and answer the new incoming call: ```typescript let activeCall: Call | null = null; client.events.on("call.offer", async (incomingCall: Call) => { if (activeCall && activeCall.active) { // 1. Show Call Waiting notification on UI console.log(`Call Waiting from ${incomingCall.from} (${incomingCall.from_name})`); // 2. When Agent clicks "Hold & Answer": await activeCall.hold(); await incomingCall.accept(); activeCall = incomingCall; } else { // Single call flow activeCall = incomingCall; } }); ``` ### Pattern B: Auto-Reject / Busy (Single-Call Policy) If your call center policy restricts agents to one call at a time: ```typescript client.events.on("call.offer", async (incomingCall: Call) => { if (client.activeCalls.size > 1) { // Auto-reject 2nd call with Busy status (SIP 486 Busy) // The backend Call Flow will immediately execute the fallback branch (Voicemail/Reroute) await incomingCall.reject(); } }); ``` --- ## Planned SDKs We are actively developing SDKs for additional languages and mobile frameworks to help you build native apps: | Platform / Language | Status | Package Name | Target Environment | | :------------------ | :--------- | :---------------------- | :----------------- | | **Flutter** | 🔜 Planned | `firetell_flutter` | iOS & Android | | **React Native** | 🔜 Planned | `react-native-firetell` | iOS & Android | If you need an SDK for another language, please reach out to us at [developers@firetell.com](mailto:developers@firetell.com). --- # Webhook Event Catalog Detailed payloads and schemas for all webhook events supported by Firetell. --- ## Call Events ### `call.created` Triggered immediately when a new call (inbound or outbound) is initiated. ```json { "event": "call.created", "data": { "workspace_id": "ws_company", "occurred_at": "2026-07-21T12:00:00.000Z", "attempt": 1, "call_id": "call_abc123", "direction": "inbound", "status": "started", "type": "audio", "from": { "name": "+84901234567", "number": "+84901234567" }, "to": { "name": "Hotline", "number": "+84909876543" }, "started_at": "2026-07-21T12:00:00.000Z", "answered_at": null, "ended_at": null, "duration": 0, "billsec": 0, "hangup_cause": null } } ``` --- ### `call.answered` Triggered whenever a call transitions to answered state. ```json { "event": "call.answered", "data": { "workspace_id": "ws_company", "occurred_at": "2026-07-21T12:00:05.000Z", "attempt": 1, "call_id": "call_abc123", "direction": "inbound", "status": "active", "type": "audio", "from": { "name": "+84901234567", "number": "+84901234567" }, "to": { "name": "Hotline", "number": "+84909876543" }, "started_at": "2026-07-21T12:00:00.000Z", "answered_at": "2026-07-21T12:00:05.000Z", "ended_at": null, "duration": 5, "billsec": 0, "hangup_cause": null } } ``` --- ### `call.ended` Triggered when a call hangs up or fails. ```json { "event": "call.ended", "data": { "workspace_id": "ws_company", "occurred_at": "2026-07-21T12:02:30.000Z", "attempt": 1, "call_id": "call_abc123", "direction": "inbound", "status": "completed", "type": "audio", "from": { "name": "+84901234567", "number": "+84901234567" }, "to": { "name": "Hotline", "number": "+84909876543" }, "started_at": "2026-07-21T12:00:00.000Z", "answered_at": "2026-07-21T12:00:05.000Z", "ended_at": "2026-07-21T12:02:30.000Z", "duration": 150, "billsec": 145, "hangup_cause": "NORMAL_CLEARING" } } ``` --- ### `call.recording_ready` Triggered when audio recording processing completes and the cloud media file URL is ready. ```json { "event": "call.recording_ready", "data": { "workspace_id": "ws_company", "occurred_at": "2026-07-21T12:02:35.000Z", "attempt": 1, "recording_id": "rec_xyz789", "call_id": "call_abc123", "url": "https://storage.firetell.app/recordings/ws_company/call_abc123.mp3", "duration": 145, "format": "mp3", "size_bytes": 1160000, "created_at": "2026-07-21T12:02:35.000Z" } } ``` --- ## Contact Events ### `contact.created` Triggered whenever a new contact is created in the workspace. ```json { "event": "contact.created", "data": { "workspace_id": "ws_company", "occurred_at": "2026-07-21T12:10:00.000Z", "attempt": 1, "id": "ct_987654321", "address_book_id": "ab_sales_vip", "first_name": "Nguyen", "last_name": "Van A", "company": "Acme Corp", "phone_numbers": [ { "phone_number": "+84901234567", "normalized_number": "84901234567", "type": "mobile", "is_primary": true } ], "emails": [], "assigned_agent_id": "ag_john", "created_at": "2026-07-21T12:10:00.000Z" } } ``` --- ### `contact.updated` Triggered whenever contact details or address book assignment are modified. ```json { "event": "contact.updated", "data": { "workspace_id": "ws_company", "occurred_at": "2026-07-21T12:15:00.000Z", "attempt": 1, "id": "ct_987654321", "address_book_id": "ab_sales_vip", "first_name": "Nguyen", "last_name": "Van A", "company": "Acme Global", "title": "CTO", "updated_at": "2026-07-21T12:15:00.000Z" } } ``` --- ### `contact.deleted` Triggered when a contact is permanently removed. ```json { "event": "contact.deleted", "data": { "workspace_id": "ws_company", "occurred_at": "2026-07-21T12:20:00.000Z", "attempt": 1, "id": "ct_987654321", "address_book_id": "ab_sales_vip" } } ``` --- ## Agent Events ### `agent.state` Triggered whenever an agent's presence state (available, busy, away, offline) changes. ```json { "event": "agent.state", "workspace_id": "ws_company", "data": { "id": "650000000000000000000001", "username": "agent_john", "state": "busy", "reason": "On call", "changed_by": "supervisor_mary" }, "timestamp": "2026-08-03T15:20:00.000Z", "attempt": 1 } ``` ### `agent.created` / `agent.updated` / `agent.deleted` Triggered whenever a workspace agent account is created, updated, or deleted. ```json { "event": "agent.created", "workspace_id": "ws_company", "data": { "id": "650000000000000000000001", "username": "agent_john", "display_name": "John Doe", "email": "john@company.com", "role": "agent", "avatar": "https://cdn.firetell.com/avatars/agent_john.png", "country_code": "US", "is_active": true }, "timestamp": "2026-08-03T15:20:00.000Z", "attempt": 1 } ``` --- # Webhooks Overview Webhooks allow your application to receive real-time HTTP notifications when events occur in your Firetell workspace. ## How Webhooks Work 1. You register a webhook URL via the [Webhooks API](/docs/rest-api/workspace-api/webhooks) or the Firetell Console 2. When an event occurs, Firetell sends an HTTP `POST` request to your URL 3. Your server processes the event and responds with a `200` status code ## Webhook Payload Format All webhook payloads follow a standard envelope structure: ```json { "event": "call.ended", "data": { "workspace_id": "yourcompany", "occurred_at": "2026-07-24T00:56:02.000Z", "attempt": 1, "call_id": "call_abc789", "direction": "inbound", "status": "completed", "type": "audio", "from": { "name": "+84901234567", "number": "+84901234567" }, "to": { "name": "+84909876543", "number": "+84909876543" }, "started_at": "2026-07-24T00:55:00.000Z", "answered_at": "2026-07-24T00:55:02.000Z", "ended_at": "2026-07-24T00:56:02.000Z", "duration": 62, "billsec": 60, "cost": 0.0517, "hangup_cause": "NORMAL_CLEARING" } } ``` ### Event Payload Examples #### `call.created` ```json { "event": "call.created", "data": { "workspace_id": "yourcompany", "occurred_at": "2026-07-24T00:55:00.000Z", "attempt": 1, "call_id": "call_abc789", "direction": "inbound", "status": "started", "type": "audio", "from": { "name": "+84901234567", "number": "+84901234567" }, "to": { "name": "+84909876543", "number": "+84909876543" }, "started_at": "2026-07-24T00:55:00.000Z", "answered_at": null, "ended_at": null, "duration": 0, "billsec": 0, "cost": 0, "hangup_cause": null } } ``` #### `call.recording_ready` ```json { "event": "call.recording_ready", "data": { "workspace_id": "yourcompany", "occurred_at": "2026-07-24T00:56:05.000Z", "attempt": 1, "recording_id": "rec_call_abc789", "call_id": "call_abc789", "url": "https://storage.firetell.app/recordings/call_abc789.mp3", "duration": 60, "size_bytes": 1440000, "format": "mp3", "created_at": "2026-07-24T00:56:05.000Z" } } ``` ## Event Types | Event | Description | |-------|------------| | `call.created` | A new call has been initiated | | `call.answered` | A call was answered by an agent or user | | `call.ended` | A call has ended | | `call.recording_ready` | A call recording audio file has finished processing and is ready | | `contact.created` | A new contact was created in the workspace | | `contact.updated` | An existing contact was updated | | `contact.deleted` | A contact was deleted | | `agent.state` | An agent's presence state was changed (available, busy, away, offline) | | `agent.created` | A new workspace agent account was created | | `agent.updated` | An existing workspace agent account was updated | | `agent.deleted` | A workspace agent account was deleted | ## Retry Policy If your endpoint returns a non-`2xx` status code or times out (30 seconds), Firetell will retry the delivery: | Attempt | Delay | |---------|-------| | 1st retry | 1 minute | | 2nd retry | 5 minutes | | 3rd retry | 30 minutes | | 4th retry | 2 hours | After 4 failed retries, the webhook is marked as failed and no further attempts are made. ## Security Validate incoming webhooks by checking the `X-Webhook-Signature` header. This header contains an HMAC-SHA256 signature of the JSON request body, signed with your webhook secret (prefixed with `whse-`). ```javascript const crypto = require('crypto'); function verifySignature(payload, signature, secret) { const expected = crypto .createHmac('sha256', secret) .update(payload) .digest('hex'); return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expected) ); } // Usage in an Express handler: app.post('/webhooks/firetell', (req, res) => { const signature = req.headers['x-webhook-signature']; const isValid = verifySignature( JSON.stringify(req.body), signature, 'whse-your-webhook-secret' ); if (!isValid) return res.status(401).send('Invalid signature'); // Process event... res.status(200).send('OK'); }); ``` ## Best Practices - **Respond quickly** — Return `200` immediately and process asynchronously - **Handle duplicates** — Use the event ID for idempotency - **Use HTTPS** — Always use HTTPS endpoints in production - **Validate signatures** — Verify the `X-Webhook-Signature` header ---