
# Entry Points API

An **Entry Point** is a routing rule: "when this happens on this channel, hand the conversation to this Agent". Connecting a channel gets messages into the account and creating an Agent gives you something that can reply, but neither decides who answers a stranger's first message. Entry Points do. For the product itself, see the [Entry Points guide](../ai-agents/entry-points.md).

- **Base URL** — `https://api.youraiconnector.com/v1`
- **Authentication** — your API key (see [Authentication](authentication.md))
- **Errors & paging** — see [Errors & Pagination](errors-and-pagination.md)

All examples below show the `?apiKey=` query form in cURL and the `X-API-Key` header in JavaScript and Python — either works on every endpoint.

> **In the API explorer.** Every endpoint on this page is in the published OpenAPI specification, so you can browse its exact fields and run live requests in the [API explorer](reference.md).


---

## The one call most integrations need

Connect a channel, create an Agent, then point the channel at the Agent:

```bash
curl -X PUT "https://api.youraiconnector.com/v1/entry-points/channel-defaults?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "channel": "whatsapp", "agent_id": "ag7HkQ2ZpLxR3mNb" }'
```

That is the whole setup for "this Agent answers WhatsApp". Everything else on this page is for narrower rules (keywords, comments, new followers), several numbers on one channel, and reading back what is configured.

---

## How routing is decided

When a message arrives, the platform walks a fixed ladder and the first step that decides wins:

