Conventions
These hold everywhere in the SDK.
Async-only
Section titled “Async-only”Every call is a coroutine; there is no synchronous twin. It drops into an async service without a thread pool in the middle.
In an async framework (FastAPI, Starlette, aiohttp) await it from the handler directly. In a
synchronous entry point, wrap it once with asyncio.run(main()).
Cancelling the task awaiting a call raises asyncio.CancelledError as usual. For chat() that
stops your waiting only — the turn keeps running server-side until you call cancel().
Every list_* reads all pages
Section titled “Every list_* reads all pages”A list_* method returns the whole collection, not the server’s first page — it keeps reading
until it has everything, adopting whatever page size the server honoured.
projects = await aura.list_projects() # all of themfiles = await aura.list_files(WorkspaceFiles(project_id=project_id)) # all of themThis avoids a class of silent bug: aura deploy prunes remote files by diffing a listing, so a
short read would delete files that are still there. The cost is that a large collection takes
several round-trips inside one await.
Everything returned is a pydantic model
Section titled “Everything returned is a pydantic model”Response models are generated from the backend’s OpenAPI spec and live in aura_sdk.models, so
they validate at the boundary: a response that does not match the contract fails there, loudly,
rather than surfacing as an AttributeError three calls later. The REST reference
documents what is inside each one; the API reference documents the methods that
return them.
They serialise back out cleanly, which is what you want when a response is on its way to your own API or a queue:
snapshots = await aura.list_snapshots(project_id)payload = [s.model_dump(mode="json") for s in snapshots]Enumerations are StrEnums, equal to their wire value. Where a method is typed with one, pass the
member so a type checker can follow:
from aura_sdk.models import SnapshotKind
solutions = await aura.list_snapshots(project_id, kind=SnapshotKind.SOLVE, has_solution=True)Four exception types, one base
Section titled “Four exception types, one base”AuraApiError (the call was rejected), AuraResponseError (the answer was unreadable),
AuraTurnCancelledError (a turn stopped early) and AuraTurnTimeoutError (your wait elapsed) all
inherit AuraError, so one except covers everything Aura says, and the specific types let you
branch without string matching. The one bare AuraError is upload_file refusing a root-level
workspace path, before any request. Errors has the detail.
Nothing answering is not among them. A host that cannot be reached, a TLS failure or an elapsed
timeout surface as httpx’s own exceptions — httpx.ConnectError, httpx.ReadTimeout, … all
httpx.TransportError — because those already name what went wrong. Catch them beside AuraError
where a network fault is a case you handle.
A deployment is named, a reference is a handle
Section titled “A deployment is named, a reference is a handle”Methods acting on a deployment (get_deployment, list_deployment_skills, …) take the bare
name — your key already says which company, and the REST path cannot carry a /. Methods pointing
at one from elsewhere (create_project’s from_deployment, checkout, pull_agent_config)
take the <company>/<name> handle, and pull_agent_config accepts either.
A list is filenames, a get is bodies
Section titled “A list is filenames, a get is bodies”list_project_skills and its deployment twin carry each skill’s name, description and filenames — enough to
say what is in force without shipping every byte of all of them. get_project_skill returns one
skill’s file contents.
Snapshots are the only container writes can’t reach
Section titled “Snapshots are the only container writes can’t reach”upload_file / delete_file write through the same FileContainer you already pass to
list_files / read_file: a workspace or deployment container accepts the write, a snapshot
container rejects it — a run’s captured state has to stay retrievable. Prefer asking the agent in
a turn to change a project’s inputs; reach for these methods directly for out-of-band writes, like
staging a dashboard file. See File containers for when to
use which container.
Two things a workspace write asks of you that a deployment write does not. The path must name a
directory (data/orders.csv, not orders.csv), which upload_file refuses locally rather than
letting the server explain it. And a project has an agent that may already have read the file, so
both methods take an optional conversation_id: pass it and the change announces itself, so the
agent re-reads on its next turn; omit it and the write is silent.
await aura.upload_file( WorkspaceFiles(project_id=project_id), "data/orders.csv", body, content_type_for_path("data/orders.csv"), conversation_id, # omit to write without telling the agent)upload_file sends one file in one request, up to 100 MiB. Between 100 and 250 MiB the server
plans a multipart upload the SDK does not implement, so the call raises AuraApiError with status
501 before any bytes move; above 250 MiB the server refuses the plan with 413.
deploy_from_folder has the same ceiling per file, excludes nothing by default (the server refuses
dot-prefixed paths such as .venv/), and only uploads — aura deploy is what
syncs.
content_type_for_path is exported so a single-file upload records the same type
deploy_from_folder would; zip_url(container, path=None) returns a single-use URL for the
container (or one subtree) as a zip — fetch it once, without your API key.
The *_instructions and *_skill methods are the one thing that writes without a turn, and they
are not an exception to this: agent config is stored outside the
workspace, which is exactly why the agent cannot edit the rules it operates under.
Reaching the rest of the API
Section titled “Reaching the rest of the API”The client wraps the run journey. A few operations are REST-only —
capturing a snapshot on demand, reading or deleting a tag, restarting the dashboard, renaming a
project or a conversation. Call them with the same key against aura.base_url; the request and
response shapes are in the REST reference.
import httpx
async with httpx.AsyncClient( base_url=aura.base_url, headers={"Authorization": f"Bearer {api_key}"}) as http: captured = await http.post( f"/api/v1/projects/{project_id}/snapshots", json={"label": "before the capacity change"} ) captured.raise_for_status() snapshot = captured.json()