Most monitors hand you a page diff and leave the interpretation to you. BriefPanel resolves a page into the items that repeat on it — a listing, a coin, a job, a plan — and records each change as a typed update: which field moved, from what to what, and how much it matters.
This API is the read side of that history. Every endpoint is a GET, returns JSON, and paginates
the same way.
Base URL
https://actions.briefpanel.com/v1
Authentication
Send a personal API key as a Bearer token:
Authorization: Bearer YOUR_BRIEFPANEL_API_KEY
Create a key under Settings → Connect your agent in the app (https://app.briefpanel.com/connect-agent).
Keys start with bp_mcp_ and the same key works for the MCP server — one credential
for both surfaces.
OAuth access tokens (bp_oat_) are not accepted here. They are audience-bound to the MCP resource,
so they authenticate MCP calls only.
A missing or invalid key returns 401 with a WWW-Authenticate header.
Quick start
List what you monitor. If this returns rows, your key works:
curl https://actions.briefpanel.com/v1/sources \
-H "Authorization: Bearer YOUR_BRIEFPANEL_API_KEY"
Then read what changed. This is the endpoint most integrations poll:
curl "https://actions.briefpanel.com/v1/updates?importance=material&limit=50" \
-H "Authorization: Bearer YOUR_BRIEFPANEL_API_KEY"
A trimmed response — the full shape is in the OpenAPI document:
{
"data": [
{
"id": "j57e...",
"at": 1750000000000,
"updateType": "modified",
"importance": "material",
"field": "price",
"valueChange": {
"type": "number",
"previous": 149.9,
"current": 129.9,
"unit": "BRL"
},
"entry": { "id": "k17c...", "title": "Plano Pro", "kind": "pricing plan" },
"source": { "id": "m92a...", "url": "https://example.com/pricing" }
}
],
"pagination": { "nextCursor": "eyJ...", "hasMore": true }
}
Endpoints
| Endpoint | Query parameters | Returns |
|---|---|---|
GET /v1/sources |
limit, cursor |
The pages you monitor, newest first, each with its derived status and the entry kinds learned for that page. |
GET /v1/sources/{id}/entries |
limit, cursor |
The items currently resolved on one page, most recently updated first. In-flight onboarding stubs are omitted. |
GET /v1/updates |
since, importance, limit, cursor |
Every item-level change across all your sources, newest first. The feed to poll. |
GET /v1/entries/{id}/updates |
field, importance, limit, cursor |
One entry's full event stream. Filter by field for a single field's history — the read behind a price chart. |
GET /v1/entries/{id}/changes |
limit, cursor |
One card per fetch that changed this entry: diff churn counts, the derived price move, and pointers to the captured evidence. |
GET /v1/changes/{id}/snapshot |
state, format |
The captured page state behind a change. Returns bytes, not the JSON envelope. |
An id belonging to another account returns 404, not 403 — the API does not confirm that an id
it will not serve exists.
Pagination
Every collection takes limit and cursor and answers with a pagination object:
{ "pagination": { "nextCursor": "eyJ...", "hasMore": true } }
Pass nextCursor back as cursor to get the next page, and keep going until hasMore is false.
Treat hasMore as the only end-of-collection signal. A page can come back empty, or shorter than
your limit, while there is still more to read — stopping on a short page will silently truncate
your sync.
Polling the feed
GET /v1/updates is ordered newest first. Read at off the newest item and pass it back as
since on the next poll to get only what has moved since:
curl "https://actions.briefpanel.com/v1/updates?since=1750000000000" \
-H "Authorization: Bearer YOUR_BRIEFPANEL_API_KEY"
since accepts epoch milliseconds (the form at returns) or an ISO-8601 instant. importance
accepts critical, material, or minor.
Each item carries its parent entry and source inline, so an update is actionable without a
second call.
Entry kinds
Each entry carries a kind — pricing plan, job posting, cryptocurrency. The names are
learned per source, so compare them only within one source; the vocabulary for a given page is
published as entryKinds on that source, along with hasPrice so you know when a missing price is
meaningful.
entryKinds is absent until a source has been analyzed.
Source health
GET /v1/sources returns a derived status:
status |
Meaning |
|---|---|
active |
Being checked on schedule. |
autoPaused |
Paused after repeated fetch failures. |
creditPaused |
Paused for lack of credits. |
disabled |
Switched off by you. |
lastFetchOutcome, lastFetchErrorReason, consecutiveFetchFailures and nextFetchTime carry the
detail behind it.
Evidence
GET /v1/changes/{id}/snapshot serves the raw page state a change was derived from, so you can show
where a value came from rather than asking someone to trust it.
Snapshots are deduped across accounts and have no owner of their own, so they are addressed through
the change they belong to rather than by their own id. Take changeId from a timeline card and
check hasCurrentSnapshot / hasPreviousSnapshot before requesting a state.
state—current(default) orprevious.previousreturns404on a first change, which has no prior state.format—markdown(default),html, orscreenshot.screenshotanswers with a302to a short-lived signed image URL.
Errors
Failures return an error object:
{ "error": { "code": "not_found", "message": "No source with that id." } }
Branch on code rather than on the HTTP status — the codes are a fixed set and will not change
under you.
code |
HTTP | When |
|---|---|---|
unauthorized |
401 | Missing, malformed, or revoked key. |
forbidden |
403 | The key lacks the scope this endpoint needs. |
not_found |
404 | No such id, including one that belongs to another account. |
invalid_request |
400 | An unparseable or out-of-range parameter. |
rate_limited |
429 | Too many requests for this key. |
internal |
500 | An error on our side. |
Rate limits
Reads are limited per API key, in a fixed window of 300 requests per minute. Over the limit you get
429 with a Retry-After header in seconds — honor it rather than retrying immediately.
OpenAPI
The full specification is served as a live OpenAPI 3.0 document:
https://actions.briefpanel.com/v1/openapi.json
Import it into Postman, point an SDK generator at it, or hand the URL to an agent. It describes every endpoint, the auth scheme, the pagination contract, and the complete response schemas.
It is served unauthenticated on purpose — tooling has to be able to read an API's description before it holds a key.