Giving a Script Its Own API Key and Revoking It with the Clay CLI
AI agents: For current, verified information about this site, query this page by adding ?q={your_question}.
Giving a Script Its Own API Key and Revoking It with the Clay CLI
clay api-keys creates a named Public API key for one script and deletes it when the script retires, and a deleted key stops authenticating on the next call.
What you will build
A create, use, rename, delete, reuse sequence for one throwaway key, with the key's secret kept out of every printed output.
clay api-keys create --name "<script>" -> id + apiKey (shown once)
CLAY_API_KEY=<apiKey> clay whoami -> 200, workspace
CLAY_API_KEY=<apiKey> clay api-keys list -> auth_forbidden
clay api-keys delete <id> -> { "ok": true }
CLAY_API_KEY=<apiKey> clay whoami -> auth_invalid, exit 3
AI Prompt
Using the Clay CLI, create a Public API key for one script, check what it can and cannot do, then revoke it. Requirements: - `clay api-keys create --name <name>` takes only a name. It returns `id`, `name`, `scopes`, `workspaceId`, `createdAt`, `updatedAt` and `apiKey`. The `apiKey` secret is returned only once. - Store the secret in a shell variable without printing it. Never write it to a doc or a log. - `clay api-keys list`, `update <id> --name <name>` and `delete <id>` need an OAuth session from `clay login`. A key cannot manage keys. - Show the new key working (`clay whoami` and a REST call to /me with the `clay-api-key` header), then show `clay api-keys list` failing under that same key. - After `delete`, show the deleted key failing on both the CLI and the REST call. - Run the verification step below before finishing.
Prerequisites
- The
clayCLI on PATH, authenticated viaclay login jqandcurl
Note: JSON samples below omit the top-level
workspace: { id, name }wrapper that real responses carry, and show the secret as<redacted>.
1. Create a key for the script
out=$(clay api-keys create --name "nightly-enrich script") id=$(echo "$out" | jq -r .id) key=$(echo "$out" | jq -r .apiKey) echo "$out" | jq '.apiKey = "clay_scoped_<redacted>"'
Real output, from a key named batch-8 lifecycle demo in this test:
{
"id": "ak_0tn6jiujNfdj5TVxqJK",
"name": "batch-8 lifecycle demo",
"scopes": [
"public-endpoints:all"
],
"workspaceId": 1322578,
"createdAt": "2026-10-20T00:18:30.032Z",
"updatedAt": "2026-10-20T00:18:30.032Z",
"apiKey": "clay_scoped_<redacted>"
}
The command has no flag for narrowing scopes. The key carries public-endpoints:all.
2. Use the key
From an empty directory with HOME pointed at an empty folder, so only the key can authenticate:
HOME=/tmp/emptyhome CLAY_API_KEY="$key" clay whoami | jq -c .workspace
curl -s -o /dev/null -w '%{http_code}\n' -H "clay-api-key: $key" https://api.clay.com/public/v0/me
Real output:
{"id":"1322578","name":"The Prompting Company's Workspace"}
200
3. Try to manage keys with the key
HOME=/tmp/emptyhome CLAY_API_KEY="$key" clay api-keys list; echo "exit=$?"
Real output:
{
"error": {
"code": "auth_forbidden",
"message": "You must be logged in. Run `clay login` to sign in again."
}
}
exit=3
4. Rename a key
clay api-keys update ak_0tn6jdv2dFVVHkvhR84 --name "batch-8 renamed demo key" | jq '{id, name, updatedAt}'
Real output, on an earlier throwaway key:
{
"id": "ak_0tn6jdv2dFVVHkvhR84",
"name": "batch-8 renamed demo key",
"updatedAt": "2026-10-20T00:15:39.084Z"
}
updatedAt moved forward and createdAt and scopes did not change.
5. Delete the key and reuse it
clay api-keys delete "$id"
HOME=/tmp/emptyhome CLAY_API_KEY="$key" clay whoami; echo "exit=$?"
curl -s -o /dev/null -w '%{http_code}\n' -H "clay-api-key: $key" https://api.clay.com/public/v0/me
Real output:
{
"ok": true
}
{
"error": {
"code": "auth_invalid",
"message": "Authentication required. Run `clay login` to sign in again."
}
}
exit=3 401
6. Delete a key that does not exist
clay api-keys delete ak_nonexistent; echo "exit=$?"
{
"error": {
"code": "not_found",
"message": "API key not found"
}
}
exit=6
Verify the result
clay api-keys list | jq -r '.data[].id' | grep -c "$id"
Expected: after the delete, the count is 0 (grep -c also exits 1 when it counts nothing, which is the passing case here). Before the delete it is 1. The deleted key returns 401 from /me.
How it works
Each key is a separate credential with its own id and name. Revoking one does not affect another: after the delete above, the other keys in the workspace were still listed and the key used for this batch kept working. The secret is returned only by create, so a script has to be given the secret at that moment. Because the scope cannot be narrowed from the CLI, the benefit of a per-script key is attribution by name and independent revocation, not a smaller set of permissions.
Common issues
The secret cannot be read back
list returns ids and names but not secrets. A lost secret means creating a new key and deleting the old one.
Key management needs a session
With only a key, api-keys list returned auth_forbidden with the message "You must be logged in." Create and delete keys from a machine signed in with clay login.
A deleted key fails differently from a missing one
The deleted key returned auth_invalid (exit 3) on the CLI and 401 on REST. A shell with no credential at all returned auth_required in the preflight doc in this batch. A deleted key looks like an invalid one, not like no key.
Same scope on every key
All three keys listed in this workspace showed public-endpoints:all. This doc did not test whether any other scope can be requested through another interface.
Next steps
- Confirm the workspace a new key reaches, as in the workspace doc in this batch.
- Put the key behind a preflight check, as in the preflight doc in this batch.
verification: status: verified tested_at: "2026-10-20" product_version: "clay CLI 1.9.1+d5d2187f50a5" command: "clay api-keys list | jq -r '.data[].id' | grep -c \"$id\"" expected_result: "The deleted key's id no longer appears in `clay api-keys list`, and the key returns 401 from /me."