Navigation
CX-Engine API · 20 min read

Smart Routings

Shared concepts of the Smart Routings API, plus the inbound rules (call queues), groups, time spans, exceptions, import/export, inbound rule lookup and CFD token endpoints.

Smart Routings decide where an incoming call goes, based on inbound rules (call queues), opening hours, exceptions, geography, CTI keys or contacts. For a functional overview, see the Smart Routings documentation.

This page covers the concepts shared by every Smart Routings endpoint, then the inbound rule endpoints. The other resources have their own pages:

Page Resources
CTIs CTIs and their destinations, CTI lookups
Contacts Routing contacts and contact fields
Geo Routing Geographic models, lists and destinations, geographic lookup
Surveys Surveys and survey records

Headers: Authorization: Bearer {workspace-token}


#Selecting the customer account

Smart Routings data belongs to a customer account. Every Smart Routings endpoint (and the other customer-scoped endpoints such as integration keys or call history) needs to know which customer it acts on. The customer is not deduced from the token: pass it in the request.

Parameter Description
customer_account Customer account code (preferred)
customer_id Customer ID, used when customer_account is absent
account_id Alias of customer_id

The parameter is usually sent in the query string (?customer_account=ACME01); it is also read from the body.

The request is rejected with 403 when:

  • no customer matches the parameters (No valid account specified.);
  • the authenticated user does not belong to that customer (You do not have access to this account.);
  • the OAuth client is restricted to another customer (This credential is not authorized for this account.).

Use GET /api/customers/resolve to check which customer a credential and a customer_account resolve to.


#Scopes and authentication

Scope Gives access to
client Every Smart Routings endpoint
cfd Lookup endpoints (inbound rules, geographic, CTI) and survey recording only

Call flows usually authenticate with a CFD token, exchanged for a short cfd-scoped access token. Any OAuth token with the client scope also works on the lookups (see Authentication).


#Permissions

Permissions are checked against the authenticated user's role. The workspace owner and users with the superadmin role have every permission.

Resource Permissions
Inbound rules call-queue.view, call-queue.create, call-queue.edit, call-queue.delete, call-queue.lookup
Groups call-queue-group.view, call-queue-group.create, call-queue-group.edit, call-queue-group.delete
Time spans and exceptions Same permissions as their parent: view to read, edit to create, change or delete
CFD tokens cfd-token.view, cfd-token.create

#Conventions

  • Write endpoints expect a JSON body (Content-Type: application/json), except the import endpoint (multipart).
  • Lists are paginated (see Pagination) and accept filter[...], sort and include parameters as documented per endpoint. Text filters match partial values.
  • Validation errors return 400 with a hint array:
{
  "response": "bad request: please check body parameters.",
  "hint": ["The name field is required."]
}
  • Business rule errors return a message:
{ "message": "The provided time span conflicts with another span for this entity." }
  • Destination types, used by inbound rules, time spans and exceptions: extension, voicemail, call_queue, end_call, external_number, other.
  • Local time. Time spans and exceptions are evaluated in the customer's timezone (the timezone setting, see PUT /api/customers/settings).

#Inbound rules (call queues)

An inbound rule describes a 3CX queue or a DID number, its default destination, and optionally the group it inherits time spans and exceptions from.

#The inbound rule object

{
  "id": 5,
  "customer_id": 12,
  "call_queue_group_id": 2,
  "active": true,
  "name": "Support",
  "host_name": "acme.3cx.eu",
  "type": "queue",
  "code": "SUP",
  "number": "800",
  "did_number": null,
  "default_destination_type": "voicemail",
  "default_destination": "800",
  "created_at": "2025-01-15T09:30:00.000000Z",
  "updated_at": "2025-01-20T16:02:11.000000Z",
  "group": { "id": 2, "name": "Business hours" }
}
Field Description
type queue (identified by number) or did (identified by did_number)
host_name 3CX host name the queue lives on
code Free code, max 50 characters
call_queue_group_id Group the rule belongs to, or null

