AI Knowledge & Logic

Custom Actions

Connect your bot to external systems. Custom actions let workflows fetch data, create orders, process refunds, and more — using simple API calls or Python scripts.

Actions let your bot do things — not just talk. They connect workflows to your systems: ERPs, payment processors, CRMs, subscription platforms, shipping APIs.

Actions are not active by default. You must enable them inside a workflow.


How actions work

  1. You create an action — give it a name, description, and define how it connects to your system
  2. You enable it in a workflow
  3. In the workflow's instructions, you tell the AI when to call it (e.g., "Step 3: call getOrderDetails")
  4. The AI calls the action with the required arguments
  5. The action runs and returns data to the AI
  6. The AI uses the returned data in its response

Action types

There are two types: API actions for simple HTTP calls, and Python actions for anything that needs logic.

API ActionPython Action
Best forSimple GET/POST requestsMulti-step logic, data transformation, multiple API calls
SetupConfigure URL, method, headers, and params in the UIWrite a Python script
Dynamic inputs$variableName syntax in URL, headers, or bodycontext["args"]["variableName"]
ExamplesFetch order status, get store details, check stockProcess refunds with validation, compose data from multiple APIs, tag conversations

API Actions

API actions make a single HTTP request to an external endpoint. They're the simplest way to connect to an API — no code required.

Name

Use camelCase and be descriptive. The AI uses the name to understand the action.

  • getOrderDetails
  • checkProductStock
  • getStoreLocations

Description

A one-sentence explanation of what the action does. Write it so the AI understands when to call it.

Retrieves the customer's order status, items, and tracking link given an order ID.

API configuration

