> ## Documentation Index
> Fetch the complete documentation index at: https://docs.macropay.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Signals API

> Report what an AI agent did and achieved. Ingest activity and outcome signals to bill on results and prove agent value.

The Signals API is how an AI agent tells Macropay what it did and what it achieved. You `POST /v1/signals` with one or more signals; each becomes an event named `activity.<name>` or `outcome.<name>`, attributed to a specific agent — so you can bill on results and read each agent's value.

## Activity vs. outcome

Every signal has a `kind`. The distinction drives both how you read agent performance and how you bill.

<CardGroup cols={2}>
  <Card title="activity" icon="bolt">
    An action the agent took — `message_sent`, `lead_enriched`, `document_processed`. Activities measure effort and throughput. They are not, on their own, billable results.
  </Card>

  <Card title="outcome" icon="trophy">
    A billable result the agent produced — `meeting_booked`, `ticket_resolved`, `revenue_attributed`. Outcomes carry a `data.value_cents` and an optional `data.verified` flag, and they feed the value receipt.
  </Card>
</CardGroup>

A signal of kind `activity` named `message_sent` is recorded as the event `activity.message_sent`. A signal of kind `outcome` named `meeting_booked` is recorded as `outcome.meeting_booked`. Both are attributed to the `agent_id` you pass, so usage-based meters and per-agent reporting can filter on the agent that produced the work.

## Request shape

`POST https://api.macropay.ai/v1/signals` accepts a batch. Send as many signals as you like in one call.

```json theme={null}
{
  "organization_id": "org_8af21c",
  "signals": [
    {
      "event_name": "message_sent",
      "kind": "activity",
      "agent_id": "agt_sdr_01",
      "external_customer_id": "cus_acme_42",
      "external_id": "msg_2024-06-03T14:22:08Z_8f3a",
      "data": {
        "channel": "email",
        "thread_id": "th_91c2"
      }
    },
    {
      "event_name": "meeting_booked",
      "kind": "outcome",
      "agent_id": "agt_sdr_01",
      "external_customer_id": "cus_acme_42",
      "external_id": "mtg_a1b2c3d4",
      "data": {
        "value_cents": 5000,
        "verified": false,
        "meeting_at": "2026-06-10T15:00:00Z"
      }
    }
  ]
}
```

