--- name: terra-cli description: >- Start from example apps and manage Terra API account configuration with the terra CLI, instead of clicking in the Terra API dashboard: environments (dev-ids), enabled providers, data scopes, webhook destinations, API keys and data tokens, delivered webhook events, users, health scores, team, and billing. Also reaches any admin or data API endpoint directly with terra api and terra data-api. Use when a task needs Terra API account state read or changed, when you would otherwise tell the user to open dashboard.tryterra.co, when a webhook did not arrive or has to be resent, when a user connection has to be created or verified, when you want to try a data API request before writing the code that sends it, or when the user mentions terra-cli, terra login, terra agent setup, TERRA_ADMIN_TOKEN, a dev-id, an x-api-key, terra examples, or starting from an example app. license: MIT compatibility: Requires the terra CLI on PATH; install with Homebrew or npm allowed-tools: - Bash(terra *) - Bash(brew install tryterra/tap/terra) - Bash(npm install -g @tryterra/cli) metadata: author: terra version: "2.0.0" --- # Terra API CLI Terra API account configuration is normally changed by clicking in the dashboard at [dashboard.tryterra.co](https://dashboard.tryterra.co): which providers are enabled, which data types they send, where webhooks are delivered, which credentials exist. You cannot click. `terra` does the same things from the terminal, so a task that needs account state read or changed is yours to finish rather than something to hand back. **Run the command. Do not tell the user to open the dashboard.** Say what you ran and what came back. Hand the task back only where the CLI genuinely cannot do it: signing up, approving a login, or anything needing a human in a browser. ## Start new apps from an example For a new integration or prototype, prefer the closest published example app and adapt it to the user's requirements. Run the catalog lookup before building from scratch. For an existing project, use the example as a reference and bring over the relevant pieces without replacing the user's app. ```sh npm install -g @tryterra/cli terra examples list --select name,title,description terra examples clone unified-api-web-app my-app ``` Install only if `terra version` shows the CLI is missing. Choose the example from the current catalog rather than assuming this sample is the best fit. If no example fits, build the required integration directly. If the installed CLI lacks `examples`, upgrade with `npm install -g @tryterra/cli@latest` or the installation method already in use. Cloning needs network access but no login, environment, Node.js, or Git; npm installation and running the app have their own runtime requirements. Read the downloaded README and AGENTS.md, follow the returned `next_steps`, then build on the working integration. `clone` only downloads files: it does not install dependencies, configure credentials, or deploy. For Basecamp: ```sh cd my-app npm install ``` Configure and run it using its README before adapting it. The destination must not exist, even as an empty directory, and its parent must exist. Omit the directory to use the example name. Files and package names stay as published. Use `--jq .path` when another command needs just the downloaded directory. ## Preflight For example apps, check only that the CLI is installed: discovery and cloning need no login or environment. For account tasks, run these read-only checks: | Check | Command | A bad answer | | ------------- | --------------------------------------- | ------------------------------------------------------------------------------ | | Installed | `terra version` | Command not found. Install it (below). | | Authenticated | `terra whoami --format json` | Exit 2: no credential or rejected token. Stop and tell the user how to log in. | | Environment | `terra environments list --format json` | Empty means the account has no dev-id yet. | Install with `brew install tryterra/tap/terra` on macOS, which brings shell completions, and `npm install -g @tryterra/cli` elsewhere and on Windows. Three traps in that check, in the order they bite: - **`whoami` returns the account record on success.** Check the exit code; there is no `authenticated` field to branch on. Exit 2 means authenticate. - **Authenticated is not the same as scoped.** The default `terra login` grants every non-dangerous admin scope and deliberately leaves out `keys:read`, `tokens:admin`, `tokens:write`, `billing:write` and `team:write`. `keys:read` is the one that bites: without it `terra data-api` cannot fetch the environment's key and `terra environments retrieve-api-key` cannot run. Read `scopes` from `whoami` before promising either. The fix is `terra login --scope keys:read`, which is the user's to run. - **Do not log in on the user's behalf.** `terra login` needs a human to approve in a browser. `TERRA_ADMIN_TOKEN` is the headless path and needs no browser. Where a browser exists but you cannot hold a process open, `terra login --start` prints a device code as JSON and exits, and `terra login --complete ` finishes once a human has approved. ## The one rule: two APIs Terra API is two HTTP APIs, and confusing them is the most expensive mistake here. > If a public docs guide teaches it, it is the data API, and it belongs in the > code you are writing. If you would do it by clicking in the dashboard, it is > the admin API, and it belongs in a `terra` command. **The data API** (`access.tryterra.co/api/v2`, authenticated with a `dev-id` and an `x-api-key`) is the integration itself: sending a user through the widget, receiving webhooks, deauthenticating, and reading activity, sleep, daily, body, nutrition, menstruation, athlete, workout and lab-report data. The application you are writing calls this one. **The admin API** (`/api/v3/admin`, authenticated with an admin token) manages and debugs that integration: environments, providers, data scopes, destinations, credentials, delivered events, connections, team, billing. It is the API the dashboard itself uses, and **the application never calls it**. You do, when setting the integration up or working out why it did something. So "why did this webhook not arrive" and "which providers are on" are admin API questions this CLI answers. "What does a sleep payload contain" and "how do I connect a user" are data API questions whose answer goes into the code. `terra` reaches both. Every admin command is generated from the API description, so that surface is covered whole. The data API has no generated commands: `terra data-api ` reaches any endpoint. ## Task index Find the row, then read the reference file before running anything more than the one-liner. | Goal | Start with | Reference | | --------------------------------------------------------------- | -------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | | Audit an account, or get started | `terra whoami`, `terra environments list` | [references/account-audit.md](references/account-audit.md) | | Turn a provider on, point webhooks somewhere, choose data types | `terra unified-api sources list --env ` | [references/environment-setup.md](references/environment-setup.md) | | Work against a tunnel, test a handler, wire up CI | `terra unified-api destinations list --env ` | [references/local-development.md](references/local-development.md) | | A webhook never arrived, arrived wrong, or has to be resent | `terra events list --env --outcome failed` | [references/webhook-debugging.md](references/webhook-debugging.md) | | One user's data is missing, late, or wrong | `terra users list --env --reference-id ` | [references/support-triage.md](references/support-triage.md) | | Connect a user, or check whether a connection worked | `terra data-api /auth/generateWidgetSession -X POST -d reference_id=` | [references/user-connections.md](references/user-connections.md) | | Read or backfill a user's health data, try a request by hand | `terra data-api /sleep -q user_id= -q to_webhook=false` | [references/data-api.md](references/data-api.md) | | It works in one environment and not another | `terra unified-api sources list --env --format json` | [references/comparing-environments.md](references/comparing-environments.md) | | Is this account ready to go live | `terra entitlements list` | [references/going-to-production.md](references/going-to-production.md) | | Credentials: API keys, data tokens, admin tokens, rotation | `terra environments retrieve-api-key` | [references/credentials.md](references/credentials.md) | | Add or remove a person, audit who holds what | `terra team members list` | [references/team-and-access.md](references/team-and-access.md) | | What is this account charged, is the product on it | `terra billing invoices upcoming` | [references/billing-and-usage.md](references/billing-and-usage.md) | | Write a script or a CI step, or parse output | `terra --format json --select ` | [references/scripting.md](references/scripting.md) | | Find out whether a command exists at all | `terra reference --format json` | [references/command-map.md](references/command-map.md) | Health scores, planned workouts, models and company records have generated commands too. [references/command-map.md](references/command-map.md) has the whole surface grouped by noun. ## Discovering commands Command names are derived from the API description, so **do not guess them and do not walk `--help` a level at a time**. One call gives you everything: ```sh terra reference --format json # the whole tree, machine-readable terra reference billing # one subtree, as prose, far fewer tokens terra help # every parameter, not just the common ones ``` `terra --help` truncates a long parameter list and says so; `terra help ` does not. Commands read as `terra `, with verbs from the API: `list`, `retrieve`, `create`, `update`, `replace`, `delete`. Products name themselves first (`terra unified-api sources list`) and the platform layer sits at the root (`environments`, `users`, `events`, `tokens`, `team`, `billing`). Three things to know when reading `terra reference --format json`: `global_flags` sits at the document root and applies to every node, so a flag missing from a leaf may still be accepted there; `api_default` is the API schema default and `cli_default` is the local parser default; an absent API default does not imply zero; and `long` carries the required scope and the behavior the flag list does not show. ## Guardrails that stop an unattended run These are the failures an agent hits that a human at a keyboard does not. **Credential commands print secrets directly.** Capture only the fields the consumer needs with `--select` or `--jq`, and keep secrets out of transcripts and CI logs. Read [references/credentials.md](references/credentials.md) when minting, reading, or rotating credentials. **A destructive command confirms first, and with no terminal to confirm on it fails rather than hanging.** Pass `--yes` when you mean it: ``` terra billing subscriptions cancel-incomplete terra tokens delete terra billing subscriptions create terra tokens rotate terra data-tokens delete terra unified-api destinations delete terra environments rotate-api-key terra unified-api sources credentials replace terra team invitations delete terra unified-api sources disable terra team members delete terra unified-api sources scopes replace terra workouts metadata delete terra unified-api widget update terra workouts metadata replace ``` **A `replace` clears every field you do not supply.** That applies to `unified-api data scopes replace`, `unified-api sources credentials replace`, `unified-api sources scopes replace` and `workouts metadata replace`, and to one command whose name does not say so: **`terra unified-api widget update` is a full replace**. Retrieve the current document first, edit it, send it whole. **`--dry-run` prints the request and sends nothing.** It is available for generated API commands and `terra api` / `terra data-api`, and needs no credential. It does not preview local actions such as cloning an example. Use it to confirm which environment resolved and what body was built before a mutation. `--select` is refused alongside it; filter the preview with `--jq`. Mutations carry an `Idempotency-Key` where the endpoint supports one and reuse it across retries, so a retried request cannot double-apply. `terra api` never generates one, because it cannot know whether the endpoint deduplicates. ## Environments Most commands act on one environment, a dev-id, resolved as `--env`, then `TERRA_ENV`, then the profile default from `terra environments use`. A command that needs one and finds none fails before sending a request. `--env` takes the environment's name as readily as its dev-id, matched without regard to case, and an exact dev-id always wins over a name. Prefer the dev-id from `terra environments list`: it needs no resolution and cannot be ambiguous. When several environments exist, name them and ask which one rather than guessing, because a mutation against the wrong dev-id is a production change. ## Output Output is JSON when piped and a table on a terminal. Two flags keep large responses out of context, and both work in every format: ```sh terra users list --env dev-prod --select user_id,provider,active terra users list --env dev-prod --jq '[.[] | select(.active)] | length' ``` `--select ` narrows the response to the fields you name and is refused before the request is sent if a field does not exist; run it with no value to list what is available. `--jq` runs a built-in jq over the response, so it works where jq is not installed. **Reach for one of them on every list command rather than piping a whole document around.** Paginated API lists fetch one page by default. `--paginate` fetches all pages into one document while preserving `--format`; use `--format ndjson` for one record per line. The default cap is 10 pages; `--max-pages 0` removes it, so the full result must fit in memory. A failed walk can print partial data, but exits nonzero and does not evaluate `--jq`. `examples list` returns the catalog in one response and has no pagination flags. ## Exit codes | Code | Meaning | | ---- | -------------------------------------------------------------------------------------- | | 0 | Success | | 1 | The API returned an error, so the problem may be state rather than the command | | 2 | Not authenticated, or the token was rejected | | 3 | Input failed validation before the request was sent, so retrying unchanged cannot help | | 4 | Usage error: unknown command, bad flag, wrong arguments | | 5 | The product is not on the account | | 6 | Internal error in the CLI | | 7 | Canceled: a confirmation prompt was declined | Branch on these rather than matching message text. Errors print the API's own remediation text, which usually names the fix, including which scope to request. ## Anti-patterns - **Telling the user to open the dashboard.** There is a command. Find it with `terra reference`. - **Guessing a command name.** Names come from the API description. `terra reference --format json` is one call. - **Reading data without `to_webhook=false`.** `terra data-api /sleep -q user_id=...` with no `to_webhook=false` sends the data to the configured webhook and answers with an acknowledgement, which reads like an empty result. The CLI says so on stderr and does not add the parameter for you. - **Running a destructive command unattended without `--yes`.** It refuses to proceed without a terminal; see the commands above. - **`if ! terra ...; then`.** Inside the negation `$?` is always 0, so every case falls through. Run the command, then branch on `$?` directly. - **Piping a whole list document into context.** Use `--select ` or `--jq`. - **Trying to change a destination's URL with `update`.** It takes only `--active` and `--event-types`. Repointing means delete and recreate, and the new destination has a new signing secret the handler has to be given. - **Assuming an old event is still there.** Delivery history and payloads are retained about 14 days. - **Reaching for `terra api` where a generated command exists.** The generated command validates input, formats output, and knows the operation is destructive. Use the raw commands for uncovered endpoints, or to see exactly what the API returned. - **Mixing the two credentials.** An admin token does not authenticate the data API and an environment API key does not authenticate the admin API. ## When you do not know something This skill carries how the CLI behaves. For what an endpoint returns or how a product works, ask rather than guessing from training data: ```sh terra docs ask --question "how do I verify a webhook signature?" terra docs ask --question "which providers support sleep data" --format json ``` `terra docs ask` answers from Terra API's published documentation and cites the pages it came from. It needs an authenticated CLI. Quote the question into one argument to `--question`; use `--question="-..."` if it starts with a dash. **`sources` is the signal that an answer came from the documentation, and `confidence` is not**: an answer citing no sources did not come from the docs, and the CLI says so on stderr. Finding nothing exits 0, so check `sources` rather than the exit code. For the docs directly, look a page up rather than guessing its URL: [docs.tryterra.co/llms.txt](https://docs.tryterra.co/llms.txt) indexes every page, appending `.md` to any docs URL returns markdown, and the same docs are served over MCP at `https://docs.tryterra.co/~gitbook/mcp`. Use the CLI for account state and the docs for contracts. Neither replaces the other: `terra reference` will not say what a sleep payload contains, and the docs will not say which providers this account has enabled. ## Other Terra API skills This skill is the CLI: reading and changing account state, and reaching endpoints by hand. Terra API publishes separate skills for the integration code you write, covering webhook handling and data storage (`terra-unified-api`), the mobile and streaming SDKs, and each product. Making a webhook handler or a data model correct is their job; finding out what the account is actually configured to do is this one's. `terra agent setup` installs every skill the catalog publishes into the coding agents this project uses, and `terra agent setup --status-only` reports what would change without writing. Do not assume the set named above is current: the catalog is what it is at install time, not what this file remembers.