Skip to main content

POST /calls/search

Search call history

A page of calls, newest first, for a time window you specify.

Scopereports:read
Role permissionCall reports
CostFree

A POST that reads. The filter is a nested object — include and exclude sets, four ranges, the AI fields — which does not survive a query string, and query strings are logged by every proxy in the path. This request creates nothing, changes nothing and is safe to retry.

start and end are required, unix seconds, at most 366 days apart. There is deliberately no default window: a client that forgot the field would otherwise receive one day of data and report it as the month.

Request body

FieldTypeRequiredDescription
startintegeryesStart of the window, unix seconds. Required.
endintegeryesEnd of the window, unix seconds. Required, must be after start, and at most 366 days later.
timeZonestringnoIANA zone, e.g. America/New_York. Decides where a day boundary falls for the day-of-week and day-of-month filters. Defaults to UTC, which shifts every daily figure — send it.
limitintegernoPage size. Defaults to 50, capped at 500.
offsetintegernoRows to skip.
directioninbound | outboundnoOmit for both.
filtersobjectnoEvery field is optional. Excludes are applied after includes.
filters.numbersstring[]noMatch these account numbers.
filters.numbersExcludestring[]no
filters.extensionsstring[]no
filters.extensionsExcludestring[]no
filters.queuesstring[]no
filters.queuesExcludestring[]no
filters.huntgroupsstring[]no
filters.huntgroupsExcludestring[]no
filters.usersstring[]no
filters.directionsinbound | outbound[]no
filters.statusesanswered | missed | voicemail[]noThe words, not the numeric codes.
filters.searchstringnoMatches caller and called names and numbers.
filters.durationobjectnoEither end may be omitted for an open bound. Durations are seconds; timeOfDay takes HH:MM.
filters.duration.startstringno
filters.duration.endstringno
filters.talkTimeobjectnoEither end may be omitted for an open bound. Durations are seconds; timeOfDay takes HH:MM.
filters.talkTime.startstringno
filters.talkTime.endstringno
filters.waitTimeobjectnoEither end may be omitted for an open bound. Durations are seconds; timeOfDay takes HH:MM.
filters.waitTime.startstringno
filters.waitTime.endstringno
filters.timeOfDayobjectnoEither end may be omitted for an open bound. Durations are seconds; timeOfDay takes HH:MM.
filters.timeOfDay.startstringno
filters.timeOfDay.endstringno
filters.callPurposestring[]noPer-account vocabulary, not a fixed enum. A value your account does not use matches nothing — that is not an error, and not a typo we can catch for you.
filters.callSentimentstring[]noPer-account vocabulary, as callPurpose.
sortobjectno
sort.fieldstringnoA response field name, e.g. startTime.
sort.directionasc | descno

Everything in a time window:

{
"start": 1754006400,
"end": 1754092800,
"timeZone": "America/New_York",
"limit": 50
}

Inbound calls nobody answered:

{
"start": 1754006400,
"end": 1754092800,
"timeZone": "America/New_York",
"direction": "inbound",
"filters": {
"statuses": [
"missed"
]
}
}

One queue, filtered on the AI purpose field:

{
"start": 1754006400,
"end": 1754092800,
"filters": {
"queues": [
"501"
],
"callPurpose": [
"billing"
]
},
"sort": {
"field": "startTime",
"direction": "desc"
}
}

Responses

200

A page of matching calls.

FieldTypeDescription
callsobject[]
calls[].idstringThe call id — the same value in call webhooks and the recordings endpoints. Keep it a string: it looks like a decimal number and is not one, and parsing it as a float destroys the low digits.
calls[].directioninbound | outbound
calls[].statusanswered | missed | voicemail | unknownThe word rather than the switch's integer.
calls[].statusCodeintegerThe raw code, because status flattens a distinction some clients need: -1 missed, 0 voicemail, 1 answered, and anything above 1 means answered by that many people — a call that rang a group and was picked up after a transfer. All of those report answered.
calls[].callerTypestringexternal, user, queue, huntgroup. Read this before assuming a number field holds a phone number — on an internal leg it is an extension.
calls[].callerNamestring
calls[].callerNumberstring
calls[].calledTypestring
calls[].calledNamestring
calls[].calledNumberstring
calls[].dialledNumberstringWhat was actually dialled — for an inbound call, your DID.
calls[].callerIdInternalstring
calls[].callerIdExternalstring
calls[].startTimestring (RFC 3339)RFC 3339 UTC.
calls[].endTimestring (RFC 3339)Omitted for a call with no recorded end.
calls[].durationSecondsintegerIncludes ringing.
calls[].talkTimeSecondsintegerConnected time only. On a missed call this is 0 and durationSeconds is how long it rang.
calls[].transferredintegerA count, not a boolean — a call can be transferred more than once.
calls[].abandonedbooleanThe caller hung up before anyone answered. This is what separates "we missed it" from "they gave up" — reporting on missed calls without it blames agents for calls nobody could have taken.
calls[].recordedbooleanAudio was captured. Fetch it with GET /calls/{callid}/recordings — this stays true for a call whose recording was since deleted, so it means "was recorded", not "can be played".
calls[].calledBackstringThe id of the call that returned this one, when an abandoned call was called back.
calls[].repeatedintegerEarlier calls from the same caller within the window.
totalintegerHow many calls match the filter across all pages — what a "1–50 of 812" control needs. Not the size of this page.
limitintegerThe page size actually applied, after defaulting and capping. Send limit: 10000 and this comes back 500, so a short page is explained rather than mysterious.
offsetinteger
{
"data": {
"total": 812,
"limit": 50,
"offset": 0,
"calls": [
{
"id": "1754489201.884213",
"direction": "inbound",
"status": "answered",
"statusCode": 1,
"callerType": "external",
"callerName": "ACME ROOFING",
"callerNumber": "13475550123",
"calledType": "user",
"calledName": "Dana Spencer",
"calledNumber": "204",
"dialledNumber": "12125550188",
"startTime": "2026-08-11T13:20:01Z",
"endTime": "2026-08-11T13:24:00Z",
"durationSeconds": 239,
"talkTimeSeconds": 220,
"transferred": 0,
"abandoned": false,
"recorded": true
}
]
}
}

Errors

StatusMeaning
400invalid_request — a missing or malformed field, an unknown enum value, or an unknown field in the body. Nothing was charged.
401unauthenticated — no credential, or one that is invalid, revoked or expired. The WWW-Authenticate header names the scope the endpoint wanted.
403Two different failures share this status, and the type tells them apart:
500internal_error — something failed on our side. For a send, nothing was charged, guaranteed, which is what makes a retry safe.

See Errors for the full catalog and what to do about each.

Example

curl -X POST 'https://api.account.telebroad.com/api/public/v1/calls/search' \
-H "Authorization: Bearer $TB_KEY" \
-H 'Content-Type: application/json' \
-d '{"start":1754006400,"end":1754092800,"timeZone":"America/New_York","limit":50}'

Try it in the playground →