BroadNet Engage API

Carrier-grade outbound voice dialer API for campaign management, live call control, compliance, and real-time reporting. MENA-first, built for scale.

Version v1
Base URL https://your-domain.com/v1
Format JSON
Status Operational

Authentication

All API requests require a JWT bearer token obtained via the login endpoint. Tokens expire after 24 hours.

Authorization header: Include Authorization: Bearer {your_token} on every request. Requests without a valid token return 401 Unauthorized.
POST /v1/auth/login Obtain JWT token

Authenticate with email and password to receive a JWT access token and refresh token. The access token is valid for 24 hours.

Request
Response
cURL
Body parameters
ParameterTypeRequiredDescription
emailstringRequiredUser email address
passwordstringRequiredUser password
Request body
{
  "email": "agent@acme.com",
  "password": "••••••••"
}
200 OK
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "bearer",
  "expires_in": 86400,
  "user": {
    "id": "01919a1b-c2d3-7e4f-8a5b-6c7d8e9f0a1b",
    "email": "agent@acme.com",
    "full_name": "Layla Ahmed",
    "role": "agent",
    "tenant_id": "01919a1b-0000-7e4f-8a5b-ffffffffffff"
  }
}
401 Unauthorized
{
  "type": "https://engage.broadnet.me/errors/invalid-credentials",
  "title": "Invalid credentials",
  "status": 401,
  "detail": "Email or password is incorrect."
}
curl -X POST https://your-domain.com/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"agent@acme.com","password":"secret"}'
POST /v1/auth/refresh Refresh access token

Exchange a valid refresh token for a new access token. Use this before the 24-hour access token expires to avoid prompting users to re-login.

Request
Response
cURL
Body parameters
ParameterTypeRequiredDescription
refresh_tokenstringRequiredRefresh token from login response
{ "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." }
200 OK
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "bearer",
  "expires_in": 86400
}
curl -X POST https://your-domain.com/v1/auth/refresh \
  -H "Content-Type: application/json" \
  -d '{"refresh_token":"eyJ..."}'

Errors & Status Codes

All errors follow RFC 7807 (Problem Details for HTTP APIs). The response body always contains type, title, status, and detail.

StatusMeaningCommon Cause
200OKRequest succeeded
201CreatedResource created successfully
400Bad RequestInvalid JSON, missing required field, validation failure
401UnauthorizedMissing or expired JWT token
403ForbiddenInsufficient role permissions
404Not FoundResource does not exist in your tenant
409ConflictDuplicate resource (e.g., duplicate phone number in DNC)
422UnprocessableSemantic validation error (e.g., tenant_id in body rejected)
429Rate LimitedExceeded 2,000 requests/minute per tenant
500Server ErrorInternal error — contact support with instance ID

Pagination

All list endpoints use cursor-based pagination. Pass the next_cursor from a response as the cursor query parameter to fetch the next page.

GET /v1/calls?limit=50&cursor=eyJpZCI6IjAxOTE5...&status=completed

{
  "items": [ ... ],
  "total": 4821,
  "next_cursor": "eyJpZCI6IjAxOTE5YWZiLWE4YjItN2VjZC04ZTVmLWZmZmZmZmZmZmZmZiJ9",
  "has_more": true
}

Campaigns

Manage outbound dialing campaigns. A campaign links a contact list, dialing mode, CLI group, and compliance rules.

GET /v1/campaigns List all campaigns

Returns a paginated list of campaigns for the authenticated tenant. Filter by status, dialing mode, or date range.

Request
Response
cURL
Query parameters
ParameterTypeRequiredDescription
statusstringOptionaldraft | active | paused | completed
dialing_modestringOptionalpreview | power | progressive | predictive
limitintegerOptionalPage size (default: 20, max: 100)
cursorstringOptionalPagination cursor from previous response
200 OK
{
  "items": [
    {
      "id": "01919a1b-c2d3-7e4f-8a5b-6c7d8e9f0a1b",
      "name": "Q2 Renewal Drive",
      "status": "active",
      "dialing_mode": "progressive",
      "total_contacts": 12500,
      "contacted": 4821,
      "connected": 1203,
      "abandon_rate": 2.1,
      "cli_group_id": "01919a1b-0001-7e4f-8a5b-aaaaaaaaaaaa",
      "created_at": "2026-06-01T08:00:00Z",
      "started_at": "2026-06-02T09:00:00Z"
    }
  ],
  "total": 14,
  "next_cursor": null,
  "has_more": false
}
curl https://your-domain.com/v1/campaigns?status=active \
  -H "Authorization: Bearer {token}"
