Sinatra Docs

CLI, MCP and API

Start and follow Sinatra tasks from your terminal, from Claude Code, Cursor, claude.ai or ChatGPT, or from your own scripts.

Sinatra's task API lets you hand it work without a Linear ticket or GitHub issue. Start a task from your terminal with the sinatra CLI, from an MCP client such as Claude Code, Cursor, claude.ai or ChatGPT, or from a script over HTTP. You read the agent's answers and questions, reply, and stop a turn the same way.

All three use the same API. The CLI, Claude Code, Cursor, claude.ai and ChatGPT sign in with your Sinatra account in the browser. The HTTP API uses a personal access token, and so can the CLI on a computer without a browser. A task started this way runs like any other: it gets a sandbox, can open a pull request, and shows up in the dashboard.

Create a token

  1. In the dashboard, open CLI & API in the sidebar. This opens the Access tokens page at app.sinatra.dev/tokens.
  2. Name the token, for example Laptop CLI, and pick when it expires: 30, 90 or 365 days.
  3. Copy the token. It starts with sntr_ and is shown once. Afterwards the page shows only its name and last four characters.

The same page shows how to set up the CLI, Claude Code, Cursor, claude.ai, ChatGPT and other MCP clients, and the HTTP API with curl.

A token acts as you in every workspace you belong to, with your role in each. Sinatra checks your membership on every request, so if you leave a workspace, the token stops working there. Revoke a token on the same page at any time; anything using it stops working immediately. sinatra logout and DELETE /v1/me/token revoke the token they use.

Keep tokens out of committed files. Sinatra stores only a hash of each token, so a lost token can't be recovered: revoke it and create a new one.

CLI

The CLI needs Node 22 or newer. Signing in through the browser, --token and --no-browser need version 0.2.0 or newer; on an older version sinatra login asks for a token. Run npm i -g sinatra.dev to update.

npm i -g sinatra.dev
sinatra --version
sinatra login

sinatra login opens your browser on a Sinatra page that asks you to approve the Sinatra CLI. Sign in to Sinatra there if you aren't already, click Approve, and the CLI is logged in, with no token to create or copy. It then asks you to pick a default workspace if you belong to more than one. If you don't approve within 5 minutes, the CLI stops waiting.

The CLI saves the sign-in to ~/.config/sinatra/config.json ($XDG_CONFIG_HOME/sinatra if set, %APPDATA%\sinatra on Windows), readable only by you. It renews its access as it goes, and stays signed in for up to 90 days, or until it goes 30 days unused. It's listed as Sinatra CLI under Connected apps on the Access tokens page, where Disconnect signs it out at once. After either, the CLI asks you to run sinatra login again.

If the browser doesn't open, sinatra login --no-browser prints the URL to open instead. The browser has to run on the same computer as the CLI, because Sinatra sends it back to a port the CLI listens on there. On a computer you reach over SSH, or with no browser, log in with a token: sinatra login --token asks for it without echoing it.

To log in from a script, pipe the token in and pass -w <id> to set the default workspace: sinatra login -w <workspace-id> < token-file. Piped input reads a token unless you also pass --no-browser. A workspace that isn't one of yours is refused, and nothing is saved. Piped without -w, login saves no default when you belong to more than one workspace, and says how to set one.

sinatra logout ends the sign-in on Sinatra, or revokes the token you logged in with, then deletes that file. If Sinatra can't be reached, the file is still deleted and the CLI warns that the sign-in or token may still work. Disconnect or revoke it on the dashboard's CLI & API page.

Examples

sinatra "fix the flaky retry test"              # start a task and follow it
sinatra "explain this" -f notes.md -f err.log   # attach text files
sinatra reply <task-id> "also update the docs"  # follow up, then follow the task
sinatra reply <task-id> "see this" -f err.log   # attach files to a reply
sinatra show <task-id>                          # print the task and its conversation
sinatra tasks                                   # the 20 most recently active tasks
sinatra tasks --status pr_open -n 50            # filter by status, show more
sinatra stop <task-id>                          # stop a running turn
sinatra whoami                                  # who you're logged in as, and your workspaces
sinatra workspace                               # list your workspaces and the default
sinatra workspace <id>                          # make another workspace the default
sinatra logout                                  # sign out and forget the login
sinatra "fix it" --no-wait --json               # start a task, print it as JSON

