Skip to documentation
Documentation

Put your Vercel agent on more channels

Keep your agent on Vercel. Keiki brings it SMS, iMessage, WhatsApp, Slack, Telegram and more.

Your agent keeps running on Vercel, on your model and your code. Keiki receives every channel message, sends it to your agent as a signed turn, and delivers the reply. Your app never holds channel credentials or a Keiki dashboard token.

Expose your agent to more channels

Set the Keiki agent's harness to external in push mode, with dispatchUrl pointing at your deployment's turn route (see External harness):

JSON
{
  "harness": "external",
  "harness_config": {
    "mode": "push",
    "dispatchUrl": "https://your-app.vercel.app/api/keiki"
  }
}

The app needs no secret. Keiki signs every turn with its Ed25519 platform key, and createKeikiHandler verifies it against Keiki's published keys. To use a per-agent secret instead, generate one with an API key for that agent and store it in the Vercel project as KEIKI_SIGNING_SECRET:

Shell
curl -X POST https://onkeiki.com/api/v1/developer/agents/<agent-id>/harness/signing-secret \
  -H "Authorization: Bearer sk_live_…"

Install @onkeiki/ai-sdk, @vercel/functions, ai and @ai-sdk/mcp, then add the turn route. waitUntil lets the function acknowledge the dispatch with 202 and keep running the model:

TypeScript
// app/api/keiki/route.ts
import { createKeikiHandler, keikiDelivered } from '@onkeiki/ai-sdk'
import { waitUntil } from '@vercel/functions'
import { stepCountIs, streamText } from 'ai'

export const maxDuration = 300

export const POST = createKeikiHandler({
  waitUntil,
  onTurn: async ({ instructions, messages, tools, prepareStep, abortSignal }) =>
    streamText({
      model: 'openai/gpt-5.4-nano',
      instructions,
      messages,
      tools: await tools(),
      prepareStep,
      stopWhen: [keikiDelivered, stepCountIs(10)],
      abortSignal,
    }),
})

stopWhen lets the model read tool results before it answers. keikiDelivered stops the turn once reply or send_message has delivered, so the answer is not sent twice; a failed send returns an error and the model can try again.

The handler verifies Keiki's Ed25519 signature against the raw body and authenticates its turn API and MCP calls with the request's per-turn Keiki-Turn-Token. With KEIKI_SIGNING_SECRET set, it verifies the HMAC Keiki-Signature and signs those calls with the secret instead. It reports the reply and completion back to Keiki. Keiki keeps the turn trace, history and delivery. tools() returns the turn's Keiki tools (channel, capability and skill tools), authorized for that agent and turn only; the app never gets a workspace token. See AI SDK integration for arrivals, cancellation and serving several agents.

To develop locally, run the app and point the agent at it through the tunnel:

Shell
curl -fsSL https://onkeiki.com/api/v1/harness/tunnel.sh \
  | KEIKI_API_KEY=sk_live_… PORT=3000 TURN_PATH=/api/keiki sh

examples/vercel-connect-next in the repository is this route as a runnable Next.js app.

Add channels

Attach channels to the Keiki agent in the dashboard under Channels: an iMessage or SMS line, Slack, WhatsApp, Telegram or an email inbox. Messages on any of them reach the same turn route; nothing changes in your app.

Set it up from your app

An app can do the setup itself with one OAuth grant for one agent. In Vercel Connect, create a connector from https://onkeiki.com/api/v1/agent-channels; Connect discovers Keiki's channels issuer (https://onkeiki.com/.well-known/oauth-authorization-server/channels) and registers itself, and every client registered there can only be granted channels. Any other OAuth client can do the same with dynamic client registration and PKCE. The user picks an agent, or names a new one, on the consent screen; the token reaches that agent's channel setup and nothing else in the workspace. An org needs a plan to grant it.

With that token, point the agent at the deployment and attach channels:

Shell
# The bound agent, its dispatch settings and its channels.
curl https://onkeiki.com/api/v1/agent-channels -H "Authorization: Bearer $TOKEN"

curl -X PUT https://onkeiki.com/api/v1/agent-channels/dispatch \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"mode":"push","dispatchUrl":"https://your-app.vercel.app/api/keiki"}'

curl -X POST https://onkeiki.com/api/v1/agent-channels/channels \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"channels":[{"type":"telegram","bot_token":"…"}]}'

channels takes the same channel specs as POST /api/v1/developer/deployments. A channel that needs a browser step (Slack, WhatsApp) comes back with a link to finish it. If the agent is deleted, the token stops working with 401 and the app has to ask the user to connect again.