Skip to main content

Connectors API

Kheish exposes connectors through two different HTTP surfaces:
  • runtime CRUD for operators
  • ingress routes for external systems
They are related, but they do not serve the same purpose.

Runtime CRUD endpoints

  • GET /v1/runtime/connectors
  • GET /v1/runtime/connectors/{kind}/{name}
  • PUT /v1/runtime/connectors/{kind}/{name}
  • DELETE /v1/runtime/connectors/{kind}/{name}
Supported kinds:
  • telegram
  • slack
  • http
  • external
GET returns projected connector views with:
  • source: file or daemon
  • non-secret transport settings
  • redacted secret metadata such as configured, source, secret_ref, or env
Deletion rules:
  • file-backed connectors are readable but not mutable through the daemon control plane
  • deletion is rejected when the connector is still referenced by dependent config such as schedules or reply-target relationships

Runtime upsert semantics

PUT /v1/runtime/connectors/{kind}/{name} behaves like a field-aware upsert.
  • If the connector does not exist, the daemon creates it.
  • If the connector already exists, only fields present in the JSON payload are updated.
  • Fields omitted from the payload keep their stored value.
This is important because the endpoint uses PUT, but it is not a naive full-record replacement.

Shared helper shapes

ConnectorSecretInput

Secret-bearing runtime fields such as bot_token, signing_secret, secret_token, and bearer_token use the same write-only helper shape:
{
  "secret_ref": "connectors.http.inbox.bearer_token",
  "value": "super-secret-token"
}
Supported fields:
  • value: store or rotate a daemon-managed generic secret slot
  • secret_ref: point at an existing daemon-managed generic secret slot
  • env: read the secret from an environment variable
Rule:
  • env cannot be combined with value or secret_ref

ReplyHandle

Connector reply-target fields use raw ReplyHandle objects:
{
  "plugin": "http",
  "address": "{\"url\":\"https://example.com/replies\",\"headers\":{\"Authorization\":\"Bearer out-token\"}}"
}
This same wire shape is used by:
  • HTTP connector default_reply_targets
  • Slack connector additional_reply_targets
  • Telegram connector additional_reply_targets
  • HTTP ingress reply_targets
It is different from the structured SessionReplyTargetRequest surface used by /v1/sessions/{session_id}/reply-targets. Plugin-specific address payloads commonly encode:
  • http: a raw URL string or serialized HttpReplyRoute
  • slack: serialized SlackReplyRoute
  • telegram: serialized TelegramReplyRoute
  • external: serialized ExternalReplyRoute

session_policy

Connectors can optionally bootstrap sessions for inbound traffic:
{
  "session_policy": {
    "create_if_missing": true,
    "persona_id": "support-bot",
    "capability_scope": {
      "skill_deny": ["dangerous-inline-tool"]
    },
    "credential_scope": {
      "route_allow": ["openai"],
      "mcp_server_deny": ["github"]
    }
  }
}
Fields:
  • create_if_missing
  • persona_id
  • capability_scope
  • credential_scope
This policy is applied when the connector needs to materialize a session for inbound traffic. persona_id is validated against existing daemon persona records.

HTTP connectors

Runtime payload

PUT /v1/runtime/connectors/http/{name} accepts:
  • actor_id
  • fixed_session_id
  • bearer_token
  • allow_unauthenticated_ingress
  • default_reply_targets
  • default_binding_keys
  • session_policy
Example:
{
  "actor_id": "webhook-user",
  "bearer_token": {
    "value": "inbox-token"
  },
  "default_binding_keys": ["team:docs"],
  "default_reply_targets": [
    {
      "plugin": "http",
      "address": "{\"url\":\"https://example.com/replies\",\"headers\":{\"Authorization\":\"Bearer out-token\"}}"
    }
  ],
  "session_policy": {
    "create_if_missing": true,
    "persona_id": "triage"
  }
}
Validation rule:
  • if no bearer token source is configured, allow_unauthenticated_ingress must be true

Ingress endpoint

  • POST /v1/connectors/http/{name}
Payload fields:
  • session_id
  • binding_keys
  • actor_id
  • content
  • input_items
  • attachments
  • metadata
  • reply_targets
  • reply_plugin
  • reply_address
  • idempotency_key
Example:
{
  "binding_keys": ["customer:acme", "channel:ticket-123"],
  "content": "Summarize the latest ticket state.",
  "metadata": {
    "ticket_id": "123"
  },
  "idempotency_key": "ticket-123-update-9"
}
HTTP ingress session resolution order:
  1. connector fixed_session_id
  2. payload session_id
  3. session already bound to the provided binding_keys
  4. connector-derived fallback session id based on the first binding key
