Skip to content

AuraClient

Async client for the Strangeworks Aura public /api/v1 surface.

AuraClient(*, api_key: str, base_url: str | None = None, region: Region | None = None, timeout: float = 30.0, transport: httpx.AsyncBaseTransport | None = None, websocket_factory: WebSocketFactory = default_websocket_factory)

Authenticates with a company API key (sk-aura-…). Every method is a coroutine; there is no synchronous variant. chat follows a turn live over the project WebSocket and falls back to polling when the socket cannot be held open, hiding both behind a single await.

Owns HTTP connections, so use it as an async context manager, or call aclose:

async with AuraClient(api_key=key) as aura:
project = await aura.create_project(name="demo", from_deployment="acme/vrp")

All arguments are keyword-only.

Parameter Type Default Description
api_key str A company API key, minted under Settings → API Keys. A key belongs to the region whose app you minted it in.
base_url str | None None Root of the Aura deployment, for a host no region names. Wins over region.
region Region | None None Which deployment to reach, as a shorthand for its base_url. Defaults to "eu".
timeout float DEFAULT_TIMEOUT Per-request HTTP timeout in seconds. This bounds one call, not a whole agent turn — see chat’s own timeout. Elapsing raises httpx’s timeout error, not an AuraError.
transport httpx.AsyncBaseTransport | None None Optional httpx transport — an httpx.MockTransport in tests, or a wrapper that logs or retries. Byte uploads to storage go through it too.
websocket_factory WebSocketFactory default_websocket_factory Opens the project event socket; tests pass a scripted fake.
Property Type Default Description
base_url (read-only) str The Aura origin this client talks to, without a trailing slash.
Area Methods
Lifecycle aclose
Deployments create_deployment, list_deployments, get_deployment, delete_deployment, check_deployment
Agent instructions and skills get_deployment_instructions, set_deployment_instructions, delete_deployment_instructions, list_deployment_skills, set_deployment_skill, get_deployment_skill, delete_deployment_skill, get_project_instructions, set_project_instructions, delete_project_instructions, list_project_skills, get_project_skill, set_project_skill, delete_project_skill, pull_agent_config
Files upload_file, list_files, read_file, delete_file, zip_url
Projects create_project, list_projects, get_project, delete_project
Conversations list_conversations, create_conversation, delete_conversation
Chat turns send_message, get_conversation_state, chat, cancel, get_agent_status
Run dashboard create_dashboard_ticket, dashboard_url
Solve executions list_executions, get_execution
Snapshots and tags list_snapshots, get_snapshot, rename_snapshot, list_tags, set_tag
Workspace get_workspace, checkout
Usage get_usage, list_user_usage, list_api_key_usage
async def aclose() -> None

Close the underlying HTTP connections.

async def create_deployment(name: str) -> DeploymentResponse

Create an empty deployment for your company.

Parameter Type Default Description
name str Deployment name, unique within the company.

The created deployment.

Exception When
AuraApiError The name is invalid or already taken.
async def list_deployments() -> list[DeploymentResponse]

List every deployment your company owns, following pagination.

All deployments, in server order.

async def get_deployment(name: str) -> DeploymentResponse

Fetch one deployment by name.

Parameter Type Default Description
name str The deployment’s name.

The deployment.

Exception When
AuraApiError No such deployment.
async def delete_deployment(name: str) -> DeploymentDeleteResponse

Delete a deployment: purge its files and free its name for reuse.

Irreversible — there is no undelete, and a new deployment claiming the same name starts empty. Projects already seeded from this deployment keep their own copy of the workspace and are left alone.

Requires an admin, which every API key satisfies.

Parameter Type Default Description
name str The deployment’s name.

The handle, how many files were purged, and how many projects had been seeded from it.

Exception When
AuraApiError No such deployment.
async def check_deployment(name: str) -> DeploymentCheckResponse

Ask the server whether a deployment satisfies the run contract.

Parameter Type Default Description
name str The deployment’s name.

ok, plus the missing paths and warnings behind that verdict.

async def get_deployment_instructions(name: str) -> DeploymentAgentInstructionsResponse

Fetch the operating instructions a deployment ships.

Parameter Type Default Description
name str The deployment’s name.

