Skip to main content

Overview

Send data directly to the Moda ingest API. This supports LLM conversations across any channel:
  • Standard chat completions (OpenAI, Anthropic, etc.)
  • Chat/messaging platforms (Slack, Discord, etc.)
  • Email conversations
  • Voice and video call transcripts
  • Tool/function calls within conversations
  • Custom integrations and languages without Moda SDK support
All events are processed by Moda and appear in your dashboard alongside SDK-ingested data.

Endpoint

POST https://moda-ingest.modas.workers.dev/v1/ingest

Authentication

Include your Moda API key in the Authorization header:
-H "Authorization: Bearer YOUR_MODA_API_KEY"

Request format

You can set the environment at the request level to apply to all events:
{
  "environment": "staging",
  "events": [...]
}
Send an array of events in the request body:
{
  "events": [
    {
      "conversation_id": "conv-123",
      "role": "user",
      "message": "What is the capital of France?",
      "timestamp": "2024-01-15T10:30:00Z"
    },
    {
      "conversation_id": "conv-123",
      "role": "assistant",
      "message": "The capital of France is Paris.",
      "timestamp": "2024-01-15T10:30:01Z",
      "input_tokens": 12,
      "output_tokens": 8,
      "model": "gpt-4o",
      "provider": "openai"
    }
  ]
}

Event fields

Required fields

FieldTypeDescription
conversation_idstringUnique ID for the conversation
rolestringOne of: user, assistant, system
message or contentstring (or array for content)The message content. Provide either message (string) or content (string or array of content blocks).

Optional fields

FieldTypeDescription
timestampstringISO 8601 timestamp (defaults to now)
trace_idstringFor linking related events (defaults to conversation_id)
user_idstringIdentifier for the end user
input_tokensnumberNumber of input/prompt tokens used
output_tokensnumberNumber of output/completion tokens used
reasoning_tokensnumberTokens used for extended thinking (Claude models)
modelstringModel name (e.g., gpt-4o, claude-3-opus)
providerstringProvider name (e.g., openai, anthropic)
environmentstringEnvironment name: development, staging, or production (defaults to production). Can also be set at the request level.
prompt_idstringPrompt template ID (for prompt management tracking)
prompt_namestringPrompt template name
prompt_versionstringPrompt template version
content_blocksarrayStructured content blocks (see below)
When using content as an array of content blocks, text is automatically extracted from text-type blocks for search and analytics. If both message and content are provided, message takes precedence.

Content blocks

For conversations with tool use, extended thinking, or images, include structured content blocks:
Block TypeFieldsDescription
texttextPlain text content
thinkingtextModel reasoning (extended thinking)
tool_usetool_name, tool_use_id, inputTool/function call
tool_resulttool_use_id, content, is_errorTool response
imagesourceImage (base64 or URL)
Example with tool use:
{
  "events": [{
    "conversation_id": "conv-123",
    "role": "assistant",
    "message": "Let me search for that.",
    "content_blocks": [
      {"type": "text", "text": "Let me search for that."},
      {"type": "tool_use", "tool_name": "web_search", "tool_use_id": "toolu_abc", "input": {"query": "latest news"}}
    ]
  }]
}
Example with extended thinking:
{
  "events": [{
    "conversation_id": "conv-123",
    "role": "assistant",
    "message": "The answer is 42.",
    "reasoning_tokens": 150,
    "output_tokens": 10,
    "content_blocks": [
      {"type": "thinking", "text": "Let me think through this step by step..."},
      {"type": "text", "text": "The answer is 42."}
    ]
  }]
}

Channel-specific event formats

LLM conversations happen across many channels beyond standard chat completions. Use the messageType field to send conversations from messaging platforms, email, voice calls, and to log tool invocations. You can mix these with standard events in the same request.

Channel messages

LLM-powered conversations in chat platforms or messaging systems.
{
  "messageType": "channel",
  "id": "msg-123",
  "conversationId": "thread-456",
  "message": "Hello, how can I help you today?",
  "role": "assistant",
  "userId": "user-789",
  "timestamp": "2025-01-04T12:00:00Z"
}
FieldTypeRequiredDescription
messageTypestringYesMust be "channel"
idstringYesUnique message ID
conversationIdstringYesThread/channel ID for grouping messages
messagestringYesMessage content
rolestringYesOne of: user, assistant, system
userIdstringNoUser identifier
timestampstringNoISO 8601 timestamp (defaults to now)
metadataobjectNoCustom metadata

Tool calls

Tool and function invocations made by the LLM during a conversation, with full request and response data.
{
  "messageType": "tool_call",
  "id": "tool-123",
  "conversationId": "thread-456",
  "name": "search_knowledge_base",
  "toolCallRequest": {
    "query": "refund policy",
    "limit": 5
  },
  "toolCallResponse": {
    "results": [
      {"title": "Refund Policy", "content": "..."}
    ]
  }
}
FieldTypeRequiredDescription
messageTypestringYesMust be "tool_call"
idstringYesUnique tool call ID
conversationIdstringYesConversation ID for grouping
namestringYesTool/function name
toolCallRequestanyNoRequest payload (can be any JSON)
toolCallResponseanyNoResponse payload (can be any JSON)
userIdstringNoUser identifier
timestampstringNoISO 8601 timestamp
metadataobjectNoCustom metadata