#GET /api/smart-routings/call-queues/queues

Returns the inbound rules of the customer, most recently updated first.

Query parameters

Parameter Description
customer_account Required. Customer account code
filter[call_queue_group_id] Exact group ID
filter[active] Exact value, 1 or 0
filter[type] queue or did
filter[name], filter[code], filter[number], filter[did_number], filter[host_name] Partial match
filter[search] Partial match on name, code, number or DID number
include customer, exceptions, timeSpans
sort id, created_at, updated_at, name, code, type, call_queue_group_id, resource (number or DID number). Prefix with - for descending. Default: -updated_at
per_page Results per page (default: 20)

Response 200 — paginated list of inbound rule objects


#GET /api/smart-routings/call-queues/queues/{id}

Returns an inbound rule with its group, exceptions and time_spans.

Response 200 — inbound rule object


#POST /api/smart-routings/call-queues/queues

Creates an inbound rule for the customer. When call_queue_group_id is set, the group's time spans and exceptions are copied to the new rule.

Request body

Field Type Required Description
name string yes Name, max 50 characters
type string no queue or did
number string no Queue number, max 50 characters
did_number string no DID number, max 255 characters
host_name string no 3CX host name
code string no Free code, max 50 characters
active boolean no Whether the rule is active
call_queue_group_id integer no ID of an existing group
default_destination_type string no One of the destination types
default_destination string no Default destination, max 255 characters

Response 201 — the created inbound rule object


#PUT /api/smart-routings/call-queues/queues/{id}

Updates an inbound rule. Accepts the same fields as the creation, all optional.

Moving the rule to another group replaces its time spans and exceptions with copies of the new group's. Setting call_queue_group_id to null detaches it: the copies become the rule's own, editable rows.

Response 201 — the updated inbound rule object


#DELETE /api/smart-routings/call-queues/queues/{id}

Deletes an inbound rule with its time spans and exceptions.

Response 200

{ "response": "element deleted" }

#PATCH /api/smart-routings/call-queues/queues/bulk

Updates several inbound rules of the customer at once. Only the fields present in the body are changed. Requires call-queue.edit.

Request body

Field Type Required Description
ids integer[] yes IDs of the rules to update
active boolean no Activate or deactivate
default_destination_type string no One of the destination types
default_destination string no Default destination
{ "ids": [5, 6, 9], "default_destination_type": "voicemail", "default_destination": "800" }

Response 200 — array of the updated inbound rule objects


#DELETE /api/smart-routings/call-queues/queues/bulk

Deletes several inbound rules of the customer, along with their time spans and exceptions. Requires call-queue.delete. Rules of another customer are ignored.

Request body

Field Type Required Description
ids integer[] yes IDs of the rules to delete

Response 200

{ "response": "elements deleted" }

#Groups

A group shares its time spans and exceptions with every inbound rule it contains. The rules receive locked copies: only their destination and destination_type can be changed on the rule itself.

#The group object

{
  "id": 2,
  "customer_id": 12,
  "name": "Business hours",
  "created_at": "2025-01-10T08:00:00.000000Z",
  "updated_at": "2025-01-10T08:00:00.000000Z",
  "queues_count": 4
}

queues_count is only present in lists.


#GET /api/smart-routings/call-queues/groups

Returns the groups of the customer, most recently updated first.

Query parameters

Parameter Description
customer_account Required. Customer account code
filter[name] Partial match
include queues, customer, exceptions, timeSpans
sort id, created_at, updated_at, name. Default: -updated_at
per_page Results per page (default: 20)

Response 200 — paginated list of group objects


#GET /api/smart-routings/call-queues/groups/{id}

Returns a group with its queues, exceptions and time_spans.

Response 200 — group object


#POST /api/smart-routings/call-queues/groups

Request body

Field Type Required Description
name string yes Name, max 50 characters

Response 201 — the created group object


