API Reference
Complete documentation for the Fusion Calling APIs. Manage calendar events and leads programmatically — with copy-paste examples and a built-in request builder.
https://app.fusioncalling.comQuick Start
Get started with Fusion Calling APIs in 5 minutes.
Beginner
New to APIs? Learn the basics with step-by-step tutorials.
- Learn API fundamentals
- Make your first API call
- Understand authentication
Integrator
Connect your existing tools quickly with ready-to-use guides.
- Platform-specific guides
- Ready-to-use templates
- Quick setup instructions
Developer
Deep dive into API capabilities with the complete reference.
- Complete endpoint reference
- Interactive request builder
- Best practices
Authentication
All Fusion Calling APIs use API key authentication via the Authorization header using the Bearer token format.
Getting Your API Key
- Log in to your Fusion Calling account
- Navigate to Settings → API Keys
- Click "Generate New API Key"
- Copy the API key (it's only shown once when created)
Using Your API Key
Include the API key in the Authorization header of every request:
Authorization: Bearer your-api-key-hereWarning
Bearer (case-sensitive). Any other format, an empty key, or using the x-api-key header returns 401 Unauthorized — the x-api-key header is not supported.Error Response Shapes
Fusion Calling uses two distinct error shapes depending on where the failure happens — handle both in your client:
Auth failure (401)
{
"error": "Unauthorized",
"message": "Invalid or missing API key"
}Validation / route error
{
"success": false,
"error": "Description of what went wrong"
}Authentication Examples
curl -X GET "https://app.fusioncalling.com/api/calendar/external/v1/events" \
-H "Authorization: Bearer bf207bcf-c0de-4067-bd28-45728bd305aa"Warning
Authentication Best Practices
- ✅ Store API keys in environment variables
- ✅ Rotate API keys regularly
- ✅ Use different keys for development and production
- ✅ Implement key rotation in your deployment process
- ✅ Monitor API key usage for unusual activity
- ❌ Never commit API keys to version control
- ❌ Never share API keys via email or chat
Your First Request
Make your first API call to verify your authentication and understand the response format.
1 · Test your API key
List your calendar events to confirm everything works:
curl -X GET "https://app.fusioncalling.com/api/calendar/external/v1/events" \
-H "Authorization: Bearer YOUR_API_KEY"2 · Create your first event
Create a simple event with only the required fields:
1curl -X POST "https://app.fusioncalling.com/api/calendar/external/v1/events" \
2 -H "Authorization: Bearer YOUR_API_KEY" \
3 -H "Content-Type: application/json" \
4 -d '{
5 "title": "My First API Event",
6 "start_time": "2025-12-15T14:30:00",
7 "end_time": "2025-12-15T16:00:00"
8 }'Success
Calendar API
Manage calendar events programmatically with full CRUD operations, integrated video conferencing (Google Meet and Zoom), and attendees management.
- Base URL
- https://app.fusioncalling.com/api/calendar/external/v1
- Version
- v2.0
- Rate limit
- 12 requests / minute
- Auth
- Bearer API key
Endpoints at a glance
/api/calendar/external/v1/eventsList calendar events
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
from | string | Optional | Filter events from this date (inclusive). Flexible datetime formats. e.g. 2025-12-01T00:00:00 |
to | string | Optional | Filter events until this date (inclusive). Flexible datetime formats. e.g. 2025-12-31T23:59:59 |
fromOptionalFilter events from this date (inclusive). Flexible datetime formats.
2025-12-01T00:00:00toOptionalFilter events until this date (inclusive). Flexible datetime formats.
2025-12-31T23:59:59Success Response
2001{
2 "success": true,
3 "data": [
4 {
5 "id": "550e8400-e29b-41d4-a716-446655440000",
6 "title": "Team Meeting",
7 "description": "Weekly team sync",
8 "start_time": "2025-12-15T14:30:00.000Z",
9 "end_time": "2025-12-15T16:00:00.000Z",
10 "all_day": false,
11 "google_meet_enabled": true,
12 "meet_link": "https://meet.google.com/abc-defg-hij",
13 "zoom_enabled": false,
14 "zoom_meeting_id": null,
15 "zoom_join_url": null,
16 "attendees": [
17 {
18 "email": "john@example.com",
19 "display_name": "John Doe",
20 "response_status": "accepted"
21 }
22 ],
23 "caller_phone": null,
24 "caller_name": null,
25 "source": "local",
26 "created_at": "2025-12-10T10:00:00.000Z",
27 "updated_at": "2025-12-10T10:00:00.000Z"
28 }
29 ]
30}Need every status code and common errors? .
/api/calendar/external/v1/events/{id}Get a single event
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | UUID | Required | The unique identifier of the event. e.g. 550e8400-e29b-41d4-a716-446655440000 |
idRequiredThe unique identifier of the event.
550e8400-e29b-41d4-a716-446655440000Success Response
2001{
2 "success": true,
3 "data": {
4 "id": "550e8400-e29b-41d4-a716-446655440000",
5 "title": "Team Meeting",
6 "description": "Weekly team sync",
7 "start_time": "2025-12-15T14:30:00.000Z",
8 "end_time": "2025-12-15T16:00:00.000Z",
9 "all_day": false,
10 "google_meet_enabled": true,
11 "meet_link": "https://meet.google.com/abc-defg-hij",
12 "zoom_enabled": false,
13 "zoom_meeting_id": null,
14 "zoom_join_url": null,
15 "attendees": [
16 {
17 "email": "john@example.com",
18 "display_name": "John Doe",
19 "response_status": "accepted"
20 }
21 ],
22 "caller_phone": null,
23 "caller_name": null,
24 "source": "local",
25 "created_at": "2025-12-10T10:00:00.000Z",
26 "updated_at": "2025-12-10T10:00:00.000Z"
27 }
28}Need every status code and common errors? .
/api/calendar/external/v1/eventsCreate an event
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
title | string | Required | Event title (1-255 characters). e.g. Meeting with Client |
start_time | string | Required | Event start time (flexible datetime format). e.g. 2025-12-15T14:30:00 |
end_time | string | Required | Event end time (must be after start_time). e.g. 2025-12-15T16:00:00 |
description | string | Optional | Event description (max 500 characters). e.g. Discuss project requirements |
all_day | boolean | Optional | Whether the event is all-day. Must be a boolean, not a string. Video conferencing cannot be enabled for all-day events. e.g. false |
google_meet_enabled | boolean | Optional | Enable Google Meet link generation (requires Google Calendar connected). Mutually exclusive with zoom_enabled. e.g. false |
zoom_enabled | boolean | Optional | Enable Zoom meeting link generation (requires Zoom connected in Settings → Integrations). Mutually exclusive with google_meet_enabled. e.g. true |
attendees | array<object> | Optional | List of attendees (max 50). Each has email, optional display_name, and optional response_status. Duplicates are removed automatically. e.g. [{"email":"client@example.com","display_name":"Client Rep"}] |
caller_phone | string | Optional | Caller's phone number in E.164 format (e.g. +15551234567). Create-only — triggers an appointment SMS and cannot be changed via PATCH. e.g. +15551234567 |
caller_name | string | Optional | Caller's display name (max 200 characters). Create-only — used in the SMS template and cannot be changed via PATCH. e.g. Jane Doe |
titleRequiredEvent title (1-255 characters).
Meeting with Clientstart_timeRequiredEvent start time (flexible datetime format).
2025-12-15T14:30:00end_timeRequiredEvent end time (must be after start_time).
2025-12-15T16:00:00descriptionOptionalEvent description (max 500 characters).
Discuss project requirementsall_dayOptionalWhether the event is all-day. Must be a boolean, not a string. Video conferencing cannot be enabled for all-day events.
falsegoogle_meet_enabledOptionalEnable Google Meet link generation (requires Google Calendar connected). Mutually exclusive with zoom_enabled.
falsezoom_enabledOptionalEnable Zoom meeting link generation (requires Zoom connected in Settings → Integrations). Mutually exclusive with google_meet_enabled.
trueattendeesOptionalList of attendees (max 50). Each has email, optional display_name, and optional response_status. Duplicates are removed automatically.
[{"email":"client@example.com","display_name":"Client Rep"}]caller_phoneOptionalCaller's phone number in E.164 format (e.g. +15551234567). Create-only — triggers an appointment SMS and cannot be changed via PATCH.
+15551234567caller_nameOptionalCaller's display name (max 200 characters). Create-only — used in the SMS template and cannot be changed via PATCH.
Jane DoeSuccess Response
2011{
2 "success": true,
3 "data": {
4 "id": "770e8400-e29b-41d4-a716-446655440002",
5 "title": "Meeting with Client",
6 "description": "Discuss project requirements",
7 "start_time": "2025-12-15T14:30:00.000Z",
8 "end_time": "2025-12-15T16:00:00.000Z",
9 "all_day": false,
10 "google_meet_enabled": false,
11 "meet_link": null,
12 "zoom_enabled": true,
13 "zoom_meeting_id": 123456789,
14 "zoom_join_url": "https://zoom.us/j/123456789",
15 "attendees": [
16 {
17 "email": "client@example.com",
18 "display_name": "Client Rep",
19 "response_status": "needsAction"
20 }
21 ],
22 "caller_phone": "+15551234567",
23 "caller_name": "Jane Doe",
24 "source": "local",
25 "created_at": "2025-12-11T10:00:00.000Z",
26 "updated_at": "2025-12-11T10:00:00.000Z"
27 }
28}Need every status code and common errors? .
/api/calendar/external/v1/events/{id}Update an event
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | UUID | Required | The unique identifier of the event. e.g. 550e8400-e29b-41d4-a716-446655440000 |
idRequiredThe unique identifier of the event.
550e8400-e29b-41d4-a716-446655440000Request Body
| Name | Type | Required | Description |
|---|---|---|---|
title | string | Optional | Event title (1-255 characters). e.g. Updated Meeting Title |
description | string | Optional | Event description. Use null to clear. e.g. Updated description |
start_time | string | Optional | Event start time (flexible datetime format). e.g. 2025-12-15T15:00:00 |
end_time | string | Optional | Event end time (flexible datetime format). e.g. 2025-12-15T17:00:00 |
all_day | boolean | Optional | Whether the event is all-day. Must be a boolean. Video conferencing cannot be enabled for all-day events. e.g. false |
google_meet_enabled | boolean | Optional | Enable/disable Google Meet. Disabling deletes the existing Meet. Mutually exclusive with zoom_enabled. e.g. false |
zoom_enabled | boolean | Optional | Enable/disable Zoom. Disabling deletes the existing Zoom meeting. Mutually exclusive with google_meet_enabled. e.g. true |
attendees | array<object> | Optional | Merged with the stored attendees list (max 50 total). Duplicates are removed by lowercase email; stored display_name/response_status are preserved unless overridden. e.g. [{"email":"client@example.com","display_name":"Client Rep"}] |
titleOptionalEvent title (1-255 characters).
Updated Meeting TitledescriptionOptionalEvent description. Use null to clear.
Updated descriptionstart_timeOptionalEvent start time (flexible datetime format).
2025-12-15T15:00:00end_timeOptionalEvent end time (flexible datetime format).
2025-12-15T17:00:00all_dayOptionalWhether the event is all-day. Must be a boolean. Video conferencing cannot be enabled for all-day events.
falsegoogle_meet_enabledOptionalEnable/disable Google Meet. Disabling deletes the existing Meet. Mutually exclusive with zoom_enabled.
falsezoom_enabledOptionalEnable/disable Zoom. Disabling deletes the existing Zoom meeting. Mutually exclusive with google_meet_enabled.
trueattendeesOptionalMerged with the stored attendees list (max 50 total). Duplicates are removed by lowercase email; stored display_name/response_status are preserved unless overridden.
[{"email":"client@example.com","display_name":"Client Rep"}]Success Response
2001{
2 "success": true,
3 "data": {
4 "id": "550e8400-e29b-41d4-a716-446655440000",
5 "title": "Updated Meeting Title",
6 "description": "Updated description",
7 "start_time": "2025-12-15T15:00:00.000Z",
8 "end_time": "2025-12-15T17:00:00.000Z",
9 "all_day": false,
10 "google_meet_enabled": false,
11 "meet_link": null,
12 "zoom_enabled": true,
13 "zoom_meeting_id": 987654321,
14 "zoom_join_url": "https://zoom.us/j/987654321",
15 "attendees": [
16 {
17 "email": "client@example.com",
18 "display_name": "Client Rep",
19 "response_status": "needsAction"
20 }
21 ],
22 "caller_phone": "+15551234567",
23 "caller_name": "Jane Doe",
24 "source": "local",
25 "created_at": "2025-12-10T10:00:00.000Z",
26 "updated_at": "2025-12-11T11:30:00.000Z"
27 }
28}Need every status code and common errors? .
/api/calendar/external/v1/events/{id}Delete an event
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | UUID | Required | The unique identifier of the event. e.g. 550e8400-e29b-41d4-a716-446655440000 |
idRequiredThe unique identifier of the event.
550e8400-e29b-41d4-a716-446655440000Success Response
200{
"success": true,
"message": "Event deleted successfully"
}Need every status code and common errors? .
Datetime Formats
The API accepts many common formats and normalizes them to ISO 8601 (UTC) for storage.
| Format | Notes |
|---|---|
"2025-12-15T14:30:00" | Simple — no timezone (assumes UTC). Recommended. |
"2025-12-15T14:30:00Z" | ISO 8601 without milliseconds, UTC. |
"2025-12-15T14:30:00.000Z" | Full ISO 8601 (RFC 3339) with milliseconds. |
"2025-12-15T14:30:00+05:30" | With timezone offset. |
"2025-12-15 14:30:00" | Space separator (assumes UTC). |
Video Conferencing
Generate meeting links automatically by enabling google_meet_enabled or zoom_enabled on create/update. Google Meet returns a meet_link; Zoom returns a zoom_meeting_id and zoom_join_url.
Warning
Enable Google Meet
{
"google_meet_enabled": true
}Requires Google Calendar connected. Returns meet_link.
Enable Zoom
{
"zoom_enabled": true
}Requires Zoom connected (Settings → Integrations). Returns zoom_join_url.
Switch providers by disabling one and enabling the other in the same PATCH:
// Google Meet -> Zoom
{
"google_meet_enabled": false,
"zoom_enabled": true
}Information
warnings array (e.g. ["zoom_not_connected"]) and the corresponding link fields stay null.Boolean Field Rules
Fields like all_day, google_meet_enabled, and zoom_enabled must be real booleans, not strings.
✓ Correct
{
"all_day": true,
"google_meet_enabled": false,
"zoom_enabled": true
}✗ Incorrect
{
"all_day": "true",
"zoom_enabled": "false"
}Warning
true, false, 1, 0) returns a validation error: "Expected boolean, received string".Attendees
Pass an attendees array (max 50) on create or update. On update (PATCH) the new attendees are merged with the stored list — duplicate emails (matched case-insensitively) are removed, and stored display_name / response_status are preserved unless you override them.
1{
2 "attendees": [
3 {
4 "email": "client@example.com",
5 "display_name": "Client Rep",
6 "response_status": "needsAction"
7 },
8 {
9 "email": "team@example.com"
10 }
11 ]
12}response_status accepts one of:
needsActionaccepteddeclinedtentativeEvent Object
The full shape of a calendar event.
1{
2 "id": "uuid", // auto-generated
3 "title": "string", // 1-255 chars (required)
4 "description": "string", // optional, max 500
5 "start_time": "ISO 8601", // required
6 "end_time": "ISO 8601", // required, after start_time
7 "all_day": false, // default false
8 "google_meet_enabled": false, // default false
9 "meet_link": "string|null", // response-only (Google Meet)
10 "zoom_enabled": false, // default false
11 "zoom_meeting_id": 123456789, // response-only (Zoom)
12 "zoom_join_url": "string|null", // response-only (Zoom)
13 "attendees": [{ // optional, max 50
14 "email": "…",
15 "display_name": "string",
16 "response_status": "needsAction|accepted|declined|tentative"
17 }],
18 "caller_phone": "string|null", // optional, E.164 format
19 "caller_name": "string|null", // optional, max 200 chars
20 "source": "local|google", // response-only
21 "created_at": "ISO 8601", // response-only
22 "updated_at": "ISO 8601" // response-only
23}Warning
caller_phone and caller_name are create-only (POST) — they cannot be changed via PATCH. When caller_phone is set on create, it triggers an appointment SMS. Internal delivery fields (sms_sent, sms_sent_at) exist on the record but are never returned by the external API.Leads API
Send leads from your favorite tools to Fusion Calling. v1.2 adds read (GET), PATCH updates, Default Feed routing, and atomic batch inserts.
- Base URL
- https://app.fusioncalling.com/api/leads/external/v1
- Version
- v1.2
- Rate limit
- 100 requests / minute
- Auth
- Bearer API key
Fields & Default Feed
All request bodies use JSON with these fields.
Required
| Name | Type | Required | Description |
|---|---|---|---|
name | string | Required | Lead's full name (2-100 characters). e.g. John Doe |
phone | string | Required | 10-25 characters raw, normalized to 10-15 digit E.164. +, spaces, -, () allowed. e.g. 5551234567 or +1 234 567 8900 |
nameRequiredLead's full name (2-100 characters).
John DoephoneRequired10-25 characters raw, normalized to 10-15 digit E.164. +, spaces, -, () allowed.
5551234567 or +1 234 567 8900Optional
| Name | Type | Required | Description |
|---|---|---|---|
email | string | Optional | Valid email address (max 254 characters). Nullable. e.g. john@example.com |
company | string | Optional | Company name (max 100 characters). Nullable. e.g. Acme Corp |
tags | array<string> | Optional | Up to 20 tags. Each matches /^[a-zA-Z0-9_-]+$/ (1-50 chars). Nullable. e.g. ["vip","website-lead"] |
custom_fields | object | Optional | Up to 20 keys (/^[a-zA-Z0-9_-]+$/). Values: string (max 1000), number, boolean, or null. e.g. {"source":"website","score":8} |
campaign_id | UUID | Optional | Target campaign UUID. Omit to use the Default Feed (call-only). e.g. omit → Default Feed |
kind | enum | Optional | Lead kind; must match the target campaign. 'sms' requires a campaign_id.callsms |
emailOptionalValid email address (max 254 characters). Nullable.
john@example.comcompanyOptionalCompany name (max 100 characters). Nullable.
Acme CorptagsOptionalUp to 20 tags. Each matches /^[a-zA-Z0-9_-]+$/ (1-50 chars). Nullable.
["vip","website-lead"]custom_fieldsOptionalUp to 20 keys (/^[a-zA-Z0-9_-]+$/). Values: string (max 1000), number, boolean, or null.
{"source":"website","score":8}campaign_idOptionalTarget campaign UUID. Omit to use the Default Feed (call-only).
omit → Default FeedkindOptionalLead kind; must match the target campaign. 'sms' requires a campaign_id.
callsmsInformation
+1 (234) 567-8900, 5551234567, and +15551234567 all match the same lead.Warning
campaign_id, leads now route to the Default Feed instead of your oldest campaign.Information
status: "pending" and call_attempts: 0. The status field is not settable through the external API — it progresses as the platform processes the lead.Warning
kind: "sms" requires a campaign_id. The Default Feed is call-only, so sending an SMS lead without a campaign returns a validation error.Information
account_id, user_id, phone_normalized, failure_reason, cost, and sms_marketing_opt_out.Endpoints at a glance
/api/leads/external/v1/leadsCreate a lead
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
name | string | Required | Lead's full name (2-100 characters). e.g. John Doe |
phone | string | Required | Phone number, 10-25 characters raw, auto-normalized to 10-15 digit E.164. e.g. +1 234 567 8900 |
email | string | Optional | Email address (max 254 characters). Send null to clear. e.g. john@example.com |
company | string | Optional | Company name (up to 100 characters). Send null to clear. e.g. Acme Corp |
tags | array<string> | Optional | Lead tags (max 20). Each tag must match /^[a-zA-Z0-9_-]+$/ (1-50 chars). e.g. ["vip","website-lead"] |
custom_fields | object | Optional | Custom metadata (max 20 keys). Keys match /^[a-zA-Z0-9_-]+$/ (1-50 chars); values can be string (max 1000), number, boolean, or null. e.g. {"source":"website","score":8} |
campaign_id | UUID | Optional | Target campaign UUID. Omit to route to the Default Feed (call-only). |
kind | enum | Optional | Lead kind. Must match the target campaign. 'sms' requires a campaign_id (the Default Feed is call-only).callsmse.g. call |
nameRequiredLead's full name (2-100 characters).
John DoephoneRequiredPhone number, 10-25 characters raw, auto-normalized to 10-15 digit E.164.
+1 234 567 8900emailOptionalEmail address (max 254 characters). Send null to clear.
john@example.comcompanyOptionalCompany name (up to 100 characters). Send null to clear.
Acme CorptagsOptionalLead tags (max 20). Each tag must match /^[a-zA-Z0-9_-]+$/ (1-50 chars).
["vip","website-lead"]custom_fieldsOptionalCustom metadata (max 20 keys). Keys match /^[a-zA-Z0-9_-]+$/ (1-50 chars); values can be string (max 1000), number, boolean, or null.
{"source":"website","score":8}campaign_idOptionalTarget campaign UUID. Omit to route to the Default Feed (call-only).
kindOptionalLead kind. Must match the target campaign. 'sms' requires a campaign_id (the Default Feed is call-only).
callsmscallSuccess Response
2011{
2 "success": true,
3 "data": {
4 "mode": "atomic",
5 "inserted": 1,
6 "leads": [
7 {
8 "id": "f1c2d3e4-5b6a-7c8d-9e0f-1a2b3c4d5e6f",
9 "name": "John Doe",
10 "phone": "+12345678900",
11 "email": "john@example.com",
12 "company": "Acme Corp",
13 "campaign_id": "a1b2c3d4-...",
14 "campaign_name": "Default Feed",
15 "status": "pending",
16 "tags": ["vip", "website-lead"],
17 "custom_fields": { "source": "website", "score": 8 },
18 "call_attempts": 0,
19 "created_at": "2026-06-17T12:00:00.000Z"
20 }
21 ]
22 }
23}Need every status code and common errors? .
/api/leads/external/v1/leadsCreate leads in batch
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
leads | array<object> | Required | Array of 1-100 lead objects, each with name & phone. e.g. [{"name":"Lead 1","phone":"+1234567890"},{"name":"Lead 2","phone":"+1234567891"}] |
kind | enum | Optional | Batch-level kind. Must match the target campaign kind. 'sms' requires a campaign_id (the Default Feed is call-only).callsmse.g. call |
leadsRequiredArray of 1-100 lead objects, each with name & phone.
[{"name":"Lead 1","phone":"+1234567890"},{"name":"Lead 2","phone":"+1234567891"}]kindOptionalBatch-level kind. Must match the target campaign kind. 'sms' requires a campaign_id (the Default Feed is call-only).
callsmscallSuccess Response
2011{
2 "success": true,
3 "data": {
4 "mode": "atomic",
5 "inserted": 2,
6 "leads": [
7 { "id": "uuid-1", "name": "Lead 1", "phone": "+1234567890", "status": "pending" },
8 { "id": "uuid-2", "name": "Lead 2", "phone": "+1234567891", "status": "pending" }
9 ]
10 }
11}Need every status code and common errors? .
/api/leads/external/v1/leadsLookup lead by phone
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
phone | string | Required | Phone number to lookup (US 10-digit works without country code). e.g. +1234567890 |
campaign_id | UUID | Optional | Optional UUID to scope the lookup to a specific campaign. |
status | enum | Optional | Optional filter applied after lookup. A matched lead that does not satisfy it returns 404.pendingin-progressretrycompletedfailed |
updated_since | string | Optional | Optional filter applied after lookup (any parseable datetime -> ISO UTC). |
phoneRequiredPhone number to lookup (US 10-digit works without country code).
+1234567890campaign_idOptionalOptional UUID to scope the lookup to a specific campaign.
statusOptionalOptional filter applied after lookup. A matched lead that does not satisfy it returns 404.
pendingin-progressretrycompletedfailedupdated_sinceOptionalOptional filter applied after lookup (any parseable datetime -> ISO UTC).
Success Response
2001{
2 "success": true,
3 "data": {
4 "lookup": "phone",
5 "campaign_id": "a1b2c3d4-...",
6 "campaign_name": "Default Feed",
7 "lead": {
8 "id": "f1c2d3e4-5b6a-7c8d-9e0f-1a2b3c4d5e6f",
9 "name": "John Doe",
10 "phone": "+12345678900",
11 "email": "john@example.com",
12 "company": "Acme Corp",
13 "status": "pending",
14 "tags": ["vip"],
15 "custom_fields": { "source": "website" },
16 "call_attempts": 0,
17 "created_at": "2026-06-16T12:00:00.000Z",
18 "updated_at": "2026-06-16T12:00:00.000Z"
19 }
20 }
21}Need every status code and common errors? .
/api/leads/external/v1/leadsList leads (paginated)
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
campaign_id | UUID | Optional | Optional UUID. Omit to read from the Default Feed. |
status | enum | Optional | Optional status filter. Omit to return leads of any status.pendingin-progressretrycompletedfailede.g. pending |
updated_since | string | Optional | Only return leads updated since this datetime (any parseable format -> ISO UTC). e.g. 2026-07-01T00:00:00 |
page | number | Optional | Page number (default 1). e.g. 1 |
pageSize | number | Optional | Results per page (default 50, min 1, max 100). e.g. 50 |
sort | enum | Optional | Sort order (default created_at:desc).created_at:desccreated_at:ascupdated_at:desce.g. created_at:desc |
campaign_idOptionalOptional UUID. Omit to read from the Default Feed.
statusOptionalOptional status filter. Omit to return leads of any status.
pendingin-progressretrycompletedfailedpendingupdated_sinceOptionalOnly return leads updated since this datetime (any parseable format -> ISO UTC).
2026-07-01T00:00:00pageOptionalPage number (default 1).
1pageSizeOptionalResults per page (default 50, min 1, max 100).
50sortOptionalSort order (default created_at:desc).
created_at:desccreated_at:ascupdated_at:desccreated_at:descSuccess Response
2001{
2 "success": true,
3 "data": {
4 "campaign_id": "a1b2c3d4-...",
5 "campaign_name": "Default Feed",
6 "page": 1,
7 "pageSize": 50,
8 "total": 2,
9 "leads": [
10 { "id": "uuid-1", "name": "John Doe", "phone": "+12345678900", "status": "pending", "tags": ["vip"], "created_at": "2026-06-16T12:00:00.000Z" },
11 { "id": "uuid-2", "name": "Jane Roe", "phone": "+12345678901", "status": "pending", "tags": null, "created_at": "2026-06-16T12:05:00.000Z" }
12 ]
13 }
14}Need every status code and common errors? .
/api/leads/external/v1/leads/{lead_id}Get a lead by ID
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
lead_id | UUID | Required | Lead UUID from the POST response. e.g. f1c2d3e4-5b6a-7c8d-9e0f-1a2b3c4d5e6f |
lead_idRequiredLead UUID from the POST response.
f1c2d3e4-5b6a-7c8d-9e0f-1a2b3c4d5e6fSuccess Response
2001{
2 "success": true,
3 "data": {
4 "lead": {
5 "id": "f1c2d3e4-5b6a-7c8d-9e0f-1a2b3c4d5e6f",
6 "name": "John Doe",
7 "phone": "+12345678900",
8 "email": "john@example.com",
9 "company": "Acme Corp",
10 "campaign_id": "a1b2c3d4-...",
11 "campaign_name": "Default Feed",
12 "status": "pending",
13 "tags": ["vip"],
14 "custom_fields": { "source": "website" },
15 "call_attempts": 0,
16 "created_at": "2026-06-16T12:00:00.000Z",
17 "updated_at": "2026-06-16T12:00:00.000Z"
18 }
19 }
20}Need every status code and common errors? .
/api/leads/external/v1/leadsUpdate a lead
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
phone | string | Required | Lookup key that identifies the lead (normalized to E.164). The phone number itself cannot be changed. e.g. +1234567890 |
campaign_id | UUID | Optional | Optional scoping campaign UUID. Omit to target the Default Feed. |
kind | enum | Optional | Updated lead kind. 'sms' requires a campaign_id (the Default Feed is call-only).callsms |
name | string | Optional | Updated lead name (2-100 characters). e.g. John Doe Updated |
email | string | Optional | Updated email address (max 254). Send null to clear. e.g. john.new@example.com |
company | string | Optional | Updated company name (max 100). Send null to clear. e.g. New Corp |
tags | array<string> | Optional | Updated tags (max 20). Send null to clear. e.g. vip,updated |
custom_fields | object | Optional | Merged into the existing custom_fields (keys max 20). Set a key to null to remove it. e.g. {"score":9} |
phoneRequiredLookup key that identifies the lead (normalized to E.164). The phone number itself cannot be changed.
+1234567890campaign_idOptionalOptional scoping campaign UUID. Omit to target the Default Feed.
kindOptionalUpdated lead kind. 'sms' requires a campaign_id (the Default Feed is call-only).
callsmsnameOptionalUpdated lead name (2-100 characters).
John Doe UpdatedemailOptionalUpdated email address (max 254). Send null to clear.
john.new@example.comcompanyOptionalUpdated company name (max 100). Send null to clear.
New CorptagsOptionalUpdated tags (max 20). Send null to clear.
vip,updatedcustom_fieldsOptionalMerged into the existing custom_fields (keys max 20). Set a key to null to remove it.
{"score":9}Success Response
2001{
2 "success": true,
3 "data": {
4 "lead": {
5 "id": "f1c2d3e4-5b6a-7c8d-9e0f-1a2b3c4d5e6f",
6 "name": "John Doe Updated",
7 "phone": "+12345678900",
8 "email": "john.new@example.com",
9 "company": "New Corp",
10 "campaign_name": "Default Feed",
11 "status": "pending",
12 "tags": ["vip", "updated"],
13 "custom_fields": { "source": "website", "score": 9 },
14 "call_attempts": 0,
15 "created_at": "2026-06-17T12:00:00.000Z",
16 "updated_at": "2026-06-17T12:05:00.000Z"
17 }
18 }
19}Need every status code and common errors? .
Lead Object
The shape returned for a single lead. GET-by-ID and PATCH wrap it under data.lead; phone lookup wraps it under data.lead with a lookup: "phone" marker.
1{
2 "id": "uuid", // always present
3 "name": "string", // always present
4 "phone": "string", // normalized E.164
5 "email": "string|null",
6 "company": "string|null",
7 "status": "pending", // always "pending" on external create
8 "campaign_id": "uuid",
9 "campaign_name": "string",
10 "tags": ["string"]|null, // present if set
11 "custom_fields": { ... }|null, // present if set
12 "call_attempts": 0, // present if set
13 "last_called_at": "ISO 8601|null", // present if set
14 "created_at": "ISO 8601",
15 "updated_at": "ISO 8601" // present if set
16}Note: account_id, user_id, phone_normalized, failure_reason, cost, and sms_marketing_opt_out are never returned.
FAQ
Integration Guides
Connect Fusion Calling with your favorite tools.
GoHighLevel
Send leads from GoHighLevel to Fusion Calling whenever a contact is created.
- Go to Settings → Automation → Webhooks
- Click "Add Webhook"
- URL:
https://app.fusioncalling.com/api/leads/external/v1/leads - Method: POST
- Header:
Authorization: Bearer YOUR_API_KEY
1{
2 "name": "{{contact.name}}",
3 "phone": "{{contact.phone}}",
4 "email": "{{contact.email}}",
5 "tags": ["gohighlevel"]
6}n8n
Connect any system with visual workflows using an HTTP Request node.
- • Method: POST
- • URL:
https://app.fusioncalling.com/api/leads/external/v1/leads - • Auth: Generic Credential · header
Authorization=Bearer YOUR_API_KEY - • Body content type: JSON
Information
Zapier
Connect Fusion Calling with 5,000+ apps.
- • Facebook Lead Ads → Fusion Calling
- • Google Sheets → Fusion Calling
- • Typeform → Fusion Calling
- • HubSpot → Fusion Calling
Action: Webhooks by Zapier · POST to the Leads endpoint with the Authorization header.
Web Forms
Capture leads from website forms with server-side processing.
Warning
1<?php
2$apiKey = 'YOUR_API_KEY';
3$endpoint = 'https://app.fusioncalling.com/api/leads/external/v1/leads';
4
5$data = [
6 'name' => $_POST['name'],
7 'phone' => $_POST['phone'],
8 'email' => $_POST['email'],
9 'tags' => ['website-form'],
10];
11
12$ch = curl_init($endpoint);
13curl_setopt($ch, CURLOPT_POST, true);
14curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
15curl_setopt($ch, CURLOPT_HTTPHEADER, [
16 'Authorization: Bearer ' . $apiKey,
17 'Content-Type: application/json',
18]);
19curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
20
21$response = curl_exec($ch);
22curl_close($ch);Reference
Error codes, rate limiting, and best practices.
Error Codes
400Bad RequestCheck the request format and required fields.401UnauthorizedVerify your API key in the Authorization header.404Not FoundVerify the resource (and ID) exists.409ConflictResource already exists — use PATCH to update.429Too Many RequestsImplement retry with exponential backoff.500Server ErrorRetry with backoff; contact support if it persists.Rate Limiting
All Fusion Calling APIs implement rate limiting to protect the service from abuse and ensure fair usage across all users.
Calendar API
Limit
12 requests per minute
Window
Sliding window (last 60 seconds)
Scope
Per API key
Leads API
Limit
100 requests per minute
Window
Sliding window (last 60 seconds)
Scope
Per API key (each batch counts as 1 request)
Default (any other route)
Limit
12 requests per minute
Window
Sliding window (last 60 seconds)
Scope
Per API key
Rate Limit Headers
All API responses include rate limit headers to help you track your usage:
| Header | Description | Example |
|---|---|---|
X-RateLimit-Limit | Maximum requests allowed | 12 |
X-RateLimit-Remaining | Requests remaining in current window | 8 |
X-RateLimit-Reset | Unix timestamp when limit resets | 1704067200 |
Retry-After | Seconds to wait before retry (429 only) | 30 |
429 Response Body
When the limit is exceeded, the JSON body echoes the same values as the headers so you can read either:
1{
2 "error": "Rate limit exceeded",
3 "message": "Too many requests...",
4 "retryAfter": 47,
5 "limit": 100,
6 "remaining": 0
7}Handling Rate Limits
When you exceed the rate limit, you'll receive a 429 Too Many Requests response with a Retry-After header indicating how many seconds to wait before retrying.
Best Practices
- ✅ Implement exponential backoff for retries
- ✅ Cache responses to reduce unnecessary requests
- ✅ Use batch operations when available
- ✅ Monitor rate limit headers in production
- ✅ Queue requests when approaching rate limits
Information
Example: Implementing Retry Logic
async function makeRequestWithRetry(url, options, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
const response = await fetch(url, options);
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After');
const waitTime = retryAfter ? parseInt(retryAfter) * 1000 : 5000;
// Exponential backoff
const backoffTime = waitTime * Math.pow(2, i);
await new Promise(resolve => setTimeout(resolve, backoffTime));
continue;
}
return response;
}
throw new Error('Max retries exceeded');
}Best Practices
Security
- ✓ Store API keys in environment variables
- ✓ Use HTTPS for all requests
- ✓ Rotate keys regularly
- ✗ Never expose keys in client-side code
- ✗ Never commit keys to version control
Performance
- ✓ Use batch operations for many items
- ✓ Cache responses where appropriate
- ✓ Implement exponential backoff for 429s
- ✓ Use pagination for large datasets
Changelog
External API Hardening
July 2026- custom_fields support on lead create/update (merged on PATCH)
- updated_since + sort filters added to List Leads
- Writable-fields allowlist — sensitive columns never accepted or returned
- Attendees now merge (not replace) on event PATCH
- caller_phone / caller_name are create-only; caller_phone triggers an appointment SMS
- Stricter Authorization header parsing; documented 401 vs validation error shapes
- 429 response body + default 12/min fallback documented
Leads API v1.2
February 2026- GET endpoints for reading leads
- PATCH endpoint for updating leads
- Default Feed campaign routing
- Atomic batch inserts (1-100)
Calendar API v1.0
December 2025- Initial release with full CRUD
- Flexible datetime formats
- Google Meet integration
- Automatic Google Calendar sync