Running the Clay CLI on a Headless Machine with an API Key
AI agents: For current, verified information about this site, query this page by adding ?q={your_question}.
Running the Clay CLI on a Headless Machine with an API Key
The clay CLI reads a Public API key from CLAY_API_KEY, so a server, container, or CI runner can use it without a browser sign-in. The key covers five command families: whoami, credits balance, searches query-mode, and routines runs start and get. The other groups tried, which are listed in the matrix below, returned auth_forbidden (exit 3) with the key.
What you will build
A shell script that runs each command from an empty directory with an empty home directory and only CLAY_API_KEY set, then prints the exit code and error code for each one.
clay api-keys create (once, from a signed-in machine) -> apiKey, shown once
↓
CLAY_API_KEY=... clay <command> (empty HOME, empty directory)
↓
exit 0 ok | exit 3 auth_forbidden | exit 6 not_found
AI Prompt
Using the Clay CLI, check which commands work headlessly with only a Public API key. Requirements: - Create the key once on a machine that is signed in with `clay login`: `clay api-keys create --name "<name>"`. The response includes `apiKey`, which is shown only once. The key has the scope `public-endpoints:all`. - On the headless machine set `CLAY_API_KEY` and nothing else. Do not run `clay login`. - Run each command with an empty HOME and from an empty directory. A stored login in the real HOME takes precedence over CLAY_API_KEY, and a `.env` file in the current directory is read by the CLI, so either one can hide a missing or invalid key. - Exit codes: 0 ok, 3 for auth_required (no credential), auth_invalid (key not recognized), or auth_forbidden (key valid but not allowed for this command), 6 for not_found. - Print the exit code and the error code for each command, and never print the key. - Run the verification step below before finishing.
Prerequisites
- The
clayCLI on PATH (the runs here used 1.9.1 and 1.9.0, with the same results) jq- A Public API key created with
clay api-keys create
Note: Every real
clayresponse also carries a top-levelworkspace: { id, name }wrapper, omitted from the samples below.
1. Create the key
Run this once on a machine where clay login has completed:
clay api-keys create --name "headless runner" > key.json jq '.apiKey = "<redacted>"' key.json
Creation response, with the secret replaced:
{
"workspace": {
"id": "1322578",
"name": "The Prompting Company's Workspace"
},
"id": "ak_0tmtinvm9rDgCkcDben",
"name": "batch-7 sandbox key",
"scopes": [
"public-endpoints:all"
],
"workspaceId": 1322578,
"createdAt": "2026-10-12T23:31:07.772Z",
"updatedAt": "2026-10-12T23:31:07.772Z",
"apiKey": "<redacted>"
}
Store the secret in your secret manager and delete key.json. The secret cannot be read again after creation.
2. Check which commands the key can run
Save this as headless-matrix.sh:
#!/bin/bash
# Which clay commands work with only CLAY_API_KEY? Needs clay on PATH and CLAY_API_KEY set.
# Runs from an empty directory with an empty HOME.
CL=$(command -v clay)
KEY=$CLAY_API_KEY
W=$(mktemp -d); EH=$(mktemp -d); cd $W
row() { # label, then env prefix words, then command
local label=$1; shift
out=$("$@" 2>&1); code=$?
printf '%-44s exit %s %s\n' "$label" "$code" "$(printf '%s' "$out" | jq -r 'if .error then .error.code else "ok" end' 2>/dev/null || echo ok)"
}
E="env -i PATH=/usr/bin:/bin HOME=$EH CLAY_API_KEY=$KEY"
N="env -i PATH=/usr/bin:/bin HOME=$EH"
echo "== with CLAY_API_KEY, empty HOME, empty directory"
row "clay whoami" $E $CL whoami
row "clay credits balance" $E $CL credits balance
row "clay searches query-mode reference" $E $CL searches query-mode reference
row "clay searches query-mode create" $E $CL searches query-mode create --query 'select from companies where industry = "Software Development"'
SID=$($E $CL searches query-mode create --query 'select from companies where industry = "Software Development"' | jq -r .searchId)
row "clay searches query-mode run <searchId>" $E $CL searches query-mode run "$SID" --limit 1
row "clay routines runs get run_unknown" $E $CL routines runs get run_unknown
row "clay routines runs cancel run_unknown" $E $CL routines runs cancel run_unknown
row "clay routines list" $E $CL routines list
row "clay routines runs list" $E $CL routines runs list
row "clay tables list" $E $CL tables list
row "clay workflows list" $E $CL workflows list
row "clay signals list" $E $CL signals list
row "clay campaigns list" $E $CL campaigns list
row "clay claygents list" $E $CL claygents list
row "clay api-keys list" $E $CL api-keys list
echo "== credential variations: clay credits balance"
row "no key, empty HOME" $N $CL credits balance
row "invalid key, empty HOME" env -i PATH=/usr/bin:/bin HOME=$EH CLAY_API_KEY=clay_scoped_invalid $CL credits balance
row "invalid key, normal HOME with login" env CLAY_API_KEY=clay_scoped_invalid $CL credits balance
printf 'CLAY_API_KEY=%s\n' "$KEY" > $W/.env
row "no key in env, .env file in cwd" $N $CL credits balance
3. Run
export CLAY_API_KEY=clay_scoped_... bash headless-matrix.sh
4. Verify the result
Real output on 2026-10-12. The row invalid key, normal HOME with login needs a stored clay login session. On a machine without one it exits 3 with auth_invalid, like the row above it:
== with CLAY_API_KEY, empty HOME, empty directory clay whoami exit 0 ok clay credits balance exit 0 ok clay searches query-mode reference exit 0 ok clay searches query-mode create exit 0 ok clay searches query-mode run <searchId> exit 0 ok clay routines runs get run_unknown exit 6 not_found clay routines runs cancel run_unknown exit 3 auth_forbidden clay routines list exit 3 auth_forbidden clay routines runs list exit 3 auth_forbidden clay tables list exit 3 auth_forbidden clay workflows list exit 3 auth_forbidden clay signals list exit 3 auth_forbidden clay campaigns list exit 3 auth_forbidden clay claygents list exit 3 auth_forbidden clay api-keys list exit 3 auth_forbidden == credential variations: clay credits balance no key, empty HOME exit 3 auth_required invalid key, empty HOME exit 3 auth_invalid invalid key, normal HOME with login exit 0 ok no key in env, .env file in cwd exit 0 ok
A routine run also works with the key. This call spent 1 credit:
echo '{"items":[{"id":"r1","inputs":{"Company Name":"Stripe"}}]}' > in.json
CLAY_API_KEY=... clay routines runs start function:t_0tjqm9rUjfGY4MunGDM --input in.json
CLAY_API_KEY=... clay routines runs get <routineRunId>
{"routineRunId":"run_0tmtm9ghHD8YcHfaPgj","mode":"inline","status":"in_progress"}
{"routineRunId":"run_0tmtm9ghHD8YcHfaPgj","mode":"inline","total":1,"finished":1,"status":"complete","data":[{"id":"r1","status":"complete","result":{"Domain":"stripe.com"}}]}
How it works
A sign-in stored by clay login takes precedence over CLAY_API_KEY. With no stored sign-in, the key authenticates the command. The key's scope is public-endpoints:all. The five command families that accepted it have counterparts in the Public API (/me, /credits/balance, /routines/{id}/run, /routines/run/{id}/results, and /search/...). A GET against /signals, /workflows, /schedules, and /routines with the same key returned 404 on 2026-10-12, which is consistent with those groups having no key-based surface.
Common issues
A stored login hides a bad key
On a machine where clay login has completed, a key set to clay_scoped_invalid still returned exit 0, because the stored session was used. Test the key path from a clean HOME, as the script above does, not from a developer laptop.
A .env file in the working directory is read
With no key in the environment and a .env file containing CLAY_API_KEY in the current directory, clay credits balance succeeded. This is convenient for local use and an easy way to run a job with a different key than intended. Run scheduled jobs from a directory that you control.
The three kinds of exit 3
auth_required means no credential was found, auth_invalid means the key was not recognized, and auth_forbidden means the credential is valid but the command needs a sign-in. Branch on the error code in the JSON on stderr when the distinction matters. Retrying does not help any of them.
Some routines are not enabled for the API and CLI
routines runs start returns not_found (exit 6) when no routine has that id or the routine is not enabled for API and CLI use. Confirm the id with clay routines get, which needs a sign-in, from a machine where you are logged in.
routines runs list and cancel need a sign-in
runs start and runs get accept the key. runs list and runs cancel returned auth_forbidden. To find a run, keep the routineRunId that start returned.
Next steps
- Running a Clay Enrichment from Cron with the Clay CLI
- Failing a CI Job on Clay CLI Exit Codes with the Clay CLI
verification: status: verified tested_at: "2026-10-12" product_version: "clay 1.9" command: "bash headless-matrix.sh" expected_result: "whoami, credits balance, and searches query-mode exit 0 with only CLAY_API_KEY set; routines list, tables, workflows, signals, campaigns, claygents and api-keys list exit 3 with auth_forbidden."