#PUT /api/smart-routings/call-queues/groups/{id}

Request body

Field Type Required Description
name string no Name, max 50 characters

Response 201 — the updated group object


#DELETE /api/smart-routings/call-queues/groups/{id}

Deletes a group. Its inbound rules are kept and detached from the group; the copies they had inherited become their own rows.

Response 200

{ "response": "element deleted" }

#Time spans

A time span routes calls to a destination during an opening period, either every week (day_of_week) or on a specific date (date_full). A time span belongs to an inbound rule or to a group: every time span endpoint that lists or creates spans identifies that parent with two query parameters.

Parameter Description
time_spanable_type call_queue (inbound rule) or call_queue_group
time_spanable_id ID of the inbound rule or group

An inbound rule that belongs to a group cannot have its own time spans: create them on the group (422).

#The time span object

{
  "id": 31,
  "source_time_span_id": null,
  "imported_at": null,
  "time_spanable_id": 5,
  "time_spanable_type": "call_queue",
  "reference": "morning",
  "destination": "800",
  "destination_type": "call_queue",
  "day_of_week": 1,
  "date_full": null,
  "start_time": "08:30:00",
  "end_time": "12:30:00",
  "created_at": "2025-01-15T09:35:00.000000Z",
  "updated_at": "2025-01-15T09:35:00.000000Z"
}
Field Description
day_of_week 1 (Monday) to 7 (Sunday)
date_full Specific date (YYYY-MM-DD). Takes priority over weekly spans on that day
source_time_span_id Set on copies inherited from a group (locked rows)
imported_at Set on spans created by an import

#GET /api/smart-routings/call-queues/time-spans

Query parameters

Parameter Description
customer_account Required. Customer account code
time_spanable_type, time_spanable_id Required. Parent of the spans
filter[reference], filter[destination], filter[day_of_week], filter[date_full], filter[start_time], filter[end_time] Filters
sort id, created_at, updated_at. Default: -updated_at
per_page Results per page (default: 20)

Response 200 — paginated list of time span objects


#GET /api/smart-routings/call-queues/time-spans/{id}

Returns a time span with its parent (time_spanable).

Response 200 — time span object


#POST /api/smart-routings/call-queues/time-spans

Creates a time span on the parent given in the query string. Time spans created on a group are copied to every inbound rule of the group.

Request body

Field Type Required Description
start_time string yes Start time, HH:MM:SS
end_time string yes End time, HH:MM:SS, after start_time
day_of_week integer yes* 1 (Monday) to 7 (Sunday)
date_full string yes* Specific date, YYYY-MM-DD
destination_type string no One of the destination types
destination string no Destination, max 50 characters
reference string no Free reference, max 50 characters

*Provide day_of_week or date_full. If both are sent, day_of_week is ignored.

Response 201 — the created time span object

Response 422 — the span overlaps another span of the same parent on the same day


#PUT /api/smart-routings/call-queues/time-spans/{id}

Updates a time span. Accepts the same fields, all optional. On a locked span (inherited from a group), only destination and destination_type can be sent (422 otherwise).

Response 201 — the updated time span object


#DELETE /api/smart-routings/call-queues/time-spans/{id}

Deletes a time span. Locked spans cannot be deleted (403): delete them on the group.

Response 200

{ "response": "element deleted" }

#POST /api/smart-routings/call-queues/time-spans/bulk

Creates the same opening period on several days at once: one span per value of day_of_week and of date_full. Requires the time_spanable_type and time_spanable_id query parameters.

Request body

Field Type Required Description
day_of_week integer[] yes* Days of the week, 1 to 7
date_full string[] yes* Dates, YYYY-MM-DD
start_time string yes Start time, HH:MM:SS
end_time string yes End time, HH:MM:SS
destination_type string no One of the destination types
destination string no Destination
reference string no Free reference

*Provide day_of_week, date_full, or both.

