Python SDK Reference
The curate-me Python package provides two main interfaces:
CurateGateway— configures OpenAI and Anthropic SDK clients to route through the gatewayGatewayAdmin— manages policies, secrets, API keys, and queries usage
Installation
pip install curate-meOptional dependencies for provider SDKs:
pip install curate-me openai # For OpenAI integration
pip install curate-me anthropic # For Anthropic integration
pip install curate-me openai anthropic # For bothThe SDK uses httpx for HTTP requests (included as a dependency).
CurateGateway
The gateway client wraps provider SDKs so they route through the Curate-Me gateway.
Constructor
from curate_me.gateway import CurateGateway
gw = CurateGateway(
api_key="cm_sk_your_key",
gateway_url="https://api.curate-me.ai", # default
timeout=60.0, # seconds, default
retry=RetryPolicy(max_retries=2), # optional
)| Parameter | Type | Default | Description |
|---|---|---|---|
api_key | str | required | Your Curate-Me API key (cm_sk_...) |
gateway_url | str | https://api.curate-me.ai | Base URL of the gateway |
timeout | float | 60.0 | Request timeout in seconds |
retry | RetryPolicy | 2 retries, exponential backoff | Retry policy |
openai()
Returns an openai.OpenAI client configured to proxy through the gateway.
client = gw.openai(provider_key="sk-your-openai-key")
# Or use a stored secret (no provider key needed)
client = gw.openai()
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello!"}],
)
print(response.choices[0].message.content)async_openai()
Returns an openai.AsyncOpenAI client for async usage.
client = gw.async_openai(provider_key="sk-your-openai-key")
response = await client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello!"}],
)anthropic()
Returns an anthropic.Anthropic client configured to proxy through the gateway.
client = gw.anthropic(provider_key="sk-ant-your-key")
# Or use a stored secret
client = gw.anthropic()
message = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello, Claude!"}],
)
print(message.content[0].text)async_anthropic()
Returns an anthropic.AsyncAnthropic client for async usage.
client = gw.async_anthropic(provider_key="sk-ant-your-key")
message = await client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello!"}],
)openai_config() / anthropic_config()
Returns a config dict you can spread into the SDK constructor for custom configuration:
config = gw.openai_config(provider_key="sk-your-key")
# config = {
# "api_key": "sk-your-key",
# "base_url": "https://api.curate-me.ai/v1",
# "default_headers": {"X-CM-API-Key": "cm_sk_your_key", "X-Provider-Key": "sk-your-key"},
# "timeout": 60.0,
# "max_retries": 2,
# }
from openai import OpenAI
client = OpenAI(**config)admin()
Returns a GatewayAdmin client for managing the gateway.
admin = gw.admin()GatewayAdmin
The admin client provides async methods for managing gateway resources.
Constructor
from curate_me.gateway import GatewayAdmin
admin = GatewayAdmin(
api_key="cm_sk_your_key",
gateway_url="https://api.curate-me.ai",
timeout=30.0,
retry=RetryPolicy(max_retries=2),
)Or create it from a CurateGateway instance:
admin = gw.admin()Policies
get_policies()
policies = await admin.get_policies()
# policies = {"org_id": "org_abc123", "rpm_limit": 60, "daily_budget": 25.0, ...}get_policy(org_id)
policy = await admin.get_policy("org_abc123")set_policy(org_id, policy)
Replace the governance policy for an org (PUT semantics):
await admin.set_policy("org_abc123", {
"rpm_limit": 100,
"daily_budget": 50.0,
"monthly_budget": 1000.0,
"max_cost_per_request": 2.0,
"pii_scan_enabled": True,
"pii_action": "block",
"allowed_models": ["gpt-4o-mini", "claude-haiku-3-5-20241022"],
"hitl_cost_threshold": 10.0,
})update_policy(policy)
Create or update a policy (POST semantics):
await admin.update_policy({
"org_id": "org_abc123",
"daily_budget": 100.0,
})delete_policy(org_id)
Delete a policy, reverting to tier defaults:
await admin.delete_policy("org_abc123")simulate_policy(draft_policy, replay_hours)
Simulate a draft policy against recent traffic:
result = await admin.simulate_policy(
draft_policy={"rpm_limit": 30, "daily_budget": 10.0},
replay_hours=24,
)
# Shows how many requests would have been blockedUsage and Costs
get_usage(days, limit, org_id)
usage = await admin.get_usage(days=7, limit=100)
# usage = {"records": [...], "total": 847}get_daily_costs(days, org_id)
costs = await admin.get_daily_costs(days=30)
# costs = {"days": [{"date": "2026-03-17", "total_cost": 42.15, ...}, ...]}get_usage_record(request_id)
record = await admin.get_usage_record("gw_a1b2c3d4")
# record = {"request_id": "gw_a1b2c3d4", "model": "gpt-4o", "actual_cost": 0.0031, ...}Latency
get_latency_stats()
stats = await admin.get_latency_stats()
# stats = {"providers": {"openai": {"p50_ms": 320, "p95_ms": 890, "p99_ms": 1450}}}API Keys
list_api_keys()
keys = await admin.list_api_keys()
# keys = {"keys": [{"key_id": "key_xyz", "name": "prod", "created_at": "..."}], "total": 3}create_api_key(name, scopes)
new_key = await admin.create_api_key(
name="staging-api",
scopes=["proxy"],
)
# new_key = {"key_id": "key_new123", "key": "cm_sk_abc...", "name": "staging-api"}
# The plaintext "key" field is shown only onceSecrets (stored provider keys)
store_secret(provider, key, label)
await admin.store_secret(
provider="openai",
key="sk-your-openai-key",
label="Production OpenAI",
)list_secrets(org_id)
secrets = await admin.list_secrets()
# secrets = {"secrets": [{"provider": "openai", "label": "Production OpenAI", ...}]}rotate_secret(provider, new_key)
await admin.rotate_secret("openai", "sk-new-openai-key")revoke_secret(provider)
await admin.revoke_secret("openai")Models
list_models()
List available models from all configured providers:
models = await admin.list_models()
# models = {"object": "list", "data": [{"id": "gpt-4o", "object": "model", ...}, ...]}Provider Targets
get_provider_targets(org_id)
targets = await admin.get_provider_targets()trigger_discovery(provider_id, api_key)
result = await admin.trigger_discovery("openai")
# result = {"provider_id": "openai", "models": ["gpt-4o", "gpt-4o-mini", ...], "total": 12}Discovery
get_discovery_catalog(provider_id, limit, cursor, org_id)
catalog = await admin.get_discovery_catalog(limit=50)get_discovery_history(provider_id, limit, cursor, org_id)
history = await admin.get_discovery_history(limit=10)HITL (Human-in-the-Loop)
wait_for_approval(approval_id, timeout)
Poll an approval until it is resolved or times out:
result = await admin.wait_for_approval("apr_abc123", timeout=300)
# result = {"approval_id": "apr_abc123", "status": "approved", ...}Raises TimeoutError if not resolved within the timeout.
Policy GitOps
validate_policy_bundle(bundle, strict)
result = await admin.validate_policy_bundle(bundle=my_policy_bundle, strict=True)export_policy_bundle(org_id, version)
bundle = await admin.export_policy_bundle("org_abc123")import_policy_bundle(org_id, bundle, dry_run, merge_strategy)
result = await admin.import_policy_bundle(
"org_abc123",
bundle=my_policy_bundle,
dry_run=True,
merge_strategy="replace",
)diff_policy(org_id, draft_policy)
diff = await admin.diff_policy("org_abc123", draft_policy={"rpm_limit": 30})list_policy_versions(org_id, limit)
versions = await admin.list_policy_versions("org_abc123", limit=10)rollback_policy(org_id, target_version, reason)
await admin.rollback_policy("org_abc123", target_version="v3", reason="Reverted budget change")RetryPolicy
Configure retry behavior for transient failures:
from curate_me.gateway import RetryPolicy
policy = RetryPolicy(
max_retries=3, # default: 2
initial_delay=1.0, # seconds, default: 0.5
max_delay=16.0, # seconds, default: 8.0
backoff_factor=2.0, # default: 2.0
retryable_status_codes={429, 500, 502, 503, 504}, # defaults
)Error handling
The SDK raises typed exceptions for gateway errors:
from curate_me.gateway import (
GatewayError,
GatewayAuthenticationError,
GatewayGovernanceError,
GatewayRateLimitError,
)
try:
usage = await admin.get_usage()
except GatewayAuthenticationError as e:
print(f"Bad API key: {e}")
print(f"Request ID: {e.request_id}")
except GatewayGovernanceError as e:
print(f"Blocked by policy: {e}")
except GatewayRateLimitError as e:
print(f"Rate limited. Retry after: {e.retry_after}s")
except GatewayError as e:
print(f"Gateway error [{e.status_code}]: {e}")| Exception | Status | When |
|---|---|---|
GatewayAuthenticationError | 401 | Invalid or missing API key |
GatewayGovernanceError | 403 | Blocked by governance policy |
GatewayRateLimitError | 429 | Rate limit exceeded |
GatewayError | any | All other gateway errors |
Streaming error recovery
The SDK includes resilient streaming helpers that automatically retry on mid-stream failures:
from curate_me import resilient_openai_stream, StreamRetryConfig
client = gw.openai(provider_key="sk-your-key")
async for event in resilient_openai_stream(
client.chat.completions.create,
model="gpt-4o",
messages=[{"role": "user", "content": "Tell me a long story"}],
stream=True,
retry_config=StreamRetryConfig(max_retries=3),
):
if event.type == "content":
print(event.content, end="")
elif event.type == "error":
print(f"\nStream error (retrying): {event.error}")Complete example
import asyncio
from curate_me.gateway import CurateGateway
async def main():
gw = CurateGateway(api_key="cm_sk_your_key")
# 1. Store your OpenAI key (one-time setup)
admin = gw.admin()
await admin.store_secret("openai", "sk-your-openai-key", label="Production")
# 2. Set governance policy
await admin.set_policy("org_abc123", {
"rpm_limit": 100,
"daily_budget": 50.0,
"max_cost_per_request": 2.0,
"pii_scan_enabled": True,
"allowed_models": ["gpt-4o", "gpt-4o-mini"],
"hitl_cost_threshold": 10.0,
})
# 3. Make governed requests (no provider key needed -- stored secret)
client = gw.openai()
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Summarize this document..."}],
)
print(response.choices[0].message.content)
# 4. Check costs
costs = await admin.get_daily_costs(days=7)
for day in costs.get("days", []):
print(f"{day['date']}: ${day['total_cost']:.2f}")
asyncio.run(main())