> ## Documentation Index
> Fetch the complete documentation index at: https://modem.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Ingest API

> Send conversations from your own tools into Modem

The ingest API lets you push conversations from tools Modem does not natively connect to. You describe the places where conversations happen (a chat channel, a support ticket, a tracked issue) as **source containers**, then send the **messages** that belong to each one. Modem processes each source container's messages in order and aggregates what it finds into topics alongside your built-in integrations.

This is a server-to-server API. It is designed for a small sync service or webhook handler that you run, not for browser or mobile code.

## How Modem organizes ingested data

Modem's processing pipeline works on three things:

| Entity               | What it is                                                                                                                                          | Identified by                          |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- |
| **Source**           | A tool that produces conversations: your team chat, your help desk, your issue tracker.                                                             | `source_name`, a slug you choose       |
| **Source container** | One unit of conversation inside a source: a chat channel, a support ticket, a tracked issue, an email thread. Has a `kind` and a lifecycle `state`. | `source_name` + `kind` + `external_id` |
| **Message**          | One post, comment, or reply inside a source container.                                                                                              | `source_name` + `source_message_id`    |

<Frame>
  <img src="https://mintcdn.com/modem-844d7a4a/EdDjKGfipUMuZrlp/images/ingest-data-model.svg?fit=max&auto=format&n=EdDjKGfipUMuZrlp&q=85&s=84b3bddae9311ec5c457a13cc4b43923" alt="Three sources (team chat, help desk, issue tracker) each produce source containers (a channel, a ticket, an issue). Messages inside each source container are processed in order and aggregated into a topic." width="760" height="300" data-path="images/ingest-data-model.svg" />
</Frame>