{
  "day_of_week": [1, 2, 3, 4, 5],
  "start_time": "08:30:00",
  "end_time": "12:30:00",
  "destination_type": "call_queue",
  "destination": "800"
}

Response 201 — array of all the time spans of the parent

Response 422 — one of the spans conflicts with an existing span


#PATCH /api/smart-routings/call-queues/time-spans/bulk

Changes the destination of several time spans of the parent. Works on locked spans.

Request body

Field Type Required Description
ids integer[] yes IDs of the time spans
destination_type string no One of the destination types
destination string no Destination

Response 200 — array of the updated time span objects


#DELETE /api/smart-routings/call-queues/time-spans/bulk

Deletes several time spans of the parent.

Request body

Field Type Required Description
ids integer[] no IDs of the time spans to delete

Without ids, every time span of the parent is deleted.

Response 200

{ "response": "elements deleted" }

Response 403 — one of the targeted spans is locked (inherited from a group)


#Exceptions

An exception overrides the time spans on a given day (public holiday, closure…). Exceptions take priority over time spans. Like time spans, they belong to an inbound rule or a group, identified with two query parameters on the list, create and bulk endpoints.

Parameter Description
exceptionable_type call_queue (inbound rule) or call_queue_group
exceptionable_id ID of the inbound rule or group

An inbound rule that belongs to a group cannot have its own exceptions: create them on the group (422).

#The exception object

{
  "id": 77,
  "source_exception_id": null,
  "exceptionable_id": 2,
  "exceptionable_type": "call_queue_group",
  "reference": "France:christmasDay",
  "label": "Christmas",
  "destination": "800",
  "destination_type": "voicemail",
  "day": "2026-12-25",
  "created_at": "2026-01-05T10:00:00.000000Z",
  "updated_at": "2026-01-05T10:00:00.000000Z"
}

source_exception_id is set on locked copies inherited from a group.


#GET /api/smart-routings/call-queues/exceptions

Query parameters

Parameter Description
customer_account Required. Customer account code
exceptionable_type, exceptionable_id Required. Parent of the exceptions
filter[reference], filter[destination], filter[day] Filters
sort id, created_at, updated_at, day. Default: day
per_page Results per page (default: 20)

Response 200 — paginated list of exception objects


#GET /api/smart-routings/call-queues/exceptions/{id}

Returns an exception with its parent (exceptionable).

Response 200 — exception object


#POST /api/smart-routings/call-queues/exceptions

Request body

Field Type Required Description
day string yes Date, YYYY-MM-DD
label string no Label, max 50 characters
reference string no Reference, max 50 characters, unique per parent
destination_type string no One of the destination types
destination string no Destination, max 50 characters

Response 201 — the created exception object

Response 400 — the reference already exists, or another exception exists on that day


#PUT /api/smart-routings/call-queues/exceptions/{id}

Updates an exception. Accepts the same fields, all optional. On a locked exception, only destination and destination_type can be sent (422 otherwise).

Response 201 — the updated exception object


#DELETE /api/smart-routings/call-queues/exceptions/{id}

Deletes an exception. Locked exceptions cannot be deleted (403).

Response 200

{ "response": "element deleted" }

#PATCH /api/smart-routings/call-queues/exceptions/bulk

Changes the destination of several exceptions of the parent. Works on locked exceptions.

Request body

Field Type Required Description
ids integer[] yes IDs of the exceptions
destination_type string no One of the destination types
destination string no Destination

Response 200 — array of the updated exception objects


#DELETE /api/smart-routings/call-queues/exceptions/bulk

Deletes several exceptions of the parent.

Request body

Field Type Required Description
ids integer[] yes IDs of the exceptions to delete

Response 200

{ "response": "elements deleted" }

Response 403 — one of the targeted exceptions is locked


#GET /api/smart-routings/call-queues/exceptions/holidays

Lists the countries available for public holiday import.

Response 200

