ordainer (Python)
Ask Ordainer before your tool runs: one decide call checks the caller's plan and counts the use.
Python 3.9+, standard library only (http.client, json), no required dependencies. Optional async client
via pip install ordainer[async] (httpx). Mirrors the Ruby gem (packages/ordainer-ruby, 0.1.1) and the
Node SDK (packages/ordainer-node).
Install from PyPI:
pip install ordainer
Async client:
pip install "ordainer[async]"
import os
from ordainer import Client, deny_response
client = Client(api_key=os.environ["ORDAINER_API_KEY"]) # ord_test_... or ord_live_...
decision = client.decide(tool="research.run")
if decision.allowed:
print(decision.decision, decision.remaining)
else:
blocked = deny_response(decision) # {"status", "headers", "body": {"error": {"code", "message", ...}}}
print(blocked["status"], blocked["body"]["error"]["code"])
decide_or_raise is the Python name for the Ruby client's decide!. It returns the Decision when the call
may run, and raises DeniedError, RejectedError, RateLimitedError or UnavailableError otherwise
(all BlockedError, with .status, .headers, .body, .decision).
import os
from ordainer import BlockedError, Client
client = Client(api_key=os.environ["ORDAINER_API_KEY"])
try:
decision = client.decide_or_raise(tool="research.run")
print(decision.decision)
except BlockedError as error:
blocked = {"status": error.status, "headers": error.headers, "body": error.body}
print(blocked["status"], blocked["body"]["error"]["message"])
There is no Django, Flask or FastAPI middleware in 0.1.0.
Options
| Option | Default | Meaning |
|---|---|---|
api_key |
ORDAINER_API_KEY, then ORDAINER_KEY |
Agent key. Override per call: decide(tool=..., api_key=...). |
base_url |
ORDAINER_BASE_URL or https://api.ordainer.dev |
|
timeout |
0.3 |
Seconds per attempt. |
fail_mode |
"open" |
"open" lets the call run when Ordainer can't answer; "closed" blocks it. |
retries |
1 |
Extra attempts (0 to 5) after a timeout, network error or 503, with jittered backoff, same idempotency key. |
retry_budget |
0.65 |
Seconds for all attempts of one decide. |
mode |
None |
None follows the plan's mode in the console; "enforce" or "shadow". |
billing_lapse |
"open" |
What to do on 403 subscription_inactive. |
deny_status |
429 |
Status for quota denies: 429 or 402. |
sticky_deny |
60 |
Seconds to keep denying would-be fail-opens for an (agent key, tool) after a quota deny. 0 = off. |
logger |
warnings to stderr | An object with warning (or warn) and optional error, or False. |
on_decision |
None |
Called with every Decision. |
Durations are seconds, the same lengths as the Node SDK's millisecond settings (timeoutMs 300, retryBudgetMs 650, stickyDenyMs 60000).
Rules (same as the Node SDK and the Ruby gem)
decidenever raises for an Ordainer answer or an outage. A deny is a normal result. Bad options raiseConfigurationError.- Only a deny from Ordainer blocks in enforce mode. Shadow never blocks (
would_deny). fail_modecovers only "Ordainer couldn't answer": timeout, network error, 5xx, or a 200 that isn't a decision.- A 4xx (bad or missing key, 400, 409, 429 rate limit, ...) is Ordainer answering "no": blocked in any
fail_mode(rejected). 403subscription_inactivefollowsbilling_lapseinstead. 429safety_limit_reachedlets the call run (safety_limited). - Only timeouts, network errors and 503 are retried, never other 5xx or 4xx. A retry sends the same idempotency key, so it is replayed, never counted twice. Length is counted in UTF-16 code units, the same way as the Node SDK and the server. A key over 255 units, or a key that contains a control character or an unpaired surrogate, is hashed. An unpaired surrogate is hashed from its UTF-16LE bytes. Anything else is hashed as UTF-8.
- What the caller reads (
deny_response, same text as the Node SDK and the Ruby gem): a block a retry can't fix answers500 plan_check_rejectedwith plain words for the cause and the API's code last, e.g. "This call wasn't run. This is a setup issue on the service's side: one of its keys isn't recognized. It isn't something you did, and retrying won't help until the service fixes the key. (For the service's team: unknown_key.)" A429 rate_limitedanswers "The service is getting too many requests on this key right now, so this call was held back. Please wait a moment and try again. (rate_limited)" withRetry-After; a429 concurrency_limitedanswers "The service is handling too many calls on this key at once, so this call was held back. Please wait a moment and try again. (concurrency_limited)" (thecodefield staysrate_limited). Billing blocks (403) keep the Legal-approved text. - The same text as the Node SDK for the other blocks too (PM final wording, Oct 18). Error messages can be reworded;
codes, statuses and fields stay the same, so match on
code, not the message. An outage underfail_mode="closed"answers503 ordainer_unavailablewithRetry-After: 5: "This call wasn't run. The service couldn't finish a safety check it does before each call, so it stopped instead of going ahead without it. Nothing is wrong on your side. Please try again in a minute. (ordainer_unavailable)". A used-up test allowance answers429 test_limit_reached: "This call wasn't run. This service's test calls for the month are used up. They reset on November 1 (UTC). Nothing is wrong on your side, and trying again won't help until they reset. If you need it sooner, contact the service's team. (test_limit_reached)" (the day isresets_atin UTC as ""; no "They reset on" sentence unlessresets_atis a valid ISO-8601 date-time). Atool_not_in_plandeny answers403 plan_denied: "This call wasn't run because your plan doesn't include "research.run". If you need it, ask whoever manages your plan. (tool_not_in_plan)", naming the tool you passed todecidein quotes only when it matches^[A-Za-z0-9][A-Za-z0-9_.:/-]{0,63}$and has no//and no leadingscheme:(^[A-Za-z][A-Za-z0-9+.-]*:, so nothing URL-like is echoed), otherwise "this action". Any other deny answers "This call wasn't run because it isn't allowed on your account right now. If you need it, contact the service's team. (reason)", with the reason in brackets only when it looks like an API code (lowercase letters and_, up to 40 characters); the reason is also its ownreasonfield. - A shadow deny logs exactly:
[ordainer] shadow: would deny tool=research.run reason=quota_exceeded remaining=0/5 request_id=req_s (call allowed)(remainingisno capwhenlimitis null). The agent key is never logged. - After a
quota_exceededdeny, a later call for the same agent key and tool that would fail open is denied instead (sticky_deny) untilsticky_denyseconds pass, the plan resets, or an allow arrives. Shadow still runs the call.
Decision
allowed, decision ("allow" / "deny" / None), reason, remaining, limit, resets_at,
request_id, mode, would_deny, fail_open, fail_closed, rejected, rate_limited,
retry_after_sec, billing_inactive, billing_lapse, test_limit_reached, safety_limited, sticky_deny, replayed,
attempts, latency_ms, error (code, message, status, limit_scope, request_id), to_dict().
Async
import asyncio
import os
from ordainer import AsyncClient
async def main():
async with AsyncClient(api_key=os.environ["ORDAINER_API_KEY"]) as client:
decision = await client.decide(tool="research.run")
print(decision.allowed, decision.decision)
asyncio.run(main())
Same options, same rules, same messages.
Shared client
import os
import ordainer
def configure(cfg):
cfg.api_key = os.environ["ORDAINER_API_KEY"]
cfg.fail_mode = "open"
ordainer.configure(configure)
decision = ordainer.client().decide(tool="research.run")
print(decision.allowed, decision.decision)
Tests
cd packages/ordainer-python && python -m pip install -e ".[async,dev]" && python -m pytest
Set ORDAINER_LIVE_TEST_KEY (a test key) to also run one live call against ORDAINER_BASE_URL.
Metadata
Release files for ordainer 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| ordainer-0.1.0.tar.gz | 23.2 kB | With provenance | Details for ordainer-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| ordainer-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for ordainer-0.1.0-py3-none-any.whl |
Total release size: 51.1 kB
Release files / ordainer-0.1.0.tar.gz
| Download URL | ordainer-0.1.0.tar.gz |
|---|---|
| Size | 23.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
90ae0e1e99892eb606ec059c8bdf9eb3676141c4a4c71944acb3600200cba3f7
|
|
BLAKE2b-256 checksum How to use checksums |
91234b3753c6ad72b2c62eb6f1849027db6864467de4f392f5d39d18186380d3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Oct 31, 2026.
Transparency logRelease files / ordainer-0.1.0-py3-none-any.whl
| Download URL | ordainer-0.1.0-py3-none-any.whl |
|---|---|
| Size | 27.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
74f1bce41577359e0ce700d2003be894bfc8782095229cf075f2b2234aa98deb
|
|
BLAKE2b-256 checksum How to use checksums |
5078d45d41faf685ddfa6f57b150954671e49503de4c78d5dc35728f9adc17b9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Oct 31, 2026.
Transparency logSecurity for ordainer 0.1.0
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.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for ordainer-0.1.0.tar.gz
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 Oct 31, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.