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:

FieldTypeRequiredNotes
phone_numberstringyesAny format; normalized to E.164 server-side.
trigger"ringing" | "answered"noWhen in the call lifecycle this fired.
session_idstringnoAccepted correlation value; reuse it in calls/end when supplied.
agent_idstringnoAccepted agent identifier. Do not rely on persistence unless your tenant contract names it.
metadataobjectnoAccepted 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:

FieldTypeRequiredNotes
session_idstringyesReuse the paired recall session ID when one was supplied.
phone_numberstringyesSame caller.
transcriptarray of {role, text, ts_ms}yesrole is "user" or "agent". Not a raw string.
duration_snumberyesCall length in seconds, ≥ 0.
outcomestringyesOne of appointment_booked, quote_given, callback_requested, no_answer, other.
agent_metadataobjectnoArbitrary 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": "..." } }
StatusCodeMeaning
400bad_requestMalformed input — e.g. an unparseable phone number.
401unauthorizedMissing or invalid bearer token.
403insufficient_scopeThe key is valid but lacks the scope this route needs.
404not_foundGET /v1/caller/{phone_number} on an unknown caller.
422validation_errorRequest body fails schema validation.
429rate_limitedToken bucket exhausted; back off and retry.
500internal_errorAn unexpected server error occurred; retain the request ID.
503service_unavailableA 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