{ "countries": ["Andorra", "Argentina", "Australia", "Austria", "Belgium", "...", "France", "..."] }

#POST /api/smart-routings/call-queues/exceptions/holidays

Creates one exception per public holiday of a country on the parent given by exceptionable_type and exceptionable_id. Without year, the holidays falling within the next 12 months (starting today, possibly spanning two calendar years) are created; with year, those of that calendar year. Each exception gets the holiday name as label and {country}:{holiday} as reference (e.g. France:christmasDay).

A holiday is skipped when the parent already has an exception on that day, so the request can be repeated without creating duplicates.

Request body

Field Type Required Description
country string yes Country name as returned by GET …/exceptions/holidays (e.g. France)
year integer no Year (4 digits). Default: the next 12 months
destination_type string no Destination type applied to every holiday
destination string no Destination applied to every holiday

Response 201 — array of the created exception objects (empty when every holiday already had an exception)

Response 422 — unknown country or invalid year


#Import and export

The inbound rules of a customer can be exported to and imported from a spreadsheet. The file has one row per time span; inbound rules without time spans have a single row.

Row Content
1 Section labels: Call queue, Time span
2 Column names
3+ Data
Column Description
name Rule name
number Queue number, or DID number for did rules
type queue or did
host_name 3CX host name, must be one of the customer's hosts
group_name Group name; created if it does not exist
active 1 or 0
code Free code
default_destination_type, default_destination Default destination
day_of_week 1 (Monday) to 7 (Sunday)
full_date Specific date, YYYY-MM-DD
start, end Times, HH:MM:SS
destination_type, destination Destination of the time span
reference Time span reference

#GET /api/smart-routings/call-queues/export

Downloads the customer's inbound rules as inbound_rules.xlsx. Requires call-queue.view.

Response 200 — Excel file


#POST /api/smart-routings/call-queues/import

