Briefing Desk
API

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

FieldTypeNotes
topicstring, required2–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_daysint 1–30Default 1. Only pages published inside this window are read. Out-of-range values clamp rather than fail.
sourcesint 5–20Default 12. How many pages to fetch and read. More sources widens the read; it does not lengthen the brief.
lensstring ≤ 500Free-text steer. Adds a search angle and tells the writer what to weight.
domains_includestring[]Hosts or URLs. Anything without a dot is dropped rather than sent upstream, where it would silently match nothing.
domains_excludestring[]Same format, inverted.
brain_idsuuid[]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.
formatmarkdown | json | bothDefault 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 sourcesConsulted instead
  • 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

POST/api/briefs12 credits
Build one brief. Cookie or Bearer.
GET/api/briefsfree
List your briefs.
GET/api/briefs/{id}?format=free
One stored brief, as json, markdown or both.
DELETE/api/briefs/{id}free
Archive a brief.
GET/api/brainsfree
The brains available as a lens. Needs a forwardable ounie.com credential.
POST/api/x402/brief~$0.144 USDC
Keyless. See below.

Errors, and when you are not charged

StatuserrorMeans
400invalid_requestSomething fatal and knowable from your request alone — a missing topic, an unknown format. Never billed, and on x402 never even quoted.
401auth_requiredNo usable credential.
402insufficient_creditsCarries required_credits, balance_credits and buy_credits_url. Nothing ran.
422thin_materialNothing published in the window, or nothing anchored to a citable source. Refunded pool-exact.
502generation_failedThe model returned nothing usable. Refunded.
503temporarily_unavailableSearch 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_briefOne cited brief on a topic, from pages published inside your window.12 credits
list_briefsYour stored briefs, newest first.free
get_briefOne stored brief in full, as JSON or Markdown.free
list_brainsThe Ounie brains available as a ranking lens. Master key only.free
get_credit_balanceSpendable Ounie credits + monthly included.free
get_pricingPer-action pricing. No auth required.public
whoamiThe 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}'