Configuration¶
pplx-export keeps your identity data — the account registry (display names, login emails, user IDs) and the BOT space — in a user-level TOML file that lives outside the repository. This page covers where that file lives, every field it accepts, what happens when it is missing, and how the registry drives multi-account cookie handling.
Why the config lives outside the repo¶
The account registry and the BOT space are personal data and are never committed to the repository (pplx_export/config.py:7-12). The repo ships only a placeholder template, config.example.toml; your real values go into a private copy. Everything else the tool needs — the site domain, API URLs, the default archive root — is a code constant (pplx_export/config.py:50-58), not user configuration.
The TOML carries identity data only. Cookie sourcing and transport selection are per-invocation CLI flags, not config fields — see CLI flags, not config fields below.
Location and load priority¶
configure() (pplx_export/config.py:113) resolves the config path with this precedence (pplx_export/config.py:95-110):
| Priority | Source | Counts as explicit |
|---|---|---|
| 1 | --config PATH CLI flag |
yes |
| 2 | PPLX_EXPORT_CONFIG environment variable |
yes |
| 3 | ~/.config/pplx-export/config.toml (default path) |
no |
"Explicit" matters for error behavior when the file is missing — see degraded mode. Both CLI entries reload the config in strict mode after argument parsing (pplx_export/cli.py:223, pplx_export/ask_cli.py:278); the import-time load (pplx_export/config.py:174-179) is fault-tolerant, so importing the package never fails on a missing file.
Creating your config¶
mkdir -p ~/.config/pplx-export
cp config.example.toml ~/.config/pplx-export/config.toml
chmod 600 ~/.config/pplx-export/config.toml
Then edit the copy. The template uses pure placeholders — copy the structure, replace every value:
# Default account used when --account is not given (a key of [accounts.<name>] below)
default_account = "alice"
# Account registry: key = account username (the username in thread URLs / library)
[accounts.alice]
# Full display name: used for archive directory naming (web_archive/<display name>/…)
display_name = "Alice Example"
# Login email: verifies cookie ownership
email = "alice@example.com"
# Account uid (required for thread-viewed telemetry)
user_id = "00000000-0000-4000-8000-0000000000aa"
[accounts.bob]
display_name = "Bob Example"
email = "bob@example.com"
user_id = "00000000-0000-4000-8000-0000000000bb"
# BOT space: where threads created by pplx-ask are collected after completion
[bot_space]
uuid = "00000000-0000-4000-8000-0000000000b0"
slug = "bot-EXAMPLE"
Placeholder style: alice/bob are made-up account usernames, emails use example.com, and UUIDs use the all-zeros 00000000-0000-4000-8000-… form. In your real file the table key must be the actual account username as it appears in thread URLs and your library.
Keep it private
The real config contains personal data (emails, user IDs). Recommended permission is 0o600; never commit it to any git repository (config.example.toml:4-6).
Field reference¶
Top level¶
| Field | Type | Meaning |
|---|---|---|
default_account |
string | Key of one [accounts.<name>] table, used when --account is not given (pplx_export/commands/common.py:84-85). Empty/missing = degraded mode. |
[accounts.<name>]¶
One table per account; <name> is the account username. The registry loads into three dicts keyed by username: ACCOUNT_DISPLAY_NAMES, ACCOUNT_EMAIL, ACCOUNT_UID (pplx_export/config.py:65-75).
| Field | Type | Required | Meaning |
|---|---|---|---|
display_name |
string | no | Full display name, used for archive directory naming (web_archive/<display name>/…); falls back to the username when omitted. See Archive layout. |
email |
string | recommended | Login email. The transport verifies cookie ownership against it, preventing "an export for account B carrying account A's session" (pplx_export/config.py:69-72). On mismatch the tool enumerates per-account session tokens in the browser and switches automatically — see Multi-account cookie model. |
user_id |
string | for pplx-ask telemetry |
Account uid, required by thread-viewed telemetry (pplx_export/config.py:73-75). Read it from GET /api/auth/linked-accounts, which returns every signed-in account's user_id / email / display_name — see API authentication. |
[bot_space]¶
The BOT space is the collection point for threads created by pplx-ask after they complete (pplx_export/config.py:76-79). Create the space itself with pplx-ask space-create (see pplx-ask), then register it here.
| Field | Type | Meaning |
|---|---|---|
uuid |
string | Space UUID. pplx-ask moves finished threads here (pplx_export/ask_cli.py:156-158); when empty, the move step is skipped. |
slug |
string | The space's URL slug. Loaded into BOT_SPACE_SLUG (pplx_export/config.py:79); the runtime CLI does not read it — the fixture-maintenance tool consumes it, building an identity-replacement pair from it (tests/scrub_fixtures.py:446-447). |
CLI flags, not config fields¶
The TOML has no transport or cookie settings. Those are chosen per invocation:
| Concern | Where it is set |
|---|---|
| Config file path | --config PATH, or PPLX_EXPORT_CONFIG |
| Cookie source | --cookies-from BROWSER / --cookies FILE |
| Transport | --transport cookie\|webbridge (pplx-export only; default cookie) |
See pplx-export for the full flag reference.
Missing config: degraded mode¶
When nothing is loaded, the module-level registries stay empty and LOADED_CONFIG_PATH is None (pplx_export/config.py:83-85). Behavior by scenario (resolve_cli_account, pplx_export/commands/common.py:51-90):
| Scenario | Behavior |
|---|---|
No config at the default path, --account not given |
Degraded mode: a warning is logged and commands run with a placeholder account (username='default'); the email ownership check is skipped. Day-to-day offline commands are unaffected (pplx_export/commands/common.py:86-90). |
No config, explicit --account |
SystemExit naming the lookup order and pointing at config.example.toml (pplx_export/commands/common.py:67-74). |
Config loaded, --account not registered |
SystemExit naming the loaded file, asking you to add [accounts.<name>] (pplx_export/commands/common.py:77-82). |
Explicit path (--config / env var) does not exist |
ConfigError in strict mode (pplx_export/config.py:140-146). |
| File exists but fails to parse | Always ConfigError — a corrupt config must not silently degrade (pplx_export/config.py:147-150). |
--account omitted, config loaded |
default_account is used (pplx_export/commands/common.py:84-85). |
What "offline commands" cover and how degraded runs interact with the archive is detailed in Offline operations.
Multi-account cookie model¶
With several accounts signed into the same browser, the store holds one session cookie per account, and the config's email field tells the tool which one it needs:
- Each signed-in account has a
__Secure-pplx.session.<uid>cookie (ACCOUNT_SESSION_PREFIX,pplx_export/core/cookies.py:93); the<uid>suffix is the account'suser_id. - The active account is whichever token currently sits in
__Secure-next-auth.session-token(ACTIVE_SESSION_COOKIE,pplx_export/core/cookies.py:94). Switching accounts = writing the target account's per-account cookie value into that cookie — no browser UI needed (pplx_export/core/cookies.py:102-104). - At startup the transport probes
GET https://www.perplexity.ai/api/auth/sessionand compares the returned email againstaccounts.<name>.email(pplx_export/commands/common.py:126-130). - On mismatch,
_try_switch_account(pplx_export/commands/common.py:190-215) enumerates every account token in the browser vialist_account_tokens(pplx_export/core/cookies.py:97-128, preferring entries on thewww.subdomain), tries each one in__Secure-next-auth.session-token, and rebuilds the transport on the first match. - If no token matches, the command exits naming both emails and asking you to log the target account in the browser first (
pplx_export/commands/common.py:142-145) — see Troubleshooting. - An account with no registered
emailproceeds unchecked, with a warning asking you to confirm the browser login yourself (pplx_export/commands/common.py:146-149).
For the full switching flow and the session-endpoint semantics, see Ask and accounts and API authentication.
Cookie cache¶
After successful validation the resolved cookies are cached so later runs skip the browser:
| Property | Value |
|---|---|
| Path | <archive root>/index/.cookies.json — follows --out (pplx_export/commands/common.py:111) |
| Freshness | 12 hours (CACHE_MAX_AGE_S = 12 * 3600, pplx_export/core/cookies.py:39); a stale or corrupt cache is treated as absent |
| Contents | fetched_at, source, account_email, cookies (pplx_export/core/cookies.py:211-215) |
| Write | Atomic: temp file created with mode 0o600, then os.replace (pplx_export/core/cookies.py:198-216) |
| Git | Covered by .gitignore (**/index/.cookies.json) |
Cookie resolution order (cookies.resolve, pplx_export/core/cookies.py:237-269): explicit --cookies-from → explicit --cookies file → fresh cache → auto-detect browsers (edge → chrome → firefox → safari). The cache is refreshed after every successful account validation (pplx_export/commands/common.py:150).
Protecting your files¶
chmod 600yourconfig.toml— it contains personal data (emails, user IDs).- The cookie cache is already written with mode
0o600by the tool; session cookies are login-equivalent credentials. - If you hand-craft a cookie file for
--cookies, applychmod 600to it as well.
When authentication fails¶
Expired cookies, an account the auto-switch cannot find, browser keychain permission errors, and other auth failures are covered in Troubleshooting.