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

# Connect agents to chat apps and schedules

> Bind a saved agent to a Telegram bot or WhatsApp number so chat messages become runs, and start runs on a one-time, interval, or cron schedule.

A [saved agent](/docs/managing-agents) runs when you call `POST /api/v1/agents/{agent_id}/runs`. Connectors and schedules run it for you.

* A connector binds the agent to a Telegram bot or a WhatsApp Business phone number. Every allowed inbound message becomes an agent run, and the reply goes back to the chat it came from.
* A schedule starts runs on a timer: once at a fixed time, every N seconds, or on a cron expression. The timer survives service restarts.

Both belong to the agent, so the agent ID is in every path. By the end you will have a Telegram bot and a WhatsApp number answering from one agent, and a cron schedule starting runs every weekday morning.

<Note>
  The [`mka1` CLI reference](/docs/cli/commands) does not cover connector or schedule commands yet, so this guide has no CLI tab. Use an SDK or curl.
</Note>

## Before you start

Connector management is team-level. Every connector endpoint returns `403` when the request carries `X-On-Behalf-Of`, so leave the header off all connector calls. Beyond that:

* Create and activate need an API key. They reject session authentication, because the connector keeps running in the background under that key. List, retrieve, delete, and the WhatsApp setup call also accept a console session.
* The key needs `write:agents` plus the runtime scopes `write:conversations`, `write:responses`, and `read:responses`. Listing and retrieving need `read:agents`. The key's scopes at creation time are a ceiling: a scope you add to the key later does nothing until you [activate](#activate-a-connector) the connector, and each message runs with the ceiling minus anything the key has since lost.
* Optional scopes: `write:feedback` turns thumbs-up and thumbs-down reactions into response feedback. `write:files` lets the connector receive photos and documents, `read:files` lets it deliver managed files, and a generated image needs both, because it is saved as a managed file before delivery. Text-only connectors need neither file scope.

Schedules take the same API key and, unlike connectors, accept `X-On-Behalf-Of`.

Each provider needs its own credentials:

| Provider        | Fields you send                                                                                                                           | Where they come from                                                                                                                                                                                                                                       |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Telegram        | `credentials.bot_token`                                                                                                                   | BotFather                                                                                                                                                                                                                                                  |
| WhatsApp (Beta) | `credentials.access_token`, `credentials.app_secret`, `account.app_id`, `account.whatsapp_business_account_id`, `account.phone_number_id` | A Meta app with the WhatsApp product: a long-lived system-user token with `whatsapp_business_messaging` and `whatsapp_business_management`, the app ID and secret, and the IDs of the WhatsApp Business Account (WABA) and phone number the app can access |

Every connector also needs an `access` allowlist with 1 to 100 entries: Telegram chat IDs (integers) or WhatsApp sender IDs (digit strings without a leading `+`). The connector acknowledges and drops messages from anyone else, and stores nothing. There is no public mode.

To find a Telegram chat ID, message the bot before you bind it, then open `https://api.telegram.org/bot<telegram-bot-token>/getUpdates` and read `message.chat.id`. Group and supergroup IDs are negative.