The instruction text, with content set to None when the deployment ships none — distinct from the 404 below, which means no such deployment.

Exception When
AuraApiError No such deployment.
async def set_deployment_instructions(name: str, content: str) -> AgentInstructionsResponse

Replace the operating instructions a deployment ships.

Projects already seeded from this deployment keep the copy they were created with; only projects created afterwards get the new text at seed time.

Parameter Type Default Description
name str The deployment’s name.
content str The instruction text.

What was stored.

Exception When
AuraApiError No such deployment, or the content is empty or over 64 KiB.
async def delete_deployment_instructions(name: str) -> None

Remove a deployment’s operating instructions. Idempotent.

Parameter Type Default Description
name str The deployment’s name.
async def list_deployment_skills(name: str) -> list[SkillSummary]

List the customer skills a deployment ships.

Parameter Type Default Description
name str The deployment’s name.

One summary per skill, each with its description and file list.

Exception When
AuraApiError No such deployment.
async def set_deployment_skill(name: str, skill: str, files: Mapping[str, str]) -> SkillSummary

Create or replace one customer skill.

Replaces the skill’s whole file set rather than merging, so a file dropped locally does not linger server-side.

Parameter Type Default Description
name str The deployment’s name.
skill str The skill’s directory name, which is what the model invokes. Aura’s own skill names are reserved.
files Mapping[str, str] Bodies keyed by path relative to the skill’s directory. Must include SKILL.md, whose frontmatter needs a name matching skill and a non-empty description. At most 128 KiB per file and 256 KiB per skill.

The stored skill’s summary.

Exception When
AuraApiError No such deployment, or the skill failed validation.
async def get_deployment_skill(name: str, skill: str) -> SkillDetail

Fetch one skill in full, file bodies included.

Separate from list_deployment_skills, which carries filenames only — describing twenty skills should not ship every byte of all of them.

Parameter Type Default Description
name str The deployment’s name.
skill str The skill’s directory name.

The skill’s name, description, and every file’s contents.

Exception When
AuraApiError No such deployment, or it ships no such skill.
async def delete_deployment_skill(name: str, skill: str) -> None

Remove one customer skill and all its files. Idempotent.

Parameter Type Default Description
name str The deployment’s name.
skill str The skill’s directory name.
async def get_project_instructions(project_id: str) -> ProjectAgentInstructionsResponse

Fetch both instruction blocks in force for a project.

Parameter Type Default Description
project_id str The project’s id.

The deployment block and the project block, either of which may be None, and pulled_from, the deployment the former came from.

async def set_project_instructions(project_id: str, content: str) -> AgentInstructionsResponse

Set a project’s own instructions, layered onto anything pulled from its deployment.

Parameter Type Default Description
project_id str The project’s id.
content str The instruction text.

What was stored.

Exception When
AuraApiError No such project, or the content is empty or over 64 KiB.
async def delete_project_instructions(project_id: str) -> None

Remove every instruction block a project holds. Idempotent.

Both scopes, since both are the project’s — never the deployment’s original. Siblings are unaffected, and the project gets the rules back with pull_agent_config.

Parameter Type Default Description
project_id str The project’s id.
async def list_project_skills(project_id: str) -> ProjectSkillListResponse

List the customer skills in force for a project.

Parameter Type Default Description
project_id str The project’s id.

The skills in the deployment scope and the project scope, and pulled_from, the deployment the former came from. A project skill sharing a deployment skill’s name shadows it.

async def get_project_skill(project_id: str, skill: str) -> ProjectSkillDetail

Fetch one of a project’s skills in full, with the scope in force for it.

The project’s own copy when it has one, since that is the copy a turn mounts — the shadowed deployment one would describe rules that do not apply.

Parameter Type Default Description
project_id str The project’s id.
skill str The skill’s directory name.

The skill’s contents plus scope ("deployment" or "project").

Exception When
AuraApiError No such project, or no such skill is in force for it.
async def set_project_skill(project_id: str, skill: str, files: Mapping[str, str]) -> SkillSummary

Create or replace one of a project’s own skills.

Naming it after one of the deployment’s skills shadows it for this project alone, leaving the deployment untouched. Replaces the skill’s whole file set rather than merging, so a file dropped locally does not linger server-side.

