Developers & AI builders

Developers & AI Builders

Everything you need to integrate the Sentient Pipeline and Emotional Call Handling & Profiling API — authentication, endpoints, response objects, and a ready-to-run script.

Sentient Machines Public API

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.

header
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.

ScopeGrants
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.

1

Submit the recording

POST /api/investigate with the audio file. You get back a jobId immediately — the job itself runs in the background.

2

Poll until it's done

GET /api/investigate/:jobId on whatever interval suits you. Watch status move from running to complete.

3

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.

4

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.

5

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.

6

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.

POST /api/investigate 🔒 pipeline.submit

Request body — multipart/form-data

FieldTypeRequiredDescription
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.
  • 2026-08-28T14:32:00Z
  • 2026-08-28T09:32:00-05:00
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.
  • default
  • marketing-calls

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 number
  • meta.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
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

json
{
  "jobId": "e424667a-c3fc-4563-9e2d-4be7db6743f4",
  "organization": "acme-corp"
}

Responses

201Accepted. Save the jobId — you'll need it for both calls below.
400No file part found, or the file isn't a recognized audio type (wav, mp3, m4a, aac, ogg, flac, webm).
401Missing or invalid bearer token.
403Your 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.)
422Your organization isn't licensed for any pipeline manifest yet — contact your Sentient Machines representative.
500The 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.

GET /api/investigate/:jobId 🔒 pipeline.submit

Path parameter

FieldTypeDescription
jobId string The id returned when you submitted the job.

Request

curl
curl https://your-instance.sentientmachines.tech/api/investigate/e424667a-c3fc-4563-9e2d-4be7db6743f4 \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Response — 200 OK

