> ## 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.

> ## Agent Instructions
> Maximem Synap is a managed memory layer for AI agents: it ingests conversations and documents, extracts structured memory, and returns scoped context to the agent at retrieval time.
> The Python and JavaScript/TypeScript SDKs are the supported way to use Synap. Write integration code with SDK calls, not raw HTTP requests.
> Start with the Quickstart: https://docs.maximem.ai/getting-started/quickstart.md. Every SDK method is documented in the SDK reference: https://docs.maximem.ai/sdk-reference/overview.md.

# Files

> Turn PDFs, Office documents, text files and images into memory with memories.create_from_file, and when to extract the text yourself and call memories.create instead.

`memories.create_from_file` uploads a file, or a piece of text, and Synap extracts the text on the server. That text then goes through the same ingestion pipeline as [`memories.create`](/sdk-reference/memories/create): it is split if it is long, its memories are extracted, and they are stored under the person you named. The call returns an `ingestion_id` right away and the work happens in the background.

## Upload a file

```python Python theme={null}
from maximem_synap import MaximemSynapSDK

sdk = MaximemSynapSDK()  # reads SYNAP_API_KEY from the environment
await sdk.initialize()

result = await sdk.memories.create_from_file(
    user_id="user_123",
    customer_id="acme",         # B2B only; on B2C leave out this line
    relationship_type="b2b",    # B2B only; on B2C leave out this line
    file_path="contracts/acme-msa-2026.pdf",
    document_type="pdf",
)

status = await sdk.memories.wait_for_completion(result.ingestion_id)
print(status.status, status.memories_created)
```

```typescript TypeScript theme={null}
import { SynapClient } from '@maximem/synap-js-sdk';

const sdk = new SynapClient(); // reads SYNAP_API_KEY from the environment
await sdk.initialize();

const result = await sdk.memories.create_from_file({
  user_id: 'user_123',
  customer_id: 'acme', // B2B only; see the B2C note below
  relationship_type: 'b2b', // B2B only
  file_path: 'contracts/acme-msa-2026.pdf',
  document_type: 'pdf',
});

const status = await sdk.memories.wait_for_completion(result.ingestion_id);
console.log(status.status, status.memories_created);
```

Pass the content in exactly one of three ways:

| Parameter | Use it for |
| - | - |
| `file_path` | A file on disk. The SDK reads it and uses its name for the upload. In TypeScript this reads from the local file system, so it needs Node. |
| `file` with `filename` | Bytes you already hold, such as an upload from your own users. Python takes an object opened in binary mode (`open(path, "rb")`); TypeScript takes a `Blob`, `Uint8Array` or `ArrayBuffer`. Always pass `filename` with the right extension, because Synap picks the reader from it. |
| `text` | Plain text. No file reading happens; the text goes straight to ingestion. |

## Use the right identity: `relationship_type`

`create_from_file` takes a `relationship_type` that the other ingestion calls do not, and it defaults to `"b2c"`. Set it to match your instance:

| Your instance | `customer_id` | `relationship_type` |
| - | - | - |
| B2C | Leave it out | Leave the default, `"b2c"` |
| B2B | Required | `"b2b"` |

