Skip to content

feat: get_seller_listings — list another seller's other listings - #13

Open
asklepios-png wants to merge 7 commits into
ahnl:mainfrom
asklepios-png:feature/seller-listings
Open

feat: get_seller_listings — list another seller's other listings#13
asklepios-png wants to merge 7 commits into
ahnl:mainfrom
asklepios-png:feature/seller-listings

Conversation

@asklepios-png

Copy link
Copy Markdown
Contributor

Summary

Adds get_seller_listings(ad_id) — list the other active listings by the same seller as a given ad. This completes the "who else is this seller selling?" flow that began with owner_id extraction from the adview meta block.

How the endpoint was found

The seller-listings gateway endpoint is not visible from the website — the public profile page is fully server-rendered (the browser never sees the upstream call, and there's no client-side JSON endpoint). It was captured from the Android app's own traffic (emulator + HTTP Toolkit TLS interception):

GET /org/SEARCH_ID_BAP_COMMON?client=…&orgId={ownerId}&include_anonymous=false&page={n}
    host:            apps-gw-poc.svc.tori.fi
    finn-gw-service: SEARCH-QUEST-RC          (no X-Client-Id header needed)
    response:        { "docs": [ … ], "metadata": { "paging": { "param": "page", "current", "last" } } }

Key points that made this small:

  • orgId is exactly the ownerId already carried in an adview's meta block, so listings.owner(ad_id) feeds straight in — no new identifier needed.
  • It uses the same SEARCH-QUEST-RC service and BAP search key as the public search, so the existing finn-gw-key signing works unchanged — no changes to _request.
  • The response shape matches the public search docs, so mapping is trivial.

Changes

  • listings.seller_ads(owner_id, max_results=None) — paginates via the page param until metadata.paging.last
  • MCP tool get_seller_listings(ad_id) — resolves owner → seller_ads → compact list
  • tests/test_seller_ads.py — 4 tests (mock client, no network): single page, pagination, max_results cap, empty seller
  • Docs: README tool table + library usage example

Verification

  • Full test suite green (13/13)
  • Live-verified against a real seller: owner(ad_id)owner_id → 28 listings returned, titles and prices matching the app

🤖 Generated with Claude Code

asklepios-png and others added 6 commits July 13, 2026 12:23
The adinput API is two-phase: PUT .../update only stores a draft
revision; the live ad changes only after the commit step (productcontext
+ POST /order/choices — the same sequence create() uses). edit_listing
stopped after the PUT and inferred success from the returned ETag, so
edits were never published while the tool reported success.

- ListingsAPI.edit(): update → publish (Basic) → read-back verification
  against adview (staggered ~5.5 min window; propagation takes minutes).
  Raises instead of ever reporting an unverified success.
- update() now raises on meta-data validation violations (the API
  returns them with HTTP 200).
- MCP edit_listing and CLI listings edit routed through edit();
  set_price() too. CLI checkmark output made cp1252-safe.
- Regression tests (mocked client): publish ordering, read-back
  mismatch, violation handling. Red-green verified.
- CONTEXT.md added: root cause, live findings (order/choices is the
  commit; publish_free_ad POSTs 404; edited timestamp = revision time).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The adview response carries the seller only in `meta` (ownerId / ownerUrn);
the `ad` body has no seller field, and the MCP get_listing wrapper dropped
meta entirely. That made it impossible to tell whether two listings belong
to the same person.

- owner_from_adview(): pure extraction, shared by library and MCP wrapper
- ListingsAPI.owner(ad_id) -> {"owner_id", "owner_urn"}
- MCP get_listing now returns owner_id

Groundwork for a "seller's other listings" lookup. CONTEXT.md records the
recon behind the next phases: the tori.fi profile page renders its ad list
server-side (podlet trust-public-profile-layout) with no JSON endpoint, so
the remaining routes are a SPiD web session inside torium, a browser
adapter, or a mobile capture to find the native gateway path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… adapter recommended

Findings from the 2026-08-10 investigation into "seller's other listings":
- Headless SPiD web session: dead end. type=session exchange yields identity
  cookies but not an interactive SSO session; OAuth authorize bounces to OTP
  email-login, and login initiation is client-side JS.
- Native gateway: TRUST-PROFILE-API exists and its X-Client-Id filter is
  satisfiable, but the ads sub-resource path is not discoverable by guessing
  (service double-prefixes /public; all guesses 404). Needs mobile capture.
- Dead: AD-SUMMARIES ?ownerId is ignored (always own listings); SEARCH-QUEST
  seller/owner/user_id params -> 400.
- Profile ad list is pure SSR (podlet trust-public-profile-layout), no
  client-side JSON endpoint. Browser adapter is the shippable route.
- Pagination: seller 451218839 (28 active) renders all cards at once, no
  load-more / infinite scroll / page param. 50+ still unverified.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…lla endpointilla

Kaapattiin Android-appin liikenteestä oikea gateway-endpoint (emulaattori API 33
+ HTTP Toolkit): GET /org/SEARCH_ID_BAP_COMMON?client=…&orgId={owner_id}&include_anonymous=false,
finn-gw-service SEARCH-QUEST-RC, ei X-Client-Id:tä. orgId == adview'n meta.ownerId,
joten olemassa oleva owner() syöttää suoraan. Vastaus = päähaun docs-muoto; sivutus
page-parametrilla metadata.paging.last:iin asti.

Recon-oletus (TRUST-PROFILE-API + X-Client-Id) oli väärä — oikea on hakuperheen
/org/{searchKey}?orgId=, sama palvelu jonka torium jo osaa allekirjoittaa, joten
_request-koneistoa ei tarvinnut laajentaa.

- listings.seller_ads(owner_id, max_results=None): sivuttava haku
- MCP-työkalu get_seller_listings(ad_id): owner -> seller_ads -> kompakti lista
- tests/test_seller_ads.py (4 kpl, mock-client)

Live-vahvistettu 2026-08-11: myyjä 451218839 -> 28 ilmoitusta, otsikot/hinnat täsmäävät.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@asklepios-png

Copy link
Copy Markdown
Contributor Author

Personal note: this one scratches an itch I've been chasing for weeks — reliably pulling up another seller's other listings from just one of their ads. I'd tried a few dead ends (a profile-API path that always 404'd, the SSR web profile with no JSON endpoint, a headless SPiD session that needed OTP) before capturing the app's actual traffic finally revealed it was hiding in the search family all along (/org/{searchKey}?orgId=). Really happy it works end to end now. Glad to adjust anything for your review.

Pinnaamaton mcp veti tuoreessa asennuksessa mcp 2.0.0:n, josta mcp.server.fastmcp
on poistettu, joten mcp_server.py:n import kaatuu eikä torium-mcp käynnisty. uv.lock
on .gitignoressa, joten tuore asennus ei saa toimivaa lukitustakaan. Pinni pitää
serverin käynnistyskelpoisena myös puhtaassa asennuksessa (esim. PR:n arviointi).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

1 participant