Skip to content

SDK drift: Python Exchange.__init__ has api_token parameter not present in TypeScript ExchangeOptions #469

Closed as duplicate

Description

@realfishsam

Drift

The Python Exchange base class constructor accepts an api_token parameter that sends apiToken to the sidecar. TypeScript's ExchangeOptions interface has no corresponding field, so TypeScript callers have no way to pass this credential through the base class.

TypeScript SDK

sdks/typescript/pmxt/client.ts, lines 167–213 — ExchangeOptions interface (no apiToken field):

// sdks/typescript/pmxt/client.ts lines 167–213
export interface ExchangeOptions {
    exchange?: string;
    pmxtApiKey?: string;
    baseUrl?: string;
    apiKey?: string;
    apiSecret?: string;
    // ... other fields
    // apiToken is absent
}

The TypeScript Exchange constructor passes ExchangeOptions to the sidecar via _getCredentialsDict() — apiToken is never included.

Python SDK

sdks/python/pmxt/client.py, line 263 — api_token in base class __init__:

# sdks/python/pmxt/client.py line 263
def __init__(
    self,
    exchange: str,
    ...
    api_token: Optional[str] = None,
    ...
):
    self._api_token = api_token

_get_credentials_dict (client.py) then includes "apiToken": self._api_token when set, sending it to the sidecar with every request.

Metaculus (in _exchanges.py) is the concrete exchange that relies on this path.

Expected

ExchangeOptions in TypeScript should include an apiToken?: string field, and the TypeScript Exchange._getCredentialsDict() should include it when present — mirroring Python's behaviour. Alternatively, if api_token is purely a Metaculus-specific credential, it should be removed from the Python base class and handled only in the Metaculus subclass in both languages (with a dedicated constructor parameter).

Impact

TypeScript users of Metaculus cannot pass a bearer token through the base-class credential path. Python users can, creating an undocumented asymmetry. Any sidecar exchange handler that checks for apiToken in the credentials object will never receive it from TypeScript clients.

Found by automated SDK cross-language drift audit

Activity

  1. realfishsam commented on Jun 28, 2026

    @realfishsam
    ContributorAuthor

    Additional context from duplicate #1370 (filed 2026-06-28):

    Detailed code path analysis showing exactly why TypeScript callers cannot pass an API token programmatically:

    Root cause in TypeScript SDK:

    sdks/typescript/pmxt/client.ts:231–290 — ExchangeOptions has no apiToken field.

    sdks/typescript/pmxt/client.ts:464–474 — Exchange.getCredentials() never includes apiToken:

    protected getCredentials(): ExchangeCredentials | undefined {
        if (!this.apiKey && !this.privateKey) {
            return undefined;
        }
        return {
            apiKey: this.apiKey,
            privateKey: this.privateKey,
            funderAddress: this.proxyAddress,
            signatureType: this.signatureType,
            // apiToken absent
        };
    }

    sdks/typescript/pmxt/client.ts:3360–3364 — Metaculus class uses generic ExchangeOptions.

    Core expects apiToken:

    core/src/server/exchange-factory.ts:110–114:

    case 'metaculus':
        return new MetaculusExchange({
            apiToken: credentials?.apiToken || process.env.METACULUS_API_TOKEN,
        });

    core/src/BaseExchange.ts:386 declares apiToken in ExchangeCredentials.

    Because getCredentials() never returns apiToken, the sidecar always falls back to process.env.METACULUS_API_TOKEN only — there is no programmatic path to pass a token from TypeScript.


    Generated by Claude Code

  2. realfishsam commented on Jun 30, 2026

    @realfishsam
    ContributorAuthor

    Additional context from duplicates #1384 and #1420 (later re-filings of this same finding):

    • The root cause is slightly deeper than just a missing field: Exchange.getCredentials() in sdks/typescript/pmxt/client.ts (~lines 464–474) never forwards apiToken even if the field existed, and Metaculus has no getCredentials() override to compensate.
    • core/src/server/exchange-factory.ts:110–114 confirms apiToken is the only credential field MetaculusExchange reads — without it the sidecar silently falls back to unauthenticated/env-var-only mode.
    • Current line references (as of late June): TS ExchangeOptions interface at client.ts:231–290, Python Exchange.__init__ at client.py:329 forwarding via _get_credentials_dict() at client.py:555–564, and Metaculus.__init__ forwarding at sdks/python/pmxt/_exchanges.py:341.
    • Impact also includes: the failure is silent (no compile/runtime error), so it specifically breaks programmatic/multi-tenant credential rotation and hosted-mode usage for Metaculus via the TypeScript SDK.

    Generated by Claude Code

  3. realfishsam commented on Jul 3, 2026

    @realfishsam
    ContributorAuthor

    Closing as duplicate of #465. Both describe the same underlying credential-plumbing gap for the identical Python api_token parameter that only Metaculus currently uses. Relevant context has been copied over.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions