FishMem

Document methods

Ingest exact textual sources and retrieve citation-ready evidence with the TypeScript and Python SDKs.

TypeScript

Ingest

const ingested = await fishmem.documents.ingest(
  {
    source_key: "docs/architecture.md",
    title: "Architecture",
    mime_type: "text/markdown",
    content: markdown,
    user_id: "alex",
    metadata: { repository: "fishmem" },
  },
  { idempotencyKey: "architecture-v1" },
);

FishMem Cloud requires the idempotency key. Replaying the same command returns the committed result; reusing the key for different content returns 409.

Upload a file

Browser applications can pass a File. Node applications can construct a Blob from bytes and provide the filename explicitly:

import { readFile } from "node:fs/promises";

const queued = await fishmem.documents.upload(
  {
    file: new Blob([await readFile("./handbook.pdf")], {
      type: "application/pdf",
    }),
    filename: "handbook.pdf",
    source_key: "docs/handbook.pdf",
    user_id: "alex",
    metadata: { repository: "fishmem" },
  },
  { idempotencyKey: "handbook-pdf-v1" },
);

const ready = await fishmem.operations.wait(queued.operation.id, {
  intervalMs: 1_000,
  timeoutMs: 10 * 60_000,
});

upload() performs create, exact-byte PUT, and completion. It calculates SHA-256 locally and returns after the durable document_extract operation is queued, not after extraction finishes. Poll operations.wait() or documents.getUpload() when your application needs the final document_id. If source_key is omitted it defaults to File.name, the explicit filename, or document.bin.

Use createUpload(), the returned upload.url, completeUpload(), and getUpload() directly when you need to stream bytes yourself or persist each lifecycle checkpoint. deleteUpload(id) cancels and removes an upload that is not processing or ready. Failed extraction can be redriven with operations.retry(queued.operation.id).

FishMemDesktop.documents.upload() accepts the same Blob/File input. It is intentionally different: Desktop strictly decodes UTF-8 locally and sends the resulting exact content through the existing machine-oriented CLI command. Desktop does not run Docling, OCR, a remote file extractor, or a remote embedding provider.

const { results } = await fishmem.documents.search({
  query: "projection repair",
  user_id: "alex",
  limit: 5,
  neighbors: 1,
});

for (const hit of results) {
  console.log(
    hit.document.source_key,
    hit.chunk.start_byte,
    hit.chunk.end_byte,
    hit.chunk.content,
  );
}

neighbors returns adjacent chunks as additional local context. It does not change the matched chunk's provenance.

List, content, and delete

const page = await fishmem.documents.list({
  user_id: "alex",
  limit: 50,
});

for await (const document of fishmem.documents.listAll({
  user_id: "alex",
})) {
  console.log(document.source_key);
}

const descriptor = await fishmem.documents.get(ingested.document.id);
const original = await fishmem.documents.content(descriptor.id);

await fishmem.documents.delete(descriptor.id, {
  idempotencyKey: `delete-${descriptor.id}`,
});

content() returns the exact original UTF-8 text. Deleting any version deletes the complete stable-source family.

Python

The sync and async clients expose the same documents resource:

from pathlib import Path

uploaded = fishmem.documents.upload(
    Path("./handbook.pdf"),
    {
        "source_key": "docs/handbook.pdf",
        "user_id": "alex",
        "metadata": {"repository": "fishmem"},
    },
    idempotency_key="handbook-pdf-v1",
)

ready = fishmem.operations.wait(
    uploaded["operation"]["id"],
    interval=1.0,
    timeout=600.0,
)

ingested = fishmem.documents.ingest(
    {
        "source_key": "docs/architecture.md",
        "content": markdown,
        "mime_type": "text/markdown",
        "user_id": "alex",
    },
    idempotency_key="architecture-v1",
)

hits = fishmem.documents.search(
    {
        "query": "projection repair",
        "user_id": "alex",
        "neighbors": 1,
    }
)

original = fishmem.documents.content(ingested["document"]["id"])

For a manually-created upload, call delete_upload(asset_id) before it is actively processing. After a failed or dead operation, call operations.retry(operation_id) to reuse the retained raw bytes and artifact.

Use async for with AsyncFishMem.documents.list_all.

Input boundary

The HTTP SDKs accept supported PDF, Office, EPUB, email, image, and textual files up to 25,000,000 bytes and 300 extracted pages. FishMem keeps the raw file immutable, stores lossless extraction structure, and indexes extracted Markdown through the normal document RAG writer. Audio and video are not accepted.

Direct documents.ingest() remains synchronous and accepts already-textual UTF-8 content up to 1,000,000 bytes. Desktop supports only this textual path; its upload() convenience method reads and validates UTF-8 locally.

See Asynchronous file extraction for the complete lifecycle, supported media types, states, limits, retention, and errors. Cloudflare uses R2 for raw files and artifacts; Node/Docker uses the configured durable asset directory; Desktop keeps textual originals locally.

On this page