# Statboy agent guide

Statboy is an iPhone app that shows its user dashboards, and alerts that arrive as push notifications. It has no editing screens: everything in it is sent by agents like you.

## Connecting

The user gives you a connect code, such as `STAT-7Q4K-92XM-H3TD`, in the prompt they copied from the app. It works once. Call `connect` with the code as a Bearer token and a short name that says what you do for the user, such as `finance-agent`. Until you have a name, only `connect` and `whoami` work.

`connect` returns `api_key`, your lasting Statboy key (`sbk_` and 42 characters). It is shown once. Store it at once wherever your platform keeps secrets (an environment variable, a secret store or your settings), then send it as the Bearer token on every later request. The connect code stops working as soon as you connect. If you lose connect's result, call `connect` again with the same code and name within 15 minutes for a new key.

The code connects one agent. If Statboy says it has expired or was already used and you have no key, ask the user to copy a fresh prompt from the Statboy app.

The MCP server at `https://statboy.ai/mcp` is not available yet; requests to it return a JSON error pointing here. Use the HTTP API.

## Keeping access

Your key lasts until the user revokes it in the Statboy app; it does not expire. Never ask the user for another key, and never write it into chat, logs or files others can read. If you think it was exposed, call `rotate_key` for a new one; the old key stops at once.

For each script, scheduled job or other environment that runs without you, create its own API token with `create_api_token`, named for what it does (such as `weekly-revenue-job`), with `alerts_only` when it only notifies or `alerts_and_data` when it updates dashboards. Store it beside that job. The token (`sbt_` and 42 characters) is shown once; if it is lost, create another and revoke the old one with `revoke_api_token`. A separate token per job means each job is named in the app and can be stopped without touching the others.

If your key stops working (`revoked` or `unauthenticated`), the user revoked it or it was lost. Tell the user and ask them to copy a fresh prompt from the Statboy app; do not try to recover an old connect code.

## HTTP quick start

Every action is `POST https://statboy.ai/api/v1/<action>` with a JSON object as the body.

```sh
# Once, with the connect code. Store the api_key it returns.
curl -sS https://statboy.ai/api/v1/connect -H "Authorization: Bearer STAT-XXXX-XXXX-XXXX" \
  -H "Content-Type: application/json" -d '{"name": "finance-agent"}'
# From then on, with your key.
curl -sS https://statboy.ai/api/v1/send_alert -H "Authorization: Bearer $STATBOY_KEY" \
  -H "Content-Type: application/json" -d '{"title": "Statboy is connected", "summary": "Test alert from finance-agent."}'
curl -sS https://statboy.ai/api/v1/replace_dashboard -H "Authorization: Bearer $STATBOY_KEY" \
  -H "Content-Type: application/json" -d '{"dashboard_key": "revenue", "name": "Revenue", "cards": [
    {"card_key": "mrr", "type": "stat", "label": "MRR", "value": 48214, "format": {"style": "currency", "currency": "USD"},
     "change": {"direction": "up", "amount": "4.1%", "period": "in 30 days"}, "better_when": "higher"},
    {"card_key": "mrr-trend", "type": "line_chart", "title": "MRR", "caption": "30 days",
     "points": [{"at": "2026-10-14", "value": 46312}, {"at": "2026-11-13", "value": 48214}]}]}'
```

## Actions

### connect

Finishes connecting by naming this agent, and exchanges the connect code for your lasting Statboy key. Call it first, with the connect code as the Bearer token: until you have a name, only connect and whoami work. The result&#39;s api_key is shown once. Store it at once wherever your platform keeps secrets and send it on every later request; the connect code stops working. It lasts until the user revokes it. If you lose the result, call connect again with the same code and name within 15 minutes for a new key. The name is shown to the user next to everything you send, so pick a short one that says what you do for them, such as &quot;finance-agent&quot;. Lowercase letters, digits and hyphens, up to 32 characters. A connection is named once; a different agent needs its own connect code.

- `name` (required): Your name, such as &quot;finance-agent&quot;.

### whoami

Tells you who you are in Statboy: your agent name, your organization, what you are allowed to do, and how much of each limit is left. Takes no input.


### rotate_key

Replaces the key you are using with a new one and stops the old one working at once. Use it if your key may have been exposed, or when Statboy&#39;s key_upgrade notice asks you to. The new key is shown once: store it at once wherever your platform keeps secrets, then use it on every request. Works for agent keys and API tokens.


### list_dashboards

Lists every dashboard in the order the Dashboards tab shows them, with its key, name, stale-after period, when its data was last updated and by which agent, whether it is stale now, and its card keys in order. Takes no input.


### get_dashboard

Returns one dashboard with its cards and their current data, so you can see what is there before changing it. Each card is in the form you send it, plus `info` with updated_at, updated_by and, for a stat, history_points. The cards list can be edited and sent back to replace_dashboard as it is.

- `dashboard_key` (required): 1 to 40 lowercase letters, digits, hyphens and underscores, starting with a letter or digit

### replace_dashboard

Sends a complete dashboard: the normal way to build or refresh one. It creates the dashboard if needed and makes its cards match `cards` exactly, in that order, in one step that applies completely or not at all. A card that is on the dashboard but missing from `cards` is deleted with its history, so send every card you want to keep (read them first with get_dashboard). Every card you send counts as freshly updated. Each card is an object with card_key, type (stat, line_chart, bar_chart or text) and that type&#39;s fields; the full rules are at https://statboy.ai/agents#cards. At most one stat may have headline true; it is the number shown on the Dashboards tab, and without one the first stat is used.

- `dashboard_key` (required): The dashboard&#39;s key, such as &quot;revenue&quot;. A new key creates a dashboard.
- `name`: Up to 40 characters. Required when the dashboard is new.
- `stale_after_minutes`: Minutes before the dashboard counts as stale: 5 to 129600 (90 days), or &quot;never&quot;. A new dashboard gets 1440 (24 hours).
- `cards` (required): 0 to 30 cards, in display order.

### set_dashboard

Creates an empty dashboard, or changes a dashboard&#39;s name or stale-after period, without touching its cards. It does not make the dashboard&#39;s data count as fresh. A new dashboard goes to the end of the Dashboards tab.

- `dashboard_key` (required): 1 to 40 lowercase letters, digits, hyphens and underscores, starting with a letter or digit
- `name`: Up to 40 characters. Required when the dashboard is new.
- `stale_after_minutes`: Minutes before the dashboard counts as stale: 5 to 129600 (90 days), or &quot;never&quot;. A new dashboard gets 1440 (24 hours).

### reorder_dashboards

Sets the order of the Dashboards tab. List every dashboard key exactly once, first to last.

- `dashboard_keys` (required): Every dashboard key, in the order to show them.

### delete_dashboard

Deletes a dashboard, its cards and their history at once. It cannot be undone, and the app does not ask the user first.

- `dashboard_key` (required): 1 to 40 lowercase letters, digits, hyphens and underscores, starting with a letter or digit

### set_card

Creates or replaces one card on a dashboard that already exists, leaving the other cards alone. A new card goes to the end unless before_card_key says where; an existing card keeps its place unless before_card_key is given. Replacing a stat with another type, or a number with text, deletes the stat&#39;s history. A stat sent with headline true becomes the dashboard&#39;s only headline. The card rules are at https://statboy.ai/agents#cards.

- `dashboard_key` (required): 1 to 40 lowercase letters, digits, hyphens and underscores, starting with a letter or digit
- `card` (required): A card: card_key, type (stat, line_chart, bar_chart or text) and that type&#39;s fields. See https://statboy.ai/agents#cards.
- `before_card_key`: Optional. Place the card just before this one.

### update_stat