The console has the same controls under **Agents → your agent → Connectors** (`/agents/{agent_id}/connectors` on your cluster's console, for example platform.mka1.com).

## Connectors

### Connect a Telegram bot

Create a bot with BotFather, collect the chat IDs you want to allow, then bind the bot to the agent.

<CodeGroup>
  ```ts MKA1 SDK theme={null}
  import { SDK } from '@meetkai/mka1';

  const mka1 = new SDK({ bearerAuth: 'Bearer <mka1-api-key>' });

  // No xOnBehalfOf here: connector endpoints reject it with 403.
  const connector = await mka1.agentConnectors.createAgentConnector({
    id: 'agt_123',
    createAgentConnectorRequest: {
      provider: 'telegram',
      name: 'support-bot',
      credentials: { botToken: process.env.TELEGRAM_BOT_TOKEN! },
      access: { mode: 'allowlist', chatIds: [123456789] },
    },
  });

  console.log(connector.id, connector.status, connector.providerAccountName);
  ```

  ```csharp C# SDK theme={null}
  using MeetKai.MKA1;
  using MeetKai.MKA1.Types.Components;

  var sdk = new SDK(
      bearerAuth: "Bearer <mka1-api-key>",
      serverUrl: "https://apigw.mka1.com"
  );

  // No xOnBehalfOf here: connector endpoints reject it with 403.
  var created = await sdk.AgentConnectors.CreateAgentConnectorAsync(
      id: "agt_123",
      body: CreateAgentConnectorRequest.CreateTelegram(new CreateTelegramAgentConnectorRequest()
      {
          Provider = CreateTelegramAgentConnectorRequestProvider.Telegram,
          Name = "support-bot",
          Credentials = new CreateTelegramAgentConnectorRequestCredentials()
          {
              BotToken = Environment.GetEnvironmentVariable("TELEGRAM_BOT_TOKEN")!,
          },
          Access = new CreateTelegramAgentConnectorRequestAccess()
          {
              Mode = CreateTelegramAgentConnectorRequestMode.Allowlist,
              ChatIds = new List<long> { 123456789 },
          },
      })
  );

  var connector = created.AgentConnector!;
  Console.WriteLine($"{connector.Id} {connector.Status} {connector.ProviderAccountName}");
  ```

  ```python Python SDK theme={null}
  import os

  from meetkai_mka1 import SDK

  sdk = SDK(bearer_auth="Bearer <mka1-api-key>")

  # No x_on_behalf_of here: connector endpoints reject it with 403.
  connector = sdk.agent_connectors.create_agent_connector(
      id="agt_123",
      body={
          "provider": "telegram",
          "name": "support-bot",
          "credentials": {"bot_token": os.environ["TELEGRAM_BOT_TOKEN"]},
          "access": {"mode": "allowlist", "chat_ids": [123456789]},
      },
  )

  print(connector.id, connector.status, connector.provider_account_name)
  ```

  ```bash bash theme={null}
  # No X-On-Behalf-Of header: connector endpoints reject it with 403.
  curl https://apigw.mka1.com/api/v1/agents/agt_123/connectors \
    --request POST \
    --header 'Content-Type: application/json' \
    --header 'Authorization: Bearer <mka1-api-key>' \
    --data '{
      "provider": "telegram",
      "name": "support-bot",
      "credentials": {
        "bot_token": "<telegram-bot-token>"
      },
      "access": {
        "mode": "allowlist",
        "chat_ids": [123456789]
      }
    }'
  ```
</CodeGroup>

The call returns `201 Created` with a connector object (`con_...`). It does all of this before returning:

1. Validates the token with Telegram's `getMe`. A token Telegram rejects returns `400`. Telegram being unreachable returns `502` with `retryable` in the error details.
2. Stores the credentials, encrypted, and reserves the bot. Responses never include credentials; `credentials_configured` tells you they are stored. Binding the same bot a second time, from any team, returns `409`.
3. Registers the webhook with `drop_pending_updates` fixed at `true`, so messages sent to the bot before this binding are discarded.
4. Sets `status` to `active`.

If webhook registration fails, the connector is left in `status: "error"` with `last_error`, and the call returns `502`. Call [activate](#activate-a-connector) to retry once Telegram is reachable.

To check it works, send the bot a message from an allowed chat. The reply comes back in that chat, and in a private chat you see a draft that updates while the model streams. The run is scoped to the chat, not to your API key, so a plain listing of the agent's runs does not show it. Pass the chat's synthetic ID as `X-On-Behalf-Of`, using the connector's `provider_account_id` as the bot ID. The thread segment is `root` unless the message came from a forum topic, in which case it is the topic ID:

```bash theme={null}
curl https://apigw.mka1.com/api/v1/agents/agt_123/runs \
  --header 'Authorization: Bearer <mka1-api-key>' \
  --header 'X-On-Behalf-Of: telegram:<provider-account-id>:chat:123456789:thread:root'
```

### Connect a WhatsApp number (Beta)

WhatsApp needs an app-level webhook in Meta before the first phone connector exists, so this takes two calls.

First, ask for the webhook values for your Meta app:

<CodeGroup>
  ```ts MKA1 SDK theme={null}
  const setup = await mka1.agentConnectors.setupWhatsAppAppWebhook({
    id: 'agt_123',
    whatsAppWebhookSetupRequest: { appId: '300000000003' },
  });

  console.log(setup.result.callbackUrl);
  console.log(setup.result.verifyToken);
  ```

  ```csharp C# SDK theme={null}
  var setup = await sdk.AgentConnectors.SetupWhatsAppAppWebhookAsync(
      id: "agt_123",
      body: new WhatsAppWebhookSetupRequest() { AppId = "300000000003" }
  );

  Console.WriteLine(setup.WhatsAppWebhookSetup!.CallbackUrl);
  Console.WriteLine(setup.WhatsAppWebhookSetup!.VerifyToken);
  ```

  ```python Python SDK theme={null}
  setup = sdk.agent_connectors.setup_whats_app_app_webhook(
      id="agt_123",
      app_id="300000000003",
  )

  print(setup.result.callback_url)
  print(setup.result.verify_token)
  ```

  ```bash bash theme={null}
  curl https://apigw.mka1.com/api/v1/agents/agt_123/connectors/whatsapp/setup \
    --request POST \
    --header 'Content-Type: application/json' \
    --header 'Authorization: Bearer <mka1-api-key>' \
    --data '{"app_id": "300000000003"}'
  ```
</CodeGroup>

Copy `callback_url` and `verify_token` into the Meta app under **WhatsApp → Production setup → Configure Webhooks**, pick the WhatsApp Business Account object, and subscribe to the `messages` field. The verify token is stable: call setup again whenever you need it, since the connector object does not carry it. The response is sent with `Cache-Control: no-store`.

Then create the connector:

<CodeGroup>
  ```ts MKA1 SDK theme={null}
  const connector = await mka1.agentConnectors.createAgentConnector({
    id: 'agt_123',
    createAgentConnectorRequest: {
      provider: 'whatsapp',
      name: 'whatsapp-support',
      credentials: {
        accessToken: process.env.META_SYSTEM_USER_TOKEN!,
        appSecret: process.env.META_APP_SECRET!,
      },
      account: {
        appId: '300000000003',
        whatsappBusinessAccountId: '123456789012345',
        phoneNumberId: '109876543210987',
      },
      access: { mode: 'allowlist', userIds: ['15551234567'] },
    },
  });

  console.log(connector.id, connector.status);
  ```

  ```csharp C# SDK theme={null}
  var created = await sdk.AgentConnectors.CreateAgentConnectorAsync(
      id: "agt_123",
      body: CreateAgentConnectorRequest.CreateWhatsapp(new CreateWhatsAppAgentConnectorRequest()
      {
          Provider = CreateWhatsAppAgentConnectorRequestProvider.Whatsapp,
          Name = "whatsapp-support",
          Credentials = new CreateWhatsAppAgentConnectorRequestCredentials()
          {
              AccessToken = Environment.GetEnvironmentVariable("META_SYSTEM_USER_TOKEN")!,
              AppSecret = Environment.GetEnvironmentVariable("META_APP_SECRET")!,
          },
          Account = new Account()
          {
              AppId = "300000000003",
              WhatsappBusinessAccountId = "123456789012345",
              PhoneNumberId = "109876543210987",
          },
          Access = new CreateWhatsAppAgentConnectorRequestAccess()
          {
              Mode = CreateWhatsAppAgentConnectorRequestMode.Allowlist,
              UserIds = new List<string> { "15551234567" },
          },
      })
  );

  Console.WriteLine(created.AgentConnector!.Status);
  ```

  ```python Python SDK theme={null}
  connector = sdk.agent_connectors.create_agent_connector(
      id="agt_123",
      body={
          "provider": "whatsapp",
          "name": "whatsapp-support",
          "credentials": {
              "access_token": os.environ["META_SYSTEM_USER_TOKEN"],
              "app_secret": os.environ["META_APP_SECRET"],
          },
          "account": {
              "app_id": "300000000003",
              "whatsapp_business_account_id": "123456789012345",
              "phone_number_id": "109876543210987",
          },
          "access": {"mode": "allowlist", "user_ids": ["15551234567"]},
      },
  )

  print(connector.id, connector.status)
  ```

  ```bash bash theme={null}
  curl https://apigw.mka1.com/api/v1/agents/agt_123/connectors \
    --request POST \
    --header 'Content-Type: application/json' \
    --header 'Authorization: Bearer <mka1-api-key>' \
    --data '{
      "provider": "whatsapp",
      "name": "whatsapp-support",
      "credentials": {
        "access_token": "<meta-system-user-token>",
        "app_secret": "<meta-app-secret>"
      },
      "account": {
        "app_id": "300000000003",
        "whatsapp_business_account_id": "123456789012345",
        "phone_number_id": "109876543210987"
      },
      "access": {
        "mode": "allowlist",
        "user_ids": ["15551234567"]
      }
    }'
  ```
</CodeGroup>

Creation checks that the token belongs to `app_id`, carries both WhatsApp permissions, and can see `phone_number_id` under the business account. It then subscribes the app to the WABA, if it is not already subscribed, and registers a callback for this phone number. Messages that Meta still sends to the app-level webhook reach the connector too, matched by `phone_number_id`.

WhatsApp replies are session messages: Meta accepts them inside the 24-hour window that a user's message opens. The connector never sends template messages, so it cannot start a conversation on its own.

To check it works, send a message from an allowed number to the business number; the reply comes back in the same WhatsApp chat. Meta's test number is enough for this while you develop. Connector runs are scoped to the sender, so list them with `X-On-Behalf-Of: whatsapp:<phone-number-id>:user:15551234567`.

### List, retrieve, and delete connectors

List the agent's connectors, or read one to see its `status` and `last_error`:

<CodeGroup>
  ```ts MKA1 SDK theme={null}
  const connectors = await mka1.agentConnectors.listAgentConnectors({ id: 'agt_123' });
  console.log(connectors.data.map((c) => `${c.id} ${c.provider} ${c.status}`));

  const connector = await mka1.agentConnectors.getAgentConnector({
    id: 'agt_123',
    connectorId: 'con_123',
  });
  console.log(connector.lastError);
  ```

  ```csharp C# SDK theme={null}
  var list = await sdk.AgentConnectors.ListAgentConnectorsAsync(id: "agt_123");
  foreach (var c in list.AgentConnectorList!.Data)
  {
      Console.WriteLine($"{c.Id} {c.Provider} {c.Status}");
  }

  var detail = await sdk.AgentConnectors.GetAgentConnectorAsync(
      id: "agt_123",
      connectorId: "con_123"
  );
  Console.WriteLine(detail.AgentConnector!.LastError);
  ```

  ```python Python SDK theme={null}
  connectors = sdk.agent_connectors.list_agent_connectors(id="agt_123")
  print([(c.id, c.provider, c.status) for c in connectors.data])

  connector = sdk.agent_connectors.get_agent_connector(
      id="agt_123",
      connector_id="con_123",
  )
  print(connector.last_error)
  ```

  ```bash bash theme={null}
  curl 'https://apigw.mka1.com/api/v1/agents/agt_123/connectors?limit=20&order=desc' \
    --header 'Authorization: Bearer <mka1-api-key>'

  curl https://apigw.mka1.com/api/v1/agents/agt_123/connectors/con_123 \
    --header 'Authorization: Bearer <mka1-api-key>'
  ```
</CodeGroup>

To delete a connector:

<CodeGroup>
  ```ts MKA1 SDK theme={null}
  const deleted = await mka1.agentConnectors.deleteAgentConnector({
    id: 'agt_123',
    connectorId: 'con_123',
  });
  console.log(deleted.deleted); // true
  ```

  ```csharp C# SDK theme={null}
  var deleted = await sdk.AgentConnectors.DeleteAgentConnectorAsync(
      id: "agt_123",
      connectorId: "con_123"
  );
  Console.WriteLine(deleted.AgentConnectorDeleted!.Deleted);
  ```

  ```python Python SDK theme={null}
  deleted = sdk.agent_connectors.delete_agent_connector(
      id="agt_123",
      connector_id="con_123",
  )
  print(deleted.deleted)  # True
  ```

  ```bash bash theme={null}
  curl https://apigw.mka1.com/api/v1/agents/agt_123/connectors/con_123 \
    --request DELETE \
    --header 'Authorization: Bearer <mka1-api-key>'
  ```
</CodeGroup>

```json theme={null}
{
  "object": "agent.connector.deleted",
  "id": "con_123",
  "deleted": true
}
```

Delete unregisters the webhook, fails any events still in flight, erases the stored credentials, and marks the connector `deleted`. Telegram drops its pending updates. WhatsApp clears only this number's callback and leaves the app subscription in place for other numbers. If the provider cannot confirm the cleanup, for example because the token was already revoked, the connector moves to `error` with `last_error.operation` set to `unregister_webhook` and the call returns `502`. Repeat the `DELETE` to retry; the bot or number stays reserved until cleanup is confirmed.

There is no update call. To change the allowlist or the credentials, delete the connector and create it again.

An agent with connectors cannot be deleted. `DELETE /api/v1/agents/{agent_id}` returns `409` until every connector is gone.

### Activate a connector

Activate re-runs credential validation and webhook registration with the stored credentials, under the calling key:

<CodeGroup>
  ```ts MKA1 SDK theme={null}
  const connector = await mka1.agentConnectors.activateAgentConnector({
    id: 'agt_123',
    connectorId: 'con_123',
  });

  console.log(connector.status, connector.lastError);
  ```

  ```csharp C# SDK theme={null}
  var activated = await sdk.AgentConnectors.ActivateAgentConnectorAsync(
      id: "agt_123",
      connectorId: "con_123"
  );

  Console.WriteLine(activated.AgentConnector!.Status);
  ```

  ```python Python SDK theme={null}
  connector = sdk.agent_connectors.activate_agent_connector(
      id="agt_123",
      connector_id="con_123",
  )

  print(connector.status, connector.last_error)
  ```

  ```bash bash theme={null}
  curl https://apigw.mka1.com/api/v1/agents/agt_123/connectors/con_123/activate \
    --request POST \
    --header 'Authorization: Bearer <mka1-api-key>'
  ```
</CodeGroup>

Use it when a connector shows `error` (webhook registration failed, or the key behind it was revoked) and after you rotate the API key. The rules:

* Only the user who created the connector can activate it; anyone else gets `403`. Teammates can list, inspect, and delete a team connector, but to run it under another user's key, delete and recreate it.
* The stored credentials must still resolve to the same bot or phone number. If they do not, the call returns `409` and the connector moves to `error` with `last_error.operation` set to `register_webhook`.
* A connector that is being deleted, or that has no stored credentials, returns `409`.
* Success sets `status` to `active`, clears `last_error`, and replaces the stored scope ceiling with the calling key's current scopes.

### How a message becomes a run

1. The provider posts to the connector's webhook. Telegram requests must carry the secret header set at registration (`401` otherwise). WhatsApp requests must carry a valid `X-Hub-Signature-256` HMAC over the raw body. These signatures are the only authentication on the webhook paths.
2. The connector checks its status. `provisioning` or `activating` returns `503`, so the provider retries after the binding commits. Any other non-`active` status, or a deleted agent, is acknowledged and dropped.
3. The connector acknowledges and drops a message from any chat or sender outside the allowlist, and stores nothing.
4. The message is stored as an event, unique per connector and provider update ID. A redelivered update is ignored. Each chat gets one thread: a Telegram chat or forum topic, or a WhatsApp sender under the phone number.
5. Events in one thread are processed one at a time, in arrival order, so replies in one chat never overtake each other. A message that could not be queued when it arrived is picked up within 30 seconds.
6. Before running, the connector re-checks its stored API key. A revoked, disabled, or expired key, a removed team membership, a suspended organization, or a missing runtime scope moves the connector to `error` with `last_error.code` set to `connector_identity_revoked` and fails the event. If the check itself cannot complete, the event waits and is retried.
7. Photos and documents are downloaded from the provider (Telegram `getFile`, WhatsApp media IDs; 20 MiB cap for both) and uploaded to the Files API. The run input carries `input_image` and `input_file` items that reference those files. Provider URLs and tokens are never persisted.
8. Each thread gets one gateway conversation, created on its first message and reused afterwards. Sending `/start` in a private Telegram chat resets the thread before the next run, so the reply starts a fresh conversation.
9. The connector creates an agent run against the agent's current configuration, with `metadata` carrying `agent_connector_id`, `connector_event_id`, `connector_provider`, and `connector_actor_id`. The run is stamped with a synthetic end-user ID for the chat: `telegram:<bot-id>:chat:<chat-id>:thread:<topic-id>` (`root` when the chat has no topic) or `whatsapp:<phone-number-id>:user:<sender-id>`. `GET /api/v1/agents/{agent_id}/runs` filters by that scope, so a plain API key without `X-On-Behalf-Of` does not list connector runs. Org admins see them in the team-wide list, and any caller can pass the synthetic ID as `X-On-Behalf-Of` to read one chat's runs.
10. The final text is split into provider-sized parts and sent, followed by any generated images or files. Private Telegram chats also get a live draft preview while the model streams; Telegram groups and supergroups get a typing indicator instead. Delivery is at-least-once: a crash between the provider accepting a part and its message ID being committed can produce one duplicate on recovery.
11. When the key has `write:feedback`, a thumbs-up or thumbs-down reaction on any part of the reply becomes feedback on the gateway response, and removing the reaction clears it. Only the user whose message produced the reply can rate it.

Provider limits:

| Limit                      | Telegram                                                                 | WhatsApp                                                   |
| -------------------------- | ------------------------------------------------------------------------ | ---------------------------------------------------------- |
| Inbound media download cap | 20 MiB                                                                   | 20 MiB                                                     |
| Outbound photo             | 10 MiB; larger photos, or photos Telegram rejects, are sent as documents | JPEG or PNG, 5 MiB                                         |
| Outbound document          | 50 MiB                                                                   | 50 MiB; plain text, PDF, Word, PowerPoint, and Excel types |
| Text per message           | 32,768 code points                                                       | 4,096 code points                                          |
| Feedback                   | Native reactions in private chats, groups, and supergroups               | Native thumbs reactions                                    |

Telegram albums are not merged: each item in a media group is its own update and therefore its own run. Managed files created for connector input and output are kept so retries and conversation history keep working; the connector does not delete them.

### Connector reference

Connector object fields:

| Field                                    | Description                                                                                                                                                                                                                                                                                                          |
| ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                                     | `con_...`                                                                                                                                                                                                                                                                                                            |
| `agent_id`                               | The agent the connector belongs to.                                                                                                                                                                                                                                                                                  |
| `provider`                               | `telegram` or `whatsapp`.                                                                                                                                                                                                                                                                                            |
| `name`                                   | Optional label, 1 to 120 characters.                                                                                                                                                                                                                                                                                 |
| `status`                                 | See the status table below.                                                                                                                                                                                                                                                                                          |
| `provider_account_id`                    | Telegram bot user ID, or WhatsApp phone number ID.                                                                                                                                                                                                                                                                   |
| `provider_account_name`                  | Bot username or display phone number, when the provider returned one.                                                                                                                                                                                                                                                |
| `config`                                 | `webhook_path` and the stored `access` allowlist. Telegram connectors also record `allowed_updates` (`message`, `message_reaction`, `callback_query`).                                                                                                                                                               |
| `credentials_configured`                 | `true` while encrypted credentials are stored.                                                                                                                                                                                                                                                                       |
| `last_error`                             | `null`, or an object with `provider`, `operation` (`register_webhook`, `unregister_webhook`, or `resolve_identity`), `message`, and, depending on the failure, `code` (`connector_identity_revoked` when the stored key no longer passes), `http_status`, `error_code`, `error_subcode` (WhatsApp), and `retryable`. |
| `created_at`, `updated_at`, `deleted_at` | ISO 8601 timestamps.                                                                                                                                                                                                                                                                                                 |

Connector statuses:

| Status         | Meaning                                                                                                 |
| -------------- | ------------------------------------------------------------------------------------------------------- |
| `provisioning` | Credentials validated and stored; the webhook is not registered yet. Webhook traffic is asked to retry. |
| `activating`   | Webhook registration in progress, at creation or after activate.                                        |
| `active`       | Receiving messages and running the agent.                                                               |
| `error`        | Registration, cleanup, or the key check failed. Read `last_error`, fix the cause, then activate.        |
| `deleting`     | Provider cleanup in progress. No new work starts.                                                       |
| `deleted`      | Gone. Deleted connectors are not listed.                                                                |

## Schedules

A schedule stores a run request (`input`, optional `conversation`, `previous_response_id`, `context_management`, and `metadata`) plus a timing rule, and starts a normal agent run each time the rule fires. The page calls one firing a tick.

### Create a schedule

<CodeGroup>
  ```ts MKA1 SDK theme={null}
  import { SDK } from '@meetkai/mka1';

  const mka1 = new SDK({ bearerAuth: 'Bearer <mka1-api-key>' });

  const schedule = await mka1.agentSchedules.createAgentSchedule({
    agentId: 'agt_123',
    xOnBehalfOf: '<end-user-id>', // optional — attribute the request to one of your end users
    createAgentScheduleRequest: {
      name: 'weekday-morning-briefing',
      schedule: {
        type: 'cron',
        cronExpression: '0 9 * * 1-5',
        timezone: 'America/Los_Angeles',
      },
      input: 'Summarize the release notes published in the last 24 hours.',
      metadata: { source: 'briefing' },
    },
  });

  console.log(schedule.id, schedule.status);
  ```

  ```csharp C# SDK theme={null}
  using MeetKai.MKA1;
  using MeetKai.MKA1.Types.Components;

  var sdk = new SDK(
      bearerAuth: "Bearer <mka1-api-key>",
      serverUrl: "https://apigw.mka1.com"
  );

  var created = await sdk.AgentSchedules.CreateAgentScheduleAsync(
      agentId: "agt_123",
      body: new CreateAgentScheduleRequest()
      {
          Name = "weekday-morning-briefing",
          Schedule = AgentScheduleSpec.CreateCron(new AgentScheduleSpecCron()
          {
              Type = TypeCron.Cron,
              CronExpression = "0 9 * * 1-5",
              Timezone = "America/Los_Angeles",
          }),
          Input = CreateAgentScheduleRequestInputUnion.CreateStr(
              "Summarize the release notes published in the last 24 hours."
          ),
          Metadata = new Dictionary<string, string> { { "source", "briefing" } },
      },
      xOnBehalfOf: "<end-user-id>" // optional — attribute the request to one of your end users
  );

  var schedule = created.AgentSchedule!;
  Console.WriteLine($"{schedule.Id} {schedule.Status}");
  ```

  ```python Python SDK theme={null}
  from meetkai_mka1 import SDK

  sdk = SDK(bearer_auth="Bearer <mka1-api-key>")

  schedule = sdk.agent_schedules.create_agent_schedule(
      agent_id="agt_123",
      x_on_behalf_of="<end-user-id>",  # optional — attribute the request to one of your end users
      name="weekday-morning-briefing",
      schedule={
          "type": "cron",
          "cron_expression": "0 9 * * 1-5",
          "timezone": "America/Los_Angeles",
      },
      input="Summarize the release notes published in the last 24 hours.",
      metadata={"source": "briefing"},
  )

  print(schedule.id, schedule.status)
  ```

  ```bash bash theme={null}
  curl https://apigw.mka1.com/api/v1/agents/agt_123/schedules \
    --request POST \
    --header 'Content-Type: application/json' \
    --header 'Authorization: Bearer <mka1-api-key>' \
    --header 'X-On-Behalf-Of: <end-user-id>' \
    --data '{
      "name": "weekday-morning-briefing",
      "schedule": {
        "type": "cron",
        "cron_expression": "0 9 * * 1-5",
        "timezone": "America/Los_Angeles"
      },
      "input": "Summarize the release notes published in the last 24 hours.",
      "metadata": {
        "source": "briefing"
      }
    }'
  ```
</CodeGroup>

The call returns `201 Created` with a schedule object (`sched_...`) in status `active`. A `502` means nothing was saved; retry the create.

The `schedule` object picks the kind:

| `type`     | Required                                | Optional               | Fires                                                                                                                                                                                                           |
| ---------- | --------------------------------------- | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `once`     | `run_at` (ISO 8601 date-time)           | `timezone`             | Once, at `run_at`. A tick missed during an outage is caught up for 10 minutes. The schedule then moves to `completed`.                                                                                          |
| `interval` | `interval_seconds` (60 to 31,536,000)   | `start_at`, `timezone` | Every `interval_seconds`, aligned to the Unix epoch rather than to creation time: `3600` fires on the hour, whenever you created it. `start_at` suppresses firings before it and does not shift that alignment. |
| `cron`     | `cron_expression` (1 to 200 characters) | `timezone`             | On every match of the expression, evaluated in `timezone`.                                                                                                                                                      |

`timezone` defaults to `UTC` and only affects `cron`. `run_at` and `start_at` are absolute instants: write them with an offset or a trailing `Z`.

```json theme={null}
{ "type": "once", "run_at": "2026-09-01T09:00:00Z" }
```

```json theme={null}
{ "type": "interval", "interval_seconds": 3600, "start_at": "2026-09-01T00:00:00Z" }
```

`input` takes the same shapes as a run: a string, or an array of input items. `metadata` is a string map; every run the schedule starts gets it, plus `agent_schedule_id`.

A schedule remembers the identity that created it, including `X-On-Behalf-Of`, and every run it starts executes as that identity. Runs from a schedule created for an end user are listed under that end user's run scope; org admins also see them in the team-wide run list.

### List, retrieve, and update

<CodeGroup>
  ```ts MKA1 SDK theme={null}
  const schedules = await mka1.agentSchedules.listAgentSchedules({
    agentId: 'agt_123',
    xOnBehalfOf: '<end-user-id>',
  });
  console.log(schedules.data.map((s) => `${s.id} ${s.status} runs=${s.runCount}`));

  const schedule = await mka1.agentSchedules.getAgentSchedule({
    agentId: 'agt_123',
    scheduleId: 'sched_123',
    xOnBehalfOf: '<end-user-id>',
  });
  console.log(schedule.lastRunAt, schedule.lastRunId);
  ```

  ```csharp C# SDK theme={null}
  var list = await sdk.AgentSchedules.ListAgentSchedulesAsync(
      agentId: "agt_123",
      xOnBehalfOf: "<end-user-id>"
  );
  foreach (var s in list.AgentScheduleList!.Data)
  {
      Console.WriteLine($"{s.Id} {s.Status} runs={s.RunCount}");
  }

  var detail = await sdk.AgentSchedules.GetAgentScheduleAsync(
      agentId: "agt_123",
      scheduleId: "sched_123",
      xOnBehalfOf: "<end-user-id>"
  );
  Console.WriteLine($"{detail.AgentSchedule!.LastRunAt} {detail.AgentSchedule!.LastRunId}");
  ```

  ```python Python SDK theme={null}
  schedules = sdk.agent_schedules.list_agent_schedules(
      agent_id="agt_123",
      x_on_behalf_of="<end-user-id>",
  )
  print([(s.id, s.status, s.run_count) for s in schedules.data])

  schedule = sdk.agent_schedules.get_agent_schedule(
      agent_id="agt_123",
      schedule_id="sched_123",
      x_on_behalf_of="<end-user-id>",
  )
  print(schedule.last_run_at, schedule.last_run_id)
  ```

  ```bash bash theme={null}
  curl 'https://apigw.mka1.com/api/v1/agents/agt_123/schedules?limit=20&order=desc' \
    --header 'Authorization: Bearer <mka1-api-key>' \
    --header 'X-On-Behalf-Of: <end-user-id>'

  curl https://apigw.mka1.com/api/v1/agents/agt_123/schedules/sched_123 \
    --header 'Authorization: Bearer <mka1-api-key>' \
    --header 'X-On-Behalf-Of: <end-user-id>'
  ```
</CodeGroup>

Update takes any subset of the create fields, and at least one:

<CodeGroup>
  ```ts MKA1 SDK theme={null}
  const updated = await mka1.agentSchedules.updateAgentSchedule({
    agentId: 'agt_123',
    scheduleId: 'sched_123',
    xOnBehalfOf: '<end-user-id>',
    updateAgentScheduleRequest: {
      name: 'weekday-morning-briefing-v2',
      schedule: {
        type: 'cron',
        cronExpression: '30 8 * * 1-5',
        timezone: 'America/Los_Angeles',
      },
    },
  });
  console.log(updated.status); // "active"
  ```

  ```csharp C# SDK theme={null}
  var updated = await sdk.AgentSchedules.UpdateAgentScheduleAsync(
      agentId: "agt_123",
      scheduleId: "sched_123",
      body: new UpdateAgentScheduleRequest()
      {
          Name = "weekday-morning-briefing-v2",
          Schedule = AgentScheduleSpec.CreateCron(new AgentScheduleSpecCron()
          {
              Type = TypeCron.Cron,
              CronExpression = "30 8 * * 1-5",
              Timezone = "America/Los_Angeles",
          }),
      },
      xOnBehalfOf: "<end-user-id>"
  );
  Console.WriteLine(updated.AgentSchedule!.Status);
  ```

  ```python Python SDK theme={null}
  updated = sdk.agent_schedules.update_agent_schedule(
      agent_id="agt_123",
      schedule_id="sched_123",
      x_on_behalf_of="<end-user-id>",
      name="weekday-morning-briefing-v2",
      schedule={
          "type": "cron",
          "cron_expression": "30 8 * * 1-5",
          "timezone": "America/Los_Angeles",
      },
  )
  print(updated.status)  # "active"
  ```

  ```bash bash theme={null}
  curl https://apigw.mka1.com/api/v1/agents/agt_123/schedules/sched_123 \
    --request POST \
    --header 'Content-Type: application/json' \
    --header 'Authorization: Bearer <mka1-api-key>' \
    --header 'X-On-Behalf-Of: <end-user-id>' \
    --data '{
      "name": "weekday-morning-briefing-v2",
      "schedule": {
        "type": "cron",
        "cron_expression": "30 8 * * 1-5",
        "timezone": "America/Los_Angeles"
      }
    }'
  ```
</CodeGroup>

Changing `schedule` re-creates the timer and sets `status` back to `active`, even if the schedule was paused. Changing any other field leaves the timer and the status alone.

A `502` from an update means the new spec was saved but the timer may not have been replaced; the status is unchanged. Repeat the update until it returns `200`.

### Pause, resume, and delete

<CodeGroup>
  ```ts MKA1 SDK theme={null}
  const paused = await mka1.agentSchedules.pauseAgentSchedule({
    agentId: 'agt_123',
    scheduleId: 'sched_123',
    xOnBehalfOf: '<end-user-id>',
  });
  console.log(paused.status); // "paused"

  const resumed = await mka1.agentSchedules.resumeAgentSchedule({
    agentId: 'agt_123',
    scheduleId: 'sched_123',
    xOnBehalfOf: '<end-user-id>',
  });
  console.log(resumed.status); // "active"

  const deleted = await mka1.agentSchedules.deleteAgentSchedule({
    agentId: 'agt_123',
    scheduleId: 'sched_123',
    xOnBehalfOf: '<end-user-id>',
  });
  console.log(deleted.deleted); // true
  ```

  ```csharp C# SDK theme={null}
  var paused = await sdk.AgentSchedules.PauseAgentScheduleAsync(
      agentId: "agt_123",
      scheduleId: "sched_123",
      xOnBehalfOf: "<end-user-id>"
  );
  Console.WriteLine(paused.AgentSchedule!.Status);

  var resumed = await sdk.AgentSchedules.ResumeAgentScheduleAsync(
      agentId: "agt_123",
      scheduleId: "sched_123",
      xOnBehalfOf: "<end-user-id>"
  );
  Console.WriteLine(resumed.AgentSchedule!.Status);

  var deleted = await sdk.AgentSchedules.DeleteAgentScheduleAsync(
      agentId: "agt_123",
      scheduleId: "sched_123",
      xOnBehalfOf: "<end-user-id>"
  );
  Console.WriteLine(deleted.Object!.Deleted);
  ```

  ```python Python SDK theme={null}
  paused = sdk.agent_schedules.pause_agent_schedule(
      agent_id="agt_123",
      schedule_id="sched_123",
      x_on_behalf_of="<end-user-id>",
  )
  print(paused.status)  # "paused"

  resumed = sdk.agent_schedules.resume_agent_schedule(
      agent_id="agt_123",
      schedule_id="sched_123",
      x_on_behalf_of="<end-user-id>",
  )
  print(resumed.status)  # "active"

  deleted = sdk.agent_schedules.delete_agent_schedule(
      agent_id="agt_123",
      schedule_id="sched_123",
      x_on_behalf_of="<end-user-id>",
  )
  print(deleted.deleted)  # True
  ```

  ```bash bash theme={null}
  curl https://apigw.mka1.com/api/v1/agents/agt_123/schedules/sched_123/pause \
    --request POST \
    --header 'Authorization: Bearer <mka1-api-key>' \
    --header 'X-On-Behalf-Of: <end-user-id>'

  curl https://apigw.mka1.com/api/v1/agents/agt_123/schedules/sched_123/resume \
    --request POST \
    --header 'Authorization: Bearer <mka1-api-key>' \
    --header 'X-On-Behalf-Of: <end-user-id>'

  curl https://apigw.mka1.com/api/v1/agents/agt_123/schedules/sched_123 \
    --request DELETE \
    --header 'Authorization: Bearer <mka1-api-key>' \
    --header 'X-On-Behalf-Of: <end-user-id>'
  ```
</CodeGroup>

Delete returns `{ "object": "agent.schedule.deleted", "id": "sched_123", "deleted": true }`. Delete succeeds even when the scheduler is unreachable; a timer left behind that way is harmless. A repeated `DELETE` returns `404`.

Delete an agent's schedules before you delete the agent. Deleting the agent does not delete its schedules, and afterwards they cannot be reached, because a schedule is only addressable through `/agents/{agent_id}/schedules/...`.

### How runs are recorded

* Each tick creates a normal agent run, so it appears in the agent's run history with `metadata.agent_schedule_id` set.
* After the run, the schedule's `last_run_at`, `last_run_id`, and `run_count` are updated. A run that fails at the gateway is still recorded as a run (with `status: "failed"`) and still counts.
* There is no per-schedule run listing. To see a schedule's runs, list the agent's runs with the same `X-On-Behalf-Of` the schedule was created with and keep the ones whose `metadata.agent_schedule_id` matches. The console does the same to label a run as "schedule".

### Durability and failure

* Pause and resume act on the timer named by `temporal_schedule_id`; when that field is `null`, they return `409`.
* If a tick fires while the previous run is still executing, that tick is dropped rather than queued. Expect gaps in `run_count` on long-running agents with short intervals.
* Missed ticks are backfilled for up to 10 minutes after their scheduled time, for example after a service restart. Older misses are dropped.
* A tick that fails before its run is recorded is retried, up to 3 attempts. A retry can duplicate a run if the failure happened after the gateway accepted the request. A run that reaches the gateway and fails is recorded as failed and is not retried.
* A tick does nothing when the schedule is not `active`, or its agent has been deleted.

### Schedule reference

| Field                                                                 | Description                                                                                                                                                                  |
| --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                                                                  | `sched_...`                                                                                                                                                                  |
| `agent_id`                                                            | The agent the schedule runs.                                                                                                                                                 |
| `name`                                                                | Optional label, 1 to 120 characters.                                                                                                                                         |
| `status`                                                              | `active`, `paused`, `completed` (a `once` schedule that has fired), or `deleted`.                                                                                            |
| `schedule`                                                            | The timing rule: `type` plus `run_at`, `start_at`, `interval_seconds`, `cron_expression`, and `timezone` as applicable. Interval schedules fire on epoch-aligned boundaries. |
| `input`, `conversation`, `previous_response_id`, `context_management` | The stored run request.                                                                                                                                                      |
| `metadata`                                                            | Copied onto every run, with `agent_schedule_id` added.                                                                                                                       |
| `temporal_schedule_id`                                                | ID of the backing timer, or `null` when there is none (pause and resume then return `409`).                                                                                  |
| `last_run_at`, `last_run_id`, `run_count`                             | Updated after each tick.                                                                                                                                                     |
| `created_at`, `updated_at`, `deleted_at`                              | ISO 8601 timestamps.                                                                                                                                                         |

## Troubleshooting

| Symptom                                                                                                   | Cause                                                                                                                                                                                                                                     | Fix                                                                                                                                                   |
| --------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| The bot never replies                                                                                     | `status` is not `active`. Anything else is acknowledged and dropped.                                                                                                                                                                      | Read the connector; `last_error` says why. Fix the cause, then activate.                                                                              |
| The bot never replies                                                                                     | The chat or sender is not in `config.access`. Unlisted chats are dropped silently. Telegram group and supergroup IDs are negative numbers.                                                                                                | Delete the connector and create it again with the right allowlist; there is no update call.                                                           |
| Telegram stopped delivering after a token change                                                          | The stored token is the old one; credentials cannot be edited.                                                                                                                                                                            | Delete the connector and create it again with the new token. Creation drops updates queued under the old binding.                                     |
| WhatsApp messages never reach the agent                                                                   | The app-level webhook is missing in Meta, or its values differ from setup.                                                                                                                                                                | Configure **WhatsApp → Production setup → Configure Webhooks** with the exact `callback_url` and `verify_token` from setup, subscribed to `messages`. |
| WhatsApp replies are not sent                                                                             | The 24-hour customer-service window is closed.                                                                                                                                                                                            | Wait for the user to message again; the connector never sends template messages.                                                                      |
| `403` on a connector endpoint                                                                             | The request carried `X-On-Behalf-Of`; create or activate was called with a session; the key lacks `read:agents` or `write:agents`, or the runtime scopes on create and activate; or someone other than the creating user called activate. | Drop the header. Use an API key with the scopes listed under [Before you start](#before-you-start). Activate with the creating user's key.            |
| `400` at creation: "Telegram rejected the bot credentials" or "WhatsApp rejected the account credentials" | The token or app credentials are wrong.                                                                                                                                                                                                   | Check them and create again. A `502` instead means the provider was unreachable; retry.                                                               |
| `409` at creation                                                                                         | The bot or phone number is already bound to another connector, possibly in another team.                                                                                                                                                  | Delete that connector first.                                                                                                                          |
| `status: "error"` with `last_error.code` = `connector_identity_revoked`                                   | The API key behind the connector was revoked, disabled, or lost a runtime scope, or the creating user left the team.                                                                                                                      | Fix the key, then activate from the creating user's key. If that user is gone, delete the connector and create it again under another key.            |
| `status: "error"` with `last_error.operation` = `register_webhook` or `unregister_webhook`                | The provider call failed; `http_status` and `error_code` are the provider's. After activate, it can also mean the stored credentials no longer match the bot or phone number.                                                             | Activate, or repeat the delete, once the provider is healthy. If the credentials no longer match, delete and recreate.                                |
| A schedule never runs                                                                                     | `status` is `paused` or `completed`.                                                                                                                                                                                                      | Resume a paused schedule. A `completed` one has fired; create a new schedule.                                                                         |
| A `once` schedule never runs                                                                              | `run_at` was already in the past.                                                                                                                                                                                                         | Create a new schedule with a future `run_at`.                                                                                                         |
| A cron schedule fires at the wrong hour                                                                   | `timezone` is `UTC` by default. `0 9 * * 1-5` in `UTC` is early morning on the US west coast.                                                                                                                                             | Set `timezone`.                                                                                                                                       |
| `400` on create with an interval                                                                          | `interval_seconds` is below 60.                                                                                                                                                                                                           | Raise it.                                                                                                                                             |
| Ticks are missing from `run_count`                                                                        | The previous run was still executing when the next tick fired, so the tick was skipped.                                                                                                                                                   | Compare `last_run_at` with the run's duration and lengthen the interval.                                                                              |
| `run_count` never increases                                                                               | The agent was deleted; a tick for a deleted agent creates no run.                                                                                                                                                                         | Nothing to do; the schedule cannot be reached without its agent. Delete schedules before their agent.                                                 |
| `502` on create                                                                                           | The scheduler refused the schedule. Nothing was kept.                                                                                                                                                                                     | Retry the create.                                                                                                                                     |
| `502` on update                                                                                           | The new spec was saved, but the timer may not have been replaced.                                                                                                                                                                         | Repeat the update until it returns `200`.                                                                                                             |

## API reference

Every field and status code is in the Agent Connectors and Agent Schedules groups of the [API Reference](/api-reference/agent-connectors/list-connectors-for-an-agent).

## See also

* [Manage agents](/docs/managing-agents) - agent definitions, runs, and version rollback.
* [Conversations](/docs/conversations) - the conversation object each connector thread keeps.
* [Files and vector stores](/docs/files-and-vector-stores) - the Files API that connector media flows through.
