# Call the API from Python

Call the Fundalyze REST API with requests, read the quota header and handle the errors you will meet.

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.

## Before you start

- An API key from [Account → API & MCP](/account/api), in an environment variable so it never lands in your code:

  ```bash title="Terminal"
  export FUNDALYZE_API_KEY="fdz_live_YOUR_KEY"
  pip install requests
  ```

## Your first call

```python title="first_call.py"
import os

import requests

response = requests.get(
    "https://api.fundalyze.ai/api/public/v1/get_financials",
    params={
        "ticker": "MSFT",
        "metrics": ["revenue", "operating_income", "net_income"],
        "years": 3,
    },
    headers={"Authorization": f"Bearer {os.environ['FUNDALYZE_API_KEY']}"},
    timeout=60,
)
response.raise_for_status()
answer = response.json()

print(answer["text"])  # the compact table an assistant would read
print(answer["link"])  # where to check the figures on fundalyze.ai
print("Calls left today:", response.headers.get("X-Quota-Remaining"))
```

A list parameter such as `metrics` can be sent as a Python list (requests repeats the parameter) or as one comma-separated string; the API accepts both.

## Use `data` in code

`text` is laid out for a language model. In code, read `data`: the same content as JSON, with every field described on the tool's [reference page](/developers/reference/get_financials).

```python title="first_call.py (continued)"
data = answer["data"]
for metric in data["metrics"]:
    by_year = dict(zip(data["periods"], metric["values"]))
    print(metric["key"], by_year)
# revenue {'FY2024': 245122, 'FY2025': 281724, 'FY2026': 331839}  (USD millions)
```

## A small client

Fundalyze runs **one call at a time per key**, so make calls one after another, not in parallel threads. This helper waits and retries when the key is still busy with an earlier call, stops when the day's quota is used, and raises the API's own message for anything else:

```python title="fundalyze_client.py"
import os
import time

import requests

BASE = "https://api.fundalyze.ai/api/public/v1"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['FUNDALYZE_API_KEY']}"


class FundalyzeError(Exception):
    def __init__(self, status, detail):
        super().__init__(f"{status}: {detail}")
        self.status = status
        self.detail = detail


def call(tool, **params):
    for attempt in range(4):
        response = session.get(f"{BASE}/{tool}", params=params, timeout=60)
        if response.ok:
            return response.json()
        try:
            detail = response.json().get("detail", response.text)
        except ValueError:
            detail = response.text
        busy = response.status_code == 429 and "still running" in detail
        if busy and attempt < 3:
            time.sleep(1 + attempt)  # another call with this key is in flight
            continue
        raise FundalyzeError(response.status_code, detail)


apple = call("get_company", ticker="AAPL")
print(apple["data"]["name"], apple["data"]["sic_description"])

filings = call("list_filings", ticker="AAPL", form_type="10-K", limit=3)
for filing in filings["data"]["filings"]:
    print(filing["filing_date"], filing["form"], filing["sec_url"])
```

Errors come back as `{"detail": "..."}` with a status code; the message says what to fix (for example `Unknown ticker: APPL. Use search_companies to find the symbol.`). The full list is in [Errors](/developers/concepts/errors).

## Without a key

Two endpoints need no key, which is handy for generating code or checking what exists:

```python title="catalogue.py"
import requests

tools = requests.get("https://api.fundalyze.ai/api/public/v1/tools", timeout=30).json()["tools"]
for tool in tools:
    print(tool["name"], "-", tool["description"].split(". ")[0])

dictionary = requests.get("https://api.fundalyze.ai/api/public/v1/dictionary", timeout=30).json()
print(len(dictionary["metrics"]), "metric keys")
```

Next: [Limits and quotas](/developers/concepts/limits), or a worked task in [Recipes](/developers/recipes).
