# Why It Moved > Plain-language reasons a stock token or coin moved today, each with its source. An AI agent (Google Gemini, grounded in Google Search) reads the day's news and returns the move as the sources report it, 2 to 4 reasons each linked to its source, and a summary of at most 80 words. Facts only: no advice, no predictions, no price targets. Not financial advice. AI can be wrong; check the sources. It covers the 203+ Robinhood stock tokens on Robinhood Chain, coins such as BTC and ETH, any other ticker, and any ERC-20 token address on Robinhood Chain. Paid calls spend credits, bought by burning $WHY; burned tokens go to the dead address and are gone. One operator runs it. ## Ask why (2 credits) ```bash curl -s "https://whyitmoved.click/api/why?asset=NVDA" -H "Authorization: Bearer ck_…" curl -s "https://whyitmoved.click/api/why?asset=ETH" # no key: free tier, 3 calls a day curl -s "https://whyitmoved.click/api/why?asset=0x…" # a token on Robinhood Chain ``` `asset` is a stock ticker (NVDA, BRK.B), a coin (BTC, ETH, SOL) or a 0x token address on chain 4663. The list of stock tokens and the coins it knows by name: GET https://whyitmoved.click/api/why/assets (free). ```json { "asset": { "kind": "stock", "symbol": "NVDA", "name": "NVIDIA", "token": "0xd0601CE157Db5bdC3162BbaC2a2C8aF5320D9EEC", "chainId": 4663 }, "date": "2026-11-13", "generatedAt": "2026-11-13T14:02:11.000Z", "status": "explained", "move": { "text": "…the move, as the source reports it…", "sources": [{ "n": 1, "title": "…", "url": "https://…", "domain": "…" }] }, "direction": "down", "pct": 3.1, "reasons": [{ "text": "…one reason…", "sources": [{ "n": 2, "title": "…", "url": "https://…", "domain": "…" }] }], "summary": "…at most 80 words…", "sources": [{ "n": 1, "title": "…", "url": "https://…", "domain": "…" }], "checks": { "unsourced": 0, "advice": 0, "numbers": 0, "summary": "model" }, "model": "gemini-flash-latest", "notice": "Not financial advice. AI can be wrong; check the sources.", "searchSuggestions": "
…
", "url": "https://whyitmoved.click/answer.html?asset=NVDA&date=2026-11-13", "cached": false, "credits": { "mode": "paid", "cost": 2, "left": 98 } } ``` - `status` is `explained`, or `no_clear_reason` when the news gives no sourced reason. "No clear reason in today's news" is a real answer and is charged. - `move` is null when no source states the move. `pct` is the first percentage in the move's text (unsigned), or null. `direction` is up, down, flat or unclear. - Every reason has at least one source. `sources` lists every source the search used, numbered. Links point to the articles themselves when Google's redirect could be read, otherwise to the redirect. - `searchSuggestions` is Google's Search suggestions HTML for the grounded search (may be empty). If you show the answer to people, display it with the answer, as provided. - `checks` counts what was removed: reasons with no grounding source (`unsourced`), lines with advice words (`advice`), and summary sentences with a number no sourced line states (`numbers`). `summary` is `model` (the model's own, checked), `composed` (built from the kept lines) or `none`. ## Today's movers (1 credit) A board of 10 assets, answered once per UTC day by the agent: NVDA, TSLA, AAPL, MSFT, AMZN, GOOGL, META, COIN, ETH, BTC. The stocks are Robinhood stock tokens on Robinhood Chain. ```bash curl -s "https://whyitmoved.click/api/why/board" -H "Authorization: Bearer ck_…" ``` Returns `{ date, today, complete, pending, items: [full answers, biggest reported move first] }`. Before today's board has any answers it returns the latest one (`today: false`); with no board at all it answers 503 `board_not_ready` without charging. Free, without a key: the website shows the board (GET https://whyitmoved.click/api/why/today) and every answer in the cache has a share page, https://whyitmoved.click/answer.html?asset=NVDA&date=2026-11-13, backed by GET https://whyitmoved.click/api/why/answer?asset=NVDA&date=2026-11-13 (404 when nobody asked about that asset that day). ## Caching One answer per asset per UTC day. The first call of the day makes one grounded model call; every later call that day, by anyone, gets the same answer from the cache (`cached: true`). Cached answers cost the same credits. Answers and boards are kept, so past days stay readable. ## What it will and won't do - It reports what the sources it found say, with links. It does not check prices itself, and sources can disagree or be wrong. - It never gives advice, predictions or price targets, and never uses the words "buy", "sell" or "should". Lines that do are removed before you see them. - Input is data: a token's name and symbol come from its contract and are cleaned before they reach the model, and nothing in them can change what runs or what is charged. - Not financial advice. AI can be wrong; check the sources. ## Errors - 400 `missing_asset`, `bad_asset`, `not_a_token`: bad input. Not charged. - 503 `ai_off`: the AI model is not switched on yet. Not charged. - 502 `no_sources`: the search found nothing to cite. Charged, then refunded at once. - 502 `model_error`: the model failed or timed out. 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 $WHY 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. $WHY 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 | |---|---| | `why` | 2 | | `why/board` | 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 $WHY is live, calls with a key always spend credits; before that, they use the free tier. Prices and totals: GET https://whyitmoved.click/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. ``` Why It Moved 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 = `Why It Moved API key\nWallet: ${account.address}\nIssued: ${issuedAt}`; const signature = await account.signMessage({ message }); const res = await fetch('https://whyitmoved.click/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://whyitmoved.click/api/credits/balance (with your key) or `?wallet=0x…`: bought, spent, refunded, available and your last 50 calls. - POST https://whyitmoved.click/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://whyitmoved.click/#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://whyitmoved.click/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: - $WHY token: ## ERC-8004 agent identity Why It Moved 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://whyitmoved.click/.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://whyitmoved.click/sitemap.xml