Documentation

API reference

The full public v1 surface is three endpoints. Base URL for all of them: https://mcp.mnemix.ai. Every request needs a bearer token:

Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxx

Host migration: the canonical platform host is moving to mcp.northsun.ai. During the transition mcp.mnemix.ai continues to serve the full surface unchanged — existing integrations keep working, no action required. When the successor host is generally available, responses from this host will carry a Deprecation header (RFC 9745) and a Link rel="successor-version" header pointing at the corresponding mcp.northsun.ai URL; a retirement date, if one is ever set, will be announced in advance with a Sunset header (RFC 8594). No cutoff is scheduled.

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.mnemix.ai/v1/recall_and_enrich \
  -H "Authorization: Bearer $MNEMIX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phone_number": "+15551234567",
    "trigger": "answered",
    "session_id": "call_abc123"
  }'

Response — 200 OK:

{
  "trace_id": "trace_...",
  "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": "int_...",
        "started_at_ms": 1750900000000,
        "duration_s": 92,
        "summary": "Confirmed 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. 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. The durable write and the cache write-through the next recall reads from both complete before this call responds 204 — the next recall_and_enrich for that caller sees it immediately. A durable queue also receives the event as a retry net for reprocessing; that queue's own processing is asynchronous, but it is not what makes the call durable. See Hangup, and what happens next for the full guarantee.

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.mnemix.ai/v1/calls/end \
  -H "Authorization: Bearer $MNEMIX_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 the full guarantee. Retain the session trace for troubleshooting.

GET /v1/caller/

Read-only profile fetch. No side effects — doesn't trigger enrichment, doesn't create a contact on a miss.

curl -sS https://mcp.mnemix.ai/v1/caller/%2B15551234567 \
  -H "Authorization: Bearer $MNEMIX_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.
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