If none of those can resolve a session, the request is rejected. HTTP ingress auth:
  • uses the Authorization: Bearer ... header when a connector bearer token is configured
  • otherwise requires allow_unauthenticated_ingress=true

Slack connectors

Runtime payload

PUT /v1/runtime/connectors/slack/{name} accepts:
  • bot_token
  • signing_secret
  • allow_unauthenticated_ingress
  • api_base_url
  • fixed_session_id
  • include_self_output
  • additional_reply_targets
  • additional_binding_keys
  • session_policy
Example:
{
  "bot_token": {
    "secret_ref": "slack.prod.bot_token"
  },
  "signing_secret": {
    "secret_ref": "slack.prod.signing_secret"
  },
  "include_self_output": true,
  "additional_binding_keys": ["team:support"],
  "session_policy": {
    "create_if_missing": true
  }
}
Validation rule:
  • if no signing secret source is configured, allow_unauthenticated_ingress must be true

Ingress endpoint

  • POST /v1/connectors/slack/{name}
Behavior:
  • verifies the Slack signature when a signing secret is configured
  • rejects stale or far-future signed requests
  • answers url_verification with the Slack challenge
  • ignores bot-authored events
  • derives one durable session from channel plus root thread timestamp when fixed_session_id is not configured
  • automatically downloads inbound files when a bot_token is configured
Slack ingress idempotency:
  • prefers Slack event_id

External connectors

Runtime payload

PUT /v1/runtime/connectors/external/{name} accepts:
  • platform
  • mode
  • base_url
  • shared_token
  • allow_unauthenticated_ingress
  • fixed_session_id
  • include_self_output
  • additional_reply_targets
  • additional_binding_keys
  • session_policy
  • ingress_events_per_second
  • child_process
External connector reads return:
  • non-secret transport config
  • child_process.env_keys
  • child_process.credential_env_keys
  • redacted secret metadata for the shared token
When child_process.credential_slots is configured, the daemon does not inject those platform secrets into the child environment directly. It starts the sidecar with one short-lived opaque credential token and the list of allowed env keys, and the sidecar fetches each concrete secret from the daemon on demand.

Ingress endpoints

  • POST /v1/connectors/external/{name}/events
  • POST /v1/connectors/external/{name}/events/batch
External ingress is documented in detail in External connectors protocol. The short version:
  • single-event ingress returns accepted, duplicate, or rejected
  • single-event rate limiting is HTTP 429
  • batch ingress is ordered, non-atomic, and returns per-item statuses
  • session derivation can use thread.path, routing_key, or fixed_session_id
  • sidecar runtime endpoints stay on protocol 1, while ingress semantics now default to protocol 2
Reply targets:
  • when include_self_output=true, replies default back into the same Slack channel/thread
  • additional_reply_targets are appended after that default self-target

Telegram connectors

Runtime payload

PUT /v1/runtime/connectors/telegram/{name} accepts:
  • bot_token
  • secret_token
  • allow_unauthenticated_ingress
  • api_base_url
  • ingress_mode
  • polling_timeout_seconds
  • fixed_session_id
  • include_self_output
  • additional_reply_targets
  • additional_binding_keys
  • session_policy
Example:
{
  "bot_token": {
    "secret_ref": "telegram.prod.bot_token"
  },
  "secret_token": {
    "secret_ref": "telegram.prod.secret_token"
  },
  "ingress_mode": "webhook",
  "include_self_output": true,
  "session_policy": {
    "create_if_missing": true,
    "persona_id": "chat-operator"
  }
}
Validation rules:
  • ingress_mode=polling requires a bot token
  • ingress_mode=webhook requires either a secret token source or allow_unauthenticated_ingress=true

Ingress endpoint

  • POST /v1/connectors/telegram/{name}
Behavior:
  • verifies x-telegram-bot-api-secret-token when a secret token is configured
  • derives one durable session from chat id plus topic thread id when fixed_session_id is not configured
  • downloads inbound photos and documents through the Telegram Bot API when a bot token is available
Telegram ingress idempotency:
  • keyed by Telegram update_id
Reply targets:
  • when include_self_output=true, replies default back to the same chat or topic
  • additional_reply_targets are appended after the self-target

Output expectations

Connector ingress and output routing are separate concerns:
  • ingress routes submit work into Kheish
  • reply targets determine where finalized output is delivered
  • output delivery can stay daemon-local or flow through output plugins
Read Connectors and reply targets, Connector configuration, and Output routing for the broader model.