Skip to main content

AutoSynth Python SDK

Typed Python client and CLI for the AutoSynth experiment-check API. The core flow: a chemist registers an observation from the lab against an approved plan, submits a perception check, and gets back the status and a structured result. The SDK also covers review, replay, lineage, datasets, and evaluation.

autosynth CLI demo

  • Sync and async clients (AutoSynth, AsyncAutoSynth) with pydantic models for every response.
  • Retries with exponential backoff on 429/5xx and network errors, honoring Retry-After.
  • Writes are only resent when that is safe: artifacts are content-addressed, and check submissions carry an auto-generated Idempotency-Key.
  • runs.wait() polls with backoff. Failed runs come back as data with an error code; they don't raise.
  • An autosynth command line, plus reproducible synthetic vial images so you can try everything without a camera.

Install

pip install autosynth-sdk        # or: uv add autosynth-sdk

Requires Python 3.10+. The distribution is autosynth-sdk; the import name is autosynth.

Quickstart

from autosynth import AutoSynth, synthetic

with AutoSynth() as client:                      # AUTOSYNTH_API_KEY, AUTOSYNTH_BASE_URL from the environment
    plan = client.plans.list(status="approved")[0].plan_ref
    photo = synthetic.vial(plan, seed=7, scenario="positive").png   # or open("vial.png", "rb").read()
    run = client.checks.run(photo, plan)         # register -> submit -> wait
    print(run.status, run.outcome, run.result.confidence)           # succeeded positive 1.0

The same flow, one step at a time:

artifact = client.artifacts.register("vial.png", "PLN-0001", metadata={"camera": "bay2-cam1"})
run = client.checks.submit("PLN-0001", artifact.artifact_id)        # 201, usually status "queued"
run = client.runs.wait(run, timeout=60)                             # polls until succeeded | failed

if run.succeeded:
    print(run.result.outcome, run.result.confidence, run.result.model, run.result.findings)
    print(run.provenance.input_sha256, run.provenance.config_hash, run.provenance.code.git_sha)
else:
    print(run.error.code, run.error.message)     # e.g. image_quality: observation too blurry to assess

Authentication

Create an API key on the Developers page of the staff app at https://autosynth.bio, then export it:

export AUTOSYNTH_API_KEY=...                       # sent as  Authorization: Bearer <key>

