Skip to main content

rftools

PyPI Python

Python client for rftools.io — 241 RF & electronics calculators, plus 13 async simulation job types (FDTD, antenna NEC-2, filter Monte Carlo, and more), accessible from Python and the command line.

Installation

pip install rftools-io

Quick Start

Get a free API key (5 calls/month) without copying it anywhere:

rftools login

This prints a page and a code. Open the page, sign in or sign up, and approve the code. The key is saved to ~/.config/rftools/credentials.json (%APPDATA%\rftools\ on Windows, readable by you only), and every later rftools.Client() and rftools command reads it from there. The key is never printed. From Python, the same thing is rftools.Client().login().

Or set a key you already have. The variable takes precedence over the saved file:

export RFTOOLS_API_KEY=rfc_your_key_here
import rftools

result = rftools.calculate("vswr-return-loss", {"vswr": 2.5})
print(result["returnLoss"])  # 9.54 dB
print(result.values)         # {"returnLoss": 9.54, "reflectionCoeff": 0.333, ...}

Authenticated Usage (API tier)

Get an API key at rftools.io/pricing ($19/mo, 10,000 calls/month).

import rftools

client = rftools.Client(api_key="rfc_live_xxx")
# or set environment variable: export RFTOOLS_API_KEY=rfc_live_xxx

result = client.calculate("free-space-path-loss", {"frequency": 2400, "distance": 100})
print(result["pathLoss"])  # dB

A key is sent as Authorization: Bearer <key> and as X-API-Key. The service accepts either on every route.

Usage

The monthly allowance belongs to your account. Every key on the account draws on it.

usage = client.usage()             # GET /v1/usage, which is never metered
print(usage.used, "of", usage.allowance, "used;", usage.remaining, "left")
print("resets", usage.reset_at)
for key in usage.keys:
    print(key.key_id, key.label, key.used)

result = client.calculate("vswr-return-loss", {"vswr": 2.5})
print(result.usage.remaining)      # from this response's X-Usage-* headers
print(client.last_usage)           # the latest metered response's, refusals included

batch() results carry .usage too, and so does a job submitted with a key (job.usage).

Provenance

A calculator result says how it was computed. provenance is None until the service sends it.

p = result.provenance
print(p.method, p.version)          # calculator:microstrip-impedance api@4ce2c2e0a1b2
print(p.formula_ref)                # Hammerstad & Jensen (1980); Wadell, ...
for a in p.assumptions:
    print(a.code, a.text)
print(p.valid_range.status)         # inside | outside | unknown
print(p.valid_range.outside)        # the inputs outside their stated bounds

BatchResultItem.provenance is the same per item. A member missing from an older result reads as None and is listed in p.missing. The contract is vendored as rftools/schemas/result-provenance.schema.json.

Batch Calculations

Run up to 50 calculations in a single HTTP request (API tier only):

results = client.batch([
    ("vswr-return-loss", {"vswr": 1.5}),
    ("vswr-return-loss", {"vswr": 2.0}),
    ("free-space-path-loss", {"frequency": 2400, "distance": 50}),
])
for r in results:
    if r.ok:
        print(r.values)
    else:
        print(f"Error: {r.error}")

Typed Category Stubs

IDE-friendly typed functions with parameter defaults matching the web UI:

from rftools.calculators import rf, pcb, antenna, power

# RF
result = rf.vswr_return_loss(vswr=2.5)
result = rf.free_space_path_loss(frequency=2400.0, distance=100.0)

# Antenna
result = antenna.dipole_antenna(frequency=433.0)
result = antenna.parabolic_dish_antenna(frequency=10000.0, diameter=0.6)

# PCB
result = pcb.trace_width_current(current=2.0, tempRise=10.0, thickness=1.0)

# Power
result = power.voltage_divider(vin=12.0, r1=10000.0, r2=10000.0)

All 13 categories available: rf, pcb, power, signal, antenna, general, motor, protocol, emc, thermal, sensor, unit_conversion, audio.

CLI

# Run a calculation
rftools calc vswr-return-loss --vswr 2.5