POST /v1/campaigns Create campaign

Create a new outbound campaign. The campaign starts in draft status. Call POST /v1/campaigns/{id}/start to activate dialing.

Request
Response
cURL
Body parameters
ParameterTypeRequiredDescription
namestringRequiredDisplay name for the campaign
dialing_modestringRequiredpreview | power | progressive | predictive
cli_group_iduuidRequiredCLI group to use for caller ID
max_attemptsintegerOptionalMax dial attempts per contact (default: 3)
retry_delay_minutesintegerOptionalMinutes between retries (default: 60)
dial_rationumberOptionalLines per agent for power mode (default: 1.5)
timezonestringOptionalIANA timezone (default: Asia/Dubai)
Request body
{
  "name": "Q3 Insurance Renewal",
  "dialing_mode": "progressive",
  "cli_group_id": "01919a1b-0001-7e4f-8a5b-aaaaaaaaaaaa",
  "max_attempts": 4,
  "retry_delay_minutes": 90,
  "dial_ratio": 2.0,
  "timezone": "Asia/Dubai"
}
201 Created
{
  "id": "01919bcc-c2d3-7e4f-8a5b-6c7d8e9f0a1b",
  "name": "Q3 Insurance Renewal",
  "status": "draft",
  "dialing_mode": "progressive",
  "cli_group_id": "01919a1b-0001-7e4f-8a5b-aaaaaaaaaaaa",
  "max_attempts": 4,
  "retry_delay_minutes": 90,
  "dial_ratio": 2.0,
  "timezone": "Asia/Dubai",
  "total_contacts": 0,
  "created_at": "2026-06-12T10:30:00Z"
}
curl -X POST https://your-domain.com/v1/campaigns \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Q3 Insurance Renewal",
    "dialing_mode": "progressive",
    "cli_group_id": "01919a1b-0001-7e4f-8a5b-aaaaaaaaaaaa"
  }'
POST /v1/campaigns/{id}/start Start dialing

Transition a campaign from draft or paused to active. Requires at least one contact in the list and at least one logged-in agent assigned to the campaign.

Compliance gate: Every dial attempt calls compliance-service before connecting. Campaigns cannot bypass this check.
Request
Response
cURL
Path parameters
ParameterTypeRequiredDescription
iduuidRequiredCampaign ID

No request body required.

200 OK
{
  "id": "01919bcc-c2d3-7e4f-8a5b-6c7d8e9f0a1b",
  "status": "active",
  "started_at": "2026-06-12T11:00:00Z",
  "message": "Campaign dialing started."
}
409 Conflict
{
  "type": "https://engage.broadnet.me/errors/campaign-no-contacts",
  "title": "Campaign has no contacts",
  "status": 409,
  "detail": "Upload contacts before starting the campaign."
}
curl -X POST https://your-domain.com/v1/campaigns/01919bcc-c2d3-7e4f-8a5b-6c7d8e9f0a1b/start \
  -H "Authorization: Bearer {token}"
POST /v1/campaigns/{id}/pause Pause dialing

Pause an active campaign. In-progress calls are allowed to complete. No new outbound dials are initiated after this call returns.

Request
Response
cURL

No request body required. Pass campaign ID in the URL.

200 OK
{
  "id": "01919bcc-c2d3-7e4f-8a5b-6c7d8e9f0a1b",
  "status": "paused",
  "paused_at": "2026-06-12T13:45:00Z"
}
curl -X POST https://your-domain.com/v1/campaigns/01919bcc-c2d3-7e4f-8a5b-6c7d8e9f0a1b/pause \
  -H "Authorization: Bearer {token}"

Contacts

Upload, search, and manage contact lists. Contacts are always scoped to a campaign. Phone numbers are validated and normalized to E.164 format.

GET /v1/campaigns/{id}/contacts List contacts in campaign

Returns a paginated list of contacts for a specific campaign. Filter by dial status to find pending, contacted, or failed contacts.

