# Chrome extension

> Investi for Chrome shows your Investi position, history, orders and verdict when you hover a ticker on Schwab and ten other finance sites. Read-only.

Source: https://investi.live/docs/integrations/chrome-extension

Investi for Chrome is a Chrome and Edge extension (Manifest V3). When you hover a stock ticker on a supported finance site, it shows an Investi card next to it with your numbers for that company. Every number comes from your Investi; nothing is read from the page except the ticker you hover. It is read-only: it never places, changes or cancels orders, never reads credentials or cookies, and never runs on sign-in, order-entry or password pages.

Inside Chrome the extension is listed simply as "Investi". The download keeps its original file name, `Investi-for-Schwab-<version>.zip`, from when it covered only Schwab.

## Supported sites

Each site has its own detection rules, tested against pages captured from the real site. An element counts as a ticker only when its visible text is exactly the symbol that the page names structurally, such as a link to that symbol's quote page. From 0.1.9 a link to the quote page of any supported site counts, so a Yahoo Finance quote link on www.yahoo.com or on another supported site is recognized too. Indexes, currencies, crypto pairs and non-US listings are skipped. If a site's structure changes, the extension detects nothing there rather than guessing.

| Site | Where it runs | Checked on the live site (2026-11-02, signed out) |
| --- | --- | --- |
| Yahoo Finance | finance.yahoo.com, the localized Yahoo Finance sites (ca, uk, au, sg, nz, hk, de, fr, es) and www.yahoo.com (from 0.1.9) | Yes: tickers detected and the card shown, in light and dark, including article pages and uk.finance.yahoo.com |
| Google Finance | www.google.com/finance | Yes: tickers detected and the card shown |
| TradingView | tradingview.com (the `/chart` page with its order panel is excluded) | Yes: tickers detected and the card shown |
| Robinhood | robinhood.com (order and transfer pages excluded) | Yes: tickers detected, the card shown, and the Buy panel avoided |
| Seeking Alpha | seekingalpha.com | Partly: tickers detected live, but the signed-out overlay blocked hovering |
| MarketWatch | marketwatch.com | No: the site blocks automated browsers. Tested on saved pages |
| Finviz | finviz.com, elite.finviz.com | No: the site blocks automated browsers. Tested on the served HTML |
| Morningstar | morningstar.com | No: the site blocks automated browsers. Tested on the served HTML |
| Schwab | *.schwab.com | Public pages show Schwab's firewall to automated browsers. Signed-in pages (positions, watchlists, quotes, order status) are tested only against a reconstructed positions page, not the live site |
| Fidelity | www, digital, oltx, research2 and screener .fidelity.com (trade and order paths excluded) | No: signed-in pages are not live-tested. The public quote page has no ticker markup to test |
| E*TRADE | us.etrade.com, www.etrade.com (trade, order and login paths excluded) | No: quotes are signed-in only, so it is tested against a synthetic page |

**Apple Stocks** is a native Mac app, not a web page, so no browser extension can reach it.

## What the card shows

The top of the card, from the first request:

- The company logo (from 0.1.12), fetched from your Investi by the extension itself, so the site you are on never sees the request; a monogram when there is none
- Delayed price and the market session
- Your position and unrealized P/L
- Lifetime round trips and realized P/L
- Your last buy and last sell
- Open orders
- The 52-week low
- The Investi verdict, as a badge with the verdict word and a distinct shape (GREEN, YELLOW, RED, FLAGGED, DORMANT, or NO DATA for INSUFFICIENT), so color is never the only signal

