> ## Documentation Index
> Fetch the complete documentation index at: https://nexus.gerowallet.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Market page

> Put a full Cardano market page on your own site with the hosted Nexus market kit

**Market page (hosted market kit).** Nexus hosts a ready-made, configurable Cardano market page that you drop into your own site with two tags and one element. It renders a token screener with sparklines, a trending strip, market stats, a token page with a candlestick chart, recent trades, an order book, per-DEX markets and top traders, an ADA page and a live swaps feed. It also embeds the [swap widget](https://nexus.gerowallet.io/docs/addons/aggregator.md#which-key-to-use), so visitors can trade without leaving your page.

The page is a custom element, `<gero-market>`, that authenticates with the same **public partner key** as the `<gero-swap>` widget. You describe the page in a JSON config, either by hand or in the Nexus console, which has a visual builder with a live preview and gives you the finished `index.html`.

```html theme={null}
<link rel="stylesheet" href="https://nexus.gerowallet.io/market-kit/v1/gero-market.css">
<script src="https://nexus.gerowallet.io/market-kit/v1/gero-market.js"></script>

<gero-market partner-key="nxs_your_partner_key"
             config='{"branding":{"title":"Acme Market"}}'></gero-market>
```

> [!NOTE]
>
> The market data on the page is read through your key, so the **Market Data** add-on must be active on the plan of the account that owns the key, on its own or through the [Data or Complete bundle](https://nexus.gerowallet.io/docs/addons/overview.md#bundles). Without it the page shows a notice that market data is not available on this key (HTTP 402). Letting visitors build and submit a swap also needs the **Transaction Builder** add-on, as it does for the standalone widget.

Hosted at: `https://nexus.gerowallet.io/market-kit/v1/` · Auth: a public partner key in the `partner-key` attribute (sent as `X-Api-Key`)

## Who it is for

Anyone who wants a market data page on their own domain without building one: a wallet, a community or project site, a launchpad, a dashboard. You keep your own domain, branding, navigation and footer. Nexus keeps the page working: fixes and new features reach your visitors without a change on your side (see [Versioning](#versioning)).

If you want to build a page from scratch, or read this data from a server, use the [Market Data endpoints](https://nexus.gerowallet.io/docs/addons/market-data.md) with an ordinary API key instead. The market page is a front end over those same reads.

## Before you start

| You need | Why |
| - | - |
| A **public partner key** | The page sends it as `X-Api-Key`. Issue one from the Nexus dashboard; it is the key the `<gero-swap>` embed snippet uses. |
| Your site's **origin registered on the key** | The key works only from the origins registered on it. The origin is the scheme, host and port of the page that loads the kit, for example `https://markets.example.com`. |
| The **Market Data** add-on on the key owner's plan | Every market read the page makes is gated by it. |
| The **Transaction Builder** add-on (only if visitors can swap) | The swap panel quotes with Market Data alone, but building and submitting a swap needs it. Set `swap.placement` to `off` for a read-only page. |

Partner keys are public by design: anyone can read the key out of your page source. The registered origins are what stops it being used from any other site. See [Which key to use](https://nexus.gerowallet.io/docs/addons/aggregator.md#which-key-to-use).

## Build one in the console

The console has a builder for the page at `/dashboard/market-page`. It is switched on per account; if you do not see it, you can still write the config by hand with the [reference](#config-reference) below, or start from one of the [recipes](#recipes).

**1. Choose a key**

Pick the partner key the page will use. The console saves the page per key, so reopening the builder restores it.

**2. Design the page**

Work through the builder's tabs, listed below. A live preview updates as you change settings.

**3. Copy or download the page**

The builder generates a complete `index.html` containing the two tags, the element and your config. Copy it or download it.

**4. Host it**

Upload the file to any static host, as described next.

What each tab of the builder holds:

| Tab | What it holds |
| - | - |
| **Blocks** | Which parts of the page appear, and in what order. |
| **Layout** | Preset, density and width. **Opening view** sets the view the page opens on and, for a token page, which token. **Token page chart** sets the chart's starting resolution, its indicators and its height. |
| **Design** | Mode, colors, radius and fonts. |
| **Data** | Display currency, default sort, columns, filters and refresh cadence. **Screener tabs** picks the tabs the screener shows, **Opens on** picks the one it starts on, **Rows shown** caps the table, and **Currencies offered** sets what the currency selector lists. |
| **Swap** | Where the swap panel opens and its defaults. |
| **Branding** | Title, **Tagline**, logo, navigation links and footer. **Show currency selector** and **Show sync status** switch those two header items on or off. |

## Host the page

The generated `index.html` is a plain static page. Put it on any static host (your own server, an object store with a CDN, a pages service) or paste the two tags and the element into a page you already have.

* The page must be served from an origin you registered on the key. A different domain, a preview URL, `http` instead of `https`, or a different port is a different origin and gets a 403.
* Opening the file from disk (`file://`) does not work, because the browser does not send a usable origin.
* Nothing else is required: no build step and no server of your own. The kit loads the swap widget bundle itself, from the same Nexus host.

To embed the market page inside a larger page, put the element where you want it and set `routing` to `"none"`. The kit then never touches your page's URL and reports navigation through an event instead (see [Routes](#routes) and [Events](#events)).

## The element

```html theme={null}
<gero-market partner-key="nxs_..." base-url="https://nexus.gerowallet.io" config='{...}' route="/live">
  <script type="application/json">{ ...config... }</script>
</gero-market>
```

| Attribute | Property | Meaning |
| - | - | - |
| `partner-key` | `partnerKey` | The partner's public key, sent as `X-Api-Key`. |
| `base-url` | `baseUrl` | The Nexus origin. Default `https://nexus.gerowallet.io`. |
| `config` | `config` | The page config. JSON text in the attribute; an object or JSON text in the property. |
| `route` | `route`, `navigate(path)` | The initial route when `routing` is `"none"`, for example `/token/<assetId>`. |

* A JavaScript property wins over its attribute.
* Config sources, highest priority first: the `config` property, the `config` attribute, then a `<script type="application/json">` child, which is read once when the element connects. With no config at all, the default page renders.
* Assigning a new object, or changing the attribute, re-renders in place. Mutating the same object does nothing.
* Invalid JSON, or JSON that is not an object, renders an inline error and fires `gero-market:error`. Fields that are wrong inside a valid object are repaired to their defaults and listed in one `console.warn`. The element never throws.

To mount a page from script instead of markup:

```js theme={null}
const unmount = window.GeroMarket.mount(document.getElementById('market'), config, {
  apiKey: 'nxs_your_partner_key',
  baseUrl: 'https://nexus.gerowallet.io',
});
// later: unmount();
```

## Config reference

The config is a JSON document, schema version 1. The element accepts a partial config and fills in the defaults below for anything you leave out. A config saved from the console is checked strictly instead: the original sections are present (`header` and `chart` may be left out, and so may every field added after the first release), unknown keys are refused at every level, enums are exact, and the whole document is at most 65,536 bytes. Names are case-sensitive.

### Top level

| Field | Type | Default | Notes |
| - | - | - | - |
| `schemaVersion` | `1` | `1` | Required in a stored config. |
| `network` | `"mainnet"` | `"mainnet"` | Partner keys are Cardano mainnet only. |
| `routing` | `"hash"` or `"none"` | `"hash"` | `hash` uses the URL fragment. `none` leaves your page's URL alone and emits `gero-market:navigate`. |

### `branding`

| Field | Type | Default | Notes |
| - | - | - | - |
| `title` | string, 1 to 120 characters | `"Cardano Market"` | The accessible label of the page. |
| `brandName` | string, 1 to 120 characters | `"Market"` | The name in the page header. |
| `logoUrl` | string | none | An `https://` URL or a `data:image/` URI, at most 200,000 characters. |
| `nav` | list of `{ label, href }` | `[]` | At most 12 links. `label` is 1 to 40 characters. `href` starts with `https://`, `http://`, `/` or `#`, has no whitespace and is at most 2,048 characters. |
| `footer` | string, up to 400 characters | none | Plain text. |
| `poweredBy` | boolean | `true` | Shows the "Powered by" line. |

### `header`

Settings for the page header. The header belongs to the `chrome` block, so they have no effect when that block is left out.

| Field | Type | Default | Notes |
| - | - | - | - |
| `currencySelector` | boolean | `true` | Shows the currency selector in the header. |
| `syncStatus` | boolean | `true` | Shows the sync indicator, a dot with a short status, and polls `/api/sync/status` to feed it. `false` also removes that request, which lowers the [quota estimate](#requests-and-quota). |
| `tagline` | string, 1 to 160 characters | none | Shown under the brand name instead of the page title. |

### `layout`

| Field | Type | Default | Notes |
| - | - | - | - |
| `preset` | `"terminal"`, `"dashboard"` or `"minimal"` | `"terminal"` | How the token page is arranged. It also picks the default `blocks`. |
| `blocks` | list of block ids | all 12 for `terminal` and `dashboard`; `chrome`, `screener`, `token.header`, `token.chart` for `minimal` | Known ids, each at most once. The order is the order of the home page. |
| `density` | `"compact"` or `"comfortable"` | `"compact"` | The default row density. A visitor's own choice wins. |
| `maxWidth` | `"full"` or `"xl"` | `"full"` | `xl` centers the page at 1280 px. |
| `startView` | `"home"`, `"token"`, `"ada"` or `"live"` | `"home"` | The view the page opens on when there is no route in the URL. A later navigation to `#/` still shows home. See [Routes](#routes). |
| `startAssetId` | lowercase hex asset id | none | The token to open when `startView` is `"token"`: the policy id followed by the asset name. Without it the page opens the token with the highest volume. |

The three presets arrange the token page differently. `terminal` puts the chart beside a rail with trades and the order book, with markets and top traders below. `dashboard` shows the header and the chart at full width, then the other cards in a two-column grid. `minimal` is a single column of whatever is enabled.

Block ids:

| Id | What it shows | Where |
| - | - | - |
| `chrome` | The header (brand, navigation links, currency selector, search) and the data-sync indicator. Wraps every view. The [`header`](#header) section tunes it. | Every page |
| `trending` | A strip of the tokens that moved most over the selected timeframe. | Home |
| `stats` | Market statistics. | Home |
| `screener` | The token table with sparklines, tabs, search, category filter and watchlist. | Home |
| `ada` | The ADA page. | `#/ada` |
| `live` | The live swaps feed. | `#/live` |
| `token.header` | The token's name, price and key figures. | Token page |
| `token.chart` | The candlestick chart with indicators. | Token page |
| `token.trades` | Recent trades. | Token page |
| `token.orderbook` | The order book, real or simulated. | Token page |
| `token.markets` | The token's markets across the DEXes it trades on. | Token page |
| `token.topTraders` | The token's most active traders. | Token page |

A block you leave out of `blocks` does not render.

### `theme`

| Field | Type | Default | Notes |
| - | - | - | - |
| `mode` | `"dark"`, `"light"` or `"auto"` | `"dark"` (the preset's mode when you give a preset and no mode) | `auto` follows the visitor's `prefers-color-scheme` live. |
| `preset` | string, up to 40 characters | `"terminal-dark"` | Known presets: `terminal-dark`, `clean-light`, `minimal-dark`. Other names are stored but ignored. A preset applies only when its designed mode equals the mode in effect. |
| `tokens` | object | `{}` | Color overrides. Keys: `bg`, `surface`, `raised`, `text`, `muted`, `border`, `accent`, `up`, `down`, `warn`. Values: `#rgb` or `#rrggbb`. Overrides always win over a preset. |
| `radius` | number, 0 to 32 (px) | `8` | The base corner radius. |
| `font` | font stack, 1 to 120 characters | system sans-serif | No `;`, `{`, `}`, `<`, `>`, `"`, backslash or control characters. |
| `monoFont` | font stack, 1 to 120 characters | system monospace | Same rules as `font`. |

### `data`

| Field | Type | Default | Notes |
| - | - | - | - |
| `denomination` | `"ADA"`, `"USD"`, `"EUR"`, `"GBP"`, `"JPY"` or `"BTC"` | `"ADA"` | The default display currency. A visitor's own choice wins. Falls back to USD if the rate is missing. |
| `timeframe` | `"1h"`, `"24h"`, `"7d"` or `"30d"` | `"24h"` | The default timeframe for price changes. A visitor can switch it. |
| `sort` | `{ column, dir }` | `{ "column": "volume24h", "dir": "desc" }` | `dir` is `"asc"` or `"desc"`. `column` is a column id. |
| `columns` | list of column ids | all 15 | Non-empty, each at most once. The order is the order of the screener's columns. |
| `pollMs` | whole number, 15,000 to 300,000 | `60000` | How often the page refreshes prices, in milliseconds. See [Requests and quota](#requests-and-quota). |
| `swapFlashMs` | `null`, or a whole number from 10,000 to 60,000 | `null` | Live swap flashes in the screener. `null` turns them off and makes no request. A number turns them on and is how often, in milliseconds, the page checks for new swaps. The key must be present, as `null` or a number, in a stored config. |
| `sparklines` | boolean | `true` | Shows the sparkline column. Off also skips the sparklines request. |
| `verifiedOnly` | boolean | `false` | Lists only verified tokens. |
| `allow` | list of ids | none | When non-empty, only these tokens are listed. |
| `deny` | list of ids | none | These tokens are never listed. Wins over `allow`. |
| `featured` | list of ids | none | These tokens are pinned to the top, in this order. |
| `tabs` | list of tab ids | all 7, in the order listed below | Non-empty, each at most once. The screener shows only these tabs, in this order. |
| `defaultTab` | a tab id | `"all"` | The tab the screener opens on. It must be listed in `tabs`. |
| `rowLimit` | `null`, or a number from 10 to 500 | `null` | The most rows the screener shows, counted after filtering and sorting. `null` shows every row. Featured and starred rows still come first. |
| `currencies` | list of currency codes | all 6 | Non-empty, each at most once, and it must include `denomination`. The currency selector offers only these, and only the ones the currency rates endpoint can convert. ADA and USD are always available. |

Column ids: `name`, `price`, `priceChange1h`, `priceChange24h`, `priceChange7d`, `priceChange30d`, `spark`, `volume24h`, `volume7d`, `txnCount24h`, `makerCount24h`, `tvl`, `marketCap`, `totalSupply`, `risk`.

Tab ids: `all`, `trending`, `gainers`, `losers`, `volume`, `new`, `watchlist`. Currency codes: `ADA`, `USD`, `EUR`, `GBP`, `JPY`, `BTC`.

`allow`, `deny` and `featured` each take at most 500 entries. An entry is a lowercase hex asset id (the policy id followed by the asset name) or a bare 56-character policy id, which covers every asset under that policy. Tickers are not accepted, because any token can claim any ticker.

### `chart`

The chart on the token page.

| Field | Type | Default | Notes |
| - | - | - | - |
| `defaultResolution` | `"15m"`, `"1h"`, `"1D"` or `"1W"` | `"1D"` | The resolution the chart opens with. |
| `indicators` | list of `"sma"`, `"ema"`, `"bb"` or `"rsi"` | `[]` | The overlays the chart starts with: simple and exponential moving averages, Bollinger bands and RSI. A visitor's own choice is remembered in their browser and wins from then on. |
| `height` | `"compact"`, `"default"` or `"tall"` | `"default"` | The height of the chart column on the token page. `compact` is about 360 px, `tall` about 620 px, and `default` is the standard height. |

### `swap`

| Field | Type | Default | Notes |
| - | - | - | - |
| `placement` | `"drawer"`, `"side"`, `"inline"`, `"modal"` or `"off"` | `"drawer"` | `drawer` slides in from the side, `side` is a sticky column beside the page, `inline` sits above the view, `modal` is a dialog, `off` removes the swap panel. |
| `defaultTokenOut` | lowercase hex asset id, or `"lovelace"` | none | The token the swap form opens with as the token to buy. On a token page, that token is used instead. |
| `slippage` | percent string such as `"0.5"`, at most 16 characters, 0 to 50 | none | The default slippage tolerance. |
| `theme` | object | none | Overrides for the swap panel's look. Keys: `accent`, `bg`, `surface`, `text`, `verifiedCheck` (colors, `#rgb` or `#rrggbb`), `radius` (0 to 32) and `fontFamily` (a font stack). By default the panel follows the page's theme. |

### A complete example

```json theme={null}
{
  "schemaVersion": 1,
  "network": "mainnet",
  "branding": {
    "title": "Acme Market",
    "brandName": "Acme",
    "nav": [{ "label": "Home", "href": "https://acme.example" }],
    "footer": "Prices are indicative.",
    "poweredBy": true
  },
  "header": { "currencySelector": true, "syncStatus": true, "tagline": "Live Cardano prices" },
  "layout": {
    "preset": "dashboard",
    "blocks": ["chrome", "trending", "screener", "token.header", "token.chart", "token.trades"],
    "density": "comfortable",
    "maxWidth": "xl",
    "startView": "token",
    "startAssetId": "279c909f348e533da5808898f87f9a14bb2c3dfbbacccd631d927a3f534e454b"
  },
  "theme": { "mode": "auto", "preset": "clean-light", "tokens": { "accent": "#e91e63" }, "radius": 12, "font": "Inter, system-ui, sans-serif", "monoFont": "ui-monospace, monospace" },
  "data": {
    "denomination": "USD",
    "timeframe": "24h",
    "sort": { "column": "volume24h", "dir": "desc" },
    "columns": ["name", "price", "priceChange24h", "spark", "volume24h"],
    "pollMs": 60000,
    "swapFlashMs": null,
    "sparklines": true,
    "verifiedOnly": true,
    "tabs": ["all", "trending", "gainers", "losers", "watchlist"],
    "defaultTab": "trending",
    "rowLimit": 100,
    "currencies": ["ADA", "USD", "EUR"]
  },
  "chart": { "defaultResolution": "1h", "indicators": ["sma"], "height": "tall" },
  "swap": { "placement": "drawer", "defaultTokenOut": "lovelace", "slippage": "0.5" },
  "routing": "hash"
}
```

## Recipes

Three configs for common pages. Each is a partial config you can paste into the `config` attribute: the page fills in the defaults for everything left out. In the console, the same settings are in the tabs listed under [Build one in the console](#build-one-in-the-console).

### A project's own token page

The page opens on your token, pins it to the top of the screener, and keeps the screener to two tabs for visitors who go looking for other tokens. Replace both asset ids with your token's policy id followed by its asset name in hex.

```json theme={null}
{
  "layout": {
    "startView": "token",
    "startAssetId": "279c909f348e533da5808898f87f9a14bb2c3dfbbacccd631d927a3f534e454b",
    "blocks": ["chrome", "screener", "token.header", "token.chart", "token.trades", "token.markets"]
  },
  "header": { "tagline": "The Acme token, live" },
  "data": {
    "featured": ["279c909f348e533da5808898f87f9a14bb2c3dfbbacccd631d927a3f534e454b"],
    "tabs": ["all", "watchlist"],
    "defaultTab": "all"
  }
}
```

### A clean screener only

A single home page: the token table with the trending strip and market stats, capped at 100 rows and offering two currencies. No token, ADA or live pages, and no sync indicator.

```json theme={null}
{
  "layout": {
    "startView": "home",
    "blocks": ["chrome", "trending", "stats", "screener"]
  },
  "header": { "syncStatus": false },
  "data": {
    "rowLimit": 100,
    "currencies": ["ADA", "USD"]
  }
}
```

### Dark terminal with indicators

The terminal layout in its dark preset, with a tall chart that starts with a moving average and Bollinger bands on hourly candles, and every screener tab.

```json theme={null}
{
  "layout": { "preset": "terminal" },
  "theme": { "mode": "dark", "preset": "terminal-dark" },
  "chart": {
    "defaultResolution": "1h",
    "indicators": ["sma", "bb"],
    "height": "tall"
  },
  "data": {
    "tabs": ["all", "trending", "gainers", "losers", "volume", "new", "watchlist"],
    "defaultTab": "volume"
  }
}
```

## Theming

The page is themed with CSS variables on the `<gero-market>` element. Set them from your own stylesheet, or on any ancestor, and they override the config's colors and fonts:

```css theme={null}
gero-market {
  --gero-market-accent: #e91e63;
  --gero-market-radius: 12px;
}
```

| Variable | Controls |
| - | - |
| `--gero-market-bg` | Page background |
| `--gero-market-surface` | Panels and cards |
| `--gero-market-raised` | Raised and hovered surfaces |
| `--gero-market-text` | Main text |
| `--gero-market-muted` | Secondary text |
| `--gero-market-border` | Borders and dividers |
| `--gero-market-accent` | Links, highlights and the primary action |
| `--gero-market-up` | Price rises and buys |
| `--gero-market-down` | Price falls and sells |
| `--gero-market-warn` | Warnings |
| `--gero-market-radius` | Corner radius |
| `--gero-market-font` | Text font stack |
| `--gero-market-mono` | Monospace font stack |

The presets are starting points for the colors:

| Preset | Mode | Look |
| - | - | - |
| `terminal-dark` | dark | The default: a dark trading terminal. |
| `clean-light` | light | A light page with a cyan accent. |
| `minimal-dark` | dark | A neutral dark page. |

The kit's stylesheet is scoped to the `<gero-market>` element and its popovers, and never restyles the rest of your page. A plain class selector in your own CSS can override any of it. The swap panel follows the page's theme unless `swap.theme` says otherwise.

## Routes

With `routing` set to `"hash"` (the default) the page navigates with the URL fragment, so every view has a link you can share and the browser's back button works.

| Hash | View |
| - | - |
| `#/` | Home: the enabled blocks among `trending`, `stats` and `screener`, in config order. |
| `#/token/<assetId>` | A token page. `<assetId>` is the lowercase hex asset id; anything else shows home. |
| `#/token/<assetId>?swap=1` | A token page with the swap panel open. |
| `#/ada` | The ADA page. |
| `#/live` | The live swaps feed. |

Links in your `branding.nav` can point at these fragments, for example `{ "label": "Live", "href": "#/live" }`.

**Opening view.** The page opens on home unless the config says otherwise. Set `layout.startView` to `"token"`, `"ada"` or `"live"` to open on another view when there is no route to open: no fragment under `hash` routing, no `route` attribute under `none`. With `"token"`, `layout.startAssetId` names the token, and without it the page opens the token with the highest volume. A link that carries its own route, such as `#/live`, opens that view instead, and a later navigation to `#/` still shows home. If the chosen view's block is turned off, or no token can be shown, the page opens on home.

With `routing` set to `"none"`, the page does not read or write your URL. Start it on a view with the `route` attribute (for example `route="/live"`), move it with the element's `navigate(path)` method, and listen for `gero-market:navigate` to keep your own URL in step.

## Events

The element dispatches these events. All of them bubble and cross shadow roots, so you can listen on the element or any ancestor.

| Event | `detail` |
| - | - |
| `gero-market:ready` | `{ version }`. Fires once, after the first render. |
| `gero-market:error` | `{ code, message, ... }`. `INVALID_CONFIG`; `RENDER_ERROR` (with `block` when one block failed); and, when there is no data to show, `MARKET_DATA_UNAVAILABLE`, `UNAUTHORIZED`, `FORBIDDEN`, `UPSTREAM_UNAVAILABLE` or `NETWORK`, with `status` and `requestId`. |
| `gero-market:navigate` | `{ view, assetId?, swapOpen, path }`. Only with `routing: "none"`. |
| `gero-market:swap` | `{ status, txHash?, trackingUrl?, code?, message? }`. Fires when the embedded swap panel reports a result: `status` is `"submitted"` (with the transaction hash and a link to follow it) or `"error"` (with the widget's error code and message). |

```js theme={null}
const market = document.querySelector('gero-market');

market.addEventListener('gero-market:ready', (e) => console.log('kit build', e.detail.version));
market.addEventListener('gero-market:error', (e) => console.warn(e.detail.code, e.detail.message));
market.addEventListener('gero-market:swap', (e) => {
  if (e.detail.status === 'submitted') analytics.track('swap_submitted', { txHash: e.detail.txHash });
});
```

## Requests and quota

Every request a visitor's browser makes counts against the key owner's monthly quota and per-second rate limit, exactly like a call made from a server. A page with many visitors is many callers on one key, so the refresh cadence is worth a thought. See [Rate limits](https://nexus.gerowallet.io/docs/concepts/rate-limits.md).

What the page does by default:

* It refreshes prices and the RSI indicator every 60 seconds (two requests per refresh), the sync status once a minute (or as often as `pollMs` when that is slower), and the sparklines and currency rates every 5 minutes. Set `header.syncStatus` to `false` and the sync status request is not made.
* It **pauses when the browser tab is hidden** and resumes when the visitor comes back.
* Live swap flashes are **off** by default. A token page loads its chart, trades, order book, markets and top traders when it opens and does not poll them.
* When a request is answered 429, it waits for the `Retry-After` the response carries before trying again, and shows a notice. After a 402 it stops polling until the visitor presses the retry button.

Approximate steady-state cost of one open, visible tab on the home page:

| Setting | Requests per hour |
| - | - |
| Defaults (`pollMs` 60,000, sparklines and sync status on) | about 204 |
| `pollMs` 15,000 (the minimum) | about 560 |
| `pollMs` 300,000 (the maximum) | about 60 |
| `sparklines` off | about 12 fewer |
| `header.syncStatus` off | about 60 fewer at the default `pollMs` |
| `swapFlashMs` 10,000 | about 360 more |
| `swapFlashMs` 60,000 | about 60 more |
| The `#/live` view open | about 240 more at the default cadence of 15 seconds |

At the defaults that is about 120 requests for the token list (60 refreshes of two requests each), 60 for the sync status, 12 for currency rates and 12 for sparklines. These are estimates, and opening a page or a token adds a handful of requests. Multiply by your concurrent visitors to size the quota. The data settings that move the number most are:

| Setting | Effect on quota |
| - | - |
| `data.pollMs` | The main lever. Doubling it roughly halves the home page's cost. Range 15 seconds to 5 minutes. |
| `data.swapFlashMs` | `null` (the default) makes no swap requests at all. A smaller number polls the swaps feed more often. |
| `data.sparklines` | Off skips the sparklines request. |
| `header.syncStatus` | Off skips the sync status request, about 60 an hour at the default `pollMs`. |
| `layout.blocks` | A block you leave out does not make its own requests. Leaving out `chrome` also removes the sync status request. |

If you read these endpoints with your own code from a registered origin, a browser can read these response headers, which the partner gateway exposes to your page: `Retry-After`, `X-Candles-Truncated` (a candles range was cut to the per-response limit), `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `X-Request-Id`. Quote `X-Request-Id` when you contact support.

## What the key can read

The page uses only read-only endpoints that a partner key may call from a registered origin: `/api/prices/**`, `/api/market/**`, `/api/dex/swaps/**`, `/api/dex/pools/token/**`, `/api/dex/orderbook/**`, `/api/dex/tokens/{policyId}/{assetName}/top-traders`, `/api/sync/status` and the swap aggregator for the swap panel. The full list is under [Which key to use](https://nexus.gerowallet.io/docs/addons/aggregator.md#which-key-to-use). Pool lists, orders by token or DEX, quotes, NFT, leaderboard, wallet and blueprint routes stay off a partner key.

Two of the price endpoints exist for the page and are described in [Market Data](https://nexus.gerowallet.io/docs/addons/market-data.md#sparklines-and-currency-rates): sparkline series and currency rates.

## Troubleshooting

> **The page says it cannot load market data (403)**
>
> The key was refused. The usual cause is that the origin serving the page is not registered on the key. The origin is the scheme, host and port, so `https://www.example.com`, `https://example.com` and `http://example.com` are three different origins. Register the exact one in the Nexus dashboard. If you set `base-url` to a proxy of your own, the proxy must forward the browser's `Origin` header unchanged.
>
> To see what is wrong, call the [self-check](https://nexus.gerowallet.io/docs/addons/aggregator.md#diagnosing-a-partner-key) with the same key and origin. It names an unregistered or missing origin, an out-of-date endpoint allowlist (`SCOPE_INCOMPLETE`), and a revoked or expired key. It reports the Transaction Builder add-on, not Market Data, so a missing Market Data add-on shows up as a 402 instead.

> **The page says market data is not available (402)**
>
> The account that owns the key does not have the **Market Data** add-on. The response body names it: `"addon": "marketData"`. Activate Market Data, or a bundle that includes it, on the owner's plan; see [Add-ons](https://nexus.gerowallet.io/docs/addons/overview.md). The Starter plan covers the core API only, so it cannot carry the add-on. The page stops polling after a 402; once the add-on is active, reload the page or press the retry button.

> **The page says it is slowing down (429)**
>
> Either the owner's monthly quota is used up or the per-second rate limit was exceeded by the combined traffic on the key. The page waits for the `Retry-After` it is given and tries again. To use less quota, raise `data.pollMs`, leave `data.swapFlashMs` at `null`, and turn `data.sparklines` and `header.syncStatus` off if you do not need them; see [Requests and quota](#requests-and-quota). To raise the limits, change the plan; see [Rate limits](https://nexus.gerowallet.io/docs/concepts/rate-limits.md).

> **The page is empty, or shows my config as defaults**
>
> Check the browser console. Invalid JSON, or JSON that is not an object, shows an inline error and fires `gero-market:error` with `INVALID_CONFIG`. A field that is wrong inside a valid object is replaced by its default, and every repair is listed in a single `console.warn`. Common causes are a misspelled key (unknown keys are ignored), an enum in the wrong case, a number outside its range, or a color that is not `#rgb` or `#rrggbb`.

> **The swap panel is missing or cannot build a swap**
>
> The panel is missing when `swap.placement` is `"off"`. If it opens but cannot build or submit a swap, check that the key's owner has the **Transaction Builder** add-on; see [DEX Aggregator](https://nexus.gerowallet.io/docs/addons/aggregator.md).

> **The console preview did not load**
>
> The preview is the kit's own `preview.html`, shown in a frame inside the console. Reload the builder and try again. If the frame stays blank, disable browser extensions that block frames or scripts for `nexus.gerowallet.io` and check the browser console for blocked requests. If the preview shows a notice about market data instead of the page, that is the 402 or 403 above, not a loading problem.

> **Data is delayed, or a block shows an error**
>
> A 5xx from the data service shows "delayed" and keeps the last data it received while it retries with a growing delay. One block failing does not take down the rest of the page: it shows an inline error and fires `gero-market:error` with `RENDER_ERROR`. Quote the reference shown on the page, or the `requestId` in the event, when you contact support.

## Versioning

The kit lives at `https://nexus.gerowallet.io/market-kit/v1/` and is **replaced in place on every deploy**. You keep one snippet and get fixes and new features without touching your page. Two consequences:

* **Do not pin Subresource Integrity (`integrity="sha384-..."`) on the kit's files.** The hash changes with every deploy, so a pinned page would stop loading after the next one. This differs from the swap widget bundle, which is version-pinned and has published hashes.
* The files are served so that browsers revalidate them on every load, which is cheap when nothing changed.

Changes that add something stay in `v1/`. A breaking change, such as a removed or renamed config field, ships under a new path, `v2/`, with its own `schemaVersion`; you opt in by changing the snippet. `window.GeroMarket.version` and the `version` in the `gero-market:ready` event tell you which build a page is running.

> **[DEX Aggregator](https://nexus.gerowallet.io/docs/addons/aggregator.md)**
>
> The partner key, registered origins, the self-check and the swap endpoints behind the swap panel.

> **[Market Data](https://nexus.gerowallet.io/docs/addons/market-data.md)**
>
> The endpoints the page reads, for when you want the data without the page.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.