After you start a task or reply, the CLI prints what Sinatra posts until the turn is done, then tells you how to reply. If the task is queued because your workspace is running as many tasks at a time as its plan allows, the CLI says so. Ctrl-C stops waiting; the task keeps going. After 30 minutes, or the minutes you set with --timeout, the CLI stops waiting and exits with code 3. Pick up later with sinatra show <task-id>.

If you reply to a task that had completed, failed or been cancelled and Sinatra hasn't picked up your message within two minutes, the CLI says so and exits with code 3, without reporting the previous turn's result. A task paused on access or billing exits with code 2.

A prompt that starts with a command word still starts a task, unless it has exactly that command's shape. sinatra stop retrying on 429 starts a task, but a command word followed by a single word, such as sinatra stop now, is treated as a mistyped command. sinatra start "..." always starts one.

A prompt that starts with - goes after --, so it isn't read as an option: sinatra -- "-v is broken". A title that starts with - takes the = form: --title="-x title".

sinatra show prints the conversation only for tasks started from the CLI, MCP or API. A task started elsewhere, such as from Linear or GitHub, has its conversation there.

Flags

start here means starting a task, with or without the word: sinatra "..." and sinatra start "..." are the same.

FlagCommandsWhat it does
-r, --repo <owner/name>startThe repository to work on. Must be connected to the workspace.
-f, --file <path>start, replyAttach a text file to the message. Repeatable.
-t, --title <title>startThe task's title. Defaults to the prompt's first line.
-w, --workspace <id>start, reply, show, tasks, stop, loginThe workspace. Defaults to the one picked at login or with sinatra workspace <id>. With login, the default workspace to save.
--timeout <minutes>start, replyHow long to follow the task before the CLI stops waiting. Defaults to 30.
--no-waitstart, replyStart the task or send the reply, then return without following it.
--jsonwhoami, tasks, show; start and reply with --no-waitPrint JSON and nothing else, for scripts.
--status <status>tasksOnly tasks in this status, such as implementing or pr_open.
-n, --limit <n>tasksHow many tasks to list, 1 to 100. Defaults to 20.
--no-browserloginPrint the sign-in URL instead of opening the browser. The browser must still be on this computer.
--tokenloginPaste a personal access token instead of signing in through the browser.
--api-url <url>loginThe API to use. Defaults to https://api.sinatra.dev. Must be https.
-v, --versionnonePrint the CLI's version.

A flag the command doesn't use is an error, not silently ignored.

Repository. Inside a git checkout, the task runs on the checkout's GitHub repository. The CLI looks at the origin remote, then upstream. Another GitHub remote counts only when neither of those is on GitHub. SSH host aliases from ~/.ssh/config, such as git@github-work:owner/repo.git, count when they point at github.com. If origin's repository isn't connected to the workspace, the CLI says so and tries upstream's, so a fork falls through to the repository it forks. When none is connected, Sinatra's agent picks one. The CLI prints which remote it used. A repository you name with -r is never replaced: if it isn't connected, the start fails. Outside a GitHub checkout, without -r, the agent picks.

Attachments. Files go into the new task's first message or into your reply, marked as file content rather than instructions. Only text files can be attached, up to 80 KB together. Anything larger is refused, not cut short.

Retries. Every request that changes something carries an idempotency key. The CLI tries a request up to three times on a dropped connection or a server error, and a retry never starts a second task or sends a message twice.

Exit codes

CodeMeaning
0The turn finished, or the command succeeded.
1An error: bad arguments, not logged in, an invalid token, a refused request, or a network failure.
2The task failed, was cancelled, or is paused on access or billing.
3Still working when the CLI stopped waiting, or Sinatra hasn't picked up your reply yet.

