Three on-ramps. One brief.
Sign in and use REST with a bearer key, connect over MCP (including from the Ounie AI Team), or skip the account entirely and pay per call in USDC. All three run the same pipeline and return the same brief.
There is no scheduler here
Briefing Desk is one call, one brief. There is no endpoint that creates a schedule, no subscription object, no webhook you register, no watchlist that fires and no digest queue — and there will not be one. The state model is: briefs you have already paid for are stored and re-readable for free.
An agent running a daily brief simply calls the endpoint each morning from whatever already wakes up in its stack — cron, a task runner, its own loop — and keeps the JSON wherever it already keeps things. That keeps the schedule yours, keeps the ability to stop it yours, and means the keyless USDC rail behaves exactly like the account rail, which it could not if there were a subscription to attach to.
Authentication · two keys, one wallet
Mint a per-app key at /dashboard/api-keys. It is shown once; only its hash is stored. Send it as Authorization: Bearer brf_live_…, or as ?api_key=brf_live_… when your host cannot set headers.
The fleet master key (ounie_live_… from ounie.com, with “Use across Ounie apps” enabled) works everywhere a brf_live_ key does and unlocks the brain lens, because it is the only credential we can forward upstream on your behalf. Both draw the same wallet, and neither can overdraw: a short balance is a 402 refusal, never a queued job.
curl -X POST https://briefing.ounie.com/api/briefs \
-H "Authorization: Bearer brf_live_..." \
-H "Content-Type: application/json" \
-d '{
"topic": "stablecoin regulation in the EU",
"recency_days": 1,
"sources": 12,
"domains_include": ["reuters.com", "ft.com"],
"format": "both"
}'Parameters
| Field | Type | Notes |
|---|---|---|
| topic | string, required | 2–300 chars. This is the search query the brief is built from — a missing or empty topic is a 400 on every rail, including x402, before any price is quoted. |
| recency_days | int 1–30 | Default 1. Only pages published inside this window are read. Out-of-range values clamp rather than fail. |
| sources | int 5–20 | Default 12. How many pages to fetch and read. More sources widens the read; it does not lengthen the brief. |
| lens | string ≤ 500 | Free-text steer. Adds a search angle and tells the writer what to weight. |
| domains_include | string[] | Hosts or URLs. Anything without a dot is dropped rather than sent upstream, where it would silently match nothing. |
| domains_exclude | string[] | Same format, inverted. |
| brain_ids | uuid[] | Up to 5. The ranking lens. Needs a session or the ounie_live_ master key; without one the brief still runs, unlensed, and says so in lens.note. |
| format | markdown | json | both | Default both. Anything else is a 400. |
The citation contract
The model is handed a numbered list of the pages we fetched and answers with a source_index. It never writes a URL, so it cannot invent one. Every index is then re-checked in code against that same list:
- an index outside the fetched range → the item is deleted, not reassigned
- surviving sources are renumbered compactly, so [1] really is the first one
- a page nobody cited is not shipped as a source — it is counted in
sourcesConsultedinstead - a quote that does not appear verbatim in its source is replaced by the provider's own highlight, never by a paraphrase presented as a quotation
- zero surviving items ⇒ thin ⇒ refunded (credits) or refused before settlement (x402)
watchList and openQuestions are forward-looking by design and carry no citation — they are labelled as speculative in the response and in every rendering, and they are the one part of a brief that is not a claim about the past.
{
"headline": "...",
"throughLine": "...",
"summary": "...",
"items": [
{
"title": "...",
"whyItMatters": "...",
"quote": "verbatim excerpt or null",
"sourceIndex": 1,
"url": "https://...",
"source": "reuters.com",
"publishedAt": "2026-08-24T09:00:00.000Z"
}
],
"watchList": ["..."],
"openQuestions": ["..."],
"sources": [
{ "index": 1, "title": "...", "url": "https://...",
"source": "reuters.com", "publishedAt": "..." }
],
"lensed": false,
"lensNames": [],
"sourcesConsulted": 12,
"itemsDropped": 0,
"generatedAt": "..."
}A brief carries at most 12 items. Fewer is normal and is not an error — most windows do not hold twelve distinct developments.
REST endpoints
/api/briefs12 credits/api/briefsfree/api/briefs/{id}?format=freejson, markdown or both./api/briefs/{id}free/api/brainsfree/api/x402/brief~$0.144 USDCErrors, and when you are not charged
| Status | error | Means |
|---|---|---|
| 400 | invalid_request | Something fatal and knowable from your request alone — a missing topic, an unknown format. Never billed, and on x402 never even quoted. |
| 401 | auth_required | No usable credential. |
| 402 | insufficient_credits | Carries required_credits, balance_credits and buy_credits_url. Nothing ran. |
| 422 | thin_material | Nothing published in the window, or nothing anchored to a citable source. Refunded pool-exact. |
| 502 | generation_failed | The model returned nothing usable. Refunded. |
| 503 | temporarily_unavailable | Search is down on our side. Refunded. This is our outage, not your error. |
MCP · /api/mcp
Streamable HTTP at /api/mcp (legacy SSE at /api/sse). Works with Claude, Cursor, the AI SDK, ChatGPT and the Ounie AI Team — whose manual HTTP entries cannot set static headers, which is why the key may ride the URL as ?api_key=.
{
"mcpServers": {
"briefing-desk": {
"url": "https://briefing.ounie.com/api/mcp?api_key=brf_live_..."
}
}
}| build_brief | One cited brief on a topic, from pages published inside your window. | 12 credits |
| list_briefs | Your stored briefs, newest first. | free |
| get_brief | One stored brief in full, as JSON or Markdown. | free |
| list_brains | The Ounie brains available as a ranking lens. Master key only. | free |
| get_credit_balance | Spendable Ounie credits + monthly included. | free |
| get_pricing | Per-action pricing. No auth required. | public |
| whoami | The authenticated key's owner + key id. | free |
There is no scheduling tool in that list, and its absence is the design. Call build_brief on your own clock.
x402 · keyless, pay-per-call
POST /api/x402/brief with no X-Payment header returns a 402 carrying the price for your exact payload — 12 credits at 1.2×, so 144000 atomic USDC (~$0.144) on Base. Sign the EIP-3009 authorization, retry with the header, and you get the brief.
A keyless caller has no brains, so the lens is supplied inline as source_text — optional, minimum 200 characters when present. Nothing else differs: the facts come from live sources the app fetches itself on both rails.
Two gates protect your money. Anything fatal we can see in the request alone — a missing topic, an unknown format, a source_text below the minimum — returns a 4xx with no price in the body, because making you sign an authorization for work that cannot succeed is worse than refusing. Anything only knowable after the work — an empty window, no citable item — is caught after your payment is verified but before it is settled. The order is always verify → run → settle: a settlement is final on chain and there is no refund, so nothing moves until there is a real brief to hand back.
curl -X POST https://briefing.ounie.com/api/x402/brief \
-H "Content-Type: application/json" \
-d '{"topic":"stablecoin regulation","recency_days":1}'
# → 402 with accepts[0].maxAmountRequired = 144000
# network "base", asset = USDC, extra = the USDC EIP-712 domain
curl -X POST https://briefing.ounie.com/api/x402/brief \
-H "X-Payment: <base64 payment payload>" \
-H "Content-Type: application/json" \
-d '{"topic":"stablecoin regulation","recency_days":1}'