---
title: "Login"
description: "Device-code sign-in, and how a session is scoped to one workspace."
url: "https://saved.sh/docs/cli/login"
---

```bash
sctl login
```

```
To sign in, open:

    https://auth.saved.sh/device

and enter the code: WDJB-MJHT

Waiting for confirmation…

Signed in as you@example.com.
```

The **device code** flow is the right one for a terminal: no local callback server, no browser
embedded in a CLI, and it works over SSH. Approve it in a browser on any device, including
your phone, and the CLI picks the session up.

## Where the session lives [#where-the-session-lives]

| Platform | Path                                             |
| -------- | ------------------------------------------------ |
| Linux    | `~/.config/sctl/config.yaml`                     |
| macOS    | `~/Library/Application Support/sctl/config.yaml` |
| Windows  | `%AppData%\sctl\config.yaml`                     |

The directory is created `0700` and the file `0600` on Unix-like systems. On Windows it
inherits your user profile's ACL.

<Callout type="warn">
  **That file is a credential.** It holds an access token and a refresh token. Treat it like an
  SSH private key: do not sync it to a shared drive, do not commit it, and delete it before
  handing a machine back.
</Callout>

Tokens are refreshed automatically. You will not normally sign in twice on the same machine.

## Checking who you are [#checking-who-you-are]

```bash
sctl auth whoami
```

```
User:    you@example.com
ID:      018f3c2a-...
Workspace: 4b7e1d90-...
Role:    admin
Permissions:
  - backups:read
  - backups:write
  ...
```

This is the fastest answer to "why was that refused": the permission list is exactly what the
backend will enforce. If the permission you expected is missing, the problem is your role,
not the command.

## Workspaces [#workspaces]

A session is scoped to **exactly one workspace**. Switching re-mints your token into the new
one, so there is no token that spans two.

```bash
sctl workspace list
sctl workspace switch <workspace-id>
sctl workspace current
```

`switch` takes the workspace **ID**, which `sctl workspace list` prints alongside the name.

```bash
sctl workspace create "analytics"
```

Creating one makes you its admin, and it is human-only: a machine credential is refused, since
a brand-new workspace has no permissions to check against.

<Callout>
  If you have no workspace yet, `sctl login` says so and tells you to create one or accept an
  invitation. A new user with no membership has no permissions anywhere, which is intentional.
</Callout>

## Signing in for CI [#signing-in-for-ci]

Do not use your own session in a pipeline. Use an [API key](/docs/cli/api-keys), and supply it
through the environment rather than logging in.

```bash
export SCTL_ACCESS_TOKEN='sk_live_...'
export SCTL_WORKSPACE_ID='4b7e1d90-...'
sctl backup list
```

```powershell
$env:SCTL_ACCESS_TOKEN = 'sk_live_...'
$env:SCTL_WORKSPACE_ID = '4b7e1d90-...'
sctl backup list
```

Both variables are needed. A machine never ran `sctl workspace switch`, so nothing has told it
which workspace to act on.

<Callout type="warn">
  A machine key **cannot** create a workspace, manage workers, manage other API keys, or touch
  billing, whatever permissions you try to grant it. See
  [permissions](/docs/security/permissions#machine-limits).
</Callout>

## Pointing at a different backend [#pointing-at-a-different-backend]

```bash
sctl --backend-url https://api.staging.saved.sh workspace list
export SCTL_BACKEND_URL=https://api.staging.saved.sh
```

The value is stored in the config file when you log in, so a session and the backend it was
minted against stay together.

<Callout type="error">
  Environments are fully isolated. A session for one is worthless against another, and mixing
  them is the usual cause of a confusing `401`. If you work across two, keep two shells with
  different `SCTL_BACKEND_URL` values rather than switching back and forth in one.
</Callout>

## Signing out [#signing-out]

There is no `sctl logout`. Delete the config file:

```bash
rm ~/.config/sctl/config.yaml                          # Linux
rm ~/Library/Application\ Support/sctl/config.yaml     # macOS
```

```powershell
Remove-Item "$env:AppData\sctl\config.yaml"
```

That removes the session from **this machine**. It does not revoke anything server-side: your
account and any API keys are unaffected. To end access for a person, remove their membership;
to end access for a machine, revoke its key.

## Troubleshooting [#troubleshooting]

| Message                                           | Cause                                                                       |
| ------------------------------------------------- | --------------------------------------------------------------------------- |
| `not signed in`                                   | No access token in the config file                                          |
| `no workspace selected`                           | Signed in, but no workspace chosen. In CI, `SCTL_WORKSPACE_ID` is unset     |
| `401` on every command                            | The session is for a different backend, or the key was revoked              |
| `403` naming a permission                         | The credential is valid and lacks that permission. Check `sctl auth whoami` |
| The browser approved but the CLI is still waiting | The code expired. Run `sctl login` again                                    |

## Next [#next]

<Cards>
  <Card href="/docs/cli/workspaces" title="Workspaces" description="Members, roles and invitations." />

  <Card href="/docs/cli/api-keys" title="API keys" description="Credentials for automation." />
</Cards>