Parameter Type Default Description
project_id str The project’s id.
skill str The skill’s directory name, which is what the model invokes. Aura’s own skill names are reserved.
files Mapping[str, str] Bodies keyed by path relative to the skill’s directory. Must include SKILL.md, whose frontmatter needs a name matching skill and a non-empty description. At most 128 KiB per file and 256 KiB per skill.

The stored skill’s summary.

Exception When
AuraApiError No such project, or the skill failed validation.
async def delete_project_skill(project_id: str, skill: str) -> None

Remove a skill from a project, whichever scope holds it. Idempotent.

Never the deployment’s copy, so siblings are unaffected and the project gets the skill back with pull_agent_config.

Parameter Type Default Description
project_id str The project’s id.
skill str The skill’s directory name.
async def pull_agent_config(project_id: str, from_deployment: str, *, instructions: bool = True, skills: bool = True) -> PullConfigResponse

Pull a deployment’s instructions and/or skills into a project.

The non-destructive half of a checkout: the project takes the operator’s current rules without checkout overwriting work in flight. Replaces the project’s deployment scope rather than merging into it, so a rule the deployment retired stops applying; the project’s own scope is untouched.

Parameter Type Default Description
project_id str The project’s id.
from_deployment str The deployment to pull from, as a bare name (vrp) or a <company>/<name> handle (acme/vrp).
instructions bool True Pull the deployment’s AGENTS.md.
skills bool True Pull the deployment’s skills.

The deployment handle pulled from, and how many objects landed.

Exception When
AuraApiError No such project or deployment.
async def upload_file(container: FileContainer, path: str, body: bytes | str, content_type: str, conversation_id: str | None = None) -> FileEntry

Upload one file to a container.

Three steps behind one await: plan the upload, PUT the bytes to the URL the plan names, then complete it. The byte PUT carries only the plan’s own headers, never your API key.

One request, one file, up to 100 MiB. Between 100 and 250 MiB the server plans a multipart upload, which this SDK does not implement, so the call raises instead; above 250 MiB the server refuses the plan outright.

Parameter Type Default Description
container FileContainer Which files to write to. A SnapshotFiles is rejected server-side; a snapshot is immutable by design.
path str Destination path within the container, e.g. run/solve.py. A WorkspaceFiles destination must name a directory (data/orders.csv, not orders.csv).
body bytes | str File contents. A str is encoded as UTF-8.
content_type str MIME type recorded for the file.
conversation_id str | None None Announce the write on this conversation, so the project’s agent re-reads the file on its next turn instead of trusting what it read earlier. Workspace only. Refused with a 409 while a turn is in flight, since a notice written mid-turn would never be seen; omit it to write silently.

The stored file’s entry.

Exception When
AuraError path is at the workspace root — raised before any request.
AuraApiError The plan, the byte upload, or the completion failed, or the file is over 100 MiB (501, raised before any bytes move) or over 250 MiB (413).
async def list_files(container: FileContainer, path_prefix: str | None = None) -> list[FileEntry]

List a container’s files, following pagination.

Parameter Type Default Description
container FileContainer Which files to list.
path_prefix str | None None Restrict the listing to paths under this prefix.

All matching file entries.

async def read_file(container: FileContainer, path: str) -> FileContentResponse

Read one file from a container.

Parameter Type Default Description
container FileContainer Which files to read from. SnapshotFiles reads a run’s frozen copy, which later runs cannot have overwritten.
path str The file’s path, e.g. solutions/out.json for a run’s solution.

The file’s contents: text for text files, base64 for binary ones.

Exception When
AuraApiError No such file in that container.
async def delete_file(container: FileContainer, path: str, conversation_id: str | None = None) -> DeleteResponse

Delete one file from a container.

Not recursive — pass a file path, not a directory.

Parameter Type Default Description
container FileContainer Which files to delete from. A SnapshotFiles is rejected server-side; a snapshot is immutable by design.
path str The file’s path within the container.
conversation_id str | None None Announce the deletion on this conversation; see upload_file.

What the server deleted.

async def zip_url(container: FileContainer, path: str | None = None) -> str

A URL serving a container as a zip.

