> ## Documentation Index
> Fetch the complete documentation index at: https://plain-docs-orca-916-agent-docs-restructure.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Building agents on Plain

> Build a support agent on Plain, or connect one you already have, using the same API your team's app uses.

Plain is designed to be a great home for agents. You can build your own customer support agent using whatever AI stack you prefer and have it work alongside your team in the same threads, with the same tools, and the same audit trail as everyone else.

These docs are about the **plumbing** of building an agent on Plain: how it gets an identity, how it receives events, how it decides when to act, and how it replies. The AI part (e.g. model choice, prompts, RAG, tool use) is up to you.

<Note>
  To chat about support data with a local agent, the [MCP server](/integrations/mcp-server) is the fastest way to get a model talking to Plain. This guide is about building autonomous agents within Plain.
</Note>

## What an agent looks like in Plain

An agent in Plain is made of three pieces:

<Card title="A machine user" icon="user" href="/agents/machine-users">
  The agent's identity in Plain. Has a public name, an avatar, and one or more API keys.
</Card>

<Card title="A webhook listener" icon="webhook" href="/agents/support-agent">
  A public HTTPS endpoint that receives Plain events like new threads, incoming messages, and assignment changes.
</Card>

<Card title="A GraphQL client" icon="code" href="/graphql/sdk">
  Makes calls back to Plain to read threads, reply, change assignment, add labels, create notes, and more.
</Card>

When something happens in Plain (a customer sends an email, a user assigns a thread), Plain delivers a webhook to your endpoint. Your code decides whether the agent should act and uses the GraphQL API to do whatever it does: read context, reply, label, summarize, hand off, post a note, anything else.

Agents come in many shapes. Some reply to every new thread; some only act when assigned; some never reply at all and only classify, summarize, or post internal notes. The building blocks below are the same regardless of what your agent does.

## Two journeys

There are two places an agent can work in Plain, and they share only an identity and your knowledge sources. Pick the one you are building; the pages below are each complete on their own.

<Steps>
  <Step title="Create a machine user">
    Both journeys start here. A machine user is the agent's identity in Plain, with a public name, an avatar, and one or more API keys.

    [Set up a machine user →](/agents/machine-users)
  </Step>

  <Step title="Build a support agent, working customer threads">
    Your agent receives thread events, decides which threads are its own, reads it, and replies, labels, notes, or hands off to a person. One page covers identity through to the actions it takes.

    [Build a support agent →](/agents/support-agent)
  </Step>

  <Step title="Or build an internal agent, answering your team">
    Your team asks your agent questions inside Plain, where they ask Sidekick. It reports its progress and tool calls, and can put an approval in front of a person before it acts.

    [Build an internal agent →](/agents/internal-agent)
  </Step>

  <Step title="Ground it in your own content">
    Either agent can search your Help Center articles and indexed documents so its answers come from real content.

    [Search knowledge sources →](/agents/searching-knowledge)
  </Step>
</Steps>

## A minimal example

Here's one possible shape: an agent that replies to every new thread. It uses [Express](https://expressjs.com/), but the same shape works with any HTTP framework. Your own agent will likely do something different in the handler.

```ts theme={null}
import express from "express";
import { verifyPlainWebhook } from "@team-plain/webhooks";
import { PlainClient } from "@team-plain/graphql";

const plain = new PlainClient({ apiKey: process.env.PLAIN_API_KEY! });

const app = express();
app.use(express.text({ type: "*/*" })); // we need the raw body for signature verification

app.post("/webhooks/plain", async (req, res) => {
  const result = verifyPlainWebhook(
    req.body,
    req.header("plain-request-signature")!,
    process.env.PLAIN_WEBHOOK_SECRET!,
  );

  if (result.error) {
    return res.status(400).send(result.error.message);
  }

  const event = result.data;

  if (event.payload.eventType === "thread.thread_created") {
    const reply = await generateReply(event.payload.thread); // your AI goes here

    await plain.mutation.replyToThread({
      input: {
        threadId: event.payload.thread.id,
        textContent: reply,
      },
    });
  }

  res.sendStatus(200);
});

app.listen(3000);
```

Replace `generateReply` with whatever AI library you prefer, like the [Vercel AI SDK](https://ai-sdk.dev/), [Anthropic SDK](https://docs.claude.com/en/api/getting-started), [OpenAI SDK](https://platform.openai.com/docs/libraries), or your own.

## What's next

<CardGroup cols={2}>
  <Card title="Machine users" icon="user" href="/agents/machine-users">
    Create the agent's identity and API key.
  </Card>

  <Card title="Support agent" icon="headset" href="/agents/support-agent">
    Webhooks, routing, reading threads, and the five actions, in order.
  </Card>

  <Card title="Internal agent" icon="message-circle" href="/agents/internal-agent">
    Answer your own team in a Sidekick discussion, with an approval gate.
  </Card>

  <Card title="Searching knowledge" icon="search" href="/agents/searching-knowledge">
    Ground replies in your Help Center and indexed documents.
  </Card>

  <Card title="Agent integrations" icon="plug" href="/integrations">
    Connect Plain to your editor or a local AI assistant instead.
  </Card>
</CardGroup>
