Python SDK for Ophanix, including the Tool Gateway client.
Project description
Ophanix Python SDK
Python SDK for Ophanix. The current package provides the Tool Gateway client for agents that call governed tools through the Ophanix Tool Gateway.
Install
When the package is published to your configured Python index:
pip install ophanix-python-sdk
export OPHANIX_GATEWAY_TOKEN="replace-with-a-gateway-token"
If the package has not been published to your configured package index yet,
install from a validated wheel generated by python3 scripts/validate_release.py,
or install from the repository path during internal testing:
pip install .
The package distribution is named ophanix-python-sdk; the stable import path
for the Tool Gateway client remains ophanix_tool_gateway.
Gateway tokens are issued by Ophanix for a specific agent, organization,
environment, scope, and tool resource. Do not hard-code them in application
source or commit them to configuration files. Prefer environment variables,
secret managers, or a custom token provider that reads from your runtime's
credential store. Production tokens must be generated by the Ophanix credential
issuance flow or an equivalent cryptographically random issuer; short,
human-readable fixture tokens are only acceptable in local tests.
Token providers must return the raw token without the Bearer prefix. Raw
tokens may contain only A-Z, a-z, 0-9, ., _, ~, +, /, =, and
-, with no whitespace, and must be 4096 characters or fewer.
Use https:// gateway URLs outside local development. Plain http:// is accepted for
localhost and loopback addresses only; non-local HTTP requires the explicit
allow_insecure_http=True opt-in.
Sync Usage
from ophanix_tool_gateway import (
EnvironmentTokenProvider,
OphanixToolGatewayClient,
ToolAuthenticationError,
ToolDeniedError,
ToolGatewayError,
)
with OphanixToolGatewayClient(
base_url="https://gateway.example.com",
token_provider=EnvironmentTokenProvider(),
) as client:
try:
compatibility = client.check_compatibility()
if not compatibility.compatible:
raise RuntimeError("SDK and gateway contract versions do not match")
tools = client.list_all_tools()
result = client.call_tool("claims.lookup", {"claim_id": "claim_123"})
except ToolDeniedError as exc:
print(f"Denied by gateway policy: {exc.reason_code}")
except ToolAuthenticationError:
print("Gateway token was rejected")
except ToolGatewayError as exc:
print(f"Gateway call failed: {exc.code or exc.status_code}")
else:
print([tool.name for tool in tools])
print(result.result)
API Reference
OphanixToolGatewayClient is the synchronous client. Use it as a context manager
so the underlying HTTP client is closed when the agent shuts down.
AsyncOphanixToolGatewayClient mirrors the synchronous API for async runtimes.
It accepts either a sync token provider or an async token provider.
Use ToolGatewayClientConfig with OphanixToolGatewayClient.from_config(...)
or AsyncOphanixToolGatewayClient.from_config(...) when several workers share
the same timeout, cache, retry, and response-limit settings.
Common constructor options:
base_url: absolute gateway URL. Non-localhttp://is rejected unlessallow_insecure_http=True.token_provider: object withget_token(). UseEnvironmentTokenProvider()forOPHANIX_GATEWAY_TOKEN, orStaticTokenProvider(token)for tests and short-lived scripts.timeout_seconds: per-request timeout, default5.0.max_payload_bytes: client-side serialized payload cap, default1_000_000.max_response_bytes: client-side gateway response cap, default1_000_000. The built-in HTTPX clients enforce this while streaming response bytes and before JSON parsing. Custom injected clients must exposestream()by default. Setallow_buffered_custom_http_client=Trueonly when the injected client enforces equivalent response-size limits before materializing bodies.cache_tools: opt-in discovery cache, defaultFalse.cache_ttl_seconds: cache TTL whencache_tools=True, default300.0.max_cache_entries: maximum process-local discovery cache entries when caching is enabled, default256.event_hook: optional callable that receives immutable, token-free telemetry events for tool-call start/success/denial/error. Hook exceptions are swallowed and debug-logged by default so observability code cannot break tool calls.raise_event_hook_errors: whenTrue, event hook failures are surfaced to the caller. Use this in tests or strict observability environments.discovery_max_retries: retry count for discovery only, default2.discovery_retry_backoff_seconds: base exponential backoff, default0.2.discovery_retry_max_sleep_seconds: cap for retry sleeps andRetry-After, default5.0.discovery_retry_jitter_ratio: client-side retry jitter ratio, default0.2.invocation_max_retries: retry count for idempotent tool invocations, default2. The SDK only uses this whencall_tool(..., idempotency_key=...)is provided.invocation_retry_backoff_seconds: base invocation retry backoff, default0.2.invocation_retry_max_sleep_seconds: cap for invocation retry sleeps andRetry-After, default5.0.invocation_retry_jitter_ratio: invocation retry jitter ratio, default0.2.allow_buffered_custom_http_client: defaultFalse; set only for injected clients that enforce equivalent response-size limits without SDK streaming.
Main methods:
call_tool(tool_name, payload, correlation_id=None, idempotency_key=None): invokes one tool. Payload must be a JSON object with string keys and finite numeric values. Whenidempotency_keyis set, the SDK sendsIdempotency-Keyand may retry transient invocation failures with the same key.check_compatibility(): calls authenticated/api/v1/gateway/capabilitiesand returnsGatewayCompatibility.list_tools(owner_team=None, limit=50, offset=0): lists one page of callable active tools for the configured credential. The gateway discovery API is active-only; the legacystatus="active"argument remains accepted only for compatibility and emitsDeprecationWarning.list_all_tools(owner_team=None, page_size=200, max_total=None): follows discovery pagination and can fail closed if discovery exceedsmax_total.get_tool(tool_name_or_id): finds one callable tool by name or id.clear_tool_cache(): clears cached discovery results whencache_tools=True.
get_tool() searches only the definitions visible to the current credential. A
missing result raises ToolGatewayError with code="tool_not_visible" because
the SDK cannot safely distinguish a nonexistent tool from a tool hidden by
authorization.
Async Usage
from ophanix_tool_gateway import AsyncOphanixToolGatewayClient, EnvironmentTokenProvider
async with AsyncOphanixToolGatewayClient(
base_url="https://gateway.example.com",
token_provider=EnvironmentTokenProvider(),
) as client:
tool = await client.get_tool("claims.lookup")
result = await client.call_tool(tool.name, {"claim_id": "claim_123"})
print(result.request_id, result.result)
Error Handling
The SDK raises ToolGatewayValidationError for invalid local configuration or
caller input, ToolAuthenticationError for rejected gateway credentials,
ToolDeniedError for gateway policy denials, and ToolGatewayError for
transport, gateway, upstream, and response-contract failures.
ToolGatewayValidationError is a ValueError subclass for backward-compatible
callers that already catch ValueError. Exception messages are intentionally
generic so application logs do not inherit arbitrary upstream text. Use
status_code, code, reason_code, request_id, correlation_id,
retry_after_seconds, and the sanitized response_body for debugging.
try:
result = client.call_tool("claims.lookup", {"claim_id": "claim_123"})
except ToolAuthenticationError as exc:
refresh_or_rotate_token = exc.code
except ToolDeniedError as exc:
audit_reason = exc.reason_code
except ToolGatewayError as exc:
request_id = exc.request_id
diagnostic_code = exc.code or exc.status_code
retry_after = exc.retry_after_seconds
Reliability And Safety
Discovery uses the gateway-authenticated /api/v1/gateway/tools route and only returns
active tools callable by the configured credential. list_all_tools() follows pagination
for you, while list_tools() exposes explicit limit and offset controls.
Pagination is offset-based in 0.1.x; consumers that need very large or
high-churn catalogs should set max_total and re-run discovery after contract
changes until the gateway exposes cursor pagination.
The optional owner_team discovery filter is an exact, case-sensitive server
filter; use the owner-team value from the tool definition rather than display
text.
Discovery requests retry transient 408, 429, and 5xx responses by default with
bounded exponential backoff, Retry-After support, and jitter. Tool invocation
requests retry transient transport, 408, 429, and 5xx failures only when
the caller supplies idempotency_key; the same key is sent on every attempt so
the gateway can replay a completed result or reject a conflicting payload.
The SDK validates payloads as strict JSON objects, rejects non-finite numbers,
non-string object keys, and cyclic Python payloads, rejects header control
characters, rejects raw gateway tokens longer than 4096 characters, redacts
sensitive fields from exception diagnostic bodies, caps
gateway responses while streaming bytes before JSON parsing when using the
built-in HTTPX clients, and keeps static tokens out of generated repr() output.
When cache_tools=True, discovery cache entries are partitioned by a
process-local HMAC fingerprint of the current bearer token and expire after
cache_ttl_seconds.
Cached results do not cross credential rotations, and list_all_tools() /
get_tool() use one credential context across their paginated requests. Cached
tool definitions are copied before they are returned so caller mutation cannot
poison later cache reads. The cache is process-local and bounded by
max_cache_entries; call clear_tool_cache() after urgent permission or
tool-contract changes instead of waiting for TTL expiry.
Client cache mutation is internally guarded, but applications should still prefer
one client per worker process or event loop so timeout, token-provider, and
lifecycle behavior remains easy to reason about.
Async calls follow normal asyncio cancellation semantics. If a task awaiting
call_tool() is cancelled, treat the invocation outcome as unknown unless your
tool contract is idempotent or your application can reconcile by request_id or
correlation_id.
Use a unique idempotency key per logical invocation when retrying or when the caller cannot tolerate duplicate upstream side effects:
result = client.call_tool(
"claims.lookup",
{"claim_id": "claim_123"},
idempotency_key="claim-123-refresh-2026-05-11T09:00Z",
)
Reusing a key with a different payload returns idempotency_conflict. Reusing a
key while the original call is still running returns idempotency_in_progress.
EnvironmentTokenProvider reads the configured environment variable on each
request. For high-throughput agents, prefer a custom provider backed by your
runtime secret manager or refresh cache:
class CachedTokenProvider:
def __init__(self, loader):
self._loader = loader
self._token = None
def get_token(self) -> str:
if self._token is None:
self._token = self._loader()
return self._token
SDK event hooks receive immutable mappings with no bearer tokens:
| Event | Fields |
|---|---|
tool_call.start |
tool_name, correlation_id, idempotent |
tool_call.success |
tool_name, request_id, correlation_id, reason_code, elapsed_ms |
tool_call.denied |
tool_name, status_code, elapsed_ms |
tool_call.error |
tool_name, status_code or code, elapsed_ms |
tool_call.retry |
tool_name, attempt, delay_seconds, status_code, code |
tool_discovery.retry |
attempt, delay_seconds, status_code |
Sanitized exception response_body values redact common credential keys and
PII-like keys including authorization, api_key, credential, password,
secret, token, key, email, phone, address, and ssn.
Credential Issuance
The SDK consumes gateway bearer tokens; it does not mint them. Production tokens must be issued through Product Platform agent credential issuance:
- Register or select an active agent identity.
- Approve the capability scope the credential needs.
- Bind the scope to the exact tool resource where possible.
- Issue a short-lived bearer credential from the Product Platform API or operator workflow.
- Store the raw token in a runtime secret store and expose it to the SDK through
EnvironmentTokenProvideror a custom provider. - Rotate before expiry and clear discovery caches after emergency permission changes.
Local fixture tokens in direct HTTP examples are deterministic demo data and are not production credentials.
Minimal local issuance flow for demo data:
cd packages/product-platform
python3 -m product_platform.cli db migrate
python3 -m product_platform.cli db seed
python3 -m product_platform.cli serve --host 127.0.0.1 --port 8088
export OPHANIX_BASE_URL="http://127.0.0.1:8088"
export OPHANIX_OPERATOR_TOKEN="<operator-api-token-from-your-local-login-or-admin-flow>"
curl -sS -X POST "$OPHANIX_BASE_URL/api/v1/agents/agent_claims_demo/credentials" \
-H "Authorization: Bearer $OPHANIX_OPERATOR_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Environment-ID: env_default" \
--data '{
"credential_type": "bearer",
"issuer": "local-demo",
"ttl_seconds": 900,
"scopes": [
{"scope": "claims.lookup:read", "resource_type": "tool", "resource_id": "claims.lookup"}
]
}'
The response includes the raw token once. Store it in a local secret store or
OPHANIX_GATEWAY_TOKEN; do not commit it. Exact endpoint names may differ in
private operator builds, so verify the local API shape against your deployment
before writing automation.
Stability
The package is still 0.1.0 and classified as Beta. Constructor names, typed
error classes, and the main sync/async methods are treated as the supported
surface for this line; compatibility shims such as list_tools(status="active")
may emit deprecation warnings before removal in a future pre-1.0 release.
Compatibility matrix:
| SDK version | Expected gateway contract | Python versions | Notes |
|---|---|---|---|
0.1.x |
/api/v1/gateway/capabilities, /api/v1/gateway/tools, and /api/v1/tools/{name}/invoke |
3.11, 3.12, 3.13 | Beta MVP. product_platform.tool_gateway imports are compatibility shims; prefer ophanix_tool_gateway. |
MVP support boundary: the SDK is suitable for controlled internal or design
partner pilots where gateway operators control tokens, upstream targets,
ingress limits, and rollout scope. It is not an enterprise production
certification. Idempotent invocation retries are implemented for callers that
provide Idempotency-Key; cursor pagination, externally enforced ingress
limits, and broader release-governance evidence remain post-MVP hardening.
Troubleshooting
ToolGatewayValidationError: base_url must use https: use anhttps://gateway URL outside localhost development, or explicitly opt into non-local HTTP for a trusted test network.ToolGatewayValidationError: token is required: check thatOPHANIX_GATEWAY_TOKENis present and not blank.ToolGatewayValidationError: token must be the raw gateway token without the Bearer prefix: removeBearerfrom the value returned by your token provider. The SDK adds the authorization scheme itself.ToolDeniedError: the credential authenticated successfully, but policy, permission, scope, or resource binding denied the call.ToolGatewayError(code="invalid_response"): the gateway returned a successful HTTP response that did not match the SDK contract. Capture therequest_idand sanitizedresponse_bodywhen reporting the issue.ToolGatewayError(code="transport_error"): the SDK could not reach the gateway. Check DNS, TLS, connectivity, proxy settings, andtimeout_seconds.ToolGatewayError(code="response_too_large"): the gateway response exceededmax_response_bytesbefore SDK JSON parsing.ToolGatewayError(code="client_closed"): the SDK client was used afterclose()or context-manager exit. Create a new client for further calls.
Production Adoption Checklist
- Use an
https://gateway URL outside localhost development. - Use the SDK instead of direct HTTP examples for production agents.
- Issue high-entropy gateway tokens through the Product Platform credential workflow, store them in a secret manager, and rotate them before expiry.
- Scope credentials to the minimum tool resource and operation required.
- Set
timeout_secondsandmax_payload_bytesfor the caller's workload. - Use
idempotency_keyfor any invocation that may be retried or reconciled after an unknown outcome. - Enable discovery caching only when stale contract/permission data is acceptable
for up to
cache_ttl_seconds. - Capture
request_id,correlation_id,code, and sanitizedresponse_bodyin application diagnostics. - Use custom HTTP clients only when they support streaming response limits, or
set
allow_buffered_custom_http_client=Truewith an equivalent safety control.
Release Validation
Before publishing a standalone SDK release, install release tooling and validate both artifact shapes:
python3 -m pip install '.[release]'
python3 scripts/validate_release.py
The release check builds a wheel and sdist, verifies that both artifacts contain
ophanix_tool_gateway/__init__.py, ophanix_tool_gateway/sdk.py, and
ophanix_tool_gateway/py.typed, rejects local/generated files such as SQLite
databases and __pycache__, verifies parity with the product-platform vendored
SDK copy, installs the built wheel with runtime dependencies into a temporary
target, writes a local CycloneDX SBOM with artifact hashes, and runs
twine check over the generated artifacts. Dependency audit can be enforced in
CI with:
python3 -m pip install '.[security]'
python3 scripts/validate_release.py --require-dependency-audit
python3 scripts/validate_release.py --strict-git
The SDK depends only on httpx. It is also re-exported from
product_platform.tool_gateway for compatibility with earlier internal imports.
CI exercises the SDK on Python 3.11, 3.12, and 3.13. Dependency range changes
must keep that matrix green before release.
--strict-git additionally requires a clean SDK package worktree and an exact
release tag matching v<project.version> unless --expected-tag is supplied.
Publishing is intentionally separate from local artifact validation. A release owner must publish only artifacts produced by the validated workflow, preserve the build logs, artifact checksums, provenance attestations, and SBOM, and document whether the final upload is performed through GitHub trusted publishing, Azure DevOps, or another approved internal release pipeline.
Project details
Release history Release notifications | RSS feed
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 ophanix_python_sdk-0.1.0.tar.gz.
File metadata
- Download URL: ophanix_python_sdk-0.1.0.tar.gz
- Upload date:
- Size: 33.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
233c05a88ab97507938968e4e127431472ef5a6983e875ecfcc3a09ce31e0aa2
|
|
| MD5 |
22c19c5b56ad43a538763abf18fbf7da
|
|
| BLAKE2b-256 |
2f887c74258bf34a4b6df4e0b0e865c075c499bcb450b86e093a31dc62b30c81
|
Provenance
The following attestation bundles were made for ophanix_python_sdk-0.1.0.tar.gz:
Publisher:
publish.yml on igordjuric404/ophanix-python-sdk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ophanix_python_sdk-0.1.0.tar.gz -
Subject digest:
233c05a88ab97507938968e4e127431472ef5a6983e875ecfcc3a09ce31e0aa2 - Sigstore transparency entry: 1509847020
- Sigstore integration time:
-
Permalink:
igordjuric404/ophanix-python-sdk@256e57ea8b7b9b7cebaf5ca9a28d07213916a035 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/igordjuric404
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@256e57ea8b7b9b7cebaf5ca9a28d07213916a035 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file ophanix_python_sdk-0.1.0-py3-none-any.whl.
File metadata
- Download URL: ophanix_python_sdk-0.1.0-py3-none-any.whl
- Upload date:
- Size: 23.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4a26b54ba1debd8b04633b056b791850037907f59299c7d3635823fdee9f8ccb
|
|
| MD5 |
2f30ac20860116e9b9d7b1046d028e9c
|
|
| BLAKE2b-256 |
d2685a8f9e536e675a8d889962073232967d0b3eb9706f02b02b977c3c302f2e
|
Provenance
The following attestation bundles were made for ophanix_python_sdk-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on igordjuric404/ophanix-python-sdk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ophanix_python_sdk-0.1.0-py3-none-any.whl -
Subject digest:
4a26b54ba1debd8b04633b056b791850037907f59299c7d3635823fdee9f8ccb - Sigstore transparency entry: 1509847169
- Sigstore integration time:
-
Permalink:
igordjuric404/ophanix-python-sdk@256e57ea8b7b9b7cebaf5ca9a28d07213916a035 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/igordjuric404
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@256e57ea8b7b9b7cebaf5ca9a28d07213916a035 -
Trigger Event:
workflow_dispatch
-
Statement type: