
# Build an Integration End-to-End

This guide walks through everything you need to run <span data-t="appName">Your AI Connector</span> from your own code, without ever opening the dashboard. By the end you will have built a minimal integration that:

1. Authenticates with an API key
2. Creates an AI Agent and configures its assistant behaviour
3. Connects a messaging channel (we use WhatsApp Web as the worked example) and points it at the Agent
4. Imports contacts
5. Sends and reads messages
6. Reads analytics
7. Subscribes to webhooks for real-time events

Every step links to the full resource guide so you can dig into the details when you need them. This page is the map; the resource guides are the territory.

> **Before you start.** API access is a paid feature. If your plan does not include it, every request returns `403`. See [API Access](../integrations/api-access.md) to confirm it is enabled, and [Authentication](authentication.md) for all the ways to pass your key.

All paths below are relative to the base URL:

```
https://api.youraiconnector.com/v1
```

---

## Step 1 — Get an API key and make your first request

Your API key lives in the app under **Settings → Integrations → API Key** — its own section under Integrations, separate from Webhooks, which only appears once API access is on the plan. Generate one, copy it, and store it somewhere safe (a server-side secret store or environment variable — never in browser code). Full instructions are in [API Access](../integrations/api-access.md).

Once you have a key, confirm it works by calling the health endpoint. There are several ways to send the key; the simplest is the `?apiKey=` query parameter, but for real code prefer the `X-API-Key` header so the key never ends up in server logs or browser history.

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/health?apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const BASE = "https://api.youraiconnector.com/v1";
const headers = { "X-API-Key": "YOUR_API_KEY", "Content-Type": "application/json" };

const res = await fetch(`${BASE}/health`, { headers });
const data = await res.json();
console.log(data); // { "success": true, ... }
```

**Python**

```python
import requests

BASE = "https://api.youraiconnector.com/v1"
HEADERS = {"X-API-Key": "YOUR_API_KEY", "Content-Type": "application/json"}

res = requests.get(f"{BASE}/health", headers=HEADERS)
print(res.json())  # { "success": true, ... }
```

Every successful response is wrapped in the same envelope — a `success: true` field plus the result data. Errors return `success: false` with an `error` message and an `error_code`. See [Errors & Pagination](errors-and-pagination.md) for the full list and for how list endpoints page with `?limit` and `?cursor`.

> **Rate limit.** Authenticated requests are capped at **300 per minute** (with a wider 1,200/minute ceiling per account). Going over returns `429`; back off and retry.

---

## Step 2 — Create an AI Agent

An **AI Agent** is the unit that holds your assistant's behaviour: its instructions, its goal, its active hours, and how it talks to contacts. It is what answers a conversation, so this is the natural first thing to create.

Create one with `POST /agents`. `name` is the only field worth sending up front; everything else can be set with the bot-config call below.

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/agents" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Inbound WhatsApp Leads",
    "language": "en"
  }'
```

**JavaScript**

```javascript
const res = await fetch(`${BASE}/agents`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    name: "Inbound WhatsApp Leads",
    language: "en",
  }),
});
const { agent_id } = await res.json();
```

**Python**

```python
res = requests.post(
    f"{BASE}/agents",
    headers=HEADERS,
    json={"name": "Inbound WhatsApp Leads", "language": "en"},
)
agent_id = res.json()["agent_id"]
```

A successful create returns `201` with the new ID:

```json
{
  "success": true,
  "agent_id": "abc123agent"
}
```

**Save the `agent_id`** — you will reference it when routing channels.

### Configure the assistant

`PUT /agents/{agentId}/bot-config` sets the assistant's behaviour. It *merges* the fields you send into the existing configuration, so anything you leave out is preserved:

```bash
curl -X PUT "https://api.youraiconnector.com/v1/agents/abc123agent/bot-config" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "instructions": "Greet warmly, answer questions about our services, and offer to book a call.",
    "goal": "Book a discovery call.",
    "ai_speed": "balanced"
  }'
```

Set active hours with `PUT /agents/{agentId}/active-hours` so the assistant only replies during business hours; outside those windows it does not reply automatically.

> **Knowledge base.** To have the assistant answer from your own content, attach FAQs. See the [FAQs guide](faqs.md).

> **Legacy: classic campaigns.** Accounts that still have a **Campaigns** page create the same assistant behaviour on a campaign instead (`POST /campaigns` with a `type` and a `bot` object, then `PUT /campaigns/{campaignId}/bot-config`). The full campaign field list and lifecycle controls are in the [Campaigns guide](campaigns.md). If you are building something new, create an Agent.

---

