# Quickstart & Recipes

Practical recipes in Python (`requests` + `pandas`). Every call needs your API key as a bearer
token; returns come back as **decimal fractions** (`0.0523` = +5.23%) — see [Methodology](/methodology).

<Button asChild>
  <a href="https://app.fruitstand.dev/pricing">Get a free API key</a>
</Button>

## Setup

```python
import requests

BASE = "https://api.fruitstand.dev"
API_KEY = "YOUR_API_KEY"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {API_KEY}"
```

## Latest trailing returns for one fund

```python
r = session.get(f"{BASE}/v1/funds/SPY/trailing-returns")
r.raise_for_status()
spy = r.json()
print(spy["as_of_date"], spy["return_1y"], spy["return_5y"])
# 2026-08-14 0.153204 0.142010   → +15.32% (1y), +14.20% annualized (5y)
```

Add `?as_of=YYYY-MM-DD` for a historical snapshot (it snaps back to the nearest prior trading day):

```python
session.get(f"{BASE}/v1/funds/SPY/trailing-returns", params={"as_of": "2020-03-23"})
```

## A watchlist in one call → DataFrame

The `/batch` endpoints take up to 100 codes and count as a **single** request against your quota.

```python
import pandas as pd

codes = ["SPY", "QQQ", "VTI", "IWM", "DIA"]
r = session.post(f"{BASE}/v1/trailing-returns/batch", json={"codes": codes})
r.raise_for_status()

df = pd.DataFrame(r.json()).set_index("code")
# show a few periods as percentages
cols = ["return_1y", "return_3y", "return_5y", "return_earliest_available"]
print((df[cols] * 100).round(2))
```

> Unknown codes are omitted from the response (no error), so compare `df.index` against `codes`
> to spot any misses.

## Calendar-year returns

```python
# one fund, one year
session.get(f"{BASE}/v1/funds/QQQ/calendar-returns", params={"year": 2022}).json()
# many funds, one year
session.post(f"{BASE}/v1/calendar-returns/batch",
             json={"codes": codes, "year": 2022}).json()
```

## Search / list the universe (with pagination)

`GET /v1/funds` uses cursor pagination — loop until `next_cursor` is `null` (`limit` max 1000):

```python
def all_funds(**filters):
    cursor = None
    while True:
        params = {"limit": 1000, **filters}
        if cursor:
            params["cursor"] = cursor
        page = session.get(f"{BASE}/v1/funds", params=params).json()
        yield from page["data"]
        cursor = page.get("next_cursor")
        if not cursor:
            break

etfs = list(all_funds(type="ETF", q="s&p 500"))
print(len(etfs), etfs[0]["code"], etfs[0]["name"])
```

## Compare two funds

```python
a, b = "SPY", "QQQ"
rows = session.post(f"{BASE}/v1/trailing-returns/batch", json={"codes": [a, b]}).json()
by_code = {row["code"]: row for row in rows}
for period in ["return_1y", "return_5y", "return_10y"]:
    print(period, by_code[a][period], "vs", by_code[b][period])
```

Next: [Errors, rate limits & quotas](/errors) for limits and pagination details, or the full
[API Reference](/api) for every parameter.
