# `api.php` Reference

Single endpoint, action-routed via `?action=`. Runs identically from the CLI (`php api.php <action> <query>`) or as a web request (`GET`/`POST` with `action` in query string or POST body).

All responses are JSON with this envelope:

```json
{
  "timestamp": "2026-08-14 15:42:10 ET",
  "market_status": { "open": true, "reason": "Regular Trading Hours" },
  "status": "success | error",
  "debug_trace": ["..."],
  "data": { ... } ,
  "message": "present only when status is error"
}
```

- `timestamp` — server request time in `America/New_York`, for debugging/logging only. **Not** the quote time — see `data.{symbol}.quote_date` for that.
- `market_status.open` — boolean, whether US markets are currently in regular trading hours.
- `market_status.reason` — one of `Regular Trading Hours`, `Pre-Market`, `After-Hours`, `Weekend`, `Market Holiday`.
- `debug_trace` — array of human-readable strings logging what the router did; surfaced in the frontend's console log panel.

## `GET ?action=load_json`

Reads `stocks.json` and fetches current data for every symbol in it.

No parameters. Returns `data` as described below, keyed by symbol.

## `GET ?action=search&query=SYMBOL1,SYMBOL2,...`

Fetches current data for arbitrary symbols. Does not read or write `stocks.json`.

| Param | Required | Notes |
|---|---|---|
| `query` | yes | Comma-separated ticker list. `^` prefix supported for indices (`^DJI`, `^GSPC`, `^IXIC`). |

## `POST ?action=modify_watchlist`

Overwrites `stocks.json` with a new ticker list. Does not fetch quote data.

Request body (JSON):
```json
{ "tickers": ["AAPL", "TSLA", "^DJI"] }
```

Response `data`:
```json
{
  "message": "Watchlist saved successfully.",
  "current_list": ["AAPL", "TSLA", "^DJI"]
}
```

## `data` shape for `load_json` / `search`

Keyed by ticker symbol:

```json
{
  "AAPL": {
    "status": "success",
    "price": 231.45,
    "price_display": "$231.45",
    "previous_close": 228.90,
    "change": 2.55,
    "change_pct": 1.11,
    "fifty_two_week_high": 260.10,
    "fifty_two_week_low": 164.08,
    "quote_date": "2026-08-14",
    "history_dates": ["2025-08-15", "2025-08-18", "...", "2026-08-14"],
    "history_prices": [212.30, 213.10, "...", 231.45]
  },
  "BADTICKER": {
    "status": "error",
    "message": "BADTICKER: no historical data returned by source."
  }
}
```

- `history_dates` / `history_prices` — one year of daily closes, used by the frontend to render the 7D/30D/6mo/1Y chart views without additional requests.
- `quote_date` — date of the last data row returned by the source. Combine with the top-level `market_status.open` to label it as intraday vs. closing (this labeling is done in the frontend, not the API).
- On error, only `status` and `message` are present for that symbol — no price fields.

## Sanitization

Every ticker (from `query` or `tickers`) is passed through:
```php
preg_replace('/[^A-Za-z0-9.\^-]/', '', $ticker)
```
before being used anywhere, including before being shell-escaped and passed to `scraper.py`. Anything outside letters, digits, `.`, `-`, and `^` is silently stripped.
