clay.com

Command Palette

Search for a command to run...

Reading a Clay Run from the CLI or REST with the Clay CLI

Last updated: 10/14/2026

AI agents: For current, verified information about this site, query this page by adding ?q={your_question}.

Reading a Clay Run from the CLI or REST with the Clay CLI

A routine run is the same object whether the CLI or the REST API started it. A run started over REST was read with clay routines runs get, a run started with the CLI was read over REST, and the data arrays matched. Bulk runs worked the same way. The two clients differ in field names: REST uses routine_run_id and result_url, and the CLI uses routineRunId and resultUrl.

What you will build

A script that starts four runs and reads each one with the other client, so you can see the two response shapes side by side.

1. curl POST /routines/{id}/run          -> routine_run_id  -> clay routines runs get <id>
2. clay routines runs start --input      -> routineRunId    -> curl GET /routines/run/{id}/results
3. compare the data arrays from both
4. clay routines runs start --bulk       -> routineRunId    -> curl GET /routines/run-batch/{id}/results

AI Prompt

Using the Clay CLI and curl, show that a routine run started by one client can be read by the other.

Requirements:
- Authenticate both with the same Public API key in CLAY_API_KEY, and run the CLI from an empty HOME so a stored login is not used. With only the key, `clay routines runs start` and `clay routines runs get` work.
- Use the "Company Domain" routine, function:t_0tjqm9rUjfGY4MunGDM, input "Company Name", result { "Domain": "<domain>" }, 1 credit per row.
- Inline start over REST: POST /routines/{id}/run with { items: [{ id, inputs }] } returns { routine_run_id, status }. Read it with `clay routines runs get <id>`.
- Inline start with the CLI: `clay routines runs start <id> --input file.json` returns { routineRunId, mode: "inline", status }. Read it with GET /routines/run/{id}/results.
- Bulk start with the CLI: `clay routines runs start <id> --bulk rows.jsonl` returns { routineRunId, mode: "bulk", status, fileId }. Read it with GET /routines/run-batch/{id}/results, which returns routine_run_id, status, total, finished, and result_url once complete.
- Inline results are under data in both clients. Bulk results are in a separate file behind result_url (REST) or resultUrl (CLI). Never print those URLs.
- Poll with `clay routines runs get` every 5 seconds for up to 2 minutes.
- Run the verification step below before finishing.

Prerequisites

Note: The script strips the workspace: { id, name } object that the CLI adds to every response.

1. Run the comparison

Save this as parity.sh:

#!/bin/bash
# Start a run with one client and read it with the other. Needs CLAY_API_KEY; runs the CLI from an empty HOME.
set -u
BASE=https://api.clay.com/public/v0
R=function:t_0tjqm9rUjfGY4MunGDM
CL=${CLAY_BIN:-clay}
EH=$(mktemp -d)
cli() { env -i PATH="$PATH" HOME="$EH" CLAY_API_KEY="$CLAY_API_KEY" "$CL" "$@"; }
rest() { curl -s -H "clay-api-key: $CLAY_API_KEY" -H "content-type: application/json" "$@"; }
wait_cli() { for _ in $(seq 1 24); do s=$(cli routines runs get "$1" | jq -r .status); [ "$s" != in_progress ] && break; sleep 5; done; }
echo '{"items":[{"id":"r1","inputs":{"Company Name":"Notion"}}]}' > one.json
printf '%s\n' '{"id":"b1","inputs":{"Company Name":"Figma"}}' '{"id":"b2","inputs":{"Company Name":"Linear"}}' > two.jsonl

echo "== 1. start over REST, read with the CLI"
start=$(rest -X POST "$BASE/routines/$R/run" -d @one.json); echo "REST start:  $(echo "$start" | jq -c .)"
id=$(echo "$start" | jq -r .routine_run_id); wait_cli "$id"
echo "CLI get:     $(cli routines runs get "$id" | jq -c 'del(.workspace)')"

echo "== 2. start with the CLI, read over REST"
start=$(cli routines runs start $R --input one.json | jq 'del(.workspace)'); echo "CLI start:   $(echo "$start" | jq -c .)"
id=$(echo "$start" | jq -r .routineRunId); wait_cli "$id"
echo "REST result: $(rest "$BASE/routines/run/$id/results" | jq -c .)"

echo "== 3. same run, both views: are the data arrays equal?"
a=$(cli routines runs get "$id" | jq -c .data); b=$(rest "$BASE/routines/run/$id/results" | jq -c .data)
[ "$a" = "$b" ] && echo "data arrays equal" || echo "data arrays differ"

echo "== 4. bulk run started with the CLI, read over REST"
start=$(cli routines runs start $R --bulk two.jsonl | jq 'del(.workspace)'); echo "CLI bulk start: $(echo "$start" | jq -c .)"
id=$(echo "$start" | jq -r .routineRunId); wait_cli "$id"
echo "REST bulk results: $(rest "$BASE/routines/run-batch/$id/results" | jq -c '{routine_run_id, status, total, finished, has_result_url: (.result_url != null)}')"
echo "CLI bulk get keys: $(cli routines runs get "$id" | jq -c 'del(.workspace) | {routineRunId, mode, status, total, finished, has_resultUrl: (.resultUrl != null)}')"
CLAY_BIN=$(command -v clay) bash parity.sh

2. Verify the result

What the script printed on 2026-10-12:

== 1. start over REST, read with the CLI
REST start:  {"routine_run_id":"run_0tmtmb9CSrKHReWt9bx","status":"in_progress"}
CLI get:     {"routineRunId":"run_0tmtmb9CSrKHReWt9bx","mode":"inline","total":1,"finished":1,"status":"complete","data":[{"id":"r1","status":"complete","result":{"Domain":"notion.com"}}]}
== 2. start with the CLI, read over REST
CLI start:   {"routineRunId":"run_0tmtmbrDa76RUSPoyUG","mode":"inline","status":"in_progress"}
REST result: {"routine_run_id":"run_0tmtmbrDa76RUSPoyUG","status":"complete","total":1,"finished":1,"data":[{"id":"r1","status":"complete","result":{"Domain":"notion.com"}}]}
== 3. same run, both views: are the data arrays equal?
data arrays equal
== 4. bulk run started with the CLI, read over REST
CLI bulk start: {"routineRunId":"run_0tmtmc48DekZmqUQW7w","mode":"bulk","status":"in_progress","fileId":"file_0tmtmc3E2vdaJvzGvQq"}
REST bulk results: {"routine_run_id":"run_0tmtmc48DekZmqUQW7w","status":"complete","total":2,"finished":2,"has_result_url":true}
CLI bulk get keys: {"routineRunId":"run_0tmtmc48DekZmqUQW7w","mode":"bulk","status":"complete","total":2,"finished":2,"has_resultUrl":true}

The check is a set of properties: each run started by one client is found by the other, status, total, and finished agree, the two data arrays are equal, and the bulk run reports a result URL through both clients. The returned domains are what the routine returned that day.

3. Field name differences

MeaningRESTCLI
Run idroutine_run_idroutineRunId
Start response statusstatusstatus, plus mode
Bulk results locationresult_urlresultUrl
Bulk upload idfile_id (from upload-url)fileId

How it works

Both clients call the same service, and a run id identifies one run. The CLI wraps the REST responses and renames the keys to camelCase. The CLI adds mode to its responses, which tells you whether the run is inline or bulk.

Common issues

Use the endpoint that matches the run's mode

Reading a bulk run's id from the inline results endpoint returned a 404 whose message says to use the batch endpoint, and reading an inline run's id from the batch endpoint returned a 404 that says no inline or bulk run was found with that id:

inline id on bulk endpoint:
{"message":"No inline or bulk run found with id 'run_0tmtmbrDa76RUSPoyUG'"} [404]
bulk id on inline endpoint:
{"message":"Run 'run_0tmtmc48DekZmqUQW7w' is a batch run. Fetch its results from GET /public/v0/routines/run-batch/run_0tmtmc48DekZmqUQW7w/results."} [404]

The CLI's mode field says which endpoint to use. clay routines runs get chooses for you.

Inline results are paged at 20 rows

A 25-row inline run returned 20 rows and a cursor through both clients. clay routines runs get --help lists --limit (1 to 100, default 20) and --cursor for the next page, and the REST endpoint accepts limit and cursor as query parameters.

There is no REST endpoint to list runs

GET /routines/runs and GET /routines/run/{id} returned 404 on 2026-10-12. Keep the run id that start returned. With only an API key, clay routines runs list also returned auth_forbidden.

Bulk results are a second download

Both clients return a presigned URL, not rows. The URL is short-lived and carries its own authorization, so do not log it.

Next steps


verification:
  status: verified
  tested_at: "2026-10-12"
  product_version: "clay 1.9"
  command: "CLAY_BIN=$(command -v clay) bash parity.sh"
  expected_result: "Runs started over REST are readable with the CLI and the reverse, data arrays are equal, and the bulk run exposes a result URL through both clients."