json
{
  "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

200Current status, shown below.
401Missing or invalid bearer token.
404No 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.

GET /api/investigate/:jobId/result 🔒 conversations.read

Path parameter

FieldTypeDescription
jobId string The id returned when you submitted the job.

Request

curl
curl https://your-instance.sentientmachines.tech/api/investigate/e424667a-c3fc-4563-9e2d-4be7db6743f4/result \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Response — 200 OK

json
{
  "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

FieldTypeDescription
jobIdstringThe same id you submitted and polled with.
idstringInternal record id for this interaction. Stable, and safe to store as your own foreign key for this call.
creationDateISO 8601When the interaction happened (from submission, or when it was recorded).
tagsobjectKey/value classification output — sentiment, vulnerability, and more, depending on your pipeline configuration.
risksarrayNotable moments the pipeline flagged, each as { "text": "…" }. Empty when nothing was flagged.
metadataobjectWhatever meta.<key> fields you attached at submission, unchanged.
utterancesarrayThe call transcript, broken into speaker turns, in chronological order. See Utterance object below.
analysisobjectPipeline-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.
metricsobject | nullThis 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

FieldTypeDescription
startTimenumberSeconds from the start of the recording to the start of this turn.
endTimenumberSeconds from the start of the recording to the end of this turn.
speakerstringWho 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.
textstringWhat 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

200Result, shown above. metrics may be null, or carry null fields within it — see Metrics object.
401Missing or invalid bearer token.
403Your token doesn't have the conversations.read scope.
404No 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.

GET /api/conversations/:id 🔒 conversations.read

Path parameter

FieldTypeDescription
id string The conversation's own id — the same field Get result returns, and the same one Metrics object is keyed by.

Query parameter

FieldTypeRequiredDescription
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
curl "https://your-instance.sentientmachines.tech/api/conversations/6a9175051d000031000fcb4f?includeEmotionalMetrics=true" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Response — 200 OK

json
{
  "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:

json
  ...
  "metrics": null,
  "metricsError": {
    "code": "insufficient_scope",
    "message": "Token does not have the 'emotional-metrics.read' scope."
  }
}

Body

FieldTypeDescription
idstringSame value as the id you requested — echoed back so this payload is self-describing.
creationDateISO 8601When the interaction happened.
tagsobjectKey/value classification output, same shape as Get result's.
risksarrayNotable moments the pipeline flagged. Empty when nothing was flagged.
metadataobjectWhatever meta.<key> fields were attached at submission, unchanged.
utterancesarrayThe call transcript, same shape as Utterance object.
analysisobjectSame shape as Get result's — currently just aiSummary.
metricsobject | null | absentAbsent 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.
metricsErrorobject | absentOnly 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

200Conversation, shown above.
401Missing or invalid bearer token.
403Your token doesn't have the conversations.read scope.
404No 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.

POST /api/customer-profile 🔒 customer-profiles.read

Request body — application/json

FieldTypeRequiredDescription
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
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

json
{
  "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

FieldTypeDescription
customerIdstringA 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.
callsnumberHow many calls this profile is built from — always the full history, regardless of limit.
firstCall / lastCallstringDates (YYYY-MM-DD) of the oldest and newest call on record for this customer.
departmentsarrayEvery department that has handled this customer, sorted and de-duplicated. ["unknown"] when your pipeline doesn't populate a department.
meanSentimentnumber | nullMean sentiment score across their calls, roughly −1 (negative) to +1 (positive). null if no call has a classified sentiment yet.
trendstring | null"Stable", "Volatile", "Deteriorating", or "Improving" — null until there are at least two calls to compare.
baselineFac / latestFacobjectFrustration/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.
deviationobjectHow far latestFac has moved from baselineFac, per dimension. Positive f means more frustrated than usual for this caller.
deviationFlaggedbooleantrue 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.
vulnerabilityFlagsarrayOne entry per call classified as vulnerable, each as { "date": "…", "label": "vulnerable" }. Empty when none.
callSeriesarrayOne 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

FieldTypeDescription
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

200Profile, shown above. Always at least one call — a customer with no history is a 404, not an empty profile.
400phone is missing or blank.
401Missing or invalid bearer token.
403Your token doesn't have the customer-profiles.read scope.
404No 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.

POST /api/agent-profile 🔒 agent-profiles.read

Request body — application/json

FieldTypeRequiredDescription
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
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

json
{
  "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

FieldTypeDescription
callsnumberHow many calls this profile is built from — always the full history, regardless of limit.
firstCall / lastCallstring | nullDates (YYYY-MM-DD) of the oldest and newest call on record for this agent.
ehriIndex / aehsIndexnumberThis 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.
nsirnumberNet sentiment improvement rate across this agent's calls — roughly −1 (mostly ends worse than it started) to +1 (mostly ends better).
statusstring"Top Tier", "On Track", "Development Priority", or "Low Volume" (too few calls/day in view to read reliably — see reliable).
reliablebooleanfalse 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 / businessIssueCallsnumberHow many of this agent's calls were flagged vulnerable, or involved a business issue, respectively.
monthlyTrendarrayThis agent's mean AEHS and call volume per calendar month (YYYY-MM), oldest first.
regionMixarrayThis agent's call volume by department, highest first.
recentCallsarrayTheir limit most recent calls, most recent first — see the item shape below.

recentCalls item

FieldTypeDescription
id / fullIdstringid 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 / hourstringYYYY-MM-DD and HH:MM, in the instance's own timezone.
deltaKindstring"recovered", "lost", or "flat" — how this call's sentiment moved from start to end.
topicsarrayDetected discussion topics, each { "label": "…", "confidence": 0.0–1.0 }. Empty when none were detected.
behaviourarrayThis call's own agent soft-skill scores, same shape as Customer profile's behaviour.
airtimeobject{ "agentSecs", "customerSecs", "silencePct" } — how the call's talk time split.
aehsCallnumber | nullThis one call's own AEHS score. null if the call has neither an empathy nor a sentiment reading.

Responses

200Profile, shown above. Always at least one call — an agent name with no matching calls is a 404, not an empty profile.
400agentName is missing or blank.
401Missing or invalid bearer token.
403Your token doesn't have the agent-profiles.read scope.
404No 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

json
{
  "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

json
{ "metrics": null /* … rest of the result, unaffected … */ }

Fields

FieldTypeDescription
speechFileIdstringSame value as id in the enclosing Get result response.
daystring | nullThe call's date, YYYY-MM-DD.
emotion.f / a / cnumber | nullFrustration/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.erinumber | nullEmotional Risk Index, derived from f/a/c. null whenever any of the three is null.
emotion.sentimentScore / deltaSentimentnumber | null, string | nullThis call's overall sentiment score, and how it moved from start to end of the call (e.g. "neutral_to_negative").
deepfake.alinumber | nullAffect-Language Incongruence — how much this call's sentiment diverges from its own F/A/C. null whenever f/a/c is.
deepfake.scfnumber | nullSpeaker-Confidence Flatness, from the speaker-diarization classifier's own confidence scores.
deepfake.sranumberSilence-Ratio Anomaly, derived from the call's raw silence ratio.
deepfake.speakerConfMeannumberMean 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.silenceRationumberRaw fraction of the call that was silence.
deepfake.vulnerabilityLabelstring | nullThe customer channel's own vulnerability classification for this call.

Job statuses

The three states a job can be in, returned by the status endpoint.

running

Still moving through the pipeline. Poll again shortly.

complete

Finished. The result endpoint has data for you now.

terminated

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.)

json
{ "message": "No job found with id 'e424667a-...'" }
400The request is missing something required — check the field table for the endpoint you called.
401Your Authorization header is missing, malformed, or the token isn't recognized.
403Either 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.
422The request was well-formed, but your organization isn't set up to process it yet.
404Nothing matches that job id or phone number for your organization.
500Something failed on our end. Safe to retry with backoff.
503A 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.

submit-and-fetch-result.sh Template — placeholder values