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

# Account Insights

> A deterministic financial health report over a customer's account activity, plus a read-only advisory chat that narrates it

Account Insights answers two questions for a customer: *"how is my account doing?"* and *"what should I do about it?"* — as data your UI can render directly, and as a conversational layer on top.

* **`GET /customers/{customer_id}/insights`** — a computed report: a snapshot of balances and obligations, observations about recent activity, and advisory suggestions. **No LLM anywhere in this path** — every number, date, and sentence is computed deterministically from the customer's own objects.
* **`POST /customers/{customer_id}/insights/chat`** — an advisory chat that answers questions about the account. The model narrates the same deterministic data the report is built from; it holds **no write capability of any kind**.

<Info>
  **Alpha** — part of the agentic early-access surface. Insights are read-only by construction: neither endpoint can move money, create, or change anything.
</Info>

## The report

The report is a **view, not a stored object** — computed on demand from the customer's scheduled payments, mandates, recipients, wallets, and balances. There is nothing to create, poll into existence, or clean up.

```bash theme={null}
curl https://api.platform.dakota.xyz/customers/2vWxCustomer0000000000000000/insights \
  -H "X-API-Key: $DAKOTA_API_KEY"
```

```json theme={null}
{
  "customer_id": "2vWxCustomer0000000000000000",
  "generated_at": 1783075200,
  "snapshot": {
    "total_usd": "3100.25",
    "balances": [
      { "wallet_id": "2vWxWallet000000000000000000", "name": "Operating",
        "network_id": "base-mainnet", "asset": "USDC", "amount_usd": "3100.25" }
    ],
    "upcoming": { "days": 14, "count": 3, "totals": { "USDC": "5200" } },
    "open_scheduled_payments": 5,
    "active_mandates": 2
  },
  "insights": [
    {
      "kind": "upcoming_payments",
      "severity": "info",
      "message": "3 payment(s) scheduled in the next 14 days (5200 USDC).",
      "detail": { "count": 3, "window_days": 14 },
      "evidence": [ { "type": "scheduled_payment", "id": "2vWxPayment00000000000000000" } ]
    }
  ],
  "suggestions": [
    {
      "kind": "funding_shortfall",
      "severity": "critical",
      "message": "A funding wallet shows $3100.25 of USDC but 5200 USDC of payments fire from it on base-mainnet in the next 7 days — top up at least ~2099.75 USDC by 2026-07-14 or 3 payment(s) may fail.",
      "detail": { "wallet_id": "2vWxWallet000000000000000000", "shortfall_estimate": "2099.75" },
      "evidence": [
        { "type": "wallet", "id": "2vWxWallet000000000000000000" },
        { "type": "scheduled_payment", "id": "2vWxPayment00000000000000000" }
      ]
    }
  ]
}
```

Three parts:

* **`snapshot`** — typed facts for stat cards: per-wallet balances (aggregated over all the customer's linked wallets), upcoming-payment totals per asset, open/active counts.
* **`insights[]`** — observations: what *is happening*.
* **`suggestions[]`** — advice: what the customer *may want to do*. Always advisory — acting on one is a separate, human-gated step through the normal payment flow.

Both arrays share **one item schema**: `{kind, severity, message, detail, evidence}`.

### Rendering items

* **`severity`** is a closed set: `info | warn | critical`. Sort and color by it.
* **`kind`** is an **open set** — new kinds appear over time without a breaking change. Render an unrecognized kind generically from `message` + `severity`; never drop it.
* **`message`** is a complete, human-readable sentence. A minimal UI can show messages alone and be correct.
* **`detail`** carries the raw computed values behind the sentence, for custom rendering.
* **`evidence[]`** contains typed references (`{type, id}` — `wallet`, `scheduled_payment`, `mandate`, `recipient`; up to 10 per item) to the exact objects the item was computed from — deep-link each claim to the data behind it. `evidence.type` is open-set like `kind`.

### The kind catalog

| kind                         | array       | severity        | fires when                                                                                                                          |
| ---------------------------- | ----------- | --------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `upcoming_payments`          | insights    | info            | open payments due within the next 14 days (count + per-asset totals)                                                                |
| `payment_failures_clustered` | insights    | warn            | ≥2 recent failures to the same payee sharing one reason — the likely root cause, surfaced once                                      |
| `payments_failed`            | insights    | info            | remaining recent singleton failures                                                                                                 |
| `account_activity`           | insights    | info            | executed volume over the last 30 days + mandates awaiting signature                                                                 |
| `new_counterparty`           | insights    | info            | open payments to a recently added payee                                                                                             |
| `counterparty_concentration` | insights    | info            | one payee dominates recent executed outflow                                                                                         |
| `payment_at_risk`            | suggestions | warn            | open payments already past their scheduled time                                                                                     |
| `funding_shortfall`          | suggestions | critical        | a funding wallet's balance is below what fires from it in the next 7 days — includes the estimated top-up and deadline              |
| `mandate_expiring`           | suggestions | warn / info     | an active mandate ends within 14 days — `warn` if open payments depend on it                                                        |
| `mandate_headroom`           | suggestions | critical / warn | a mandate window cannot cover what's planned (`critical` — the excess **will** be denied at fire time) or is ≥80% consumed (`warn`) |

A mandate that is exactly on plan — filled by its own scheduled series — stays silent; headroom items fire only on breach or tightness.

### Degradation, not errors

A deployment without a balance index simply omits balance-derived parts (snapshot balances, shortfall math); the rest of the report still computes. Missing data degrades the report — it never 500s.

## The advisory chat

```bash theme={null}
curl -X POST https://api.platform.dakota.xyz/customers/2vWxCustomer0000000000000000/insights/chat \
  -H "X-API-Key: $DAKOTA_API_KEY" \
  -H "X-Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [
      { "role": "user", "content": "Anything I should know about my account this week?" }
    ]
  }'
```

```json theme={null}
{
  "reply": "You have 3 payments (5200 USDC) due in the next two weeks, and your Operating wallet shows $3100.25 — about 2100 USDC short. Consider topping it up before July 14.",
  "conversation_status": "ok"
}
```

The chat is **stateless**: send the running transcript (up to 40 messages) each turn and append the reply. Replies come back as light GitHub-flavored markdown (bold, bullet lists) ready to render.

### Where the numbers come from

The model never computes facts. It reads them from deterministic, server-computed tool data:

| It can read        | Which is                                                                                  |
| ------------------ | ----------------------------------------------------------------------------------------- |
| the insight report | byte-identical to what `GET .../insights` returns — chat and dashboard can never disagree |
| recent payments    | statuses, amounts, payee names, and human-readable dates, resolved server-side            |
| mandates           | with used/remaining headroom, same math as the fire-time gate                             |

There is no write tool, no propose tool, and no way to pass another customer's identifiers — the tools are scoped to the path customer by construction. Asked to *do* something ("pay this invoice"), the assistant declines and points at the payment flow.

### Conversation status

`conversation_status` reports where the conversation stands with the assistant's scope guard:

| Value     | Meaning                                                                                                                       |
| --------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `ok`      | on-topic                                                                                                                      |
| `warned`  | the conversation drifted off account matters — warnings escalate                                                              |
| `blocked` | the conversation is terminated; further turns get a canned refusal without spending model budget. Start a fresh conversation. |

### Bounds

Requests are validated before any model spend: at most **40 messages**, **4,000 characters per message**, **64 KB total**; empty messages are rejected. Chat turns are rate-limited **per customer** — 100/hour and 500/day — returning `429` beyond that. The report endpoint is deterministic and carries no chat-specific limit beyond the standard [API rate limits](/documentation/authentication/rate-limiting).

## Report or chat?

Render the **report** — it is structured, deterministic, and cheap. Add the **chat** where a customer benefits from asking follow-ups ("why did my payments to Brightline fail?") — its answers cite the same data the report shows, so the two surfaces always agree.