Request
Response
cURL
Query parameters
ParameterTypeRequiredDescription
statusstringOptionalpending | dialing | connected | completed | failed | dnc
limitintegerOptionalPage size (default: 50, max: 200)
cursorstringOptionalPagination cursor
200 OK
{
  "items": [
    {
      "id": "01919cdd-c2d3-7e4f-8a5b-111111111111",
      "phone": "+971501234567",
      "first_name": "Ahmad",
      "last_name": "Al Rashid",
      "status": "pending",
      "attempts": 0,
      "last_call_at": null,
      "disposition": null,
      "custom_fields": {
        "account_number": "ACC-00421",
        "renewal_date": "2026-07-15"
      }
    }
  ],
  "total": 12500,
  "next_cursor": "eyJpZCI6IjAxOTE5Y2RkIn0=",
  "has_more": true
}
curl "https://your-domain.com/v1/campaigns/01919bcc-c2d3-7e4f-8a5b-6c7d8e9f0a1b/contacts?status=pending&limit=50" \
  -H "Authorization: Bearer {token}"
POST /v1/campaigns/{id}/contacts Upload contacts

Bulk-upload contacts to a campaign. Supports up to 10,000 contacts per request. Duplicates (same phone number) are silently deduplicated. Phone numbers are validated and normalized to E.164 format.

DNC filtering: Phone numbers matching your tenant's DNC list are automatically excluded from dialing. They are still stored but marked with status dnc.
Request
Response
cURL
Body parameters
ParameterTypeRequiredDescription
contactsarrayRequiredArray of contact objects (max 10,000)
contacts[].phonestringRequiredPhone number in E.164 or local format
contacts[].first_namestringOptionalContact first name
contacts[].last_namestringOptionalContact last name
contacts[].custom_fieldsobjectOptionalArbitrary key-value metadata shown to agents
Request body
{
  "contacts": [
    {
      "phone": "+971501234567",
      "first_name": "Ahmad",
      "last_name": "Al Rashid",
      "custom_fields": {
        "account_number": "ACC-00421",
        "policy_type": "comprehensive",
        "renewal_date": "2026-07-15"
      }
    },
    {
      "phone": "+97144561234",
      "first_name": "Sara",
      "last_name": "Hassan"
    }
  ]
}
201 Created
{
  "imported": 2,
  "duplicates_skipped": 0,
  "dnc_filtered": 0,
  "invalid_phones": 0,
  "total_in_campaign": 12502
}
curl -X POST https://your-domain.com/v1/campaigns/01919bcc-c2d3-7e4f-8a5b-6c7d8e9f0a1b/contacts \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{"contacts":[{"phone":"+971501234567","first_name":"Ahmad"}]}'
POST /v1/contacts/search Search contacts

Full-text search across all contacts in your tenant. Search by phone number, name, or custom field values. Results are ranked by relevance.

Request
Response
cURL
{
  "q": "Ahmad",
  "campaign_id": "01919bcc-c2d3-7e4f-8a5b-6c7d8e9f0a1b",
  "limit": 20
}
200 OK
{
  "items": [
    {
      "id": "01919cdd-c2d3-7e4f-8a5b-111111111111",
      "phone": "+971501234567",
      "first_name": "Ahmad",
      "last_name": "Al Rashid",
      "campaign_id": "01919bcc-c2d3-7e4f-8a5b-6c7d8e9f0a1b",
      "campaign_name": "Q3 Insurance Renewal",
      "status": "pending"
    }
  ],
  "total": 1
}
curl -X POST https://your-domain.com/v1/contacts/search \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{"q":"Ahmad","limit":20}'

Calls

Query call history, live call counts, and originate manual calls. All times are in UTC ISO-8601.

GET /v1/calls List calls with filters

Returns paginated call history with rich filtering. Supports filtering by campaign, agent, date range, direction, and disposition.

