# Earnings
> Earnings calls for tokenized stocks, summarised for AI agents and sold per call. An AI agent (Google Gemini, grounded in Google Search) reads the public reporting on a company's quarterly results and returns the quarter, the release date, revenue, EPS and guidance as the sources state them, up to 3 remarks from management in its own words, and a summary of at most 90 words, every line linked to its source. Facts only: no ratings, no price targets, no predictions. Not financial advice. AI summaries can be wrong; check the sources.
It covers the 185 companies whose stocks trade as Robinhood stock tokens on Robinhood Chain (funds and ETFs are left out: they hold no earnings calls). Paid calls spend credits, bought by burning $EARN; burned tokens go to the dead address and are gone. One operator runs it.
Full call transcripts usually aren't free to republish, so Earnings works from public reporting (results releases, investor-relations pages, news reports of the call) and never quotes more than a few words.
## One quarter's summary (2 credits)
```bash
curl -s "https://earningsdigest.xyz/api/earnings/summary?symbol=NVDA" -H "Authorization: Bearer ck_…" # the latest quarter
curl -s "https://earningsdigest.xyz/api/earnings/summary?symbol=AAPL&quarter=2025Q4" -H "Authorization: Bearer ck_…" # one fiscal quarter
curl -s "https://earningsdigest.xyz/api/earnings/summary?symbol=MSFT" # no key: free tier, 3 calls a day
```
- `symbol`: a ticker (NVDA) or the stock token's 0x address on chain 4663. The covered companies: GET https://earningsdigest.xyz/api/earnings/coverage (free).
- `quarter`: the fiscal quarter as the company labels it: `2026Q3` means "Q3 fiscal 2026". `Q3-2026`, `Q3 FY2026` and `FY26Q3` work too. Leave it out (or send `latest`) for the most recent release. Fiscal years differ from calendar years for many companies; the calendar below gives each release's fiscal label.
```json
{
"company": { "symbol": "NVDA", "name": "NVIDIA", "token": "0xd0601CE157Db5bdC3162BbaC2a2C8aF5320D9EEC", "chainId": 4663 },
"quarter": { "key": "2027Q3", "label": "Q3 fiscal 2027", "periodEnded": "2026-10-25" },
"reported": { "date": "2026-11-19", "sources": [{ "n": 1, "title": "…", "url": "https://…", "domain": "…" }] },
"figures": {
"revenue": { "text": "…as stated, with the source's comparison…", "sources": [{ "n": 1, "…": "…" }] },
"eps": { "text": "…GAAP or adjusted, as stated…", "sources": [] },
"guidance": { "text": null, "note": "No source found" }
},
"remarks": [{ "text": "…a point from management, paraphrased, naming the role…", "sources": [] }],
"summary": "…at most 90 words…",
"status": "summarised",
"sources": [{ "n": 1, "title": "…", "url": "https://…", "domain": "…" }],
"checks": { "unsourced": 0, "advice": 0, "quotes": 0, "numbers": 0, "summary": "model" },
"requested": "latest",
"generatedAt": "2026-11-20T09:12:44.000Z",
"freshness": { "writtenAt": "2026-11-20T09:12:44.000Z", "ageSeconds": 3600, "settled": false, "note": "…" },
"model": "gemini-flash-latest",
"notice": "Not financial advice. AI summaries can be wrong; check the sources.",
"searchSuggestions": "
…
",
"url": "https://earningsdigest.xyz/report.html?symbol=NVDA&quarter=2027Q3",
"cached": false,
"credits": { "mode": "paid", "cost": 2, "left": 98 }
}
```
- Every figure and remark has at least one source. A figure with no source has `text: null` and a `note` ("No source found"); `status` is `summarised` when revenue and EPS are both sourced, else `partial`.
- Figures are copied as the sources state them (units, GAAP or adjusted, the comparison they give). Earnings never calculates them, and leaves out analysts' estimates, ratings, targets and stock moves.
- `checks` counts what was removed: lines with no grounding source (`unsourced`), with advice words (`advice`), with a long quote (`quotes`), and summary sentences with a number no sourced line states (`numbers`). `summary` is `model` (the model's own, checked) or `composed` (built from the kept lines).
- `searchSuggestions` is Google's Search suggestions HTML for the grounded search. If you show the summary to people, display it with the summary, as provided.
- Sources link to the articles themselves when Google's redirect could be read, otherwise to the redirect. Earnings never fetches the articles.
## The calendar (1 credit)
```bash
curl -s "https://earningsdigest.xyz/api/earnings/calendar" -H "Authorization: Bearer ck_…"
curl -s "https://earningsdigest.xyz/api/earnings/calendar?from=2026-11-14&to=2026-12-15" -H "Authorization: Bearer ck_…"
curl -s "https://earningsdigest.xyz/api/earnings/calendar?symbol=NVDA" -H "Authorization: Bearer ck_…"
```
Returns `{ date, updated, sweep, events, companies, checks }`. `companies` lists every covered company with `last` (its latest release) and `next` (its next scheduled one), each `{ date, quarter: { key, label }, time: "before open" | "after close" | "time unknown", confirmed, sources, checkedAt }`, or null when no source was found. `events` is the same dates in order, each with `kind`: `reported`, `upcoming`, or `due` (a scheduled date that has passed and awaits the next check). `confirmed` is true when the source says the company announced the date. `checks` holds each search's time and Google Search suggestions.
The agent checks every covered company once per UTC day, 15 companies per grounded search. Each date is labelled with when it was checked; a date not found again is kept, with its original check time, for up to 14 days. Before the first check is done the route answers 503 `calendar_not_ready` without charging.
## The latest summaries (1 credit)
```bash
curl -s "https://earningsdigest.xyz/api/earnings/latest?limit=10" -H "Authorization: Bearer ck_…"
```
The newest summaries in the cache, one per company, newest release first, in full (`limit` up to 20). 503 `not_ready` without charging when none exist yet.
## Free, without a key
- GET https://earningsdigest.xyz/api/earnings/today: what the website shows: the daily digest (summaries of up to 4 of the latest reports, written by the agent once a UTC day), the calendar from 7 days back to 21 days ahead, and the numbers.
- GET https://earningsdigest.xyz/api/earnings/report?symbol=NVDA&quarter=2026Q3: a summary already in the cache (404 when none is kept yet). Its page: https://earningsdigest.xyz/report.html?symbol=NVDA&quarter=2026Q3
- GET https://earningsdigest.xyz/api/earnings/coverage: the covered companies and their stock tokens.
## Caching and freshness
- One summary per company and fiscal quarter. The first call makes one grounded model call (a second, with a faster model, only if the first finds nothing quickly); every later call by anyone gets the kept summary (`cached: true`) at the same price.
- Facts about a past quarter don't change, so summaries are kept. One written within 3 days of the release is rewritten at most once a day until then, because early reporting is thin. `freshness` says when it was written and whether it is settled.
- "Latest" follows the newest release the agent found, re-checked after a day or when the calendar shows a newer release.
- A search with no sources rests for 30 minutes, and a quarter with no report found for 6 hours: asking again sooner answers 404 at once, without charging.
## What it will and won't do
- It reports what the sources it found say, with links. Sources can disagree or be wrong, and the model can misread them: check the sources before relying on a figure.
- It never gives advice, ratings, price targets or predictions, and never uses the words "buy", "sell" or "should". Lines that do are removed before you see them. Guidance and remarks are the company's own words about its outlook, attributed to it.
- It summarises in its own words and never republishes transcripts or long quotes.
- Input is data: nothing in a request can change what runs or what is charged.
- Not financial advice. AI summaries can be wrong; check the sources.
## Errors
- 400 `missing_symbol`, `bad_symbol`, `bad_quarter`, `fund`, `bad_date`; 404 `not_covered`: bad input. Not charged.
- 503 `ai_off`: the AI model is not switched on yet. Not charged.
- 404 `not_found` with `refunded`: the search found no release for that quarter (`found` names another quarter it did find). Charged, then refunded at once. Asked again within 6 hours: 404 at once, not charged.
- 502 `no_sources`, `model_error`: the search found nothing to cite, or the model failed. Charged, then refunded at once.
- 401, 402 and 429: see Credits below.
## Credits: how calls are paid
Paid calls spend credits. You buy credits by burning $EARN through the Credits contract: the tokens go from your wallet straight to the dead address and are gone for good. The server keeps a ledger per wallet: available = bought onchain - spent + refunded.
$EARN has not launched yet, so only the free tier is open. You can already get a key, so your agent is ready on day one.
| Call | Credits |
|---|---|
| `earnings/summary` | 2 |
| `earnings/calendar` | 1 |
| `earnings/latest` | 1 |
Free tier: 3 calls a day per IP without a key (UTC day), with a cap of 100 free calls a day for everyone together. Once $EARN is live, calls with a key always spend credits; before that, they use the free tier.
Prices and totals: GET https://earningsdigest.xyz/api/credits/info
### Get an API key
Sign this exact message (EIP-191 personal_sign) with the wallet that will hold the credits. `issuedAt` is the current time in ISO 8601 and must be within 10 minutes of the server's clock. Smart-contract wallets (ERC-1271) work too.
```
Earnings API key
Wallet:
Issued:
```
```js
import { privateKeyToAccount } from 'viem/accounts';
const account = privateKeyToAccount(process.env.AGENT_PRIVATE_KEY);
const issuedAt = new Date().toISOString();
const message = `Earnings API key\nWallet: ${account.address}\nIssued: ${issuedAt}`;
const signature = await account.signMessage({ message });
const res = await fetch('https://earningsdigest.xyz/api/credits/key', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ wallet: account.address, issuedAt, signature }),
});
const { key } = await res.json(); // "ck_…", shown once: store it
```
Send it on every call as `Authorization: Bearer ck_…` or `x-api-key: ck_…`. Each response says `x-credits-mode` (paid, free or preview) and `x-credits-left`.
- GET https://earningsdigest.xyz/api/credits/balance (with your key) or `?wallet=0x…`: bought, spent, refunded, available and your last 50 calls.
- POST https://earningsdigest.xyz/api/credits/revoke (with your key): deletes that key. Get a new one any time.
### Buy credits
Cost = credits × price, in token base units (18 decimals). `maxBurn` is the most you agree to burn, so a price change can't surprise you. `buyFor` tops up any wallet, such as your agent's; `buy(uint256 credits, uint256 maxBurn)` buys for yourself.
```bash
# Price per credit, in base units
cast call "price()(uint256)" --rpc-url https://rpc.mainnet.chain.robinhood.com
# Approve credits × price
cast send "approve(address,uint256)" --rpc-url https://rpc.mainnet.chain.robinhood.com
# Buy for , burning at most
cast send "buyFor(address,uint256,uint256)" --rpc-url https://rpc.mainnet.chain.robinhood.com
```
Credits count as soon as the transaction lands. Or buy on the site: https://earningsdigest.xyz/#credits
### Errors
This is plain HTTP 402 with buy instructions, not x402.
```
401 { "error": "bad_key", "message": "…" } the key is wrong or revoked
402 { "error": "credits", "need": 2, "available": 0, "wallet": "0x…",
"buy": { "chainId", "contract", "token", "ticker", "pricePerCredit", "call", "approve", "page" } }
402 { "error": "free_limit", "message": "…", "resetsAt": "…", "launched": true, "need": 2, "buy": { … } }
no key, and today's free calls are used up
429 { "error": "free_limit", "message": "…", "resetsAt": "…", "launched": false }
before launch, free calls used up
```
`pricePerCredit` is in whole tokens as a string. `call` is `buyFor(address account, uint256 credits, uint256 maxBurn)`.
### Refunds
If a paid call fails after it was charged (a model error or a timeout, say), its credits go straight back to your balance. Every refund, paid or free, is listed in a public log: GET https://earningsdigest.xyz/api/credits/refunds?limit=50 (max 500), newest first, as { t, id, route, cost, wallet, reason, detail, mode }.
### Credits contract rules
- `buy` and `buyFor` burn credits × price and add the credits to `bought(wallet)`. At most 1,000,000 credits per purchase. Zero credits or a zero wallet revert, and so does a cost above `maxBurn`.
- The owner can pause buying and set the price per credit between 0.001 and 10,000,000 tokens. Nothing else: no withdrawals, no minting credits, no editing balances. The contract never holds tokens.
- Reads: `price()`, `bought(address)`, `totalCredits()`, `totalBurned()`, `paused()`.
- Credits:
- $EARN token:
## ERC-8004 agent identity
Earnings Agent is an ERC-8004 agent. It registers in the ERC-8004 Identity Registry on Robinhood Chain (eip155:4663:0x8004A169FB4a3325136EB29fA0ceB6D2e539a432) at launch.
- Registration file: https://earningsdigest.xyz/.well-known/agent-registration.json
- Identity Registry: https://robinhoodchain.blockscout.com/address/0x8004A169FB4a3325136EB29fA0ceB6D2e539a432
- Reputation Registry (feedback from other agents): https://robinhoodchain.blockscout.com/address/0x8004BAa17C55a88189AE136b182e5fdA19dE9b63
- Sitemap: https://earningsdigest.xyz/sitemap.xml