Messaging

AI auto-reply automations

Automatically reply to every inbound WhatsApp DM on a number with your AI agent. The automation owns the inbound → agent → send graph; the messages API delivers the reply with the same service-window, opt-out, and billing rules as any other send.

1. Create an AI agent

POST/v1/ai-agents

Create the agent persona the automation will invoke, and keep its id. Agent creation is API/SDK-only today — the dashboard does not yet have an AI agent creation screen.

curl https://api.tyxter.com/v1/ai-agents \
  -H "authorization: Bearer $TYXTER_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "name": "support_bot",
    "system_prompt": "You are a friendly support agent. Answer briefly."
  }'

2. Create, version, and publish the automation

POST/v1/automations
POST/v1/automations/{'{automation_id}'}/versions
POST/v1/automations/{'{automation_id}'}/publish

Create the automation, add a version whose graph is the canonical inbound → ai_agent.invokemessage.send shape, then publish that version. Bind the inbound_message trigger to the WhatsApp number that should auto-reply via its phone_number_id. Published versions are immutable; a run always points at the exact version it executes.

3. The contact messages the number

When a contact sends an inbound WhatsApp text, Tyxter receives it, honors the contact's opt-out state, and creates a single automation run. Runs are deduplicated per inbound message, so provider retries never produce a double reply.

4. The agent replies automatically

The run invokes your AI agent with the contact's message and sends the agent's reply back through the messages API. If the agent yields no usable reply, the builder-configured fallback message is sent instead.

One auto-reply per number

At most one enabled inbound_message automation may be bound to a given phone_number_id per environment. Publishing or enabling a second inbound automation for the same number is rejected with 409 automation_inbound_number_conflict.

Only WhatsApp text inbound triggers an auto-reply in this release. Instagram and non-text inbound are not yet supported.

Beyond auto-reply: the workflow engine

An automation graph is a directed, acyclic set of nodes connected by edges. A trigger node starts a run; the runtime walks the graph, persists each node's output, and resumes across waits. The full node config for every type is published as a per-type oneOf in /openapi.json so you (or an agent) can author a valid graph from the spec alone.

Node catalog

Referencing run data with expressions

String config fields are interpolated against the run context by a sandboxed engine — never eval. Three namespaces: trigger.* (the run input), nodes.<id>.output.* (any upstream node's output, surviving delay / time_gate), and run.*.

{ "type": "message.send",
  "config": {
    "to": "{{ trigger.contact_phone }}",
    "from": "{{ trigger.business_number }}",
    "body": "AI says: {{ nodes.agent.output | default:'(no reply)' }}"
  } }

Filters: default, lower, upper, json, date. Boolean expressions (for condition nodes and edge guards) support comparisons, &&/||/!, contains, and an anchored, ReDoS-safe matches. Invalid expressions are rejected when you create the version.

Reliability: retries, timeouts, cancellation

Side-effect nodes carry an optional config.retry = { max_attempts, backoff_ms } (http.request and ai_agent.invoke retry transient failures by default) and a config.timeout_ms budget. A wedged call that exhausts its retries lands the run in timed_out. Cancel a run with

POST/v1/automation-runs/{'{run_id}'}/cancel
— it emits an automation.run_cancelled webhook and stops at the next step boundary.

Authenticated HTTP (credentials vault)

An http.request node authenticates via config.auth = { credential_id }, referencing a stored credential whose secret is encrypted at rest and injected only at send time — it never appears in graph JSON, run summaries, logs, or the OpenAPI spec.

Receiving third-party webhooks

A webhook.trigger can verify a third party's native signature instead of a relay. Set config.verification.mode to stripe ( verifies Stripe-Signature), github (verifies X-Hub-Signature-256), or shared_secret_header; the default tyxter_hmacverifies Tyxter's own signed envelope. A forged or unsigned request is rejected with a 404.