SDKs
Official Sinatra clients for Node.js, Python and Go, to start, follow and stop tasks from your own code.
Sinatra has official clients for Node.js, Python and Go. They wrap the HTTP API, so a task started from code runs like one started from the CLI or MCP: it gets a sandbox, can open a pull request, and shows up in the dashboard.
| Language | Package | Needs | Source |
|---|---|---|---|
| Node.js | @sinatra-dev/sdk | Node 20 or newer | sinatra-dev/sinatra-node |
| Python | sinatra-dev | Python 3.11 or newer | sinatra-dev/sinatra-python |
| Go | github.com/sinatra-dev/sinatra-go | Go 1.24 or newer | sinatra-dev/sinatra-go |
Create a personal access token on the CLI & API page of the dashboard, as described in Create a token, and set it as SINATRA_TOKEN. Each client reads it from there, or takes it as an option.
Every quick start below does the same three things: reads who the token acts as, starts a task in your first workspace with an idempotency key, and prints the conversation until the turn ends. The API has no streaming, so each one polls. Each loop reads the task's status before the conversation, so it never stops before printing the reply that ended the turn. See How replies work for when a turn is done.
Node.js
npm install @sinatra-dev/sdkimport { Sinatra } from "@sinatra-dev/sdk";
const sinatra = new Sinatra(); // reads SINATRA_TOKEN
const me = await sinatra.me.get();
const workspace = me.workspaces[0];
if (!workspace) throw new Error("The token's user isn't in any workspace yet.");
const { task } = await sinatra.tasks.create(
workspace.id,
{ prompt: "Fix the flaky login test", repo: "acme/web" },
{ idempotencyKey: "fix-flaky-login-1" },
);
// The turn is done when the task leaves these statuses (for example, it reaches replied or pr_open).
const WORKING = new Set(["pending", "investigating", "implementing", "rework"]);
let after: string | undefined;
for (;;) {
const { status } = await sinatra.tasks.get(workspace.id, task.id);
for await (const m of sinatra.messages.listAll(workspace.id, task.id, { after })) {
console.log(`${m.role}: ${m.body}`);
after = m.id;
}
if (!WORKING.has(status)) break;
await new Promise((r) => setTimeout(r, 3000));
}Python
pip install sinatra-devimport 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 Python client is synchronous. There is no async client yet.
Go
go get github.com/sinatra-dev/sinatra-goThe package name is sinatra. This is the body of main, where check calls log.Fatal on an error. The full program has the imports.
ctx := context.Background()
client, err := sinatra.NewClient() // reads SINATRA_TOKEN
check(err)
me, err := client.Me.Get(ctx)
check(err)
if len(me.Workspaces) == 0 {
log.Fatal("the token's user isn't in any workspace yet")
}
ws := me.Workspaces[0].Id
created, err := client.Tasks.Create(ctx, ws, sinatra.CreateTaskRequest{Prompt: "Fix the flaky login test"},
sinatra.WithIdempotencyKey("fix-flaky-login-1"))
check(err)
working := map[string]bool{"pending": true, "investigating": true, "implementing": true, "rework": true}
var after *string
for ; ; time.Sleep(3 * time.Second) {
task, err := client.Tasks.Get(ctx, ws, created.Task.Id)
check(err)
for m, err := range client.Messages.ListAll(ctx, ws, task.Id, &sinatra.MessagesListParams{After: after}) {
check(err)
fmt.Printf("%s: %s\n", m.Role, m.Body)
after = &m.Id
}
if !working[task.Status] {
fmt.Println("status:", task.Status)
return
}
}How they behave
The clients are generated from the API's OpenAPI spec, and a new version is released automatically when the API changes. All three behave the same way:
- Token. They read
SINATRA_TOKEN, or take the token as an option. Creating a client without one fails. - Retries. A request that failed with
429or503is retried up to 3 times when repeating it is safe. A429waits for theRetry-Afterthe server sends, at most 60 seconds; a503waits 0.5, 1 and 2 seconds. Starting a task and sending a message always carry an idempotency key, and every retry reuses it, so a retry never starts a second task or sends a message twice. If you don't pass a key, the client makes one. Pass your own to make a retry safe across process restarts too. Stopping a task retries on503only, delegating an issue is never retried, and timeouts and network failures are never retried. - Paging.
tasks.list_allandmessages.list_all(listAllin Node.js,ListAllin Go) are iterators that fetch every page for you. - Errors. An error response becomes one
SinatraError(*sinatra.Errorin Go) with the HTTPstatus, the API's errorcode, areasonwritten for a person, and therequestId(request_idin Python). Include the request id when you contact support. Network failures and timeouts aren't wrapped; they surface as your language reports them. - New values. Fields such as
status,source,roleand the errorcodeare plain strings. When the API adds a value, the clients keep working; handle values you don't recognise.
The clients are 0.x. While the major version is 0, a breaking API change bumps the minor version, so pin the version you've tested. Each repository's README lists every method.
Next
- The API Reference documents every endpoint, field and error code.
- CLI, MCP and API covers tokens, limits and how replies work.
Delegate an issue
Hands Sinatra an existing Linear or GitHub issue as you, as if you'd assigned it to Sinatra yourself, and returns 202 with {"ok": true}.
Troubleshooting
Why the agent didn't pick up your ticket, no PR appeared, a run stalled, or Slack, the CLI, the MCP server or the API isn't working — and how to fix it.