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

# Buyer Activity

> Review API calls and domain changes for debugging, compliance, and operational visibility

## Overview

Buyer Activity has two complementary records:

* **Calls** show each admitted buyer API request, including reads, failures, denials, latency, workload identity, and correlation IDs.
* **Changes** show meaningful `CREATE`, `UPDATE`, `DELETE`, `ARCHIVE`, and `EXECUTE` mutations on advertisers, campaigns, creatives, media buys, products, and packages.

Use Calls to debug what an agent attempted. Use Changes to verify what actually changed.

Use audit logs for:

* **Compliance** — review the recorded trail of who changed what and when across multi-tenant workspaces.
* **Debugging** — reconstruct the sequence of events around a campaign that suddenly stopped pacing or a creative that flipped to `rejected`.
* **Post-incident analysis** — answer "what changed in the last hour?" and "did this agent touch anything it wasn't supposed to?".
* **Operational visibility** — feed the data into a SIEM, a Slack channel, or your own dashboard.

Audit logs are scoped to your account. Each row identifies the actor (`userId` + `userEmail` for humans, `serviceTokenId` + `serviceTokenName` for agents), the resource (`resourceType` + `resourceId` + `resourceName`), the action, and the parameters or field-level changes that drove it.

<Note>
  MCP is the primary agent interface. Calls made through MCP and REST appear in the same Activity ledger. The REST endpoints below power the in-app view and let external operational systems query that ledger directly; they are not a separate REST-only activity product.

  Scope3 internal admin and staff operations are excluded from your account's Activity ledger, including when a Scope3 operator is inspecting your workspace.
</Note>

## Open Activity in your agent client

On the v2 buyer MCP server, call the typed `list_buyer_activity` read—or ask your agent to “show my API activity,” “show failed calls,” or “what did this workload do?” MCP clients that support MCP Apps render the Activity page inside the current conversation, so you do not need to switch to the Interchange web app.

```json theme={null}
{
  "name": "list_buyer_activity",
  "arguments": {
    "view": "calls",
    "outcome": "failed",
    "surface": "mcp"
  }
}
```

The page self-fetches authorized data and provides:

* **Calls** — normalized REST, MCP, and A2A operations with workload, outcome, latency, environment, and correlation IDs;
* **Call detail** — recorded request fields and available response or error details, redacted allowlisted headers, validation/response steps, timing, and retry guidance;
* **Ask Murph about this call** — calls the shared typed `ask_murph` tool with the exact `activityUid` and renders the evidence-grounded diagnosis inline, without adding a chat turn; and
* **Changes** — the compatible domain-mutation history previously shown as the entire Activity log.

The typed result includes a compact model-readable summary and the standard `ui://agentic-api/buyer-activity/mcp-app.html` resource directive. Clients without MCP Apps support can still summarize the typed result and query the REST endpoints below. They should not describe the separate Changes feed as a complete API-call history.

## Prerequisites

* A Scope3 API key (see [Authentication](/v2/authentication))
* Buyer members can list account-scoped call and change metadata. Account or advertiser admins can open redacted call detail. Service tokens can see only activity attributed to their stable workload lineage.
* Optional: a campaign or advertiser ID to scope the feed

```bash theme={null}
export SCOPE3_API_KEY=scope3_<your_api_key>
export BASE=https://api.interchange.io/api/v2/buyer
export API_BASE=https://api.interchange.io/api/v2
```

## Step 1: List recent activity

<Steps>
  <Step title="Pull the most recent 50 events">
    ```bash theme={null}
    curl "$BASE/audit-logs?take=50" \
      -H "Authorization: Bearer $SCOPE3_API_KEY"
    ```
  </Step>

  <Step title="Inspect the response">
    ```json theme={null}
    {
      "data": {
        "logs": [
          {
            "id": 81234,
            "timestamp": "2026-04-26T14:30:12.481Z",
            "createdAt": "2026-04-26T14:30:12.481Z",
            "action": "UPDATE",
            "resourceType": "CAMPAIGN",
            "resourceId": "camp_abc123",
            "resourceName": "Q2 Brand Launch",
            "parentType": "BRAND_AGENT",
            "advertiserId": 42,
            "userId": 901,
            "userEmail": "alex@brand.example",
            "userName": "Alex Operator",
            "serviceTokenId": null,
            "serviceTokenName": null,
            "parameters": { "budget": { "total": 250000 } },
            "changes": { "budget.total": { "from": 200000, "to": 250000 } },
            "description": "Increased total budget from $200,000 to $250,000"
          }
        ],
        "total": 4128
      },
      "meta": {
        "pagination": { "skip": 0, "take": 50, "total": 4128, "returned": 1 }
      }
    }
    ```
  </Step>
