justETF Mobile Client
An unofficial client for justETF's mobile API
Go core library with Python bindings via ctypes.
Quick start | API surface | Python | Go | Build | Testing | C ABI
Quick Start
Clone the repository:
git clone https://github.com/wavepacket0/justetf-mobile-client.git
cd justetf-mobile-client
# Makes a live public profile request for the sample ISIN IE000VAHT5T0
go run ./examples/go profile
To build from source, follow Build.
See examples/ for additional Go and Python examples and Test for testing. For authenticated endpoints, you need to provide an account.
API Surface
| Area | Included |
|---|---|
| Public REST | profiles, discovery, and financial data |
| Account API | authenticated endpoints and profile updates |
| Go | Native client with caller contexts and typed request options |
| Python | Standard-library ctypes, decoded JSON, and exact raw-response access |
| Native ABI | Versioned C interface with explicit buffer ownership |
Shared HTTP execution
Endpoint methods define the route and request data, then delegate HTTP work to a shared Go executor. It builds and sends requests, applies common mobile API headers (client identity, language, API key, and request ID), and handles responses and errors.
Authentication is endpoint-specific: public requests remain unauthenticated even after login, while protected requests use the session managed by the Go client. Login and token refresh stay internal, session tokens are not exposed through the Python interface.
Build
The Python binding loads the Go client as a shared library. Build it for the same OS and architecture as the Python interpreter. Local builds require Go 1.22+, Python 3.10+, and a C compiler supported by cgo (MinGW-w64 GCC on Windows, Xcode Command Line Tools on macOS, or GCC/Clang on Linux).
Native library
# Windows
New-Item -ItemType Directory -Force bin | Out-Null
go build -trimpath -buildmode=c-shared -o bin/justetf-native.dll ./cmd/justetf-native
# Linux
mkdir -p bin
go build -trimpath -buildmode=c-shared -o bin/libjustetf-native.so ./cmd/justetf-native
# macOS
mkdir -p bin
go build -trimpath -buildmode=c-shared -o bin/libjustetf-native.dylib ./cmd/justetf-native
Python wheel
Build and smoke-test a wheel from the repository root. Wheel builds require cibuildwheel. Linux builds also require Docker or Podman.
python -m pip install --upgrade cibuildwheel
cp LICENSE python/LICENSE # CI does this so the wheel bundles the license
python -m cibuildwheel --config-file python/pyproject.toml --output-dir wheelhouse python
Each wheel targets the current OS and architecture, bundles the matching shared library, and runs an isolated mock-API smoke test.
Test
Install test-only dependencies, then run the offline suite:
python -m pip install -r test/requirements.txt
python test/run.py --mode offline
Run the optional handle-lookup benchmark with:
go test ./cmd/justetf-native -run '^$' -bench BenchmarkSessionLookup -benchmem
Python tests build the Go shared library in bin, load it with ctypes, and send requests to local mock servers. By default, tests use mock servers and synthetic account credentials, never the real API. The handle benchmark measures registry lookup overhead, not network or whole-client speed.
Go unit tests stay beside their packages. Root test/ owns a shared Python mock server and synthetic catalogue for both clients. Choose a test tier with JUSTETF_TEST_MODE:
| Tier | Target and behavior |
|---|---|
offline (default) |
Local mock server and synthetic credentials; no real API calls. Exercises reads, writes, and failure cases. |
online-readonly |
Online API, performs login/refresh and reads without creating or deleting account records. |
online |
Online API, also creates scoped test records and cleans them up. Use a dedicated test account. |
Copy .env.example to .env to configure the runner. Leave credentials blank for offline, both online tiers require an account email and password:
JUSTETF_TEST_MODE=offline
JUSTETF_TEST_EMAIL=
JUSTETF_TEST_PASSWORD=
The .env file is Git-ignored and read only by the test runner. --mode overrides the value in .env. See the test guide for run commands and safety details.
Python usage
Runnable Go and Python code for profile lookup, search, financial data, and account reads is in examples/. The tables below list every method.
Search and discovery
| Go Method | Python Method / Native Operation | Python Options |
|---|---|---|
GetUniverseCountries |
get_universe_countries |
None |
GetAccountAccess |
get_account_access |
Optional country string |
GetSearchMetadata |
get_search_metadata |
SearchMetadataOptions |
SearchETFs |
search_etfs |
SearchOptions |
SearchISINs |
search_isins |
SearchISINsOptions |
GetCards |
get_cards |
CardsOptions |
CompareETFs |
compare_etfs |
ComparisonOptions |
SearchHoldings |
search_holdings |
HoldingSearchOptions |
Every Python method has a _json variant returning owned bytes. Option dataclasses are frozen, keyword-only, and optional, omitted values are resolved in Go, not by a separate Python implementation. Go methods take their typed options as values (use the zero value for defaults). Pointer-valued Go Limit fields distinguish omission from an invalid explicit zero. Responses remain raw JSON in Go and dynamically decoded values in Python, no unverified response schema is enforced.
Defaults, overridable per request:
- Country/currency come from the client, universe type is
PRIVATE, period end isYESTERDAY, period isY1. - Search: fund size descending, limit 20, page 0. Cards:
BEST, limit 30. Search-ISINs:LARGEST. Holding search: name ascending, limit 15. - Pages and offsets must be nonnegative, limits positive. No upper limit or closed enum set is imposed.
- Search filters are opaque identifiers from search metadata. Omitted lists serialize as
[], nevernull. - The API's
returnPeriod(search) versusreturnsPeriod(cards) spelling is preserved.
These methods reproduce observed unauthenticated requests.
Financial REST data
| Go Method | Python Method / Native Operation | Options |
|---|---|---|
GetQuote |
get_quote |
CurrencyOptions |
GetPerformanceChart |
get_performance_chart |
ChartOptions |
GetReturns |
get_returns |
PeriodEndOptions |
GetRisks |
get_risks |
PeriodEndOptions |
GetCountryExposure |
get_country_exposure |
ExposureOptions |
GetSectorExposure |
get_sector_exposure |
ExposureOptions |
GetHoldingExposure |
get_holding_exposure |
ExposureOptions |
GetHoldingDetails |
get_holding_details |
MarketOptions |
GetHoldingETFs |
get_holding_etfs |
HoldingETFsOptions |
GetBrokers |
get_brokers |
UniverseOptions |
GetMarketOverview |
get_market_overview |
MarketOverviewOptions |
GetIndexPerformance |
get_index_performance |
IndexPerformanceOptions |
GetRecommendedFilters |
get_recommended_filters |
RecommendedFiltersOptions |
GetSavingsPlans |
get_savings_plans |
SavingsPlansOptions |
GetOrderFees |
get_order_fees |
OrderFeesOptions |
Instrument methods take an ISIN before options. Brokers, overview, index performance, and recommended filters take options only. Every Python method has a _json variant. get_profile also accepts MarketOptions. Go retains GetProfile and adds GetProfileWithOptions, preserving the legacy C entry.
Charts:
- Dates use
YYYY-MM-DD: reversed or malformed ranges are rejected. - Omitted dates send bare
dateFrom/dateTokeys: explicit empty strings senddateFrom=/dateTo=. - Period defaults to
Y1:period=""sends a bare period for custom ranges. There is no implicit switch between period and custom-range mode. - Reduction and dividends default to true, values type to
RELATIVE_CHANGE. Explicit false values are preserved. - Unavailable charts raise
APIErrorwith status 404, never an empty success.
Other defaults (all overridable through options):
- Exposure: top 4 for countries/sectors, top 10 for holdings, scale 4, threshold
"0.01". - Holding ETFs: top 15, scale 2, page 0.
- Overview:
D1, yesterday, home-screen-only false. - Index performance: TOP/RETURN_Y1/ALL, limit 50.
- Savings plans: amount
"100", RATING. Order fees: amount"100", BEST.
Thresholds and monetary request amounts are decimal strings, never floats. Plain nonnegative decimals are accepted, monetary amounts must be positive. Exponent notation, negative amounts, NaN, and infinity are rejected. Counts must be positive, scales/pages nonnegative, explicit zero scale is preserved. Raw responses retain nulls, unknown fields, and exact numeric tokens. Holding supplementary data uses the authenticated account-read API below.
Defaults are Italian, IT, and EUR. Timeout is a positive number of seconds, at most 300, rounded up to milliseconds. It covers the entire Go HTTP operation including reading the body.
Decoded results use Python's standard JSON numeric behavior. For exact decimal processing, use raw JSON with json.loads(raw_json, parse_float=Decimal) from decimal. The Go/native/raw-bytes path preserves the original numeric tokens.
Errors are ValueError for invalid arguments/configuration, APIError with status_code for HTTP failures, TransportError for I/O/timeouts/invalid upstream JSON, and ClientClosedError for closed clients. Other native failures raise ClientError. Missing/changed sessions raise AuthenticationError, failed upstream login/refresh HTTP responses still raise APIError with their status.
Authentication
from getpass import getpass
from justetf_mobile_client import Client
# For a local build. With an installed wheel, use Client() and omit the path.
with Client("bin/justetf-native.dll") as client:
client.login(input("Email: "), getpass("Password: "))
print(client.is_authenticated())
client.refresh()
client.logout()
Go provides Login(ctx, email, password) error, Refresh(ctx) error, Logout(), IsAuthenticated() bool, and Close(). Python login/refresh/logout return None. The status method reports local token presence, not remote validity or expiry.
Login reproduces GET /v1/auth/tokens with Basic authorization. Refresh uses GET /v1/auth/refreshed-tokens with the refresh token as Bearer authorization. Both send {} and the standard mobile headers. Tokens stay in the Go client and are not persisted or returned through the ABI. Passwords are not stored on the client. Login starts a new session and clears the old one before its request, a failed login leaves the client unauthenticated. Logout/close clear stored token references and invalidate pending authentication results. Managed-runtime memory is not guaranteed to be securely overwritten.
When an authenticated read gets a 401, the client uses its refresh token to get a new access token and retries the read once. If several reads need this at once, only one refresh request is sent. Cancelling one Go request does not cancel that refresh for other reads: logout, close, or the client timeout does.
After an automatic refresh fails, the client does not keep retrying it on every request. Call refresh() to try again. If justETF rejects the refresh token with 401 or 403, log in again.
Authenticated Account Reads
from getpass import getpass
from justetf_mobile_client import Client, PortfolioListOptions, TradeQuoteOptions
# For a local build. With an installed wheel, use Client() and omit the path.
with Client("bin/justetf-native.dll") as client:
client.login(input("Email: "), getpass("Password: "))
watchlists = client.get_watchlists()
portfolios = client.get_portfolios(PortfolioListOptions(limit=10, page=0))
quote = client.get_trade_quote("IE00B5BMR087", TradeQuoteOptions(date="2026-10-14"))
All listed methods require login() and read from that account; they do not change account data. Eligible reads can refresh the session and retry once after a 401. IDs are passed as strings. Python returns decoded JSON by default, use a method's _json variant when you need the raw response bytes.
| Go Method | Python Method / Native Operation | Arguments After Context / Client |
|---|---|---|
GetPersonalData |
get_personal_data |
Optional country string (Go: empty string for default) |
GetWatchlists |
get_watchlists |
WatchlistOptions |
GetSavedFilters |
get_saved_filters |
None |
GetPriceAlerts |
get_price_alerts |
None |
GetETFPriceAlerts |
get_etf_price_alerts |
ISIN |
GetPriceAlert |
get_price_alert |
ISIN, alert ID |
GetPortfolios |
get_portfolios |
PortfolioListOptions |
GetPortfolio |
get_portfolio |
Portfolio ID, PortfolioOptions |
GetPortfolioComposition |
get_portfolio_composition |
Portfolio ID, PortfolioCompositionOptions |
CanCreatePortfolio |
can_create_portfolio |
None; reads eligibility, does not create |
GetTradeQuote |
get_trade_quote |
ISIN, TradeQuoteOptions with required date |
GetPortfolioTransactions |
get_portfolio_transactions |
Portfolio ID, PageOptions |
GetPortfolioAllocation |
get_portfolio_allocation |
Portfolio ID, AllocationOptions |
GetPortfolioPerformanceChart |
get_portfolio_performance_chart |
Portfolio ID, PortfolioChartOptions |
GetHoldingSupplementaryData |
get_holding_supplementary_data |
ISIN |
Account Updates
These operations change justETF account data, a portfolio transaction is only a tracker entry, not a broker order. Live updates require explicit opt-in and should use a dedicated test account. Routine tests use local mocks.
| Go Method | Python Method / Native Operation | Parameters | Result |
|---|---|---|---|
CreatePriceAlert |
create_price_alert |
ISIN, PriceAlertRequest |
JSON |
DeletePriceAlert |
delete_price_alert |
ISIN, alert ID | Void |
CreateSavedFilter |
create_saved_filter |
SavedFilterRequest |
Void |
DeleteSavedFilter |
delete_saved_filter |
Saved-filter ID | Void |
CreatePortfolio |
create_portfolio |
CreatePortfolioRequest |
JSON |
DeletePortfolio |
delete_portfolio |
Portfolio ID | Void |
AddShareTransaction |
add_share_transaction |
Portfolio ID, ShareTransactionRequest |
JSON |
Pass monetary values as decimal strings in Python and json.Number in Go to preserve precision, the client does not infer missing transaction values. Updates are never retried automatically. If a timeout or transport error leaves the result uncertain, check the account before trying again. Only the operations listed above are supported, other account changes are not implemented.
Concurrency and lifetime
Clients support concurrent calls and reuse HTTP connections. Use with or call close() when finished. Closing cancels active requests and clears the session, but does not wait for every call to return.
C ABI v1
The build produces a C header beside the shared library. That header defines ABI version 1, the C types, and the calling conventions. The main entry points create and close client handles, dispatch supported operations, report the ABI version, and free returned buffers. The legacy justetf_profile entry point remains available.
Clients are referenced by opaque handles. Calls pass UTF-8 input bytes and return a JustETFBuffer containing output bytes. Input memory remains caller-owned and must stay valid for the call. Free each returned buffer exactly once with justetf_buffer_free. Output is not guaranteed to be NUL-terminated, and Go pointers do not cross the ABI.
justetf_call accepts only registered operations and JSON parameters. It is not an arbitrary HTTP or URL interface. Check the returned status for success, invalid input, API errors, transport errors, closed handles, internal errors, or authentication and session errors. Use a native library matching the wrapper version and rebuild it when required exports are missing.
Go usage
The root Go package is justetf. Import it from github.com/wavepacket0/justetf-mobile-client.
client, err := justetf.NewClient(justetf.Config{
Timeout: 15 * time.Second,
})
if err != nil {
return err
}
defer client.Close()
profile, err := client.GetProfile(ctx, "IE000VAHT5T0")
Direct Go consumers can supply per-request contexts for cancellation. The native binding exposes a per-client timeout and client-wide cancellation through Close(), but not per-request cancellation handles. CloseIdleConnections() only manages the connection pool. Use Close() to cancel work, clear authentication, and end the client's lifecycle.
Protocol defaults and implemented endpoint paths live in constants.go. Add endpoint constants as features are implemented rather than copying the full research catalog into runtime code. Country, currency, and language remain per-client configuration. Tests retain independent expected protocol literals.
Disclaimer
This is an independent, unofficial project and is not affiliated with, endorsed by, sponsored by, or supported by justETF. It uses justETF's mobile API, which may change or become unavailable without notice. The justETF name, logo, and other branding remain the property of justETF or their respective rights holders.
Metadata
Release files for justetf-mobile-client 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distributions (wheels)
Total release size: 22.7 MB
Release files / justetf_mobile_client-0.1.0-py3-none-win_amd64.whl
| Download URL | justetf_mobile_client-0.1.0-py3-none-win_amd64.whl |
|---|---|
| Size | 5.7 MB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
788257ef6adf99a112848879922278ee044d1f9c6ab94f0d698483e2f1e6c0de
|
|
BLAKE2b-256 checksum How to use checksums |
2923bd65f59266ffe5e5bbad858b7dd020decee4cd985ad538e3a9efe4f1b2ba
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.
Transparency logRelease files / justetf_mobile_client-0.1.0-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
| Download URL | justetf_mobile_client-0.1.0-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl |
|---|---|
| Size | 5.2 MB |
| Tags | Linux glibc 2.17+ ARM64 Linux glibc 2.28+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
5629113f9bfbb0f68924a30884962da58c7a154a1ca00bb5838b35ba87bc27a0
|
|
BLAKE2b-256 checksum How to use checksums |
7a65a76e709ebcae699d891070f07a332f82444d319f060b84ad0a7b2c4121bb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.
Transparency logRelease files / justetf_mobile_client-0.1.0-py3-none-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl
| Download URL | justetf_mobile_client-0.1.0-py3-none-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl |
|---|---|
| Size | 5.8 MB |
| Tags | Linux glibc 2.28+ x86-64 Linux glibc 2.5+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
21908a29d9ad7c784b175df7344bff9c551e0aeeeecde84b0261dfd95d3db795
|
|
BLAKE2b-256 checksum How to use checksums |
18308499993d9b34d3fbd2d485b38431958ac704e8a6809a432f789285e209b2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.
Transparency logRelease files / justetf_mobile_client-0.1.0-py3-none-macosx_11_0_arm64.whl
| Download URL | justetf_mobile_client-0.1.0-py3-none-macosx_11_0_arm64.whl |
|---|---|
| Size | 2.9 MB |
| Tags | Python 3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
9f95cc5619f6d1a994fde2a1c90146871fe764269d4b3d29bce51b0501e59911
|
|
BLAKE2b-256 checksum How to use checksums |
06e5ed5b0a43c31974eea85b768ae2d4bb989feed43b99b6ed9e923f14f80181
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.
Transparency logRelease files / justetf_mobile_client-0.1.0-py3-none-macosx_10_9_x86_64.whl
| Download URL | justetf_mobile_client-0.1.0-py3-none-macosx_10_9_x86_64.whl |
|---|---|
| Size | 3.2 MB |
| Tags | Python 3 macOS 10.9+ x86-64 |
|
SHA-256 checksum How to use checksums |
29c8268946c3eb356fedfb452ca45a24fa79143d85d85709809389b6a096d19d
|
|
BLAKE2b-256 checksum How to use checksums |
09c139a41c8e0db19c18267d48899b1ae24409b988f84ac816872d2849f7494a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.
Transparency logSecurity for justetf-mobile-client 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 5 files in this release have been uploaded with provenance metadata.
Attestation for justetf_mobile_client-0.1.0-py3-none-win_amd64.whl
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.