Skip to main content
The Ingestion API accepts conversation data over plain HTTP. Use it when your language has no Moda SDK, when data originates outside your application process (backfills, message queues, webhook consumers), or when you need channel formats such as email and call transcripts.

Base URL

All endpoints live under this host. There is no path-level versioning beyond the /v1 prefix.

Authentication

Every endpoint except GET /health requires an API key (called an ingestion key in the dashboard) in the Authorization header:
Keys are created at Settings → Ingestion keys and are shown once at creation. A key is bound to your tenant and cannot be re-pointed. See Authentication for key management and revocation. Requests with a missing or invalid key return 401. The message field names the failure — Missing Authorization header when the header is absent, Invalid or expired API key otherwise:
The Bearer prefix is optional: a bare key in the Authorization header is also accepted. CLI session tokens are not valid here — only moda_sk_ API keys.

Request IDs

You can send an optional X-Request-ID header on any request. If the value is a valid UUID, it is echoed back as requestId in the JSON response; otherwise the server generates a random UUID. Use it to correlate requests, responses, and retries in your own logs.

GET /health

Health check. No authentication. Returns 200 with the plain-text body ok.

POST /v1/ingest

Sends conversation events as simple JSON. Each event is one message in a conversation. Events with the same conversation_id are grouped into one conversation.

Request body

string
Environment for all events in the request. Must be exactly development, staging, or production (defaults to production); any other value is rejected with 400. Individual events can override it with their own environment field.
array
Up to 1,000 events per request. An empty or absent array returns 200 with count: 0 and the message No events provided.

Event fields

string
required
ID of the conversation this event belongs to. Events sharing a conversation_id form one conversation.
string
required
Who produced the message — typically user, assistant, or system. Events with role assistant are recorded as model output; all other roles are recorded as input to the model.
string
The message text. Used when content and content_blocks are absent.
string | array
The message content, either as a string or as an array of content blocks (see below). If both content and message are present, content takes precedence.
array
Structured content blocks for tool use, extended thinking, or images (see below). Used when content is absent.
string
ISO 8601 date-time of the event, for example 2026-08-16T10:30:00Z. Defaults to the time the event is received. A timestamp that is present but unparseable is rejected with 400.
string
Groups related events under one trace. Defaults to conversation_id.
string
Identifier for the end user of the conversation.
number
Prompt (input) tokens used by the generation. Assistant events.
number
Completion (output) tokens used by the generation. Assistant events.
number
Tokens used for extended thinking or reasoning output.
number
Prompt tokens served from the provider’s cache.
number
Prompt tokens written to the provider’s cache.
string
Model ID, for example gpt-4o or claude-sonnet-4-20250514.
string
Provider name, for example openai or anthropic.
string
Why the model stopped generating, for example stop, length, or tool_calls. Assistant events.
string
Moda prompt ID, linking the event to a managed prompt. See Prompt attribution.
string
Name of the managed prompt used for this generation.
string
Version of the managed prompt used for this generation.
string
Moda prompt version ID, from .moda/prompts.lock.json.
string
In multi-agent systems, the agent that authored this event.
string
Your own stable, unique ID for this message.
string
Per-event override of the request-level environment. Common aliases are normalized (dev/developdevelopment, stg/stagestaging, prodproduction); unrecognized values fall back to production.
Events carrying a messageType field are rejected with 400. Channel-specific events (chat platforms, email, calls, tool invocations) belong on POST /v1/ingest/multi.

Content blocks

Use content blocks (in content or content_blocks) when a message contains more than plain text: Every block accepts an optional numeric index (defaults to the block’s position in the array). Anthropic API field names are also accepted: name is normalized to tool_name, id to tool_use_id, and a thinking field on thinking blocks to text.

Example

Success response:
Validation errors return 400 with a message naming the first failing event; nothing from the batch is stored:
Temporary ingestion outages return 503 with retryable: true:
The full status-code table and retry guidance are on the Reliability page.

POST /v1/ingest/multi

Ingests conversations that span channels: chat platform messages, tool invocations, emails, and call transcripts. Each event declares its shape with messageType.

Common event fields

string
required
Your stable, unique ID for the event (non-empty). It becomes the event’s external_message_id.
string
required
One of channel, tool_call, email, or call.
string
required
Conversation or thread ID that groups events, required for all four event types.
string
Identifier for the end user.
string
ISO 8601 date-time. Defaults to the time the event is received.
object
Free-form object. Accepted, but its contents are not currently attached to the stored message.
The request body has the same envelope as /v1/ingest: an optional request-level environment plus an events array of up to 1,000 events.

Type-specific fields

channel — a message in a chat or messaging platform: tool_call — a tool or function invocation: email — one email in a thread: call — a phone, video, or voice call. Each call is stored as a single record containing the full transcript:

Example

Success response — details breaks the accepted count down by event type:
details.call_transcript_messages is always 0; it is retained for backward compatibility. Calls are counted under details.call.
Validation failures return 400 with a per-event message (for example Event 0: Missing or invalid 'id' field), and nothing from the batch is stored.

POST /v1/traces

The OpenTelemetry OTLP/HTTP trace endpoint. Both Moda SDKs export here by default, and any OTLP-capable exporter can target it directly.
  • Accepts OTLP/JSON, and OTLP/protobuf when the Content-Type header contains protobuf.
  • The body is a standard ExportTraceServiceRequest (resourceSpansscopeSpansspans).
  • Conversations are extracted from GenAI span attributes: OTel GenAI semantic conventions (gen_ai.*), OpenLLMetry (llm.prompts.* / llm.completions.*), Vercel AI SDK (ai.*), and moda.* attributes for conversation ID, user ID, environment, and prompt attribution.
  • Only the 5 MB body cap applies; there is no per-request span count limit.
Responses: JSON requests get the standard envelope (200 with {"success": true, "count": N, "requestId": "..."}, 400 with the message Invalid OTLP format on parse failure). Successful protobuf requests get an OTLP ExportTraceServiceResponse body instead; parse and auth failures return the JSON envelope regardless of request format. See OpenTelemetry for the full list of supported span attributes and the conversation ID precedence rules.
Coding-agent telemetry (Claude Code, Codex, Cursor) uses dedicated /v1/otel/... routes documented on the coding agents page.

Next steps