Endpoint Reference
This is the per‑endpoint reference for the read‑only surface of the Native Focus REST API. Every call below requires a bearer token from POST /authWithApiKey — see Getting Started → Authentication.
Only GET and search‑style POST endpoints are listed. Write, update and delete operations exist on the API but are not documented publicly. Contact support if your use case needs one.
All paths are relative to your tenant's {{baseUrl}} (see Base URLs). All authenticated calls require:
Authorization: Bearer YOUR.BEARER.TOKEN
Content-Type: application/json
Authentication
Authenticate with API key
POST /authWithApiKey
Exchange an API key for a short‑lived bearer JWT.
Body
{
"clientId": 1234567,
"apiKey": "YOUR-API-KEY-XYZ",
"username": "YOUR-INTEGRATION-USERNAME"
}
| Field | Type | Required | Notes |
|---|---|---|---|
clientId | integer | yes | Your Focus account id. |
apiKey | string | yes | API key generated in the admin portal. |
username | string | yes | Auto‑generated integration username shown next to the API key in admin. The token inherits this user's roles & rights. |
Response
{
"token": "eyJhbGciOi...",
"refreshToken": "eyJhbGciOi..."
}
When token expires, call this endpoint again with the same body to get a fresh one.
Recordings
The "recording" resource represents a single communication — a phone call, Teams meeting, SMS message, etc. Most endpoints accept either the numeric interactionId (a.k.a. recordingId) or the source‑system thirdPartyId depending on what they do.
Search recordings
POST /client/recording.search
Search and filter recordings. This is the workhorse endpoint for any integration that needs to find calls.
Required right: Recording.View
Body — every field is optional; combine them to narrow your result set.
| Field | Type | Notes |
|---|---|---|
recordingIds | { include?: number[], exclude?: number[] } | Filter by internal ids. |
thirdPartyIds | { include?: string[], exclude?: string[] } | Filter by source ids. |
endpointIds | { include?, exclude? } | Filter by endpoint (phone/email/Teams id) record. |
personIds | { include?, exclude? } | Filter by person record. |
userIds | { include?, exclude? } | Calls owned by these Focus users. |
dateTimeRange | { from: ISO8601, to: ISO8601 } | Most common filter; use a tight window when paging. |
durationRangeInSeconds | { min?: number, max?: number } | |
timeRanges | { include?, exclude? } | Time‑of‑day filters. |
communicationsBetween | object | Filter to interactions between two specific endpoint sets. |
freeText | string | Searches transcript / metadata. |
communicationTypes | { include?: string[], exclude?: string[] } | e.g. phone, teams, sms. |
direction | "In" | "Out" | |
tagIds | { include?, exclude? } | |
isPrivate | boolean | |
hasComments | boolean | |
legalHoldId | integer | Restrict to a legal hold. |
playlistId | integer | Restrict to a playlist. |
startTimeDirection | "ASC" | "DESC" | Sort direction. |
page, pageSize | number | Pagination. |
Example request
curl -X POST "$BASE_URL/client/recording.search" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"dateTimeRange": { "from": "2026-05-12T00:00:00Z", "to": "2026-05-13T00:00:00Z" },
"communicationTypes": { "include": ["phone", "teams"] },
"direction": "In",
"startTimeDirection": "DESC",
"page": 1,
"pageSize": 100
}'
Example response (abridged)
{
"recordings": [
{
"interactionId": 9876543,
"thirdPartyId": "6414aa9f-ZZZAAABBB",
"startDatetimeUTC": "2026-05-12T10:30:00.000Z",
"endDatetimeUTC": "2026-05-12T10:34:21.482Z",
"durationInSeconds": 261.482,
"type": "phone",
"endpoints": [
{ "endpoint": { "id": 81, "type": "phone", "endpoint": "447896423595" } }
]
}
],
"total": 4321,
"page": 1,
"pageSize": 100
}
Full example response
Fields below match the Interaction shape returned by the API. The exact contents of analysis, tags, dataGroups and optional depend on what your account has configured; the values shown are illustrative.
{
"recordings": [
{
"interactionId": 9876543,
"thirdPartyId": "6414aa9f-ZZZAAABBB",
"startDatetimeUTC": "2026-05-12T10:30:00.000Z",
"endDatetimeUTC": "2026-05-12T10:34:21.482Z",
"durationInSeconds": 261.482,
"type": "phone",
"source": "teams",
"commentsCount": 1,
"transcriptSnippet": "The call focused on resetting the customer's online banking credentials…",
"analysis": ["Customer Service"],
"tags": ["verified_id"],
"conversationPhases": ["greeting", "resolution", "closing"],
"isPrivate": false,
"isPrivateReason": "",
"mediaAccessStatus": "Permitted",
"retentionGroupId": 12,
"retainUntil": "2033-05-12T10:34:21.482Z",
"legalHolds": [],
"dataGroups": { "Department": "Support" },
"optional": {},
"endpoints": [
{
"isOriginator": true,
"isRecordingSubject": true,
"user": "jane.smith@example.com",
"endpoint": { "id": 81, "type": "phone", "endpoint": "447896423595" },
"person": { "id": 42, "fullName": "Jane Smith", "email": "jane.smith@example.com" }
},
{
"isOriginator": false,
"isRecordingSubject": false,
"user": "",
"endpoint": { "id": 9123, "type": "phone", "endpoint": "447700900111" },
"person": { "id": null, "fullName": "", "email": null }
}
]
}
],
"total": 4321,
"page": 1,
"pageSize": 100,
"recordingsSearchToken": "st_01HQ1WHV01BVANN5NW4PG3N573"
}
Get recording detail
POST /client/recording.detail.get
Fetch the full detail blob for one or more recordings (metadata, endpoints, owners, retention info, encryption metadata).
Required right: Recording.View
Body
{ "interactionIds": [9876543, 9876544] }
Returns an array of detail objects keyed on interactionId. Each entry carries the underlying metadata, audio waveForm summary and encryptionDetail blocks for the recording. Use this after recording.search when you need raw metadata that isn't included in the search projection.
Full example response
The metadata, waveForm and encryptionDetail blocks are passthroughs from the underlying recording pipeline — the exact contents vary by communication source (Teams, telephony, SMS, etc.). The example below is illustrative.
[
{
"interactionId": 9876543,
"metadata": {
"accountGuid": "d6b8…",
"recorder": "teams-bot",
"region": "uk",
"sourceFileDetail": {
"mp3": { "fileSize": 4189312 },
"wav": { "fileSize": 41893120 }
}
},
"waveForm": {
"sampleRate": 8,
"peaks": [0.01, 0.42, 0.55, 0.31, 0.18, 0.0]
},
"encryptionDetail": {
"algorithm": "AES-256-GCM",
"keyId": "kek-2026-05"
}
}
]
Get transcription
POST /client/recording.transcription.get
Fetch transcription segments for one or more recordings.
Required right: Recording.Transcription.View
Body
{ "interactionIds": [9876543] }
The response is an array of { interactionId, analysis } objects. The analysis field is the raw output written by the transcription engine for that interaction — typically a transcript containing time‑aligned segments plus a detected language. An empty or null analysis means transcription is disabled, hasn't run yet, or isn't available for that media type.
Full example response
Real payload taken from a call processed by the Focus transcription pipeline. Segment timings are in seconds (floating point); speaker diarisation is not currently included in the public response.
[
{
"interactionId": 9876543,
"analysis": {
"transcript": {
"language": "en",
"segments": [
{
"text": " Hello. Thank you for holding me on. How can I help? Hi, yeah. I want to know what the progress was with my fellow installation…",
"start": 2.585,
"end": 28.933
},
{
"text": " Perfect, it stays like its waiting an install date, so what I can do is Ill pop you over to the install team…",
"start": 30.435,
"end": 45.111
}
]
}
}
}
]
Get SMS message
POST /client/recording.sms-message.get
Fetch the message body for SMS interactions.
Required right: Recording.View
Body
{ "interactionIds": [9876543] }
Use this for type: "sms" interactions where the "audio" is actually a text payload. The response projects the SMS body and any underlying provider message ids out of the recording's metadata.
Full example response
[
{
"interactionId": 9876543,
"message": "Hi — your appointment is confirmed for 14 May at 10:30. Reply STOP to opt out.",
"rawSmsIds": ["sms-7c2f-001"]
}
]
Download audio
POST /client/recording.playBackByThirdPartyId
Stream or download the audio (or video) for a recording. Returns a binary stream — pipe to a file.
Required right: Recording.Play
Body
| Field | Type | Required | Notes |
|---|---|---|---|
thirdPartyId | string | yes | Source‑system id taken from a recording.search result. |
format | string | no | One of mp3 (default), mp3-mono, wav, wav-mono, raw, mp4. |
streamThrough | boolean | no | Stream as the file is decrypted instead of buffering. |
comment | string | conditional | Required when the account has forced comments enabled. |
Example
curl -X POST "$BASE_URL/client/recording.playBackByThirdPartyId" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "thirdPartyId": "abc-123-call-id", "format": "mp3" }' \
--output recording.mp3
Returns 404 if the thirdPartyId is unknown or not visible to the calling user.
Response details
The response is a binary stream, not JSON. Useful response headers:
HTTP/1.1 200 OK
Content-Type: audio/mpeg
Content-Disposition: attachment; filename="6414aa9f-ZZZAAABBB.mp3"
Content-Length: 4189312
Content-Type follows the requested format (audio/mpeg for mp3/mp3-mono, audio/wav for wav/wav-mono, video/mp4 for mp4, application/octet-stream for raw).
Get recording audit log
POST /client/recording/audit
Read the access audit log for a single recording — every play, download or detail view, with user and timestamp.
Required right: Recording.Audit.View
Body
{ "recordingId": 9876543, "page": 1, "pageSize": 100 }
Use this to evidence who has accessed a particular interaction (e.g. for a regulator request). The response is an array of audit rows; each row describes one HTTP call against this recording.
Full example response
Fields below match the AuditSearchResult shape. metadata is a freeform object recorded by the API at the time of the action and varies by route.
[
{
"auditHeaderId": 5512,
"method": "POST",
"route": "/client/recording.playBackByThirdPartyId",
"description": "Play recording",
"username": "jane.smith@example.com",
"insertedAt": "2026-05-12T11:02:14.000Z",
"ipAddress": "203.0.113.10",
"entityId": 9876543,
"parentEntityId": 9876543,
"entityName": "Recording",
"parentEntityName": "Recording",
"metadata": { "comment": "QA review for ticket #1234", "format": "mp3" },
"responseCode": 200
},
{
"auditHeaderId": 5513,
"method": "POST",
"route": "/client/recording.detail.get",
"description": "Get recording detail",
"username": "compliance-svc",
"insertedAt": "2026-05-12T11:05:01.000Z",
"ipAddress": "10.20.30.40",
"entityId": 9876543,
"parentEntityId": 9876543,
"entityName": "Recording",
"parentEntityName": "Recording",
"metadata": null,
"responseCode": 200
}
]
People & Endpoints
A person is an individual tracked in your Focus tenant; an endpoint is one of their addressable identities (a phone number, email or Teams user id). Endpoints are owned by people, and recordings are linked to endpoints.
Search people
POST /client/people.search
Search people with rich filters.
Required right: People.View
Body
| Field | Type | Notes |
|---|---|---|
freeText | string | Free‑text match across name / metadata. |
personType | "staff" | "external" | |
personIds | number[] | Restrict to specific people. |
thirdPartyIds | string[] | Restrict by source id. |
emails | string[] | Restrict by email endpoint. |
groupAssignment | array | Filter by auto‑group rules. |
retentionGroupId | number | null | Filter by retention group; null means "no group". |
byoStorageAccountId | number | null | Filter by BYO storage account. |
includeInteractions | boolean | If true, include recent interaction stats per person. |
onlyReturnNonAssociated | boolean | Only people without an associated user. |
isActive | boolean | |
page, pageSize | number |
Full example response
{
"persons": [
{
"id": 42,
"fullName": "Jane Smith",
"email": "jane.smith@example.com",
"thirdPartyId": "AAD-1f2c",
"personType": "staff",
"isActive": true,
"avatar": "https://cdn.example.com/avatars/42.png",
"retentionGroupId": 12,
"mostRecentCommAt": "2026-05-12T10:34:21.482Z",
"endpoints": [
{ "id": 81, "type": "phone", "endpoint": "447896423595", "ownedByPersonId": 42, "isRecorded": true },
{ "id": 82, "type": "email", "endpoint": "jane.smith@example.com", "ownedByPersonId": 42, "isRecorded": false }
],
"autoGroupAssignment": [
{ "groupName": "Department", "groupValue": "Support", "groupNameId": 3, "groupValueId": 17 }
],
"metadata": { "additionalFields": { "costCentre": "CC-204" } }
}
],
"total": 137,
"page": 1,
"pageSize": 25
}
Search people by name
POST /client/people.searchByName
Lightweight prefix search — useful for type‑ahead UIs.
Required right: People.View
Body
{ "prefix": "smi", "personType": "staff", "isActive": true, "page": 1, "pageSize": 25 }
personType is required; prefix is matched against full name.
Full example response
{
"persons": [
{ "id": 42, "fullName": "Jane Smith", "personType": "staff", "isActive": true },
{ "id": 58, "fullName": "Jamie Smithson", "personType": "staff", "isActive": true }
],
"total": 2,
"page": 1,
"pageSize": 25
}
Get people by id
POST /client/people.get
Fetch full person records by id.
Required right: People.View
Body — a JSON array of ids (numeric or string thirdPartyId):
[1, 2, 3]
Response (abridged)
[
{ "id": 1, "fullName": "Jane Smith", "personType": "staff" }
]
Full example response
[
{
"id": 1,
"fullName": "Jane Smith",
"email": "jane.smith@example.com",
"thirdPartyId": "AAD-1f2c",
"personType": "staff",
"isActive": true,
"avatar": "https://cdn.example.com/avatars/1.png",
"retentionGroupId": 12,
"mostRecentCommAt": "2026-05-12T10:34:21.482Z",
"endpoints": [
{ "id": 81, "type": "phone", "endpoint": "447896423595", "ownedByPersonId": 1, "isRecorded": true },
{ "id": 82, "type": "email", "endpoint": "jane.smith@example.com", "ownedByPersonId": 1, "isRecorded": false }
],
"autoGroupAssignment": [
{ "groupName": "Department", "groupValue": "Support", "groupNameId": 3, "groupValueId": 17 }
],
"metadata": { "additionalFields": { "costCentre": "CC-204" } }
}
]
Search endpoints
POST /client/people/endpoint/search
Find endpoints (phone numbers, emails, Teams user ids) and the people that own them.
Required right: Endpoints.View
Body
| Field | Type | Notes |
|---|---|---|
type | "phone" | "email" | "teamsUserId" | |
endpointSearch | string | Exact / partial match on the endpoint value. |
freeText | string | Free‑text match. |
isRecorded | boolean | Endpoint has ever been recorded. |
isCurrentlyRecorded | boolean | Recording is currently active. |
onlyReturnOwned / onlyReturnNonOwned | boolean | |
page, pageSize | number |
Example
curl -X POST "$BASE_URL/client/people/endpoint/search" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "type": "phone", "endpointSearch": "447896" }'
Full example response
Fields below match the RecordedEndpoint shape returned by the API. The handler returns a plain JSON array — there is no total / page wrapper. inflightCall is populated only while a live call is in progress on the endpoint.
[
{
"id": 81,
"type": "phone",
"endpoint": "447896423595",
"metadata": { "label": "Support desk" },
"ownedByFullName": "Jane Smith",
"isPrivate": false,
"isCurrentlyRecorded": true,
"isRecorded": true,
"inflightCall": null,
"mostRecentCommAt": "2026-05-12T10:34:21.482Z"
},
{
"id": 92,
"type": "phone",
"endpoint": "447896000999",
"metadata": {},
"ownedByFullName": "",
"isPrivate": false,
"isCurrentlyRecorded": false,
"isRecorded": false,
"inflightCall": null,
"mostRecentCommAt": null
}
]
Get endpoints by value
POST /client/people/endpoint/get
Fetch endpoint records by exact value.
Required right: People.View
Body — an array of { type, endpoint } objects:
[
{ "type": "phone", "endpoint": "447896423595" },
{ "type": "email", "endpoint": "jane.smith@example.com" }
]
Returns the matching endpoint records including the owning person id and recording flags. The response is a plain JSON array — entries are returned in the order they're found, not necessarily the order requested.
Full example response
[
{
"id": 81,
"type": "phone",
"endpoint": "447896423595",
"ownedByPersonId": 42,
"isPrivate": false,
"isRecorded": true
},
{
"id": 82,
"type": "email",
"endpoint": "jane.smith@example.com",
"ownedByPersonId": 42,
"isPrivate": false,
"isRecorded": false
}
]
Users
Search users
POST /client/user/search
Search Focus portal users.
Required right: Users.View
Body — all fields optional:
| Field | Type | Notes |
|---|---|---|
userId | integer | Single‑user lookup. |
username | string | Exact username match. |
freeText | string | Free‑text match across user fields. |
rolesAndRights | string[] | Filter to users with these rights. |
isActive, isLocked | boolean | |
onlyReturnNonAssociated | boolean | Users with no person record. |
searchOrderProperty | object | Sort options. |
page, pageSize | number |
Example response
[
{ "id": 42, "username": "alice@example.com", "rolesAndRights": ["Admin"] }
]
Full example response
{
"users": [
{
"id": 42,
"fullName": "Alice Adams",
"username": "alice@example.com",
"email": "alice@example.com",
"externalId": "AAD-9f3a",
"personId": 1042,
"clientId": 1234567,
"isActive": true,
"isLocked": false,
"sso": true,
"lastLoginTime": "2026-05-12T08:14:00.000Z",
"rolesAndRights": ["Admin", "Recording.View", "Recording.Play"],
"policies": ["default-retention"],
"audioPlayback": "streaming"
}
],
"total": 1,
"page": 1,
"pageSize": 1000
}
Retention Groups
List retention groups
GET /admin/retentionGroup
List all retention groups configured on the account, including their default status and assigned people counts.
Required right: RetentionGroup.View
curl -H "Authorization: Bearer $TOKEN" "$BASE_URL/admin/retentionGroup"
Full example response
{
"accountDefault": {
"default": 2555,
"phone": 2555,
"teams": 2555,
"sms": 1095
},
"retentionGroups": [
{
"id": 12,
"name": "Standard 7 years",
"retentionGroup": { "default": 2555, "phone": 2555, "teams": 2555, "sms": 1095 },
"peopleIds": [42, 58, 91]
},
{
"id": 13,
"name": "Short 90 days (training)",
"retentionGroup": { "default": 90 },
"peopleIds": [120, 121]
}
]
}
Duration values are expressed in days. accountDefault applies to anyone not in a specific group.
Comments
Get comments on a recording
GET /client/recording/comment
List comments attached to a recording.
Required right: Recording.Comment.View
Query parameters
| Param | Required | Notes |
|---|---|---|
recordingId | yes | Internal recording id. |
bID | no | Optional bookmark id filter. |
cIdf | no | Optional comment‑identifier filter. |
Example
curl -H "Authorization: Bearer $TOKEN" \
"$BASE_URL/client/recording/comment?recordingId=9876543"
Comments may include start/end offsets within the recording, a tag and the inserting user.
Full example response
Fields below match the CommentOut shape. tag is omitted when the comment isn't tagged.
[
{
"comment": "Customer ID verified at 00:35.",
"startOffsetInSeconds": 35,
"endOffsetInSeconds": 42,
"recordingId": 9876543,
"insertedByUserId": 42,
"insertedByUsername": "jane.smith@example.com",
"insertedAt": "2026-05-12T10:36:00.000Z",
"tag": { "id": 3, "tag": "verified_id" }
},
{
"comment": "Reviewed for QA — no issues.",
"startOffsetInSeconds": 0,
"recordingId": 9876543,
"insertedByUserId": 7,
"insertedByUsername": "compliance-svc",
"insertedAt": "2026-05-12T11:05:00.000Z"
}
]