1. **A human has taken over** the conversation — no AI.
2. **The contact is already assigned to an Agent**, manually or because a conversation with that Agent is under way — the same Agent keeps it. Entry Points never move an existing conversation; to hand a chat to a different Agent, assign it (in the app, or with the [Automations](../automations/automations.md#actions) action).
3. **The contact is replying to a broadcast** — the broadcast's Agent answers, or nobody if the broadcast had none.
4. **A narrow Entry Point matches.** Keyword rules beat comment rules, which beat follower rules. Between two rules of the same kind, the most recently updated one wins.
5. **The channel default** for the channel the message arrived on. A default scoped to the specific number the contact wrote to beats the channel-wide default.
6. **Nothing matched** — the message lands in the inbox for your team and no assistant replies.

Two things soften step 6. An account with **exactly one active Agent** and no default configured for the channel still gets that Agent as the answerer, so a fresh account that connects WhatsApp and sends a test message does not get silence. That floor never applies to a channel that has a keyword rule (there, a message that matches no keyword is deliberately left for a human) and never overrides a channel you set to nobody (see [Leave a channel with nobody answering](#leave-a-channel-with-nobody-answering)).

Whether the ladder is live for an account is reported by `GET /entry-points/routing-status`. It is on for every account today; the call exists so an integration can check rather than assume.

---

## The Entry Point object

```json
{
  "id": "ep3KmQ8vTzXr5nWd",
  "type": "keyword",
  "channels": ["whatsapp", "instagram"],
  "agent_id": "ag7HkQ2ZpLxR3mNb",
  "enabled": true,
  "match_config": {
    "keywords": ["pricing", "quote"]
  },
  "first_response_mode": null,
  "first_response_exact_text": null,
  "public_comment_reply_exact_text": null,
  "created_at": 1700000000000,
  "last_modified_at": 1700000000000
}
```

| Field | Description |
|---|---|
| `id` | The rule's ID. |
| `type` | One of `channel_default`, `keyword`, `instagram_comment`, `facebook_comment`, `instagram_follower`. See [Rule types](#rule-types). |
| `channels` | The channels the rule covers: `whatsapp`, `whatsapp_web`, `instagram`, `instagram_private`, `messenger`, `telegram`, `sms`, `email`, `chat_widget`, `custom_channel`, `line`, `viber`, `tiktok`, `imessage`, `linkedin`, `skool`. Comment rules use `instagram` or `facebook`. |
| `agent_id` | The Agent the rule routes to. Empty on a channel default that is deliberately set to nobody. |
| `enabled` | `false` for a rule that has been retired. Retired rules are history, not live settings, and both come back from the list endpoints. |
| `match_config` | Type-specific settings — see [Rule types](#rule-types). Empty for a plain channel default. |
| `first_response_mode` | `ai` (default) lets the Agent write the first reply; `exact_text` sends `first_response_exact_text` verbatim. Honoured on comment rules today; accepted and stored on keyword rules but not yet used there. |
| `first_response_exact_text` | The fixed first DM when `first_response_mode` is `exact_text`. `{{first_name}}` is replaced with the person's first name, or "there" when it is unknown. |
| `public_comment_reply_exact_text` | Comment rules only: the fixed public reply under the comment. Blank skips the public reply; the DM still goes out. |
| `created_at`, `last_modified_at` | Epoch milliseconds. |

### Rule types

| `type` | Fires when | `match_config` |
|---|---|---|
| `channel_default` | A new, unknown contact writes in on one of the `channels`. | `phone_numbers` (optional) — scope the default to one connected number instead of the whole channel. See [One Agent per WhatsApp number](#one-agent-per-whatsapp-number). |
| `keyword` | A new contact's first message is one of the `keywords`. Matching ignores case and spaces, and a near miss ("info pls" against `INFO`) is still resolved by AI unless you set `fuzzy_match: false` — do that for promo codes and SKUs where a near miss must not count. Not applied on `sms` or `imessage`. | `keywords` (at least one, required), `fuzzy_match` (default `true`). |
| `instagram_comment` / `facebook_comment` | Someone comments on one of your posts. `channels` must include `instagram` or `facebook` respectively. | `keywords` (empty means every comment on the watched posts counts), `post_ids` (empty means all posts), `delay_minutes` (wait before the DM goes out), `reply_instructions` (how the Agent should word its reply). |
| `instagram_follower` | Someone new follows your Instagram account. Needs the [Instagram (Personal)](../messaging-channels/instagram-personal.md) connection — the official Instagram DMs connection cannot see followers. | `reply_instructions` (optional). |

A keyword rule on a channel with no channel default also works as a gate: messages that match none of the keywords get no automatic reply and simply land in your inbox, even on an account with a single Agent.

---

## Point a channel at an Agent

`PUT /entry-points/channel-defaults` — makes one Agent the answerer for new contacts on a channel. Any other Agent currently set as that channel's default is retired in the same call, so a channel always has exactly one answerer. Setting the Agent that already is the default changes nothing.

| Field | Required | Description |
|---|---|---|
| `channel` | Yes | The channel, for example `whatsapp`, `whatsapp_web`, `instagram`, `messenger`, `telegram`, `sms`, `email`, `chat_widget` or `custom_channel`. |
| `agent_id` | Yes | The Agent that should answer. Must belong to your account. |
| `phone_number` | No | Scope the default to one of your connected numbers on this channel (E.164 with the leading `+`, exactly as it shows under connected numbers). Leaves the channel-wide default untouched. See [One Agent per WhatsApp number](#one-agent-per-whatsapp-number). |

**cURL**

```bash
curl -X PUT "https://api.youraiconnector.com/v1/entry-points/channel-defaults?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "channel": "instagram", "agent_id": "ag7HkQ2ZpLxR3mNb" }'
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/entry-points/channel-defaults", {
  method: "PUT",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ channel: "instagram", agent_id: "ag7HkQ2ZpLxR3mNb" }),
});
const data = await res.json();
```

**Python**

```python
import requests

res = requests.put(
    "https://api.youraiconnector.com/v1/entry-points/channel-defaults",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"channel": "instagram", "agent_id": "ag7HkQ2ZpLxR3mNb"},
)
data = res.json()
```

**Response**

```json
{
  "success": true,
  "entry_point_id": "ep3KmQ8vTzXr5nWd",
  "disabled_entry_point_ids": ["epPrevious1234"]
}
```

`entry_point_id` is the rule now in force; `disabled_entry_point_ids` lists any rules retired to make room for it (empty when there was nothing to replace). Only contacts you have never spoken to are affected — anyone already in a conversation with an Agent keeps that Agent.

A `400` means `channel` or `agent_id` is missing, the Agent belongs to another account, or `phone_number` is not one of your connected numbers.

---

## See who answers each channel

`GET /entry-points/channel-defaults` — every channel default on the account, newest first, including retired ones (`enabled: false`) and a channel deliberately set to nobody (`agent_id: ""`). Filter on `enabled` yourself for the current picture.

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/entry-points/channel-defaults?apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/entry-points/channel-defaults", {
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
```

**Python**

```python
import requests

res = requests.get(
    "https://api.youraiconnector.com/v1/entry-points/channel-defaults",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
```

**Response**

```json
{
  "success": true,
  "entry_points": [
    {
      "id": "ep3KmQ8vTzXr5nWd",
      "type": "channel_default",
      "channels": ["whatsapp"],
      "agent_id": "ag7HkQ2ZpLxR3mNb",
      "enabled": true,
      "match_config": {},
      "created_at": 1700000000000,
      "last_modified_at": 1700000000000
    },
    {
      "id": "epAEnhHoozpoGVze",
      "type": "channel_default",
      "channels": ["whatsapp"],
      "agent_id": "agRotterdamBranch",
      "enabled": true,
      "match_config": { "phone_numbers": ["+31685101091"] },
      "created_at": 1700000000000,
      "last_modified_at": 1700000000000
    }
  ]
}
```

This is the account-wide read. Listing one Agent's rules with `GET /agents/{agentId}/entry-points` cannot show a channel set to nobody, because that rule belongs to no Agent.

---

## Leave a channel with nobody answering

`DELETE /entry-points/channel-defaults?channel=instagram` — retires the channel-wide default for one channel. The channel is named as a query parameter, not in a body. Add `&phone_number=%2B31685101091` to clear only that number's default and let the number go back to whoever answers the channel.

**cURL**

```bash
curl -X DELETE "https://api.youraiconnector.com/v1/entry-points/channel-defaults?channel=instagram&apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/entry-points/channel-defaults?channel=instagram",
  { method: "DELETE", headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();
```

**Python**

```python
import requests

res = requests.delete(
    "https://api.youraiconnector.com/v1/entry-points/channel-defaults",
    params={"channel": "instagram"},
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
```

**Response**

```json
{ "success": true, "disabled_entry_point_ids": ["ep3KmQ8vTzXr5nWd"] }
```

Safe to repeat: clearing a channel that has no default is a `200` with an empty list. Clearing means **unset, not silence** — on an account with exactly one active Agent, an unconfigured channel still falls back to that Agent. To keep the AI off a channel entirely, pick **No one's answering** for it in the app's **Who Answers New Conversations** panel (that writes an explicit "nobody" default which the fallback never overrides), or pause the Agent with `PATCH /agents/{agentId}/active`.

---

## One Agent per WhatsApp number

Routing is per channel by default: all your WhatsApp numbers share one answerer. With two or more numbers connected on WhatsApp Business or WhatsApp Web, a default can be scoped to a single number, so a business with a number per branch or brand can give each its own Agent inside one account.

Send `phone_number` with the set call:

```bash
curl -X PUT "https://api.youraiconnector.com/v1/entry-points/channel-defaults?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "whatsapp_web",
    "agent_id": "agRotterdamBranch",
    "phone_number": "+31685101091"
  }'
```

- The number must be one of your connected numbers on that channel, written as it shows under connected numbers (E.164 with the `+`); anything else is a `400`.
- The rule is stored as a channel default with `match_config.phone_numbers: ["+31685101091"]`. A message that arrives on that number goes to its Agent; every other number keeps following the channel-wide default.
- Setting or clearing the channel-wide default leaves number-scoped rules alone, and vice versa. Clear a number's own rule with `DELETE /entry-points/channel-defaults?channel=whatsapp_web&phone_number=%2B31685101091`.
- Replies always go out from the number the contact wrote to, so the contact keeps talking to the same number and the same Agent.

---

## Add a narrower rule

`POST /agents/{agentId}/entry-points` — creates a keyword, comment or follower rule (or a channel default, though `PUT /entry-points/channel-defaults` is the better call for that because it retires the previous answerer for you). The Agent in the path always wins: a rule can never be created for a different Agent than the one in the URL.

| Field | Required | Description |
|---|---|---|
| `type` | Yes | `keyword`, `instagram_comment`, `facebook_comment`, `instagram_follower` or `channel_default`. |
| `channels` | Yes | A non-empty list of the channels the rule covers. A comment rule must list its own channel (`instagram` or `facebook`). |
| `match_config` | Depends on type | See [Rule types](#rule-types). A keyword rule needs at least one entry in `keywords`. |
| `enabled` | No | Defaults to `true`. |
| `first_response_mode`, `first_response_exact_text`, `public_comment_reply_exact_text` | No | The first-response settings described in [The Entry Point object](#the-entry-point-object). |

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/agents/ag7HkQ2ZpLxR3mNb/entry-points?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "keyword",
    "channels": ["whatsapp", "instagram"],
    "match_config": { "keywords": ["pricing", "quote"] }
  }'
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/agents/ag7HkQ2ZpLxR3mNb/entry-points",
  {
    method: "POST",
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      type: "keyword",
      channels: ["whatsapp", "instagram"],
      match_config: { keywords: ["pricing", "quote"] },
    }),
  }
);
const data = await res.json();
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/agents/ag7HkQ2ZpLxR3mNb/entry-points",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "type": "keyword",
        "channels": ["whatsapp", "instagram"],
        "match_config": {"keywords": ["pricing", "quote"]},
    },
)
data = res.json()
```

**Response** (`201`)

```json
{ "success": true, "entry_point_id": "ep3KmQ8vTzXr5nWd" }
```

A comment-to-DM rule that only reacts to comments saying "LINK" on two specific posts, waits two minutes, and sends a fixed first message:

```json
{
  "type": "instagram_comment",
  "channels": ["instagram"],
  "match_config": {
    "keywords": ["LINK"],
    "post_ids": ["17895695668004550", "17841400008460056"],
    "delay_minutes": 2
  },
  "first_response_mode": "exact_text",
  "first_response_exact_text": "Hi {{first_name}}, here is the link you asked for: https://example.com/guide",
  "public_comment_reply_exact_text": "Sent you a DM!"
}
```

Leave `keywords` empty to DM everyone who comments on the watched posts, and `post_ids` empty to watch every post. A `400` names what is wrong: an unknown `type`, an empty `channels`, a keyword rule without keywords, or a comment rule that does not list its own channel.

---

## List an Agent's rules

`GET /agents/{agentId}/entry-points` — the rules that send conversations to this Agent, newest first: its channel defaults, keyword rules, comment rules and follower rules. Retired rules come back too, with `enabled: false`.

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/agents/ag7HkQ2ZpLxR3mNb/entry-points?apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/agents/ag7HkQ2ZpLxR3mNb/entry-points",
  { headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();
```

**Python**

```python
import requests

res = requests.get(
    "https://api.youraiconnector.com/v1/agents/ag7HkQ2ZpLxR3mNb/entry-points",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
```

**Response**

```json
{
  "success": true,
  "entry_points": [
    {
      "id": "ep3KmQ8vTzXr5nWd",
      "type": "keyword",
      "channels": ["whatsapp", "instagram"],
      "agent_id": "ag7HkQ2ZpLxR3mNb",
      "enabled": true,
      "match_config": { "keywords": ["pricing", "quote"] },
      "created_at": 1700000000000,
      "last_modified_at": 1700000000000
    }
  ]
}
```

---

## Change a rule

`PUT /entry-points/{entryPointId}` — changes one rule. Send only the fields you are changing; nested settings can be addressed leaf by leaf with a dotted key such as `"match_config.keywords"`. Whenever the change touches `type`, `channels` or `match_config`, the whole rule is re-checked, so a partial edit can never leave an unusable rule behind (switching `type` to `keyword` without supplying keywords is rejected). Sending `agent_id` hands the rule to another of your Agents; a blank one is rejected. Ownership and identity fields are ignored.

**cURL**

```bash
curl -X PUT "https://api.youraiconnector.com/v1/entry-points/ep3KmQ8vTzXr5nWd?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "match_config": { "keywords": ["pricing", "quote", "demo"] } }'
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/entry-points/ep3KmQ8vTzXr5nWd", {
  method: "PUT",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ match_config: { keywords: ["pricing", "quote", "demo"] } }),
});
const data = await res.json();
```

**Python**

```python
import requests

res = requests.put(
    "https://api.youraiconnector.com/v1/entry-points/ep3KmQ8vTzXr5nWd",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"match_config": {"keywords": ["pricing", "quote", "demo"]}},
)
data = res.json()
```

**Response**

```json
{ "success": true, "entry_point_id": "ep3KmQ8vTzXr5nWd" }
```

Other common edits: `{ "enabled": false }` retires a rule without deleting it, and `{ "agent_id": "agOtherAgent" }` moves it to a different Agent. An empty body returns `400` with `"No fields to update"`.

---

## Delete a rule

`DELETE /entry-points/{entryPointId}` — removes the rule permanently. Nothing else refers to an Entry Point, so there is nothing to detach first.

**cURL**

```bash
curl -X DELETE "https://api.youraiconnector.com/v1/entry-points/ep3KmQ8vTzXr5nWd?apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/entry-points/ep3KmQ8vTzXr5nWd", {
  method: "DELETE",
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
```

**Python**

```python
import requests

res = requests.delete(
    "https://api.youraiconnector.com/v1/entry-points/ep3KmQ8vTzXr5nWd",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
```

**Response**

```json
{ "success": true, "entry_point_id": "ep3KmQ8vTzXr5nWd" }
```

To stop a rule from firing but keep it around, set `enabled` to `false` instead. Channel defaults in particular are normally retired rather than deleted, which is what `DELETE /entry-points/channel-defaults` does.

---

## Check that routing is live

`GET /entry-points/routing-status` — returns whether the Entry Points ladder decides who answers on this account. Readable with view access, so a teammate sees the same answer the owner does.

```bash
curl "https://api.youraiconnector.com/v1/entry-points/routing-status?apiKey=YOUR_API_KEY"
```

```json
{ "success": true, "cutover_enabled": true }
```

It is `true` on every account today. The call is kept so an integration can verify before telling someone their routing change is live rather than assume it.

---

## The older, campaign-shaped calls

Two endpoints from before Agents still work for accounts organised around campaigns. New integrations should use the channel-defaults calls above instead.

- `PUT /channel-routing/{channel}` with `{ "campaignId": "cp5NbV8xQrT2wYzA" }` — names a campaign, and that campaign's Agent becomes the channel's answerer. `{ "campaignId": null }` clears the channel. An outgoing-only campaign is rejected because it has no inbound behaviour to offer.
- `POST /channel-routing/clear` with `{ "channels": ["whatsapp", "instagram"] }` — frees several channels from whichever Agent answers them in one call, typically before pointing them somewhere else. The response lists `released_channels`, the ones that actually had an answerer.

Both unset rather than silence: on an account with exactly one active Agent, a freed channel still falls back to that Agent.

---

## Entry Points API errors

Entry Point endpoints return the standard error envelope:

```json
{
  "success": false,
  "error": "Entry point not found"
}
```

| Status | When it happens on an Entry Point endpoint |
|---|---|
| `400` | A field is missing or the rule would be unusable: no `channel` or `agent_id` on a set call, an unknown `type`, an empty `channels`, a keyword rule without keywords, a comment rule that does not list its own channel, a blank `agent_id` on an update, an empty update body, or a `phone_number` that is not one of your connected numbers. |
| `403` | The key or team member may not edit routing. Writes need edit rights on campaigns; the list and status reads need view rights. |
| `404` | The Entry Point or Agent was not found — either it does not exist or it belongs to another account. |

The shared codes every endpoint can return — `401`, `403` (your plan does not include API access), `429` (rate limit) and `500` — are listed with retry guidance in [Errors & Pagination](errors-and-pagination.md).


---

## Next steps

- [Entry Points](../ai-agents/entry-points.md) — the concept, the rule types, and the **Who Answers New Conversations** panel in the app.
- [AI Agents API](agents.md) — create and configure the Agents these rules route to.
- [Channels API](channels.md) — connect the channels themselves.
- [Comment-to-DM Automation](../ai-automation/comment-to-dm.md) — what the comment rules do once they fire.