Returns the URL rather than the bytes because that is what the API hands back — always the tokenized /raw leg, since a zip is assembled on demand and cannot be presigned. The token is single-use and is the credential, so fetch it without your API key, and only once.

Parameter Type Default Description
container FileContainer Which files to zip.
path str | None None Restrict the zip to this subtree; the whole container by default.

An absolute URL to fetch the zip from.

async def create_project(name: str, *, from_deployment: str | None = None, short_description: str | None = None, description: str | None = None) -> ProjectResponse

Create a project, optionally seeded from a published deployment.

A project seeded from a deployment lands directly in run mode, so the run agent is usable on it immediately.

Parameter Type Default Description
name str Human-readable project name.
from_deployment str | None None A deployment handle, "<company>/<name>". The wire field is from, which is a reserved word in Python.
short_description str | None None One-line summary.
description str | None None Longer description.

The created project.

Exception When
AuraApiError The deployment handle is unknown or fails its run contract.
async def list_projects() -> list[ProjectSummary]

List your projects, following pagination.

A summary of every project you can see.

async def get_project(project_id: str) -> ProjectResponse

Fetch one project.

Parameter Type Default Description
project_id str The project’s id.

The project.

Exception When
AuraApiError No such project, or it is not yours.
async def delete_project(project_id: str) -> None

Delete a project and everything in it.

Parameter Type Default Description
project_id str The project’s id.
Exception When
AuraApiError No such project, or it is not yours.
async def list_conversations(project_id: str) -> list[ConversationResponse]

List a project’s conversations, following pagination.

Parameter Type Default Description
project_id str The project’s id.

All conversations in the project.

async def create_conversation(project_id: str, title: str | None = None) -> ConversationResponse

Start a new conversation in a project.

Parameter Type Default Description
project_id str The project’s id.
title str | None None Optional title; the server names it otherwise.

The created conversation.

async def delete_conversation(project_id: str, conversation_id: str) -> None

Delete one conversation.

Parameter Type Default Description
project_id str The project’s id.
conversation_id str The conversation’s id.
Exception When
AuraApiError No such conversation in that project.
async def send_message(project_id: str, content: str, *, conversation_id: str | None = None, client_msg_id: str | None = None) -> ChatResponse

Dispatch a message without waiting for the agent’s reply.

Use this when you want to poll get_conversation_state yourself; chat is the one-await version.

Parameter Type Default Description
project_id str The project’s id.
content str The message text.
conversation_id str | None None Target conversation. Defaults to the project’s active one.
client_msg_id str | None None Your own idempotency key for the dispatch. Resending one with the same content returns the stored message with turn_id None and starts nothing; with different content the server answers 409.

The dispatch result, including the stored user message and its sequence.

async def get_conversation_state(project_id: str, conversation_id: str | None = None) -> ConversationSyncResponse

Read an atomic snapshot of a conversation: its messages plus turn state.

Parameter Type Default Description
project_id str The project’s id.
conversation_id str | None None Which conversation. Defaults to the project’s active one at the time of the call.

The messages the server has stored and whether a turn is in flight.

async def chat(project_id: str, content: str, *, conversation_id: str | None = None, client_msg_id: str | None = None, poll_interval: float = 1.0, timeout: float = 900.0, quiet_probe: float = 30.0, on_delta: Callable[[str], None] | None = None, on_tool_activity: Callable[[ToolActivityData], None] | None = None) -> ChatMessageResponse

Send a message and wait for the agent’s reply, following the turn live.

The turn is followed over the project WebSocket, so the wait is event-driven — turn-end comes from the authoritative stream_end rather than from polling — and on_delta / on_tool_activity see the turn as it happens. When the socket cannot be opened, or dies mid-turn, this degrades silently to polling /chat/sync: the reply is unaffected, only the callbacks stop.

The returned message always comes from a /chat/sync reconcile, never from the accumulated deltas. Deltas carry no sequence numbers, so a dropped frame is undetectable; the stream is liveness and the stored row is truth.

On the polling path the reply is the first assistant message whose sequence exceeds the dispatched one, accepted only once the turn reads idle and the message is unchanged across two reads. Comparing sequences rather than counting messages is what makes the wait attributable to this caller, and immune to the newest-N window /chat/sync returns.

