Scripting

Use these patterns to pass inputs, page through results, and handle command outcomes.

Reading input from files and stdin

Any flag that takes JSON or a list also accepts @path to read a file, or @- to read stdin.

studio workflows import --workflow @wf.json
studio tables rows query tbl_9f3c1a05d4b7426e8c2f0917ab35de64 --filter @filter.json
cat wf.json | studio workflows import --workflow @-

List flags

Primitive lists take space-separated values. With @, the file supplies one value per line:

studio files mv --file-ids wf_3Qm8ZtLpR2yVnKd7BsXwC wf_5Hn1JvTqW9xUcMb4RzPgL --to Archive
studio files mv --file-ids @file-ids.txt --to Archive
printf 'wf_3Qm8ZtLpR2yVnKd7BsXwC\nwf_5Hn1JvTqW9xUcMb4RzPgL\n' | studio files mv --file-ids @- --to Archive

Arrays of objects stay JSON.

Passing a literal leading @

Because @ introduces a file reference, a value that genuinely starts with one is written @@. Only the leading @ is dropped, and every @-aware flag accepts the escape:

studio files share set wf_3Qm8ZtLpR2yVnKd7BsXwC --is-active true --auth-type email --allowed-emails @@example.org
studio secrets set API_HOST --scope workspace --value @@internal

Without it, --allowed-emails @example.org can only be read as a request to open a file named example.org.

Filtering table rows

--filter takes the same predicate tree the API uses: all (AND) or any (OR) groups of {field, op, value} conditions, nestable.

studio tables rows query tbl_9f3c1a05d4b7426e8c2f0917ab35de64 \
  --filter '{"all":[{"field":"status","op":"eq","value":"open"},
                    {"field":"score","op":"gt","value":10}]}' \
  --limit 50

Operators: eq, ne, gt, gte, lt, lte, in, nin, contains, ncontains, startsWith, endsWith, like, ilike, nlike, nilike, isEmpty, isNotEmpty, isNull, isNotNull.

--sort is also JSON, an ordered list of keys:

studio tables rows query tbl_9f3c1a05d4b7426e8c2f0917ab35de64 --sort '[{"field":"createdAt","direction":"desc"}]'

Pagination

Resource lists and directory ls follow every page by default. Pass --limit N to cap the returned items:

studio workflows list --output json
studio files ls /Reports --limit 50

Table rows (including queries), logs, audit/billing events, workflow runs and versions, and knowledge documents/chunks retain a default limit of 100. Connector document lists also use this cap. Use --limit 0 to fetch every page of a large dataset:

studio logs list --limit 0 --output json > all-logs.json

Pages are fetched sequentially, accumulated in memory, and printed as one result. A large table can consume substantial memory; use filters or an explicit limit when you only need a subset. Each API request fetches at most 100 items.

Paginated JSON and YAML results have the shape { data: [...], nextCursor }. nextCursor is the cursor after the last returned row, or null when all pages have been fetched. Scripts that read list rows should use .data[]:

studio logs list --output json | jq -r '.data[].runId'

The capped dataset commands accept --cursor to resume from the previous result's nextCursor. Keep the same resource, filters, and sort order, and stop when the cursor is null:

studio tables rows list tbl_9f3c1a05d4b7426e8c2f0917ab35de64 --limit 100 --output json
studio tables rows list tbl_9f3c1a05d4b7426e8c2f0917ab35de64 --limit 100 --cursor "$nextCursor" --output json

--limit applies to each invocation starting at its cursor. --limit 0 with --cursor fetches every remaining page. Table-row queries, logs (including billing and audit logs), workflow run/version histories, and knowledge document/chunk lists all support this continuation.

Destructive commands

Deletions require an explicit selector and --yes. There is no "delete everything" default:

studio tables rows batch-delete tbl_9f3c1a05d4b7426e8c2f0917ab35de64 --row row_2f81c0a94db54e6f8a13c7e0526bd94a row_6b3e59d0af1c42d7b80e94f3a271c568 --yes
studio files delete wf_8Kd2NpVrY6zTfQa3XwBmS --yes

Without --yes the command explains what it would have destroyed and stops.