Imports inbound rules from an .xlsx or .csv file (CSV: ; delimiter, " enclosure). Requires call-queue.create. Send the request as multipart/form-data.

Rules are matched on type and number (or DID number): existing rules are updated, others are created. The whole file is validated first and imported in a single transaction: if one row is invalid, nothing is imported.

Form fields

Field Type Required Description
document file yes .xlsx or .csv file
replace_all boolean no Also delete the customer's rules missing from the file, and groups left empty

Response 200

{ "response": "Import successful." }

Response 422 — invalid rows

{
  "response": "The import failed validation.",
  "failures": [
    {
      "row": 4,
      "attribute": "host_name",
      "errors": ["The selected host_name is invalid."],
      "values": { "name": "Support", "number": "800", "type": "queue", "host_name": "unknown.3cx.eu" }
    }
  ]
}

#Inbound rule lookup

#GET /api/smart-routings/call-queues/lookup

Returns the destination of an inbound rule at a given moment. This is the endpoint a call flow calls when a call comes in. Accepts the client or cfd scope and requires call-queue.lookup.

The destination is chosen in this order:

  1. an exception on that day;
  2. a time span on that specific date (date_full) covering the time;
  3. a weekly time span (day_of_week) covering the time.

Only active rules are considered. Day and time are evaluated in the customer's timezone.

Query parameters

Parameter Type Required Description
customer_account string yes* Customer account code
customer_id integer yes* Customer ID
queue_id integer yes** Inbound rule ID
queue_number string yes** Queue number
queue_name string yes** Rule name
day string no Day to evaluate, YYYY-MM-DD. Default: today
time string no Time to evaluate, HH:MM:SS. Default: now

*Provide customer_account or customer_id. **Provide one of queue_id, queue_number or queue_name (checked in that order).

Response 200

{
  "reference": "morning",
  "destination": "800",
  "label": null,
  "inherited": true,
  "matched": "time_span,day_of_week"
}
Field Description
reference Reference of the matched exception or time span
destination Destination to route the call to
label Exception label (null for time spans)
inherited true when the match comes from the rule's group
matched exception, time_span,date_full or time_span,day_of_week

Response 404

{ "message": "No destination found matching given parameters." }

Other 404 messages: No customer found matching given parameters., No queue found matching given parameters.. When nothing matches, apply the rule's default destination in your call flow.

Response 400 — invalid parameters, or invalid day/time


#CFD tokens

A CFD token is a long-lived secret designed for call flows (3CX Call Flow Designer). It is attached to a user and exchanged for a short access token with the cfd scope, which only gives access to the lookup endpoints and to survey recording.

CFD tokens are workspace-level: they are not filtered by customer, but the endpoints still require customer_account.

#The CFD token object

{
  "id": 3,
  "user_id": 8,
  "name": "Main call flow",
  "token": "q8V3...80 characters...Xz",
  "hosts": ["203.0.113.10"],
  "expires_at": "2027-01-01T00:00:00.000000Z",
  "created_at": "2026-01-05T10:00:00.000000Z",
  "updated_at": "2026-01-05T10:00:00.000000Z",
  "expired": false,
  "user": { "id": 8, "name": "Call flow bot", "group": "admin", "email": "cfd@acme.com" }
}

The token value is returned by every CFD token endpoint. Treat it as a password.

Field Description
hosts IP addresses allowed to exchange the token. null or ["*"] allows any address
expires_at Expiry date, or null for no expiry
expired true once expires_at is in the past. Always false when expires_at is null

The list endpoint does not return hosts.


#GET /api/smart-routings/cfd-tokens

Requires cfd-token.view.

Query parameters

Parameter Description
customer_account Required. Customer account code
filter[name] Partial match
include user (always loaded)
sort id, name, expires_at, created_at, updated_at. Default: -updated_at
per_page Results per page (default: 20)

Response 200 — paginated list of CFD token objects


#GET /api/smart-routings/cfd-tokens/{id}

Requires cfd-token.view. Non-admin users can only read their own tokens.

Response 200 — CFD token object


#POST /api/smart-routings/cfd-tokens

Creates a CFD token. The token value is generated (80 characters). Requires cfd-token.create.

Request body

Field Type Required Description
user_id integer yes User the token authenticates as
name string no Name, max 50 characters
hosts string[] no Allowed IP addresses
expires_at string no Expiry date, YYYY-MM-DD. The token expires at 00:00 UTC on that date. Omit it for a token that never expires

Response 201 — the created CFD token object


#PUT /api/smart-routings/cfd-tokens/{id}

Updates a CFD token (user_id, name, hosts, expires_at). The token value cannot be changed. Allowed to admin users and to the token's owner.

Response 201 — the updated CFD token object


#DELETE /api/smart-routings/cfd-tokens/{id}

Allowed to admin users and to the token's owner.

Response 200

{ "response": "element deleted" }

#GET /api/smart-routings/cfd-auth/t/{token}

Exchanges a CFD token for an access token with the cfd scope. No Authorization header is needed.

Response 200

{
  "message": "success",
  "token": "eyJ0eXAiOiJKV1QiLCJhbGciOi...",
  "expires_at": "2026-10-09T11:42:00+00:00",
  "user": { "id": 8, "name": "Call flow bot", "email": "cfd@acme.com", ... }
}

Use the returned token as the Bearer token of the lookup calls. It is valid for 1 hour (expires_at): exchange the CFD token again on each call rather than storing the access token.

Response 403

{ "message": "Host not authorised." }

Response 404 — unknown token, expired token, or its user no longer exists

{ "message": "This token does not exists or has expired." }

The expiry is checked on every exchange: once expires_at is past, the CFD token can no longer be exchanged for an access token. A token without expires_at never expires. The check in the controller:

if (! $cfdToken || $cfdToken->expired) {
    return response()->json([
        'message' => 'This token does not exists or has expired.',
    ], 404);
}

Expiring or deleting a CFD token also revokes the access tokens it issued: the lookups made with them answer 401 straight away.