Python SDK
The official Python client. Async-first, typed, and shipped on PyPI as heyarchie. The core client is hand-written; the per-skill surface is generated from the canonical skill registry, so it tracks the live catalogue. Read the source if you want to know how something works.
Install
python3 -m venv .venv
source .venv/bin/activate
pip install heyarchiePython 3.10 or newer. The package has zero runtime dependencies beyond httpx and pydantic.
Authenticate
Pass api_key or set ARCHIE_API_KEY in the environment. Tokens start with sk_.
from heyarchie import Archie
archie = Archie(api_key="sk_...")
# or, with env var:
archie = Archie()Typed skill methods
Every public skill is a typed method on client.skills — 65 of them, one per skill, generated from the canonical skill registry (the same source that drives the skill catalogue). Required input fields are required keyword arguments; optional fields default to None. Each method delegates to skills.invoke(), so the wire format is unchanged.
Version note: typed methods ship in heyarchie 1.4.0. PyPI currently serves 1.3.0, which has the generic skills.invoke() only. Until 1.4.0 publishes, get the typed surface from a repo checkout: pip install -e apps/sdk-python.
from heyarchie import Archie
client = Archie()
# List topics — typed kwargs, no JSON body to assemble
topics = client.skills.topic_list(limit=5)
# Create one
client.skills.topic_create(name="FY26 audit — Acme", type="client")
# Advisory skills are typed too
advice = client.skills.advise_tax(
topic="CGT discount on an active asset",
jurisdiction="AU",
)The async client gets the same 65 methods, awaitable:
from heyarchie import AsyncArchie
async with AsyncArchie() as archie:
topics = await archie.skills.topic_list(limit=5)Extra keyword arguments pass through to the wire untouched, so a field added server-side is usable before the SDK regenerates. The generic skills.invoke(slug, **inputs) remains the fallback for any skill by slug — and the only path on 1.3.0.
Async usage
Use AsyncArchie when you're inside an event loop. Same API, awaitable methods.
from heyarchie import AsyncArchie
async def main():
async with AsyncArchie() as archie:
reply = await archie.skills.invoke(
"ask_archie",
query="What does ASC 842 require for a finance lease?",
)
return reply.contentResources
The client mirrors the REST surface. Each resource is a property on the client.
archie.skills: invoke any skill in the registry — genericinvoke()plus one typed method per skill (1.4.0)archie.messages: pretty alias for the ask_archie skillarchie.search: knowledge-base searcharchie.conversations: list and update conversationsarchie.drafts: communication draftingarchie.workstreams: list, run, observe workstream cyclesarchie.documents: analyse uploaded documentsarchie.standards: compare against AASB, FRS, IFRS, ASCarchie.reports: export reports, fetch metricsarchie.keys: manage your own API keys
Managing API keys
Full CRUD on /api/v1/connect-keys/* — every operation you can do in the portal is also reachable from client.keys. heyarchie 1.2.0+. See Managing keys for the full walkthrough.
from heyarchie import Archie
client = Archie()
# Mint a new key with per-key knobs at create time.
key = client.keys.create(
name="ci-deploys",
permissions=["ask:execute", "search:read"],
rate_limit_per_minute=30,
description="staging deploy bot",
)
print(key.value) # one-time sk_… — store immediately
# Edit metadata without rotating. `permissions` updates return 501;
# use `rotate` for permission changes.
client.keys.update(
key.id,
workspace_id="b0000000-0000-0000-0000-000000000001",
description="staging deploy bot — updated",
)
# Mint a replacement, parent dies after grace_period_hours.
new_key = client.keys.rotate(key.id, grace_period_hours=1)
print(new_key.rotated_from_key_id) # = old key.id
# Idempotent revoke. Subsequent requests through the key 401.
client.keys.revoke(key.id)
# List + single-key read return the merged shape (WorkOS canonical
# + mirror canonical fields like workspace_id, rate_limit_per_minute,
# description, environment, revoked_at).
page = client.keys.list()
for k in page.data:
print(k.id, k.obfuscated_value, k.workspace_id)Framework adapters
One line to use Archie skills as native tools in LangGraph, LangChain, or the OpenAI Agents SDK. The adapter fetches the live registry at call time so your tool list stays in sync as the skill catalogue evolves.
LangGraph / LangChain
pip install heyarchie langchain-core langgraphfrom heyarchie.adapters.langgraph import as_langgraph_tools
from langchain_anthropic import ChatAnthropic
# Standalone function
tools = as_langgraph_tools(api_key="sk_...", scope="ask:execute")
model = ChatAnthropic(model="claude-sonnet-4-6").bind_tools(tools)
# Or via the client
archie = Archie(api_key="sk_...")
tools = archie.skills.as_langgraph_tools(scope="ask:execute")OpenAI Agents SDK
pip install heyarchie openai-agentsfrom heyarchie.adapters.openai_agents import as_openai_tools
from agents import Agent, Runner
tools = as_openai_tools(api_key="sk_...", scope="ask:execute")
agent = Agent(
name="Archie Tax Advisor",
instructions="Expert accounting assistant.",
tools=tools,
)
result = Runner.run_sync(agent, "What is the BAS for Q1 2026?")
print(result.final_output)The scope= parameter filters to skills whose permission prefix matches — the same vocabulary as MCP scopes and restricted API keys. Pass client_id= to set a default client context for all tool calls.
Skill discovery
Before picking a skill, call skills.discover() to survey the catalogue. Agents should use this instead of hard-coding slug names that can change as the registry evolves.
ranked = archie.skills.discover(goal="prepare Q1 financial statements")
best = ranked["ranked"][0] # {"name": "reports.financial.prepare", "score": 0.95, ...}
# Invoke the top match
result = archie.skills.invoke(best["name"], period="2026-Q1")
# Filter by routing cluster (returns all skills in cluster, no scoring)
tax_skills = archie.skills.discover(cluster="TAX")
# Async variant
ranked = await async_archie.skills.discover(goal="reconcile bank accounts")Two-tier dispatch
Skills route through a two-tier model. A general-intent classifier picks a primary cluster (TAX, ASR, CMP, WLT, BOP, XCT) from your query, then a specialist resolves the exact skill. The cluster is exposed as x-skill-cluster in the OpenAPI spec and on every reply, so you can route follow-ups to the same cluster for continuity. Skills are reachable at POST /api/v1/skills/{slug}/invoke; for chat-style flows, the ask_archie skill is the first-class entry point that routes through the orchestrator and accepts free-form queries.
Streaming
Long replies stream back as Server-Sent Events. Use skills.invoke_stream() instead of skills.invoke() to get an async generator of SkillStreamChunk objects. Each chunk has a delta string (or None for the terminal event).
from heyarchie import AsyncArchie
async with AsyncArchie() as archie:
async for chunk in archie.skills.invoke_stream(
"ask_archie",
query="Walk me through ASC 842 lease accounting.",
):
if chunk.delta:
print(chunk.delta, end="", flush=True)The sync client also has skills.invoke_stream(). It returns a regular generator, not an async generator.
Errors
The client raises typed exceptions per the OpenAPI error envelope:
from heyarchie import (
ArchieAuthError, # 401, invalid or revoked key
ArchiePermissionError, # 403, key lacks permission
ArchieRateLimitError, # 429, includes retry_after_seconds
ArchieNotFoundError, # 404
ArchieAPIError, # 5xx
)
try:
reply = archie.skills.invoke("ask_archie", query="...")
except ArchieRateLimitError as e:
time.sleep(e.retry_after_seconds)
except ArchieAPIError as e:
log.exception("archie call failed", extra={"request_id": e.request_id})Sandbox fixture for CI
We ship a deterministic sandbox harness so your tests don't hit production. Any test key (sk_ issued in a Test workspace) is routed against the seeded magic-data org.
from heyarchie.testing import sandbox_archie
def test_revenue_recognition_flow():
with sandbox_archie() as archie:
reply = archie.skills.invoke(
"ask_archie",
query="When is revenue recognized for a 30-day return policy?",
)
assert any(c.standard == "ASC 606" for c in reply.citations)Reference
Full type signatures are checked into apps/sdk-python in the public repo. The REST reference lists every endpoint the SDK calls under the hood.