Replaces a stat card&#39;s value and change, and records the value in its history: the cheapest call for a script that tracks one number. `change` and `display` are cleared when you leave them out. A stat holding a number cannot be turned into text here, so its history is never lost by accident; use set_card for that.

- `dashboard_key` (required): 1 to 40 lowercase letters, digits, hyphens and underscores, starting with a letter or digit
- `card_key` (required): 1 to 40 lowercase letters, digits, hyphens and underscores, starting with a letter or digit
- `value` (required): A JSON number, or text of up to 12 characters for a state such as &quot;Healthy&quot;.
- `change`: Optional. The change shown under the value: direction (up, down or flat), amount (up to 10 characters, such as &quot;4.1%&quot;) and period (up to 20 characters, such as &quot;in 30 days&quot;). Colour comes from better_when.
- `display`: one line of text that is not only spaces

### reorder_cards

Sets the order of a dashboard&#39;s cards. List every card key on the dashboard exactly once, first to last.

- `dashboard_key` (required): 1 to 40 lowercase letters, digits, hyphens and underscores, starting with a letter or digit
- `card_keys` (required): Every card key, in the order to show them.

### delete_card

Deletes one card and its history. It cannot be undone, and the app does not ask the user first.

- `dashboard_key` (required): 1 to 40 lowercase letters, digits, hyphens and underscores, starting with a letter or digit
- `card_key` (required): 1 to 40 lowercase letters, digits, hyphens and underscores, starting with a letter or digit

### send_alert

Sends an alert to everyone in the organization. It arrives on their iPhone as a push notification and stays in the app&#39;s Alerts feed. It cannot be edited or deleted afterwards. Severity sets how it arrives: info is silent, warning makes a sound, critical is time-sensitive and gets through most Focus modes. Send only what the user would want to be interrupted for. To make a retried script safe, give a repeat_key: a second alert from you with the same repeat_key within 24 hours is not sent, and the first alert is returned with duplicate true. Limits: 6 alerts a minute per agent, and 30 an hour and 100 a day per organization.

- `title` (required): Up to 60 characters. The notification&#39;s title.
- `summary` (required): Up to 180 characters. The notification&#39;s text and the feed&#39;s summary.
- `severity`: info (default, silent), warning (sound) or critical (time-sensitive).
- `repeat_key`: Optional. A second alert from you with this key within 24 hours is ignored.

### list_alerts

Lists alerts sent to the organization by any agent, newest first, so you can check what was already said before sending more. Pass next_before_alert_id from one page as before_alert_id to get the next.

- `limit`: 1 to 50, default 20.
- `before_alert_id`: Only alerts older than this one.

### create_api_token

Creates an API token for one script, scheduled job or other environment that runs without you, so it never needs your own key and the user never needs to give out another. Name it for the job, such as &quot;weekly-revenue-job&quot;; the app shows that name beside what it sends. Choose alerts_only when the job only sends alerts, and alerts_and_data when it also changes dashboards. The token is shown once: store it with the job, in its secret store. If it is lost, create another and revoke the old one with revoke_api_token.

- `name` (required): The job&#39;s name, such as &quot;weekly-revenue-job&quot;.
- `permission` (required): alerts_only (send alerts and read) or alerts_and_data (also change dashboards).
- `expires_at`: Optional. When the token stops working, such as 2027-01-01T00:00:00Z. Without it, it lasts until revoked.

### list_api_tokens

Lists the organization&#39;s API tokens that still work, without their secrets: name, permission, who created it, when it was last used, and when it expires.


### revoke_api_token

Stops an API token working at once. What it already sent stays, labelled with its name. Name it by the name it was created with.

- `name` (required): lowercase letters, digits and hyphens, starting with a letter or digit

## Cards

A dashboard is an ordered list of up to 30 cards; an organization has up to 50 dashboards. Every card has a `card_key` (unique on its dashboard) and a `type`. Keys are 1 to 40 lowercase letters, digits, hyphens and underscores. Sending content under an existing key updates it; a new key creates it, so a script can send the same keys every run. Statboy lays cards out itself: two stats side by side, everything else full width.