You can also pass the key directly: AutoSynth(api_key=...). Each key is bound to one project, and every id you see (plans, artifacts, runs) is scoped to that project. Ids from other projects look the same as missing ones and return 404. Keys act in staff mode. The customer-portal endpoints (/portal/*) need a customer's browser session, so the SDK doesn't wrap them.

Hosted vs local

Base URL Auth
Hosted (default) https://api.autosynth.bio API key required
Local docker compose up (monorepo) http://localhost:8000 none in dev mode; a key from WARP_API_KEYS if set
export AUTOSYNTH_BASE_URL=http://localhost:8000   # or AutoSynth(base_url="http://localhost:8000")

Results and failures

A check ends with status set to succeeded or failed. A succeeded run has result.outcome of positive, negative or uncertain, plus confidence, findings (for example crystals with bright_pixels, and sharpness), the model name and version, config_hash, and latency_ms. Failed runs are persisted with error.code:

error.code Meaning
image_quality frame too blurry to assess (failed the sharpness gate)
corrupt_input image can't be decoded (for example, a truncated upload)
input_missing / input_integrity evidence is gone, or its bytes no longer match the sha256
timeout the model missed the 500 ms online deadline
plan_not_approved / unknown_model the plan is draft or revoked, or the model name or version is unknown
overloaded, inference_unavailable, model_error the inference service stayed unavailable after the server's retries
storage_unavailable, infrastructure_error, orphaned, internal platform failures; no run is left running forever

Registering by uri= (a file already in the deployment's store) only accepts your project's upload prefix, s3://<bucket>/uploads/<project>/...; anything else raises PermissionDeniedError with code forbidden_uri. The bytes are copied into content-addressed storage right away. If the source is missing, artifacts.register raises InvalidRequestError with code input_missing, so the problem shows up before any check is submitted.

An uncertain edge result may be escalated to a larger model. The original result stays in run.result (escalated=True), the verdict is appended to run.result_revisions, and run.effective_result.source becomes "escalation". Precedence is: accepted annotation, then the latest result revision, then the original result. run.provenance.route records which backend served the result (mock, hf or remote).

run.outcome is the effective outcome. If a reviewer annotated the run, it is the reviewer's label; otherwise it is the model's. The model's own result stays in run.result.

Errors, retries, timeouts

from autosynth import AutoSynth, ConflictError, NotFoundError, RateLimitError, APIConnectionError

client = AutoSynth(timeout=30, max_retries=3)    # timeout: seconds or an httpx2.Timeout
Exception When
AutoSynthError base class for everything below
APIStatusError (.status, .code, .message) any 4xx/5xx; .code is the API's machine-readable code
BadRequestError 400, AuthenticationError 401, PermissionDeniedError 403, NotFoundError 404
ConflictError 409 idempotency_conflict, invalid_transition, already released
InvalidRequestError 422 validation failed (for example invalid_metadata)
RateLimitError 429, InternalServerError 5xx raised after the automatic retries run out
APIConnectionError, APITimeoutError no HTTP response (DNS, refused connection, TLS error, timeout)
WaitTimeoutError (also a TimeoutError) runs.wait() gave up; .run holds the last state seen

Retry behaviour:

  • Reads, artifact registration, dataset snapshots, and submits/replays (which carry an Idempotency-Key) are retried on connection errors, timeouts, 408, 429 and 5xx.
  • Append-only or once-only writes (annotations, releases, training runs, request transitions) are retried only when the server provably did not act: connection refused, 429, or 503.
  • Backoff is exponential with full jitter (0.5 s, 1 s, 2 s, ... capped at 8 s). Retry-After and Retry-After-Ms take precedence, capped at 60 s.
  • To make a submit safe across processes, pass your own idempotency_key=. The same key with the same body returns the original run; the same key with a different body raises ConflictError.

Async

import asyncio
from autosynth import AsyncAutoSynth, synthetic

async def main():
    async with AsyncAutoSynth() as client:
        frames = [synthetic.vial("PLN-0003", seed) for seed in range(32)]
        runs = await asyncio.gather(*(client.checks.run(f.png, "PLN-0003", priority="bulk") for f in frames))
        print(sum(r.outcome == "positive" for r in runs), "of", len(runs), "crystallized")

asyncio.run(main())

AsyncAutoSynth has the same resources and methods as AutoSynth; every call is awaited, and runs.iter() is an async iterator.

CLI

$ autosynth synth vial.png --scenario positive          # write a synthetic observation (no API call)
$ autosynth check vial.png --plan PLN-0001 --wait
registered art_94e6989b2aff3242 (3249 bytes, sha256 a8fc26cb8bbf…)
submitted  run_8b3c181d9c973e9c on PLN-0001 (online, queued)
run_8b3c181d9c973e9c  succeeded  positive  conf 1.00  vialnet@1.3.0  72 ms
  plan PLN-0001 · artifact art_94e6989b2aff3242 · input sha256 a8fc26cb8bbf… · online
  findings: crystals 1.00 (bright_pixels 155) · sharpness 1.03
  events: queued → running → succeeded
$ autosynth runs list --status failed --limit 10
$ autosynth runs get run_8b3c181d9c973e9c
$ autosynth runs annotate run_8b3c181d9c973e9c uncertain --comment "glare on the vial wall"
$ autosynth runs replay run_8b3c181d9c973e9c --wait
$ autosynth lineage run_8b3c181d9c973e9c
run_8b3c181d9c973e9c run/perception · positive
├─ ← input_of art_94e6989b2aff3242 artifact/observation
├─ → produced art_c0bc71b9b9fa8344 artifact/result
└─ → replay_of run_babceea49588cae6 run/perception · positive
$ autosynth plans
$ autosynth health

Every command accepts --json, --base-url and --api-key, and reads the same environment variables as the client. Exit codes:

  • 0: success.
  • 1: the check ran but failed (for example image_quality), or the wait timed out.
  • 2: an API, network, or usage error.

python -m autosynth works too.

API reference

All methods exist on both AutoSynth and AsyncAutoSynth. Every method returns a pydantic model from autosynth.models.

Method HTTP Returns
client.health() GET /healthz Health
plans.list(status=None) GET /plans list[Plan]
plans.approve(plan_ref) POST /plans/{ref}/approve dict
artifacts.register(source, plan_ref, kind="observation", metadata=None, *, uri=None, parent_artifact_id=None) POST /artifacts Artifact
artifacts.get(artifact_id) GET /artifacts/{id} Artifact (with runs, children, metadata history)
artifacts.list(kind=, plan_ref=, tag=, meta=, since=, limit=) GET /artifacts list[Artifact]
artifacts.content(artifact_id) GET /artifacts/{id}/content bytes
artifacts.presign(artifact_id) GET /artifacts/{id}/content?presign=true PresignedURL
artifacts.update_metadata(artifact_id, metadata, author=None) PATCH /artifacts/{id}/metadata Artifact
checks.submit(plan_ref, artifact_id, *, model=None, priority="online", idempotency_key=None) POST /runs Run
checks.run(source, plan_ref, *, metadata=, model=, priority=, wait=True, timeout=120) register + submit + wait Run
runs.get(run_id) GET /runs/{id} Run
runs.list(plan_ref=, status=, limit=, cursor=) GET /runs RunPage
runs.iter(plan_ref=, status=, page_size=100) GET /runs (all pages) iterator of Run
runs.wait(run, *, timeout=120, poll_interval=0.2, max_poll_interval=2.0) polls GET /runs/{id} Run
runs.annotate(run_id, label, *, comment="", reviewer=None, status="accepted") POST /runs/{id}/annotations AnnotationResult
runs.annotations(run_id) GET /runs/{id}/annotations RunAnnotations
runs.replay(run_id, *, model="pinned", priority="bulk", idempotency_key=None) POST /runs/{id}/replay Run
runs.release(run_id) POST /runs/{id}/release Release
runs.export(since=None) GET /export/runs.jsonl list[dict]
lineage.get(node_id, *, direction="both", depth=3) GET /lineage/{id} LineageGraph
datasets.create(name, *, plan_ref=, model=, status="succeeded", since=, until=, labeled_only=False) POST /datasets Artifact
datasets.manifest(artifact_id) GET /artifacts/{id}/content DatasetManifest
evaluation.get(*, model=None, since=None, reference="annotations") GET /evaluation list[ModelEvaluation]
stats.weekly(*, weeks=8, as_of=None) GET /stats/weekly list[WeeklyStats]
training_runs.list(kind=, limit=) / training_runs.create(kind, *, dataset_artifact_ids, config_hash, ...) GET / POST /training/runs TrainingRun
requests.list(status=) / requests.plan(id, title=) / requests.reject(id, reason) staff request workflow StaffRequest

Notes:

  • source is a file path or bytes. Re-registering the same bytes returns the same artifact.
  • Models keep fields they don't recognise (extra="allow"), so a newer server doesn't break an older SDK.

Synthetic observations

autosynth.synthetic.vial(plan_ref, seed, scenario) returns a reproducible 64×64 PNG plus capture metadata. It is byte-identical to the frames the platform's robot mock produces. The hosted mock model answers each scenario as follows:

Scenario Status Result
positive succeeded positive
negative succeeded negative
uncertain succeeded uncertain (faint evidence after a short settle)
blurry failed image_quality
corrupt failed corrupt_input (truncated upload)
None varies sampled from the policy

metadata["truth"] holds the later lab-confirmed outcome, which evaluation.get(reference="truth") scores against.

Examples

Compatibility

warp_sdk.WarpClient, the monorepo's pre-0.1 flat client (register_artifact, submit_check, get_run, wait, ...), ships in the same wheel as a thin deprecated wrapper over AutoSynth (available as .client).

Development

cd sdk
uv venv && uv pip install -e . pytest anyio mypy
pytest                                              # unit tests (httpx2.MockTransport); integration tests skip
AUTOSYNTH_INTEGRATION_URL=http://localhost:8000 AUTOSYNTH_INTEGRATION_KEY=... pytest -m integration
mypy --strict autosynth
uv build && uvx twine check dist/*

Releases go out by pushing a tag sdk-vX.Y.Z (the version must match pyproject.toml). The sdk-publish GitHub workflow builds, tests, and publishes to PyPI through trusted publishing. The terminal demo is recorded with docs/demo.sh.

License

MIT

Metadata

Release files for autosynth-sdk 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for autosynth-sdk 0.1.1
File Size Uploaded Provenance
autosynth_sdk-0.1.1.tar.gz 70.7 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for autosynth-sdk 0.1.1
File Interpreter ABI Platform
autosynth_sdk-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 106.1 kB

Release files / autosynth_sdk-0.1.1.tar.gz

Download URL autosynth_sdk-0.1.1.tar.gz
Size 70.7 kB
Tags Source
SHA-256 checksum
How to use checksums
e261192bc005e221007d7535d0c83e993b1ec64f3dfd29cfd4380e7b94d0992d
BLAKE2b-256 checksum
How to use checksums
f613de9c601d63495039a60ddd5f7859aa62669f312a0799f8f05fd35aee0be5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 19, 2026.

Transparency log

Release files / autosynth_sdk-0.1.1-py3-none-any.whl

Download URL autosynth_sdk-0.1.1-py3-none-any.whl
Size 35.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9a16b008bf63b6fbfc7040e414e44eeb0fc845a31c3816f57c5515c5963719c5
BLAKE2b-256 checksum
How to use checksums
30c996541176548349110c10136e9da9c5810777e85cd6f88df984bd3e8f43b9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 19, 2026.

Transparency log

Security for autosynth-sdk 0.1.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for autosynth_sdk-0.1.1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 19, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page