Sander Korf
  • Portfolio
  • Resume

Sander Korf

Dutch Full Stack AI Engineer helping businesses cut costs through intelligent automation. 13+ years turning expensive manual processes into systems that work 24/7. Based in Amsterdam, available for freelance projects.

Navigation

  • Portfolio
  • Resume

Legal

  • Cookies
  • Privacy Policy
  • Terms and Conditions

Blog

Algolia

  • Algolia InstantSearch INP and object identity
  • Algolia merch by Sanity _type, not title
  • Algolia InstantSearch search key, not admin

Vercel

  • Next.js empty Suspense fallbacks wreck CLS P95
  • Vercel Workflows: skip closed eToro legs

eToro

  • eToro deploy vs rotate: idle cash not rotate
  • eToro search 404: query fields, not ticker
  • eToro v2 opens at-most-once, settlement
  • eToro LLM overlay ±10pp: critic cannot veto

Next.js

  • next-intl useTranslate skip links above fold
  • next-intl owns Klaviyo nl-NL email copy

Firebase & Expo

  • Staging deep links must not hijack production
  • Turn off Worklets Bundle Mode for EAS SHA-1
  • Reclaim Firebase orphan without password wipe

Centra & Klaviyo

  • Centra to Klaviyo tags need a plugin contract

About

  • Full Stack AI Engineer in Amsterdam

© 2026 Sander Korf. All rights reserved

94719489

  • Applied AI
  1. Home
  2. Blog
  3. eToro search 404: query fields, not ticker

eToro search 404: query fields, not ticker

A search API 404 often means bad query parameters, not a missing ticker. I keep a cached ticker map and treat an empty 200 as the real miss.

Sander KorfPublished August 12, 20265 min read
etoroapiredis

This is an eToro rebalance that resolves tickers

eToro GET /market-data/search. Redis holds the ticker-to-instrumentId map. If search 404s on a bad fields list, every name looks missing. If we drop the Redis map on a miss, the book goes empty on a Tuesday.

What they would notice if it failed

HTTP 200 at the job. Zero orders. Logs full of 404. Or a stale-but-correct Redis id thrown away because search coughed. They notice a book that did not trade.

The deploy that placed nothing

I shipped a rebalance against eToro. The job came back HTTP 200. Zero orders went out. That is a special kind of silence: not a crash, not a timeout, just a polite success that did nothing.

The logs were worse. Every ticker hit GET /market-data/search and came back HTTP 404. resolvedCount=0. I briefly assumed the catalog had vanished overnight. It had not. The instruments were still there. My query string was not.

A 404 is not a miss

On eToro search, a real miss is 200 plus an empty items array. A 404 means you asked search a question it refuses to parse. Same class of error as a bad sort. The whole request dies. One unknown field name, and every ticker looks missing.

GET /market-data/search requires fields. I sent a projection with mixed casings: instrumentID in one place, instrumentId in another, plus internalSymbolFull when I remembered the docs. eToro does not skip the unknown one. It 404s the call. I treated that as "ticker not found" and kept walking the list. Every row failed the same way, so the book stayed empty and the 200 at the top of the job looked fine.

Then I stacked a second bug. URLSearchParams encodes commas as %2C. eToro wants a literal comma in fields=instrumentId,internalSymbolFull. Encoded commas look like another malformed projection. Same 404. Same empty book.

The first 404 also threw. Later search strategies never ran. I had written a fallback for unknown tickers. It never got a chance to try, because I aborted on the first "not found" that was actually a bad request.

Encode like the docs, fail like a miss

Documented field names only. No creative casing. If the docs say instrumentId and internalSymbolFull, those are the only spellings that exist. Alternate casings are not aliases. They are unknown fields.

Rewrite %2C back to , after URLSearchParams. The browser helper is fine until eToro is picky about list separators:

function encodeSearchQuery(params: Record<string, string>): string {
	const query = new URLSearchParams();
	for (const [k, v] of Object.entries(params)) query.set(k, v);
	return query.toString().replaceAll('%2C', ',');
}

On 404, retry once with a minimal field set. If that still 404s, return an empty match and try the next strategy. Do not abort the job because one projection was ugly.

eToro search query encoder

URLSearchParams turns list commas into %2C. eToro GET /market-data/search wants a literal comma in fields. A 404 is a bad projection, not a missing ticker.

URLSearchParams

fields=instrumentId%2CinternalSymbolFull

After rewrite

fields=instrumentId,internalSymbolFull

HTTP 404Whole request rejected. Same class as a bad sort.

An eToro search 404 is often your fields list, not their catalog. Treat it like a bad request you can recover from, not a missing ticker you should panic over.

Redis stale cache of eToro instrument IDs is a feature

The same morning those 404s landed, I had already thrown away the fallback I needed.

I kept a Redis map of ticker to eToro instrument ID. After about 73 days I marked it stale and discarded it. Classic cache advice says: do not serve stale. That advice is wrong when the value is a stable external identifier. Those IDs do not rotate. eToro assigned 22 to AAPL years ago and it is still 22. Age is not a validity signal.

When I discarded the Redis map, I forced every resolve through live eToro search. Search was 404ing. Every ticker missed. Coverage went to zero. The old map was still valid. I discarded truth because a clock said it was old.

A session token goes stale. A price quote goes stale. An eToro instrument ID does not.

New policy: Redis reads return the stale map. Live eToro search only fills missing tickers and refreshes updatedAt. An eToro search outage cannot zero the book. Tests flipped from stale → null to stale → { AAPL: 22 }.

if (ageMs > INSTRUMENT_LOOKUP_CACHE_MAX_AGE_MS) {
	log.info('instrument_lookup_cache_stale', { ageMs, symbolCount, updatedAt });
	return payload.symbolToInstrument; // still trade
}

I still log the age. I still try to refresh. I do not evict a map of identifiers that do not change. Stale here means "we have not confirmed lately," not "this is wrong."

Ask one question: if this value is old, can it be false? If yes, expire it. If no, keep it and refresh in the background. For a lookup table of stable eToro IDs, the Redis TTL is a reminder to refresh, not a license to delete.

Stale lookup map, 73 days old. Search outage plus discard-stale zeros the book. Serve-stale keeps the IDs that never rotate.
Search API
Cache policy

Coverage 0 / 6

Stale map discarded. Live search 404s. Book is empty.

Resolved eToro instrument IDs for the current policy
TickerInstrument IDSource
AAPL—unresolved
MSFT—unresolved
GOOG—unresolved
AMZN—unresolved
TSLA—unresolved
NVDA—unresolved

Toggle an eToro search outage against two Redis policies. Discard-stale plus outage: coverage 0. Serve-stale plus outage: coverage N. Same Redis cache. Same eToro outage. Fix the fields projection. Keep the map.


Happy coding!
Sander