Skip to content

Support other accounts of a login, e.g. a company account, via --account - #399

Open
HerrNiklasRaab wants to merge 3 commits into
pytr-org:masterfrom
HerrNiklasRaab:switch-account
Open

HerrNiklasRaab wants to merge 3 commits into
pytr-org:masterfrom
HerrNiklasRaab:switch-account

Conversation

@HerrNiklasRaab

@HerrNiklasRaab HerrNiklasRaab commented Sep 30, 2026 •

Copy link
Copy Markdown

What this adds

A Trade Republic login can act for more accounts than its own, e.g. a company account (legal entity, "Private Holding"). pytr could not reach them. This PR adds:

  • pytr accounts lists the accounts of the login (type, name, customer id, state).
  • --account <type, name or customer id> on every subcommand that logs in, e.g. pytr portfolio --account LEGAL_ENTITY or pytr dl_docs --account "Mustermann Holding GmbH" ./docs.
  • TradeRepublicApi.relationships() and TradeRepublicApi.switch_account() for library users.

Without --account nothing changes: same requests, same errors.

How Trade Republic addresses these accounts

They are not a second securities account under the same customer. Each one is a customer of its own, and the web app swaps the session cookies to act for it:

  1. GET /api/v1/customer/relationships/detailed lists them. The login's own entry has relationshipType: SELF; a company account has relationshipType: LEGAL_ENTITY_ACTOR, accountType: LEGAL_ENTITY.
  2. POST /api/v2/auth/web/session with {"subjectId": <customerId>} returns cookies that act for that customer. The web app uses the same call, with the current subject, to refresh its session.
  3. From then on the REST calls and the websocket answer for that account only. accountPairs returns its securities and cash account; compactPortfolioByType, cash and the timeline topics work as usual.

Two things behave differently in a switched session:

  • /api/v2/auth/account answers 400 INVALID_AUTH_ACCOUNT_STATE. pytr reads the securities account number from that call (Fix portfolio mode by adding secAccNo parameter to compactPortfolio #256), so after a switch compact_portfolio() reads it from accountPairs instead.
  • The refresh pytr uses today, GET /api/v1/auth/web/session, returns the session to the login's own account. After a switch the refresh therefore posts the subject. A new process starts with the subject-less refresh, so the choice is never carried over from an earlier run through saved cookies.

Changes

  • switch_account() resolves the argument against relationships() (customer id, type, name or first name; exact, case-insensitive; ambiguous or unknown values raise and name the known accounts) and posts the subject to the session endpoint.
  • _web_request keeps the chosen subject when it refreshes the session. For the own account it is unchanged.
  • compact_portfolio() looks the securities account up via accountPairs after a switch. Answers to other subscriptions that arrive during that lookup are kept for the next recv().
  • login() switches after logging in when --account is given and exits with a message if the account is unknown or the switch is refused.
  • README: new section and regenerated usage block.

Testing

Unit tests: 47 new cases in tests/test_accounts.py; the whole suite, ruff check, ruff format --check and mypy pass.

Run against a real login that has a company account, with --v2:

  • pytr accounts lists both accounts.
  • pytr portfolio --account LEGAL_ENTITY switches and loads that account's portfolio and cash, both after a fresh login and when resuming from cookies saved with --store_credentials.
  • A run without --account after that uses the own account again.
  • A switched session still acts for the company account after a forced refresh and after waiting 330 seconds; a subject-less refresh returns it to the own account; switch_account() without an argument does the same.
  • An unknown --account value exits with the list of accounts.

Not tested:

  • Child accounts (Support Child Savings Accounts #228). I have none. If the web app switches to them the same way, this should cover them, but I have not seen what their entry in the relationships list looks like, so this PR does not claim to close that issue.
  • A session created by the v1 login. All real runs used --v2.
  • A login with more than one account of the same type. Selecting by customer id is covered by unit tests only.

Refs #228

🤖 Generated with Claude Code

HerrNiklasRaab and others added 3 commits October 1, 2026 01:35
A Trade Republic login can act for more accounts than its own, e.g. a
company account (legal entity) or a child's account. These are separate
customers: the web app lists them at /api/v1/customer/relationships/detailed
and switches by posting the customer id as subjectId to
/api/v2/auth/web/session, which swaps the session tokens.

- add relationships() and switch_account() to the API, and --account to
  every subcommand that logs in
- keep the session on the chosen account when it is refreshed
- read the securities account number from the accountPairs topic when
  /api/v2/auth/account refuses with INVALID_AUTH_ACCOUNT_STATE, which it
  does while the session acts for another account
- accept saved cookies that act for another account instead of forcing a
  new login, and return to the own account when --account is not given

Refs pytr-org#228

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Prints type, name and state of every account the login can act for, from
the same relationships call the switch resolves its argument with.

Refs pytr-org#228

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Live tests showed that the session refresh without a subject returns the
session to the login's own account, and that a new process always starts
with that refresh. So saved cookies never carry the chosen account into
the next run, and the handling added for that case could not be reached.

- drop the INVALID_AUTH_ACCOUNT_STATE handling in resume_websession(),
  login() and compact_portfolio(); the own-account paths are as before
- keep answers to other subscriptions while compact_portfolio() looks up
  the securities account, instead of discarding them
- report a missing or unanswered accountPairs lookup as ValueError
- exit with a message when the switch is refused, not a traceback
- match accounts by customer id, type, name or first name; show the
  customer id in 'pytr accounts' for accounts that share type and name
- tolerate missing or non-text fields in the relationships list
- stop claiming child accounts work: only a company account was tested
- tests: fail on unexpected requests, isolate login() from ~/.pytr, pin
  the request order of a new process, cover the error paths

Refs pytr-org#228

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@HerrNiklasRaab HerrNiklasRaab changed the title Support company and child accounts via --account Support other accounts of a login, e.g. a company account, via --account Oct 1, 2026
@HerrNiklasRaab
HerrNiklasRaab marked this pull request as ready for review October 1, 2026 00:03
@RealCLanger

Copy link
Copy Markdown
Collaborator

Sounds cool - I'll give it a try. 😄

@HerrNiklasRaab

Copy link
Copy Markdown
Author

Do you have child account you could try?

@RealCLanger

Copy link
Copy Markdown
Collaborator

Do you have child account you could try?

Yes, I can test that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants