Gap
Metaculus requires a token on every call — per core's own comments, "Metaculus no longer allows unauthenticated access to any endpoint." The sidecar factory only ever reads credentials.apiToken for this venue, never apiKey. The TypeScript SDK's ExchangeOptions interface has no apiToken field anywhere, and Exchange.getCredentials() never populates one — so the TypeScript Metaculus class has no code path to authenticate at all. The Python SDK does this correctly via a dedicated api_token parameter.
Core
core/src/server/exchange-factory.ts:110-114:
case "metaculus":
return new MetaculusExchange({
apiToken:
credentials?.apiToken || process.env.METACULUS_API_TOKEN,
});
core/src/exchanges/metaculus/index.ts:60-65 — this.apiToken = credentials?.apiToken; (apiKey is never read for this venue).
TypeScript SDK
Missing — sdks/typescript/pmxt/client.ts:233-302 (ExchangeOptions interface has no apiToken member) and client.ts:482-493:
protected getCredentials(): ExchangeCredentials | undefined {
if (!this.apiKey && !this.apiSecret && !this.privateKey) return undefined;
return { apiKey: this.apiKey, apiSecret: this.apiSecret, privateKey: this.privateKey,
funderAddress: this.proxyAddress, signatureType: this.signatureType };
}
apiToken never appears anywhere in sdks/typescript/ (confirmed via full-package grep — zero matches). The Metaculus class (client.ts:3564-3568) has no constructor override to add the field, unlike SuiBets (client.ts:3684-3691), which does override getCredentials() to add its own venue-specific field. The class's own JSDoc even claims "authenticated calls accept a bearer token via apiKey" — incorrect, since the server discards apiKey for this venue.
Python SDK
Works correctly — sdks/python/pmxt/client.py:329 (api_token constructor param) and client.py:569-570:
if self.api_token:
creds["apiToken"] = self.api_token
Evidence
grep -rn "apiToken" sdks/typescript/ returns zero matches anywhere in the TypeScript package. exchange-factory.ts:110-114 reads only credentials?.apiToken for the metaculus case; the TS ExchangeOptions/getCredentials() machinery has no field with that name to populate it.
Impact
Every authenticated Metaculus operation — and per core's own docs, effectively every operation, since Metaculus rejects unauthenticated requests entirely — is unreachable through the TypeScript SDK. new Metaculus({ apiKey: '...' }) silently sends a field the server discards for this venue; the request goes through as anonymous and is rejected by Metaculus. This is distinct from the already-filed passphrase-field gap (#2039, a different field/venue) and from #2136 (a docs-only issue about the self-hosted config table).
Found by automated Core-to-SDK surface coverage audit
Gap
Metaculus requires a token on every call — per core's own comments, "Metaculus no longer allows unauthenticated access to any endpoint." The sidecar factory only ever reads
credentials.apiTokenfor this venue, neverapiKey. The TypeScript SDK'sExchangeOptionsinterface has noapiTokenfield anywhere, andExchange.getCredentials()never populates one — so the TypeScriptMetaculusclass has no code path to authenticate at all. The Python SDK does this correctly via a dedicatedapi_tokenparameter.Core
core/src/server/exchange-factory.ts:110-114:core/src/exchanges/metaculus/index.ts:60-65—this.apiToken = credentials?.apiToken;(apiKeyis never read for this venue).TypeScript SDK
Missing —
sdks/typescript/pmxt/client.ts:233-302(ExchangeOptionsinterface has noapiTokenmember) andclient.ts:482-493:apiTokennever appears anywhere insdks/typescript/(confirmed via full-package grep — zero matches). TheMetaculusclass (client.ts:3564-3568) has no constructor override to add the field, unlikeSuiBets(client.ts:3684-3691), which does overridegetCredentials()to add its own venue-specific field. The class's own JSDoc even claims "authenticated calls accept a bearer token viaapiKey" — incorrect, since the server discardsapiKeyfor this venue.Python SDK
Works correctly —
sdks/python/pmxt/client.py:329(api_tokenconstructor param) andclient.py:569-570:Evidence
grep -rn "apiToken" sdks/typescript/returns zero matches anywhere in the TypeScript package.exchange-factory.ts:110-114reads onlycredentials?.apiTokenfor themetaculuscase; the TSExchangeOptions/getCredentials()machinery has no field with that name to populate it.Impact
Every authenticated Metaculus operation — and per core's own docs, effectively every operation, since Metaculus rejects unauthenticated requests entirely — is unreachable through the TypeScript SDK.
new Metaculus({ apiKey: '...' })silently sends a field the server discards for this venue; the request goes through as anonymous and is rejected by Metaculus. This is distinct from the already-filedpassphrase-field gap (#2039, a different field/venue) and from #2136 (a docs-only issue about the self-hosted config table).Found by automated Core-to-SDK surface coverage audit