MCP server

Sinatra is a remote MCP server at https://api.sinatra.dev/v1/mcp. It uses the Streamable HTTP transport. Claude Code, Cursor, claude.ai and ChatGPT sign in with your Sinatra account over OAuth, like the CLI, so there's no token to create or paste. Clients that take a URL and headers can use a personal access token in an Authorization: Bearer header instead.

When an app signs in, Sinatra opens a page in your browser that names the app and asks you to approve it. A connected app acts as you in every workspace you belong to, with your role in each, like a token does. It stays signed in for up to 90 days, or until it goes 30 days without connecting, then asks you to sign in again. Approved apps are listed under Connected apps on the Access tokens page. Disconnect ends an app's access right away: its next call is refused, and it has to sign in again to reconnect.

Claude Code

claude mcp add --transport http --scope user sinatra https://api.sinatra.dev/v1/mcp

Then run /mcp in Claude Code, or claude mcp login sinatra, and sign in to Sinatra in your browser. --scope user makes Sinatra available in every project.

To use a token instead:

read -rs SINATRA_TOKEN && claude mcp add --transport http --scope user sinatra \
  https://api.sinatra.dev/v1/mcp --header "Authorization: Bearer $SINATRA_TOKEN"

When the command waits, paste your token and press Enter; nothing shows as you paste. read -rs keeps the token out of your shell history.

Cursor

Open the Add to Cursor install link, or add this to ~/.cursor/mcp.json, or to .cursor/mcp.json in a project:

{
  "mcpServers": {
    "sinatra": {
      "url": "https://api.sinatra.dev/v1/mcp"
    }
  }
}

The Cursor app then asks you to sign in to Sinatra. In the Cursor CLI, run cursor-agent mcp enable sinatra, then cursor-agent mcp login sinatra.

claude.ai and Claude Desktop

  1. In claude.ai, open Customize, then Connectors, click Add and choose Add custom connector.
  2. Name it Sinatra and enter https://api.sinatra.dev/v1/mcp as the URL.
  3. Click Connect, then approve the app on Sinatra's page.

Claude Desktop uses the same connectors as claude.ai, so once you've added Sinatra in one, it's in the other.

To revoke access, click Disconnect under Connected apps on Sinatra's Access tokens page; removing the connector in claude.ai alone doesn't.

ChatGPT

ChatGPT adds MCP servers as plugins. Starting a task needs a custom MCP server with write actions, which OpenAI lists for ChatGPT Business, Enterprise and Edu workspaces, on the web. On Business, an admin or owner adds it.

  1. In ChatGPT, open the Plugins page (chatgpt.com/plugins), click + and choose Add custom MCP server.
  2. Name it Sinatra, enter https://api.sinatra.dev/v1/mcp as the server URL, and choose OAuth for authentication. ChatGPT finds Sinatra's sign-in settings itself; keep the registration method it picks.
  3. Tick I understand and want to continue, click Create as a plugin, then Continue to Sinatra.
  4. Approve the app on Sinatra's page, which names it as ChatGPT, described by chatgpt.com.

To use it, mention @Sinatra in a chat, or click Try in chat.

To revoke access, click Disconnect under Connected apps on Sinatra's Access tokens page; deleting the plugin in ChatGPT alone doesn't.

Other clients

We've tested sign-in with Claude Code, Cursor, claude.ai and ChatGPT only. Another client that takes a server URL and headers can use a token:

{
  "mcpServers": {
    "sinatra": {
      "url": "https://api.sinatra.dev/v1/mcp",
      "headers": {
        "Authorization": "Bearer <token>"
      }
    }
  }
}

Tools

Every tool except whoami takes a workspace_id. Call whoami first to get yours.