Scroll the card for sections that load with a second request: the earnings trend (EPS actual against estimate per quarter, and the next report: its date, marked "estimated" when it is the vendor's projection rather than a company-confirmed date, or "Reported" with the date when the company reported in the last 10 days), analyst insights (the low, average and high price target against the current price, and six months of buy, hold and sell counts), catalysts, the intraday chart, open lots, round trips and recent fills. Until they arrive they show placeholders; if the second request fails, the top of the card stays.

**Pin** keeps the card open when the pointer leaves or the page scrolls. **Full research** opens `/research/{TICKER}` on your Investi in a new tab. The card follows your system's light or dark setting.

**Drag** the card by its header (from 0.1.10) to put it anywhere on the screen. From then on the card opens at that spot on every supported site instead of beside the ticker, until you double-click the header to put it back. The spot is kept by the extension, not by any site. A card at a remembered spot waits longer before closing (1.2 seconds instead of 280 milliseconds) and keeps waiting while the pointer is heading toward it. If the remembered spot would cover a trade ticket or a Buy, Sell or Review button on the page you are on, the card opens beside the ticker there instead, and the spot stays remembered for other pages.

All of this comes from `GET /api/v1/securities/{ticker}/intel` (the `intel.v1` payload), which reads the same server-side calculations as the web app. See [Canonical numbers](/docs/concepts/canonical-numbers). If Investi cannot match the ticker exactly, the card says so instead of guessing.

### Catalysts

From version 0.1.8, the card has a **Catalysts** section after Analysts. The hosted Investi sends catalysts; a self-hosted Investi server that predates them shows "Your Investi server does not send catalysts yet. Update Investi." It is a compact view of the same feed as the research page's [What could move it](/docs/app/research#what-could-move-it) section, which lists the sources (nasdaq.com, Google News and SEC EDGAR) and the full lean rules:

- Up to three chips for upcoming events, earliest first, such as "Earnings in 3 days", "Ex-dividend tomorrow" or a macro release with its date.
- Up to eight recent events, shown newest first. The two newest headlines always keep a slot when there are any; filings, insider trades, analyst actions, short interest and reported earnings fill the rest, so a burst of headlines never pushes them off, and more headlines fill any slots left. Each row has the lean mark (▲ usually up, ▼ usually down, ◆ moves either way; derived from the kind of event, never a prediction), the kind, the headline (a link only when the source gives an http or https URL), the source and how long ago, and the close-to-close reaction of that session.
- A footer with the sources that answered and the feed's time.

Its states:

| State | What the section shows |
| --- | --- |
| Loading (second request in flight) | Three placeholder rows. |
| Headlines still coming | "Headlines still loading" above whatever has arrived. The card asks again a couple of times while the server reports pending sources. |
| Nothing found | "No events in the last 60 days." |
| Investi server too old to send catalysts | "Your Investi server does not send catalysts yet. Update Investi." |

The feed is read tolerantly: one malformed item is dropped and the rest still show. Headlines are third-party, untrusted text, written into the card as plain text only and never used as instructions by AI features.

## Install

The extension is not in the Chrome Web Store. It is published as a zip on each `extension-v<version>` release, and the guided page at [/install/extension](/install/extension) walks you through it and connects at the end. [/downloads](/downloads) has the same download with its SHA-256 checksum.

1. Download the zip and unzip it. You get one folder, `Investi-for-Schwab-<version>`. Keep it somewhere permanent, because Chrome runs the extension from that folder.
2. Open `chrome://extensions` (Edge: `edge://extensions`) and turn on **Developer mode**.
3. Click **Load unpacked** and choose the folder.

Chrome may show a notice about developer mode extensions; that is expected for anything installed outside the store. Chrome does not allow `.crx` installs from outside the store on Windows or macOS, which is why the release is a folder you load.

**Updating:** download the new zip, replace the contents of the same folder, and click the reload button on the extension's card in `chrome://extensions`. The saved connection survives.

### Extension ID

The manifest carries a fixed public key, so every install has the same ID, `cdddflccmdpieoaodmfaffkmjajgknkk`, whatever folder you load it from. The Investi web app has this ID built in, so you never paste it.

## Connect

The extension needs your Investi address and a personal access token.

### One click from Investi

Open [/install/extension](/install/extension) or Settings, Connections in Investi. The page looks for the extension and shows one of these states:

| State | Meaning |
| --- | --- |
| Not installed | Chrome is not exposing the extension to this page. There is a link to install it and a **Check again** button. |
| Different address | The extension is set to another Investi address. Change it in the extension's settings page. |
| Installed | The extension is ready but has no token. |
| Connected | The extension has a working token. |

Click **Connect extension** (or **Reconnect**). Investi creates a personal access token named "Browser extension" with `market:read portfolio:read research:read`, hands it to the extension, and deletes it again if the extension refuses or does not answer.

One-click connect works from the hosted Investi (`https://investistocks.vercel.app` and its older hostname `https://investi-chi.vercel.app`) and from a local server at `localhost:3100` or `127.0.0.1:3100`. On any other address, Chrome does not expose the extension to the page, so use the settings page below.

### The extension's settings page

The settings page opens on first install, or from the toolbar popup under **Settings**.

1. **Investi address.** The default is `https://investistocks.vercel.app`. The older hostname and a local server work out of the box. Any other address must use `https` (plain `http` only on `localhost` or `127.0.0.1`), and Chrome asks for access to that one origin when you save it.
2. **Personal access token.** In Investi, go to [Settings](/docs/app/settings), Connections, and click **Create token**. Copy the `inv_pat_...` value, which is shown once.
3. **Save and test.** The extension calls `GET /api/v1/securities/SPY/intel` with the token and saves it only if that call succeeds.

To disconnect, use the extension's settings page.

## Web app contract

Pages talk to the extension with `chrome.runtime.sendMessage` and the fixed ID. Only Investi's own origins (the two hosted hostnames and the two local addresses) can send messages; other sites cannot message the extension or even detect it.

```js
chrome.runtime.sendMessage(EXTENSION_ID, { type: "investi.ping" }, (res) => {
  // { ok: true, version, connected, base } or { ok: false, error: "untrusted_origin" }
  // res is undefined when the extension is not installed (check chrome.runtime.lastError)
});

chrome.runtime.sendMessage(EXTENSION_ID, { type: "investi.connect", token: "inv_pat_...", base: location.origin }, (res) => {
  // { ok: true, version, base } or { ok: false, error: "<code>" }
});
```

Which origin may connect, as reported by Chrome (not by the page):

- **Fresh install:** any built-in Investi origin.
- **Connected to a built-in origin:** that origin, plus either hosted hostname. An install connected to `investi-chi` or `localhost` can be re-connected from `investistocks` without changing settings.
- **A custom address typed into the settings page:** only that origin.

The message's `base` must equal the sender's own origin, the token must look like `inv_pat_` plus 16 to 200 URL-safe characters, and a test call with it must succeed. A page can never point the extension at a server other than itself.

| Error code | Meaning |
| --- | --- |
| `bad_message` | `type`, `token` or `base` is missing or not a string. |
| `untrusted_origin` | The sending page is not an origin the extension trusts. |
| `base_mismatch` | The message's `base` is not the sender's origin. |
| `bad_token` | The token does not have the `inv_pat_` shape. |
| `no_permission` | The extension lacks host permission for the Investi origin. |
| `unauthorized` / `forbidden` | The test call returned `401` / `403` (usually a missing scope). |
| `network` / `timeout` | The test call could not reach Investi, or took longer than 8 seconds. |
| `schema` | The response was not a valid `intel.v1` payload. |

## Behavior

| Behavior | Value |
| --- | --- |
| Hover-intent delay before the card opens | 150 ms |
| Placeholder when data is not cached | Shown at once |
| Cache, in the page and in the background worker | 30 seconds |
| Close delay after the pointer leaves both the ticker and the card | 280 ms, or 1.2 s for a card at a remembered spot, restarted while the pointer heads toward it |
| Request timeout | 8 seconds |

- You can move the pointer into the card. **Esc** closes it. The card is at most 560 px tall; its sections scroll and never scroll the page behind.
- Placement uses the spot you dragged the card to, if any; otherwise it tries below, above, right and left of the ticker. It never covers a trade ticket, a confirmation dialog or a Buy, Sell or Review button, not even at a remembered spot. If nothing fits, the card is not shown.
- The card renders in a closed Shadow DOM, so the site's styles cannot change it and its styles cannot change the site.

## Security model

- **The token lives only in the background service worker.** Extension storage is restricted to trusted contexts, so the script running on a finance site cannot read it. That script sends only a ticker and receives JSON back.
- **Only GET requests, only to Investi.** The background worker calls `GET {base}/api/v1/securities/{ticker}/intel` with a bearer token, without cookies, and fails on any redirect. It never interacts with any site's forms, buttons or network requests.
- **No markup from data.** Every value from the API is written as text or as computed numbers, so injected HTML stays inert.
- **Narrow permissions.** Content scripts are declared only for the exact domains in the table above (https only). Host access is fixed for the hosted Investi and `localhost:3100`; any other Investi address is requested at runtime for that one origin.
- **Never on sign-in or order pages.** Sign-in, sign-up, password, SSO and MFA paths are excluded in the manifest and again at runtime, and so are brokerage order-entry paths. If a password field appears on a page, the extension removes itself from it.

See [Security and privacy](/docs/support/security-privacy) for how Investi stores the token on the server side.
