Skip to main content

Sinatra Python SDK

sinatra-dev is the official Python client for sinatra.dev, the hosted AI coding agent that turns Linear and GitHub issues into pull requests. Assign Sinatra an issue, mention it in Slack, or start a task from code, and it writes the change in its own sandbox and opens a draft pull request for your team to review.

With this client you can start, follow and stop Sinatra tasks and delegate Linear and GitHub issues from your own scripts, CI jobs and tools, through the Sinatra API.

It runs on Python 3.11 and later, is typed (py.typed), and depends only on httpx and attrs. The client is generated from the API's OpenAPI spec and released automatically when the spec changes. There are also official SDKs for Node.js / TypeScript and Go; see SDKs.

Install

pip install sinatra-dev

Quick start

import time

from sinatra_dev import Sinatra

# The turn is done when the task leaves these statuses (for example, it reaches replied or pr_open).
WORKING = {"pending", "investigating", "implementing", "rework"}

with Sinatra() as sinatra:  # reads SINATRA_TOKEN
    me = sinatra.me.get()
    if not me.workspaces:
        raise SystemExit("The token's user isn't in any workspace yet.")
    workspace = me.workspaces[0]

    task = sinatra.tasks.create(
        workspace.id, "Fix the flaky login test", repo="acme/web", idempotency_key="fix-flaky-login-1"
    ).task

    after = None
    while True:
        status = sinatra.tasks.get(workspace.id, task.id).status
        for m in sinatra.messages.list_all(workspace.id, task.id, after=after):
            print(f"{m.role}: {m.body}")
            after = m.id
        if status not in WORKING:
            break
        time.sleep(3)
    print(f"Task {task.id} is {status}.")

The API has no streaming. To wait for a reply, read the conversation after the last entry you've seen, and check the task's status: the agent posts one reply per turn, and the turn is done when the task stops working. The loop reads the status before the conversation, so it never stops before printing the reply that ended the turn. An unknown status counts as done, so the loop can't wait forever on a status added after this was written. See How replies work.

Examples