Emails

LLM-powered conversations happening over email, with threading support via conversationId and optional inReplyTo for referencing specific parent emails.
{
  "messageType": "email",
  "id": "email-123",
  "conversationId": "conv-456",
  "role": "user",
  "subject": "Re: Order #12345 - Shipping question",
  "body": "Thank you for reaching out. Your order is scheduled to arrive...",
  "inReplyTo": "parent-email-message-id",
  "from": "support@example.com",
  "to": ["customer@example.com"]
}
FieldTypeRequiredDescription
messageTypestringYesMust be "email"
idstringYesUnique email ID
conversationIdstringYesThread/conversation ID (groups emails together)
rolestringYes"user" (inbound) or "assistant" (outbound)
bodystringYesEmail body content
inReplyTostringNoEmail thread reference (e.g. parent email Message-ID)
subjectstringNoEmail subject line
fromstringNoSender email address
toarrayNoRecipient email addresses
userIdstringNoUser identifier
timestampstringNoISO 8601 timestamp
The conversationId field groups emails into conversations, consistent with all other event types. Use inReplyTo optionally to reference specific parent emails within a conversation (e.g. when there are multiple email threads within the same conversation).

Call transcripts

LLM-powered conversations happening over phone or video calls, with full speaker turn transcripts.
{
  "messageType": "call",
  "id": "call-123",
  "conversationId": "call-session-456",
  "transcript": [
    {"role": "user", "content": "Hi, I'm having trouble with my account."},
    {"role": "assistant", "content": "I'd be happy to help. Can you tell me more about the issue?"},
    {"role": "user", "content": "I can't log in after resetting my password."}
  ],
  "duration": 180,
  "callType": "phone"
}
FieldTypeRequiredDescription
messageTypestringYesMust be "call"
idstringYesUnique call ID
conversationIdstringYesCall session ID
transcriptarrayYesArray of transcript entries (see below)
durationnumberNoCall duration in seconds
callTypestringNoOne of: phone, video, voice
userIdstringNoUser identifier
timestampstringNoISO 8601 timestamp
metadataobjectNoCustom metadata
Transcript entry format:
FieldTypeRequiredDescription
rolestringYesSpeaker role (e.g., user, assistant, customer, agent)
contentstringYesWhat was said
timestampstringNoWhen this was said
Call transcripts are stored as a single record containing the full transcript. The complete transcript is searchable in the Moda dashboard, and the structured speaker turns are preserved for detailed analysis.

Mixed batch example

You can mix standard events and channel-specific events in the same request:
{
  "events": [
    {
      "conversation_id": "conv-123",
      "role": "user",
      "message": "What is the weather?"
    },
    {
      "messageType": "channel",
      "id": "msg-001",
      "conversationId": "support-thread-456",
      "message": "I need help with my subscription",
      "role": "user",
      "userId": "customer-789"
    },
    {
      "messageType": "tool_call",
      "id": "tool-001",
      "conversationId": "support-thread-456",
      "name": "get_subscription",
      "toolCallRequest": {"user_id": "customer-789"},
      "toolCallResponse": {"plan": "pro", "status": "active"}
    }
  ]
}
Each event is validated individually. Standard events (without messageType) use the fields documented above, while channel-specific events are validated according to their messageType schema.

Example

curl https://moda-ingest.modas.workers.dev/v1/ingest \
  -H "Authorization: Bearer YOUR_MODA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "events": [
      {
        "conversation_id": "conv-abc123",
        "role": "user",
        "message": "Hello, how are you?"
      },
      {
        "conversation_id": "conv-abc123",
        "role": "assistant",
        "message": "I am doing well, thank you for asking!",
        "input_tokens": 8,
        "output_tokens": 12,
        "model": "gpt-4o",
        "provider": "openai"
      }
    ]
  }'

Response

Success response

{
  "success": true,
  "count": 2,
  "requestId": "550e8400-e29b-41d4-a716-446655440000"
}
When the request includes channel-specific events (with messageType), the response includes a details breakdown:
{
  "success": true,
  "count": 4,
  "requestId": "550e8400-e29b-41d4-a716-446655440000",
  "details": {
    "channel": 2,
    "tool_call": 1,
    "email": 0,
    "call": 1,
    "call_transcript_messages": 0
  }
}

Error response

{
  "success": false,
  "count": 0,
  "message": "Invalid or missing API key",
  "requestId": "550e8400-e29b-41d4-a716-446655440000",
  "retryable": false
}
FieldTypeDescription
successbooleanWhether the request succeeded
countnumberNumber of events processed
requestIdstringUnique request ID for debugging
messagestringError message (on failure)
retryablebooleanWhether the error is temporary and should be retried
detailsobjectBreakdown by channel event type (only present when channel-specific events are included)

Batch limits

LimitValue
Max events per request1,000
Max message size100 KB
Max request size5 MB

Error handling

StatusMeaningRetryable
200Success-
400Invalid request formatNo
401Invalid or missing API keyNo
413Request too largeNo
503Service temporarily unavailableYes
For 503 errors, use exponential backoff when retrying. Start with 1 second and double each retry, up to a maximum of 30 seconds.