Skip to main content
The ingest API lets you send message events into Modem from your own integrations. This is a server-to-server API for developers who want to push messages from tools Modem does not natively connect to yet.
Only message events are supported in this first pass: message.created, message.updated, and message.deleted.

Authentication

Send your project key in the x-modem-public-key header.
x-modem-public-key: modem_<32_hex_characters>
Project keys are managed per project, not per organization. To get a key:
  1. Open your organization in the Modem dashboard.
  2. Go to OrganizationProjects.
  3. Open the project you want to ingest into.
  4. In Public Keys, create a key or copy an existing active key.
Only org admins and owners can create, deactivate, or revoke keys.
Despite the name, treat this key like a server-side bearer credential. Do not ship it in browser code or mobile apps.

Endpoint

POST /ingest
Content-Type: application/json
Send a JSON event body with your project key in the x-modem-public-key header:
curl -X POST https://ingest.modem.dev/ingest \
  -H "Content-Type: application/json" \
  -H "x-modem-public-key: $MODEM_PUBLIC_KEY" \
  -d '{ ... }'

Event Envelope

Every request sends a single event.
FieldRequiredNotes
event_typeYesmessage.created, message.updated, or message.deleted
created_atYesISO 8601 timestamp for when your integration emitted the event
source_nonceNoUpstream delivery ID for deduplication — see Deduplication before using
clientYesProducer metadata for the integration sending the event
bodyYesEvent payload
Modem assigns received_at internally when the event is accepted and stored. Do not send received_at in the request body.

client

