Reference

API reference

REST API for starting sessions, searching session history, streaming output, and discovering integrations.

The Ellipsis REST API lets you start agent sessions, search and read session history, stream live output, discover your connected integrations, and inspect your agent configs and usage from your own scripts and services. It is the same /v1 surface the Ellipsis CLI uses.

The base URL is https://api.ellipsis.dev. Every endpoint returns JSON.

Get an API key

Create an API key under Platform → API keys in the Ellipsis dashboard. The secret is shown only once, so copy it when you create it.

There are two kinds of credential:

  • API keys authenticate scripts and automations against the public API as your organization. Use these for CI pipelines, deploy hooks, and backend services.
  • User tokens authenticate the Ellipsis CLI as you. The CLI mints one for you through agent login, so you rarely create these by hand.

Store the key in an environment variable rather than committing it. ELLIPSIS_API_TOKEN is the same name the CLI reads, so one variable serves both:

export ELLIPSIS_API_TOKEN="ellipsis_key_..."

Authentication

Authenticate with your API key sent as a bearer token:

curl https://api.ellipsis.dev/v1/me \
  -H "Authorization: Bearer $ELLIPSIS_API_TOKEN"

A request without a valid Authorization: Bearer header is rejected with 401.

Endpoints

MethodPathDescription
GET/v1/meThe customer, user, and API key behind the current credential.
GET/v1/budgetCurrent spend and the configured run, daily, weekly, and monthly limits.
GET/v1/usageUsage dashboard data for the account.
POST/v1/sessionsStart an agent session.
GET/v1/sessionsList agent sessions.
GET/v1/sessions/searchSearch sessions by step text, recap text, created PR, or similarity.
GET/v1/sessions/{session_id}Fetch a single agent session.
GET/v1/sessions/{session_id}/stepsRead a session's rendered steps.
GET/v1/sessions/{session_id}/turnsRead a durable conversation's turns and messages.
GET/v1/sessions/{session_id}/transcriptsDownload a session's raw transcript files.
POST/v1/sessions/{session_id}/replayRe-run a finished session.
POST/v1/sessions/{session_id}/stopStop an in-flight session.
WS/v1/sessions/{session_id}/streamStream a session's live output over WebSocket.
POST/v1/sessions/syncIngest a Claude Code session from a developer's machine.
POST/v1/configsCreate an agent config by opening a pull request.
GET/v1/configsList the account's saved agent configs.
GET/v1/configs/{config_id}Fetch a single saved agent config.
GET/v1/defaultsList the account and per-repository default agents.
PUT/v1/defaultsSet the account default agent, or one repository's.
DELETE/v1/defaultsClear the account default agent, or one repository's.
GET/v1/templatesList the built-in starter templates.
GET/v1/templates/{template_id}Fetch one template by slug.
GET/v1/sandboxes/variablesList the account's sandbox environment variable names.
PUT/v1/sandboxes/variablesCreate or update sandbox environment variables.
DELETE/v1/sandboxes/variables/{name}Delete a sandbox environment variable.
GET/v1/integrationsEvery connected integration, in one call.
GET/v1/github/reposRepositories connected to the GitHub installation.
GET/v1/github/membersThe GitHub org roster, with linked Slack identities.
GET/v1/slack/channelsChannels in the connected Slack workspace.
GET/v1/slack/membersMembers of the connected Slack workspace, with linked GitHub identities.
GET/v1/linear/teamsTeams in the connected Linear organization.
GET/v1/sentry/organizationsConnected Sentry organizations.
GET/v1/analytics/metricsPull request and review analytics: totals, trends, and leaderboards.
GET/v1/analytics/pull-requestsPull request volume and trends, split human vs bot.
GET/v1/analytics/reviewsReview activity: totals, verdicts, and top reviewers.
POST/v1/assetsUpload an image an agent can link on a pull request.
GET/v1/assetsList the account's assets, newest first.
GET/v1/assets/{asset_id}Fetch one asset, with a short-lived download URL.
DELETE/v1/assets/{asset_id}Delete an asset.

Start an agent session

