Skip to content
MeridFlow AiFlow v8.x • self-hosted

Event log

Sending events covers the write side of this endpoint family: POST /api/v1/agents/{agent_id}/events, and how a posted payload turns into a matched and call_ids response. This page covers the read side: listing and inspecting every event an agent has ever received. Come here to answer "I posted an event, why didn't it call anyone?"

Authentication

Both endpoints on this page take an admin session token (see Authentication), not an API key.

Any admin role can read this

Unlike most of the endpoints documented elsewhere on this site, the event log has no elevated role requirement. Owner, Admin, Editor, and Viewer can all call both endpoints below, any active admin account works regardless of role.

List an agent's events

GET /api/v1/agents/{agent_id}/events

Returns every event ever posted to the agent, newest first (ordered by id descending), with no pagination and no query filters. There's no limit parameter. If an agent has accumulated a large event history, handle the narrowing on your own client rather than expecting the server to page results.

curl https://api.your-domain.com/api/v1/agents/1/events \
  -H "Authorization: Bearer $ADMIN_TOKEN"
import httpx

response = httpx.get(
    "https://api.your-domain.com/api/v1/agents/1/events",
    headers={"Authorization": f"Bearer {admin_token}"},
)
response.raise_for_status()
events = response.json()
const response = await fetch("https://api.your-domain.com/api/v1/agents/1/events", {
  headers: { Authorization: `Bearer ${adminToken}` },
});
const events = await response.json();
[
  {
    "id": 123,
    "agent_id": 1,
    "event_type": "abandoned_cart",
    "payload": {
      "customer": { "name": "Jamie", "phone": "+15557654321" },
      "cart_value": 89,
      "items": [1, 2, 3]
    },
    "processing_status": "triggered",
    "processing_error": null,
    "created_at": "2026-01-15T10:30:00Z"
  }
]

Get a single event

GET /api/v1/agents/{agent_id}/events/{event_id}

Returns 404 if event_id doesn't exist, or belongs to a different agent than the one in the URL.

curl https://api.your-domain.com/api/v1/agents/1/events/123 \
  -H "Authorization: Bearer $ADMIN_TOKEN"
import httpx

response = httpx.get(
    "https://api.your-domain.com/api/v1/agents/1/events/123",
    headers={"Authorization": f"Bearer {admin_token}"},
)
response.raise_for_status()
event = response.json()
const response = await fetch(
  "https://api.your-domain.com/api/v1/agents/1/events/123",
  { headers: { Authorization: `Bearer ${adminToken}` } },
);
const event = await response.json();

Fields

Field Type Description
id integer The event's id, the same value returned as event_id in the ingestion response.
agent_id integer The agent this event was posted to.
event_type string The event_type the caller sent, or "" if omitted on ingestion (it's a required field on the request, so this is only ever exactly what was posted).
payload object The exact JSON body the caller posted under payload.
processing_status string One of received, no_match, triggered. See below.
processing_error string or null Set when a matching Trigger couldn't be completed, most commonly an unresolvable phone number. See below.
created_at datetime When AiFlow recorded the event, before trigger evaluation ran.

Interpreting processing_status

Trigger evaluation happens synchronously, inside the same request that ingests the event, before the 201 response is returned. In practice, that means you'll only ever see two of the three possible values by the time you read an event back:

Value Meaning
received The event row's initial default, before trigger evaluation runs. Because evaluation completes within the same request and transaction that creates the event, this state isn't durably observable in normal operation: an event you can read at all has already been evaluated.
no_match Trigger evaluation ran, and nothing fired: no call was queued, no WhatsApp or Telegram message was sent, and no agent_task ran.
triggered At least one enabled Trigger fired: a call, a WhatsApp or Telegram send, or an agent_task run. This is the same signal as matched: true in the ingestion response. If you already captured that response when you posted the event, this field just confirms it after the fact.

no_match is a broader bucket than "nothing about this event looked right." Each of these distinct situations also produces no_match, and processing_status alone can't tell them apart:

  • No enabled Trigger on the agent has an event_type_filter that matches this event's event_type (or has none at all).
  • A candidate Trigger's conditions didn't match against payload.
  • A Trigger's conditions did match, but the resolved phone number was already within that Trigger's cooldown_seconds window. This case is silent: no processing_error is recorded, because suppressing a repeat fire isn't a failure, it's the cooldown working as configured. To distinguish "nothing matched" from "matched but cooled down," check the Trigger's own configuration (see Triggers) against the recipient's recent call and message history yourself.
  • A Trigger's conditions matched but no valid phone number could be resolved. Unlike cooldown suppression, this case is recorded, in processing_error, covered next.

Interpreting processing_error

processing_error is populated when a Trigger matched but its recipient could not be worked out. That happens two ways: a call, whatsapp, or both Trigger whose phone_field_path didn't resolve to a usable phone number in the posted payload (a missing key, or a value that isn't a valid phone number), or a telegram Trigger with no chat to send to, meaning its telegram_chat_id_field_path found nothing, no telegram_chat_id is set on it, and the deployment has no TELEGRAM_CHAT_ID either. The phone case looks like this:

{
  "processing_error": "Trigger 4 ('Abandoned cart follow-up') matched but no valid phone number was found at 'customer.phone'."
}

Two things worth knowing before you rely on this field for debugging:

  • It's a single string field, not a list. If more than one candidate Trigger fails phone resolution for the same event, only the last one encountered during evaluation is kept, earlier failures are silently overwritten. Don't treat an event with processing_error set as proof that exactly one Trigger had a problem.
  • It's never set for an agent_task Trigger (there's no phone to resolve) and never set for cooldown suppression (see above). Its absence doesn't mean every candidate Trigger evaluated cleanly, only that none of them hit this specific phone-resolution failure.

Cross-referencing calls

An event record carries no call id directly (that's what the ingestion response's call_ids is for, captured at send time). To find a call after the fact from the event side, use Calls: a Call record's triggered_by_event_id points back to the event that queued it, so filtering GET /api/v1/agents/{agent_id}/calls for that value gets you from an event to whatever call or calls it produced.

Next

  • Triggers: create, test, and manage the rules that decide whether an event matches.
  • Calls: the call log an event's triggered_by_event_id links into.