Confirming That Invalid Inputs Cost Nothing with the Clay CLI
AI agents: For current, verified information about this site, query this page by adding ?q={your_question}.
Confirming That Invalid Inputs Cost Nothing with the Clay CLI
A routine run with missing, mistyped, or malformed input is rejected before any credits are spent. Inline runs are rejected by the start command itself, and bulk runs are rejected after upload with a status of validation_failed that names the line and field.
What you will build
A set of invalid requests against one routine, each followed by a balance check, to show which errors appear and that none of them changes the credit balance.
clay credits balance
↓
invalid inline body / invalid bulk file / unknown routine
↓
error with a code (exit 2 or 6), or a run with status validation_failed
↓
clay credits balance (unchanged)
AI Prompt
Using the Clay CLI, check what happens when a routine run request is invalid.
Requirements:
- Inline runs use `clay routines runs start <routineId> --input <json>` with the body `{ "items": [ { "id": "<string>", "inputs": { ... } } ] }`, 1 to 100 items.
- Inline validation errors return `{ "error": { "code": "validation_error", "message": ... } }` and exit code 2. An unknown routine returns `not_found` and exit code 6.
- Bulk runs use `--bulk <file.jsonl>`, one `{ "id", "inputs" }` object per line. A line that is not valid JSON is rejected by the CLI before upload and the error names the line number.
- A bulk row with missing required inputs is not rejected by the CLI. The run starts, then reports `status: "validation_failed"` with `error.details[].lineNumber` and `field`.
- Read `clay credits balance` before and after to confirm that no credits were spent.
- Run the verification step below before finishing.
Prerequisites
- The
clayCLI on PATH, authenticated viaclay login jq
Note: JSON samples below are trimmed to the fields relevant to each step. Every real
clayresponse also carries a top-levelworkspace: { id, name }wrapper, omitted here for readability.
1. Record the balance
clay credits balance | jq .balance
Real output:
393066
2. Send invalid inline bodies
R=function:t_0tjqm9rUjfGY4MunGDM
clay routines runs start $R --input '{"items":[{"id":"r1","inputs":{}}]}'
clay routines runs start $R --input '{"items":[{"id":"r1","inputs":{"Company Name":123}}]}'
clay routines runs start $R --input '{"Company Name":"Stripe"}'
clay routines runs start $R --input '{"items":[]}'
clay routines runs start function:nope --input '{"items":[{"id":"r1","inputs":{}}]}'
Real output, one error per command:
{ "code": "validation_error", "message": "Invalid inputs for item r1: Company Name: Missing required field: Company Name" }
{ "code": "validation_error", "message": "Invalid inputs for item r1: Company Name: Expected type string" }
{ "code": "validation_error", "message": "--input: must match { \"items\": [{ \"id\": <string>, \"inputs\": <object> }] } at items: Invalid input: expected array, received undefined" }
{ "code": "validation_error", "message": "--input: must match { \"items\": [{ \"id\": <string>, \"inputs\": <object> }] } at items: Too small: expected array to have >=1 items" }
{ "code": "not_found", "message": "Routine not found" }
The first four exited with code 2 and the last with code 6.
3. Send a bulk file with a line that is not JSON
printf '%s\n' '{"id":"a","inputs":{"Company Name":"Notion"}}' 'not json' > bad.jsonl
clay routines runs start $R --bulk bad.jsonl
Real output:
{ "code": "validation_error", "message": "--bulk: line 2 is not valid JSON (JSON Parse error: Unexpected identifier \"not\")" }
4. Send a bulk file with a row that is missing a required input
printf '%s\n' '{"id":"a","inputs":{"Company Name":"Notion"}}' '{"id":"b","inputs":{}}' > bad2.jsonl
clay routines runs start $R --bulk bad2.jsonl
clay routines runs get <routineRunId>
Real output of start:
{ "routineRunId": "run_0tmnz6p6px4wsxJdUn9", "mode": "bulk", "status": "in_progress", "fileId": "file_0tmnz6paWMJBkyCZsTE" }
Real output of get, after the first poll:
{
"routineRunId": "run_0tmnz6p6px4wsxJdUn9",
"mode": "bulk",
"status": "validation_failed",
"error": {
"message": "Batch input has 1 invalid row.",
"totalInvalidRows": 1,
"details": [{ "lineNumber": 2, "field": "Company Name", "message": "Missing required field: Company Name" }]
}
}
The status payload carries no per-row results, and the balance did not change, so the valid first row was not charged either.
5. Confirm the balance
clay credits balance | jq .balance
Real output:
393066
Verify the result
clay routines runs start function:t_0tjqm9rUjfGY4MunGDM --input '{"items":[{"id":"r1","inputs":{}}]}'; echo "exit=$?"
Expected: a validation_error JSON error on stderr, exit=2, and an unchanged clay credits balance.
How it works
Inline requests are checked against the routine's input schema before a run is created, so the error comes back immediately. Bulk requests are uploaded first. The CLI checks that each line is valid JSON with an id and inputs, and the service checks the inputs against the routine schema after upload. A failure at that stage becomes a run with status: "validation_failed" and a list of the offending lines.
Common issues
A bulk start can succeed and the run can still fail
start returned in_progress for the file with the invalid row. Always follow a bulk start with runs get and check status, because validation_failed appears there and not in the start response.
One invalid row rejects the whole bulk batch
In the run above, row a was valid and row b was not, and the run ended as validation_failed with no per-row results. Fix every line listed in error.details and start the batch again.
Exit code 2 and exit code 6 mean different things
Exit 2 is validation_error (the request body or file) and exit 6 is not_found (the routine id). A script can branch on these without parsing the message.
Next steps
- Comparing What a Run Costs When It Finds Nothing with the Clay CLI
- Running a Bulk Routine Batch from a JSONL File with the Clay CLI
verification:
status: verified
tested_at: "2026-10-09"
product_version: "clay CLI 1.9.1"
command: "clay routines runs start function:t_0tjqm9rUjfGY4MunGDM --input '{\"items\":[{\"id\":\"r1\",\"inputs\":{}}]}'; echo \"exit=$?\""
expected_result: "A validation_error JSON error, exit=2, and an unchanged credit balance."