# Authenticating with Cargo

There is **one credential**. The CLI holds it, and everything reads it: the
REST API, the CDK, the skills, and the MCP bridge. Sign in once and nothing
else needs configuring.

```sh
npm install -g @cargo-ai/cli
cargo-ai login --email you@company.com
```

The npm package is **`@cargo-ai/cli`**; `cargo-ai` is the command it installs,
so `npx @cargo-ai/cli login` works too if you would rather not install it.

That works in a sandbox or an agent shell with no browser at any point, and it
creates the account on first use. The rest of this page is the detail.

## Discover

- **The CLI** is the shortest path and the one to reach for first:
  `npx @cargo-ai/cli login --help` documents every option, and
  `npx @cargo-ai/cli --help` lists the command surface.
- **The API surface**: `https://www.getcargo.ai/openapi.json`, an OpenAPI 3.1
  document covering every REST operation, also listed in
  `https://www.getcargo.ai/.well-known/api-catalog` (RFC 9727) and in
  `https://www.getcargo.ai/llms.txt`.
- **Versioning and deprecation**: `https://docs.getcargo.ai/api-reference/versioning`.
  The API is versioned in the path (`/v1`). Retired endpoints carry
  `Deprecation` (RFC 9745) and `Sunset` (RFC 8594) headers, with at
  least 180 days between them.
- **Authorization-server metadata** (RFC 8414):
  `https://auth.getcargo.io/.well-known/oauth-authorization-server`.
  Device authorization (what `--oauth` uses), authorization code with PKCE
  (S256), and dynamic client registration. REST callers still send a bearer
  token from the table below.
- **Getting started**: <https://docs.getcargo.ai/get-started/quickstart> and
  the reference at <https://docs.getcargo.ai/api-reference>.

**One thing to know before you write discovery code.** The REST API does not
currently answer a `401` with a
`WWW-Authenticate: Bearer resource_metadata="..."` header, so you cannot learn
the metadata URL from a failed request. Fetch the documents above directly. A
`401` is a JSON body, described under [Errors](#errors).

## Pick a method

| You are                            | Use                                      | Browser needed |
| ---------------------------------- | ---------------------------------------- | -------------- |
| An agent, a sandbox, CI, any shell | `cargo-ai login --email you@company.com` | no             |
| A person at a workstation          | `cargo-ai login --oauth`                 | yes            |
| Already holding a token            | `cargo-ai login --token <token>`         | no             |
| Calling HTTP directly              | `Authorization: Bearer <token>`          | no             |

**`--email` is the agent path and it never opens a browser.** A code is emailed
and read back at the prompt. Where there is no terminal to prompt at, the first
call sends the code and exits, so you re-run with `--code` to finish.

**`--oauth`** runs the OAuth 2.0 Device Authorization Flow in a browser. It is
the better choice for a human, and the wrong choice for an unattended agent.

**What Cargo does not advertise.** The authorization-server metadata carries no
`agent_auth` block, so there is no published `register_uri`, `claim_uri` or
`revocation_uri` under that extension, and no `identity_types_supported`. Cargo
does not support `identity_assertion` or ID-JAG (`id-jag`) token exchange: an
agent cannot present a delegated identity assertion from its own issuer and
have Cargo mint a scoped credential for it. Use a method from the table. If
your client looks for `agent_auth` first and falls back, the fallback is the
path that works.

## Register

**There is no separate sign-up step.** `--email` and `--oauth` both create the
account on first use. Signing in with an address that already has an account
resolves to its existing workspace rather than creating a second one, so this
is safe to re-run.

Pick the workspace at sign-in time to skip the interactive prompt:

```sh
npx @cargo-ai/cli login --email you@company.com --workspace-name "Acme GTM"
npx @cargo-ai/cli login --oauth --workspace-uuid 550e8400-e29b-41d4-a716-446655440000
```

`--workspace-name` reuses a workspace of that name when one exists and creates
it when it does not.

## Claim

With a terminal, one call and a prompt:

```sh
npx @cargo-ai/cli login --email you@company.com
```

Without a terminal, which is the usual agent case, it is two calls. The first
sends the code and exits; the second finishes. Pass the code on stdin to keep
it out of shell history:

```sh
npx @cargo-ai/cli login --email you@company.com
echo 123456 | npx @cargo-ai/cli login --email you@company.com --code -
```

`--email` and `--oauth` save the session to
`~/.config/cargo-ai/credentials.json` and **renew it automatically**, so you
stay signed in without minting a token per machine.

## Use the credential

Once signed in, every `cargo-ai` command is authenticated and there is nothing
to pass. Confirm with:

```sh
npx @cargo-ai/cli whoami   # JSON: user, workspace, auth source, base URL
npx @cargo-ai/cli doctor   # JSON: version, credentials, API reachability; exit code is the severity
```

**Know which workspace you are in before you write anything.** A credential
resolves to exactly one workspace, and a request against the wrong one returns
plausible data rather than an error. `whoami` reports the workspace and whether
the credential came from the environment or the credentials file.

**Precedence, highest first**, which is what to reason about when a command
resolves somewhere unexpected:

1. Environment: `CARGO_API_TOKEN`, `CARGO_WORKSPACE_UUID`, `CARGO_BASE_URL`.
2. The nearest project `.env`, found by walking up from the working directory,
   filling in only what is not already exported.
3. The saved session in `~/.config/cargo-ai/credentials.json`.

A token passed with `--token` stays pinned to its own workspace, so
`CARGO_WORKSPACE_UUID` does not override it. A session from `--email` or
`--oauth` can be pointed elsewhere with that variable, or by signing in again
with `--workspace-uuid`.

**Calling HTTP directly**, without the CLI, send the token as a bearer:

```sh
curl https://api.getcargo.io/v1/... \
  -H "Authorization: Bearer $CARGO_API_TOKEN"
```

`components.securitySchemes` in the OpenAPI document declares one scheme,
`bearerAuth`. There is no unauthenticated endpoint, and nothing accepts a query
parameter or a cookie.

**MCP uses the same credential and needs no second one.** A Cargo MCP server is
a resource inside a workspace rather than a public endpoint, so there is no
shared URL to point a client at. Expose one over stdio and it reuses the
session you already have:

```sh
npx @cargo-ai/cli ai mcp-server list
claude mcp add cargo -- cargo-ai mcp --server <uuid>
```

No token is copied into client config. `CARGO_MCP_SERVER_UUID` sets the default
server, and a workspace with exactly one server needs no `--server` at all.

## Errors

Every 4xx and 5xx response is a JSON object carrying `errorMessage`, a
human-readable description of what went wrong. Many orchestration routes also
carry `reason`, a machine-readable cause. Treat the object as open: routes may
add fields, so ignore ones you do not recognise.

| Status | What it means                                      | What to do                                                      |
| ------ | -------------------------------------------------- | --------------------------------------------------------------- |
| `400`  | The request was malformed                          | Read `errorMessage`. Do not retry unchanged                     |
| `401`  | Missing, expired or revoked credential             | Sign in again. Do not retry with the same credential            |
| `404`  | No such resource, or not visible to this workspace | Check `whoami` before concluding it does not exist              |
| `409`  | A request with this Idempotency-Key is in progress | Wait, then retry with the same key                              |
| `422`  | Idempotency-Key reused with a different body       | Use a new key for a new operation                               |
| `429`  | Rate limited, counted per workspace                | Back off by `Retry-After`, in seconds. Do not retry immediately |
| `5xx`  | Server-side                                        | Retry with backoff. On a write, reuse the same Idempotency-Key  |

Rate-limit state is reported on every `/v1` response, including `401`, as
`RateLimit` (`"workspace";r=<remaining>;t=<window seconds>`),
`RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset` and
`RateLimit-Policy`, with the legacy `X-RateLimit-*` spellings alongside them.

**Write operations accept `Idempotency-Key`.** Send a client-generated
string (a UUID is fine) on `POST`, `PUT` and `PATCH`. A retry with the
same key and the same body returns the original response instead of
creating a second record. Reusing a key with a different body returns
`422`. A concurrent retry while the first is still running returns
`409`. Keys last 24 hours. The header is optional: a request without
one is processed as it always was. Multipart file uploads reject the
header with `400`: the body is not readable in time to fingerprint it.

## Revocation

```sh
npx @cargo-ai/cli logout
```

That revokes the saved credential and removes it locally, in one step. The next
request returns `401`.

API tokens created for CI are revoked from the Console under the workspace's
API tokens, and revocation takes effect immediately.

**Rotate rather than share.** A token identifies a workspace, not a person or
an agent, so a shared token cannot be revoked for one caller without breaking
the others. An agent that needs its own access should sign in with its own
address: `--email` creates the account, so this costs nothing.