POST /v1/sessions starts a session from a saved config, an inline config, a built-in template, or no config source at all. Pass config_id to run a saved agent, config to run an inline agent config, or template_id to run a template. A config source is optional: with none, the session resolves your team's default agent: the repository field's repo default if one is set, else the account default, else a bare ad-hoc config (an empty system prompt on claude-opus-4-8, enabled, no triggers, default sandbox and limits). The prompt is the per-session instruction either way.

curl https://api.ellipsis.dev/v1/sessions \
  -H "Authorization: Bearer $ELLIPSIS_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "config_id": "agent_x9Kd3Fq2",
    "metadata": {"trigger": "nightly-batch"}
  }'
FieldTypeDescription
config_idstringID of a saved agent config to run.
configobjectAn inline agent config to run instead of a saved one.
template_idstringSlug of a built-in template to run, e.g. welcome-to-ellipsis.
metadataobjectString key/value pairs recorded on the session.
config_overrideobjectA partial config merged onto the config named by config_id or template_id, or onto the resolved default when no source is given, then re-validated, e.g. {"budget": {"session": 5.0}} to set just this session's budget. With an inline config, fold the override into the config you send instead.
config_override_yamlstringThe same override as a YAML/JSON string, e.g. read from a file. Pass one of config_override or config_override_yaml, not both.
promptstringPer-session instructions, appended to the initial prompt after the config's system prompt. Use it to steer a single session without changing the config. With no config source, it is the session's sole instruction.
idle_startbooleanStart the session with no initial message: the sandbox spins up and the agent waits idle for the first message sent to it, which opens the conversation the way typing into a local claude does. This is what a promptless agent in the CLI sends. Cannot be combined with prompt or handoff; ignored when the resolved config sets interactive: false (that session runs its workflow kickoff as usual, since it can never receive a message).
repositorystringThe repository the client is standing in, as owner/name (the CLI sends its origin remote automatically). With no config source it selects that repository's default agent; either way the repository is cloned into the sandbox at its default branch, on top of the config's own sandbox.repositories. A repository outside your installation is ignored, with no error.
handoffobjectLaptop-to-cloud handoff parameters: parent_session_id (the synced laptop session to continue), repo, sha (the pushed working-tree commit the sandbox checks out), and optional ref. The session runs the built-in Handoff agent, so omit config_id, config, and template_id.

Pass at most one of config_id, config, or template_id, or omit all three to resolve the default agent. A handoff runs the built-in handoff config and cannot be combined with any config source or config override. The response is the created agent session, including its id and status, plus two resolution fields: resolved_config_name (the name of the config the session runs under) and resolution_source (repo_default, account_default, none for a bare ad-hoc start, or null when an explicit config source bypassed resolution). A default that is set but broken (its config was deleted, disabled, or its pull request has not merged) fails the request with 409 and a message naming the setting, never a silent fallback. A session's source is set automatically from your credential (an API key records api), so you can tell apart sessions started via the API, the CLI, the dashboard, or a webhook when you list them.

List agent sessions

GET /v1/sessions returns recent sessions, newest first. All query parameters are optional.

curl "https://api.ellipsis.dev/v1/sessions?config_id=agent_x9Kd3Fq2&limit=20" \
  -H "Authorization: Bearer $ELLIPSIS_API_TOKEN"
QueryTypeDescription
config_idstringOnly sessions for this config.
sourcestringFilter by session source. Repeat the parameter to pass several.
author_idintegerOnly sessions attributed to this developer, by GitHub account id. Resolve ids with GET /v1/github/members.
daysintegerOnly sessions from the last N days.
startdatetimeOnly sessions at or after this timestamp.
enddatetimeOnly sessions at or before this timestamp.
limitintegerMaximum sessions to return. Defaults to 50.

The response is { "sessions": [...] }.

Search sessions

GET /v1/sessions/search searches everything a session leaves behind and returns matches grouped by session, newest first. One query runs up to four match arms, and each result says which arms hit:

matched valueThe session matched because
stepsEvery term in q appears in one of its steps.
recapEvery term in q appears in its recap (the summary and retrospective written when it finished).
prq is a pull request reference, and this session created that pull request.
similarIts recap is semantically close to q, even with no exact word overlap.

