# Hummingbird API v1 contract Contract date: 2026-09-22. Base URL: `https://humarb.com`. Read this reference online at [https://humarb.com/skills/hummingbird-api/references/api.md](https://humarb.com/skills/hummingbird-api/references/api.md), alongside the [Hummingbird skill](https://humarb.com/skills/hummingbird-api/SKILL.md). These public documents require no download, installation, source checkout or API key. An AI can perform user-authorized read-only analysis directly when its trusted execution environment can send HTTPS requests and already has securely configured `HUMMINGBIRD_API_KEY`. Reading the public documents alone does not establish authenticated API access or current data. If private-header HTTP requests or secure secret access are unavailable, generate a server-side adapter with placeholder configuration and mocked tests; do not claim a live connection. Never ask for the key in chat or put it in a browser URL. ## Authentication and limits Use `Authorization: Bearer ` from a server or trusted agent execution environment; read the secret inside that environment without printing it or exposing it to model prompts. Send authenticated requests only to the configured Hummingbird HTTPS origin and reject redirects. The API accepts bearer keys, not website login cookies or URL query credentials. Keys are created/revoked in `/account`; key creation requires verified email and active paid Pro access, and each API request requires an active paid entitlement and the endpoint's read scope. Free website access does not grant API access. Two active keys maximum. All account keys share **10,000 requests per UTC day and 60 per minute**. `GET opportunities` and `POST execution-plans` additionally share **10 requests per minute**, counting one bounded scan/batch as one request even when it returns no positive rows. Successful authentication adds `X-RateLimit-Remaining-Day`. Execution responses also report `X-Execution-RateLimit-Remaining-Minute` when that quota is reserved. Responses are `Cache-Control: no-store`; do not cache one customer's fee inputs for another. HTTP 429 returns `Retry-After: 60`; error codes include `API_QUOTA_EXCEEDED` and `EXECUTION_QUOTA_EXCEEDED`. Defer with bounded retries and jitter. A repeatedly exhausted daily quota needs a later UTC day, not a tight retry loop. ## Endpoints | Method and path | Scope | Data / semantics | |---|---|---| | `GET /api/v1/markets?venue=binance&asset=BTC&limit=100` | `markets:read` | Stored public contract metadata; `markets`, `asOf`, `cache`, `pagination` | | `GET /api/v1/routes?asset=BTC&size=10000&hours=24&limit=100` | `funding:read` | Stored funding candidates; `routes`, `assumptions`, `asOf`, `sourceValidUntil`, `cache`, `pagination`, `policy` | | `GET /api/v1/opportunities?size=1000&hours=8&limit=5` | `funding:read` | Scan up to five candidates with current public books; return recorded Hummingbird Engine selections with fresh positive net | | `POST /api/v1/execution-plans` | `funding:read` | Current public-book calculation for 1–5 known route IDs; details below | | `GET /api/v1/routes/{routeId}/analysis` | `funding:read` | Latest completed saved report matching the requested assumptions; original `route` and `assessment`, freshness labels | | `GET /api/v1/assessments/{assessmentId}` | `funding:read` | Immutable historical evidence while retained; no guaranteed availability period | | `GET /api/v1/history?routeId=...&days=7` | `funding:read` | Durable public settlement history, 7 or 30 days; `value` plus cache/collection metadata | | `GET /api/v1/events` | `funding:read` | Bounded funding-cycle SSE notifications; cursor via `Last-Event-ID` or `after` | No endpoint submits orders, transfers assets, changes exchange accounts or invokes Hummingbird Engine. Markets/raw routes/analysis read retained data (`X-API-Data-Source: stored-only`). Repeating them neither refreshes funding nor creates new analysis. Opportunities and execution plans may fetch public books (`stored-funding-current-public-books`), and history may advance bounded public-source collection; these do not operate customer accounts or refresh funding. Markets/raw routes accept `offset` and `limit` (1–100, default 100). Opportunities instead use candidate-scan pagination: `limit` defaults to and caps at 5. Follow `scan.nextOffset` until null, even when `routes` is empty; the offset indexes eligible candidates, not positive outputs. Routes and opportunities accept exact `asset`, substring `search`, comma-separated `venues`, `type`, `category`; `view=matrix` cannot produce an unpaginated dump. `routeId` is 32 lowercase hexadecimal characters. Do not construct it from a ticker: consume it from the API. A symbol alone does not prove two contracts match. ## Positive opportunity discovery `GET opportunities` retains the route envelope (`version`, `routes`, `assumptions`, `asOf`, `cache`, `pagination`, `policy`) and adds `mode:"current_positive_execution_references"`, `status:"ready"|"empty"`, `requestedAt`, `completedAt`, `refreshAfterMs:10000`, `modelInvoked:false`, `scan` and `engineSelections`. Each returned route has a recorded Hummingbird Engine choice and a freshly positive `executionPlan`; use its `netUsd`, never the old `route.scenarioNetUsd`. `selectedRoute` is the first surviving result or null. `scan` contains `totalCandidates`, `offset`, `scanned`, `positive` (current arithmetic), `engineSelected` (also Hummingbird Engine-selected), `nonPositive`, `unavailable`, `remaining`, `nextOffset`, `excludedBeforeBooks`, `reasonCounts`, `coverage:"bounded_scan"` and `preselection:"constant_funding_less_entered_costs_excluding_basis"`. Multiple pairs per asset remain in the retained universe. Stale or known-incomparable contracts are excluded before book work. Hummingbird Engine-selected pairs have queue priority; remaining candidates use funding less entered costs. At most five plans are checked with four VWAPs/four fees. Final Hummingbird Engine-selected positive results sort by current plan net. Negative/unknown/stale plans and unconfirmed maker fills do not enter the list; diagnostics remain in raw routes plus POST calculations. The background engine independently judges up to three same-asset current plans. Every route with its own source-bound `current_size_supported` judgment and complete positive current net may enter the candidate list; an unrelated peer rejection or inconclusive comparative preference does not erase it. The featured engine separately compares qualifying candidates and may abstain. Newer route-specific rejection withdraws that route for the same amount/fee plan; an unexamined peer is not a withdrawal. `engineSelections` carries `assessmentId`, `primaryRouteId`, selected `routeId`, original `snapshotId`, `contextHash`, `sourceDataAt`, `completedAt`, `validUntil`, `provider`, `model`, `choice`, `confidence`, `reasonCodes`, `originalNetUsd`, `originalPlan` and `basis:"recorded_jev_candidate_judgment"`. Comparative findings use the legacy `basis:"recorded_jev_choice"`. Match by route ID and publication domain. Candidate `choice` is `current_size_supported`; any comparative `routePreference` remains a separate original answer. Confidence is not a probability of profit. Do not attach primary history scores to a peer. Changed fee/size inputs require matching new evidence and judgment; an existing approval cannot be transferred. No visitor request invokes Hummingbird Engine, and arithmetic-only positives must not be relabelled as engine findings. Each metadata item also has `currentCostsRevalidated:boolean`, default false. It is true only if that route survives fresh positive cost checks **in this response's `routes`**. Metadata can include unsampled pairs in the filtered universe, or choices whose current net became negative/unknown. Thus `engineSelections` alone is not a current opportunity; never rank or display its `originalNetUsd` as current profit. Use `routes` as the dual-gate result and retain original timestamps. This flag does not claim confirmed account fees or executable orders. An empty HTTP 200 response is meaningful: keep scan counts/reasons, continue to `nextOffset` within allowance, and do not invent an opportunity or reinsert old positive data. New funding snapshots can reorder the queue; restart when `asOf` changes and deduplicate route IDs. The scan cannot claim all-market coverage. Positive fee assumptions may yield `partial` plans; preserve their limitations. Structured `options.feeProfiles` and custom capital/cost fields are POST-only: after discovery, recalculate under actual account inputs and reject any newly negative/unknown outcome. Both endpoints remain read-only scenarios, not guaranteed profit or execution recommendations. ## Scenario inputs All amount/rate values are decimal **strings**, not floating-point JSON numbers. An example amount is a per-leg budget, not total deployed capital. Always obtain actual customer inputs; the values below illustrate structure, not recommended fees or position sizes. GET routes, opportunities, history and route analysis accept: | Query | Meaning | |---|---| | `size=10000` | Budget per leg in USD | | `hours=24` | Holding window | | `longEntryFeeBps`, `longExitFeeBps`, `shortEntryFeeBps`, `shortExitFeeBps` | Four independent fee assumptions in basis points; 1 bp = 0.01% | | `feeBps` | Fallback for each omitted fee field, default `5`; these defaults are assumptions, never verified account fees | | `basisPnlUsd`, `otherCostsUsd` | Entered basis P&L and aggregate other costs for the legacy fixed-notional scenario, each default `0` | | `sameSettlement=true` | Disable the default USD stablecoin-parity assumption | POST uses the equivalent `assumptions` object names: `notionalUsd`, `holdingHours`, the four fee keys, `basisPnlUsd`, `otherCostsUsd`, `stablecoinParityAssumed` (boolean). Amount and duration must be positive. Fees/other costs may be explicit null when unknown. Pass the API's normalized `assumptions` back for continuity, or construct these fields from customer inputs. Do not treat omitted/default zero other costs as evidence that costs are zero. Opportunity/current-plan calculations replace entered basis P&L with current round-trip book P&L; entered basis profit cannot inflate the scanner's queue. ## POST execution-plans Send `Content-Type: application/json`. Body limit: 32,768 bytes. Only `routeIds`, `assumptions` and `options` are accepted at the top level. No upstream URL, arbitrary route object or exchange key is accepted. ```json { "routeIds": ["<32-hex-routeId-from-routes>"], "assumptions": { "notionalUsd": "1000", "holdingHours": "24", "longEntryFeeBps": "5", "longExitFeeBps": "5", "shortEntryFeeBps": "5", "shortExitFeeBps": "5", "basisPnlUsd": "0", "otherCostsUsd": null, "stablecoinParityAssumed": true }, "options": { "costs": {"transferUsd": null, "otherUsd": null, "capitalCostUsd": null}, "capitalUsd": "2000", "adverseBps": ["10", "25", "50"] } } ``` This intentionally incomplete example preserves unknown costs, so a numeric net may be unavailable. Replace the route placeholder and cost fields with validated customer values; never replace unknowns with zero to make the calculation pass. `options` may contain: - `feeProfiles`: at most 24 unique venue profiles; schema below. - `costs`: all three keys `transferUsd`, `otherUsd`, `capitalCostUsd`, each a nonnegative decimal string or null. If supplied, any null keeps complete net unavailable. If omitted, the calculator uses `assumptions.otherCostsUsd` as an entered aggregate, with an explicit reason code. - `capitalUsd`: positive decimal string or null. If omitted, capital is twice the per-leg budget, with `capitalBasis: two_side_budget`; explicit capital uses `user_entered`. This is not a leverage or liquidation calculation. - `adverseBps`: at most 8 distinct nonnegative decimal strings, maximum `10000` each. These are adverse exit-spread scenarios. Fee profile shape: ```ts type FeeRule = { liquidity: "maker" | "taker" | "unspecified"; feeBps: string | null; confirmed: boolean; // customer's declaration, not exchange authentication rebate?: { bps: string | null; eligible: boolean | null; confirmed: boolean; validFrom: string | null; // ISO timestamps validUntil: string | null; }; }; type FeeProfile = { venue: string; // exact API venue identifier source: "account_entered" | "unconfirmed"; tier?: string; updatedAt?: string | null; validUntil?: string | null; entry: FeeRule; exit: FeeRule; }; ``` Unknown or expired fees and unverified rebate eligibility can prevent net calculation. Negative fees require a supported, explicitly confirmed maker-fee input and its validity period; maker fills remain unconfirmed even then. Do not subtract the same rebate both in negative fees and as a second rebate. Response (field structure; descriptive values are placeholders): ```ts { version: 1, calculationVersion: "funding-execution-batch-v1", dataKind: "execution_plans", readOnly: true, executable: false, requestedAt: string, completedAt: string, capacityEvaluatedAt: string, // same completedAt clock for all results refreshAfterMs: 10000, modelInvoked: false, results: Array<{ routeId: string, status: "ready" | "partial" | "unavailable", route: FundingRoute | null, plan: FundingExecutionPlan | null, reasonCodes: string[], liquidityCapacity: FundingLiquidityCapacity, capacityEvaluatedAt: string }> } ``` HTTP 200 can contain partial failures or even all unavailable rows. Source header: `stored-funding-current-public-books`. The service reads retained funding and shared, instrument-bound public books; it can refresh books but cannot refresh funding. Expired funding remains unavailable until the background collector supplies a fresh snapshot. Poll a **small shortlist** no faster than `refreshAfterMs`, while respecting the shared request limits and checking expiry on every use; do not refresh the whole universe every few seconds. Ten-second pacing for one batch already consumes 6 of the 10 execution requests/minute. Important `plan` fields: | Fields | Correct interpretation | |---|---| | `routeId`, `snapshotId`, `contextHash`, `sourceDataAt` | Must match the accompanying fresh `route`; never bind a new plan to an old report by symbol | | `generatedAt`, `sourceTimes.long/short`, `validUntil` | Original calculation and book clocks; check expiry on use, not just receipt | | `status`, `calculationReady`, `reasonCodes`, `limitations`, `executionVerified: false` | Numeric availability and unresolved conditions; no execution approval | | `position.quantity`, `quantityStep`, `longEntryUsd`, `shortEntryUsd`, `fullBudgetCovered` | Matched base quantity; a usable smaller size may not fill the requested full budget | | `position.capitalUsd`, `capitalBasis`; `netReturnPct` | Holding-window net divided by total stated capital; not annualized account-margin return | | `prices.longEntryVwap`, `shortEntryVwap`, `longExitVwap`, `shortExitVwap`, `pricePnlUsd` | Entry and hypothetical exit at observed books; not future fill guarantees | | `prices.entrySlippageUsd`, `exitSlippageUsd`, `slippageIncludedInVwap: true` | Slippage already reflected in VWAP P&L; do not deduct again | | `fees.long/short.entry/exit`, `fees.confirmed` | Four effective fees, liquidity role, provenance and customer-declared confirmation | | `costs.entryFeesUsd`, `exitFeesUsd`, `totalOtherUsd`, `netUsd` | Total scenario arithmetic; null is unknown, negative net is a loss scenario | | `funding.legs`, `funding.estimatedUsd`, `funding.events` | Per-leg interval, next time, valuation basis and projected count at unchanged observed rates; events are hypothetical, not historical cash receipts | | `funding.eventRowsTotal`, `eventRowsTruncated` | Event array can be capped at 256; totals/counts cover the calculation horizon | | `breakEven` | First nonnegative point under constant-rate assumptions; check `remainsCoveredThroughHorizon`, not only the first crossing | | `stressScenarios`, `convergenceScenario` | Deterministic adverse exit-spread / common-exit-price scenarios, including repriced closing fees; neither predicts what prices will do | | `depth` | Top of book and 1% depth inside the returned book window, not total-market liquidity | The older route `scenarioNetUsd` uses equal USD budgets and an entered basis adjustment. It is a different scenario from the equal-base-quantity execution plan. Do not use it as a substitute or add it to `plan.netUsd`. Route `carryAprPct` is gross funding APR; `scenarioNetAprPct` includes four assumed fees and entered costs but uses **one leg's notional**. Do not compare either directly with the plan's total-capital holding-window return. ## Liquidity capacity: inspectable is not size-approved Every POST `results[]` item and GET opportunities `routes[]` item includes `liquidityCapacity` and `capacityEvaluatedAt`. The latter is the same `completedAt` clock across all items and the response envelope. GET `selectedRoute` has the same projection. Capacity uses only that route's bound execution plan; missing or mismatched plans produce unknown capacity. Source times and plan net amounts are never renewed or rewritten. A returned positive route may still have `limited` or `unknown` capacity. Keep it inspectable, but do not describe its requested amount as supported. Check capacity separately from positive net, `calculationReady`, recorded Hummingbird Engine selection and `currentCostsRevalidated`. | Field | Contract | |---|---| | `status` | `within_policy`, `limited`, `unknown`, `historical`. Only `within_policy` passes the capacity policy at response time; even then, this is not order or fill approval. | | `sampleStatus` | Original snapshot's `within_policy`, `limited` or `unknown` state. Historical over-limit evidence stays visible without becoming current. | | `basis`, `policySharePct`, `scope` | API calculation projections use `matched_order`; `policySharePct` is `"10"`. This 10% threshold is a conservative product policy on returned 1% books, not an exchange limit. | | `sides[]` | Long entry/exit and short entry/exit with `depthUsd`, `orderUsd`, `depthSharePct`, `withinPolicy`. Order notional is matched base quantity × the relevant directional VWAP. These sampled values require top-level freshness checks. | | `maxDepthSharePct`, `limitingSide` | Highest known order/depth occupancy and direction; a zero-depth side has no finite ratio. If a direction is missing, the known maximum is only partial evidence. | | `budgetCeilingPerSideUsd` | Smallest of all four directional depths × 10%; null if complete depth or verified clocks are unavailable. A snapshot budget proxy, not maximum executable size or future exit capacity. | | `budgetChecks[]` | `budgetPerSideUsd` values `"1000"`, `"10000"`, `"100000"`; `withinPolicy` is null for historical/unknown source evidence, while `sampleWithinPolicy` may retain an old comparison. `requiresRecalculation:true` always applies. | | `entrySlippageBps`, `exitSlippageBps` | Aggregate sampled slippage / corresponding two-leg notionals. Already reflected in VWAP P&L; never deduct again. No independent slippage threshold is implied. | | `sampleId`, `generatedAt`, `validUntil`, `reasonCodes` | Original provenance and limitations. Expiry checks on use remain necessary; null stays unknown. | Changing the amount requires a new plan for VWAP, fees, slippage and profit. A preset inside the depth budget does not prove it will fill. Never scale an old net amount to a larger budget or label a historical/unknown sample as currently usable. GET `scan.liquidityCapacity` counts `withinPolicy`, `limited`, `unknown` and `historical` across all calculated candidates in that bounded request, independently of `scan.positive` and `scan.engineSelected`. `scan.reasonCounts` includes capacity reasons for positive calculations too, de-duplicated per result; existing pre-book exclusion reasons remain. Neither statistic claims whole-market coverage. Saved assessments and `engineSelections[].originalPlan` remain their original historical evidence. ## History, saved Hummingbird Engine analysis and events History is returned under `value`. Inspect `value.status`, `value.pending` and `value.reasonCodes`. For each of `value.long` and `value.short`, read `events`, `reasonCodes`, `backfill.status`, `backfill.checkedRanges` and `backfill.lastSuccessAt`. HTTP **202** plus `Retry-After` means collection is incomplete; retain partial records and show pending coverage. It is not an empty-history failure. Unknown/unsupported legs and missing events remain unknown, not zero. `ready` only means source pagination was traversed, `value.coverage` remains `provided_events_only`, and `value.completeBacktest` remains false. Applied public settlements assume a position; they are not customer account payments. Historical Lighter cash totals remain excluded until historical rate units/direction are verified. Analysis is saved research, with original route, assessment, source and completion times. Inspect `historicalOnly`, `isCurrentSnapshot`, `sourceExpired`, assessment dimensions and evidence coverage. A fresh execution plan does not refresh the model report. Short funding history and model confidence do not imply calibrated winning probability or full-horizon stability. Retrieval by assessment ID returns an `assessment` wrapper containing retained evidence; do not assume it is the same envelope as route analysis. Assessment storage has a 24-hour age cap and a 96 MiB logical payload budget, with the latest 30 minutes protected from capacity pruning; older records may disappear earlier under load. The legacy response field `retentionDays: 3` is retained for compatibility, not an availability guarantee or SLA. Handle a missing record as HTTP 404 rather than assuming it must remain available for three days. SSE is a notification stream with only the latest 100 events. Resume using its cursor. If disconnected beyond retention, retrieve a route snapshot again. It is not a tick archive or a guaranteed execution feed; connecting also consumes account API quota. ## Errors and retry decisions | HTTP | Examples | Consumer action | |---|---|---| | 400 | Invalid decimals, IDs, options; `EXECUTION_ROUTE_LIMIT` | Correct the input; no automatic retries | | 401 / 403 | Missing/revoked key, insufficient scope or entitlement | Stop retries and report authentication/access requirement without logging the key | | 404 | `ROUTE_NOT_FOUND`, `ASSESSMENT_NOT_FOUND` | Refresh discovery or report absence; never invent an ID or substitute a different report | | 413 / 415 | `EXECUTION_REQUEST_TOO_LARGE`, `JSON_CONTENT_TYPE_REQUIRED` | Reduce payload / use JSON content type | | 429 | `API_QUOTA_EXCEEDED`, `EXECUTION_QUOTA_EXCEEDED` | Respect `Retry-After`, share a limiter across keys and jobs | | 503 | `API_UNAVAILABLE`, `ASSESSMENT_NOT_READY`, `EXECUTION_BATCH_UNAVAILABLE` | Surface source/report unavailability; bounded retry after background publication, not rapid model-trigger attempts | Use response schema validation and a decimal library appropriate to the customer's stack. Start with mocked HTTP tests. Any live smoke test should read only a small, user-authorized batch and emit status/coverage summaries without credentials or raw private configuration.