Source containers are the unit of processing. Modem drains each source container's messages serially, uses the source container's title, description, and [kind](/docs/api/ingest/source-containers#source-container-kinds) as context for extraction, and reads the source container's lifecycle state (open, in progress, done, and so on) as evidence for whether the underlying work is resolved. A message is not processed until the source container it references exists.

Read [Source Containers](/docs/api/ingest/source-containers) first. It explains the source container kinds, how to pick a source container's identity, and how to map your tool's statuses onto Modem's lifecycle states. Then read [Messages](/docs/api/ingest/messages) for the message payload.

## The protocol

Every integration sends the same three things, in this order.

<Steps>
  <Step title="Create the source container">
    Send a `source_container.created` event the first time you see a channel, ticket, issue, or thread. This is a full snapshot: kind, identity, title, description, and lifecycle state.
  </Step>

  <Step title="Send messages that reference it">
    Send a `message.created` event for each post or comment. Every message carries a `source_container: { kind, external_id }` reference pointing at the source container it belongs to.
  </Step>

  <Step title="Keep the source container up to date">
    When the source container changes in your tool (a ticket is closed, an issue is reopened, a channel is archived), send a `source_container.updated` event with the new snapshot. State changes are evidence Modem uses when it assesses the lifecycle of the topics that source container contributes to.
  </Step>
</Steps>

<Note>
  Messages are queued against the source container they reference, and processing starts once that source container exists. It is fine for
  a message to arrive before its `source_container.created` event; it waits. See [Ordering and failure
  modes](/docs/api/ingest/source-containers#ordering-and-failure-modes).
</Note>

## Authentication

Send your project key in the `x-modem-public-key` header.

```http theme={null}
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 **Organization** → **Projects**.
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.

<Note>Despite the name, treat this key like a server-side bearer credential. Do not ship it in browser code or mobile apps.</Note>

## Endpoint

```http theme={null}
POST https://ingest.modem.dev/ingest
Content-Type: application/json
```

Every request carries exactly one event:

```bash theme={null}
curl -X POST https://ingest.modem.dev/ingest \
  -H "Content-Type: application/json" \
  -H "x-modem-public-key: $MODEM_PUBLIC_KEY" \
  -d '{ ... }'
```

## Event envelope

| Field          | Required | Notes                                                                                                              |
| -------------- | -------- | ------------------------------------------------------------------------------------------------------------------ |
| `event_type`   | Yes      | One of the [event types](#event-types) below                                                                       |
| `created_at`   | Yes      | ISO 8601 timestamp for when your integration observed this event. See the note below                               |
| `client`       | Yes      | Producer metadata for the integration sending the event                                                            |
| `body`         | Yes      | Event payload; shape depends on `event_type`                                                                       |
| `source_nonce` | No       | Upstream delivery ID for deduplication. Read [Deduplication](/docs/api/ingest/messages#deduplication) before using this |

Modem assigns `received_at` internally when the event is accepted. Do not send it.

<Note>
  `created_at` is the **observation time**: the moment your integration looked at the thing it is describing. For a webhook, that is when
  the webhook arrived. For a poll, that is when you fetched the record. Never stamp it later than the moment you looked (for example, with
  the time you dequeued the event), and never stamp a historical import with "now". Modem orders source container snapshots by this value,
  and a later timestamp on stale data will overwrite fresher data.
</Note>

### `client`

`client.name` identifies the producer that submitted the event.

* Use a lowercase package-style identifier such as `@your-company/helphub-sync`.
* `@modem/*` is reserved for Modem-managed integrations.
* `client.name` is descriptive metadata. It is not a trusted provenance boundary.

`client.version` must be a semantic version, for example `1.0.0`.

## Event types

| `event_type`               | What it does                                                          | Documented in                                                               |
| -------------------------- | --------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| `source_container.created` | Stands up a source container with a full snapshot                     | [Source Containers](/docs/api/ingest/source-containers#source_container-created) |
| `source_container.updated` | Replaces the source container snapshot (state, title, timestamps)     | [Source Containers](/docs/api/ingest/source-containers#source_container-updated) |
| `source_container.deleted` | Tombstones a source container                                         | [Source Containers](/docs/api/ingest/source-containers#source_container-deleted) |
| `message.created`          | Stores a message and queues it for processing in its source container | [Messages](/docs/api/ingest/messages#message-created)                            |
| `message.updated`          | Updates a stored message's content                                    | [Messages](/docs/api/ingest/messages#message-updated)                            |
| `message.deleted`          | Marks a message deleted and clears its content                        | [Messages](/docs/api/ingest/messages#message-deleted)                            |

All six share the [envelope](#event-envelope) above. Only `body` differs.

## Choosing a `source_name`

`source_name` is the identity of a source inside your organization. It appears on every source container and message event from that source, and Modem hashes it into the deterministic IDs of everything the source produces.

* Use a stable lowercase slug, usually the name of the tool: `helphub`, `bugtrack`, `chatterbox`.
* Use separate names when one upstream app feeds Modem two distinct streams you want to keep apart: `helphub-support`, `helphub-sales`.
* Do not prefix custom sources with `custom_`.
* Built-in integration names (`slack`, `discord`, `github`, `linear`, `intercom`, and so on) are reserved. Sending them with a non-Modem `client.name` returns `400`.
* Do not rename a source later. A new slug is a new source identity, and existing source containers and messages will not carry over.

## Responses

Successful requests return `200 OK`. The event is queued; processing happens asynchronously.

```json theme={null}
{
    "success": true,
    "id": "0195789f-2a65-7c58-9c50-9dbf5fbc5f91",
    "message": "Event queued for processing",
    "details": {
        "textLength": 57,
        "sourceType": "helphub"
    }
}
```

| Status | When it happens                                                                                           |
| ------ | --------------------------------------------------------------------------------------------------------- |
| `400`  | Invalid JSON, invalid schema, reserved `source_name`, or a message event with no extractable text content |
| `401`  | Missing or invalid project key                                                                            |
| `405`  | Method other than `POST`                                                                                  |
| `500`  | Event could not be queued                                                                                 |

A `200` means the event was accepted, not that it was processed. Schema validation happens synchronously; everything after that is asynchronous.

## Billing

Message events and source container events both count toward your ingest usage. Send one source container event per source container (plus one per lifecycle change), not one per message.

<Note>
  Source container events are not billed today. This is temporary; they will be billed alongside message events once the current pipeline
  rollout completes.
</Note>

## Current limits

* Pull request, transcript, and social feed source containers are produced by Modem's built-in integrations only. They are not yet available through this API. See [Source container kinds](/docs/api/ingest/source-containers#source-container-kinds).
* This endpoint is not yet part of the OpenAPI spec. The rest of the public API is documented under [API Reference](/docs/api-reference/introduction).
* `client.name` provenance is not enforced by auth yet.

## Next steps

<CardGroup cols={2}>
  <Card title="Source Containers" icon="https://mintcdn.com/modem-844d7a4a/Wr2r4IRr97lNQiQb/icons/label-alt-multiple.svg?fit=max&auto=format&n=Wr2r4IRr97lNQiQb&q=85&s=e7fbaf23c28396ac670b89cf38e1e4a6" href="/docs/api/ingest/source-containers" width="24" height="24" data-path="icons/label-alt-multiple.svg">
    Source container kinds, identity, lifecycle state, and the `source_container.*` events.
  </Card>

  <Card title="Messages" icon="https://mintcdn.com/modem-844d7a4a/Wr2r4IRr97lNQiQb/icons/mail.svg?fit=max&auto=format&n=Wr2r4IRr97lNQiQb&q=85&s=734a974951595d9c5b50b15bc65a73d1" href="/docs/api/ingest/messages" width="24" height="24" data-path="icons/mail.svg">
    The message payload, the source container reference, threading, and deduplication.
  </Card>

  <Card title="Integrations" icon="https://mintcdn.com/modem-844d7a4a/Wr2r4IRr97lNQiQb/icons/link.svg?fit=max&auto=format&n=Wr2r4IRr97lNQiQb&q=85&s=e774d33588635d44c6c935b78ed55f13" href="/docs/integrations/overview" width="24" height="24" data-path="icons/link.svg">
    Modem's built-in integrations, which use this same model internally.
  </Card>

  <Card title="Security & Privacy" icon="https://mintcdn.com/modem-844d7a4a/Wr2r4IRr97lNQiQb/icons/shield.svg?fit=max&auto=format&n=Wr2r4IRr97lNQiQb&q=85&s=ee1ca3093fcbdf45ec5dc1a74652bed6" href="/docs/features/security-privacy" width="24" height="24" data-path="icons/shield.svg">
    The security model for data sent to Modem.
  </Card>
</CardGroup>