On a B2B instance the default is wrong and fails silently. With `relationship_type="b2c"`, the server treats the user as the whole identity and replaces your `customer_id` with the `user_id`. The upload succeeds, but its memories are filed under a customer named after the user, and fetches for that user under your real customer do not find them. Check which kind your instance is in [B2C vs B2B](/concepts/memory-scopes#b2c-vs-b2b-which-scopes-apply-to-you).

> [!WARNING]
>
> **TypeScript SDK on a B2C instance (0.5.11 and earlier).** When you leave `customer_id` out, `create_from_file` sends the text `"undefined"` as the customer id, and a B2C instance refuses any customer id. Until a fixed release, pass `customer_id: ''` on B2C: the SDK and the server both treat an empty value as absent. The Python SDK is not affected.

`create_from_file` always files memories at user scope, because `user_id` is required. For a document a whole customer or your whole organization should share, such as a policy or a product manual, extract the text and call `memories.create` with `customer_id` only (B2B) or with no ids at all; see [Identifiers and scopes](/concepts/memory-scopes).

## Accepted files

| Extension | How Synap reads it |
| - | - |
| `.pdf` | The text layer of every page. Scanned pages with no text layer give no text. |
| `.docx` | Paragraphs and table cells |
| `.xlsx` | Every sheet, row by row, using the values stored in the cells rather than their formulas |
| `.pptx` | The text on each slide and its speaker notes |
| `.txt`, `.md`, `.markdown`, `.json` | Read as UTF-8 text |
| `.png`, `.jpg`, `.jpeg`, `.webp` | A vision model transcribes the visible text and describes what the image shows |

Synap reads text only. Pictures and charts inside a PDF, DOCX or PPTX are not read.

Uploads that cannot be read are refused before anything is stored:

| Problem | Server response | What the SDK raises |
| - | - | - |
| Extension not in the list above, or no extension (for example `file` without `filename`) | HTTP 400, "Unsupported file type" | `InvalidInputError` |
| File larger than 10 MB | HTTP 413, "File exceeds the 10 MB limit." | A `SynapError` whose message starts with `HTTP 413` (`SynapTransientError` in Python). It is not retried for you. |
| No text could be extracted, such as a scanned PDF or an empty file | HTTP 422 | `InvalidInputError` |

## Set the document type

Synap does not detect the type from the file. If you leave `document_type` out, the upload is processed as `"document"`, whatever its extension. The type tells the pipeline what kind of text it is reading; for example, when a long text has to be split, a chat is split at turn and session boundaries and other text at paragraph boundaries. `create_from_file` passes the value through without checking it, and the server processes a value it does not recognize as `"document"`.

`memories.create` checks the value against the types the published SDKs accept:

| `document_type` | For |
| - | - |
| `ai-chat-conversation` | A conversation between a person and an assistant (the default for `memories.create`) |
| `document` | General text (the default for `create_from_file`) |
| `email` | An email or an email thread |
| `pdf` | Text taken from a PDF |
| `image` | Text describing an image, or text read from one |
| `audio` | A transcript of a recording |
| `meeting-transcript` | A transcript of a meeting |

## When to extract the text yourself

Extract the text in your own code and call `memories.create` with a `document_type` when:

* **The format is not accepted**, such as CSV, HTML, `.doc` or saved email files.
* **The PDF is scanned.** Run OCR first and send the text.
* **The file is larger than 10 MB.**
* **You need the document's date.** `create_from_file` has no `document_created_at`, so Synap cannot tell when the document was written, and a statement like "the contract renews next March" cannot be tied to the right year. `memories.create` takes the date.
* **You need `document_id` or a scope path.** Neither SDK sends a scope path from `create_from_file` (the TypeScript options declare `scope`, but releases up to 0.5.11 do not send it). On a [scope ladder](/concepts/scope-ladder) with a level between customer and user, use `memories.create` with `scope`.
* **The document is shared** by a customer or your whole organization, as described above.

```python Python theme={null}
from datetime import datetime, timezone
from pathlib import Path

# The email body, already extracted by your own code
email_text = Path("exports/msg-4471.txt").read_text()

result = await sdk.memories.create(
    document=email_text,
    document_type="email",
    user_id="user_123",
    customer_id="acme",  # B2B only; refused on B2C
    document_created_at=datetime(2026, 10, 14, 9, 30, tzinfo=timezone.utc),
)
```

```typescript TypeScript theme={null}
import { readFile } from 'node:fs/promises';

// The email body, already extracted by your own code
const emailText = await readFile('exports/msg-4471.txt', 'utf8');

const result = await sdk.memories.create({
  document: emailText,
  document_type: 'email',
  user_id: 'user_123',
  customer_id: 'acme', // B2B only; refused on B2C
  document_created_at: '2026-10-14T09:30:00Z',
});
```

Remember that `memories.create` defaults to `ai-chat-conversation`. Set `document_type` whenever the text is not a chat.

## Long documents

Text too long for one extraction pass, a few thousand tokens or more, is split into parts before extraction: conversations at session and turn boundaries, other text at paragraph boundaries. Each later part carries a short excerpt of the text before it, so its extraction has some context from the earlier parts.

Each part is processed as its own ingestion job, and the `ingestion_id` you get back belongs to the first part. When `memories.status` reports `completed` for that id, the first part is done; later parts may still be in progress.

## Duplicates

Synap fingerprints the text it extracted, ignoring differences in whitespace, and remembers the fingerprint for seven days for each user and customer. Uploading the same content again for the same person within that window returns the earlier response, with the same `ingestion_id` and `document_id`, and nothing is processed again. That response repeats the status from the first upload, usually `queued`, so call `memories.status` to see where the work actually is.

`memories.create` shares the same check, so the same text sent through it for the same person is also treated as a duplicate. The same file uploaded for a different user is processed for that user. After seven days, the same content is processed again.

## Track processing

`memories.status` returns where an ingestion is: `queued`, `processing`, `completed`, `failed` or `partial_success`, along with `memories_created`, `memory_ids` and `error_message`. `memories.wait_for_completion` polls it for you, every 2 seconds for up to 300 seconds by default.

```python Python theme={null}
status = await sdk.memories.status(result.ingestion_id)
print(status.status)

try:
    final = await sdk.memories.wait_for_completion(result.ingestion_id, timeout_seconds=120)
    if final.status == "failed":
        print(final.error_message)
except TimeoutError:
    pass  # still running; check again later with memories.status
```

```typescript TypeScript theme={null}
const current = await sdk.memories.status(result.ingestion_id);
console.log(current.status);

const final = await sdk.memories.wait_for_completion(result.ingestion_id, { timeout_seconds: 120 });
if (final.status === 'failed') {
  console.error(final.error_message);
}
// TypeScript returns the last status it saw when time runs out, so it may
// still be 'queued' or 'processing'.
```

The two SDKs end a wait differently: Python raises `TimeoutError` when time runs out, and TypeScript returns the last status it saw.

## Common mistakes

* **Leaving `relationship_type` at its default on B2B.** The upload succeeds and its memories land under the wrong customer. Pass `relationship_type="b2b"` with your `customer_id`.
* **Passing `file` without `filename`.** The upload is named `upload`, has no extension, and is refused as an unsupported type.
* **Expecting the type to come from the extension.** Without `document_type`, every upload is processed as `"document"`.
* **Uploading a scanned PDF.** There is no text layer, so nothing can be extracted. OCR it and send the text.
* **Uploading shared documents with `create_from_file`.** They are filed under the one user you named. Use `memories.create` without `user_id` for content a whole customer or your whole organization should see.
* **Treating `completed` as done for a long document.** The returned id covers the first part only.
* **Expecting a re-upload to be processed again.** Within seven days, the same content for the same person returns the earlier ingestion.
* **Sending non-chat text to `memories.create` without a type.** It is processed as a chat conversation.

## Related pages

* [`memories.create_from_file`](/sdk-reference/memories/create-from-file) and [`memories.create`](/sdk-reference/memories/create): full parameter references
* [`memories.status`](/sdk-reference/memories/status) and [`memories.wait_for_completion`](/sdk-reference/memories/wait-for-completion)
* [How ingestion works](/concepts/how-ingestion-works): what happens to the text after upload
* [Import existing history](/getting-started/bootstrap): loading many documents at once
* [Conversations and transcripts](/capture/conversations): sending what people say rather than what they upload


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.