# Authentication for CLI and scripts

> Source: https://parallelworks.com/docs/cli/authentication

Use `pw auth` for work you run yourself. Use an API key for a scheduled job or service that must run without someone signing in again. A script's runtime matters, but so does who will be there when its credentials expire.

## Choose a method

| What you need to do | Use | What happens over time |
| --- | --- | --- |
| Work in a terminal on your computer | [Browser sign-in](#browser-and-device-sign-in): `pw auth` | The CLI renews access until the sign-in expires or is revoked. |
| Work over SSH or on a computer without a browser | [Device sign-in](#browser-and-device-sign-in): `pw auth --device` | You approve in a browser on another device; renewal works like browser sign-in. |
| Run a notebook, analysis, or long-running script yourself | [Your saved sign-in](#long-running-scripts): CLI commands or an SDK created from credential config | Each tool obtains fresh access tokens as needed. |
| Run CI, cron, a scheduled job, or a persistent service | [An API key](#unattended-jobs-and-key-rotation) supplied through `PW_API_KEY` or a dedicated context | The key does not renew. Replace it before its chosen expiration. |
| Make a one-off API call from a signed-in computer | [`pw auth token --print`](#calling-the-api-with-curl) | The printed access token expires; ask the CLI for another before a later call. |

Browser and device sign-in both use your normal platform sign-in, including SSO and MFA. Device sign-in still requires a person to approve it; it is not an unattended login method. SSH keys authenticate SSH connections to clusters, not CLI or REST API requests. See [account authentication](/docs/account-settings/authentication) for SSH keys and MFA settings.

## Browser and device sign-in

Replace `cloud.parallel.works` with your platform's host:

```bash
pw auth cloud.parallel.works
```

For a remote terminal or headless computer:

```bash
pw auth --device cloud.parallel.works
```

Open the address printed by the CLI on a device with a browser. Enter the code and check that the page names the computer you are signing in from before approving. The resulting credentials are saved on the computer running `pw`, not the computer where you approved the code.

`pw auth login` is equivalent. The CLI also falls back to device sign-in when it cannot open a browser. If your organization disables [device code sign-in](/docs/organization-admin/settings/policies#disable-device-code-sign-in), use browser sign-in on the same computer or an API key.

The CLI saves a **context** containing the account, platform, and credentials. Its default name is `user@host`. Use `--context-name` to name it, then select it explicitly in scripts:

```bash
pw auth --context-name research cloud.parallel.works
pw auth whoami --context research
pw api --context research buckets
```

### How long a sign-in lasts

The current platform issues access tokens valid for one hour. The CLI renews them before expiry using a refresh token. That refresh token expires after 30 days without renewal, and a sign-in requires authentication again after at most 90 days, even with regular use. Signing the device out or an administrator revoking access can end it sooner.

These limits are why a renewable sign-in suits a long analysis you run yourself, while a persistent unattended service needs an API key and a rotation plan. A running process does not keep a sign-in alive unless it actually requests renewed access.

Refresh tokens are kept in the OS credential store when available, or in the owner-only credentials file otherwise. Let `pw` manage them: the platform rotates refresh tokens, and reusing an old one revokes the sign-in's token chain.

## Long-running scripts

### Let the CLI or SDK obtain fresh tokens

For shell scripts, call `pw` directly. For endpoints without a dedicated command, [`pw api`](/docs/cli/pw/api) handles authentication and the platform URL:

```bash
pw api --context research buckets
```

For Go, Python, and TypeScript, use the [SDK credential-config constructors](/docs/sdk#signed-in-with-pw-auth) after signing in with `pw auth`. **The SDK handles OAuth access tokens and renewal automatically through the CLI**, so your script does not need to copy tokens or implement refresh logic. It runs `pw auth token --print -o json`, caches the access token, and asks again shortly before it expires. Keep `pw` installed and available to the process, including after it has started. For example:

```python
from parallelworks_client import Client

with Client.from_credential_config(context="research").sync() as client:
    response = client.get("/api/buckets")
    response.raise_for_status()
```

Reuse that client for later requests; it handles renewal. The process needs access to the same writable credentials directory and, when used, the same OS credential store as the sign-in. If the sign-in expires or is revoked, run `pw auth --context-name research cloud.parallel.works` again, then retry the failed work. Renewal does not make a script independent of the sign-in's lifetime.

### Calling the API with curl

If a tool needs a bearer token, ask the CLI immediately before the request. This Bash example stops if obtaining a token fails and uses the named context's platform:

```bash
set -euo pipefail
unset PW_API_KEY PW_PLATFORM_HOST

pw_host=$(pw auth identify-platform)
case "$pw_host" in
  http://*|https://*) pw_base_url="$pw_host" ;;
  *) pw_base_url="https://$pw_host" ;;
esac
pw_access_token=$(pw auth token --print)
curl --fail-with-body --silent --show-error \
  -H "Authorization: Bearer $pw_access_token" \
  "$pw_base_url/api/buckets"
unset pw_access_token
```

Repeat token acquisition for each later request, including inside a polling loop. For a client that caches tokens, `pw auth token --print -o json` returns `access_token`, `token_type`, and `expires_in` when the CLI knows the remaining lifetime. Renew before that lifetime runs out; use an SDK if you do not need to implement this yourself.

### Avoid freezing a token at startup

Do not use `export PW_API_KEY="$(pw auth token --print)"` to keep a long-running script signed in. It copies one short-lived access token into the environment. `PW_API_KEY` overrides saved contexts, and the CLI prints its value as is rather than renewing it. A raw-token SDK constructor also keeps using the supplied token; it does not inherit the sign-in that produced it.

If you already exported a copied token, run `unset PW_API_KEY` before using your saved context again. API keys, including those created with the **Token** option in Account > Authentication, are fixed credentials: saving one in a context does not make it renewable.

## Unattended jobs and key rotation

[Create an API key](/docs/account-settings/authentication#create-an-api-key) for each automation so you can replace or revoke it independently. An API key acts with its owner's account access; a context name or separate credentials directory does not restrict its permissions.

For a one-off job, the **Token** option creates a [short-lived API key](/docs/account-settings/authentication#managing-tokens) lasting 1–24 hours. Choose the expiration to cover the job; this is the same API-key mechanism, with no renewal.

Have your CI secret store or service manager supply `PW_API_KEY` to the process. There is no need to run `pw auth` first:

```bash
set -eu
: "${PW_API_KEY:?Supply an API key through the job secret store}"
pw auth whoami
pw api buckets
```

An API key includes its platform host. An opaque access token from browser sign-in does not; if you deliberately supply one through `PW_API_KEY`, also supply `PW_PLATFORM_HOST` when there is no saved context identifying the platform. It still will not renew.

By default, an API key's chosen expiration must be within 730 days. The [Maximum API Key TTL policy](/docs/organization-admin/settings/policies#maximum-api-key-ttl) can change that limit; non-expiring keys are available only when the policy is **Unlimited**. API keys never renew automatically, regardless of their chosen lifetime.

Before a key expires:

1. Create a replacement and update the job's secret store or dedicated saved context.
2. Restart a persistent process or recreate its SDK client so it reads the replacement. Changing a secret does not update the environment or client already held by a running process.
3. Verify the job works with the new key, then delete the old key in Account > Authentication.

For cron, schedulers, and service managers, configure `PATH` and credentials in the job's environment; do not rely on your interactive shell startup files. When using a saved context, set an absolute `PW_CREDENTIALS_DIR` and `PW_CONTEXT`, and run the job as the OS user who owns those credentials.

## Select an account without changing other work

CLI API requests and SDK clients created from credential config choose credentials in this order:

1. `PW_API_KEY`, if nonempty.
2. An explicit `--context` flag or SDK context option.
3. `PW_CONTEXT`.
4. The saved current context.

`pw context use` changes the saved default for other commands using the same file. Prefer `--context` for one command, or `PW_CONTEXT` inside a script or subshell:

```bash
(
  unset PW_API_KEY PW_PLATFORM_HOST

  pw auth whoami
  pw api buckets
)
```

Use `pw context list` to inspect saved contexts. A selected context can still be overridden by `PW_API_KEY`, so `pw auth whoami` is the better check for the account an API request actually uses. Unset an old `PW_PLATFORM_HOST` override when returning to the platform saved in your context.

## Isolate credentials with PW_CREDENTIALS_DIR

Use contexts to switch accounts within one credentials file. Use `PW_CREDENTIALS_DIR` when a job, container, test, or remote host should have a separate file and its own current context.

| Configuration | Credentials file |
| --- | --- |
| Default | `$HOME/.config/pw/credentials` |
| Absolute `XDG_CONFIG_HOME` set | `$XDG_CONFIG_HOME/pw/credentials` |
| `PW_CREDENTIALS_DIR` set | `$PW_CREDENTIALS_DIR/.credentials` |

The override takes a **directory**, not a filename. The filename under the override is **`.credentials`**, including the leading dot. It takes precedence over `XDG_CONFIG_HOME` and disables legacy migration and fallback to `~/pw/.credentials`. An empty directory starts with no saved contexts. Merely pointing it at `$HOME/.config/pw` will not select the usual `credentials` file because the names differ.

It changes credential storage for the CLI and the SDK credential-config constructors. It does not move the CLI's settings, cache, or state, bypass `PW_API_KEY`, or change account permissions. Use an absolute path: a relative path changes meaning when the job's working directory changes.

### Give a script its own sign-in

Sign in afresh in a separate directory instead of copying a live sign-in:

```bash
(
  unset PW_API_KEY PW_CONTEXT PW_PLATFORM_HOST

  pw auth --context-name analysis cloud.parallel.works
  pw auth whoami --context analysis
)
```

Use that directory and context every time you run the script:

```bash
(
  unset PW_API_KEY PW_PLATFORM_HOST

  python analysis.py
)
```

Here, `analysis.py` is your script using `Client.from_credential_config()`. The subshell keeps these overrides out of your normal terminal session. For one command, you can also prefix it with `PW_CREDENTIALS_DIR="/absolute/path" PW_CONTEXT=analysis`, provided no `PW_API_KEY` override is set.

### Use a temporary directory for a job

If a tool expects saved CLI credentials, load an API key from a secret file into a fresh directory. In this Bash example, `/run/secrets/pw-api-key` is the secret file supplied by your runner:

```bash
(
  set -eu
  unset PW_API_KEY PW_CONTEXT PW_PLATFORM_HOST
  pw_job_credentials=$(mktemp -d)
  trap 'rm -rf "$pw_job_credentials"' EXIT

  pw auth --with-api-key --context-name job < /run/secrets/pw-api-key
  pw api --context job buckets
)
```

The trap removes the local credentials on exit. It does not revoke the API key; delete the key in Account > Authentication when it is no longer needed. Prefer injecting `PW_API_KEY` directly when a saved file is unnecessary.

### Containers, shared homes, and remote hosts

- **Protect the directory.** Keep credentials outside your repository and container image. Use a directory owned by the runtime user with mode `0700`, and a credentials file with mode `0600`. Avoid `sudo pw auth` against another user's directory.
- **Allow renewal to write.** A browser or device sign-in updates its credentials file when it renews. A read-only mount cannot support that. Mount a writable directory, not only a single file, because updates replace the file atomically.
- **Do not copy a live sign-in to another machine or container.** The file can refer to a refresh token in the original OS keychain. Even without a keychain, independent copies can reuse an already rotated refresh token and revoke the sign-in. Sign in separately on the destination or use an API key.
- **Use a separate sign-in per host when home directories are shared.** For example, set `PW_CREDENTIALS_DIR="$HOME/.config/pw-identities/$(hostname)"` before signing in and running commands on each host. The CLI coordinates local refreshes through its cache, but independent hosts must not compete to renew one sign-in. If network filesystem locks stall, use an absolute, user-owned local `XDG_CACHE_HOME`, consistently for all processes sharing that host's sign-in.

## Sign out and troubleshoot

Run `pw auth logout` with the same credentials-directory environment to revoke and remove a browser or device sign-in. Each sign-in also appears under **Settings > Connected apps**. Deleting a credentials file alone does not revoke it. For an API-key context, logout only removes the local context; delete the key in **Account > Authentication** to revoke it.

| Symptom | Check and next step |
| --- | --- |
| The wrong account is used, even with `--context` | Check whether `PW_API_KEY` is set without printing its secret value. Unset it to use the context, then run `pw auth whoami`. |
| It works at first, then fails after roughly an hour | Look for a copied access token in `PW_API_KEY` or a raw-token SDK constructor. Use a saved sign-in and the credential-config constructor. |
| A saved sign-in cannot renew | Check `pw` is on the job's `PATH`, the credentials directory is writable, and the OS credential store is accessible. If the sign-in expired or was revoked, sign in again in that same directory and context. |
| A context disappears after setting `PW_CREDENTIALS_DIR` | Check the absolute directory and the `.credentials` filename. The override does not search your default credentials file. |
| A job works in your terminal but fails in cron or a container | Check its OS user, `PATH`, `PW_CONTEXT`, `PW_CREDENTIALS_DIR`, and secret injection. Its environment and keychain access may differ from your shell. |
| An API key expired | Create a replacement, update the secret, and restart processes that hold the old value. `pw auth token --print` cannot extend its lifetime. |
