---
title: "Authenticated user's watchlist with (delayed) price enrichment"
method: GET
path: "/api/watchlist"
tags: ["Portfolio"]
---

# Authenticated user's watchlist with (delayed) price enrichment

`GET /api/watchlist`

## Request

- Base URL: `https://www.oxfordledge.com` (the document declares no server; this is the origin it was published from)
- URL: `https://www.oxfordledge.com/api/watchlist`
- Auth: none declared

User watchlist with (delayed) price enrichment.

Auth: requires a logged-in session. Watchlists are user-scoped data
(PG `user_watchlists.user_id` column), so an anonymous request had
no canonical row to return — the prior behavior fell through to
`load_watchlist()` which reads a process-wide SQLite cache shared
across all users (privacy issue + last-write-wins corruption).

## Response `200`

Successful Response

- WatchlistResponse: `GET /api/watchlist` response. F4 schema-tightening (2026-04-30): `items` REQUIRED. Handler returns `{"items": enriched}` on every 200 path; the 500 fallback returns `{"items": [], "error": "..."}` with status_code=500 (uses ErrorResponse via the responses dict, so out of scope). Frontend watchlist sidebar iterates `data.items.forEach(...)` without a null-check; a missing field NPEs the entire watchlist panel.
  - `items` WatchlistItem[], required
    - `ticker` string, nullable
    - `added_at` string, nullable
    - `note` string, nullable
    - `price` number, nullable
    - `changePct` number, nullable
    - `quoteKind` string, nullable
    - `previousCloseDate` string, nullable
    - `priceAsOf` string, nullable
  - `count` integer, nullable
  - `user` string, nullable

## Other responses

- `401`: Unauthorized
- `500`: Internal Server Error

---

[API](https://skmtc.dev/oxfordledge/apis/oxford-ledge-api.md) · [All operations](https://skmtc.dev/oxfordledge/apis/oxford-ledge-api/llms.txt) · [OpenAPI document](https://skmtc.dev/oxfordledge/apis/oxford-ledge-api/revisions/e832a1260f0b?raw)
