Get a key

Organization page → API keys. The key is shown once. It is scoped to the
organization and needs a plan with API access. Pick its scopes when you mint
it: read (every GET), research:write (append blocks, create citations),
exports (queue and download data exports). Optionally pin it to an IP allow
list, one address or CIDR range per line; a request from anywhere else is a
403 that says so.

To replace a key that may have leaked, Rotate it on the same page: the
new key keeps the label, scopes and allow list, is shown once, and the old
one stops working at that moment. Revoke a key you no longer use.

curl -H "Authorization: Bearer dfk_your_key" \
     "https://dfrds.com/api/v1/pairs?base=WETH&quote=USDC"

Two conventions

  1. Orientation. Series are token0-per-token1; the response names the
    orientation. Invert client-side if you need the other direction.
  2. Nulls are not zeros. A missing figure means we could not derive it:
    an unreplicated oracle, an unpriced asset. It never means the value was zero.
    Treat null as unknown or your aggregates will be quietly wrong.

Limits and pages

Rate limits are per key per clock hour, from your plan, and every response
carries X-RateLimit-Limit and X-RateLimit-Remaining. A 429 tells you when
the window resets. The counter survives deploys and cache flushes.

Lists page by cursor: pass back next_cursor (or follow the
Link: <…>; rel="next" header) until it is null. No list returns more than 500
rows; anything larger is an export through /datasets.

The research workspace, from a script

Everything the research page does in the browser, a key can do:

Call What it does Scope
GET /research/projects the projects the key can reach, where a new page goes read
POST /research/pages a page in a project (project_id, name, tags) research:write
PATCH / DELETE /research/pages/{id} rename, move the status, retag; trash (restorable in the interface) research:write
GET / PUT /research/pages/{id}/document the whole body as one document, loaded and saved the way the editor does read / research:write
GET / POST /research/pages/{id}/comments the review threads; open one on a block read / research:write
POST /research/pages/{id}/export the bundle the Export button makes, as the download exports
POST /research/pages/{id}/blocks append one block research:write

Two rules carry over from the interface unchanged. Reach: a key belongs
to an organization and reaches the spaces its members belong to, no wider;
a page outside that is a 404. Powers: every write is decided by the same
policy the interface uses, on behalf of the member who minted the key. A
commenter’s key comments and nothing else; an editor’s key writes; only an
owner’s key trashes. A key minted by someone who has since left the
organization has no one to act as and is refused.

The document save is optimistic. GET …/document gives you
document_version; send it back with PUT. If anyone saved in between the
answer is 409 with the current document, and nothing of yours was written:
reload, reapply, save again. A block the model refuses (a chart with no
pool) is a 422 naming its index, and again nothing is saved. Put a
{{derived:N}} token from POST /citations in a text block and the figure
travels with the page: the document comes back with the value behind it,
and the export bundle carries the rows it was computed from.

Versions

The version is in the path. Fields are added without a version change and
never removed inside one. A retired endpoint answers for at least six months
with Deprecation and Sunset headers first.

The rest

  • REST reference: every endpoint, its scope and parameters, with examples; the
    OpenAPI document behind it.
  • Webhooks: push instead of polling, with signature verification.
  • MCP: the same data inside Claude or any MCP client.