ToolArgumentsWhat it does
whoaminoneWho the token acts as, and the workspaces you belong to, with your role in each.
list_tasksworkspace_id, status, limit (1–100), cursorThe workspace's tasks, most recently active first, with status and pull request.
get_taskworkspace_id, task_idOne task's status, title, repository and pull request.
start_taskworkspace_id, prompt, title, repo, idempotency_keyStarts a task. Omit repo to let the agent choose.
follow_upworkspace_id, task_id, text, idempotency_keySends a message on a task started from the CLI, MCP or API.
get_messagesworkspace_id, task_id, after, limit (1–200)The task's conversation, oldest first. Pass after to read only what's new.
stop_taskworkspace_id, task_idStops a running task's current turn. Any member of the workspace can stop a task.

Each tool also carries a display title and hints about what it does: whoami, list_tasks, get_task and get_messages only read; start_task and follow_up start work that can push to GitHub; stop_task interrupts work in progress. Clients such as Claude can use the hints to decide which calls need your approval.

Tools return the same JSON as the HTTP API, as text and as structured content. A refused call, including an input-validation failure, returns a tool execution error (isError: true) that says what to fix.

The server answers each request with plain JSON; it opens no event stream and keeps no session. Modern clients use protocol version 2026-07-28 without an initialization handshake: include params._meta["io.modelcontextprotocol/protocolVersion"] on each request, a matching MCP-Protocol-Version header, and Mcp-Method matching the JSON-RPC method. For tools/call, also send Mcp-Name matching params.name (the =?base64?…?= encoding is supported). server/discover returns supported versions, capabilities and server information. Modern results include resultType: "complete" and server information in _meta; discovery and tool lists have a one-hour, private cache lifetime. Unsupported metadata versions receive HTTP 400 / -32022 with all supported versions; missing or mismatched headers receive HTTP 400 / -32020. Modern initialize, ping and other unknown methods receive HTTP 404 / -32601.

Modern requests must also include params._meta["io.modelcontextprotocol/clientCapabilities"] (use {} when no client capabilities are needed). Omitting it returns HTTP 400 / -32602 before tool execution.

Requests without the protocol-version metadata keep the legacy behavior: initialize supports 2025-11-25, 2025-06-18, 2025-03-26 and 2024-11-05, negotiating an unknown version to 2025-11-25. Send the negotiated version in the MCP-Protocol-Version header on subsequent legacy requests. Mcp-Session-Id and Last-Event-ID are ignored; GET and DELETE return HTTP 405 in both eras.

If an MCP request includes an Origin header, it must be a well-formed HTTPS origin or an HTTP loopback origin (127.0.0.1, localhost or [::1], with an optional port). Other values, including null, receive HTTP 403 with a JSON-RPC error and no id, before authentication. CLI and server-side clients can omit Origin. Authentication remains bearer-only; browser cookies do not authenticate MCP requests.

HTTP API

The base URL is https://api.sinatra.dev. Send your token on every request:

Authorization: Bearer sntr_...

Request and response bodies are JSON. The examples below set SINATRA_TOKEN to your token and WS to a workspace id.

Who am I

GET /v1/me returns the token's user and the workspaces they belong to. Use it to find workspace ids.

curl -s https://api.sinatra.dev/v1/me -H "Authorization: Bearer $SINATRA_TOKEN"
{
  "user": { "id": "cmabc...", "githubLogin": "octocat", "name": "Mona" },
  "workspaces": [{ "id": "cmxyz...", "name": "acme", "role": "member" }]
}

Revoke the token

DELETE /v1/me/token revokes the token that sends it and returns 204 with no body. Requests with that token return 401 invalid_token afterwards. sinatra logout calls it when you logged in with a token.

curl -s -X DELETE https://api.sinatra.dev/v1/me/token -H "Authorization: Bearer $SINATRA_TOKEN"

The task object

Task endpoints return tasks in this shape:

{
  "id": "api3f9c...",
  "status": "implementing",
  "source": "api",
  "title": "Fix the flaky retry test",
  "repo": "acme/web",
  "pullRequest": { "url": "https://github.com/acme/web/pull/42", "merged": false },
  "queued": false,
  "createdAt": "2026-10-13T14:02:11.000Z",
  "lastActivityAt": "2026-10-13T14:09:47.000Z"
}

source is api for tasks started from the CLI, MCP or API, linear for Linear issues, and github_issue for GitHub issues. Other sources can appear, so treat an unknown value as a task started elsewhere. title can be null for a task whose issue has no title. repo is null until a repository is chosen, and pullRequest is null until one opens. queued is true while a turn waits because your workspace is running as many tasks at a time as its plan allows, which the dashboard shows as Queued. See What the states mean for status values.

List tasks

GET /v1/workspaces/:id/tasks lists the workspace's tasks, most recently active first.

QueryDefaultDescription
statusOnly tasks in this status, such as implementing or pr_open.
limit501 to 100.
cursornextCursor from the previous page.
curl -s "https://api.sinatra.dev/v1/workspaces/$WS/tasks?status=pr_open&limit=20" \
  -H "Authorization: Bearer $SINATRA_TOKEN"
{ "tasks": [{ "id": "api3f9c...", "status": "pr_open", "...": "..." }], "nextCursor": "..." }

To get the next page, pass nextCursor back as cursor. Treat it as opaque. nextCursor is null on the last page.

Get a task

GET /v1/workspaces/:id/tasks/:taskId returns one task. It works for any task in the workspace, wherever it started.

Start a task

POST /v1/workspaces/:id/tasks starts a task and returns 202 with the task and the id of your first message.

FieldRequiredDescription
promptYesWhat to do, as you'd ask a teammate. Up to 100,000 characters.
titleNoUp to 200 characters. Defaults to the prompt's first line, up to 80 characters.
repoNoowner/name of a repository connected to the workspace. Omit it to let the agent choose.
curl -s -X POST "https://api.sinatra.dev/v1/workspaces/$WS/tasks" \
  -H "Authorization: Bearer $SINATRA_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: fix-retry-test-1" \
  -d '{"prompt": "Fix the flaky retry test in packages/api", "repo": "acme/web"}'
{ "task": { "id": "api3f9c...", "status": "pending", "source": "api", "...": "..." }, "messageId": "fix-retry-test-1" }

Send a message

POST /v1/workspaces/:id/tasks/:taskId/messages sends a follow-up and returns 202 with the message's id. Send {"text": "..."}, up to 100,000 characters.

Any member of the workspace can follow up on a task started from the API, CLI or MCP. Follow up on other tasks, such as Linear or GitHub issues, where they started; here they return 409 not_an_api_task. A task that has been idle for a while picks up again when you follow up.

curl -s -X POST "https://api.sinatra.dev/v1/workspaces/$WS/tasks/$TASK/messages" \
  -H "Authorization: Bearer $SINATRA_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: reply-2" \
  -d '{"text": "Also update the docs"}'
{ "messageId": "reply-2" }

Read the conversation

GET /v1/workspaces/:id/tasks/:taskId/messages returns the conversation of a task started from the API, CLI or MCP, oldest first.

QueryDefaultDescription
afterThe id of the last message you've read. Returns only later messages.
limit1001 to 200.
{
  "messages": [
    { "id": "cmm1...", "messageId": "fix-retry-test-1", "role": "user", "kind": "message", "body": "Fix the flaky retry test in packages/api", "createdAt": "2026-10-13T14:02:11.000Z" },
    { "id": "api:7d2e...", "messageId": null, "role": "agent", "kind": "clarify", "body": "Should the retry keep its 3-attempt limit?", "createdAt": "2026-10-13T14:06:30.000Z" }
  ],
  "next": null
}
rolekindWhat it is
usermessageA message a person sent. messageId is its idempotency key, or a generated id.
agentresponseThe agent's answer for a turn.
agentclarifyA question the agent needs answered to continue.
platformresponse or errorA message from Sinatra itself, such as a refusal, an error, a stop, or files the agent produced.