# With API key
RFTOOLS_API_KEY=rfc_xxx rftools calc free-space-path-loss --frequency 2400 --distance 100

# JSON output (pipe to jq)
rftools calc vswr-return-loss --vswr 2.5 --json | jq '.values.returnLoss'

# Submit an async simulation job, wait for it, print the result summary
rftools sim eye_diagram --param dataRate=10e9 --file trace.s2p

# Submit and return immediately (poll later with the printed job id)
rftools sim fdtd_sparam --param solveMode=express --no-wait

# List all calculators
rftools list

# Filter by category
rftools list --category rf

# Show calculator inputs/outputs
rftools info free-space-path-loss

# Get an API key by approving a code on rftools.io (saved; never printed)
rftools login
rftools login --client-id my-tool --no-browser

# Library version
rftools version

Error Handling

Errors are classified by HTTP status code (and, for a finished job, by the service's errorKind) — never by matching text in a message.

from rftools.exceptions import (
    AuthError, QuotaError, RateLimitError, ValidationError, NotFound, JobFailed,
)

try:
    result = client.calculate("vswr-return-loss", {"vswr": 2.5})
except QuotaError as e:
    print(f"{e.used} of {e.limit} calls used; resets {e.reset_at}. Plans: {e.upgrade_url}")
except RateLimitError as e:
    print(f"More than {e.limit} requests in {e.window_seconds}s. Retry after {e.retry_after}s")
except AuthError as e:
    print(f"Invalid or missing API key. Get one: {e.key_url or 'rftools login'}")
except NotFound:
    print("Unknown calculator slug")
except ValidationError as e:
    print(f"Bad inputs: {e.failures}")
Exception When
AuthError Missing, unknown or revoked API key (HTTP 401); .key_url is where to get one when the service names it. Also HTTP 403, and an upload attempted with no key, which is raised locally. A spent allowance is never an AuthError
QuotaError The account's monthly allowance is spent (HTTP 402, on every metered route). Carries .reason, .limit, .used, .reset_at, .upgrade_url, .overage_url, .retry_after
RateLimitError Too many requests too fast (HTTP 429). Carries .limit, .window_seconds, .retry_after
LoginError login() got no key; .error is access_denied or expired_token
ValidationError Bad inputs, locally or on the service (HTTP 400/422); .failures is the detail list
NotFound Unknown calculator slug or job id (HTTP 404)
JobFailed A job reached a terminal failed state; .kind is the service's errorKind
APIError Unexpected/unclassified HTTP error, or a network failure

RftoolsError is the base class for all of the above. Every exception raised for an HTTP response carries .status_code and .error_kind (the service's errorKind). Pre-0.2 names (RFToolsError, NotFoundError) still work as aliases.

Before the API's metering release, a spent allowance on POST /v1/calculate came back as HTTP 401. It is now 402 on every metered route, so it raises QuotaError.

Simulation jobs

Async simulation job types — FDTD transmission-line, antenna NEC-2, filter Monte Carlo, eye diagram, and 9 more — run through the same Client. Parameters and input files are validated locally against the job type's contract before anything is uploaded or sent.

import rftools

client = rftools.Client(api_key="rfc_live_xxx")

# Submit and wait for it to finish, then fetch a summary of the result
job = client.submit_job(
    "eye_diagram",
    {"dataRate": 10e9, "prbs": "prbs15"},
    files=["trace.s2p"],   # paths, or (filename, bytes) tuples
    wait=True,
    on_progress=lambda j: print(j.status, j.progress, j.stage),
)
print(job.result())          # summary: headline values, warnings, provenance
print(job.result(full=True)) # the whole result payload

# Or submit now, poll later
job = client.submit_job("fdtd_sparam", {"solveMode": "express"})
print(job.id, job.status)
...
job = client.get_job(job.id)
job = job.wait(timeout=60)   # returns even if not finished by the deadline

A job that ends in failed raises JobFailed from wait() (or result()), carrying .kind (one of the service's errorKind values) and the error message.

wait() polls the way the web app does: the first check happens immediately (no fixed initial delay), then every 2s for the first 30s, every 5s until 5 minutes, and every 15s after that, until the job reaches completed or failed.

Uploads need a key

Uploading a file needs an API key; set RFTOOLS_API_KEY or run rftools login. The service refuses an anonymous upload, so upload() and submit_job(files=...) raise AuthError with that sentence locally — before the file is read and before any request is made. A job type that takes no file still runs without a key, on the free lane, as before.

The uploaded object is recorded against the key's account, and only that account may submit it: a key obtained from a log or a shared link is refused as though it did not exist.

Uploaded files are capped at 10 MB (the API's limit) — upload()/submit_job() raise ValidationError locally if a file is over that, before any request is made.

Async Support

import asyncio
import rftools

async def main():
    async with rftools.AsyncClient(api_key="rfc_live_xxx") as client:
        result = await client.calculate("vswr-return-loss", {"vswr": 2.5})
        print(result["returnLoss"])

asyncio.run(main())

Ideal for running many calculations concurrently in FastAPI or async scripts.

Browse the Catalog

# List all calculators
calcs = rftools.list_calculators()
print(f"{len(calcs)} calculators available")

# Filter by category
rf_calcs = rftools.list_calculators(category="rf")
for c in rf_calcs:
    print(f"{c.slug}: {c.title}")

# Get a single calculator's metadata
info = rftools.get_calculator("vswr-return-loss")
print(info.inputs)   # tuple of InputField
print(info.outputs)  # tuple of OutputField

All Calculator Categories

The typed stub catalog (rftools/calculators/, rftools.list_calculators()) is generated from a snapshot of the registry and is due for a refresh to the current 241; run python scripts/generate_stubs.py --frontend-dir /path/to/rfhub/frontend to regenerate it. The counts below describe that snapshot.

Category Count Example
rf 26 vswr-return-loss, free-space-path-loss, rf-link-budget
pcb 13 trace-width-current, via-calculator, microstrip-impedance
power 20 voltage-divider, led-resistor, battery-life
signal 13 filter-designer, op-amp-gain, pwm-duty-cycle
antenna 8 dipole-antenna, eirp-calculator, parabolic-dish-antenna
general 21 lc-resonance, ohms-law, rc-time-constant
motor 18 brushless-dc-motor, stepper-motor, servo-motor
protocol 11 uart-baud-rate, i2c-pullup, can-bus-bit-timing
emc 16 emi-filter-lc, shielding-effectiveness, ground-loop
thermal 6 thermal-resistance, heat-sink, junction-temperature
sensor 17 thermistor-ntc, strain-gauge, hall-effect
unit-conversion 17 dbm-watts, frequency-wavelength, temperature
audio 17 speaker-crossover, amplifier-gain, room-acoustics

Contributing

Regenerate stubs after calculator changes:

python scripts/generate_stubs.py --frontend-dir /path/to/rfhub/frontend

Then bump the version in pyproject.toml and publish a new release.

License

MIT — see LICENSE.

Release files for rftools-io 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for rftools-io 0.3.0
File Size Uploaded
rftools_io-0.3.0.tar.gz 219.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for rftools-io 0.3.0
File Interpreter ABI Platform
rftools_io-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 434.9 kB

Release files / rftools_io-0.3.0.tar.gz

Download URL rftools_io-0.3.0.tar.gz
Size 219.3 kB
Tags Source
SHA-256 checksum
How to use checksums
92ad25b5b25e9a29ae47df3a78abbc1ab2dd8dcf53d94e83190bd61815378927
BLAKE2b-256 checksum
How to use checksums
f3afe7d1fceead4deaab94131598094b187ebfacaff2a939abe9091f92497d86
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.

Transparency log

Release files / rftools_io-0.3.0-py3-none-any.whl

Download URL rftools_io-0.3.0-py3-none-any.whl
Size 215.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
62f04f43431658bb51fa2a894fddf54e8e7e2c5d512e6c855288aee68613b945
BLAKE2b-256 checksum
How to use checksums
d02747dbcde039de8272ec7e76d13f0419d77523c02de978bb85d2512cd6825c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.

Transparency log

Release history Release notifications | RSS feed

0.4.0

2 release files

This release

0.3.0 This release

2 release files

0.2.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page