External API — Developer Guide

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

  • requests library (pip install requests)

  • An Azure AD App Registration with the following details provided by your administrator:

    • TENANT_ID — Azure AD tenant (directory) ID

    • CLIENT_ID — Application (client) ID of the registered app

    • CLIENT_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_token

Token URL format:

https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/token

Required form fields:

Field

Value

Field

Value

grant_type

client_credentials

client_id

{CLIENT_ID}

client_secret

{CLIENT_SECRET}

scope

api://{CLIENT_ID}/.default

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 True

Step 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

Environment

URL

Production Cananda

https://platform-ca.calljourney.com

Production US

https://platform.calljourney.com

Full URL:

POST https://platform-ca.calljourney.com/api/v1/search/conversationexists

Request Body (JSON):

{ "company": "{COMPANY_NAME}", "organisation": "{ORGANISATION_NAME}", "folder": "{FOLDER_NAME}", "filename": "{FILENAME}" }

Field

Type

Description

Field

Type

Description

company

string

The unique company short name as configured in the platform

organisation

string

The organization short name under the company

folder

string

The folder name where the file is stored

filename

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

Status

Meaning

200

Success — response body contains the result

400

Bad Request — check that all body fields are correct and non-empty

401

Unauthorized — token is missing, expired, or invalid

404

Not Found — the API does not exist on the platform

500

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

Field

Type

Description

exists

boolean

true if the call recording was found, false if it was not

callId

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 pass verify=False to requests.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() returns True.

  • Secret management: Never hard-code CLIENT_SECRET in source code. Use environment variables or a secrets manager (e.g., Azure Key Vault).