The wait is pinned to the conversation the dispatch resolved to, so creating or activating another conversation mid-turn cannot redirect it.

A socket that dies announces itself, but one that goes half-open does not; it simply stops delivering, and a turn’s terminal event with it. So the wait never concludes anything from silence: whenever the stream has been quiet for quiet_probe, and again before the deadline is allowed to expire, the turn’s state is read back from the server. A turn that has finished is returned, however its stream_end was lost.

Cancelling the task awaiting this coroutine raises asyncio.CancelledError as usual and stops only the waiting — the turn keeps running server-side. Call cancel to stop the turn itself.

Parameter Type Default Description
project_id str The project’s id.
content str The message text.
conversation_id str | None None Target conversation. Defaults to the project’s active one.
client_msg_id str | None None Your own idempotency key for the dispatch; see send_message.
poll_interval float 1.0 Seconds between polls, on the fallback path.
timeout float 900.0 Seconds to wait before giving up.
quiet_probe float 30.0 Seconds the stream may go quiet before the turn’s state is read back from the server rather than waited on, floored at 1.0s. Lower it to notice a half-open socket sooner, at the cost of a /chat/sync read per interval during a long tool call.
on_delta Callable[[str], None] | None None Called with each chunk of assistant text, in order.
on_tool_activity Callable[[ToolActivityData], None] | None None Called for each tool start, completion, or failure.

The agent’s completed reply.

Exception When
AuraTurnCancelledError The turn stopped before finishing, so the reply is partial. partial_content carries what the agent managed.
AuraTurnTimeoutError timeout elapsed. The turn is still running server-side; conversation_id is what to pass to cancel.
AuraApiError A call failed, or the turn died before streaming.
AuraResponseError The turn finished but its reply was not in the sync window.
async def cancel(project_id: str, conversation_id: str | None = None) -> bool

Stop a running agent turn.

Parameter Type Default Description
project_id str The project’s id.
conversation_id str | None None Restrict cancellation to this conversation. Without it, any running turn in the project is cancelled.

Whether anything was actually cancelled.

async def get_agent_status(project_id: str) -> AgentStatusResponse

Report whether a turn is in flight, without inferring it from messages.

Parameter Type Default Description
project_id str The project’s id.

The project’s current agent status.

async def create_dashboard_ticket(project_id: str) -> DashboardTicketResponse

Mint a short-lived view ticket for the project’s run dashboard.

The dashboard is a live web app in the project’s sandbox. The ticket stands in for your API key in a place a key must never go — a URL a browser opens, or an <iframe src>. It is scoped to this one project’s dashboard, is read-only, and expires after 12 hours; mint another when it does.

Parameter Type Default Description
project_id str The project’s id. Its workspace must be in run mode.

The ticket, when it expires, and the path to open. The absolute URL is f"{aura.base_url}{ticket.path}" — the path is returned relative so the response never has to guess which hostname you reached.

Exception When
AuraApiError 409 if the project has no run dashboard yet.
async def dashboard_url(project_id: str) -> str

The absolute URL of the project’s run dashboard, ticket included.

A one-call shorthand for create_dashboard_ticket when all you want is somewhere to point a browser. Use the ticket itself when you need to know when it expires.

Parameter Type Default Description
project_id str The project’s id. Its workspace must be in run mode.

A URL that opens the dashboard until the ticket expires.

async def list_executions(project_id: str) -> list[ExecutionResponse]

List the project’s solve executions, most recent first.

Parameter Type Default Description
project_id str The project’s id.

Every execution, following pagination.

async def get_execution(project_id: str, execution_id: str) -> ExecutionResponse

Fetch one solve execution.

Parameter Type Default Description
project_id str The project’s id.
execution_id str The execution’s id.

The execution, including its status and timing.

async def list_snapshots(project_id: str, *, kind: SnapshotKind | None = None, conversation_id: str | None = None, has_solution: bool | None = None, has_label: bool | None = None) -> list[SnapshotResponse]

List a project’s snapshots, newest first, following pagination.

Filters are applied server-side and combine (AND); the booleans are tri-state, where None does not filter and False asks for the rows whose column is unset. kind=SnapshotKind.SOLVE, has_solution=True is the “give me the solutions” query.