## Step 3 — Connect a channel

An Agent needs a way to send and receive messages. Seven connect flows can be driven from the API: WhatsApp Business, WhatsApp Web, Instagram and Messenger together (one shared Meta flow), Instagram personal accounts, Telegram, LINE, and Viber. The remaining channels — SMS, email, the chat widget and custom channels among them — are set up in the dashboard rather than over REST, and once they are connected the messaging, contact and routing endpoints work on them exactly the same way. `GET /channels` is the live source of truth for what a given account actually has connected:

```bash
curl "https://api.youraiconnector.com/v1/channels?apiKey=YOUR_API_KEY"
```

The full set of connect/disconnect flows for each channel is documented in the [Channels guide](channels.md). Below we walk through **WhatsApp Web** end to end, because it shows the most interesting pattern: a QR-code pairing flow that your wrapper has to render and poll.

### Worked example: pair WhatsApp Web by QR code

WhatsApp Web pairing is a three-call dance — **start**, **fetch the QR**, **poll until connected**.

**1. Start the pairing session.** Pass the number you want to connect in E.164 format.

```bash
curl -X POST "https://api.youraiconnector.com/v1/channels/whatsapp-web/connections" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "phone_number": "+15551230000" }'
```

```javascript
await fetch(`${BASE}/channels/whatsapp-web/connections`, {
  method: "POST",
  headers,
  body: JSON.stringify({ phone_number: "+15551230000" }),
});
```

```python
requests.post(
    f"{BASE}/channels/whatsapp-web/connections",
    headers=HEADERS,
    json={"phone_number": "+15551230000"},
)
```

**2. Fetch the QR code and show it to the user.** Poll this every 10–15 seconds. The response includes the raw `qr_code` payload (render it as a QR image yourself) and a ready-to-display `qr_data_url`.

```bash
curl "https://api.youraiconnector.com/v1/channels/whatsapp-web/connections/+15551230000/qr?apiKey=YOUR_API_KEY"
```

```json
{
  "success": true,
  "phone_number": "+15551230000",
  "status": "qr_pending",
  "qr_code": "2@abc...",
  "qr_data_url": "data:image/png;base64,iVBORw0KGgo..."
}
```

In your wrapper's UI, drop `qr_data_url` straight into an `<img src="...">` and ask the user to scan it from **WhatsApp → Linked Devices** on their phone. If the QR expires (a `410` response), restart from step 1 to get a fresh one.

**3. Poll the status until it connects.** After the user scans, keep polling the status endpoint until it reports `connected` (the service may also report `open`). Treat `disconnected` and `not_initialized` as terminal failures.

```python
import time

PHONE = "+15551230000"
while True:
    res = requests.get(
        f"{BASE}/channels/whatsapp-web/connections/{PHONE}/status",
        headers=HEADERS,
    )
    status = res.json()["status"]
    if status in ("connected", "open"):
        print("Connected!")
        break
    if status in ("disconnected", "not_initialized"):
        raise RuntimeError(f"Pairing failed: {status}")
    time.sleep(5)
```

```javascript
async function waitForConnection(phone) {
  while (true) {
    const res = await fetch(
      `${BASE}/channels/whatsapp-web/connections/${encodeURIComponent(phone)}/status`,
      { headers }
    );
    const { status } = await res.json();
    if (status === "connected" || status === "open") return;
    if (status === "disconnected" || status === "not_initialized") {
      throw new Error(`Pairing failed: ${status}`);
    }
    await new Promise((r) => setTimeout(r, 5000));
  }
}
```

> **Heads up.** Each connected WhatsApp Web number carries a recurring monthly maintenance charge until you disconnect it (`DELETE /channels/whatsapp-web/connections/{phoneNumber}`).

### Route the channel to your Agent

Connecting a channel makes it work; routing it tells the platform *which AI Agent* should answer brand-new inbound conversations on it. Set the channel-default Entry Point for the channel, naming the Agent you created in Step 2:

```bash
curl -X PUT "https://api.youraiconnector.com/v1/entry-points/channel-defaults" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "channel": "whatsapp_web", "agent_id": "abc123agent" }'
```

Repeat the call once per channel — one channel default per channel. To leave a channel with no Agent answering it, call `DELETE /entry-points/channel-defaults?channel=whatsapp_web`; to check whether the Entry Points ladder is live for the account, call `GET /entry-points/routing-status`. The older `POST /channels/campaign` map is retained for rollback only and is no longer consulted for inbound routing. See the [Channels guide](channels.md) for the other channel types and for the WhatsApp Business OAuth flow.

---

