Base URL
/v1 prefix.
Authentication
Every endpoint exceptGET /health requires an API key (called an ingestion key in the dashboard) in the Authorization header:
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 optionalX-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. Returns200 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 sameconversation_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/develop → development, stg/stage → staging, prod → production); 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 (incontent 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
400 with a message naming the first failing event; nothing from the batch is stored:
503 with retryable: true:
POST /v1/ingest/multi
Ingests conversations that span channels: chat platform messages, tool invocations, emails, and call transcripts. Each event declares its shape withmessageType.
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.
/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
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.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-Typeheader containsprotobuf. - The body is a standard
ExportTraceServiceRequest(resourceSpans→scopeSpans→spans). - Conversations are extracted from GenAI span attributes: OTel GenAI semantic conventions (
gen_ai.*), OpenLLMetry (llm.prompts.*/llm.completions.*), Vercel AI SDK (ai.*), andmoda.*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.
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
- Reliability — status codes, retry guidance, and limits.
- OpenTelemetry — supported span attributes for
POST /v1/traces. - Prompt attribution — how the
prompt_*fields link events to prompt versions. - Authentication — creating and revoking ingestion keys.