---
title: "Write Batch"
method: POST
path: "/api/v1/memory/write-batch"
tags: ["memory-v1"]
---

# Write Batch

`POST /api/v1/memory/write-batch`

## Request

- Base URL: `https://core.mnemoverse.com`
- URL: `https://core.mnemoverse.com/api/v1/memory/write-batch`
- Auth: one of:
  - API key in header `X-Api-Key`
  - HTTP bearer

Batch write up to 100 memory atoms.

## Idempotency

Supports the Stripe-style `Idempotency-Key` header. Clients that retry
the same logical write after a network failure (including the 2026-04-13
Cloudflare 524 / ghost-write scenario) can pass the same key and get
back the exact response the server cached on the first successful
processing, rather than double-writing atoms. The cache TTL is 24 hours
(see migrations/versions/004_idempotency.py).

The key is scoped per operation and per organization by
`_scoped_idempotency_key` so two tenants cannot collide on the same
raw UUID. The org fragment is a SHA-256 hash prefix, not the raw
org_id, to make the key delimiter-safe regardless of what characters
the upstream auth layer produces.

### ⚠️ Sequential retries only

This implementation protects **sequential** retries (client waits for
the first request to return or time out, then retries with the same
key). **Concurrent** retries are NOT serialized: if two retries of
the same key land while the first processing is still in flight,
both will observe a cache miss and both will call `engine.write()`.
In practice MCP clients retry with exponential backoff so this race
is rare, but if the dashboards surface it as a real signal we will
add a processing-sentinel row inserted with `ON CONFLICT DO NOTHING`
at the top of the handler (tracked as Step 2c.1 in the observability
sprint plan). Documented so a future reader does not assume the
current code is fully race-safe.

### Failure modes

* Missing or empty header → no caching, behaves as before.
* Header > 240 chars → 400 VALIDATION_ERROR (table column is 255).
* Cache lookup DB error → falls through to normal processing (we
  prefer a duplicate write over a hard failure on the retry path).
* Cache store DB error → the response is still returned to the
  client; we log the cache failure and move on so the successful
  write is not lost.

## Request body

- WriteBatchRequestSchema: Request body for writing multiple atoms in one request.
  - `items` WriteRequestSchema[], required: List of atoms to write.
    - `content` string, required: The insight, pattern, or lesson to remember.
    - `concepts` string[]: Key concepts (used for Hebbian associations). At most 256 items — the write-time association pass is quadratic in this count.
    - `domain` string: Namespace: 'general', 'user:X', 'room:Y', 'project:Z'.
    - `fingerprint` string, nullable: Optional SHA256-like fingerprint for exact episodic match (e.g. task hash). At most 512 characters.
    - `metadata` object: Arbitrary key-value metadata. The serialized JSON must be at most 65536 bytes.
    - `external_ref` string, nullable: Client-provided unique reference for idempotent writes and cross-system correlation. May not start with `secret:`, the namespace of Vault secret aliases (400 VALIDATION_ERROR).
    - `supersedes` string[], nullable: Ids of atoms this write REPLACES. The successor is stored and every listed atom is marked as superseded by it, in one transaction — all of it or none. At most 32 ids, all distinct. Supersession is caller-asserted: the engine never infers that two atoms describe the same fact. Not accepted on write_batch, and not accepted alongside an `xroom:` domain.
    - `author` ProvenanceWriteSchema: SUPPLIER-vouched authorship sent in a write body (CN-001b). The reserved, optional ``author`` object a SERVICE/supplier caller may send to VOUCH for the participant behind a write (e.g. the supplier's own end user). It is HONORED ONLY for supplier/service tokens and IGNORED (silently dropped) for OIDC end-users — an end-user can never set authorship (see routes._get_provenance). Even for a supplier the shape is not blindly trusted: the route re-normalizes every field via auth._normalize_supplier_ author (client_env allow-list, length caps, is_external→bool). All fields are optional and loosely typed here so an odd supplier value is normalized rather than 422-ing the whole write; ``extra="forbid"`` still blocks unknown keys INSIDE the author object.
      - `principal` string, nullable: Participant the supplier vouches for.
      - `agent` string, nullable: Stable agent identifier, if any.
      - `agent_name` string, nullable: Human-readable agent name, if any.
      - `client_env` string, nullable: Calling client environment (cursor·claude·chatgpt·chat·api·unknown); anything else normalizes to null.
      - `is_external` boolean: True when the vouched principal is external to the org.

## Response `200`

Successful Response

- WriteBatchResponseSchema: Response after batch write.
  - `total_count` integer, required: Total number of atoms processed.
  - `stored_count` integer, required: Number of atoms stored (passed importance gate).
  - `results` WriteBatchItemResult[], required: Per-atom results in request order.
    - `index` integer, required: Position in the request items list.
    - `stored` boolean, required: True if the atom was stored.
    - `atom_id` string, uuid, nullable: UUID of stored atom, or None.
    - `importance` number: Computed importance score.
    - `reason` string: Engine-provided reason when the item was filtered instead of stored.
    - `error` string, nullable: Error message if item failed.

## Other responses

- `400`: Validation error
- `401`: Unauthorized
- `403`: Forbidden
- `404`: Not found
- `422`: Validation Error
- `429`: Rate limited
- `500`: Internal server error
- `503`: Service unavailable

---

[API](https://skmtc.dev/mnemoverse/apis/mnemoverse-core.md) · [All operations](https://skmtc.dev/mnemoverse/apis/mnemoverse-core/llms.txt) · [OpenAPI document](https://skmtc.dev/mnemoverse/apis/mnemoverse-core/revisions/1f5e0048f017?raw)