## Step 4 — Import your contacts

With a channel live, load the people you want to reach. The import endpoint takes up to **500 records per call**. Each record needs a `phone_number` in international format; everything else is optional. Records with bad numbers, unsupported channels, or numbers that already exist are skipped — and each skip is reported with its index and reason, so you can retry just the failures.

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/contacts/import" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "contacts": [
      { "phone_number": "+12025551234", "first_name": "Ann", "last_name": "Lee" },
      { "phone_number": "+12025551235", "first_name": "Bob" }
    ],
    "defaultChannel": "whatsapp_web"
  }'
```

**JavaScript**

```javascript
const res = await fetch(`${BASE}/contacts/import`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    contacts: [
      { phone_number: "+12025551234", first_name: "Ann", last_name: "Lee" },
      { phone_number: "+12025551235", first_name: "Bob" },
    ],
    defaultChannel: "whatsapp_web",
  }),
});
const result = await res.json();
console.log(`${result.imported} imported, ${result.skipped.length} skipped`);
```

**Python**

```python
res = requests.post(
    f"{BASE}/contacts/import",
    headers=HEADERS,
    json={
        "contacts": [
            {"phone_number": "+12025551234", "first_name": "Ann", "last_name": "Lee"},
            {"phone_number": "+12025551235", "first_name": "Bob"},
        ],
        "defaultChannel": "whatsapp_web",
    },
)
result = res.json()
print(f"{result['imported']} imported, {len(result['skipped'])} skipped")
```

The response tells you exactly what happened:

```json
{
  "success": true,
  "imported": 2,
  "contact_ids": ["contactId1", "contactId2"],
  "skipped": []
}
```

For one-at-a-time creation, list/lookup, lists, tags, and custom fields, see the [Contacts guide](contacts.md).

---

## Step 5 — Send and read messages

### Send a message

The simplest send is **channel-agnostic**: give the contact's identity and the message body, and the platform delivers it on whatever channel the contact is on. You can target by `contact_id`, or by `channel` plus the matching identity field (`phone_number` for WhatsApp/WhatsApp Web/SMS, `instagram_id` for Instagram, and so on).

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/contacts/send" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "whatsapp_web",
    "phone_number": "+12025551234",
    "body": "Hi Ann! Thanks for reaching out."
  }'
```

**JavaScript**

```javascript
const res = await fetch(`${BASE}/contacts/send`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    channel: "whatsapp_web",
    phone_number: "+12025551234",
    body: "Hi Ann! Thanks for reaching out.",
  }),
});
const { message_id } = await res.json();
```

**Python**

```python
res = requests.post(
    f"{BASE}/contacts/send",
    headers=HEADERS,
    json={
        "channel": "whatsapp_web",
        "phone_number": "+12025551234",
        "body": "Hi Ann! Thanks for reaching out.",
    },
)
message_id = res.json()["message_id"]
```

Delivery is **asynchronous** — a `201` means the message was *accepted and queued*, not yet delivered. (Contacts with do-not-disturb or private mode on are rejected with a `422`.)

```json
{
  "success": true,
  "message_id": "aB3dE5fG7hI9jK1lM2nO",
  "contact_id": "contact123",
  "channel": "whatsapp_web"
}
```

### Read a conversation

To read messages back, list them by contact, newest first, with cursor pagination. Pass the `next_cursor` from one response as the `cursor` of the next to walk back through the history.

```bash
curl "https://api.youraiconnector.com/v1/contacts/contact123/messages?limit=50&apiKey=YOUR_API_KEY"
```

```python
res = requests.get(
    f"{BASE}/contacts/contact123/messages",
    headers=HEADERS,
    params={"limit": 50},
)
page = res.json()
for msg in page["messages"]:
    print(msg)
next_cursor = page["next_cursor"]  # pass back as ?cursor= for the next page
```

You can also filter by content type (`?filter=text|media|tool_use`) or direction (`?direction=inbound|outbound`). The [Messages guide](messages.md) covers media attachments, marking messages read, and the per-session message views.

> **Don't poll for replies.** Listing messages on a timer works, but it wastes requests and adds lag. For incoming messages, use webhooks instead — that's Step 7.

---

## Step 6 — Read analytics

Once messages are flowing, the analytics summary gives you aggregated counts over a date range: sent, delivered, read, replied, booked, contacts created, and credits spent/recharged. You get both range totals and a zero-filled per-day series — perfect for a dashboard chart. Optionally scope it to a single campaign with `campaign_id` (the examples below use a placeholder campaign ID, `abc123campaign`); leave the parameter off for account-wide totals.