client.name identifies the producer that submitted the event to Modem.
  • Use a lowercase package-style identifier such as @your-company/teams-sync.
  • @modem/* is reserved for Modem-managed integrations.
  • client.name is descriptive producer metadata. It is not a trusted provenance boundary.
client.version must use semantic versioning, for example 1.0.0.

Message Payload

For message.created and message.updated, body looks like this:
{
    "data": {
        "source_name": "microsoft-teams",
        "source_message_id": "1709854200000",
        "source_message_type": "message",
        "created_at": "2026-03-09T15:30:00Z",
        "channel": {
            "source_name": "microsoft-teams",
            "server_name": "Acme Workspace",
            "source_server_id": "workspace-123",
            "channel_name": "support",
            "source_channel_id": "channel-456"
        },
        "author": {
            "source_author_id": "user-789",
            "display_name": "Jordan Lee",
            "email": "jordan@example.com"
        },
        "content": "We are seeing login failures after the latest deploy.",
        "source_url": "https://teams.microsoft.com/l/message/1709854200000"
    }
}

Required fields

FieldNotes
source_nameStable lowercase slug for the message source
source_message_idStable upstream message ID
source_message_typeStable upstream message classification, usually message
created_atMessage creation timestamp from the source
channelSource workspace and channel info
content, title, or attachmentsAt least one must be present

Important rules

  • channel.source_name must exactly match data.source_name.
  • source_name should stay stable within your organization. Modem uses it as part of deterministic IDs.
  • Custom sources use plain slugs directly in source_name. Do not prefix them with custom_.
  • Built-in values like slack, discord, and github remain reserved for those sources.

Threading and replies

Use these optional pairs when you have them:
  • source_thread_id and source_thread_type
  • source_reply_to_id and source_reply_to_type
Each pair must be sent together or omitted together.

Custom Sources

Custom message sources are supported for message events.
  • Good source_name values: microsoft-teams, intercom, zendesk-chat
  • Better when one upstream app needs multiple distinct feeds: teams-support, teams-sales
  • Avoid renaming a source later by changing the slug. That creates a new source identity inside Modem.
source_name is the source identity. There is no separate source_key.

Examples

message.created

{
    "event_type": "message.created",
    "created_at": "2026-03-09T15:30:01Z",
    "client": {
        "name": "@your-company/teams-sync",
        "version": "1.0.0"
    },
    "body": {
        "data": {
            "source_name": "microsoft-teams",
            "source_message_id": "1709854200000",
            "source_message_type": "message",
            "created_at": "2026-03-09T15:30:00Z",
            "channel": {
                "source_name": "microsoft-teams",
                "server_name": "Acme Workspace",
                "source_server_id": "workspace-123",
                "channel_name": "support",
                "source_channel_id": "channel-456"
            },
            "author": {
                "source_author_id": "user-789",
                "display_name": "Jordan Lee",
                "email": "jordan@example.com"
            },
            "content": "We are seeing login failures after the latest deploy.",
            "source_url": "https://teams.microsoft.com/l/message/1709854200000"
        }
    }
}

message.updated

{
    "event_type": "message.updated",
    "created_at": "2026-03-09T15:45:05Z",
    "client": {
        "name": "@your-company/teams-sync",
        "version": "1.0.0"
    },
    "body": {
        "data": {
            "source_name": "microsoft-teams",
            "source_message_id": "1709854200000",
            "source_message_type": "message",
            "created_at": "2026-03-09T15:30:00Z",
            "edited_at": "2026-03-09T15:45:00Z",
            "channel": {
                "source_name": "microsoft-teams",
                "server_name": "Acme Workspace",
                "source_server_id": "workspace-123",
                "channel_name": "support",
                "source_channel_id": "channel-456"
            },
            "content": "We are seeing login failures after the latest deploy. It started after build 412."
        }
    }
}

message.deleted

{
    "event_type": "message.deleted",
    "created_at": "2026-03-09T16:00:01Z",
    "client": {
        "name": "@your-company/teams-sync",
        "version": "1.0.0"
    },
    "body": {
        "data": {
            "source_name": "microsoft-teams",
            "source_message_id": "1709854200000",
            "source_message_type": "message",
            "deleted_at": "2026-03-09T16:00:00Z",
            "channel": {
                "source_name": "microsoft-teams",
                "server_name": "Acme Workspace",
                "source_server_id": "workspace-123",
                "channel_name": "support",
                "source_channel_id": "channel-456"
            }
        }
    }
}

Responses

Successful requests return 200 OK.
{
    "success": true,
    "id": "0195789f-2a65-7c58-9c50-9dbf5fbc5f91",
    "message": "Event queued for processing",
    "details": {
        "textLength": 57,
        "sourceType": "microsoft-teams"
    }
}
Common error responses:
StatusWhen it happens
400Invalid JSON, invalid schema, or no extractable text content
401Missing or invalid project key
405Method other than POST
500Event could not be queued

Processing Behavior

  • message.created stores the message, runs attachment OCR when needed, then sends the message into grouping and topic processing.
  • message.updated updates the stored message and can OCR newly added image attachments. It does not re-run grouping or topic generation for existing conversations.
  • message.deleted marks the stored message deleted and clears message content.

Deduplication

Most integrations should not set source_nonce. Only use it when your upstream provider has at-least-once delivery guarantees and may send you the same event more than once. If you are constructing events yourself, leave source_nonce empty.
source_nonce exists for one specific scenario: your upstream source gives you a delivery ID that is unique to each delivery attempt, and that source may retry or redeliver the same payload. Setting source_nonce to that delivery ID lets Modem drop the duplicate. Good example — GitHub webhooks: GitHub assigns every webhook delivery an X-GitHub-Delivery header. GitHub may redeliver the same webhook multiple times within seconds. Using that delivery ID as source_nonce is correct because it is scoped to the delivery, not to the underlying resource.
{
    "event_type": "message.created",
    "source_nonce": "a1f4b3c0-5d6e-7f8a-9b0c-1d2e3f4a5b6c",
    "...": "..."
}
Bad example — constructing a nonce from message properties: If you build a source_nonce from characteristics of the message itself (such as source_message_id, channel ID, or a combination of fields), you will block all future events that touch that message. A message.updated event with the same nonce as the original message.created would be silently dropped as a duplicate — the update never gets applied.
// ❌ Do not do this — a future update to this message would be deduplicated away
{
    "event_type": "message.created",
    "source_nonce": "microsoft-teams:channel-456:1709854200000",
    "...": "..."
}

How it works

When source_nonce is present, Modem generates a deterministic event ID from:
organization_id + source_name + source_nonce
A second request with the same nonce produces the same event ID and is treated as a duplicate delivery of the same event. This means the nonce must identify the delivery, not the resource. If you omit source_nonce, each request gets a unique event ID and Modem treats it as a new delivery — which is the correct behavior for most integrations.

Current Limits

  • Public ingest is message-only in this version.
  • Pull request and issue event families remain built-in-only.
  • There is no public OpenAPI endpoint yet.
  • Internal vs external producer provenance is not enforced by auth yet.
https://mintcdn.com/modem-844d7a4a/Wr2r4IRr97lNQiQb/icons/link.svg?fit=max&auto=format&n=Wr2r4IRr97lNQiQb&q=85&s=e774d33588635d44c6c935b78ed55f13

Integrations

See Modem’s built-in integrations and how they capture data.
https://mintcdn.com/modem-844d7a4a/Wr2r4IRr97lNQiQb/icons/shield.svg?fit=max&auto=format&n=Wr2r4IRr97lNQiQb&q=85&s=ee1ca3093fcbdf45ec5dc1a74652bed6

Security & Privacy

Review the current security model for data sent to Modem.