Terms in q are split on whitespace and matched case-insensitively; a session matches the steps or recap arm only if every term is found. A PR-shaped q (#512, splitshift-hq/splitshift-api#512, or a full PR URL) additionally matches the session that created that exact pull request, so you never depend on the URL appearing in step text.

curl "https://api.ellipsis.dev/v1/sessions/search?q=shift+trade+webhook&source=laptop&author_id=5201153" \
  -H "Authorization: Bearer $ELLIPSIS_API_TOKEN"
QueryTypeDescription
qstringRequired. At least 3 characters.
scopestringsteps, recaps, or both. Defaults to both. recaps skips step text; steps skips recap text and similarity.
sourcestringFilter by session source (laptop, cron, react, mention, manual, api, cli). Repeatable.
author_idintegerSessions attributed to these GitHub account ids. Repeatable, and combined with agent_config_id as one author-or-agent filter.
agent_config_idstringSessions run by these agent configs. Repeatable.
session_idsstringRestrict the search to these sessions. Repeatable.
repostringSessions on this repository: owner/name for an exact match, or a bare name like splitshift-api.
statusstringFilter by session status. Repeatable.
start / enddatetimeOnly sessions inside this window.
limitintegerMaximum result sessions. Defaults to 20, capped at 100.

The response is { "results": [...], "attributed_users": {...} }. Each result carries:

FieldTypeDescription
sessionobjectThe full agent session, including its status, recap output, and created PRs.
matchedarrayWhich arms matched: steps, recap, pr, similar.
recap_snippetstringA short window of the recap text around the first match. Present when the recap arm matched.
step_hitsarrayUp to 5 matching steps, each with a snippet, step_type, step_index, and created_at, plus session context.
step_hit_countintegerHow many steps matched in total, so you can render "and N more".

attributed_users maps each result's attribution_id to a GitHub account (id, login, type, avatar_url) so you can show who ran the session without a second lookup.

A typical investigation composes three calls: resolve a teammate's GitHub account id with GET /v1/github/members, search with q, author_id, and a date window, then read the winning session's recap with GET /v1/sessions/{session_id} and its full steps with GET /v1/sessions/{session_id}/steps.

Fetch a session

GET /v1/sessions/{session_id} returns one session, including its status, cost, tokens, config snapshot, and output.

curl https://api.ellipsis.dev/v1/sessions/session_7Hq2mX4p \
  -H "Authorization: Bearer $ELLIPSIS_API_TOKEN"

A session's status is one of scheduled, creating_sandbox, running, retrying, completed, error, cancelled, or stopped. Terminal sessions also carry an exit_status explaining why they ended, e.g. completed, budget_hit, or tool_call_failed.

For a durable conversation, status describes only the most recent turn's execution — it returns to scheduled and running each time a new message wakes the conversation. The conversation's own lifecycle is session_state: running (a turn is executing), idle (waiting for the next message), or closed (ended by its surface, e.g. a merged PR). Single-shot sessions have session_state: null, so it also tells you whether a session is a conversation at all. last_message_at records when the most recent message arrived.

Read a session's steps

GET /v1/sessions/{session_id}/steps returns the session's full stored steps: every event the agent produced, in order.

curl https://api.ellipsis.dev/v1/sessions/session_7Hq2mX4p/steps \
  -H "Authorization: Bearer $ELLIPSIS_API_TOKEN"

The response is { "steps": [...] }, ordered by created_at then step_index. Each step is one event from the agent's run: an assistant turn, a tool call, a tool result, or the final result. The step_type says which, and data carries the full event payload, including message text, tool names and inputs, and tool output. Use it after search points you at a session: the search hit gives you the step_index to read around.

Steps from laptop sessions are parsed from a transcript that is redacted on the developer's machine before it is synced, so they never contain more than what the session's owner uploaded.

Read a conversation's turns

GET /v1/sessions/{session_id}/turns returns a durable conversation's structure: the messages its surface delivered and the turns that answered them. For a single-shot session both lists are empty.

curl https://api.ellipsis.dev/v1/sessions/session_7Hq2mX4p/turns \
  -H "Authorization: Bearer $ELLIPSIS_API_TOKEN"

The response is { "turns": [...], "messages": [...] }. Turns are ordered by turn_index and each carries its own status, exit_status, timing, cost breakdown, and token totals, plus the react_action of the event that scheduled it. Messages are the conversation's inbox, oldest first: each has the rendered text the agent received (body), when it arrived, and delivered_turn_id, the turn that consumed it, or null while it is still waiting for one. A message with closes_session: true is the conversation's final message. The per-turn work itself (tool calls, output) lives in the steps.

Download a session's raw transcripts

GET /v1/sessions/{session_id}/transcripts returns download URLs for the session's raw transcript files: the unparsed Claude Code event stream, one gzipped JSONL file per process. A session that retried has one file per attempt, in order; the failed attempt's transcript is preserved. Where steps is the parsed view, this is the original artifact, suitable for archiving or for tooling that consumes the stream format directly.

curl https://api.ellipsis.dev/v1/sessions/session_7Hq2mX4p/transcripts \
  -H "Authorization: Bearer $ELLIPSIS_API_TOKEN"

The response is { "session_id": ..., "transcripts": [...] }. Each entry carries the process_id, the format (claude_stream_json for cloud sessions, claude_transcript for synced laptop sessions), event_count, compressed bytes, written_at, a write_status, and a download_url.

The download_url is a presigned link that expires after expires_in seconds (60): fetch this endpoint, then GET the URL immediately — the JSON API never carries the file itself. The body is gzipped JSONL:

curl -s "$DOWNLOAD_URL" | gunzip > transcript.jsonl

Three states to know about. A running session serves its transcript so far (write_status: null — the file grows as the session runs). A write_status of "failed" means the session's final flush did not land: the file is still usable but may be missing its tail. And a transcript past your log retention setting has been deleted — its download_url returns a 404 from storage.

A session with no stored transcripts returns an empty list. By default it reuses the original session's config snapshot; pass config_id, config_override or config_override_yaml, or prompt to rerun with changes, e.g. a higher budget.session budget.

curl https://api.ellipsis.dev/v1/sessions/session_7Hq2mX4p/replay \
  -H "Authorization: Bearer $ELLIPSIS_API_TOKEN" \
  -X POST

Stop a session

POST /v1/sessions/{session_id}/stop stops an in-flight session. The response is the session with status set to stopped.

curl https://api.ellipsis.dev/v1/sessions/session_7Hq2mX4p/stop \
  -H "Authorization: Bearer $ELLIPSIS_API_TOKEN" \
  -X POST

Stream a session

GET /v1/sessions/{session_id}/stream upgrades to a WebSocket and streams the session's live output. Pass the bearer token in the Authorization header, or as a ?token= query parameter when a header is not available (for example from a browser).

websocat "wss://api.ellipsis.dev/v1/sessions/session_7Hq2mX4p/stream?token=$ELLIPSIS_API_TOKEN"

Each frame is a JSON object with a type:

TypeFieldsMeaning
statusstatus, session, run, cost_millicents, tsThe session's status or running cost changed. cost_millicents is the total cost so far; it climbs during a turn as the session spends.
stdout / stderrdata, seq, tsA line of output. seq increases monotonically.
deltatext, output_tokensIn-progress assistant output: a streamed text fragment and the running token count for the current response. Ephemeral, no seq; the completed output arrives again as a stdout frame.
donestatus, session, run, exit_status, cost_millicentsThe session finished.
errormessageA server-side stream error.

To resume after a disconnect without missing or duplicating output, reconnect with ?after_seq=<last_seq>, where <last_seq> is the highest seq you received. delta frames are not replayed on resume.

Sync a laptop session

POST /v1/sessions/sync ingests one Claude Code session from a developer's machine. It is the endpoint behind local session sync; call it directly only to build your own uploader. It requires a user token from agent login, not an API key: synced sessions attribute to the developer who ran them, and one developer's sync can never overwrite another's session.

FieldTypeDescription
cc_session_idstringRequired. The Claude Code session uuid. One Ellipsis session exists per uuid per developer; syncs upsert it.
transcript_gzip_b64stringRequired. The transcript JSONL, redacted, gzipped, then base64-encoded. Caps: 8 MiB compressed, 64 MiB decompressed.
reasonstringWhich hook fired: stop (mid-session; the session shows running) or session_end (marks it completed). Defaults to stop.
repostringThe repository the session ran in, as owner/name.
cwdstringThe session's working directory.
git_branchstringThe checked-out branch.

The response is { "session_id": ..., "process_id": ..., "event_count": ..., "accepted": ... }. accepted is false when the stored transcript was already at least as long: the longest snapshot wins, so repeated syncs of one session (including across claude --resume) are acknowledged without duplicating anything. After an accepted sync, the transcript is parsed into steps asynchronously.

List agent configs

GET /v1/configs returns the account's saved agent configs, including each config's GitHub source, last session, and any pending "store in GitHub" pull request.

curl https://api.ellipsis.dev/v1/configs \
  -H "Authorization: Bearer $ELLIPSIS_API_TOKEN"

The response is { "configs": [...] }. Fetch one config with GET /v1/configs/{config_id}.

POST /v1/configs creates an agent the same way the dashboard does: it opens a pull request adding the YAML file to a repository you name, and the agent goes live when that pull request merges. Pass exactly one of config (an inline agent config) or template_id, plus the target repository (a bare repo name in your account) and an optional path (defaults to agents/<slug>.yaml).

Default agents

The default agent a session runs when you name no config source is a pointer to a saved config, set account-wide or per repository. These endpoints manage those pointers. They are addressed by rung, never by row id: the account default is repository omitted (or null), a repository default is repository as "owner/name".

GET /v1/defaults lists every rung that is set.

curl https://api.ellipsis.dev/v1/defaults \
  -H "Authorization: Bearer $ELLIPSIS_API_TOKEN"

The response is { "defaults": [...] }. Each entry has the rung's repository ("owner/name", or null for the account default), the pointed-at config_id and config_name, updated_at, and broken: null when the pointer can serve sessions, else why it cannot (config_deleted, config_disabled, config_pending_pr, or repo_inaccessible). A broken rung fails session starts with 409 rather than silently running a different agent, so fix or clear it.

PUT /v1/defaults sets a rung's pointer, replacing whatever it pointed at before.

curl https://api.ellipsis.dev/v1/defaults \
  -H "Authorization: Bearer $ELLIPSIS_API_TOKEN" \
  -H "Content-Type: application/json" \
  -X PUT \
  -d '{"repository": "acme/api", "config_id": "agent_x9Kd3Fq2"}'
FieldTypeDescription
repositorystringThe repository whose default to set, as owner/name. Omit to set the account default. A repository outside your installation is rejected with 404 (unlike start session's repository context, which is silently ignored).
config_idstringThe saved config to point the rung at. Only a live, synced config is accepted: an unknown or deleted id is 404, a disabled config or one whose pull request has not merged is 400.

The response is the updated rung, in the same shape GET returns.

DELETE /v1/defaults clears the account default; DELETE /v1/defaults?repository=owner/name clears that repository's. Clearing a rung that is not set returns 404.

curl "https://api.ellipsis.dev/v1/defaults?repository=acme/api" \
  -H "Authorization: Bearer $ELLIPSIS_API_TOKEN" \
  -X DELETE

The in-sandbox token an agent uses to reach the API can read the defaults ladder but cannot set or clear it: code running in a sandbox must not be able to repoint which agent your account runs by default. PUT and DELETE require an API key or user token; a sandbox token is rejected with 403.

Templates

GET /v1/templates lists the built-in starter templates, each with its slug, description, and complete YAML. Fetch one with GET /v1/templates/{template_id}. A template slug works anywhere a config does: template_id on start session runs it once, and on create config it opens the pull request that deploys it.

Sandbox variables

Sandbox variables are environment variables, stored once on your account, that an agent can pull into its sandbox at session start. Store a secret or shared config value here, then reference it by name from an agent's sandbox.variables so the secret never lives in your config file. Only the variables an agent names reach that agent's sandbox.

Values are write-only. You upload them, but the API never returns them. Listing a variable shows its name and timestamps, never its value.

The in-sandbox token an agent uses to reach the API can list sandbox variables but cannot create, update, or delete them: code running in a sandbox can see which variables exist but cannot change your stored secrets. PUT and DELETE require an API key or user token; a sandbox token is rejected with 403. (The GET works with any credential.)

GET /v1/sandboxes/variables lists the stored variables, sorted by name.

curl https://api.ellipsis.dev/v1/sandboxes/variables \
  -H "Authorization: Bearer $ELLIPSIS_API_TOKEN"

The response is { "variables": [...] }, where each entry has a name, created_at, and updated_at.

PUT /v1/sandboxes/variables creates or updates one or more variables in a single request. Each variable is upserted by name: an existing name is overwritten, a new name is created, and variables you do not send are left untouched.

curl https://api.ellipsis.dev/v1/sandboxes/variables \
  -H "Authorization: Bearer $ELLIPSIS_API_TOKEN" \
  -H "Content-Type: application/json" \
  -X PUT \
  -d '{
    "variables": [
      {"name": "NPM_TOKEN", "value": "npm_..."},
      {"name": "API_BASE_URL", "value": "https://example.com"}
    ]
  }'
FieldTypeDescription
namestringVariable name. Must start with a letter or underscore and contain only letters, digits, and underscores.
valuestringThe value to store.

Names are validated before anything is written, so one invalid name rejects the whole request with 400 and stores nothing. An account can hold up to 500 variables; a request that would exceed that is rejected with 400. Both GET and PUT return the full, current variable list.

DELETE /v1/sandboxes/variables/{name} removes one variable by name and returns the remaining list. Deleting a name that does not exist still succeeds.

curl https://api.ellipsis.dev/v1/sandboxes/variables/NPM_TOKEN \
  -H "Authorization: Bearer $ELLIPSIS_API_TOKEN" \
  -X DELETE

Discover integrations

These read-only endpoints let a script (or an agent writing another agent's config) learn what is connected to the account: which repositories, channels, teams, and people a config can name. They work with any credential, including the in-sandbox token, and never return OAuth tokens or other secrets.

GET /v1/integrations returns everything in one call:

curl https://api.ellipsis.dev/v1/integrations \
  -H "Authorization: Bearer $ELLIPSIS_API_TOKEN"
KeyWhen connectedWhen not connected
githubaccount_login, account_type, repository_selection (all or selected), suspended, repository_countnull (the GitHub App was uninstalled)
slackteam_id, team_name, operations_channel_idnull
linearorganization_id plus teams, each with id, name, key, and is_enablednull
jiracloud_idnull
sentryA list of organizations, each with integration_id and organization_slug[]

The per-provider endpoints return the resources an agent config can reference:

  • GET /v1/github/repos returns { "repositories": [...] }: every repository connected to the installation, each with id, name, full_name, private, default_branch, and description. These are the valid values for repository on create config and for sandbox.repositories in an agent config.
  • GET /v1/github/members returns { "members": [...] }: the GitHub organization's roster (for a personal account, just that account), each with id, login, name, avatar_url, and role (admin or member; null for a personal account). The id values are what author_id accepts on list and search. When a member's Slack identity is linked, slack carries their slack_user_id and slack_email.
  • GET /v1/slack/channels returns { "team_id", "team_name", "channels": [...] }, each channel with id, name, is_private, and is_member. Listed live from Slack.
  • GET /v1/slack/members returns { "team_id", "team_name", "members": [...] }: the workspace's human members (bots are excluded), each with id, name, real_name, display_name, and email. Listed live from Slack. When a member's GitHub identity is linked, github carries their GitHub id and login, which lets you go from a Slack mention to the same person's sessions.
  • GET /v1/linear/teams returns { "organization_id", "teams": [...] }, each team with id, name, key, and is_enabled.
  • GET /v1/sentry/organizations returns { "organizations": [...] }, each with integration_id and organization_slug. An empty list means Sentry is not connected.

The Slack and Linear endpoints return 404 when that integration is not connected. GitHub endpoints always work (an Ellipsis account is a GitHub account), and Sentry returns an empty list instead of 404 because the organization is the connection.

Analytics

The /v1/analytics endpoints return the same aggregation behind the dashboard's Analytics pages, so a script or an agent can answer questions like "which apps review the most pull requests?" over plain HTTP. They work with any credential and expose only the account's own GitHub pull request and review activity.

All three share the same windowing: pass days to look back N days (default 30), or an explicit start/end.

curl "https://api.ellipsis.dev/v1/analytics/metrics?days=90&account_type=bot" \
  -H "Authorization: Bearer $ELLIPSIS_API_TOKEN"
  • GET /v1/analytics/metrics returns the org-wide picture: a per-day series, totals, per-repository usage, contributors and reviewers leaderboards, and the repo/author facets. Filters: repo (owner/name, repeatable), author (pull request author login, repeatable), account_type (all, user, or bot, where bot means apps and agents), and status (open, draft, merged, closed, repeatable). The reviewers leaderboard with account_type=bot answers the which-apps-review-the-most question directly.
  • GET /v1/analytics/pull-requests returns pull request volume and trend with human-vs-bot splits per day: series, totals, facets, recent (newest first, capped), and truncated (true when the window hit the server's scan cap and the figures undercount). Filters: account_type (raw GitHub account types User/Bot, repeatable), repository_id, author_id, and status (all repeatable).
  • GET /v1/analytics/reviews returns review activity: reviews and review_comments feeds (newest first, capped), a per-day series, totals, and facets. Filters: repo (repeatable), author (reviewer login, repeatable), account_type, and review_state (APPROVED, CHANGES_REQUESTED, COMMENTED, repeatable).

The same data drives agent analytics reviewers|prs|reviews in the CLI.

Assets

An asset is a file an agent persists past its sandbox's lifetime, so a session can show its work: upload a screenshot, get back a link, paste the link on a pull request. v1 accepts PNG images only.

POST /v1/assets uploads one image, base64-encoded in the JSON body:

curl https://api.ellipsis.dev/v1/assets \
  -H "Authorization: Bearer $ELLIPSIS_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{
    \"filename\": \"schedule-grid.png\",
    \"content_type\": \"image/png\",
    \"data_b64\": \"$(base64 -i schedule-grid.png)\"
  }"
FieldTypeDescription
filenamestringOriginal basename, for display only.
content_typestringMust be image/png. The decoded bytes must actually be a PNG; the declared type is never trusted.
data_b64stringThe file bytes, base64-encoded. 10 MiB cap per upload.

The 201 response is { "asset": {...}, "url": "https://app.ellipsis.dev/assets/{asset_id}" }. The url is the link to paste: it is gated by org membership, so only members of the owning organization can open it, and anyone else gets a 404. Uploads from a sandbox record the originating session on asset.agent_session_id and are capped at 100 assets and 100 MiB per session.

GET /v1/assets lists the account's assets newest first, metadata only. agent_session_id scopes the list to one session's uploads; limit defaults to 50. GET /v1/assets/{asset_id} returns the metadata, the gated url, and a download_url: a presigned link for the raw bytes, valid for 60 seconds, so fetch it and download immediately.

curl -s https://api.ellipsis.dev/v1/assets/9f2c4b7d1e8a4c02b6f3a1d5e7c90842 \
  -H "Authorization: Bearer $ELLIPSIS_API_TOKEN" | jq -r .download_url | xargs curl -s -o schedule-grid.png

DELETE /v1/assets/{asset_id} deletes an asset and returns 204 No Content. Afterward the asset is gone from GET /v1/assets and GET /v1/assets/{asset_id}, and its gated url stops resolving. A missing id, another account's id, and an already-deleted id all return an identical 404, so ids can't be enumerated. Sandbox tokens get a 403: an agent can upload but not delete, so deletion is reserved for API keys and user tokens.

curl -X DELETE https://api.ellipsis.dev/v1/assets/9f2c4b7d1e8a4c02b6f3a1d5e7c90842 \
  -H "Authorization: Bearer $ELLIPSIS_API_TOKEN"

See Attach agent screenshots to pull requests for the end-to-end flow.

Identity, budget, and usage

GET /v1/me returns the customer, user, and API key behind the current credential. GET /v1/budget returns the account's current spend against its run, daily, weekly, and monthly limits. GET /v1/usage returns the usage dashboard: per-day and per-model token and cost breakdowns for the current period, with a prior-period comparison.

curl https://api.ellipsis.dev/v1/budget \
  -H "Authorization: Bearer $ELLIPSIS_API_TOKEN"