On batch-delete and batch-update, --limit has no default and is not a page size — it is a ceiling on how many matching rows the one call may touch. Leave it off and the command acts on every row the filter matches, however many that is. --limit 0 is not the unbounded form here and is rejected; pass a whole number of 1 or more to cap the blast radius, or omit the flag deliberately.

Exit codes

CodeMeaning
0Success
1API/configuration/argument failure, or a failed workflow when using workflows runs wait
2whoami: no verification verdict; workflows runs wait: the run was cancelled
3workflows runs wait: the run is paused for human input
4workflows runs wait: the wait timed out
130Interactive secret entry was cancelled with Ctrl-C

Errors print one line to stderr, prefixed Error:, plus the API's error code and validation details when it supplies them. Failures are safe to branch on:

if ! studio workflows run 3a9e21d8-5f47-4c0b-b2ea-91d7c6034ef8 --output json > result.json; then
  echo "run failed" >&2
  exit 1
fi

An unexpected error prints a stack trace — that is a bug in the CLI, so please report it to help@seeyu.ai.

studio whoami splits its failure in two because the fixes differ: 1 means the credentials are wrong and a fresh studio login is the answer, while 2 means the CLI never got a verdict — no workspace to check against, or an endpoint that did not answer — and logging in again would not help.

studio whoami > /dev/null
case $? in
  0) ;;                                  # ready
  1) echo "run: studio login" >&2; exit 1 ;;
  2) echo "endpoint unreachable, retrying later" >&2; exit 75 ;;
esac

Selecting workflow output

--select-output shapes a streamed result, so it requires --follow. It takes blockName.field selectors; fields that a run did not produce are simply omitted:

studio workflows run 3a9e21d8-5f47-4c0b-b2ea-91d7c6034ef8 --follow --select-output agent_1.content --output json

Without --follow the CLI refuses the pair rather than spending a request on a response that carries no outputs, and --async cannot be combined with it either — there is no stream to shape. To narrow a run that has already finished, read it back with workflows runs get, which matches block ids rather than the block names workflows run takes:

studio workflows runs get "$run_id" --workflow 3a9e21d8-5f47-4c0b-b2ea-91d7c6034ef8 \
  --select-output 1d4c8f02-7b63-4a19-8e52-63f0a7c5d9b1.content --output json

Polling a long run

Start the run asynchronously, then use the CLI’s wait command:

run_id=$(studio workflows run 3a9e21d8-5f47-4c0b-b2ea-91d7c6034ef8 --async --output json | jq -r '.runId')
studio workflows runs wait "$run_id" --workflow 3a9e21d8-5f47-4c0b-b2ea-91d7c6034ef8 --wait-timeout 3600 --output json

The command polls until the run completes, fails, is cancelled, or pauses for human input. Time-based pauses continue waiting. Its exit code identifies the outcome; --wait-timeout 0 waits indefinitely.

Use studio logs get "$run_id" --trace for full diagnostics. A run paused for human input includes the context ID needed by studio workflows runs resume.

Working with folders

Every folder-backed resource — workflows, tables, files, knowledge — shares the same path commands:

studio tables ls Reports
studio tables mkdir Reports/Quarterly
studio tables folders mv Reports/Quarterly Archive/Quarterly
studio tables folders delete Archive --recursive --yes

ls lists the resources at a path plus that folder's direct children, never deeper. Its ref column is the value to pass to the next command. Use list for resources only, or folders ls for folders only. A leading / is optional.

A nightly job, end to end

nightly-digest.sh
#!/usr/bin/env bash
set -euo pipefail

export STUDIO_ENDPOINT="${STUDIO_ENDPOINT:-https://agent-studio.seeyu.ai}"
export STUDIO_API_KEY="${STUDIO_API_KEY:?missing}"
export STUDIO_WORKSPACE="${STUDIO_WORKSPACE:?missing}"
export STUDIO_OUTPUT=json

run_id=$(studio workflows run 3a9e21d8-5f47-4c0b-b2ea-91d7c6034ef8 --input '{"source":"nightly"}' | jq -r '.runId')

if [ "$(studio workflows runs get "$run_id" --workflow 3a9e21d8-5f47-4c0b-b2ea-91d7c6034ef8 | jq -r '.status')" != "completed" ]; then
  studio logs get "$run_id" >&2
  exit 1
fi