Parameter Type Default Description
project_id str The project’s id.
kind SnapshotKind | None None Only snapshots of this kind — a completed solve or a manual capture.
conversation_id str | None None Only snapshots captured from this conversation thread.
has_solution bool | None None Only snapshots that froze a solution.
has_label bool | None None Only labeled snapshots. A label also pins retention.

Every matching snapshot in the project.

async def get_snapshot(project_id: str, ref: str) -> SnapshotDetailResponse

Fetch one snapshot, including its file list.

Parameter Type Default Description
project_id str The project’s id.
ref str A snapshot id or a tag name.

The snapshot and its files.

async def rename_snapshot(project_id: str, snapshot_id: str, label: str | None) -> SnapshotResponse

Set, change, or clear a snapshot’s label.

A label is a snapshot’s display title — free text, non-unique. It is not a ref; to fetch a snapshot by name, tag it (set_tag) instead.

Parameter Type Default Description
project_id str The project’s id.
snapshot_id str The snapshot’s id.
label str | None The new label, or None to clear it (which also unpins the snapshot from retention pruning, unless it also carries a tag).

The snapshot as stored.

async def list_tags(project_id: str) -> list[SnapshotTagResponse]

List a project’s snapshot tags, following pagination.

Parameter Type Default Description
project_id str The project’s id.

Every tag and the snapshot it points at.

async def set_tag(project_id: str, name: str, snapshot_id: str) -> SnapshotTagResponse

Point a tag at a snapshot, creating or moving it.

Parameter Type Default Description
project_id str The project’s id.
name str The tag name.
snapshot_id str The snapshot the tag should name.

The tag as stored.

async def get_workspace(project_id: str) -> WorkspaceStateResponse

Read what the workspace is on, and whether a checkout is rewriting it.

checkout_in_flight comes from the lock a running checkout holds, so it is a level to poll rather than an event to have received — worth reading before issuing a checkout of your own, which the server refuses with a 409 while another one holds the workspace.

Parameter Type Default Description
project_id str The project’s id.

The workspace’s basis and whether a checkout is in flight.

Exception When
AuraApiError The project has no run workspace yet.
async def checkout(project_id: str, source: str, conversation_id: str | None = None) -> WorkspaceBasisResponse

Reset the project’s workspace to a deployment or snapshot.

The server dispatches on source’s segment count: one segment is a snapshot id or tag name, two is a <group>/<name> deployment handle, three is a <group>/<project>/<name> in-project snapshot tag.

Parameter Type Default Description
project_id str The project’s id.
source str What to check out, as described above.
conversation_id str | None None Conversation to attribute the checkout to.

The workspace’s new basis.

Exception When
AuraApiError source does not resolve to anything checkout-able, a turn is in flight, or another checkout or snapshot already holds the workspace (409) — a checkout clears the tree before it copies, so two of them interleaved would mix both sources.
async def get_usage(*, since: datetime | None = None, until: datetime | None = None) -> CompanyUsageResponse

Company-wide usage in credits.

Parameter Type Default Description
since datetime | None None Include events at or after this moment. A naive datetime is read as UTC.
until datetime | None None Include events strictly before this moment. A naive datetime is read as UTC.

The totals, with the slice attributable to no user or key broken out as unattributed.

Exception When
AuraApiError until is not after since.
async def list_user_usage(*, since: datetime | None = None, until: datetime | None = None) -> list[UserUsageResponse]

Usage grouped per user, spend-descending, following pagination.

Key-driven spend never appears here — see list_api_key_usage.

Parameter Type Default Description
since datetime | None None Include events at or after this moment. A naive datetime is read as UTC.
until datetime | None None Include events strictly before this moment. A naive datetime is read as UTC.

One row per user with recorded spend in the window.

async def list_api_key_usage(*, since: datetime | None = None, until: datetime | None = None) -> list[ApiKeyUsageResponse]

Usage grouped per API key, spend-descending, following pagination.

Revoked keys keep their history and come back flagged revoked.

Parameter Type Default Description
since datetime | None None Include events at or after this moment. A naive datetime is read as UTC.
until datetime | None None Include events strictly before this moment. A naive datetime is read as UTC.

One row per key with recorded spend in the window.