Sign In and Credentials
Browser sign-in, device sign-in, unattended runs tokens, and API keys, plus how the CLI picks between them and how to check which environment you are on.
Before the CLI can list orders or create labels, it needs to know who you are. This article walks through browser login, device login, unattended runs tokens, API keys, and the extra keys used by postsale run.
In this article, we will walk through signing in with a browser or without one, understanding token precedence, checking your environment, and connecting LLM keys for natural language.
Before We Begin
This article assumes that:
- The
postsalecommand is on your PATH (postsale --version) - You have a Postsale account (same family of credentials as the web app)
Sign in with a browser
Here's how:
- Run:
postsale auth login-
Complete the sign-in in your browser.
-
Confirm the session:
postsale auth statusTokens are stored at ~/.postsale/tokens.json readable only by your user account. When a refresh token is available, the CLI refreshes for you.
You are signed in when auth status shows a valid session and postsale account whoami returns your profile JSON.
Sign in without a browser
Headless servers and remote shells often cannot open a browser. Use device authorization:
postsale auth login --no-browserFollow the device code instructions printed in the terminal, then run postsale auth status again.
Run the CLI unattended
For scripts, scheduled jobs and agents, use an API key (next section): it is scoped, it never expires, and you can disable it without touching anyone's sign-in. A login token also works for a short unattended run when you already have one:
export POSTSALE_TOKEN='<access token>'Warning
If a stored login session already exists on the machine, the session wins over POSTSALE_TOKEN. For pure token auth, run postsale auth logout first or use a clean environment.
Use an API key
An account on a plan that includes API keys can provision scoped keys in the Postsale app under Settings → Integrations → API Keys: name the key after what will use it, pick the Shipment API and Order API permissions it needs, and leave request logging on while you integrate. The key is shown once, starts with psk_, and goes into the same variable:
export POSTSALE_TOKEN='psk_…'What to expect on a key:
- Precedence is unchanged. A stored login session still wins. When a key is being shadowed, every command that calls the API prints one
env_credential_ignoredwarning line to stderr naming the key's display prefix (local-only commands such asauth statusandaccount jwt-scopesreport it in their output asignored_env_credentialinstead);postsale auth logoutmakes the key active, andauth logouttells you the key is now in use when the variable is still set.auth statusanddoctorshow the shadowed credential asignored_env_credential. postsale auth statusreportscredential_kind: "api_key", the key's display prefix, and no expiry. Keys never expire: disable one in the app to pause it (reversible), revoke it if the secret leaked (final), or regenerate it to get a new secret with the same id and permissions.postsale doctorprobes with a scoped carriers read instead of the user profile, which a key cannot reach. A key that lacksread:carrierswarns rather than fails: it is a valid, narrower key. A key the backend does not accept at all fails the auth check with exit 100.- What a key cannot reach: the user profile (
account whoami), automations, templates, and key, payment or subscription management. Those answerapi_key_unsupported(exit 100, next stepauth.login) because they need a login session; nothing about the key's scopes changes that. - A missing permission answers
api_key_rejectedwithmissing_scopesnaming what to grant. Edit the key's permissions in the app; changes take effect within about a minute. A key the backend rejects outright (unknown, revoked, disabled, or the plan no longer includes keys) also answersapi_key_rejected, with the backend's own message kept. Logging in never fixes either. account jwt-scopescannot list a key's scopes: they are not in the credential and nothing a key can call returns them. The app is the only place to see them.- Identity is per credential. Idempotency keys and traces derive their identity from the session's subject or the key's 8-character display id (
identity: { kind, id }in the trace). Switching between a session and a key, or regenerating a key, mints fresh idempotency keys, so a rerun across that boundary does not dedup against the earlier run. - A key never reaches a browser. Owner-action hand-offs (terms acceptance, reactivation) open the tokenless app page on a key; the owner signs in there.
- Request logging on a key keeps request and response bodies for seven days in the app's activity view. Turn it off when you are done integrating.
Check your environment
postsale account envThe CLI talks to your production Postsale account. To rehearse money paths without spending anything, use Test Without Spending. Money-moving commands print a one-line red [PRODUCTION] badge to stderr before they act, so a real charge is never a surprise. Details: Safety model.
Inspect the account
postsale account whoami
postsale account whoami --include-sensitive # only when you need full fields
postsale account jwt-scopes # local decode; advisory onlywhoami redacts sensitive fields by default. jwt-scopes is a hint from the token payload, not a guarantee of what the API will allow. To learn what the API will really allow, try the operation and read the response.
Model provider keys for the agent
Natural language needs a model provider key:
| Variable | Purpose |
|---|---|
ANTHROPIC_API_KEY | Anthropic |
ANTHROPIC_AUTH_TOKEN | Anthropic Bearer alternative (API key wins if both) |
OPENAI_API_KEY | OpenAI |
POSTSALE_LLM_PROVIDER | Force anthropic or openai |
POSTSALE_LLM_MODEL | Optional model override |
If both an Anthropic credential (ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN) and OPENAI_API_KEY are set and you do not override, Anthropic is selected silently. Run postsale doctor to see the active provider (llm_provider check). It may warn when both providers are configured without an explicit choice.
Good to Know
Common failures
| What you see | Likely cause | What to do |
|---|---|---|
Exit 100 / auth_required | No valid token | Login or set POSTSALE_TOKEN |
Exit 100 / api_key_unsupported | The command needs a login session; keys cannot reach it | auth login, or skip that command on the key |
Exit 100 / api_key_rejected | Key lacks a scope, or was revoked, disabled, or is off-plan | Grant missing_scopes in the app (about a minute), or issue a key |
| Env token ignored | Session file present (a shadowed key prints env_credential_ignored) | auth logout |
run fails with empty stdout | Missing LLM key or bad config | Read stderr; set a provider key |
Related paths on disk
Conversations, traces, and saved queries also live under ~/.postsale/ by default. See Conversations, traces, and saved queries.
Additional Reading
- Doctor and schema
- How the CLI works
- Batches and Unattended Runs
- Full variable list: Environment variables
On this page
Related