- **stat**: `label` (required, up to 24 characters); `value` (required: a JSON number, or text up to 12 characters such as "Healthy"; never a number in quotes); `format` (`style`: `plain`, `currency` with `currency` such as "USD", `percent` with the number people read, so 4.1 shows as 4.1%, or `unit` with `unit` up to 6 characters; `decimals` 0 to 6); `change` (`direction` up, down or flat; `amount` up to 10 characters such as "4.1%"; `period` up to 20 characters such as "in 30 days"); `better_when` (`higher`, `lower` or `neither`, which colours the change); `display` (up to 12 characters, replaces Statboy's formatting); `headline` (true for the stat shown on the Dashboards tab; at most one per dashboard); `keep_history` (default true for numbers). Statboy records a numeric stat's value each time you send it, at most one value a minute.
- **line_chart**: `title` (required, up to 32); `points` (required, 2 to 400 objects of `at` and `value`, in time order; `at` is a date such as "2026-11-13" or a date and time with an offset, all in the same form); `caption` (up to 16, such as "30 days"); `format`; `description` (up to 200, read by VoiceOver).
- **bar_chart**: `title` (required, up to 32); `bars` (required, 1 to 24 objects of `label` up to 10 characters, `value` 0 or more, and `highlight` true to emphasise it); `caption`; `footnote` (up to 24); `format`; `description`.
- **text**: `body` (required, up to 1,500 characters); `title` (up to 40). The body may use paragraphs, **bold**, *italics*, bulleted and numbered lists, [links](https://statboy.ai) and `inline code`, and nothing else: headings, images, tables, quotes, code blocks, nested lists and HTML are rejected with the line they are on.

A dashboard is stale when no card data has arrived for `stale_after_minutes` (default 1440, or "never"); the app then shows it greyed with an amber badge. Sending the same values again keeps it fresh. Renaming, reordering and deleting do not.

Limits: 1,000 dashboard changes per organization per hour. Every successful dashboard or card action counts as one, so send whole dashboards with `replace_dashboard` rather than one card at a time when you can.

## Responses

- `connect` and `whoami` return `agent` (`name`; `kind`: `connection` for a connect code, `token` for an API token; `permission`: `full`, `alerts_and_data` or `alerts_only`; `expires_at`, null for a connection), `organization` (`name`), `can`, `limits` and `guide`. `can` lists what this credential may do: `read`, `send_alerts`, `change_dashboards` and `manage_tokens`. `connect` also returns `api_key` when it exchanges a connect code. A response may carry `key_upgrade` when you are still using a connect code as your key; follow its message and call `rotate_key`.
- `send_alert` returns `alert_id`, `sent_at`, `duplicate` (true when an earlier alert with the same `repeat_key` was returned instead), `devices_notified` (how many of the organization's iPhones have notifications turned on) and `remaining` (each alert limit's `maximum` and `remaining`).
- `list_alerts` returns `alerts`, newest first, each with `alert_id`, `title`, `summary`, `severity`, `body` (longer text; null for now), `link` (the dashboard and card an alert opens; null for now), `repeat_key`, `sent_by` and `sent_at`, plus `next_before_alert_id` for the next page, or null.
- Alert IDs are unique across Statboy, not counted per organization, so the first alert you send may not be 1.

## Limits

- Alerts: 6 a minute per agent; 30 an hour and 100 a day per organization.
- Requests: 300 a minute per credential. Request body: 1 MB.

## Errors

An error is `{"error": {"code": ..., "message": ...}}`. The message says what to do. Codes: `unauthenticated`, `connect_code_invalid`, `revoked`, `expired`, `forbidden`, `name_required`, `name_taken`, `already_connected`, `not_found`, `invalid`, `limit_reached`, `rate_limited`, `unknown_action`, `too_large`, `internal`.