FieldDescription
Base URLThe root URL of your API (e.g., https://api.example.com)
PathThe endpoint path (e.g., /orders/$orderId)
MethodGET, POST, PUT, or DELETE
HeadersJSON headers, often for authentication
Query ParametersURL parameters (e.g., ?lang=en)
Request BodyJSON body for POST/PUT requests
Request TimeoutHow long to wait before failing

Dynamic arguments

Use $variableName syntax to create dynamic inputs. Each variable becomes a required argument the bot must provide when calling the action.

GET /orders/$orderId
POST /stock
Body: { "productId": "$productId", "storeId": "$storeId" }

Use camelCase for variable names and choose names that convey meaning — orderId, customerEmail, productSku.

Example: Check product stock

GET https://api.example.com/stock/$productId
Headers: { "x-api-key": "your-api-key" }

The bot provides the product ID, and the action returns stock information. No code needed.


Python Actions

Python actions run a script when called. Use them when you need to:

  • Call multiple APIs and combine the results
  • Transform or validate data before or after an API call
  • Add safety checks (e.g., verify a refund was actually offered before processing it)
  • Handle complex input formats
  • Tag conversations or set metadata as side effects

Structure

Every Python action implements an execute_action function that receives a context object and returns data for the AI:

import requests

def execute_action(context):
    order_id = context["args"]["orderId"]

    response = requests.get(
        "https://api.example.com/orders",
        params={"id": order_id},
        headers={"Authorization": "Bearer YOUR_API_KEY"},
        timeout=30,
    )
    response.raise_for_status()
    return response.json()

The context object contains:

FieldDescription
context["args"]Arguments passed by the bot (e.g., orderId, email)
context["conversation"]Conversation data including messages and ID

Return value: Whatever you return is sent back to the AI as context for its response. Return structured data (dicts, lists) or clear error messages.

Available helpers

Python actions have access to built-in helper functions. Click any function name for full documentation with parameters, return types, and examples.

Conversation data

HelperWhat it does
get_conversation_metadata(context, key)Read a metadata value from the conversation
set_conversation_metadata(context, key, value)Write a metadata value to the conversation
delete_conversation_metadata(context, key)Delete a metadata key from the conversation
add_conversation_tag(context, tag)Add a tag to the conversation
add_conversation_event(context, event)Record an event in the conversation timeline
hand_off_conversation(context, reason=None)Hand off the conversation to a human agent

LLM classification

HelperWhat it does
llm_classify_binary(prompt, input, fallback=None)Classify input as True or False using an LLM
llm_classify_category(prompt, input, options, fallback=None)Classify input into one of the provided categories

Experimentation

HelperWhat it does
get_ab_test_variant(context, test_name, possible_variants)Get a sticky A/B test variant for this conversation

Utilities

HelperWhat it does
add_google_sheets_row(sheet_id, data)Append a row to a Google Sheet
is_holiday(country, state=None, region=None, check_date=None)Check if a date is a public holiday

Examples

Fetch order with tracking from multiple APIs

This action calls an ERP for order data and a courier API for tracking events, then combines the results:

import requests

def execute_action(context):
    order_id = context["args"]["orderId"]
    email = context["args"]["email"]

    # Fetch order from ERP
    order_resp = requests.get(
        "https://erp.example.com/orders",
        params={"id": order_id, "email": email},
        headers={"Authorization": "Bearer ERP_KEY"},
        timeout=30,
    )

    if order_resp.status_code != 200:
        return {"error": "Order not found. Please check the order ID."}

    order = order_resp.json()

    # Fetch tracking from courier
    tracking = requests.get(
        "https://tracking.example.com/shipments",
        params={"tracking_number": order["trackingNumber"]},
        timeout=30,
    ).json()

    return {
        "orderId": order_id,
        "status": order["status"],
        "trackingNumber": order["trackingNumber"],
        "trackingEvents": tracking.get("events", []),
        "estimatedDelivery": order.get("estimatedDelivery"),
    }

Process a refund with validation

This action checks the order state before processing a refund, and tags the conversation for analytics:

import requests

def execute_action(context):
    order_id = context["args"]["orderId"]
    reason = context["args"]["reason"]

    # Validate order state
    order = requests.get(
        "https://api.example.com/orders",
        params={"id": order_id},
        timeout=30,
    ).json()

    if order.get("isRefunded"):
        return {"error": "This order has already been refunded."}

    if not order.get("isCancellable"):
        return {"error": "This order cannot be refunded in its current state."}

    # Process the refund
    result = requests.post(
        "https://api.example.com/refund",
        json={"orderId": order_id, "reason": reason, "fullRefund": True},
        timeout=30,
    ).json()

    # Tag for analytics
    add_conversation_tag(context, "refunded")
    set_conversation_metadata(context, "refund_amount", str(result["amount"]))

    return {"success": True, "refundedAmount": result["amount"]}

Create an order from chat

This action takes cart items collected by the bot and creates a pending order:

import requests
import json

def execute_action(context):
    name = context["args"]["customerName"]
    phone = context["args"]["phone"]
    items = context["args"]["items"]

    # Parse items if passed as string
    if isinstance(items, str):
        items = json.loads(items)

    # Normalize phone number
    if phone.startswith("+30"):
        phone = phone[3:]

    response = requests.post(
        "https://api.example.com/orders",
        json={
            "customerName": name,
            "phone": phone,
            "items": items,
        },
        headers={"Authorization": "Bearer API_KEY"},
        timeout=30,
    )
    response.raise_for_status()
    result = response.json()

    return {
        "orderId": result["orderId"],
        "total": result["total"],
        "confirmationUrl": result["confirmationUrl"],
    }

Testing

Every action can be tested directly from the dashboard before you use it in a live workflow.

Testing API actions

  1. Open your API action in the dashboard
  2. The test panel automatically detects all $variables in your path, headers, query params, and body
  3. Fill in test values for each variable
  4. Click Run Test
  5. The dashboard makes the actual HTTP request and shows the response

Testing Python actions

  1. Open your Python action in the dashboard
  2. Fill in test values for each argument
  3. Optionally enter a Conversation ID — this populates context["conversation"] with real conversation data (messages, metadata, etc.). You can use any conversation from your dashboard — copy the ID or public ID from the conversation detail view
  4. Click Run Test
  5. The dashboard executes your code and shows the returned result

Without a conversation ID, context["conversation"] is None. This is fine for actions that only use context["args"], but if your action reads conversation messages, metadata, or tags, provide a conversation ID to test with real data.

Tip: To test iteratively, keep a test conversation open in another tab. Send messages to it to set up the conversation state you want, then use its ID in the test panel.


Security and privacy

Your API response is visible to the bot and may be surfaced to the customer. Only return data that is appropriate for the end user to see.

Reducing sensitive exposure

If exact values are confidential, return abstracted indicators instead:

# Instead of this:
return {"stock": 123}

# Return this:
return {"inStock": True}

Customer authentication

Consider requiring multiple matching identifiers when dealing with protected data:

  • Arguments: email + orderId
  • Your API filters with both values to verify ownership

Single-factor lookup (e.g., only order ID) improves UX but carries a small risk if the bot is manipulated. Choose based on your security requirements.


Best practices

  • Name clearly. getOrderDetails not getData. The AI uses the name to understand the action.
  • Describe for the AI. Write descriptions as if briefing a teammate — "Retrieves order status and tracking info given an order ID."
  • Return only what's needed. Don't expose raw database records. Return what the customer should see.
  • Handle errors gracefully. Return clear error messages the AI can relay: {"error": "Order not found"}
  • Use Python when logic is needed. If you need if/else, multiple API calls, or data transformation — use a Python action.
  • Use API actions for simple lookups. One endpoint, one response, no transformation needed — an API action is simpler to maintain.
  • Set timeouts. Always include a timeout parameter on HTTP requests to avoid hanging.

On this page