Skip to content

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.

Updated September 3, 20264 min readPostsale CLI

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 postsale command 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:

  1. Run:
bash
postsale auth login
  1. Complete the sign-in in your browser.

  2. Confirm the session:

bash
postsale auth status

Tokens 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:

bash
postsale auth login --no-browser

Follow 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:

bash
export POSTSALE_TOKEN='<access token>'

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:

bash
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_ignored warning line to stderr naming the key's display prefix (local-only commands such as auth status and account jwt-scopes report it in their output as ignored_env_credential instead); postsale auth logout makes the key active, and auth logout tells you the key is now in use when the variable is still set. auth status and doctor show the shadowed credential as ignored_env_credential.
  • postsale auth status reports credential_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 doctor probes with a scoped carriers read instead of the user profile, which a key cannot reach. A key that lacks read:carriers warns 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 answer api_key_unsupported (exit 100, next step auth.login) because they need a login session; nothing about the key's scopes changes that.
  • A missing permission answers api_key_rejected with missing_scopes naming 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 answers api_key_rejected, with the backend's own message kept. Logging in never fixes either.
  • account jwt-scopes cannot 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

bash
postsale account env

The 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

bash
postsale account whoami
postsale account whoami --include-sensitive   # only when you need full fields
postsale account jwt-scopes                   # local decode; advisory only

whoami 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:

VariablePurpose
ANTHROPIC_API_KEYAnthropic
ANTHROPIC_AUTH_TOKENAnthropic Bearer alternative (API key wins if both)
OPENAI_API_KEYOpenAI
POSTSALE_LLM_PROVIDERForce anthropic or openai
POSTSALE_LLM_MODELOptional 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 seeLikely causeWhat to do
Exit 100 / auth_requiredNo valid tokenLogin or set POSTSALE_TOKEN
Exit 100 / api_key_unsupportedThe command needs a login session; keys cannot reach itauth login, or skip that command on the key
Exit 100 / api_key_rejectedKey lacks a scope, or was revoked, disabled, or is off-planGrant missing_scopes in the app (about a minute), or issue a key
Env token ignoredSession file present (a shadowed key prints env_credential_ignored)auth logout
run fails with empty stdoutMissing LLM key or bad configRead stderr; set a provider key

Conversations, traces, and saved queries also live under ~/.postsale/ by default. See Conversations, traces, and saved queries.

Additional Reading