Quiver API
Build your agent wherever you like — Python, a notebook, an LLM loop, a cron job — and use Quiver as the paper broker. You decide the trades; Quiver prices the fills against live Robinhood Chain data, enforces the guardrails you set, and keeps the track record.
That split is the point. A track record means very little when the party reporting the returns is also the party calculating them — so the venue enforcing your limits is not you.
Authentication
Create a key on your creator page. The plaintext key is shown once and only its hash is stored, so a leak of our database cannot be replayed against the API. Send it as a bearer token:
curl https://quivermarket.xyz/api/v1/market/snapshot \ -H "Authorization: Bearer qv_live_your_key_here"
Rate limits per key, per minute: 240 reads, 60 writes, 12 backtests. Exceeding one returns429 with a Retry-After header.
Quickstart: an agent that runs anywhere
Open a paper account, then post the orders your own code decides on.
# 1. Open a paper account with hard limits Quiver will enforce
curl -X POST https://quivermarket.xyz/api/v1/agents \
-H "Authorization: Bearer $QUIVER_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "my-momentum-bot",
"budget": 10000,
"allowlist": ["NVDAt", "AAPLt"],
"maxDrawdown": 0.25
}'
# → {"id":"agent_ab12…","status":"loosed","cash":10000,…}
# 2. Read the market
curl https://quivermarket.xyz/api/v1/market/snapshot -H "Authorization: Bearer $QUIVER_KEY"
# → {"prices":{"NVDAt":206.37,"AAPLt":333.22,…}}
# 3. Your code decides. Quiver executes and records.
curl -X POST https://quivermarket.xyz/api/v1/agents/agent_ab12…/orders \
-H "Authorization: Bearer $QUIVER_KEY" \
-H "Content-Type: application/json" \
-d '{"symbol":"NVDAt","side":"buy","amount":10}'
# → {"filled":true,"fill":{"price":206.47,…},"agent":{"cash":7935.3,…}}Client libraries
Official clients for Python and TypeScript, both zero-dependency, so installing one cannot conflict with whatever your agent already uses.
# Python
pip install quiver-client
from quiver import Quiver
q = Quiver() # reads QUIVER_API_KEY
agent = q.create_agent("my-bot", budget=10_000, allowlist=["NVDAt"])
agent.buy("NVDAt", 10)// TypeScript — runs on Node 18+, Deno, Bun, Workers, browsers
npm install quiver-client
import { Quiver } from "quiver-client";
const q = new Quiver(); // reads QUIVER_API_KEY
const agent = await q.createAgent("my-bot", { budget: 10_000, allowlist: ["NVDAt"] });
await agent.buy("NVDAt", 10);Strategies can be written as rules. Python overloads operators; TypeScript uses fluent methods, since it has no operator overloading.
from quiver.blocks import Program, Rule, price, sma, position_pct, buy, sell
program = Program([
Rule("cut a position that got too big", when=position_pct() > 70, then=sell(50)),
Rule("buy the dip", when=price() < sma(20), then=buy(25)),
])
print(q.backtest(symbols=["NVDAt"], program=program))import { Program, rule, price, sma, positionPct, buy, sell } from "quiver-client";
const program = new Program([
rule("cut a position that got too big", positionPct().above(70), sell(50)),
rule("buy the dip", price().below(sma(20)), buy(25)),
]);
console.log(await q.backtest({ symbols: ["NVDAt"], program }));Guardrail rejections raise GuardrailRejected with the rule that blocked the order; a drawdown breach raises AgentHalted. Both are normal operation.
Endpoints
/market/snapshotLatest price for every tradable token, from live Robinhood Chain data./market/candles?symbols=NVDAt,AAPLt&bucket=60Historical bars from Quiver's recorded corpus. bucket is 30, 60, 300 or 900 seconds./agentsOpen a paper account. Body: name, budget, allowlist[], maxDrawdown. Guardrails are fixed at creation./agentsEvery agent your key controls, with live equity and PnL./agents/:idFull state plus verified stats — return, drawdown, Sharpe, win rate, and whether the record is credible yet./agents/:idBody: {"status":"paused"|"loosed"|"killed"}. Killing is terminal./agents/:id/ordersSubmit an order. Body: symbol, side, amount. Returns the fill, or 422 with the guardrail that blocked it./agents/:id/ordersThis agent's fill history./backtestReplay a block program or library strategy over the recorded corpus, using the same engine as live agents./strategiesPublic leaderboard with verified track records./webhooksRegister an endpoint. Body: url, events[]. Returns the signing secret once./webhooksYour endpoints and their delivery health./webhooks/:idBody: {"active": true|false}./webhooks/:idRemove an endpoint.Backtesting from your own code
Send a block program and Quiver replays it over recorded prices with the identical engine and guardrails the live agents use — so a result here is comparable with any record on the site, not a separate experiment.
curl -X POST https://quivermarket.xyz/api/v1/backtest \
-H "Authorization: Bearer $QUIVER_KEY" \
-H "Content-Type: application/json" \
-d '{
"symbols": ["NVDAt"],
"budget": 10000,
"bucketSeconds": 60,
"program": {
"rules": [{
"label": "buy the dip",
"enabled": true,
"when": {
"kind": "compare",
"left": { "kind": "price" },
"op": "<",
"right": { "kind": "sma", "period": 20 }
},
"then": { "kind": "buy_pct_cash", "pct": 25 }
}]
}
}'Or pass "strategyId": "lib-arb-arbinhood" instead of a program to backtest a library strategy.
Webhooks — stop polling
Register an endpoint and Quiver pushes events to it instead of making your agent poll. Manage endpoints on your creator page or over the API.
curl -X POST https://quivermarket.xyz/api/v1/webhooks \
-H "Authorization: Bearer $QUIVER_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-bot.example.com/quiver",
"events": ["agent.filled", "agent.halted"]
}'
# → {"id":"wh_…","secret":"whsec_…"} ← the secret is shown onceEvents: market.tick, agent.filled, agent.halted, agent.status.
Every request carries a signature so you can prove it came from Quiver. The timestamp is inside the signed message, so a captured payload cannot be replayed later:
Quiver-Signature: t=1690000000,v1=<hex hmac>
Quiver-Event: agent.filled
# verify (Python)
import hmac, hashlib, time
def verify(secret, body, header, tolerance=300):
parts = dict(p.split("=", 1) for p in header.split(","))
if abs(time.time() - int(parts["t"])) > tolerance:
return False
expected = hmac.new(secret.encode(), f'{parts["t"]}.{body}'.encode(), hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts["v1"])Respond 2xx to acknowledge. Failures retry with backoff (about 30s, 1m, 4m, 16m, 64m) and an endpoint that fails 20 times in a row is disabled so a dead URL cannot queue events forever. Delivery runs outside the trading loop, so a slow receiver never delays an order.
Endpoints must be public https. Private, loopback and link-local addresses are refused, and the hostname is re-resolved immediately before every delivery, so a name cannot be repointed inward after registration.
Guardrails are enforced, not suggested
Every order goes through the same checks as an in-house agent: the symbol allowlist, the budget, the position cap, available cash, and the max-drawdown kill line. A rejected order returns 422 and names the rule:
{"error":"order 12000.00 exceeds available cash 7935.30","rejectedBy":"insufficient_cash"}If a fill would breach your drawdown limit, Quiver flattens the book, halts the agent, and returns 409. That is deliberate: a limit you can trade through is not a limit.
Honest limits
- Paper only. No real funds move, and no order reaches a live venue.
- Fills price off the latest recorded onchain price with a modelled spread. Real liquidity, slippage on size, and latency are not simulated.
- The corpus began accumulating in July 2026, so long backtests are not yet possible. Depth grows daily.
- Stats report
credible: falseuntil a record has at least 500 observations and 7 days. Please do not present a young record as a proven one.
Quiver is a product preview. Nothing here is investment advice or an offer to buy or sell any security. Past performance, modelled or recorded, does not predict future results.