Overview
The Sentient Public API turns a call recording into conversation intelligence —
a full transcript, plus sentiment, risk flags, vulnerability signals, emotional scores
and more — and lets you read that analysis back into your own systems, per call
or per customer. Everything below sits under a single /api prefix on your
own instance's host, secured by organization-scoped bearer tokens:
- Submit and track — upload a recording once, poll it while it runs, then fetch its transcript, tags, risk flags and your own metadata when it completes.
- Per-call analytics, included — Get result returns that call's emotion and voice-authenticity scores inline, in the same response as the transcript. No second call, no second id to track.
- Look up a conversation directly — GET /api/conversations/:id fetches one conversation's own record by id, independent of the job-tracking flow above.
- Per-customer history — ask for everything you know about one caller by phone number: how many times they've called, how their sentiment is trending, and each call's own scores.
- Per-agent performance — ask for one agent's own EHRI/AEHS performance and risk profile by name, plus their most recent calls.
One base URL
Every endpoint in this reference is relative to
https://<your-instance>.sentientmachines.tech, using the host your
Sentient Machines contact assigned you — every path below already includes its
own /api prefix, so POST /api/investigate below means
exactly POST https://<your-instance>.sentientmachines.tech/api/investigate,
nothing more to add. The same bearer token works against all six endpoints; a token
is tied to your organization and its scopes, never to one endpoint over another.
Before your first request: get your origin IP allowlisted
A valid bearer token isn't enough on its own — the network your calls come from has to be allowlisted too, or every request is rejected regardless of the token. Send your outbound IP address(es) to your Sentient Machines contact — or include them in a test token request — before you start integrating; it's a one-time setup step, done before any of the calls below will work.
Checking you're allowlisted: GET /api/health
GET /api/health needs no token and returns
{ "ok": true }. It's the quickest way to confirm your network can
reach the API at all — useful before you have a token, and useful afterwards to
tell "my IP isn't allowlisted" apart from "my token is wrong". If this returns
200 but your real calls return 403, the problem is the
token or its scopes, not the network.
Authentication & scopes
Every request carries a bearer token in the Authorization
header. The token identifies your organization — it's how the API knows your
default pipeline configuration (and which ones, if more than one, you're licensed to
name explicitly via manifestId, see Submit a
job), and which jobs and profiles you're allowed to look up.
A token alone won't get a request through, though — see
Overview for the IP allowlisting step that has to happen first.
Authorization: Bearer YOUR_API_TOKEN
A token is also scoped to what it's allowed to do, granularly — there's no hierarchy, so each capability below has to be requested explicitly. Most integrations that submit calls also want conversations.read — see the callout below for why.
| Scope | Grants |
|---|---|
| pipeline.submit | POST /api/investigate and GET /api/investigate/:jobId. |
| conversations.read | The transcript from GET /api/investigate/:jobId/result, and GET /api/conversations/:id. |
| emotional-metrics.read | The metrics block inside Get result. |
| customer-profiles.read | POST /api/customer-profile. |
| agent-profiles.read | POST /api/agent-profile. |
Want analytics with your results? Ask for conversations.read + emotional-metrics.read too
Get result spans two scopes on its own: the transcript needs
conversations.read, while the metrics
block inside the same response separately needs
emotional-metrics.read. A token with
conversations.read but not
emotional-metrics.read still gets a normal
200 with the full transcript — but metrics
comes back null rather than erroring. If you're seeing
"metrics": null on every call, check your token's scopes
before looking anywhere else.
Keep it secret
Treat your token like a password. If it's ever exposed, ask your contact to rotate it — there's no self-service reset yet.
Quickstart
The full round trip, start to finish.
Submit the recording
POST /api/investigate with the audio file. You get back a jobId immediately — the job itself runs in the background.
Poll until it's done
GET /api/investigate/:jobId on whatever interval suits you. Watch status move from running to complete.
Read the result
GET /api/investigate/:jobId/result once it's complete, for the transcript, tags, risk flags, your metadata, and that call's metrics — all in one response.
Look up the caller's history, any time after
POST /api/customer-profile for everything recorded under a phone number so far — call count, sentiment trend, and each call's own scores.
Or look up an agent's own profile
POST /api/agent-profile for one agent's EHRI/AEHS performance and risk profile, plus their most recent calls.
Or fetch a conversation directly by id
GET /api/conversations/:id if you already have the conversation's own id and don't need the job-tracking flow above.
POST Submit a job
Upload a recording for analysis. Returns right away with a job id — processing continues asynchronously.
Request body — multipart/form-data
| Field | Type | Required | Description |
|---|---|---|---|
| file | file | Required | The audio recording to analyze. |
| creationDate | ISO 8601 | Optional |
When the interaction actually happened, if different from now. Defaults to the time of submission.
|
| meta.<key> | string | Optional | Any number of your own key/value pairs (e.g. meta.customerRef). Echoed back unchanged in the result, so you can correlate it with your own records. Three keys are reserved for structured use — see the callout below. |
| manifestId | string | Optional |
Which of your organization's licensed pipeline configurations to run this job through. Omit it to use your organization's default — most integrations never need to set this.
|
Reserved meta.o__* keys
Three meta. keys are recognized specially and get translated into structured fields the dashboard uses for filtering and reporting, instead of being stored as opaque metadata:
meta.o__direction— call direction (in/out)meta.o__from_number— the calling party's phone numbermeta.o__to_number— the called party's phone number
These three drive the dashboard's direction filter and the phone-number matching behind Customer profile below. Any other meta.<key> is stored as-is and simply echoed back in Get result — it isn't otherwise interpreted.
Phone calls only, for now
This endpoint accepts phone call recordings. Chat and email support isn't available yet — ask your Sentient Machines contact if you need it.
Need a specific pipeline?
Most integrations never set manifestId — your organization already has a default pipeline configured. If you run more than one kind of analysis (a different configuration per campaign or business line, say), ask your Sentient Machines contact to license your organization for the extra manifest name(s) first; naming one you're not licensed for is rejected with 403.
Request
curl -X POST https://your-instance.sentientmachines.tech/api/investigate \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -F "file=@call-recording.wav" \ -F "meta.customerRef=ACC-4471"
Response — 201 Created
{
"jobId": "e424667a-c3fc-4563-9e2d-4be7db6743f4",
"organization": "acme-corp"
}
Responses
| 201 | Accepted. Save the jobId — you'll need it for both calls below. |
| 400 | No file part found, or the file isn't a recognized audio type (wav, mp3, m4a, aac, ogg, flac, webm). |
| 401 | Missing or invalid bearer token. |
| 403 | Your token doesn't have the pipeline.submit scope, or you named a manifestId your organization isn't licensed for. (A normal JSON error — not the network-level IP-allowlist 403 described under Errors.) |
| 422 | Your organization isn't licensed for any pipeline manifest yet — contact your Sentient Machines representative. |
| 500 | The upload or pipeline lookup failed unexpectedly. Safe to retry. |
GET Check status
Look up where a job currently stands in the pipeline. Cheap enough to poll on a short interval.
Path parameter
| Field | Type | Description |
|---|---|---|
| jobId | string | The id returned when you submitted the job. |
Request
curl https://your-instance.sentientmachines.tech/api/investigate/e424667a-c3fc-4563-9e2d-4be7db6743f4 \ -H "Authorization: Bearer YOUR_API_TOKEN"
Response — 200 OK
{
"jobId": "e424667a-c3fc-4563-9e2d-4be7db6743f4",
"status": "RUNNING",
"currentStageId": "sentiment_classifier",
"startedAt": "2026-08-28T11:46:10.735Z",
"updatedAt": "2026-08-28T11:46:24.108Z",
"completedAt": null
}
Responses
| 200 | Current status, shown below. |
| 401 | Missing or invalid bearer token. |
| 404 | No job with that id, or it belongs to a different organization — the two are indistinguishable on purpose. |
GET Get result
Read back what the pipeline produced. Call this once status reads complete.
Path parameter
| Field | Type | Description |
|---|---|---|
| jobId | string | The id returned when you submitted the job. |
Request
curl https://your-instance.sentientmachines.tech/api/investigate/e424667a-c3fc-4563-9e2d-4be7db6743f4/result \ -H "Authorization: Bearer YOUR_API_TOKEN"
Response — 200 OK
{
"jobId": "e424667a-c3fc-4563-9e2d-4be7db6743f4",
"id": "6a9175051d000031000fcb4f",
"creationDate": "2026-08-28T11:46:10.735Z",
"tags": {
"sentiment": "neutral",
"customerSentiment": "neutral",
"vulnerability": "nonVulnerable",
"contactChannel": "Calls"
},
"risks": [
{ "text": "c-Gratitude-b" }
],
"metadata": {
"customerRef": "ACC-4471"
},
"utterances": [
{
"startTime": 0,
"endTime": 3.28,
"speaker": "agent",
"text": "Thank you for calling, how can I help?",
"findings": []
},
{
"startTime": 3.64,
"endTime": 6.64,
"speaker": "customer",
"text": "Hi, this is Alex, I have a question about my account.",
"findings": [
{ "classifier": "pii_classifier_v2.5", "label": "PERSON", "confidence": 1 },
{ "classifier": "audio_text_sentiment", "label": "a-frustration", "confidence": 0.42 }
]
}
],
"analysis": {
"aiSummary": "Agent: greeted the caller and offered help.\nCustomer: asked about a charge on their account."
},
"metrics": {
"speechFileId": "6a9175051d000031000fcb4f",
"day": "2026-08-28",
"emotion": {
"f": 0.5,
"a": 0,
"c": 0.5,
"eri": 66.67,
"sentimentScore": 0.06,
"deltaSentiment": "neutral_to_neutral"
},
"deepfake": {
"ali": 0.485,
"scf": 1,
"sra": 0.0973,
"speakerConfMean": -1,
"silenceRatio": 0.2027,
"vulnerabilityLabel": "v-non-vulnerable"
}
}
}
Body
| Field | Type | Description |
|---|---|---|
| jobId | string | The same id you submitted and polled with. |
| id | string | Internal record id for this interaction. Stable, and safe to store as your own foreign key for this call. |
| creationDate | ISO 8601 | When the interaction happened (from submission, or when it was recorded). |
| tags | object | Key/value classification output — sentiment, vulnerability, and more, depending on your pipeline configuration. |
| risks | array | Notable moments the pipeline flagged, each as { "text": "…" }. Empty when nothing was flagged. |
| metadata | object | Whatever meta.<key> fields you attached at submission, unchanged. |
| utterances | array | The call transcript, broken into speaker turns, in chronological order. See Utterance object below. |
| analysis | object | Pipeline-generated narrative output. Currently just aiSummary — a short plain-language recap of the call, or null if your pipeline configuration doesn't include the summarization stage. |
| metrics | object | null | This call's emotion and voice-authenticity scores. See Metrics object for every field. null if your token lacks the emotional-metrics.read scope, or if the scores couldn't be produced for this call — the transcript above is unaffected either way. |
Utterance object
| Field | Type | Description |
|---|---|---|
| startTime | number | Seconds from the start of the recording to the start of this turn. |
| endTime | number | Seconds from the start of the recording to the end of this turn. |
| speaker | string | Who said it — e.g. agent, customer, or bot, depending on your pipeline's speaker model. "unknown" if it couldn't be resolved for this turn. |
| text | string | What was said during this turn. |
| findings | array |
Everything the pipeline flagged within this turn (PII, vulnerability, sentiment, and
other classifier hits), each as
{ "classifier": "…", "label": "…", "confidence": 0.0–1.0 }.
Doesn't include speaker classification — that's already broken out as
speaker above. Empty when nothing was flagged in this turn.
|
Analytics arrive with the transcript
The metrics block is assembled for you as part of this
response — you don't need a second request, and you don't need to hold onto
a separate id to fetch it. Scores are normally ready by the time a job reads
complete; if they aren't, the first request
computes them on demand rather than making you wait for the next cycle, so no
extra delay is needed on your side.
Responses
| 200 | Result, shown above. metrics may be null, or carry null fields within it — see Metrics object. |
| 401 | Missing or invalid bearer token. |
| 403 | Your token doesn't have the conversations.read scope. |
| 404 | No job with that id, it isn't yours, or it hasn't reached complete yet. |
GET Conversation
Fetch one conversation's own record directly by id, independent of the job-tracking flow above — useful when you already have the id from somewhere other than a fresh submission.
Path parameter
| Field | Type | Description |
|---|---|---|
| id | string | The conversation's own id — the same field Get result returns, and the same one Metrics object is keyed by. |
Query parameter
| Field | Type | Required | Description |
|---|---|---|---|
| includeEmotionalMetrics | boolean | Optional |
Set to true to also attach this call's emotion/voice-authenticity
scores under metrics, same data as
Get result's own metrics block.
Omit it (or anything other than exactly true) and no second call
is made at all — metrics isn't just null,
it's absent from the response entirely.
|
Metrics are opt-in here — and unlike Get result, denial is reported
Without ?includeEmotionalMetrics=true, this endpoint never
calls out for metrics at all, so there's nothing to fail and nothing to report. Ask
for it explicitly and your token has emotional-metrics.read?
You get metrics populated. Ask for it and DON'T have the
scope (or it's genuinely unavailable)? You get metrics: null
plus a metricsError explaining why — see the Body table
below. That's the opposite of Get result, which always attempts
metrics silently and never explains a null — the
difference is that here you had to ask, so a plain "no" isn't good enough on its own.
Request — with metrics
curl "https://your-instance.sentientmachines.tech/api/conversations/6a9175051d000031000fcb4f?includeEmotionalMetrics=true" \ -H "Authorization: Bearer YOUR_API_TOKEN"
Response — 200 OK
{
"id": "6a9175051d000031000fcb4f",
"creationDate": "2026-08-28T11:46:10.735Z",
"tags": {
"sentiment": "neutral",
"customerSentiment": "neutral",
"vulnerability": "nonVulnerable",
"contactChannel": "Calls"
},
"risks": [
{ "text": "c-Gratitude-b" }
],
"metadata": {
"customerRef": "ACC-4471"
},
"utterances": [
{
"startTime": 0,
"endTime": 3.28,
"speaker": "agent",
"text": "Thank you for calling, how can I help?",
"findings": []
}
],
"analysis": {
"aiSummary": "Agent: greeted the caller and offered help.\nCustomer: asked about a charge on their account."
},
"metrics": {
"speechFileId": "6a9175051d000031000fcb4f",
"day": "2026-08-28",
"emotion": { "f": 0.5, "a": 0, "c": 0.5, "eri": 66.67, "sentimentScore": 0.06, "deltaSentiment": "neutral_to_neutral" },
"deepfake": { "ali": 0.485, "scf": 1, "sra": 0.0973, "speakerConfMean": -1, "silenceRatio": 0.2027, "vulnerabilityLabel": "v-non-vulnerable" }
}
}
Response when requested but not permitted
Same request, but the token lacks emotional-metrics.read — still 200, everything else unchanged, only the tail of the response differs:
... "metrics": null, "metricsError": { "code": "insufficient_scope", "message": "Token does not have the 'emotional-metrics.read' scope." } }
Body
| Field | Type | Description |
|---|---|---|
| id | string | Same value as the id you requested — echoed back so this payload is self-describing. |
| creationDate | ISO 8601 | When the interaction happened. |
| tags | object | Key/value classification output, same shape as Get result's. |
| risks | array | Notable moments the pipeline flagged. Empty when nothing was flagged. |
| metadata | object | Whatever meta.<key> fields were attached at submission, unchanged. |
| utterances | array | The call transcript, same shape as Utterance object. |
| analysis | object | Same shape as Get result's — currently just aiSummary. |
| metrics | object | null | absent | Absent entirely unless you passed ?includeEmotionalMetrics=true. When you did: this call's emotion/voice-authenticity scores (see Metrics object), or null if unavailable — check metricsError. |
| metricsError | object | absent | Only present when metrics was requested and came back null. { "code", "message" }, where code is "insufficient_scope" (missing the scope), "not_found" (no metrics exist for this call), or "unavailable" (transient failure; safe to retry). |
Responses
| 200 | Conversation, shown above. |
| 401 | Missing or invalid bearer token. |
| 403 | Your token doesn't have the conversations.read scope. |
| 404 | No conversation with that id, or it isn't yours — deliberately indistinguishable, same non-enumeration convention as Check status. |
POST Customer profile
A customer's whole emotional-history profile across every call so far, looked up by phone number.
Request body — application/json
| Field | Type | Required | Description |
|---|---|---|---|
| phone | string | Required |
The customer's number. Normalized to E.164 against your instance's configured
default region before lookup, so a local-format number from that region
(07700 900123) and its international form
(+447700900123) resolve to the same customer. Never stored raw:
only a one-way hash of it is ever kept. Matched against whatever you submitted
as meta.o__from_number / meta.o__to_number at
submission (same normalization applied to both sides).
|
| limit | number | Optional |
How many of the most recent calls to return in callSeries.
Defaults to your instance's configured window (5 unless your contact has
changed it), which is also the ceiling — asking for more than that
returns the maximum rather than erroring. Note this only trims
callSeries; calls and the baseline/trend figures
are always computed across the customer's whole history.
|
Numbers from outside your default region
Normalization uses one configured default region per instance, so a number written
in another country's local format won't match. If your callers span multiple
countries, send meta.o__from_number / meta.o__to_number
in full international +-prefixed form at
submission and look them up the same way — that form is
unambiguous regardless of which region your instance is configured for.
Why it's a POST
A phone number is sensitive — putting it in a URL would leak it into access logs and proxy logs by default. Sending it in the request body instead keeps it out of anything that logs the request line.
Request
curl -X POST https://your-instance.sentientmachines.tech/api/customer-profile \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"phone": "+447700900123", "limit": 3}'
Response — 200 OK
{
"customerId": "CUST-4020C31C-CCC2AA3CD3",
"calls": 3,
"firstCall": "2026-08-19",
"lastCall": "2026-09-16",
"departments": ["Collections", "Retail Banking"],
"meanSentiment": -0.18,
"trend": "Deteriorating",
"baselineFac": { "f": 0.14, "a": 0.07, "c": 0.79 },
"latestFac": { "f": 0.61, "a": 0.22, "c": 0.17 },
"deviation": { "f": 0.47, "a": 0.15, "c": -0.62 },
"deviationFlagged": true,
"vulnerabilityFlags": [
{ "date": "2026-09-16T09:12:44+00:00", "label": "vulnerable" }
],
"callSeries": [
{
"date": "2026-08-19T14:02:11+00:00",
"sentimentScore": 0.12,
"eri": 33.43,
"f": 0.14,
"a": 0.07,
"c": 0.79,
"dri": null,
"riskBand": null,
"agentName": "Alex Rivera",
"department": "Retail Banking",
"vulnerability": "nonVulnerable",
"deltaSentiment": "neutral_to_positive",
"businessIssues": "nonBi",
"behaviour": [
{ "dimension": "empathy", "value": 0.82 },
{ "dimension": "listening", "value": 0.74 },
{ "dimension": "proactive", "value": 0.61 },
{ "dimension": "respect", "value": 0.9 },
{ "dimension": "friendly", "value": 0.88 },
{ "dimension": "compassion", "value": 0.7 }
],
"aiSummary": "Agent: welcomed the caller and confirmed their account details.\nCustomer: asked about opening a savings account.\nKey points: no issues raised, straightforward product enquiry.\nConclusion: agent talked through savings options; customer said they'd think it over."
},
{
"date": "2026-09-02T10:47:38+00:00",
"sentimentScore": -0.21,
"eri": 65.36,
"f": 0.33,
"a": 0.17,
"c": 0.5,
"dri": 38.4,
"riskBand": "Low",
"agentName": "Priya Nair",
"department": "Collections",
"vulnerability": "nonVulnerable",
"deltaSentiment": "neutral_to_negative",
"businessIssues": "bi",
"behaviour": [
{ "dimension": "empathy", "value": 0.55 },
{ "dimension": "listening", "value": 0.48 },
{ "dimension": "proactive", "value": 0.4 },
{ "dimension": "respect", "value": 0.63 },
{ "dimension": "friendly", "value": 0.5 },
{ "dimension": "compassion", "value": 0.44 }
],
"aiSummary": "Agent: explained the missed-payment notice and offered a revised plan.\nCustomer: pushed back on the fee, citing a billing error from last month.\nKey points: disputed fee not yet resolved; customer asked for a callback from a supervisor.\nConclusion: agent logged a dispute ticket and promised a callback within 48 hours."
},
{
"date": "2026-09-16T09:12:44+00:00",
"sentimentScore": -0.45,
"eri": 90.32,
"f": 0.61,
"a": 0.22,
"c": 0.17,
"dri": 72.6,
"riskBand": "High",
"agentName": "Priya Nair",
"department": "Collections",
"vulnerability": "vulnerable",
"deltaSentiment": "negative_to_negative",
"businessIssues": "bi",
"behaviour": [
{ "dimension": "empathy", "value": 0.3 },
{ "dimension": "listening", "value": 0.35 },
{ "dimension": "proactive", "value": 0.28 },
{ "dimension": "respect", "value": 0.52 },
{ "dimension": "friendly", "value": 0.31 },
{ "dimension": "compassion", "value": 0.22 }
],
"aiSummary": "Agent: reiterated the outstanding balance and threatened escalation.\nCustomer: became distressed, said they were struggling financially and felt pressured.\nKey points: customer expressed financial hardship; vulnerability signals raised during the call.\nConclusion: call flagged for vulnerability review; agent did not offer a hardship plan."
}
]
}
Body
| Field | Type | Description |
|---|---|---|
| customerId | string | A stable, non-reversible id for this customer — the raw number is never derivable from it. Safe to store and use as your own key for this caller. |
| calls | number | How many calls this profile is built from — always the full history, regardless of limit. |
| firstCall / lastCall | string | Dates (YYYY-MM-DD) of the oldest and newest call on record for this customer. |
| departments | array | Every department that has handled this customer, sorted and de-duplicated. ["unknown"] when your pipeline doesn't populate a department. |
| meanSentiment | number | null | Mean sentiment score across their calls, roughly −1 (negative) to +1 (positive). null if no call has a classified sentiment yet. |
| trend | string | null | "Stable", "Volatile", "Deteriorating", or "Improving" — null until there are at least two calls to compare. |
| baselineFac / latestFac | object | Frustration/Anxiety/Calm proportions — the customer's early baseline vs. their most recent call(s). Any of f/a/c can be null when a call didn't yield an emotion reading (see Metrics object). On a customer's very first call, baselineFac and latestFac are identical. |
| deviation | object | How far latestFac has moved from baselineFac, per dimension. Positive f means more frustrated than usual for this caller. |
| deviationFlagged | boolean | true when frustration or anxiety has moved far enough from this customer's own baseline to be worth attention — the useful "is this call unusual for them" signal, rather than an absolute threshold. |
| vulnerabilityFlags | array | One entry per call classified as vulnerable, each as { "date": "…", "label": "vulnerable" }. Empty when none. |
| callSeries | array | One entry per call, oldest first, trimmed to limit. Each carries that call's own scores alongside dri (deepfake risk index, 0–100) and riskBand — both null on a caller's first observed call, where there's no prior history to compare a voice against yet — plus behaviour and aiSummary, described below. |
callSeries item — behaviour / aiSummary
| Field | Type | Description |
|---|---|---|
| behaviour | array | null |
Six agent soft-skill scores for this call, each { "dimension": "…", "value": 0.0–1.0 }, always in the same order:
empathy, listening, proactive, respect, friendly, compassion.
null if the live record behind this call couldn't be found (e.g. it's since been deleted).
|
| aiSummary | string | null | A short plain-language recap of the call. null if your pipeline configuration doesn't include the summarization stage, or the live record behind this call couldn't be found. |
Responses
| 200 | Profile, shown above. Always at least one call — a customer with no history is a 404, not an empty profile. |
| 400 | phone is missing or blank. |
| 401 | Missing or invalid bearer token. |
| 403 | Your token doesn't have the customer-profiles.read scope. |
| 404 | No profile for that number yet — either it's never called in, or the number doesn't match exactly (see the callout above on normalization). |
POST Agent profile
One agent's own EHRI/AEHS performance and risk profile, looked up by agent name, plus their most recent calls.
Request body — application/json
| Field | Type | Required | Description |
|---|---|---|---|
| agentName | string | Required | Exact match against the agent name recorded on their calls at submission. There's no separate agent directory this is checked against — a typo, or a name that's since changed, simply returns no profile rather than an error. |
| limit | number | Optional |
How many of the agent's most recent calls to return in recentCalls.
Defaults to your instance's configured window (5 unless your contact has
changed it), which is also the ceiling — asking for more than that
returns the maximum rather than erroring.
|
Why it's a POST
Same convention as Customer profile above: an agent name isn't sensitive in the same direct sense a customer's phone number is, but it's still an identifier tied to individual performance and risk data once this response is attached to it, so it's kept out of URLs and access logs on the same basis.
No per-organization isolation on this endpoint
Unlike Customer profile, an agent name isn't hashed together with your organization the way a phone number is — there's no cryptographic separation between organizations for this lookup. Not a concern on the single-tenant deployment most instances run; ask your Sentient Machines contact if that applies to you.
Request
curl -X POST https://your-instance.sentientmachines.tech/api/agent-profile \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"agentName": "Alex Rivera", "limit": 2}'
Response — 200 OK
{
"agentName": "Alex Rivera",
"calls": 214,
"firstCall": "2026-06-02",
"lastCall": "2026-09-16",
"ehriIndex": 108.4,
"aehsIndex": 106.1,
"nsir": 0.041,
"status": "On Track",
"reliable": true,
"meanEmpathy": 0.62,
"meanAgentSentiment": 0.18,
"meanRiskIndex": 31.2,
"vulnerable": 6,
"businessIssueCalls": 19,
"monthlyTrend": [
{ "month": "2026-08", "meanAehs": 104.9, "calls": 98 },
{ "month": "2026-09", "meanAehs": 106.1, "calls": 62 }
],
"regionMix": [
{ "label": "Retail Banking", "count": 140 },
{ "label": "Card Services", "count": 74 }
],
"recentCalls": [
{
"id": "fcb4f",
"fullId": "6a9175051d000031000fcb4f",
"date": "2026-09-16",
"hour": "17:29",
"durationSecs": 312,
"sentiment": "positive",
"deltaKind": "recovered",
"category": "Card Services",
"summary": "Customer disputed a card charge; resolved with a refund.",
"topics": [{ "label": "billing dispute", "confidence": 0.91 }],
"behaviour": [{ "dimension": "empathy", "value": 0.71 }],
"airtime": { "agentSecs": 180, "customerSecs": 120, "silencePct": 4 },
"aehsCall": 109.2
},
{
"id": "1a02b",
"fullId": "6a9174e91d000031000f1a02b",
"date": "2026-09-15",
"hour": "11:04",
"durationSecs": 201,
"sentiment": "neutral",
"deltaKind": "flat",
"category": "Retail Banking",
"summary": "Routine balance enquiry.",
"topics": [],
"behaviour": [{ "dimension": "empathy", "value": 0.58 }],
"airtime": { "agentSecs": 95, "customerSecs": 90, "silencePct": 8 },
"aehsCall": 101.5
}
]
}
Body
| Field | Type | Description |
|---|---|---|
| calls | number | How many calls this profile is built from — always the full history, regardless of limit. |
| firstCall / lastCall | string | null | Dates (YYYY-MM-DD) of the oldest and newest call on record for this agent. |
| ehriIndex / aehsIndex | number | This agent's EHRI/AEHS indices, z-scored against every other agent your dashboard currently has in view — 100 is the cohort mean, not a fixed absolute scale. |
| nsir | number | Net sentiment improvement rate across this agent's calls — roughly −1 (mostly ends worse than it started) to +1 (mostly ends better). |
| status | string | "Top Tier", "On Track", "Development Priority", or "Low Volume" (too few calls/day in view to read reliably — see reliable). |
| reliable | boolean | false when this agent's call volume is too low for status/ehriIndex to mean much — they're excluded from the team-level averages other agents are compared against. |
| vulnerable / businessIssueCalls | number | How many of this agent's calls were flagged vulnerable, or involved a business issue, respectively. |
| monthlyTrend | array | This agent's mean AEHS and call volume per calendar month (YYYY-MM), oldest first. |
| regionMix | array | This agent's call volume by department, highest first. |
| recentCalls | array | Their limit most recent calls, most recent first — see the item shape below. |
recentCalls item
| Field | Type | Description |
|---|---|---|
| id / fullId | string | id is a short display id (last 6 characters); fullId is the same id used elsewhere in this reference (e.g. SpeechFile metrics, if your instance also exposes that endpoint). |
| date / hour | string | YYYY-MM-DD and HH:MM, in the instance's own timezone. |
| deltaKind | string | "recovered", "lost", or "flat" — how this call's sentiment moved from start to end. |
| topics | array | Detected discussion topics, each { "label": "…", "confidence": 0.0–1.0 }. Empty when none were detected. |
| behaviour | array | This call's own agent soft-skill scores, same shape as Customer profile's behaviour. |
| airtime | object | { "agentSecs", "customerSecs", "silencePct" } — how the call's talk time split. |
| aehsCall | number | null | This one call's own AEHS score. null if the call has neither an empathy nor a sentiment reading. |
Responses
| 200 | Profile, shown above. Always at least one call — an agent name with no matching calls is a 404, not an empty profile. |
| 400 | agentName is missing or blank. |
| 401 | Missing or invalid bearer token. |
| 403 | Your token doesn't have the agent-profiles.read scope. |
| 404 | No calls found tagged with that exact agent name (see the request-field note above on exact matching). |
Metrics object
The shape of the metrics field inside Get result — one call's own emotion and voice-authenticity scores. Not a separate call: it's assembled for you as part of the result.
Requires the emotional-metrics.read scope
In addition to the conversations.read Get result itself needs, this one field also needs emotional-metrics.read — see Authentication & scopes. Without it, metrics is null and everything else in the response is unaffected.
Example
{
"speechFileId": "6a9175051d000031000fcb4f",
"day": "2026-09-16",
"emotion": {
"f": 0.5,
"a": 0,
"c": 0.5,
"eri": 66.67,
"sentimentScore": 0.06,
"deltaSentiment": "neutral_to_neutral"
},
"deepfake": {
"ali": 0.03,
"scf": 1,
"sra": 0.26,
"speakerConfMean": -1,
"silenceRatio": 0.56,
"vulnerabilityLabel": "v-non-vulnerable"
}
}
A call with no metrics yet
{ "metrics": null /* … rest of the result, unaffected … */ }
Fields
| Field | Type | Description |
|---|---|---|
| speechFileId | string | Same value as id in the enclosing Get result response. |
| day | string | null | The call's date, YYYY-MM-DD. |
| emotion.f / a / c | number | null | Frustration/Anxiety/Calm proportions for this one call. null (not zero) when the call was too short, too quiet on the customer's side, or didn't produce a sentiment-audio classification attributable to the customer — excluded from averages rather than treated as a neutral score. |
| emotion.eri | number | null | Emotional Risk Index, derived from f/a/c. null whenever any of the three is null. |
| emotion.sentimentScore / deltaSentiment | number | null, string | null | This call's overall sentiment score, and how it moved from start to end of the call (e.g. "neutral_to_negative"). |
| deepfake.ali | number | null | Affect-Language Incongruence — how much this call's sentiment diverges from its own F/A/C. null whenever f/a/c is. |
| deepfake.scf | number | null | Speaker-Confidence Flatness, from the speaker-diarization classifier's own confidence scores. |
| deepfake.sra | number | Silence-Ratio Anomaly, derived from the call's raw silence ratio. |
| deepfake.speakerConfMean | number | Mean speaker-classifier confidence across the call. -1 is a real, expected value here, not an error — it isn't reserved for missing data the way null is elsewhere in this object. |
| deepfake.silenceRatio | number | Raw fraction of the call that was silence. |
| deepfake.vulnerabilityLabel | string | null | The customer channel's own vulnerability classification for this call. |
Job statuses
The three states a job can be in, returned by the status endpoint.
Still moving through the pipeline. Poll again shortly.
Finished. The result endpoint has data for you now.
Stopped early by the pipeline itself — no result will follow for this job.
Errors
Every error response is a small JSON object with a human-readable message — with one exception: the network-level IP-allowlist 403 below. (Submit's own 403 for an unlicensed manifestId, and customer-profile's/agent-profile's own 403 for a missing scope, are not that exception — both are normal JSON responses.)
{ "message": "No job found with id 'e424667a-...'" }
| 400 | The request is missing something required — check the field table for the endpoint you called. |
| 401 | Your Authorization header is missing, malformed, or the token isn't recognized. |
| 403 | Either your origin IP isn't allowlisted yet — see Overview, blocked before it reaches the API itself, so the response won't be the usual JSON shape — or your token is missing the scope an endpoint needs, which is the usual JSON shape. See Authentication & scopes. |
| 422 | The request was well-formed, but your organization isn't set up to process it yet. |
| 404 | Nothing matches that job id or phone number for your organization. |
| 500 | Something failed on our end. Safe to retry with backoff. |
| 503 | A service this API depends on (token verification, or the analytics store) is temporarily unavailable. Safe to retry with backoff — this is not a sign anything about your request was wrong. |
Get your script
Fill in the base URL and token your Sentient Machines contact gave you, and this generates
a ready-to-run copy of submit-and-fetch-result.sh with those
values baked in — nothing you type here leaves this page.
Below is submit-and-fetch-result.sh as a template. Before running
it, replace the two values near the top with the ones your Sentient Machines contact gave you:
API_BASE_URL— replacehttps://your-instance.sentientmachines.tech/apiwith your instance's base URLAPI_TOKEN— replaceYOUR_API_TOKENwith your bearer token
Then run it with the path to an audio file: ./submit-and-fetch-result.sh call.wav.
The online version of this page can fill both values in for you.