API Changelog
Every change to the public API that a caller could notice, newest first. Each entry is dated the day it was announced here; a change that breaks existing callers is announced no later than the release that makes it, and is marked Breaking. 2 of the 8 entries below are breaking.
- Added
A target-solve call: search a calculator input for a target output
POST /api/py/v1/calculate/solve
POST /api/py/v1/calculate/solvefinds the value of one calculator input that brings one of its outputs to a target — the trace width that gives 50 Ω, the gap that gives a target differential impedance — and returns it with the forward result at exactly that value:values,warnings,errorsandprovenance, exactly what/calculatereturns for those inputs. One metered call replaces the several forward calls a client would otherwise spend searching by hand.The search runs over the input's stated bounds, narrowed by an optional
range; an input with no stated bound needs one. An optionalgridrounds the returned value to its nearest multiple inside the range, alongside the unrounded solution. A target the search cannot reach still succeeds, withreached: falseand the nearest value found, and is still charged. Refusals — an unknown calculator, an input or output name the calculator does not declare, a non-finite value, a missing range on an unbounded input — cost nothing, as for every other calculator route.The Python SDK's
Client.solve()(rftools-io0.4.0) and the MCP server'ssolve_calculationtool (2.2.0) expose the same call. - ChangedBreaking
A spent allowance on the calculator routes is 402, not 401
POST /api/py/v1/calculatePOST /api/py/v1/calculate/batch
The calculator routes answered 401 "Invalid or quota-exceeded API key" both for a key that was not valid and for an allowance that was spent, so a script could not tell a bad key from a busy month. A spent allowance is now 402, as the jobs route already answered, with
errorKind: "rate_limited",reason,limit,used,resetAt,upgradeUrl,overageUrland aRetry-Afterheader.401 now means only that the key is missing, malformed, unknown or revoked, and never mentions an allowance. A request with no key at all is 401 with a
keyUrlwhere a free key can be had.A call is checked before it is counted: an unknown calculator (404) or an input that is not a finite number (400) costs nothing, and a calculation that fails is given back. In a batch, only the items that pass those checks are counted, and items whose calculation fails are given back. Branch on the status and
errorKind, never on message text. - ChangedBreaking
The monthly allowance belongs to the account, shared by all its keys
POST /api/py/v1/calculatePOST /api/py/v1/calculate/batchPOST /api/py/v1/jobs
Each key used to carry its own monthly allowance, so an account holding several keys could spend its allowance once per key. The allowance is now the account's: the calls of every key it holds, revoked ones included, count against one monthly figure, which resets at the start of each calendar month in UTC.
An account with one key sees no difference. Calls are still attributed to the key that made them, and the dashboard and
GET /api/py/v1/usagelist them per key. - Added
A usage endpoint, and usage headers on every metered response
GET /api/py/v1/usageX-Usage-AllowanceX-Usage-UsedX-Usage-ResetX-Usage-Overage
GET /api/py/v1/usageanswers a key (X-API-KeyorAuthorization: Bearer rfc_…) or a signed-in session with the tier, the allowance, what is used and what is left, when it resets, the overage state and the calls attributed to each of the account's keys. Reading it is never counted.Every metered response carries
X-Usage-Allowance,X-Usage-Used(including that call) andX-Usage-Reset, andX-Usage-Overagewhen overage was counted. Browsers can read them. - Added
Every result says how it was computed
POST /api/py/v1/calculatePOST /api/py/v1/calculate/batchGET /api/py/v1/jobs/{jobId}
Calculator results and each batch item carry a
provenanceobject with nine members:method,version,formulaRef,assumptions,validRange,computedAt,inputs,seedandelapsedSeconds. Job results already carriedprovenance; it gainsformulaRef,assumptions,validRangeandcomputedAtrather than a second object, and itsversionis writtenworker@<revision>rather than the bare revision.An input outside the range a calculator is stated for is still computed:
validRangenames it and a warning is added. A result stored before this change keeps the members it had; treat an absent one as unknown. - Added
Programs can get a key through the device authorization grant
POST /api/py/v1/oauth/device/codePOST /api/py/v1/oauth/device/token/device/
A program with no browser in it can get a key without its user pasting one (OAuth 2.0 device authorization grant, RFC 8628). It asks
/oauth/device/codefor a code, shows its user the code and the address https://rftools.io/device/, and polls/oauth/device/tokenat the interval it was given. Once the user signs in and approves the code, the next poll returns an ordinary API key, once.The key is metered, listed and revocable like any other, and counts toward the account's key limit.
- Added
Key links for clients, and labels on keys
POST /api/py/v1/apikeys/dashboard/?newKey=1
POST /api/py/v1/apikeysaccepts an optional{"label", "client"}body and returns both; leaving the body out works as before. A client can send its user tohttps://rftools.io/dashboard/?newKey=1&client=<clientId>to create a key labelled for it, shown once. - Changed
Refusals name the limit and the way past it
429 responses402 from POST /api/py/v1/jobsAuthorization: Bearer rfc_…
The per-minute 429 carries
errorKind: "rate_limited",limit,windowSecondsandupgradeUrlbesidedetail, as every other refusal carries itserrorKind. The jobs route's 402 for a spent allowance gainsreason,limit,used,upgradeUrlandoverageUrl, and the 402 for a spentfdtd_sparamsolve-mode allowance carriesreason: "solve_mode". Existing fields are unchanged.The calculator routes accept a key as
Authorization: Bearer rfc_…as well asX-API-Key, as the jobs routes already did.
The full reference is the API documentation; the machine-readable description is the OpenAPI playground.