Now inviting stores. Join the waitlist to hear the agent call you.

Join the waitlist

Search the docs

Find a docs page

API reference

Actions

Preview

Let a call do things in your systems, such as confirming an order or changing an address, by RowCall's signed request or your own HTTP request.

An action is something only your systems can do: look up an order, change a delivery address, send a payment link. During a call, when the flow or the shopper needs one, RowCall takes the action and waits for the reply while the shopper stays on the line.

The AI never acts directly. It asks for an action; RowCall checks the request; your systems check it again against your own data and decide.

RowCall sends confirm_order to your action URL, your server answers, and the agent tells the shopper.

Define what the call may do

Actions are your org's own. RowCall suggests presets you can use as they are or change: get_order, confirm_order, cancel_order, update_delivery_address, get_checkout, send_payment_link, restore_checkout_link, get_delivery_slots, book_delivery_slot, create_exchange and handoff. You can also define any action of your own, such as check_warranty.

Manage them in Developers → Actions, or with GET /v1/actions and PUT /v1/actions/{name} (actions scope). An action is mostly words for the AI and limits for RowCall:

await rowcall.actions.save('confirm_order', {
  description: 'Marks the order as confirmed by the shopper, so it ships.',
  kind: 'write',
  parameters: { order_id: { type: 'string', description: 'The order number.' } },
  sample: { confirmed: true },
  transport: 'rowcall',
  requiresConfirmation: true,
})
  • description and parameters are what the AI reads to know when to use the action and what to pass. Write them plainly. Parameters can't change while a published flow uses the action.
  • kind is read (looks something up) or write (changes something).
  • sample is what a success looks like. Simulations use it, and so do calls while the action isn't connected.
  • requiresConfirmation makes RowCall send the action only after the shopper agreed at a confirm step in this call.
  • timeoutMs (500 to 5000; 1500 for reads and 2500 for writes by default) and maxPerCall (default 2) keep the call moving.

A new action of your own needs description, kind and parameters; a preset starts from its own definition, so you send only what you change. Every field is in the reference. DELETE /v1/actions/{name} removes one of yours, or puts a changed preset back.

Allow it in a flow

Defining an action doesn't let any flow use it. Each flow lists its allowed actions in the flow builder, and its action steps can only call those. A request for an action that isn't on the list is refused before it reaches you. When an argument is missing but the run has an input of the same name, such as order_id, RowCall fills it from the run.

Connect it

An action is taken one of three ways, its transport:

  • rowcall: RowCall sends a signed POST to your action URL for the call's mode (below).
  • http: RowCall sends your own HTTP request, built from a template, straight to an API you already have.
  • none: not connected yet; the call gets the action's sample reply.

RowCall's request (rowcall)

Set one action URL for live and one for test in Developers → Actions (HTTPS only, never taken from run inputs). Each has its own signing secret (whsec_…, shown once); after a roll, the old secret signs too for 24 hours. Until a mode has a URL, its calls get the sample reply.

RowCall posts the action request: which action, the arguments the AI filled, the run's metadata, whether the shopper confirmed, and a deadline. It's signed with the same RowCall-Signature as webhooks, checked with verifyActionRequest from the SDK, and carries an Idempotency-Key (<callId>:<actionId>) that stays the same if RowCall sends it again, so do the work once per key.

import { verifyActionRequest } from '@rowcall/sdk'

app.post('/rowcall/actions', express.raw({ type: 'application/json' }), async (req, res) => {
  const body = req.body.toString()
  if (
    !verifyActionRequest({
      body,
      signature: req.get('RowCall-Signature') ?? '',
      secret: process.env.ROWCALL_ACTION_SECRET,
    })
  ) {
    return res.sendStatus(401)
  }
  const request = JSON.parse(body)
  if (request.action === 'confirm_order') {
    const order = await orders.find(request.args.order_id)
    if (order.shippedAt)
      return res.json({ ok: false, errorCode: 'ALREADY_SHIPPED', say: 'This order has already shipped.' })
    await orders.confirm(order.id)
    return res.json({ ok: true, result: { confirmed: true } })
  }
  res.status(404).end()
})

How you answer decides what the agent does next:

  • 200 { "ok": true, "result": { … } }: success. The agent can use result in what it says.
  • 200 { "ok": false, "errorCode": "…", "say": "…" }: refused. The agent explains, using say.
  • 202 { "pending": true }: it takes longer. The agent says it's on it; you finish it later (below).
  • Anything else, or no reply by the deadline: timeout. A read action is tried once more, with the same Idempotency-Key, when two tries fit within 6 seconds.

The flow's action step branches on the result: success, refused, pending or timeout.

Finishing a pending action

When you answered 202, finish the action with POST /v1/runs/{runId}/actions/{actionId}/result, sending ok: true with a result, or ok: false with an errorCode and say. The call may be over by then; the result is still logged and announced. An action that already has its result is refused as CONFLICT.

Your own HTTP request (http)

Point an action straight at an API you already have, with transport: "http" and an http template:

{
  "transport": "http",
  "http": {
    "method": "GET",
    "url": "https://crm.example.in/warranty/{serial}",
    "body": null,
    "headers": { "Authorization": "Bearer …" },
    "successField": "data.isCovered",
    "resultFields": ["data.isCovered", "data.expiresOn"],
    "sayField": "message"
  }
}
  • {parameter} placeholders in the URL and JSON body are filled from the action's arguments.
  • headers (up to 10) are stored encrypted and never shown again; leave them out to keep the saved ones.
  • A 2xx is success, unless successField names a reply field that isn't true (or "ok", "success", "yes"): then it's refused. A 4xx is refused. A 5xx, a redirect or no reply is a timeout.
  • resultFields picks what the AI gets from the reply (dot paths); empty means all of it. sayField is what to tell the shopper on a refusal.

RowCall's own checks

Before taking an action, RowCall refuses it itself with LIMIT_REACHED when maxPerCall is used up, and with NOT_CONFIRMED when it needs a confirmation the shopper hasn't given. Every action is logged with its arguments, result and timing, on the call page and in the run's actions, and announced as action.completed or action.failed.

In test mode, rowcall actions go to your test action URL; http actions never run and get their sample reply instead, so test runs never touch a live system.

Keep it quick, and check again

The shopper is waiting on the phone. Keep timeoutMs short and host your endpoint in India, close to RowCall's servers in Bengaluru. RowCall's checks are a first gate: your system must still check that the order belongs to this shopper and that the change is allowed right now.

Next stepTest mode

Test your actions without a phone call.