When next isn't null, more messages follow: pass next as after to get the next page. An after that isn't a message of this task returns 400 invalid_cursor.

Stop a task

POST /v1/workspaces/:id/tasks/:taskId/stop stops the task's running turn and returns 202 with {"accepted": true}. It works on any task in the workspace, and any member can stop one. A completed or cancelled task, or one whose session has already ended, returns 409 not_running.

Idempotency

Send an Idempotency-Key header on POST .../tasks and POST .../messages so a retry is safe. The key is 1 to 128 printable ASCII characters, with no spaces.

  • A start retried with the same key returns the same task and never starts a second one. The same key with a different prompt, repository or title returns 409 idempotency_conflict.
  • A message retried with the same key is sent once. Keys belong to their sender: two people using the same key send two messages.
  • The key becomes the message's messageId.

If Sinatra saves your request but can't hand it to the agent yet, it returns 503 unavailable. On a start, the body includes the new taskId and messageId: retry with the same key, or send a message on that task, which sends your prompt first. On a message, retry with the returned messageId as the Idempotency-Key so it's sent once.

Errors

Errors return a JSON body such as {"error": "invalid_prompt"}.

StatuserrorMeaning
400invalid_promptprompt or text is missing, empty, or over 100,000 characters.
400invalid_titletitle is empty or over 200 characters.
400invalid_repoThe repository isn't connected to this workspace.
400invalid_idempotency_keyThe Idempotency-Key isn't 1 to 128 printable characters.
400invalid_statusNo task status has that name.
400invalid_limitlimit is out of range.
400invalid_cursorcursor is malformed, or after isn't a message of this task.
401invalid_tokenThe token is missing, malformed, expired or revoked.
404not_foundNo such task, or you aren't a member of the workspace.
409not_runningThe task isn't running.
409not_an_api_taskThe task started elsewhere, such as Linear or GitHub; it has no conversation here.
409idempotency_conflictThe key was already used with different content.
429too_many_runningYou already have 5 CLI, MCP or API tasks starting, running or queued in this workspace. The body carries the cap as a number: "limit": 5.
429bad_requestOver the request rate limit. Wait for the Retry-After header.
503unavailableSinatra couldn't reach the task. Retry with the same key.

A workspace you don't belong to answers 404, never 403, so the API doesn't reveal which workspaces exist.

Limits

  • Running tasks: up to 5 tasks you started from the CLI, MCP or API, per workspace, starting, running or queued at once. Tasks started elsewhere, such as from Linear or GitHub, don't count, and neither does a task waiting for your reply. Messages on existing tasks aren't capped.
  • Request rate: per token and per endpoint, 600 GET requests a minute and 120 POST or DELETE requests a minute. Over that, the API returns 429 with a Retry-After header. Every MCP call goes to one endpoint, so all MCP calls, including polling get_messages, share one budget of 120 a minute.
  • Message size: 100,000 characters per prompt or message.
  • Tokens: expire after at most 365 days.

How replies work

The conversation holds final answers and questions, not progress. The agent posts one reply per turn: its answer, or a question when it needs your input. Sinatra adds its own notices, such as errors and refusals. Thoughts and progress updates aren't posted.

The conversation is also on the task's page in the dashboard, where any member of the workspace can read it and reply.

There is no streaming. To wait for a reply, poll GET .../messages with after set to the last message id you read, and check the task's status with GET .../tasks/:taskId. The turn is done when the task stops working, for example when it reaches replied or pr_open. The CLI polls every three seconds.

A message sent while the agent is working doesn't change the current turn. It waits and runs as the next turn. While a task is queued, its conversation stays quiet. Its queued field says so, and the dashboard shows it as Queued.

Tasks started this way count against your workspace's plan like any other task. A running turn takes one of the plan's tasks at a time; past that it's queued, and if the queue is full, Sinatra says so in the conversation instead of starting it. See How the queue works. It also needs model access, like a task you delegate from Linear or GitHub. See Models.