meterspw-sdk
Python client for Meter-SPW — deterministic LLM routing with metered savings.
You bring your own provider keys. Meter-SPW routes each prompt to the cheapest model that can answer it, caches what repeats, and meters what it saved.
pip install meterspw-sdk
Zero runtime dependencies — the default transport is urllib from the standard
library.
Use
from meterspw import MeterSpwClient
with MeterSpwClient() as spw: # key from ROUTER_API_KEY
out = spw.relay(
"Summarise this contract in three bullets.",
model="claude-sonnet-5",
max_tokens=2000,
)
print(out.content)
print(out.model_used) # what ACTUALLY answered
print(out.savings.usd) # what that saved you
model is what you want; model_used is what answered. The difference is the
product.
Attachments
out = spw.relay_multipart(
"Summarise the attached report.",
model="claude-sonnet-5",
max_tokens=2000,
files=["report.pdf"],
)
Know where you stand
b = spw.balance()
print(b.usd, b.accrued_micro_usd, b.remaining_usd)
if b.passthrough:
print("credit exhausted — calls still work, but nothing is being routed")
Check passthrough. At zero credit the router degrades to a transparent
proxy: your calls keep succeeding, with no routing, no cache and no savings.
Nothing fails, which is exactly why it is easy to miss.
Three numbers, because two books. usd is the ledger. accrued is this
period's fees, which have not been taken yet — they settle at the monthly
close. remaining is what you should plan against.
Your receipts
rows = spw.verdicts(window="7d", q="haiku") # the last 7 days, searched
window is one of 30m 1h 6h 12h 24h 7d 4w 12m (a period ending now); q
matches the decision, the requested model and the served model. The router
applies both, so the same two values count and export exactly these rows:
curl -H "Authorization: Bearer $ROUTER_API_KEY" \
"https://spw.scitechsolutions.ai/v1/verdicts/count?window=7d&q=haiku"
curl -H "Authorization: Bearer $ROUTER_API_KEY" -o verdicts-7d.csv \
"https://spw.scitechsolutions.ai/v1/verdicts.csv?window=7d&q=haiku"
The export is refused, with the count, when more than 200 000 rows match — narrow the window or the search.
Long requests
A proxy between you and the router closes a connection that stays silent for
about 100 seconds, and the router is silent until the verified answer exists.
So relay() asks for keep-alive delivery: the router answers within a second,
keeps the line warm every 15 seconds while the model works, and delivers the
whole answer at the end — the same result, nothing to change on your side.
timeout bounds silence on the line, not the whole call. A long answer is
bounded by the router's own request limit (the operator's
ROUTER_REQUEST_TIMEOUT_SECS), not by this number.
Over raw HTTP, send the header yourself and read the last data: line:
curl -N -X POST https://spw.scitechsolutions.ai/v1/relay \
-H "Authorization: Bearer $ROUTER_API_KEY" \
-H "Accept: text/event-stream" \
-H "Content-Type: application/json" \
-d '{"prompt": "Summarise this contract in three bullets.", "model": "claude-sonnet-5", "max_tokens": 2000}'
# -N is optional: curl prints each line as it arrives instead of all at the end.
: keep-alive ← every 15 s while the model works
event: result
data: {"decision": "relayed", "model_used": "…", "content": "…", "verdict_id": "…", "latency_ms": 1840, …}
An event: error line with {"status": 502, "error": "…"} takes the place
of result if the router fails late. /v1/relay-multipart takes the same
header. Without it the reply is one JSON body — and a call longer than about
100 seconds dies at the proxy.
Errors
| Exception | When | Retry? |
|---|---|---|
AuthError |
401/403 — key wrong or revoked | no |
RateLimited |
429 | yes, honours Retry-After |
UploadCapacityFull |
503 — upload slots full | yes, honours Retry-After |
Exhausted |
502 — the router spent every lever | no |
TransportError |
never reached the server | yes |
Exhausted is not retryable on purpose. The router already climbed the model
ladder, repaired and re-called before answering — each of those attempts billed
your provider key. Asking again spends the same levers for the same failure.
The message names what it tried.
Retries use exponential backoff with deterministic jitter and a wall-clock
budget, and never extend past your own deadline. Configure with RetryPolicy.
Configuration
ROUTER_API_KEY |
your Meter-SPW key (or pass api_key=) |
ROUTER_BASE_URL |
defaults to https://spw.scitechsolutions.ai |
The SDK never sees a provider key. Those are stored once in the dashboard and used by the router on your behalf — the only credential here spends credit, not inference.
Support
support@scitechsolutions.ai — quote RelayResult.verdict_id or the
request_id from an error and we can find the exact call.
© S&T Integrated Solutions, LLC — MIT licensed.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file meterspw_sdk-0.2.0.tar.gz.
File metadata
- Download URL: meterspw_sdk-0.2.0.tar.gz
- Upload date:
- Size: 28.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
896a85e12b361ea0e00bbbd685a2a9a5fb62ef4d7911b49cfa327da1c8951cf7
|
|
| MD5 |
d205ba580300fd9643d3cdd3a82b4190
|
|
| BLAKE2b-256 |
f92c7863d5d7496a2389cb9d8f9f7c2b11564b32032c4198c4eafb936a8d761f
|
Provenance
The following attestation bundles were made for meterspw_sdk-0.2.0.tar.gz:
Publisher:
sdk-python-publish.yml on johndo0277/llm-router
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
meterspw_sdk-0.2.0.tar.gz -
Subject digest:
896a85e12b361ea0e00bbbd685a2a9a5fb62ef4d7911b49cfa327da1c8951cf7 - Sigstore transparency entry: 2745638682
- Sigstore integration time:
-
Permalink:
johndo0277/llm-router@8fe8ad6a4514cb5d2698a0b63c4b125452bc77a2 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/johndo0277
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
sdk-python-publish.yml@8fe8ad6a4514cb5d2698a0b63c4b125452bc77a2 -
Trigger Event:
push
-
Statement type:
File details
Details for the file meterspw_sdk-0.2.0-py3-none-any.whl.
File metadata
- Download URL: meterspw_sdk-0.2.0-py3-none-any.whl
- Upload date:
- Size: 25.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9dfed3f8590ae302f88cf61fe096dd355069472756966889cd10421be6df717d
|
|
| MD5 |
0a45de02f6c5cb3d746b57bb9dce13a1
|
|
| BLAKE2b-256 |
c2854c81f4a73eb59b64f083506c433eea7667f642a50328bd491ec11166c2f0
|
Provenance
The following attestation bundles were made for meterspw_sdk-0.2.0-py3-none-any.whl:
Publisher:
sdk-python-publish.yml on johndo0277/llm-router
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
meterspw_sdk-0.2.0-py3-none-any.whl -
Subject digest:
9dfed3f8590ae302f88cf61fe096dd355069472756966889cd10421be6df717d - Sigstore transparency entry: 2745638704
- Sigstore integration time:
-
Permalink:
johndo0277/llm-router@8fe8ad6a4514cb5d2698a0b63c4b125452bc77a2 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/johndo0277
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
sdk-python-publish.yml@8fe8ad6a4514cb5d2698a0b63c4b125452bc77a2 -
Trigger Event:
push
-
Statement type: