> ## Documentation Index
> Fetch the complete documentation index at: https://docs.maximem.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# conversation.ingest_transcript

> One-shot async push of a full conversation transcript (plus optional analysis) for background extraction and summarization.

```python theme={null}
await sdk.conversation.ingest_transcript(
    conversation_id: str,
    user_id: str,
    transcript: Union[str, List[TranscriptTurn]],
    customer_id: Optional[str] = None,
    conversation_type: Optional[str] = None,
    analysis: Optional[Dict[str, Any]] = None,
    metadata: Optional[Dict[str, Any]] = None,
    started_at: Optional[datetime] = None,
    ended_at: Optional[datetime] = None,
) -> TranscriptIngestResponse
```

Push a whole conversation transcript in a single call at the **end** of a session. Synap records the transcript, enqueues background extraction, and fires a summary compaction, then returns immediately — nothing here sits on a hot path. This is the call-end half of the async integration pattern; the call-start half is [`sdk.fetch(context_mode="conversation-summary")`](/sdk-reference/context/fetch).

Poll completion with [`sdk.memories.status(ingestion_id)`](/sdk-reference/memories/status) or `sdk.memories.wait_for_completion(ingestion_id)`.

### Parameters

<ParamField path="conversation_id" type="str" required={true}>
  The client's own call/conversation id. **Any string is accepted** — it is *not* validated as a UUID (unlike `record_message`). The server coerces it and echoes the original back as `external_conversation_id`. Reuse a stable id per call (e.g. your telephony provider's call id, or `"{phone}:{call_start_iso}"`).
</ParamField>

<ParamField path="user_id" type="str" required={true}>
  Caller identity — e.g. an E.164 phone number for a voice call.
</ParamField>

<ParamField path="transcript" type="str | List[TranscriptTurn]" required={true}>
  The conversation content. A plain string is split by the server on the turn grammar; a `List[TranscriptTurn]` is **preferred** because it preserves per-turn timestamps and speaker labels.

  ```python theme={null}
  from maximem_synap import TranscriptTurn

  TranscriptTurn(
      role: Literal["user", "assistant"],
      content: str,
      timestamp: Optional[datetime] = None,   # turn time; server time if absent
      speaker: Optional[str] = None,          # display label, e.g. "Agent Priya"
      metadata: Optional[Dict[str, Any]] = None,
  )
  ```
</ParamField>

<ParamField path="customer_id" type="str" required={false}>
  **Required on B2B (strict-isolation) instances**; omit on B2C (`equals_customer`), where the server collapses it from `user_id`.
</ParamField>

<ParamField path="conversation_type" type="str" required={false}>
  Free-form label, ≤ 64 chars (e.g. `"voice"`, `"text"`, `"video"`).
</ParamField>

<ParamField path="analysis" type="Dict[str, Any]" required={false}>
  Your own per-call analysis JSON (≤ 64 KB). Stored verbatim (returned by conversation-summary fetches and shown in the dashboard) **and** fed to extraction as high-confidence hints.
</ParamField>

<ParamField path="metadata" type="Dict[str, Any]" required={false}>
  Open-ended metadata (≤ 64 KB).
</ParamField>

<ParamField path="started_at" type="datetime" required={false}>
  Conversation start time.
</ParamField>

<ParamField path="ended_at" type="datetime" required={false}>
  Conversation end time.
</ParamField>

### Returns

A `TranscriptIngestResponse`.

<ResponseField name="conversation_id" type="str">The server-coerced (UUID-form) conversation id.</ResponseField>
<ResponseField name="external_conversation_id" type="str">The original id you supplied, echoed verbatim.</ResponseField>
<ResponseField name="ingestion_id" type="UUID">Handle for `sdk.memories.status()` / `wait_for_completion()`. Always set on success — never null, even on the `duplicate` branch.</ResponseField>
<ResponseField name="status" type="str">`"queued"` (recorded + enqueued) or `"duplicate"` (an identical transcript was already pushed).</ResponseField>
<ResponseField name="turns_recorded" type="int">Number of turns persisted.</ResponseField>
<ResponseField name="summary_status" type="str">`"in_progress"` (compaction enqueued), `"already_compacted"` (duplicate where the summary already exists), or `"skipped"` (no turns to summarize).</ResponseField>
<ResponseField name="queued_at" type="datetime">When the push was accepted.</ResponseField>

Every response also exposes `.raw` — the untyped response dict, for forward compatibility with fields a newer server may add.

### Idempotency

The push is idempotent on `(conversation_id, transcript)`:

* **Same transcript, same id** → `status="duplicate"` with the original `ingestion_id`. Retries are free.
* **Different transcript, same id** → `TranscriptConflictError` (HTTP 409). A call's transcript is immutable; mint a **new** `conversation_id` for a new call.

### Example

```python theme={null}
from datetime import datetime, timezone
from maximem_synap import MaximemSynapSDK, TranscriptTurn

sdk = MaximemSynapSDK(api_key="synap_your_key_here")
await sdk.initialize()

resp = await sdk.conversation.ingest_transcript(
    conversation_id="call_01H8XZ",                 # any client string
    user_id="+919812345678",                       # E.164 caller id
    conversation_type="voice",
    transcript=[
        TranscriptTurn(role="assistant", content="Hi, is this a good time?", speaker="Agent Priya"),
        TranscriptTurn(role="user", content="Yes — I'm looking for a 3 BHK in Baner.", speaker="Caller"),
    ],
    analysis={"disposition": "interested", "sentiment": "positive"},
    started_at=datetime(2026, 7, 15, 10, 0, tzinfo=timezone.utc),
    ended_at=datetime(2026, 7, 15, 10, 6, tzinfo=timezone.utc),
)

# Optionally wait for extraction to finish before the next call.
await sdk.memories.wait_for_completion(resp.ingestion_id)
```

### Raises

* `InvalidInputError`: empty transcript, oversized `analysis`/`metadata`, or a B2B push missing `customer_id` (HTTP 400/422).
* `TranscriptConflictError`: a *different* transcript was already ingested under this `conversation_id` (HTTP 409). Subclass of `ConflictError`.
* `RateLimitError` / `InsufficientCreditsError`: as applicable.
* `AuthenticationError`: when the API key is missing or invalid.

### See also

* [context.fetch (conversation-summary mode)](/sdk-reference/context/fetch) — the call-start read.
* [user.get\_profile](/sdk-reference/user/get-profile)
* [memories.status](/sdk-reference/memories/status) / [memories.wait\_for\_completion](/sdk-reference/memories/wait-for-completion)
