# Call the API from JavaScript

Call the Fundalyze REST API with fetch from Node.js or any server runtime, and handle the quota and errors.

Every tool is a `GET` endpoint under `https://api.fundalyze.ai/api/public/v1`, with the tool's parameters as the query string and your API key as a bearer token. The examples use `fetch`, built into Node.js 18 and later, Deno, Bun and serverless runtimes.

## Keep the key on the server

Call the API from server code (a script, an API route, a serverless function), never from JavaScript that runs in your users' browsers: anything sent to a browser can be read, and a key that leaks spends your calls. Put the key in an environment variable:

```bash title="Terminal"
export FUNDALYZE_API_KEY="fdz_live_YOUR_KEY"
```

## Your first call

```js title="first-call.mjs"
const params = new URLSearchParams({ ticker: 'NVDA', limit: '5' })

const response = await fetch(`https://api.fundalyze.ai/api/public/v1/get_insider_trades?${params}`, {
  headers: { Authorization: `Bearer ${process.env.FUNDALYZE_API_KEY}` },
})
const answer = await response.json()
if (!response.ok) throw new Error(`${response.status}: ${answer.detail}`)

console.log(answer.text) // the compact table an assistant would read
for (const trade of answer.data.trades) {
  console.log(trade.filed_date, trade.owner, trade.code, trade.shares, trade.value_usd)
}
console.log('Calls left today:', response.headers.get('X-Quota-Remaining'))
```

Run it with `node first-call.mjs`. A list parameter (such as `metrics` on `get_financials`) can be repeated with `params.append('metrics', 'revenue')` or sent comma-separated; the API accepts both.

## A small client

Fundalyze runs **one call at a time per key**, so `await` each call before the next instead of firing them together with `Promise.all`. This helper retries while the key is busy with an earlier call and throws the API's own message otherwise:

```js title="fundalyze.mjs"
const BASE = 'https://api.fundalyze.ai/api/public/v1'

export async function call(tool, params = {}) {
  const query = new URLSearchParams()
  for (const [key, value] of Object.entries(params)) {
    for (const v of [value].flat()) query.append(key, String(v))
  }
  for (let attempt = 0; attempt < 4; attempt++) {
    const response = await fetch(`${BASE}/${tool}?${query}`, {
      headers: { Authorization: `Bearer ${process.env.FUNDALYZE_API_KEY}` },
    })
    const body = await response.json().catch(() => ({ detail: response.statusText }))
    if (response.ok) return body
    const busy = response.status === 429 && String(body.detail).includes('still running')
    if (busy && attempt < 3) {
      await new Promise((resolve) => setTimeout(resolve, 1000 * (attempt + 1)))
      continue
    }
    throw new Error(`${response.status}: ${body.detail}`)
  }
}

// Example: three tickers, one after another.
for (const ticker of ['AAPL', 'MSFT', 'GOOGL']) {
  const { data } = await call('get_financials', { ticker, metrics: ['revenue', 'net_margin'], years: 3 })
  console.log(ticker, data.periods, data.metrics.map((m) => [m.key, m.values]))
}
```

## TypeScript

The answer's shape is the same for every tool; `data` differs per tool and is described field by field on each [reference page](/developers/reference):

```ts title="types.ts"
interface FundalyzeAnswer<Data> {
  text: string // compact table for a language model
  data: Data // the same content as JSON
  link: string | null // the fundalyze.ai page to check it on
}
```

To generate a typed client instead, point your generator at the [OpenAPI document](https://api.fundalyze.ai/api/public/v1/openapi.json).

Next: [Errors](/developers/concepts/errors), or a worked task in [Recipes](/developers/recipes).