| Field                                       | Required    | Description                                                                                                                    |
| ------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `event_name`                                | Yes         | The signal name. Mapped to `activity.<name>` or `outcome.<name>`.                                                              |
| `kind`                                      | Yes         | `"activity"` or `"outcome"`.                                                                                                   |
| `agent_id`                                  | Yes         | The agent the signal is attributed to.                                                                                         |
| `customer_id` **or** `external_customer_id` | Yes         | One of the two is required. Use `external_customer_id` to reference your own customer key.                                     |
| `external_id`                               | Recommended | Your unique idempotency key for the signal. Required in practice for outcomes — see [dedupe](#dedupe-outcomes-by-external_id). |
| `data`                                      | No          | Free-form JSON. For outcomes, set `data.value_cents` and optionally `data.verified`.                                           |

<Note>
  A `customer_id` or `external_customer_id` is required on every signal — Macropay attributes the activity and value to a customer as well as an agent. Send one or the other, not both.
</Note>

## Post an activity and an outcome

The example below reports one activity (`message_sent`) and one outcome (`meeting_booked`) in a single batch.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.macropay.ai/v1/signals \
    -H "Authorization: Bearer <token>" \
    -H "Content-Type: application/json" \
    -d '{
      "organization_id": "org_8af21c",
      "signals": [
        {
          "event_name": "message_sent",
          "kind": "activity",
          "agent_id": "agt_sdr_01",
          "external_customer_id": "cus_acme_42",
          "external_id": "msg_2024-06-03T14:22:08Z_8f3a",
          "data": { "channel": "email" }
        },
        {
          "event_name": "meeting_booked",
          "kind": "outcome",
          "agent_id": "agt_sdr_01",
          "external_customer_id": "cus_acme_42",
          "external_id": "mtg_a1b2c3d4",
          "data": { "value_cents": 5000, "verified": false }
        }
      ]
    }'
  ```

  ```python Python theme={null}
  import os, requests

  requests.post(
      "https://api.macropay.ai/v1/signals",
      headers={"Authorization": f"Bearer {os.environ['MACROPAY_ACCESS_TOKEN']}"},
      json={
          "organization_id": "org_8af21c",
          "signals": [
              {
                  "event_name": "message_sent",
                  "kind": "activity",
                  "agent_id": "agt_sdr_01",
                  "external_customer_id": "cus_acme_42",
                  "external_id": "msg_2024-06-03T14:22:08Z_8f3a",
                  "data": {"channel": "email"},
              },
              {
                  "event_name": "meeting_booked",
                  "kind": "outcome",
                  "agent_id": "agt_sdr_01",
                  "external_customer_id": "cus_acme_42",
                  "external_id": "mtg_a1b2c3d4",
                  "data": {"value_cents": 5000, "verified": False},
              },
          ],
      },
  )
  ```
</CodeGroup>

## Dedupe outcomes by external\_id

Outcomes are deduplicated by the pair `(organization, external_id)`. The first signal with a given `external_id` is recorded; later signals carrying the same `external_id` are ignored. This makes the endpoint safe to retry and protects you from double-billing.

<Warning>
  Always set a stable, unique `external_id` on every outcome — derive it from the thing that happened (a meeting ID, a resolved ticket ID, an order ID), not from a timestamp or a random value generated per call. If you regenerate the `external_id` on retry, the same outcome will be counted twice.
</Warning>

Activities can share the dedupe behavior too, but the high-value guarantee is for outcomes, because outcomes are what get billed.

## Outcomes drive the value receipt

For an `outcome` signal, two fields in `data` matter most:

* **`data.value_cents`** — the monetary value of the result, in cents. This is the amount attributed to the agent and rolled up into its [value receipt](/features/agents/value-receipts).
* **`data.verified`** — an optional boolean. Set it `true` once the value is confirmed (the meeting actually happened, the revenue actually landed). Verified value is reported separately from reported value, so you can bill on confirmed results and watch the gap between what an agent claimed and what stuck.

Outcome value and the `verified` flag flow straight into the agent's value receipt, where reported value and verified value are split. To turn outcomes into invoices, point a usage-based [meter](/features/usage-based-billing/introduction) at the relevant `outcome.<name>` event and attach a price.

<Info>
  Reporting a signal never bills a customer on its own. Signals are recorded as events; a [meter](/features/usage-based-billing/introduction) aggregates the matching events and a price turns the aggregate into an amount. Macropay then bills as the merchant of record, handling sales tax and VAT for you.
</Info>

## FAQ

**What is the difference between activity and outcome?**

An activity is an action the agent took (for example `message_sent`), recorded as `activity.<name>`. An outcome is a billable result the agent produced (for example `meeting_booked`), recorded as `outcome.<name>` and carrying `data.value_cents`. Activities measure effort; outcomes measure value and are what you bill on.

**How do I avoid double-billing an outcome?**

Set a stable, unique `external_id` on each outcome, derived from the underlying event (a meeting ID or order ID), not a timestamp or per-call random value. Outcomes dedupe by `(organization, external_id)`, so the first signal wins and any retry with the same `external_id` is ignored — making the endpoint safe to call repeatedly.

**What is verified value?**

Verified value is outcome value you've confirmed actually occurred, marked with `data.verified: true`. Macropay reports verified value separately from reported (unverified) value on the agent's value receipt, so you can bill only on confirmed results and measure the gap between what an agent claimed and what was real.

## Next steps

<CardGroup cols={2}>
  <Card title="Value receipts" icon="receipt" href="/features/agents/value-receipts">
    See how reported and verified outcome value rolls up per agent.
  </Card>

  <Card title="Usage-based meters" icon="gauge" href="/features/usage-based-billing/introduction">
    Aggregate `outcome.<name>` events and attach a price to bill on results.
  </Card>
</CardGroup>
