The whole product, over HTTP.
Read this without an account: it is the contract an engineer evaluates before anyone pays. The base URL is https://apexdatum.io, every response is JSON unless you asked for CSV, and the envelope below is stable - fields are added, never renamed or removed within v1.
Three requests tell you most of it
The first works right now, from this page, with no account.
# reproducibility metadata - free, no auth, at every tier curl https://apexdatum.io/api/v1/manifest # the catalog, annotated with what your key can read curl -H "Authorization: Bearer ep_live_..." https://apexdatum.io/api/v1/packages # one package, every dataset in it, gated by your plan curl -H "Authorization: Bearer ep_live_..." https://apexdatum.io/api/v1/packages/td-equity
An opaque key that names you, and asserts nothing
Created and revoked on your account page. API access is part of the Feed plan.
- Keys look like ep_live_... and encode no tier, no plan and no expiry. Your entitlement is computed fresh on every request, so changing plans changes what a key reads without rotating it - and a leaked key cannot be edited into a better one, because it asserts nothing.
- We store a keyed hash, not the key. It is shown exactly once at creation; revocation is immediate; up to five keys can be active per account.
- Send it in the Authorization: Bearer header, never in a URL. Query strings land in server logs, browser history and referrers; headers do not.
- Server-side only. The API sends no CORS headers, deliberately: a browser page cannot call it cross-origin, because an API key shipped in client-side JavaScript is a published key. Call it from your backend, notebook or pipeline.
5,000 requests per key, per UTC day
Counted atomically, reported on every response.
X-RateLimit-Limit 5,000 your effective daily allowance X-RateLimit-Remaining what is left today X-RateLimit-Used requests counted, including any rejected over the limit X-RateLimit-Reset unix seconds - the next UTC midnight X-RateLimit-Degraded present only if the counter was unreadable and we let you through 429 carries Retry-After. Rejected requests still count, deliberately - otherwise a client can hammer a closed door for free. If the quota counter itself is unavailable the request is allowed rather than refused, and says so: you are already authorised at that point, and breaking a paid product to defend a fair-use ceiling is the wrong trade.
The v1 surface, complete
| Method | Path | Auth | Returns |
|---|---|---|---|
| GET | /api/v1/manifest | none | Dataset version, source SHA-256 digests, stated limits - the reproducibility claim, free so it stays checkable |
| GET | /api/v1/packages | key | The catalog, annotated with what your key can read |
| GET | /api/v1/packages/{packageId} | key | One package: every dataset it contains, gated per dataset by your plan |
| GET | /api/v1/export?format=csv|json[&package=id,id] | key + Feed | Everything you are entitled to, one file per (package, dataset) |
| GET | /api/v1/export/{packageId}?format=csv|json | key + Feed | One package as files |
| GET | /api/v1/releases | none for metadata | Every archived release version |
| GET | /api/v1/releases/{version}[?dataset=name] | key + Feed for bodies | A pinned release: metadata free, byte-identical dataset bodies on the Feed plan |
| GET | /api/v1/as-of/{source}?at=<ISO8601>[&body=1] | key + Feed for bodies | Point-in-time: what a source said at any past instant. Metadata free; bodies on the Feed plan |
Export formats: csv, json. XLSX and Parquet are not served - a format we cannot round-trip byte-identically through the release archive would break the reproducibility promise the archive exists to make, so we publish the two we can prove rather than the four that would demo well.
One envelope, one error shape
Package responses carry the data, your access to it, and the exact build it came from.
{
"package": { "id", "number", "name", "parameters", "limits" },
"access": { "tierId", "tierNumber", "level", "redactedFields", "upgrade" },
"dataset": { "version", "season", "week", "generatedAt" },
"meta": { "<dataset>": { "totalRows", "returnedRows", "truncated" } },
"datasets": { "<dataset>": [ ... ] },
"disclaimer": "..."
}| Status | Code | Meaning |
|---|---|---|
| 400 | missing_at / bad_at | as-of requires an explicit ?at - an endpoint that silently answers “latest” is not point-in-time |
| 401 | unauthorized | One message for malformed, unknown and revoked keys, deliberately - the response does not reveal which |
| 402 | api_not_in_plan | Your plan has no API. Scout and Active are read-on-site by design |
| 403 | access_denied | That package is not in your plan; the body names the tier that opens it |
| 404 | unknown_package | Answered before authentication, and the body lists valid ids - the package list is public, so hiding it protects nothing |
| 429 | rate_limited | Daily quota exhausted; carries Retry-After. Rejected requests still count - a closed door is not free to hammer |
| 500 | server_error | Ours, not yours |
What each plan's key can read
Generated from the same grant object the API enforces - this table cannot promise what the engine does not gate.
| # | Package | Endpoint id | Scout | Active | Feed |
|---|---|---|---|---|---|
| 01 | Positional Stat Lines | stat-lines | ◐ | ● | ● |
| 02 | Injury & Availability Intel | injury-intel | ◐ | ● | ● |
| 03 | Market Divergence Board | market-divergence | ◐ | ● | ● |
| 04 | Licensed Data Access | licensed-data | ○ | ○ | ● |
| 05 | Touchdown Equity | td-equity | ◐ | ● | ● |
| 06 | Draft Value Engine | draft-value | ◐ | ● | ● |
| 07 | Draft Availability Simulator | availability-sim | ◐ | ● | ● |
| 08 | Game Forecast | game-forecast | ● | ● | ● |
| 09 | Verification Ledger | verification | ● | ● | ● |
| 10 | Leg Correlation Matrix | leg-correlations | ◐ | ● | ● |
| 11 | Outcome Board | outcome-board | ◐ | ● | ● |
| 12 | NHL Game Forecast | nhl-game-forecast | ◐ | ● | ● |
| 13 | The Weekly Board | weekly-board | ○ | ● | ● |
Version-pinned, hash-verified, point-in-time
Every release is archived under an immutable version with the SHA-256 of each upstream source. Ask /api/v1/releases/{version} for a version you used last month and the bytes come back identical - that is the claim CI re-proves on every push, not a marketing line. /api/v1/as-of answers what a source said at any past instant, which is what makes a backtest honest: the data your model saw, not the data as it was later corrected.
- We publish probabilities and ranges, never picks, stakes or EV figures - at any tier, over any endpoint. If your integration needs a wagering recommendation, this API will not produce one.
- No webhooks and no change notifications yet. Poll the manifest; its generatedAt changes when the data does.