```bash
curl "https://api.youraiconnector.com/v1/analytics/summary?from=2026-05-01&to=2026-05-31&campaign_id=abc123campaign&apiKey=YOUR_API_KEY"
```

```javascript
const params = new URLSearchParams({
  from: "2026-05-01",
  to: "2026-05-31",
  campaign_id: "abc123campaign",
});
const res = await fetch(`${BASE}/analytics/summary?${params}`, { headers });
const { totals, by_date } = await res.json();
```

```python
res = requests.get(
    f"{BASE}/analytics/summary",
    headers=HEADERS,
    params={"from": "2026-05-01", "to": "2026-05-31", "campaign_id": "abc123campaign"},
)
data = res.json()
totals = data["totals"]
by_date = data["by_date"]
```

The range defaults to the last 30 days and is capped at 366. For credit-by-credit usage records and AI cost breakdowns, see the [Analytics guide](analytics.md).

---

## Step 7 — Subscribe to webhooks for real-time events

Polling is fine for a quick script, but a real integration should be **push-based**. Webhooks let the platform call *your* server the moment something happens — a new contact, a reply, a booked appointment, a chat concluded.

First, discover the exact event names you can subscribe to:

```bash
curl "https://api.youraiconnector.com/v1/webhooks/events?apiKey=YOUR_API_KEY"
```

```json
{
  "success": true,
  "events": [
    "Contact Created",
    "Human Alerted",
    "Appointment Booked",
    "Replies",
    "New Message",
    "Chat Concluded",
    "Task Created",
    "Daily Summary Created"
  ]
}
```

Then create a subscription pointing at an HTTPS URL on your server. Use the exact event strings from the call above.

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/webhooks" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://hooks.example.com/incoming",
    "subscribed_to": ["Contact Created", "Replies"],
    "name": "Lead updates hook"
  }'
```

**JavaScript**

```javascript
const res = await fetch(`${BASE}/webhooks`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    url: "https://hooks.example.com/incoming",
    subscribed_to: ["Contact Created", "Replies"],
    name: "Lead updates hook",
  }),
});
const { webhook_id } = await res.json();
```

**Python**

```python
res = requests.post(
    f"{BASE}/webhooks",
    headers=HEADERS,
    json={
        "url": "https://hooks.example.com/incoming",
        "subscribed_to": ["Contact Created", "Replies"],
        "name": "Lead updates hook",
    },
)
webhook_id = res.json()["webhook_id"]
```

```json
{
  "success": true,
  "webhook_id": "1",
  "webhook": {
    "id": "1",
    "name": "Lead updates hook",
    "url": "https://hooks.example.com/incoming",
    "subscribed_to": ["Contact Created", "Replies"],
    "subscribed_to_tags": [],
    "created_at": "2026-06-09T12:00:00.000Z"
  }
}
```

The URL must use HTTPS and be publicly reachable. From here on, your server receives a POST for each subscribed event. You can send a test delivery, check a subscription's health, and re-enable a subscription that was auto-disabled after repeated failures — see the [Webhooks guide](webhooks.md) and the integrations-level [Webhooks](../integrations/webhooks.md) page for payload shapes and verification.

---

## Putting it all together

Here is the whole flow at a glance:

| Step | Goal | Key call |
|---|---|---|
| 1 | Authenticate | `GET /health` |
| 2 | Create + tune the assistant | `POST /agents`, `PUT /agents/{id}/bot-config`, `PUT /agents/{id}/active-hours` |
| 3 | Connect a channel and route it | `POST /channels/whatsapp-web/connections` → poll QR + status → `PUT /entry-points/channel-defaults` |
| 4 | Load contacts | `POST /contacts/import` |
| 5 | Send & read | `POST /contacts/send`, `GET /contacts/{id}/messages` |
| 6 | Measure | `GET /analytics/summary` |
| 7 | React in real time | `POST /webhooks` |

A minimal wrapper is just these seven calls wired into your own UI. From there, layer in the per-resource guides as you need more:

- [Campaigns](campaigns.md) · [Contacts](contacts.md) · [FAQs](faqs.md) · [Messages](messages.md) · [Appointments](appointments.md)
- [Channels](channels.md) · [Templates](templates.md) · [Analytics](analytics.md) · [Webhooks](webhooks.md) · [API Keys](api-keys.md)
- New here? [Getting Started](getting-started.md) · [Authentication](authentication.md) · [Errors & Pagination](errors-and-pagination.md)

Stuck on something this guide does not cover? Email [<span data-t="supportEmail">hi@youraiconnector.com</span>](mailto:hi@youraiconnector.com).
