Skip to main content

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)

  • decide never raises for an Ordainer answer or an outage. A deny is a normal result. Bad options raise ConfigurationError.
  • Only a deny from Ordainer blocks in enforce mode. Shadow never blocks (would_deny).
  • fail_mode covers 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). 403 subscription_inactive follows billing_lapse instead. 429 safety_limit_reached lets 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 answers 500 plan_check_rejected with 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.)" A 429 rate_limited answers "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)" with Retry-After; a 429 concurrency_limited answers "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)" (the code field stays rate_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 under fail_mode="closed" answers 503 ordainer_unavailable with Retry-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 answers 429 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 is resets_at in UTC as ""; no "They reset on" sentence unless resets_at is a valid ISO-8601 date-time). A tool_not_in_plan deny answers 403 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 to decide in quotes only when it matches ^[A-Za-z0-9][A-Za-z0-9_.:/-]{0,63}$ and has no // and no leading scheme: (^[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 own reason field.
  • A shadow deny logs exactly: [ordainer] shadow: would deny tool=research.run reason=quota_exceeded remaining=0/5 request_id=req_s (call allowed) (remaining is no cap when limit is null). The agent key is never logged.
  • After a quota_exceeded deny, a later call for the same agent key and tool that would fail open is denied instead (sticky_deny) until sticky_deny seconds 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)

Source distribution for ordainer 0.1.0
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)

Table of built distributions (wheels) for ordainer 0.1.0
File Interpreter ABI Platform Actions
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 log

Release 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 log

Security 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.

Select from 2 files

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 log

A 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.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

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