Guides
Python
There is no Python MCP host yet, but the REST API is small and the policy-layer pattern fits any orchestration framework — LangChain, LlamaIndex, custom — equally well.
Prerequisites
- Python 3.10+.
requestsfor sync,httpxfor async. We’ll show both.AC_AGENT_KEYin env.
Sync — requests
ac_client.py
import os
import uuid
import requests
BASE = os.environ.get("AC_API_BASE", "https://api.advisorscrypto.com")
KEY = os.environ["AC_AGENT_KEY"]
def _call(method, path, body=None, idempotency_key=None):
headers = {
"Authorization": f"Bearer {KEY}",
"Content-Type": "application/json",
}
if idempotency_key:
headers["Idempotency-Key"] = idempotency_key
res = requests.request(
method,
f"{BASE}{path}",
headers=headers,
json=body,
timeout=20,
)
res.raise_for_status()
return res.json() if res.content else None
def get_mandate():
return _call("GET", "/api/agent/v1/mandate")
def validate(payload):
return _call("POST", "/api/agent/v1/policy/validate", body=payload)
def propose(payload):
return _call(
"POST",
"/api/agent/v1/trades/propose",
body=payload,
idempotency_key=str(uuid.uuid4()),
)Usage
python
from ac_client import get_mandate, validate, propose
mandate = get_mandate()
print("crypto sleeve %:", mandate["snapshot"]["cryptoPct"])
decision = validate({
"action": "buy",
"assetSymbol": "BTC",
"assetType": "crypto",
"amountUsd": 5000,
})
if not decision["allowed"]:
print("Blocked:", [v["rule"] for v in decision["violations"]])
else:
result = propose({
"action": "buy",
"assetSymbol": "BTC",
"assetType": "crypto",
"amountUsd": 5000,
"rationale": "Add a 1% nudge while crypto sleeve has headroom.",
})
print("Pending attestation #", result["serviceRequestId"])Async — httpx
ac_async.py
import os
import uuid
import httpx
BASE = os.environ.get("AC_API_BASE", "https://api.advisorscrypto.com")
KEY = os.environ["AC_AGENT_KEY"]
class AcClient:
"""Async REST client. Reuses one underlying httpx.AsyncClient."""
def __init__(self) -> None:
self._client = httpx.AsyncClient(
timeout=20,
headers={
"Authorization": f"Bearer {KEY}",
"Content-Type": "application/json",
},
)
async def close(self) -> None:
await self._client.aclose()
async def _call(self, method, path, body=None, idempotency_key=None):
headers = {}
if idempotency_key:
headers["Idempotency-Key"] = idempotency_key
res = await self._client.request(
method, f"{BASE}{path}", json=body, headers=headers,
)
res.raise_for_status()
return res.json() if res.content else None
async def validate(self, payload):
return await self._call("POST", "/api/agent/v1/policy/validate", body=payload)
async def propose(self, payload):
return await self._call(
"POST",
"/api/agent/v1/trades/propose",
body=payload,
idempotency_key=str(uuid.uuid4()),
)Patterns that pay off
- One client per process. Both libraries pool connections — instantiate once at startup, reuse for the lifetime of the process.
- Treat 4xx as data, 5xx as alarms. Policy blocks come back as 200 with
allowed: false; real 4xx errors mean malformed payloads. Pageable failures are 5xx. - Surface attestation status to the user. After a successful
propose, tell them the trade is pending attestation in the AC app — not that it happened.
Last updated 2026-06-15