</Steps>

Each row carries:

| Field                                | Notes                                                              |
| ------------------------------------ | ------------------------------------------------------------------ |
| `id`, `timestamp`, `createdAt`       | Stable row identifier and event time (ISO 8601, UTC)               |
| `action`                             | One of `CREATE`, `UPDATE`, `DELETE`, `ARCHIVE`, `EXECUTE`          |
| `resourceType`                       | `CAMPAIGN`, `CREATIVE`, `MEDIA_BUY`, `PRODUCT`, `PACKAGE`          |
| `resourceId`, `resourceName`         | Public ID and human label of the resource that changed             |
| `parentType`, `advertiserId`         | Hierarchy context (e.g. the advertiser the campaign belongs to)    |
| `userId`, `userEmail`, `userName`    | Set when a human user made the change                              |
| `serviceTokenId`, `serviceTokenName` | Set when an agent / service token made the change                  |
| `parameters`                         | The input payload for the operation (e.g. the body of an `UPDATE`) |
| `changes`                            | Field-level before/after diff, populated where applicable          |
| `description`                        | Pre-rendered human-readable summary                                |

<Tip>
  Exactly one of (`userId`, `userEmail`) and (`serviceTokenId`, `serviceTokenName`) is set per row. Use that to distinguish human edits from agent activity.
</Tip>

## Step 2: Filter

The endpoint supports four filter dimensions. Combine them as needed.

| Query param            | Type                             | Meaning                                                             |
| ---------------------- | -------------------------------- | ------------------------------------------------------------------- |
| `startDate`, `endDate` | ISO timestamps                   | Inclusive time window                                               |
| `advertiserId`         | integer                          | Restrict to a single advertiser                                     |
| `campaignId`           | string (e.g. `camp_abc123`)      | The campaign row plus all of its descendants (media buys, etc.)     |
| `resourceTypes`        | repeated or comma-separated enum | Subset of `CAMPAIGN`, `CREATIVE`, `MEDIA_BUY`, `PRODUCT`, `PACKAGE` |
| `take`, `skip`         | integers                         | Pagination — `take` defaults to `50`, max `500`                     |

<CodeGroup>
  ```bash Time window theme={null}
  curl "$BASE/audit-logs?startDate=2026-04-26T00:00:00Z&endDate=2026-04-26T23:59:59Z&take=200" \
    -H "Authorization: Bearer $SCOPE3_API_KEY"
  ```

  ```bash Single advertiser, last 24h theme={null}
  curl "$BASE/audit-logs?advertiserId=42&startDate=2026-04-25T14:30:00Z" \
    -H "Authorization: Bearer $SCOPE3_API_KEY"
  ```

  ```bash Creatives + media buys only theme={null}
  curl "$BASE/audit-logs?resourceTypes=CREATIVE,MEDIA_BUY&take=100" \
    -H "Authorization: Bearer $SCOPE3_API_KEY"
  ```
</CodeGroup>

<Note>
  `resourceTypes` accepts either a comma-separated value (`?resourceTypes=CAMPAIGN,CREATIVE`) or repeated query params (`?resourceTypes=CAMPAIGN&resourceTypes=CREATIVE`). The default is the full set of buyer resource types.
</Note>

The changes feed always restricts to **meaningful actions** — `CREATE`, `UPDATE`, `DELETE`, `ARCHIVE`, `EXECUTE`. Read-only operations are available from the Calls endpoint below.

## Step 3: Common patterns

### Who changed this campaign?

Scope to a campaign and inspect the actor on each row. The `campaignId` filter matches both the campaign itself and its descendants (media buys, etc.) so you see the full activity tree.

```bash theme={null}
curl "$BASE/audit-logs?campaignId=camp_abc123&take=100" \
  -H "Authorization: Bearer $SCOPE3_API_KEY"
```

Then, on the client, group by `userEmail || serviceTokenName` to see which operator or agent has been touching the campaign.

### What did this agent do today?

Filter to a 24-hour window and look at rows where `serviceTokenId` is non-null. Match `serviceTokenName` against the agent you care about.

```bash theme={null}
curl "$BASE/audit-logs?startDate=2026-04-26T00:00:00Z&take=500" \
  -H "Authorization: Bearer $SCOPE3_API_KEY" \
  | jq '.data.logs[] | select(.serviceTokenName == "ops-bot")'
```

### Did this incident leave a trace?

For post-incident analysis, pull the time window around the symptom and filter to the resource types most likely to have driven it.

```bash theme={null}
curl "$BASE/audit-logs?startDate=2026-04-26T13:45:00Z&endDate=2026-04-26T14:15:00Z&resourceTypes=CAMPAIGN,MEDIA_BUY" \
  -H "Authorization: Bearer $SCOPE3_API_KEY"
```

The `description` field gives a one-line human summary; `parameters` and `changes` carry the full diff for deeper inspection.

## Debug API calls

`GET /activity/calls` returns the normalized call ledger for your account. It is cursor-paginated and defaults to 50 rows.

<Note>
  The Calls view is available to buyer and seller accounts. Durable call capture begins before anyone opens the view, so opening it does not begin or reset workload history.
</Note>

```bash theme={null}
curl "$API_BASE/activity/calls?startTime=2026-07-13T00:00:00Z&outcome=failed" \
  -H "Authorization: Bearer $SCOPE3_API_KEY"
```

Useful filters include `startTime`, `endTime`, `workloadUid`, `runUid`, `clientRunId`, `advertiserId`, `environment`, `outcome`, `operation`, and protocol (`surface`). Pass the returned opaque `nextCursor` as `cursor` without editing it; cursors are scoped to the account and filter set.

`clientRunId` is a caller-supplied correlation identifier, so use it to group calls rather than as independent provenance. To verify a run, cross-check the server-observed `apiVersion`, `workloadUid`, `runUid`, and the expected time window.

Each call includes:

* `activityUid`, `requestId`, and `traceId` for support and tracing;
* the normalized `operation`, protocol, HTTP status, outcome, and latency;
* `operationDispatches`, a bounded server-observed record of buyer or storefront facade operations selected by a v3 tool, in invocation order;
* the stable `workloadUid` and credential snapshot used for that request;
* an explicit `runUid` when the caller supplied a run ID or eligible W3C trace, plus its `clientRunId` correlation identifier when supplied;
* the `apiVersion` observed by the server for the routed request;
* a safe error class, fault domain, and retry disposition when the call failed.

`operationDispatches: null` means the call predates dispatch capture, did not use a captured v3 surface, or ended before the observer could produce a complete snapshot. A non-null envelope distinguishes a completed v3 call that selected no facade operation from one whose evidence is unavailable. Preserve duplicate items: retries and fan-outs are recorded as separate attempts. Treat `truncated: true` or an item whose `completionState` is `started` as incomplete evidence. Native v3 service calls that do not use the buyer or storefront facade are outside this field's scope.

Fetch one call with `GET /api/v2/activity/calls/{activityUid}`. For newly captured calls, the detail response adds:

* `redactedInput`: schema-declared MCP arguments, or schema-validated REST path, query, and body fields, after sensitive values are removed;
* `redactedOutput`: the redacted typed MCP result, or the public REST error envelope returned to the caller (successful REST payloads are not copied into the ledger);
* `safeHeaders`: an explicit allowlist of non-secret debugging headers, such as content type, idempotency key, user agent, request ID, and run ID;
* `steps`: validation and response milestones, including field paths and public validation messages; and
* route, timing, workload, run, trace, and retry guidance.

Unknown MCP arguments and unknown or invalid raw REST fields are not stored. Authorization, cookies, credentials, arbitrary headers or protocol baggage, successful REST response bodies, internal stack traces, and provider costs are never captured. Diagnostic detail expires after 90 days; slim call metadata and correlation IDs remain available for 13 months.

In the native or portable Activity page, select a call and choose **Ask Murph about this call**. The Page calls the buyer MCP server's typed `ask_murph` tool with the exact `activityUid`, then shows the answer or retry state inline. The action does not submit a synthetic user message or modify the host composer, so it behaves the same way in Interchange, Claude, ChatGPT, and other MCP-app hosts. Murph explains only what the evidence supports and can point to the relevant documentation. It cannot bypass account, workload, or private-conversation access rules. You can also paste an `X-Scope3-Activity-Id` into an authorized Murph conversation.

### Group calls into an agent run

For MCP, send the same namespaced metadata on each `tools/call` that belongs to one explicit agent cycle:

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 42,
  "method": "tools/call",
  "params": {
    "name": "api_call",
    "arguments": { "operation": "campaigns.list" },
    "_meta": {
      "scope3/run-id": "nightly-reporting:2026-07-14",
      "scope3/run-label": "Nightly reporting"
    }
  }
}
```

Every admitted MCP response includes `X-Scope3-Activity-Id`. A normal tool result also includes the same correlation in result metadata:

```json theme={null}
{
  "_meta": {
    "scope3/activity": {
      "activityUid": "e691bf1a-2f17-4eec-9394-e647dcfe994d",
      "requestId": "0c240930-4e42-4a7f-8716-7329c862932f",
      "traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
      "runUid": "421ea561-69fc-46ee-93c3-b8e2676ef603"
    }
  }
}
```

For REST, send the equivalent headers:

```bash theme={null}
curl "$BASE/campaigns" \
  -H "Authorization: Bearer $SCOPE3_API_KEY" \
  -H "X-Scope3-Run-Id: nightly-reporting:2026-07-13" \
  -H "X-Scope3-Run-Label: Nightly reporting"
```

Run IDs accept 1–128 characters from letters, numbers, `.`, `_`, `:`, and `-`. Run labels are optional and limited to 120 characters. When no run ID is present, a valid caller-supplied W3C `traceparent` root trace ID can group workload calls; server-created traces never manufacture a run.

Every durably admitted request returns `X-Scope3-Activity-Id` alongside `X-Request-Id` and `X-Trace-Id`. If the activity ledger cannot persist the start, the API returns `503 ACTIVITY_LEDGER_UNAVAILABLE` and does not run the requested operation.

## Best practices

<Tip>
  **Pagination** — the endpoint orders by `timestamp DESC`. Page with `skip` + `take`; for large back-fills, iterate by time window (`endDate` of one page becomes `startDate` of the next) instead of paging deep into a single window.
</Tip>

<Warning>
  **Polling cadence** — for live tailing, poll once per minute and use `startDate` set to the most recent `timestamp` you've already ingested. Do not poll faster than once per 30 seconds.
</Warning>

* **Retention** — safe call diagnostics are retained for 90 days and slim call metadata for 13 months. Archive records you need longer to your own warehouse.
* **Pagination ceiling** — `take` is capped at `500`. For larger exports, iterate by time window.
* **Idempotency** — `id` is stable per row, so client-side dedupe on `id` is safe across overlapping polls.
* **Don't rely on `actions` filtering at the API** — the activity feed always restricts to meaningful actions; filter further client-side if you need to (e.g. only `DELETE`).

## Endpoint reference

| Method | Path                                   | Purpose                                                   |
| ------ | -------------------------------------- | --------------------------------------------------------- |
| `GET`  | `/audit-logs`                          | List buyer activity, with optional filters and pagination |
| `GET`  | `/api/v2/activity/calls`               | List normalized API calls for the authenticated tenant    |
| `GET`  | `/api/v2/activity/calls/{activityUid}` | Inspect one authorized API call                           |

The equivalent typed MCP read is `list_buyer_activity`. It owns the portable Activity page; there is no separate `open_activity` launcher.

**Query parameters**: `startDate`, `endDate`, `advertiserId`, `campaignId`, `resourceTypes`, `take` (default `50`, max `500`), `skip` (default `0`).

**Response**: `{ data: { logs: BuyerAuditLog[], total: number }, meta: { pagination: { skip, take, total, returned } } }`.

See the OpenAPI spec for the full `BuyerAuditLog` shape: [API Reference](/v2/buyer-api-reference).

## Related

* [Notifications](/v2/guides/notifications) — push notifications for the same underlying events
* [Errors](/v2/reference/errors) — error codes you may see surfaced in `parameters`
* [Authentication](/v2/authentication) — required role and token setup