Request
Response
cURL
Query parameters
ParameterTypeRequiredDescription
campaign_iduuidOptionalFilter to a specific campaign
agent_iduuidOptionalFilter by agent
statusstringOptionalcompleted | no_answer | busy | failed | voicemail
fromdatetimeOptionalStart of date range (ISO-8601 UTC)
todatetimeOptionalEnd of date range (ISO-8601 UTC)
limitintegerOptionalPage size (default: 50, max: 200)
200 OK
{
  "items": [
    {
      "id": "01919dee-c2d3-7e4f-8a5b-222222222222",
      "campaign_id": "01919bcc-c2d3-7e4f-8a5b-6c7d8e9f0a1b",
      "agent_id": "01919a1b-c2d3-7e4f-8a5b-6c7d8e9f0a1b",
      "contact_phone": "+971501234567",
      "direction": "outbound",
      "status": "completed",
      "disposition": "sale",
      "duration_seconds": 248,
      "talk_time_seconds": 212,
      "started_at": "2026-06-12T10:15:00Z",
      "answered_at": "2026-06-12T10:15:18Z",
      "ended_at": "2026-06-12T10:19:08Z",
      "recording_id": "01919eff-aaaa-7e4f-8a5b-333333333333"
    }
  ],
  "total": 4821,
  "next_cursor": "eyJpZCI6IjAxOTE5ZGVlIn0=",
  "has_more": true
}
curl "https://your-domain.com/v1/calls?campaign_id=01919bcc-c2d3-7e4f-8a5b-6c7d8e9f0a1b&status=completed&from=2026-06-01T00:00:00Z" \
  -H "Authorization: Bearer {token}"
GET /v1/calls/count-today Today's statistics

Returns aggregated call statistics for the current calendar day in the tenant's configured timezone. Useful for dashboard widgets.

Request
Response
cURL

No parameters required. Optionally pass ?campaign_id={id} to scope to one campaign.

200 OK
{
  "date": "2026-06-12",
  "total_calls": 1842,
  "connected": 624,
  "no_answer": 810,
  "busy": 204,
  "failed": 124,
  "voicemail": 80,
  "avg_talk_time_seconds": 187,
  "abandon_rate": 2.3,
  "as_of": "2026-06-12T13:45:00Z"
}
curl https://your-domain.com/v1/calls/count-today \
  -H "Authorization: Bearer {token}"
GET /v1/calls/active-count Live active call count

Returns the real-time count of active (in-progress) calls. Updated every second from FreeSWITCH ESL. Use this to power live wallboard displays.

Request
Response
cURL

No parameters required.

200 OK
{
  "active_calls": 47,
  "ringing": 12,
  "connected": 35,
  "as_of": "2026-06-12T13:45:33Z"
}
curl https://your-domain.com/v1/calls/active-count \
  -H "Authorization: Bearer {token}"
POST /v1/calls/originate Originate a call

Originate a manual outbound call from an agent's SIP endpoint to a destination phone number. A compliance check is always performed before dialing. Returns immediately — call state is delivered via webhooks.

Compliance required: If the compliance check returns allowed: false, the call is blocked and a 403 is returned with the specific rule that blocked it.
Request
Response
cURL
Body parameters
ParameterTypeRequiredDescription
agent_iduuidRequiredAgent who will receive the bridged call
destinationstringRequiredDestination phone in E.164 format
campaign_iduuidOptionalAssociate call with a campaign for reporting
cli_overridestringOptionalOverride caller ID (must be in tenant's CLI group)
Request body
{
  "agent_id": "01919a1b-c2d3-7e4f-8a5b-6c7d8e9f0a1b",
  "destination": "+971501234567",
  "campaign_id": "01919bcc-c2d3-7e4f-8a5b-6c7d8e9f0a1b"
}
202 Accepted
{
  "call_id": "01919dee-c2d3-7e4f-8a5b-999999999999",
  "status": "dialing",
  "destination": "+971501234567",
  "compliance": {
    "allowed": true,
    "rule_version": "2026-06-01",
    "audit_ref": "AUD-9921847"
  }
}
403 Compliance Block
{
  "type": "https://engage.broadnet.me/errors/compliance-blocked",
  "title": "Call blocked by compliance",
  "status": 403,
  "detail": "Number is on the national DNC registry (TDRA).",
  "compliance": {
    "allowed": false,
    "rule": "dnc_national",
    "rule_version": "2026-06-01",
    "audit_ref": "AUD-9921848"
  }
}
curl -X POST https://your-domain.com/v1/calls/originate \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "01919a1b-c2d3-7e4f-8a5b-6c7d8e9f0a1b",
    "destination": "+971501234567",
    "campaign_id": "01919bcc-c2d3-7e4f-8a5b-6c7d8e9f0a1b"
  }'

Agents

Monitor live agent states, change availability, and check SIP registration status for your contact center floor.

GET /v1/agents/state Live agent states

Returns the real-time state of all agents in the tenant. Updated within 1 second of state changes via FreeSWITCH ESL events.

Request
Response
cURL

No parameters required. Optionally pass ?team_id={id} to filter by team.

200 OK
{
  "agents": [
    {
      "id": "01919a1b-c2d3-7e4f-8a5b-6c7d8e9f0a1b",
      "full_name": "Layla Ahmed",
      "state": "on_call",
      "state_since": "2026-06-12T13:42:17Z",
      "sip_registered": true,
      "current_call_id": "01919dee-c2d3-7e4f-8a5b-999999999999",
      "calls_today": 42,
      "talk_time_today_seconds": 8940
    },
    {
      "id": "01919a1b-c2d3-7e4f-8a5b-aabbccddeeff",
      "full_name": "Omar Khalil",
      "state": "available",
      "state_since": "2026-06-12T13:41:05Z",
      "sip_registered": true,
      "current_call_id": null,
      "calls_today": 38,
      "talk_time_today_seconds": 7612
    }
  ],
  "summary": {
    "total": 24,
    "available": 8,
    "on_call": 12,
    "break": 3,
    "offline": 1
  }
}
curl https://your-domain.com/v1/agents/state \
  -H "Authorization: Bearer {token}"
POST /v1/agents/me/state Change agent status

Allows the authenticated agent to change their own availability state. Supervisors can use POST /v1/agents/{id}/state to change another agent's state.

Request
Response
cURL
Body parameters
ParameterTypeRequiredDescription
statestringRequiredavailable | break | lunch | training | offline
reasonstringOptionalFree-text reason (shown to supervisors)
{ "state": "break", "reason": "Short break" }
200 OK
{
  "agent_id": "01919a1b-c2d3-7e4f-8a5b-6c7d8e9f0a1b",
  "state": "break",
  "state_since": "2026-06-12T13:50:00Z",
  "reason": "Short break"
}
curl -X POST https://your-domain.com/v1/agents/me/state \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{"state":"break","reason":"Short break"}'
GET /v1/agents/sip-registered SIP registration status

Returns which agents currently have an active SIP registration in FreeSWITCH. An agent must be SIP-registered to receive calls.

Request
Response
cURL

No parameters required.

200 OK
{
  "registered": [
    {
      "agent_id": "01919a1b-c2d3-7e4f-8a5b-6c7d8e9f0a1b",
      "sip_user": "agent_1001",
      "registered_at": "2026-06-12T08:02:44Z",
      "user_agent": "SIP.js/0.21.2"
    }
  ],
  "total_registered": 23,
  "total_agents": 24
}
curl https://your-domain.com/v1/agents/sip-registered \
  -H "Authorization: Bearer {token}"

Compliance & DNC

Pre-dial compliance checks and Do Not Call (DNC) list management. The compliance engine checks calling hours, national DNC registries (UAE TDRA, KSA CITC, Egypt NTRA, Jordan TRC), consent records, and CLI ownership.

Default-deny policy: Any compliance check error or timeout returns allowed: false. The dialer never assumes permission on failure.
POST /v1/compliance/check Pre-dial compliance check

Run a compliance check before dialing a number. Returns whether the call is permitted along with the governing rule version and an immutable audit reference. The dialer calls this automatically — expose it for CRM integrations or manual dial flows.

