Overview
The gateway, not the agent, decides. The SDK never bypasses a decision: it submits, waits for approval when told to, and returns the decision and result. Everything it does is also possible with plain HTTP; see the API reference.
| Distribution / import | cydra-sdk / cydra_sdk, version 0.1.0 |
| Python | 3.10 or later |
| Dependencies | httpx (0.28) |
| Style | Synchronous; usable as a context manager. No background threads. |
Installation
python -m pip install ./cydra-sdk # a directory or .whl fileYou need a registered agent and a workload identity (client ID and secret). The quick start shows how to create both. Keep the secret in your secret manager or environment, never in source code.
Submitting an action
import os
from cydra_sdk import CydraGateway
with CydraGateway(
"https://app.cydralabs.com",
os.environ["CYDRA_CLIENT_ID"],
os.environ["CYDRA_CLIENT_SECRET"],
) as gateway:
result = gateway.request_action(
tool="ledger.accounts.read",
target="ledger://accounts/operating",
parameters={"account": "operating"},
justification="Month-end balance check",
)
print(result.state, result.decision, result.reason_codes)
if result.executed:
print(result.output)request_action returns an ActionResult for every decision, including a deny: check result.decision or result.state. It raises an exception when the request itself fails (for example a validation error or an expired credential).
Waiting for approval
When policy requires approval, the initial call returns state == "approval_required" and nothing has run. authorize_and_execute handles the whole flow:
from cydra_sdk import ActionDenied, ApprovalNotGranted, ApprovalTimeout
try:
result = gateway.authorize_and_execute(
tool="payments.transfer.create",
target="treasury://payments/supplier-run",
parameters={"amount": 4800, "currency": "GBP", "beneficiary": "Harbor Logistics Ltd"},
justification="Supplier run 2026-W40",
wait_for_approval_seconds=900, # 0 (the default) returns immediately without waiting
)
except ActionDenied as exc:
print("Denied:", exc.result.reason_codes)
except (ApprovalNotGranted, ApprovalTimeout) as exc:
print("Not approved:", exc)
else:
print(result.state, result.output) # "executed", {"payment_reference": "PAY-…", ...}- Submits the action with a generated idempotency key.
- If approval is required and
wait_for_approval_secondsis above zero, polls the action's status (everypoll_seconds, default 3) until an approval token is available. - Resubmits the byte-for-byte identical request with the same key and the token. The token is bound to a hash of the request, so nothing can change in between.
- Raises
ActionDeniedif the decision is deny (or the action was denied or revoked); otherwise returns the result. Other outcomes, such asfailed, are returned: checkresult.executed.
To control each step yourself, use request_action, then wait_for_approval(action_id), then request_action(..., idempotency_key=initial.idempotency_key, approval_token=token) with the same arguments.
Authentication
- The SDK exchanges the client ID and secret at
POST /api/v1/identities/tokenon its initial call (not in the constructor) and caches the token in memory. - It refreshes the token
token_refresh_margin_seconds(default 30) before it expires. Tokens last five minutes. - When an action submission returns
401withtoken_expiredorsession_revoked, it fetches a fresh token and retries once. A revoked identity fails again withAuthenticationError. - Every request carries an
X-Correlation-IDbeginningsdk-, which appears in the audit trail and evidence.
Idempotency
Every submission carries an Idempotency-Key. If you do not pass one, the SDK generates sdk-<32 hex characters> and returns it as result.idempotency_key. Resubmitting the same body with the same key returns the stored result instead of running the action twice, which makes retries after network errors safe. Pass your own key (for example derived from a job ID) when your agent may restart between attempts.
Reference
CydraGateway
CydraGateway(base_url: str, client_id: str, client_secret: str, *,
http: httpx.Client | None = None,
token_refresh_margin_seconds: int = 30)| Method | Behaviour |
|---|---|
request_action(*, tool, target, parameters=None, operation="invoke", on_behalf_of=None, justification=None, environment=None, idempotency_key=None, approval_token=None) | Submits to POST /api/v1/actions and returns an ActionResult. |
status(action_id) | Returns the gateway status as a dict: {"id", "state", "decision", "approval": {"status", "expires_at", "approval_token"}}. |
wait_for_approval(action_id, *, timeout_seconds=900, poll_seconds=3) | Polls until an approval token is available and returns it; raises ApprovalNotGranted or ApprovalTimeout. |
authorize_and_execute(*, tool, target, parameters=None, operation="invoke", on_behalf_of=None, justification=None, wait_for_approval_seconds=0, poll_seconds=3) | Submit, wait when required and allowed, resubmit with the token; raises ActionDenied on deny. |
close() | Closes the HTTP client, including one passed in through http=. Also called when a with block ends. |
agent_uri | The agent's URI (for example agent://acme/sales-agent), set after the initial token exchange. |
There is no timeout argument: the default client uses 15 seconds (5 to connect). To change timeouts, proxies or TLS settings, pass your own httpx.Client as http.
ActionResult
| Field | Meaning |
|---|---|
action_id | ID of the action request |
state | received, approval_required, approved, executed, failed, denied, rejected, expired, cancelled or revoked |
decision | allow, deny or require_approval |
reason_codes | Reason codes from the decision (and any rejection) |
approval_id / approval_expires_at | Set when approval is required |
output / execution_status | The redacted execution output and its status, once executed |
idempotency_key | The key used for the submission |
raw | The full response body |
executed / needs_approval | Convenience properties |
Errors
| Exception | Raised when | Useful attributes |
|---|---|---|
CydraError | Base class; also any API error response | code (the API error code), correlation_id |
AuthenticationError | The token exchange is refused (wrong secret, revoked identity) | code, correlation_id |
ActionDenied | authorize_and_execute received a deny | result (use result.reason_codes) |
ApprovalNotGranted | The approval was rejected, expired or cancelled, or the action ended | result |
ApprovalTimeout | No approval within timeout_seconds | result |
Network errors from httpx (timeouts, connection failures) are not wrapped. Because submissions are idempotent, it is safe to retry them with the same idempotency_key.
Good practice
- Create one
CydraGatewayper agent process and reuse it; it caches the token. - Treat every result as authoritative: run nothing yourself when the state is not
executed. The gateway runs the tool with a scoped credential. - Pass
justification: approvers see it, inspection checks it, and it is kept in the evidence chain. - Set
on_behalf_ofwhen the agent acts for a person who has delegated authority to it (recorded on the agent's Identities tab); without a matching delegation the gateway denies the action. - Log
exc.correlation_idwhen a call fails: it identifies the request in CydraLabs support and audit logs.
Applies to the CydraLabs proof-of-concept platform. Last updated 4 October 2026. Questions or corrections: contact us.