Skip to main content

Developer API

Two API-key surfaces: one for submitting recordings to be analysed, and one used by nextneural_coach to have a simulated call scored.

Authorization: Bearer nn_k_your_key_here

Submit analyses​

POST /api/v1/sales/analyses · scope sales:analyses:write

Submits up to ten recordings by URL. The audio is fetched server-side, so whatever hosts it must be reachable from the binary.

{
"items": [
{
"audio_url": "https://recordings.example/call-1042.mp3",
"reference_id": "crm-9981",
"customer_name": "Priya Kumar",
"customer_phone": "+918888888888",
"customer_segment": "winback",
"direction": "outbound"
}
]
}
FieldRequiredLimit
audio_url✓1–500 characters
reference_id—≤ 200 — your identifier, for idempotency
customer_name—≤ 255
customer_phone—≤ 40
customer_segment—≤ 100
direction—inbound or outbound

items must contain 1 to 10 entries.

Response​

{
"accepted": 2,
"rejected": 1,
"results": [
{ "index": 0, "reference_id": "crm-9981", "call_id": 1042, "call_uuid": "sc1a2b3c-…" },
{ "index": 1, "reference_id": "crm-9982", "call_id": 1043, "call_uuid": "sc4d5e6f-…", "warning": "customer segment created" },
{ "index": 2, "reference_id": "crm-9983", "status": "rejected", "error": "audio_url could not be fetched" }
]
}

Partial success: index maps each result back to your items array, and accepted + rejected sum to its length. A rejected item can be resubmitted on its own without resending the batch.

Use reference_id — resubmitting the same one is recognised rather than creating a duplicate analysis, which is what makes a retry after a network failure safe.

Calls land in the active project, in status pending. Analysis is asynchronous; poll the next endpoint.

StatusCause
400Validation failed — the message names the field
403The key lacks sales:analyses:write
500{"detail": "failed to provision data source"}

Get an analysis​

GET /api/v1/sales/analyses/{call_id} · scope sales:analyses:read

call_id is the integer returned at submission.

{
"call_id": 1042,
"call_uuid": "84c31f06-533d-445d-826a-01945b42eb6a",
"reference_id": "crm-9981",
"status": "completed",
"error": null,
"analysis": {
"transcript": [
{ "speaker": "agent", "text": "Hi, is that Priya?" }
],
"summary": "Customer asked about delivery timing.",
"sentiment": "positive",
"outcome": "follow_up_scheduled",
"key_details": [
{ "label": "Promised date", "value": "2026-09-27" }
],
"custom_fields": { "budget_confirmed": true },
"scores": { "opening": 3 },
"goal_status": "achieved",
"goal_reasoning": "Follow-up booked.",
"is_flagged": false,
"duration_seconds": 113,
"direction": "outbound",
"customer": {
"id": 1,
"uuid": "e086be62-4c5f-4627-be9a-893a1043a966",
"name": "Priya Kumar",
"phone": "+919000000001",
"segment": "winback"
}
},
"created_at": "2026-09-22T09:41:02.118+05:30",
"updated_at": "2026-09-22T09:41:02.118+05:30"
}

The envelope carries the identifiers and processing state; everything the analysis produced sits under analysis, which is null until status is completed. error explains a failed status.

Field names differ from the console API

Inside analysis the extracted fields are custom_fields — not custom_field_values as the console call object names them. key_details is an array of {label, value} objects here.

Poll until status is completed or failed. See statuses.

StatusCause
404{"detail": "Analysis not found"} — wrong id, or another organization's

Field labels​

GET /api/v1/sales/extraction-field-labels · scope sales:analyses:read

The names and types behind the keys in custom_field_values, so an integration can label them without hardcoding your configuration:

{
"fields": [
{ "uuid": "ef1a2b3c-…", "name": "budget_confirmed", "field_type": "boolean" }
]
}

Simulation judging​

POST /api/nn-intel/simulations/judge · scope sales:simulations:judge

Scores a transcript that never happened on a phone — this is how nextneural_coach has a practice call judged by the same extraction a real call receives, so a trainee's score is comparable to live performance.

{
"transcript": [
{ "speaker": "agent", "text": "Hi, is that Priya?", "start": 0.0 },
{ "speaker": "customer", "text": "Yes, who's calling?", "start": 2.4 }
],
"call_type": "outbound",
"scenario_objective": "Book a product demo with a price-sensitive SMB owner.",
"hidden_state": { "budget": 40000, "mood": "sceptical" },
"objections": ["too expensive", "already have a supplier"],
"knowledge_context": "Standing Desk Pro is ₹52,000 with a 3-year warranty.",
"rubric_fields": [
{
"id": "objection_handling",
"name": "Objection handling",
"field_type": "number",
"description": "Did the rep address the objection with evidence?",
"min_value": 0,
"max_value": 3
}
],
"include_org_signals": true
}
FieldRequiredNotes
transcript✓At least one segment
call_type—inbound or outbound
scenario_objective—What the trainee was supposed to achieve
hidden_state—What the simulated customer knew but did not volunteer
objections—Objections the scenario was meant to raise
knowledge_context—Facts the answer should be grounded in
rubric_fields—Criteria to score against; id is required on each
include_org_signals—Also apply the org's own extraction fields. Absent means true

No call record is created — this scores and returns.

StatusCause
400Validation failed
403The key lacks sales:simulations:judge
503{"detail": "analysis pipeline is not running"}
Why this has its own scope

sales:simulations:judge is deliberately separate from sales:analyses:write, so the key issued to the coach can score simulations without also being able to create call records in your analysis history.

Scopes​

EndpointScope
POST /api/v1/sales/analysessales:analyses:write
GET /api/v1/sales/analyses/{call_id}sales:analyses:read
GET /api/v1/sales/extraction-field-labelssales:analyses:read
POST /api/nn-intel/simulations/judgesales:simulations:judge