> ## Documentation Index
> Fetch the complete documentation index at: https://docs.traceten.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Traceten CLI

> Read your numbers, and manage sites, goals and funnels, from a terminal or a CI job.

## What this lets you do

Run any endpoint of the [Traceten API](/api/authentication) from a shell and get JSON you can pipe
straight into `jq`. Sign in through your browser at a terminal, or with an API key in CI.

```bash theme={null}
npx @traceten/cli stats overview --site ttid_7Rb4TrC1dTbnD8w3s1TS12 --from 2026-08-01 --to 2026-08-26
```

## Before you start

* Node.js 22 or newer.
* A Traceten account, if you are signing in through the browser. Nothing else to set up.
* An API key, for CI or for the commands a browser session cannot run (below). Create one in the
  dashboard under **Settings, API keys**, and give it the
  [permissions](/api/authentication#permissions) the commands you want need. Reading numbers needs
  `stats:read`; editing goals, funnels, sites and webhooks needs `config:write`; anything that
  hands back a secret (creating a site, minting a key or a bot token, registering or rotating a
  webhook) needs `credentials:write`.
* Your site's `ttid_` snippet key, which `traceten sites list` returns.

## Install

```bash theme={null}
npm install -g @traceten/cli
```

Or run it without installing:

```bash theme={null}
npx @traceten/cli sites list
```

## Log in

At a terminal, `traceten login` opens your browser. You approve the CLI in Traceten, pick the
organization you want it to act for, and the session is stored on your machine.

```bash theme={null}
traceten login
```

The listener it opens for the callback is on `127.0.0.1`, on a port chosen at the moment you run it,
and it closes as soon as the browser comes back. If your browser does not open, the URL is printed:
paste it.

For CI, and for the commands a browser session cannot run, use an API key instead. The key is read
from stdin, never taken as a flag, because anything on the command line lands in your shell history
and is visible to `ps`.

```bash theme={null}
# CI: pipe it in. This never opens a browser.
echo "$TRACETEN_API_KEY" | traceten login

# At a terminal: paste it. The prompt does not echo.
traceten login --key
```

Either way the credential is written to `~/.config/traceten/config.json` with permissions `0600`,
readable only by you. Nothing ever prints it back, not even a prefix.

Check that it worked:

```bash theme={null}
traceten account get
```

`traceten logout` removes the stored key and the stored session.

### What a browser session cannot do

A browser session carries `stats:read` and `config:write`, so it can read your numbers and change
goals, funnels, sites and settings. It can never carry `credentials:write`, so these commands need
an API key and say so rather than failing with a bare `403`:

| Command                              | Why                           |
| ------------------------------------ | ----------------------------- |
| `traceten account api-keys-create`   | Mints an API key.             |
| `traceten account bot-tokens-create` | Mints a bot token.            |
| `traceten sites create`              | Mints a key for the new site. |
| `traceten webhooks create`           | Returns a signing secret.     |
| `traceten webhooks rotate-secret`    | Returns a signing secret.     |

This is deliberate: a sign-in you can revoke must not be able to create a credential you cannot. For
the same reason, listing credentials (`traceten account api-keys-list`, `api-keys-revoked` and
`bot-tokens-list`) is refused over a browser session as well, and answers `403` saying so.

### Where the credential comes from

Three sources, checked in this order:

| Order | Source           | Set by                                                       |
| ----- | ---------------- | ------------------------------------------------------------ |
| 1     | `TRACETEN_TOKEN` | The environment. Use this in CI.                             |
| 2     | Stored key       | `traceten login --key`, at `~/.config/traceten/config.json`. |
| 3     | Browser session  | `traceten login`, in the same file.                          |

The environment wins, so a CI job never picks up a key a developer happened to leave on the machine.
A stored key beats a browser session because it is the more capable of the two; if you have both,
`traceten logout` and log in again to use the session. Set `XDG_CONFIG_HOME` to move the file, which
is how you give a build agent its own.

A browser session lasts 24 hours and renews itself in the background, so signing in once is enough.
It never opens a browser on its own in the middle of another command. A renewal that fails ends one
of two ways:

* **The grant is gone:** you revoked it, or the client was removed. The session is deleted from
  your machine and the CLI exits `3`, asking you to run `traceten login` again.
* **Traceten could not be reached:** you are offline, or the sign-in service answered `429` or
  `5xx`. Your session is left exactly as it was and the CLI exits `4`. Run the same command again
  when the connection is back; you do not need to log in.

Signing out removes the session from your machine. It does not withdraw the grant, and there is no
screen that revokes one: removing the person from your organization is the only thing that ends it.
See [Safety](/mcp/safety#turning-a-connection-off).

## Running commands

The command is the path. `GET /v1/stats/overview` is `traceten stats overview`, and
`GET /v1/breakdowns/ai-sources` is `traceten breakdowns ai-sources`. Every parameter is a flag with
the same name, underscores turned into dashes, so `site_id` is `--site-id`. `--site` works
everywhere `--site-id` does.

```bash theme={null}
traceten breakdowns ai-sources --site ttid_7Rb4TrC1dTbnD8w3s1TS12 --from 2026-08-01 --to 2026-08-26
```

The full list is the [command reference](/cli/commands), which is generated from the same
description of the API that the API itself is served from, so it cannot describe a command that does
not exist.

`traceten help`, `traceten help <group>` and `traceten <group> <command> --help` all work offline.

### Global flags

| Flag               | What it does                                                                                     |
| ------------------ | ------------------------------------------------------------------------------------------------ |
| `--json`           | Compact single-line JSON, for piping.                                                            |
| `--verbose`        | Writes the request line, the response status and the credential source to stderr. Never the key. |
| `--yes`            | Confirms a command that changes or deletes something.                                            |
| `--api-base <url>` | Point at another host, for local development. Absolute `http://` or `https://` URL.              |
| `--help`           | Help for whatever you asked for.                                                                 |
| `--version`        | The CLI version.                                                                                 |

`TRACETEN_API_BASE` does the same job as `--api-base`, and the flag wins.

### Output

JSON on stdout, everything else on stderr, so a pipe only ever receives data.

```bash theme={null}
# Pretty by default, for reading.
traceten stats overview --site ttid_7Rb4TrC1dTbnD8w3s1TS12

# Compact for piping.
traceten --json breakdowns ai-sources --site ttid_7Rb4TrC1dTbnD8w3s1TS12 | jq '.data[]'
```

The body is the API's response, unchanged. Nothing is renamed, reordered or summarised, so what the
[API reference](/api/authentication) documents is exactly what you get.

### Commands that need `--yes`

Anything that deletes something, issues a credential, or changes how measurement works refuses to
run without `--yes`:

```bash theme={null}
traceten webhooks delete --site ttid_7Rb4TrC1dTbnD8w3s1TS12 --webhook-id 018f… --yes
```

It is a flag rather than a prompt so that CI can pass it deliberately. The
[command reference](/cli/commands) marks every command that needs one: the operations the
[MCP server puts behind a confirmation](/mcp/safety#the-confirmation-gate), plus the destructive
ones the MCP does not expose to an assistant at all, such as deleting a site or issuing a bot
token.

`traceten sites update` is the one conditional case: it needs `--yes` only when you pass
`--cookie-apex-domain`, because moving a site's cookie scope resets returning visitors.

### Exit codes

| Code | Meaning                                                                                                    |
| ---- | ---------------------------------------------------------------------------------------------------------- |
| `0`  | Success.                                                                                                   |
| `1`  | The API answered with an error. Its body is printed to stderr.                                             |
| `2`  | The invocation was wrong: unknown command or flag, missing value, or a command your credential cannot run. |
| `3`  | No credential, or a browser session that could not be renewed. Run `traceten login`.                       |
| `4`  | The request never reached the API, or your session could not be renewed for a network reason.              |

A `403` is exit code `1`, and the body names the permission you are missing in `required_scope`.

## What leaves your account

Most commands print counts and aggregates. One does not.

`traceten visibility runs` prints the **verbatim answers** that AI engines gave, so it is the one command whose output is unredacted third party prose rather than numbers. Those answers discuss a market, so they can name people and companies other than you. They carry no visitor identifier and describe nobody who visited your site.

Two practical consequences:

* Answers are never truncated, so a single page can be several megabytes. Redirect to a file rather than a terminal, and use `--limit` (1 to 50, default 10) to keep it small.
* Treat the output like any other third party content before pasting it into a ticket, a spreadsheet or a shared log. Piping it into a build log makes it as durable as that log.

Every other `traceten visibility` command returns aggregates: rates, counts, URLs of your own pages, and the brand names you configured.

## Use it in CI

```yaml theme={null}
- name: Check yesterday's AI share
  env:
    TRACETEN_TOKEN: ${{ secrets.TRACETEN_TOKEN }}
  run: |
    npx @traceten/cli --json stats overview \
      --site ttid_7Rb4TrC1dTbnD8w3s1TS12 \
      --from "$(date -u -d yesterday +%F)" --to "$(date -u -d yesterday +%F)" \
      | jq -e '.data.ai_session_share'
```

Give the CI key `stats:read` only. If it leaks, it cannot send events or change anything.

## Troubleshooting

**`No credential found`.** Nothing in `TRACETEN_TOKEN` and nothing stored. Run `traceten login`.

**`Your Traceten session expired`.** The grant behind your session is gone: revoked from the
dashboard, or removed. Run `traceten login` again.

**`Could not reach Clerk to renew your session`.** A network problem, not a credential problem. The
session is still on your machine; re-run the command once you are back online.

**`Traceten only signs you in at https://clerk.traceten.com`.** Something pointed the CLI at an API
that names a different sign-in service. Check `TRACETEN_API_BASE` and any `--api-base` in play.
Nothing was opened and nothing was saved. Pointing the CLI at another instance on purpose needs an
explicit `--api-base`, and it asks before opening the browser. A sign-in server named by
`TRACETEN_API_BASE` or by your config file is refused outright, with a message saying so, because
those can be set by something other than you.

**The browser did not open.** The authorization URL is printed alongside the attempt. Paste it into
a browser on the same machine, since the callback goes to a listener on that machine's `127.0.0.1`.
On a machine with no browser at all, use an API key: `echo "$KEY" | traceten login`.

**A command says it needs `credentials:write`.** You are signed in through the browser, and that can
never mint a credential. Use an API key for that command; everything else keeps working.

**`401` on every command.** The key is revoked, expired, or was replaced. Keys are shown once, so
create a new one rather than looking for the old value.

**`403` with `insufficient_scope`.** The key is valid but lacks a permission. Permissions cannot be
changed after a key is created: make a new key with the right ones, deploy it, then revoke the old
one.

**`404` on a site you own.** With a key, a site you cannot reach is a `404` rather than a `403`, so
that a key cannot be used to discover which site ids exist. Check the key's site scope, and check
you passed the `ttid_` snippet key rather than the UUID from the dashboard URL.

**`422` naming a parameter you did not send.** Check for a typo in a flag. The CLI refuses flags a
command does not have, but a value that fails the endpoint's own validation is answered by the API.

**A command you expected is missing.** The CLI covers the endpoints in the OpenAPI spec, which is
not quite the whole documented surface. Two read endpoints ([conversions](/api/conversions) and
`GET /v1/sources`) authenticate with a dashboard session only, so there is no command for them.
Two more are in the docs but not the spec, so they have no command
either: [`PATCH /v1/goals/reorder`](/api/goals) and the [data deletion and access
endpoints](/privacy/data-deletion). The deletion endpoints take an API key, so call them with `curl`,
not the CLI.

## Next

* [Command reference](/cli/commands): every command and its flags.
* [API keys and permissions](/api/authentication): what each permission grants.
* [MCP server](/mcp/overview): the same surface, as tools for Claude, ChatGPT, Codex, VS Code, Claude Code and Cursor.