examples/ has small programs you can run as they are:

  • delegate_issue.py: hand Sinatra a Linear issue (ENG-123) or a GitHub issue (acme/web#42), for example from a CI job.
  • search_issues.py: list the open issues you could delegate, yours first.
  • open_prs.py: print the pull requests Sinatra has open in each workspace as a Markdown list, for a standup or a Slack post.

With the package installed (pip install sinatra-dev), or from a checkout with uv run:

SINATRA_TOKEN=sntr_... uv run python examples/open_prs.py

Authentication

Create a personal access token on the Access tokens page (CLI & API in the dashboard). It starts with sntr_ and acts as you in every workspace you belong to. The client reads it from the SINATRA_TOKEN environment variable, or you can pass it:

sinatra = Sinatra(token=os.environ["MY_TOKEN"])

The constructor raises ValueError if it finds no token. Other options:

  • base_url: defaults to https://api.sinatra.dev.
  • timeout: seconds each attempt may wait to connect, and for each chunk of the response, before httpx raises httpx.TimeoutException. Defaults to 60; None turns it off. An httpx.Timeout works too.
  • http_client: an httpx.Client to send requests with, for example one with a proxy. The client doesn't close it.

Every method takes a timeout keyword that overrides the client's for that call:

sinatra.tasks.get(workspace_id, task_id, timeout=5)

Use the client as a context manager, or call sinatra.close(), to release its connections.

Operations

The namespaces mirror the API's operation ids:

Method Request
me.get() GET /v1/me
me.revoke_token() DELETE /v1/me/token
tasks.list(workspace_id, status=, limit=, cursor=) GET /v1/workspaces/{workspaceId}/tasks
tasks.get(workspace_id, task_id) GET /v1/workspaces/{workspaceId}/tasks/{taskId}
tasks.create(workspace_id, prompt, title=, repo=, idempotency_key=) POST /v1/workspaces/{workspaceId}/tasks
tasks.stop(workspace_id, task_id) POST /v1/workspaces/{workspaceId}/tasks/{taskId}/stop
messages.create(workspace_id, task_id, text, idempotency_key=) POST /v1/workspaces/{workspaceId}/tasks/{taskId}/messages
messages.list(workspace_id, task_id, after=, limit=) GET /v1/workspaces/{workspaceId}/tasks/{taskId}/messages
tickets.search(workspace_id, q=, repo=) GET /v1/workspaces/{workspaceId}/tickets
tickets.delegate(workspace_id, source="linear", id=) or (workspace_id, source="github", repo=, number=) POST /v1/workspaces/{workspaceId}/tickets/delegate

Responses are typed objects with snake_case attributes (task.pull_request, page.next_cursor). Fields the API added after this release are kept in each object's additional_properties.

tasks.list_all and messages.list_all page through everything for you:

for task in sinatra.tasks.list_all(workspace_id, status="pr_open"):
    print(task.id, task.title)

Fields such as status, source, role, kind and the error code are plain strings, and the API adds new values over time. Handle values you don't recognise.

Retries and idempotency

tasks.create and messages.create always send an Idempotency-Key. If you don't pass one, the client makes a random UUID. Every retry of a call sends the same key, so a retried start never starts a second task. Pass your own key to make a retry safe across process restarts too.

The client retries a request up to 3 times when it's safe to repeat:

  • 429: after the Retry-After the server sends, at most 60 seconds.
  • 503: after 0.5s, then 1s, then 2s.

GETs, me.revoke_token, tasks.create and messages.create retry on both. tasks.stop retries on 503 only. tickets.delegate takes no idempotency key and is never retried. Other errors, timeouts and network failures are never retried.

Errors

An error response raises a SinatraError:

from sinatra_dev import SinatraError

try:
    sinatra.tasks.stop(workspace_id, task_id)
except SinatraError as err:
    if err.code != "not_running":  # not_running: the task had already stopped
        raise
Attribute
status The HTTP status.
code The API's error code, such as capacity_full. http_error when the response carried none, such as an HTML error page from a proxy.
reason A sentence for a person. Don't branch on it.
request_id The request's id. Include it when you contact support.
body The response body: the parsed JSON, or the text. Refusals carry extra fields here, such as billingUrl.
headers The response headers.

Network failures and timeouts are not wrapped: they raise the httpx.HTTPError (for example httpx.TimeoutException) that httpx raised.

The API reference lists every operation's error codes.

Versions

Releases follow semver. While the version is 0.x, a breaking API change bumps the minor version and anything else bumps the patch. From 1.0, breaking changes bump the major version and additions bump the minor.

Development

Needs uv, and Node 24 for the contract tests.

uv sync
bash scripts/install-oasdiff.sh   # installs the pinned oasdiff into .tools/bin
bash scripts/generate.sh          # regenerates src/sinatra_dev/_generated from openapi.json
uv run ruff check . && uv run ruff format --check . && uv run mypy
uv run pytest                     # wrapper tests against a local stub server
bash scripts/contract.sh          # every operation against a Prism mock of openapi.json
PATH="$PWD/.tools/bin:$PATH" bash scripts/next-version.test.sh
bash scripts/build.sh             # sdist and wheel in dist/, build backend pinned by hash

src/sinatra_dev/_generated is written by openapi-python-client and never edited by hand; CI fails if it differs from what openapi.json generates (bash scripts/check-generated.sh). openapi.json is updated by the release workflow. See RELEASING.md.

License

MIT

Metadata

Release files for sinatra-dev 0.1.2

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

Source distribution (sdist)

Source distribution for sinatra-dev 0.1.2
File Size Uploaded Provenance
sinatra_dev-0.1.2.tar.gz 29.2 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for sinatra-dev 0.1.2
File Interpreter ABI Platform
sinatra_dev-0.1.2-py3-none-any.whl Python 3 none any Details

Total release size: 86.4 kB

Release files / sinatra_dev-0.1.2.tar.gz

Download URL sinatra_dev-0.1.2.tar.gz
Size 29.2 kB
Tags Source
SHA-256 checksum
How to use checksums
e5b3cbb0b28d3b024a39fd6b21511e7ee1ae6ad42e9b2529c721ce32e8466c40
BLAKE2b-256 checksum
How to use checksums
47fa337279362bcc45f8e566a993544058997c21fd6bd86894137bcdc1f3556e
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 22, 2026.

Transparency log

Release files / sinatra_dev-0.1.2-py3-none-any.whl

Download URL sinatra_dev-0.1.2-py3-none-any.whl
Size 57.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3d7c14c6fd790f066747495e831646a2deab4f0a10496545e22bc7ddd969f7ca
BLAKE2b-256 checksum
How to use checksums
95b2456add5a7641b5136bb22f4447392f72e7dc8847beaffa35f632aa43b3e1
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 22, 2026.

Transparency log

Security for sinatra-dev 0.1.2

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 sinatra_dev-0.1.2.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 22, 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.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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