Request
Response
cURL
Body parameters
ParameterTypeRequiredDescription
phonestringRequiredPhone number to check (E.164)
campaign_iduuidOptionalCampaign context for calling-hours rules
jurisdictionstringOptionalISO-3166-1 alpha-2 (auto-detected from phone if omitted)
{
  "phone": "+971501234567",
  "campaign_id": "01919bcc-c2d3-7e4f-8a5b-6c7d8e9f0a1b",
  "jurisdiction": "AE"
}
200 OK — Allowed
{
  "allowed": true,
  "rule_version": "2026-06-01",
  "audit_ref": "AUD-9921849",
  "checks": {
    "calling_hours": "passed",
    "dnc_national": "passed",
    "dnc_tenant": "passed",
    "consent": "passed",
    "cli_ownership": "passed"
  }
}
200 OK — Blocked
{
  "allowed": false,
  "rule_version": "2026-06-01",
  "audit_ref": "AUD-9921850",
  "blocking_rule": "calling_hours",
  "blocking_reason": "Outside permitted calling hours for AE (09:00–18:00 Sun–Thu).",
  "checks": {
    "calling_hours": "failed",
    "dnc_national": "passed",
    "dnc_tenant": "passed",
    "consent": "passed",
    "cli_ownership": "passed"
  }
}
curl -X POST https://your-domain.com/v1/compliance/check \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{"phone":"+971501234567","jurisdiction":"AE"}'
GET /v1/dnc List DNC records

Returns the tenant's internal DNC list (numbers added manually or via agent disposition). National registry records are not listed here.

Request
Response
cURL

Optional: ?limit=50&cursor={cursor}

200 OK
{
  "items": [
    {
      "id": "01919fff-c2d3-7e4f-8a5b-444444444444",
      "phone": "+971509876543",
      "source": "agent_disposition",
      "reason": "Customer requested no contact",
      "added_by": "Layla Ahmed",
      "added_at": "2026-06-10T14:22:00Z"
    }
  ],
  "total": 342,
  "has_more": true
}
curl https://your-domain.com/v1/dnc?limit=50 \
  -H "Authorization: Bearer {token}"
POST /v1/dnc Add to DNC list

Add one or more phone numbers to the tenant's internal DNC list. Numbers are immediately excluded from all future campaigns. Supports bulk import of up to 5,000 numbers per request.

Request
Response
cURL
{
  "phones": ["+971509876543", "+97144123456"],
  "reason": "Customer opt-out via email"
}
201 Created
{
  "added": 2,
  "duplicates_skipped": 0,
  "invalid_phones": 0
}
curl -X POST https://your-domain.com/v1/dnc \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{"phones":["+971509876543"],"reason":"Customer opt-out"}'

Recordings

Access call recordings. All recordings are stored in MinIO object storage. Playback URLs are signed and expire after 1 hour for security.

GET /v1/recordings List recordings

Returns a paginated list of call recordings. Filter by campaign, agent, date range, or minimum duration.

Request
Response
cURL
Query parameters
ParameterTypeRequiredDescription
campaign_iduuidOptionalFilter to campaign
agent_iduuidOptionalFilter to agent
fromdatetimeOptionalStart date (ISO-8601 UTC)
todatetimeOptionalEnd date (ISO-8601 UTC)
min_durationintegerOptionalMinimum duration in seconds
limitintegerOptionalPage size (default: 50)
200 OK
{
  "items": [
    {
      "id": "01919eff-aaaa-7e4f-8a5b-333333333333",
      "call_id": "01919dee-c2d3-7e4f-8a5b-999999999999",
      "agent_id": "01919a1b-c2d3-7e4f-8a5b-6c7d8e9f0a1b",
      "agent_name": "Layla Ahmed",
      "campaign_id": "01919bcc-c2d3-7e4f-8a5b-6c7d8e9f0a1b",
      "contact_phone": "+971501234567",
      "duration_seconds": 248,
      "format": "mp3",
      "size_bytes": 1982464,
      "recorded_at": "2026-06-12T10:15:00Z"
    }
  ],
  "total": 1203,
  "has_more": true
}
curl "https://your-domain.com/v1/recordings?campaign_id=01919bcc-c2d3-7e4f-8a5b-6c7d8e9f0a1b&min_duration=60" \
  -H "Authorization: Bearer {token}"
GET /v1/recordings/{id}/play Get signed playback URL

Returns a pre-signed URL for streaming or downloading a recording. The URL expires after 1 hour. Never embed raw storage paths — always use this endpoint to get a fresh signed URL.

Request
Response
cURL

No body required. Pass recording ID in the URL.

200 OK
{
  "id": "01919eff-aaaa-7e4f-8a5b-333333333333",
  "url": "https://recordings.your-domain.com/01919eff.mp3?X-Amz-Expires=3600&X-Amz-Signature=abc123...",
  "expires_at": "2026-06-12T14:45:00Z",
  "format": "mp3",
  "duration_seconds": 248
}
curl https://your-domain.com/v1/recordings/01919eff-aaaa-7e4f-8a5b-333333333333/play \
  -H "Authorization: Bearer {token}"

