---
title: "Add a user message"
method: POST
path: "/v1/projects/{project_id}/sessions/{session_id}/messages"
tags: ["Sessions"]
---

# Add a user message

`POST /v1/projects/{project_id}/sessions/{session_id}/messages`

## Request

- Base URL: `https://api.naturali.ai`
- URL: `https://api.naturali.ai/v1/projects/{project_id}/sessions/{session_id}/messages`
- 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`)

Saves a user message to the session. When `auto_generate` is enabled on the session and no generation is currently in progress, generation is triggered automatically and the response is the generated turn, the same body `?wait=true` generate answers. Otherwise returns the saved user message.

## Path parameters

- `session_id` string, required

## Headers

- `Idempotency-Key` string

## Request body

- union
  - object
    - `message` string, required: User message text
    - `tool_context` object, nullable: Key-value pairs forwarded as `X-Naturali-Context-<key>` headers on every `http` and `mcp` tool call in this generation. The header name is the deployment's configured context prefix (`X-Naturali-Context-` by default) plus the key verbatim — no character is re-cased. Keys are never case-converted — they round-trip exactly as sent. An invalid or colliding key is rejected with `400 INVALID_TOOL_CONTEXT_KEY`.
    - `idempotency_key` string: Optional deduplication key scoped to this session. If a message with the same key already exists in the session, the original message is returned with HTTP 200 and no new message or generation is triggered. Reusing the key with a different `message`, `document_id` or `tool_context` is `409 IDEMPOTENCY_KEY_REUSED`.
  - object
    - `document_id` string, required: Public ID of a document used as the user message content.
    - `tool_context` object, nullable: Key-value pairs forwarded as `X-Naturali-Context-<key>` headers on every `http` and `mcp` tool call in this generation. The header name is the deployment's configured context prefix (`X-Naturali-Context-` by default) plus the key verbatim — no character is re-cased. Keys are never case-converted — they round-trip exactly as sent. An invalid or colliding key is rejected with `400 INVALID_TOOL_CONTEXT_KEY`.
    - `idempotency_key` string: Optional deduplication key scoped to this session. If a message with the same key already exists in the session, the original message is returned with HTTP 200 and no new message or generation is triggered. Reusing the key with a different `message`, `document_id` or `tool_context` is `409 IDEMPOTENCY_KEY_REUSED`.

## Response `200`

Duplicate request — original message returned (idempotency_key matched)

- AddSessionMessageSaved: Message saved; auto-generate is off or a generation is already in progress.
  - `role` 'user'
  - `content` string
  - `document_id` string, nullable

## Other responses

- `201`: User message saved
- `401`: Missing or invalid credentials.
- `403`: The credential is scoped to a different project, or the caller's role in the project does not carry this action.
- `404`: The resource does not exist (existence is not leaked).
- `409`: `IDEMPOTENCY_KEY_REUSED` — the key is already claimed by a message in this session sent from a different request. No message is saved.
- `429`: `QUOTA_EXCEEDED`: the session auto-generates and an `enforce`-mode generation quota is exhausted. `error.meta` carries `quota_id`, `metric`, `limit`, `window` and `resets_at`, with a `Retry-After` header. The message is saved. A session with `message_delay_seconds` answers `201` before the quota is read.

---

[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)
