---
title: "Create a memory store"
method: POST
path: "/v1/projects/{project_id}/memory-stores"
tags: ["MemoryStores"]
---

# Create a memory store

`POST /v1/projects/{project_id}/memory-stores`

## Request

- Base URL: `https://api.naturali.ai`
- URL: `https://api.naturali.ai/v1/projects/{project_id}/memory-stores`
- Auth: one of:
  - HTTP bearer
  - OAuth 2 (authorization code, authorization URL `https://api.naturali.ai/authorize`, token URL `https://api.naturali.ai/token`, scopes: `mcp:access`)

Creates a new memory store configuration in a project

## Request body

- object
  - `name` string, required: Memory store name
  - `description` string: Optional description
  - `tags` TagBag: Key-value labels on a resource. A flat object of string values — an array, a nested object or a number is rejected with `400 VALIDATION_FAILED`, never coerced. Keys are opaque and stored verbatim, so `cost_center` and `costCenter` are two different tags. Matched by JSONB containment wherever tags are read: the `?tags=` filter and knowledge search. Keys beginning `system.` are reserved: the platform writes them to record which conversation, actor, agent and role a row came from, and a write naming one is refused with `400 RESERVED_TAG_KEY`. They are read and filtered like any other tag. The bag is bounded, because every pair reaches the IAM context of every access check on the resource: at most 50 keys, each key at most 128 characters and each value at most 256. A write past a bound — including a merge that would grow the stored bag past the key count — is `400 VALIDATION_FAILED` with `meta.limit` naming the bound it crossed. `system.*` keys are the platform's and do not count against the 50.
  - `duplicate_threshold` number, nullable: The store's dedup policy: cosine similarity at or above which an incoming fact is already known and the write is skipped. `null` (the default) uses the algorithm constant `0.95`. A single `POST /v1/projects/{project_id}/memories` call may override it; the agent tool and the post-turn rule never can.
  - `supersede_threshold` number, nullable: Cosine similarity at or above which an incoming fact restates a known one that has changed: the matched memory is invalidated and replaced. `null` (the default) uses the algorithm constant `0.90`. Must be lower than the effective `duplicate_threshold`, or the write is rejected with `400 VALIDATION_FAILED` — equal makes `superseded` unreachable and inverted swallows `skipped`.

## Response `201`

Memory store created

- MemoryStore
  - `id` string
  - `project_id` string
  - `name` string
  - `description` string, nullable
  - `tags` TagBag: Key-value labels on a resource. A flat object of string values — an array, a nested object or a number is rejected with `400 VALIDATION_FAILED`, never coerced. Keys are opaque and stored verbatim, so `cost_center` and `costCenter` are two different tags. Matched by JSONB containment wherever tags are read: the `?tags=` filter and knowledge search. Keys beginning `system.` are reserved: the platform writes them to record which conversation, actor, agent and role a row came from, and a write naming one is refused with `400 RESERVED_TAG_KEY`. They are read and filtered like any other tag. The bag is bounded, because every pair reaches the IAM context of every access check on the resource: at most 50 keys, each key at most 128 characters and each value at most 256. A write past a bound — including a merge that would grow the stored bag past the key count — is `400 VALIDATION_FAILED` with `meta.limit` naming the bound it crossed. `system.*` keys are the platform's and do not count against the 50.
  - `duplicate_threshold` number, nullable: The store's duplicate cutoff, or `null` when it has never been set. Reported as `null` rather than as the constant it resolves to: a store with no policy must read differently from one pinned to today's default.
  - `supersede_threshold` number, nullable: The store's supersede cutoff, or `null` when it has never been set.
  - `created_at` string, date-time
  - `updated_at` string, date-time

## Other responses

- `400`: Bad request — a missing required field, a threshold outside `[0, 1]`, or a pair that is not `supersede_threshold < duplicate_threshold`.
- `401`: Unauthorized
- `403`: Forbidden
- `500`: Internal server error

---

[API](https://skmtc.dev/naturali/apis/naturali-ai-api.md) · [All operations](https://skmtc.dev/naturali/apis/naturali-ai-api/llms.txt) · [OpenAPI document](https://skmtc.dev/naturali/apis/naturali-ai-api/revisions/e4905b78f422?raw)
