Get started · 6 / 42
API reference
The public v1 surface is three bearer-authenticated endpoints: POST /v1/recall_and_enrich, POST /v1/calls/end, and GET /v1/caller/{phone_number}.
The full public v1 surface is three endpoints. Base URL for all of them: https://mcp.northsun.ai. Every request needs a bearer token:
Authorization: Bearer sk_live_••••••••••••POST /v1/recall_and_enrich
The voice hot path. Call it once before the agent's first turn. It creates the caller record on a miss, recalls same-number memory, and returns cached or deadline-bounded enrichment when available.
Request body:
| Field | Type | Required | Notes |
|---|---|---|---|
phone_number | string | yes | Any format; normalized to E.164 server-side. |
trigger | "ringing" | "answered" | no | When in the call lifecycle this fired. |
session_id | string | no | Accepted correlation value; reuse it in calls/end when supplied. |
agent_id | string | no | Accepted agent identifier. Do not rely on persistence unless your tenant contract names it. |
metadata | object | no | Accepted context object. The frozen route does not guarantee that it is persisted. |
curl -sS -X POST https://mcp.northsun.ai/v1/recall_and_enrich \
-H "Authorization: Bearer $NORTHSUN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone_number": "+15551234567",
"trigger": "answered",
"session_id": "call_abc123"
}'Response — 200 OK:
{
"trace_id": "b6b6c6d2-4f1e-4a57-9d3b-0c9a1f6e2a10",
"context_audit_id": "b6b6c6d2-4f1e-4a57-9d3b-0c9a1f6e2a10",
"known": true,
"caller": {
"name": "Mike Reynolds",
"first_seen_ms": 1750000000000,
"last_seen_ms": 1751000000000,
"total_calls": 3,
"tags": ["fleet", "commercial"],
"custom_attributes": {}
},
"memory": {
"summary": "Repeat customer, 3 calls over 2 months. Fleet account.",
"recent_calls": [
{
"call_id": "7f0c2a64-3b1d-4c8e-8a5f-2d9e1b6c4a37",
"started_at_ms": 1750900000000,
"duration_s": 92,
"summary": "Please confirm my brake inspection for Thursday.",
"outcome": "appointment_booked"
}
]
},
"enrichment": {
"person": { "name": "Mike Reynolds", "title": null, "company": null },
"company": null,
"phone": { "carrier": "Verizon", "line_type": "mobile" }
},
"timing_ms": { "total": "<measured>", "memory_ms": "<measured>", "enrichment_ms": "<measured>", "cache_hit": true },
"freshness": { "memory_age_ms": null, "enrichment_age_ms": null }
}timing_ms and freshness describe this request; they are not published performance benchmarks. context_audit_id is an optional additive field, present only when the voice feedback loop is enabled for your deployment, and memory.recent_calls is the newest calls first, up to five. A first-time caller returns known: false, an empty memory summary, and whatever enrichment completed within the request deadline. Individual enrichment blocks may be null.
If the caller-memory database read degrades after authentication, the endpoint preserves the 200 response shape with empty defaults and degraded: true. Authentication, validation, and rate-limit failures still return non-2xx errors and must be handled normally.
caller.total_calls can be null. null means the count was unavailable for that request — we could not read it and are telling you so, rather than guessing. It does not mean zero. Branch on it rather than coalescing it:
// Correct: "unknown" stays unknown.
const seenBefore = caller.total_calls === null ? null : caller.total_calls > 0;
// Wrong: turns "we don't know" into "this person has never called".
const seenBefore = (caller.total_calls ?? 0) > 0;name, first_seen_ms and last_seen_ms use null the same way. The count is re-read on your next request — an unavailable count is never cached, so recovery does not wait on a TTL.
POST /v1/calls/end
The write-back. A 204 means the interaction is committed durably. A later recall_and_enrich for that caller normally returns it but may lag, so verify with a later controlled recall. See Hangup, and what happens next.
Request body:
| Field | Type | Required | Notes |
|---|---|---|---|
session_id | string | yes | Reuse the paired recall session ID when one was supplied. |
phone_number | string | yes | Same caller. |
transcript | array of {role, text, ts_ms} | yes | role is "user" or "agent". Not a raw string. |
duration_s | number | yes | Call length in seconds, ≥ 0. |
outcome | string | yes | One of appointment_booked, quote_given, callback_requested, no_answer, other. |
agent_metadata | object | no | Arbitrary key-value passthrough. |
curl -sS -X POST https://mcp.northsun.ai/v1/calls/end \
-H "Authorization: Bearer $NORTHSUN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"session_id": "call_abc123",
"phone_number": "+15551234567",
"transcript": [
{ "role": "agent", "text": "Thanks for calling, how can I help?", "ts_ms": 0 },
{ "role": "user", "text": "I need to confirm Thursday.", "ts_ms": 3200 }
],
"duration_s": 92,
"outcome": "appointment_booked"
}'Response — 204 No Content. No body. This confirms the interaction is already committed durably — see Hangup, and what happens next for what it does and does not promise. Retain the session trace for troubleshooting.
GET /v1/caller/{phone_number}
Read-only profile fetch. No side effects — doesn't trigger enrichment, doesn't create a contact on a miss.
curl -sS https://mcp.northsun.ai/v1/caller/%2B15551234567 \
-H "Authorization: Bearer $NORTHSUN_API_KEY"Response — 200 OK:
{
"name": "Mike Reynolds",
"first_seen_ms": 1750000000000,
"last_seen_ms": 1751000000000,
"total_calls": 3,
"tags": ["fleet", "commercial"],
"custom_attributes": {}
}404 if the number has never called — this endpoint never auto-creates. Use recall_and_enrich for that.
total_calls follows the same rule as on recall_and_enrich: null means the count was unavailable, never zero.
Errors
Every non-2xx response carries a machine-readable code:
{ "error": { "code": "validation_error", "message": "..." } }| Status | Code | Meaning |
|---|---|---|
400 | bad_request | Malformed input — e.g. an unparseable phone number. |
401 | unauthorized | Missing or invalid bearer token. |
403 | insufficient_scope | The key is valid but lacks the scope this route needs. |
404 | not_found | GET /v1/caller/{phone_number} on an unknown caller. |
422 | validation_error | Request body fails schema validation. |
429 | rate_limited | Token bucket exhausted; back off and retry. |
500 | internal_error | An unexpected server error occurred; retain the request ID. |
503 | service_unavailable | A required dependency was unavailable; retry with backoff. |
Machine-readable spec
The public site exposes an OpenAPI document at /.well-known/openapi.json for the frozen paths and request bodies. Pin the document you inspect. Current response schemas are intentionally permissive, so verify generated response types against the examples and live contract before relying on them.
Next steps
- What recall gives you back — the full response shape annotated field-by-field, including the optional additive
factsandcontext_audit_idfields. - Hangup, and what happens next — what
calls/end's204confirms and how to verify it. - MCP server — connect through hosted MCP and distinguish stable caller tools from evolving context tools.
- What is caller memory? — the concept behind
recall_and_enrich.