# Novig API in JavaScript and TypeScript

> Independent. Not affiliated with, endorsed by or sponsored by Novig.

Canonical: https://novigapi.com/novig-api-javascript
Facts checked: 2026-11-12

To use Novig data in JavaScript, call https://novigapi.com/api/v1 with fetch and an Authorization: Bearer nv_ header from Node 18+, Deno, Bun or an edge function. Responses are plain JSON with bids, asks and fair prices. On 12 Nov 2026, Jonathan Taylor 68.5 rushing yards had bids of 0.455 and 0.465, a fair Over of 0.495. (Source: [Novig API reference: List markets](https://docs.novig.com/api-reference/catalog/list-markets.md))

Novig's official API is a signed trading API that needs a Novig account. novigapi.com is an independent read-only data layer on top: one bearer key, JSON over HTTPS, names resolved. Every modern runtime has `fetch`, so you need no SDK.

## Where to call it from

Runtimes and the right pattern

| Runtime | Call novigapi.com directly? | Notes |
| --- | --- | --- |
| Node 18+, Deno, Bun | Yes | Global fetch; read the key from an environment variable. |
| Edge and serverless functions | Yes | Cache responses for about 10 seconds; data does not change faster at the source. |
| Browser | No | Your key would be visible to every visitor. Call your own server route instead. |
| React Native or Expo | No | Same reason: proxy through your backend. |


## TypeScript types

These match the response shape on every endpoint page. Prices are numbers from 0 to 1; times are ISO strings in UTC.

```typescript
export type Outcome = {
  outcome_id: string;
  name: string;
  bid: number | null;
  bid_qty: number | null;
  ask: number | null; // 1 - best bid on the other outcome
  last: number | null;
  fair: number | null; // (bid_yes + (1 - bid_no)) / 2
};

export type Market = {
  market_id: string;
  event_id: string;
  league: string;
  market_type: string;
  description: string;
  strike: number;
  status: string;
  player: { name: string; slug: string } | null;
  team: { name: string; slug: string; abbr: string } | null;
  stat: string | null;
  starts_at: string;
  fee: { coefficient: number; maker_credit: number; charged: string };
  volume: number;
  open_interest: number;
  outcomes: Outcome[];
};

export type Page<T> = {
  data: T[];
  meta: {
    next_cursor: string | null;
    limit: number;
    count: number;
    observed_at: string | null;
    source_age_seconds: number | null;
    edge_cached: boolean | null;
    plan: string;
    credits_charged: number;
    credits_remaining: number;
  };
};
```

## A small client for Node

```typescript
import type { Market, Page } from "./novig-types";

const BASE = "https://novigapi.com/api/v1";
const headers = { Authorization: "Bearer " + process.env.NOVIGAPI_API_KEY };

export async function get<T>(path: string, params: Record<string, string | number> = {}): Promise<T> {
  const url = new URL(BASE + path);
  for (const [k, v] of Object.entries(params)) url.searchParams.set(k, String(v));
  const res = await fetch(url, { headers });
  if (!res.ok) {
    const body = await res.json().catch(() => ({}));
    throw new Error(body.error?.code + ": " + (body.error?.message ?? res.status));
  }
  return res.json() as Promise<T>;
}

export async function* markets(params: Record<string, string | number>) {
  let cursor: string | null = null;
  do {
    const page: Page<Market> = await get("/markets", { ...params, limit: 500, ...(cursor ? { cursor } : {}) });
    yield* page.data;
    cursor = page.meta.next_cursor;
  } while (cursor);
}

for await (const m of markets({ league: "NFL", market_type: "RUSHING_YARDS" })) {
  const [over] = m.outcomes;
  console.log(m.description, over.bid, over.ask, over.fair);
}
```

## Devig in JavaScript

Novig's book holds bids only, so an outcome's ask is 1 minus the other side's best bid, and the fair price is the midpoint. For Jonathan Taylor 68.5 rushing yards on 12 Nov 2026: bids 0.455 and 0.465, Over ask 0.535, fair Over 0.495.

```javascript
export const fair = (bidYes, bidNo) =>
  bidYes == null || bidNo == null ? null : Math.round(((bidYes + (1 - bidNo)) / 2) * 10000) / 10000;

export const toAmerican = (p) => (p >= 0.5 ? Math.round((-100 * p) / (1 - p)) : Math.round((100 * (1 - p)) / p));

console.log(fair(0.455, 0.465)); // 0.495
console.log(toAmerican(0.495)); // 102
```

## Keep the key on the server

For a browser app, add one route on your server that calls novigapi.com and returns only what the page needs. This Next.js route handler caches for 10 seconds, which is the freshness floor of the source anyway, so a busy page spends very few credits:

```typescript
// app/api/board/route.ts
export const revalidate = 10;

export async function GET(request: Request) {
  const league = new URL(request.url).searchParams.get("league") ?? "NBA";
  const res = await fetch("https://novigapi.com/api/v1/markets?limit=200&league=" + encodeURIComponent(league), {
    headers: { Authorization: "Bearer " + process.env.NOVIGAPI_API_KEY },
    next: { revalidate: 10 },
  });
  const { data, meta } = await res.json();
  const rows = data.map((m) => ({ id: m.market_id, label: m.description, fair: m.outcomes[0]?.fair ?? null }));
  return Response.json({ rows, observed_at: meta.observed_at });
}
```

```javascript
// In the browser: call your own route, never novigapi.com with the key
const { rows, observed_at } = await (await fetch("/api/board?league=NBA")).json();
document.querySelector("#updated").textContent = "Prices as of " + new Date(observed_at).toLocaleTimeString();
```

## Push instead of poll

For many markets, open one connection to the [stream](https://novigapi.com/docs/endpoints/stream) at `wss://stream.novigapi.com/v1/stream` and subscribe by league, event, player or market. Changes arrive within 5 to 30 seconds. On servers use the `ws` package, because the built-in WebSocket cannot send an Authorization header (browsers can pass `?key=` instead, from your own backend-issued key). Free connections are delayed 300 seconds; paid plans are live.

## Errors and limits

Every error is JSON with `error.code` and `error.message`. Retry `RATE_LIMITED` after the `Retry-After` header and any 5xx after a short wait; do not retry `INVALID_API_KEY`, `NOT_FOUND` or `QUOTA_EXCEEDED`. Errors cost no credits, and there are no automatic overage charges. See [errors](https://novigapi.com/docs/endpoints/errors) and [rate limits](https://novigapi.com/docs/endpoints/rate-limits).

## Questions

### Is there an npm package for Novig data?

You do not need one. The API is plain JSON over HTTPS; the types on this page cover the response shape.

### Can I call novigapi.com straight from the browser?

Do not. Any key shipped to a browser is public. Put a small route on your server or edge function and call that from the page.

### How often should I poll?

No faster than about every 10 seconds per market list: Novig's public edge caches market lists for 10 seconds, so faster polls return the same data and still cost credits.

## Sources

- [Novig API reference: List markets](https://docs.novig.com/api-reference/catalog/list-markets.md)
- [Novig API docs: Trading fees](https://docs.novig.com/api/concepts/fees.md)
- [Novig docs: Sign a request](https://docs.novig.com/api/signing.md)
- [Novig API docs](https://docs.novig.com)

---

Get a free API key: https://novigapi.com/sign-up . Docs: https://novigapi.com/docs . MCP: https://novigapi.com/api/mcp . Operated by Stunning Couches LLC. Independent. Not affiliated with, endorsed by or sponsored by Novig.
