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

Need a human decision before the bot proceeds? Human-in-the-Loop Decisions shows how to create an approval card from Python and let the bot continue after an agent answers. If the person deciding works outside Octocom, follow the external decisions playbook.

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

API actions and their tests can call public HTTP and HTTPS endpoints. Private, loopback, link-local and internal-network destinations are blocked, including redirects and hostnames that resolve to those addresses. Use a publicly reachable, authenticated API for your integration. Existing private endpoints will need a supported connection approach before they can be used.

The request timeout is 1 to 60 seconds (30 seconds by default), and requests follow at most 21 redirects. These rules also apply to dashboard and MCP tests.

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 bot actions have a 120-second execution budget, including HTTP requests and browser waits. The same budget applies when testing an action through Copilot, MCP, REST, or the dashboard. Keep individual request timeouts shorter so the action can handle failures within its overall budget.

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 conversation data, customer profile, business info, and the arguments passed by the bot. See Python Context for the full reference.

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.

Optional arguments

Every argument is required by default: the AI must provide a value before it can call the action. Turn off Required next to an argument to make it optional — useful when the action can work with or without it, such as looking up orders by email with an optional order ID to narrow the result.

When the AI leaves an optional argument out, its key is missing from context["args"] entirely, so read it with .get() instead of square brackets:

def execute_action(context):
    email = context["args"]["email"]           # required
    order_id = context["args"].get("orderId")  # optional: None when left out

    if order_id:
        return find_order(email, order_id)
    return list_orders(email)

context["args"]["orderId"] would raise a KeyError whenever the AI skips the argument. Say in the action's description when the optional argument should be provided, so the AI knows when to include it.

Available helpers

Python actions have access to built-in helper functions for conversation data, LLM classification, integrations, and more. These are the same helpers available across all Python-based features.

See Python Helpers for the full list with documentation links.


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"],
    }

Showing UI in the chat

An action can also return cards, confirmations, order tracking and more for the customer to see in web chat, under an octocom_ui key. See Chat UI from Actions.

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. Leave an optional argument blank to test the call without it — it is then missing from context["args"], just as when the AI leaves it out
  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