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

# Quickstart

> Create an Agent API channel and send your first signed customer message.

## Prerequisites

* A Sagepilot workspace with at least one published AI agent.
* Admin access to **Settings → Channels**.
* An HTTPS response URL that Sagepilot can reach.
* A backend that can generate HMAC-SHA256 signatures.

If you send attachments, their HTTPS host must be allowlisted for your Sagepilot environment and the files must be downloadable without authentication.

## 1. Create the channel

1. In the dashboard, go to **Settings → Channels** and click **Add channel**.
2. Choose **Agent API**.
3. Enter a channel name and select the default AI agent.
4. Enter the HTTPS **Response URL** where Sagepilot should deliver replies.
5. Click **Save**.
6. Copy the shared inbound endpoint, channel ID, and one-time signing key.
7. Confirm that you saved the signing key before closing the dialog.

<Warning>
  Store the signing key in your secret manager. It is shown only once. If channel creation succeeds but your browser does not receive the key, do not create a duplicate channel; contact Sagepilot support.
</Warning>

## 2. Build a customer-message event

Send this strict V1 JSON shape. Unknown fields are rejected.

```json theme={null}
{
  "version": "v1",
  "event_type": "message",
  "channel_id": "2c4bc69a-77f7-47ea-9d9a-44019e77a36e",
  "event_id": "evt-order-help-001",
  "occurred_at": "2026-08-16T10:30:00Z",
  "customer": {
    "external_id": "cust-4821",
    "properties": {
      "name": "Priya",
      "email": "priya@example.com",
      "phone": "+919876543210",
      "instagram_id": "17841400000000000",
      "plan": "premium"
    }
  },
  "message": {
    "external_id": "msg-001",
    "text": "Where is my order?",
    "attachments": []
  }
}
```

### Event fields

| Field                  | Required    | Description                                                                                                                           |
| ---------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `version`              | Yes         | Must be `v1`.                                                                                                                         |
| `event_type`           | Yes         | Must be `message` in V1.                                                                                                              |
| `channel_id`           | Yes         | Agent API channel UUID copied from the dashboard.                                                                                     |
| `event_id`             | Yes         | Stable ID for this delivery attempt. Reuse it only when retrying the identical event.                                                 |
| `occurred_at`          | Yes         | RFC 3339 timestamp for the customer event.                                                                                            |
| `customer.external_id` | Yes         | Stable customer ID, unique within your Sagepilot workspace.                                                                           |
| `customer.properties`  | No          | Up to 50 non-null string, number, or boolean profile values. Values enrich the customer record but never control identity or routing. |
| `message.external_id`  | Yes         | Stable ID for this customer message. Reuse it only when retrying the identical message.                                               |
| `message.text`         | Conditional | UTF-8 text, up to 10 KiB. Text, attachments, or both must be present.                                                                 |
| `message.attachments`  | No          | Up to five directly downloadable HTTPS attachment descriptors.                                                                        |

Do not include `conversation_id`, `thread_id`, or `chat_id`. These are not accepted inbound fields.

### Attachment object

```json theme={null}
{
  "external_id": "attachment-001",
  "url": "https://files.example.com/order-label.pdf",
  "file_name": "order-label.pdf",
  "mime_type": "application/pdf",
  "size": 4096
}
```

Attachment rules:

* `url` must use HTTPS, contain no credentials or fragment, require no authentication, and return the file directly without redirects.
* The URL hostname must be included in Sagepilot's configured attachment-source allowlist.
* `size` is the exact byte size and cannot exceed 10 MiB.
* Supported MIME types are PDF, PNG, JPEG, GIF, and WebP.
* `sha256` is optional. When supplied, it must be the lowercase SHA-256 digest of the file bytes.

## 3. Sign the exact request body

Generate one timestamp as Unix seconds or RFC 3339. Build the canonical byte sequence:

```text theme={null}
POST\n/webhooks/custom_channel\n{timestamp}\n{exact_raw_json_body}
```

Compute a lowercase hexadecimal HMAC-SHA256 using the channel signing key, then send it as `v1={hex_digest}`.

```javascript theme={null}
import { createHmac } from "node:crypto";

const endpoint = process.env.SAGEPILOT_AGENT_API_ENDPOINT;
const signingKey = process.env.SAGEPILOT_AGENT_API_SIGNING_KEY;
if (!endpoint || !signingKey) {
  throw new Error("Agent API endpoint and signing key are required");
}
const timestamp = Math.floor(Date.now() / 1000).toString();
const event = {
  version: "v1",
  event_type: "message",
  channel_id: "2c4bc69a-77f7-47ea-9d9a-44019e77a36e",
  event_id: "evt-order-help-001",
  occurred_at: new Date().toISOString(),
  customer: {
    external_id: "cust-4821",
    properties: { name: "Priya", email: "priya@example.com" },
  },
  message: {
    external_id: "msg-001",
    text: "Where is my order?",
    attachments: [],
  },
};
const rawBody = JSON.stringify(event);
const canonical = `POST\n/webhooks/custom_channel\n${timestamp}\n${rawBody}`;
const signature = createHmac("sha256", signingKey)
  .update(canonical)
  .digest("hex");

const response = await fetch(endpoint, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Sagepilot-Timestamp": timestamp,
    "X-Sagepilot-Signature": `v1=${signature}`,
  },
  body: rawBody,
});

console.log(response.status, await response.json());
```

Set `SAGEPILOT_AGENT_API_ENDPOINT` to the shared endpoint shown in your dashboard. Its path must be exactly `/webhooks/custom_channel`, and the signed raw bytes must be identical to the HTTP request body. Requests outside the configured replay window are rejected.

## 4. Handle the acknowledgment

A newly accepted event returns HTTP `202`:

```json theme={null}
{
  "accepted": true,
  "idempotent_replay": false
}
```

Retrying the identical accepted event with the same IDs returns `202` with `idempotent_replay: true`. Acknowledgment means Sagepilot accepted the inbound message; the AI or human-agent reply arrives later at your response URL.

See [Receiving replies](/api-reference/agent-api/receiving-replies) for the callback contract.

## Inbound error responses

| Status | Meaning                                                                                                                                                   |
| ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Invalid JSON, channel ID, event shape, or field value.                                                                                                    |
| `401`  | Missing, stale, malformed, or invalid request signature.                                                                                                  |
| `403`  | Unknown or disabled Agent API channel.                                                                                                                    |
| `413`  | Event body exceeds the 256 KiB limit.                                                                                                                     |
| `422`  | The accepted event cannot be processed, such as an invalid attachment.                                                                                    |
| `503`  | Ingress, configuration lookup, attachment download, or intake is temporarily unavailable. Retry the identical event and honor `Retry-After` when present. |