Reports

Export campaign, agent, and call data in JSON, CSV, or PDF format.

POST /v1/reports/export Export report

Generate and download a report. For large exports (over 10,000 rows), a download URL is returned instead of inline data. The URL is valid for 30 minutes.

Request
Response
cURL
Body parameters
ParameterTypeRequiredDescription
report_typestringRequiredcall_detail | campaign_summary | agent_performance | disposition_summary
formatstringRequiredjson | csv | pdf
fromdatetimeRequiredReport start date (ISO-8601 UTC)
todatetimeRequiredReport end date (ISO-8601 UTC)
campaign_iduuidOptionalFilter to campaign
agent_idsarrayOptionalFilter to specific agents
Request body
{
  "report_type": "agent_performance",
  "format": "csv",
  "from": "2026-06-01T00:00:00Z",
  "to": "2026-06-12T23:59:59Z",
  "campaign_id": "01919bcc-c2d3-7e4f-8a5b-6c7d8e9f0a1b"
}
200 OK — Small report (inline)
{
  "report_type": "agent_performance",
  "format": "csv",
  "rows": 12,
  "data": "Agent Name,Calls,Connected,Talk Time (min),Avg Handle Time,Disposition Rate\nLayla Ahmed,42,28,156.2,223,66.7%\n..."
}
200 OK — Large report (download URL)
{
  "report_type": "call_detail",
  "format": "csv",
  "rows": 45210,
  "download_url": "https://exports.your-domain.com/report_2026-06-12_abcdef.csv?X-Amz-Signature=...",
  "expires_at": "2026-06-12T14:15:00Z"
}
curl -X POST https://your-domain.com/v1/reports/export \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "report_type": "agent_performance",
    "format": "csv",
    "from": "2026-06-01T00:00:00Z",
    "to": "2026-06-12T23:59:59Z"
  }'

Webhooks

BroadNet Engage sends real-time events to your configured endpoint. Register webhook URLs in Admin → Settings → Integrations.

Event Catalog

call.initiated call.answered call.completed call.abandoned call.compliance_blocked agent.state_changed campaign.started campaign.paused campaign.completed dnc.added

Payload structure

Every event shares the same envelope:

{
  "event_id": "01919fff-1111-7e4f-8a5b-555555555555",
  "event_type": "call.completed",
  "tenant_id": "01919a1b-0000-7e4f-8a5b-ffffffffffff",
  "occurred_at": "2026-06-12T13:50:22Z",
  "correlation_id": "01919dee-c2d3-7e4f-8a5b-999999999999",
  "schema_version": "1",
  "payload": {
    "call_id": "01919dee-c2d3-7e4f-8a5b-999999999999",
    "agent_id": "01919a1b-c2d3-7e4f-8a5b-6c7d8e9f0a1b",
    "campaign_id": "01919bcc-c2d3-7e4f-8a5b-6c7d8e9f0a1b",
    "contact_phone": "+971501234567",
    "status": "completed",
    "disposition": "sale",
    "duration_seconds": 248,
    "recording_id": "01919eff-aaaa-7e4f-8a5b-333333333333"
  }
}

Signature verification

Every webhook request includes an X-BroadNet-Signature header. Verify it to ensure the payload is authentic:

import hmac, hashlib

def verify_webhook(payload_bytes: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode(),
        payload_bytes,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(f"sha256={expected}", signature)

Retry policy

AttemptDelayTotal elapsed
1Immediate0s
230 seconds30s
35 minutes5m 30s
430 minutes35m 30s
52 hours2h 35m

Your endpoint must return a 2xx status within 10 seconds. Timeouts count as failures and trigger a retry.

Idempotency tip: Your webhook handler should deduplicate by event_id. Retried events have the same event_id.

SDKs & Libraries

Official SDKs are coming soon. In the meantime, use the REST API directly with any HTTP client.

Questions or issues? Contact api-support@broadnet.me or visit your support portal. Include your instance ID from error responses to speed up diagnosis.

© 2026 BroadNet Technologies. All rights reserved.  ·  API v1  ·  api-support@broadnet.me