External API — Developer Guide
Overview
This guide describes how to authenticate against the CallJourney platform API using Azure AD client credentials and how to call the available endpoints.
The API uses OAuth 2.0 Client Credentials flow (machine-to-machine). There is no user login — instead, a registered Azure AD application obtains a bearer token and passes it with every request.
Prerequisites
Python 3.x
requestslibrary (pip install requests)An Azure AD App Registration with the following details provided by your administrator:
TENANT_ID— Azure AD tenant (directory) IDCLIENT_ID— Application (client) ID of the registered appCLIENT_SECRET— Client secret for the registered app
Step 1 — Obtain an Access Token
Use the Azure AD token endpoint to request a bearer token using the client credentials grant.
import requests
def fetch_access_token(tenant_id, client_id, client_secret):
token_url = f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token"
data = {
"client_id": client_id,
"client_secret": client_secret,
"scope": f"api://{client_id}/.default",
"grant_type": "client_credentials"
}
response = requests.post(token_url, data=data)
response.raise_for_status()
access_token = response.json().get("access_token")
if not access_token:
raise ValueError("Access token not found in the response.")
return access_tokenToken URL format:
https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/token
Required form fields:
Field | Value |
|---|---|
|
|
|
|
|
|
|
|
Note: The scope uses the
api://prefix with the CLIENT_ID, which targets the API's own Azure AD application registration.
Step 2 — Check Token Expiry
JWT access tokens have a limited lifetime (typically 1 hour). Decode the token payload to check the exp claim before making API calls.
import base64
import json
from datetime import datetime, timezone
def is_token_expired(token: str) -> bool:
try:
payload_b64 = token.split(".")[1]
payload_b64 += "=" * (4 - len(payload_b64) % 4)
payload = json.loads(base64.urlsafe_b64decode(payload_b64))
exp = payload.get("exp")
if exp is None:
return False
expiry = datetime.fromtimestamp(exp, tz=timezone.utc)
now = datetime.now(tz=timezone.utc)
return now >= expiry
except Exception as e:
print(f"Could not decode token: {e}")
return TrueStep 3 — Call the API
All API requests must include the bearer token in the Authorization header.
Common Headers
headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json"
}Endpoint Reference
POST /api/v1/search/conversationexists
Checks whether a specific call recording file exists on the platform.
Base URLs:
Environment | URL |
|---|---|
Production Cananda |
|
Production US |
|
Full URL:
POST https://platform-ca.calljourney.com/api/v1/search/conversationexistsRequest Body (JSON):
{
"company": "{COMPANY_NAME}",
"organisation": "{ORGANISATION_NAME}",
"folder": "{FOLDER_NAME}",
"filename": "{FILENAME}"
}Field | Type | Description |
|---|---|---|
| string | The unique company short name as configured in the platform |
| string | The organization short name under the company |
| string | The folder name where the file is stored |
| string | The exact filename of the call recording (including extension) |
Example Request (Python):
import requests
# Retrieve or refresh token
if is_token_expired(access_token):
access_token = fetch_access_token(tenant_id, client_id, client_secret)
api_url = "https://platform-ca.calljourney.com/api/v1/search/conversationexists"
data = {
"company": "{COMPANY_NAME}",
"organisation": "{ORGANISATION_NAME}",
"folder": "{FOLDER_NAME}",
"filename": "{FILENAME}.mp3"
}
headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json"
}
api_response = requests.post(api_url, json=data, headers=headers)
if api_response.ok:
print(api_response.json())
else:
try:
error = api_response.json()
print(f"Error {api_response.status_code}:", error.get("message") or error.get("error") or error)
except Exception:
print(f"Error {api_response.status_code}:", api_response.text)Responses:
Status | Meaning |
|---|---|
| Success — response body contains the result |
| Bad Request — check that all body fields are correct and non-empty |
| Unauthorized — token is missing, expired, or invalid |
| Not Found — the API does not exist on the platform |
| Server Error — contact the API team |
Response Body (200 OK):
When the file exists on the platform:
{
"exists": true,
"callId": "xFcLypwBMZ_pyZ-a8f3p"
}
When the file does not exist on the platform:
{
"exists": false,
"callId": ""
}
Field | Type | Description |
|---|---|---|
| boolean |
|
| string | The internal platform ID of the call recording, or an empty string if not found |
Full Example (End-to-End)
import requests
import base64
import json
from datetime import datetime, timezone
TENANT_ID = "{TENANT_ID}"
CLIENT_ID = "{CLIENT_ID}"
CLIENT_SECRET = "{CLIENT_SECRET}"
def fetch_access_token(tenant_id, client_id, client_secret):
token_url = f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token"
data = {
"client_id": client_id,
"client_secret": client_secret,
"scope": f"api://{client_id}/.default",
"grant_type": "client_credentials"
}
response = requests.post(token_url, data=data)
response.raise_for_status()
access_token = response.json().get("access_token")
if not access_token:
raise ValueError("Access token not found in the response.")
return access_token
def is_token_expired(token: str) -> bool:
try:
payload_b64 = token.split(".")[1]
payload_b64 += "=" * (4 - len(payload_b64) % 4)
payload = json.loads(base64.urlsafe_b64decode(payload_b64))
exp = payload.get("exp")
if exp is None:
return False
expiry = datetime.fromtimestamp(exp, tz=timezone.utc)
return datetime.now(tz=timezone.utc) >= expiry
except Exception:
return True
# Fetch token
access_token = fetch_access_token(TENANT_ID, CLIENT_ID, CLIENT_SECRET)
# Refresh if expired
if is_token_expired(access_token):
access_token = fetch_access_token(TENANT_ID, CLIENT_ID, CLIENT_SECRET)
# Call API
api_url = "https://platform-ca.calljourney.com/api/v1/search/callexists"
data = {
"company": "{COMPANY_NAME}",
"organisation": "{ORGANISATION_NAME}",
"folder": "{FOLDER_NAME}",
"filename": "{FILENAME}.mp3"
}
headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json"
}
api_response = requests.post(api_url, json=data, headers=headers)
if api_response.ok:
print(api_response.json())
else:
try:
error = api_response.json()
print(f"Error {api_response.status_code}:", error.get("message") or error.get("error") or error)
except Exception:
print(f"Error {api_response.status_code}:", api_response.text)Notes
SSL verification: When testing against
localhost, you may need to passverify=Falsetorequests.post(...). This should not be used in production.Token caching: Avoid fetching a new token on every request. Cache the token and only refresh when
is_token_expired()returnsTrue.Secret management: Never hard-code
CLIENT_SECRETin source code. Use environment variables or a secrets manager (e.g., Azure Key Vault).