Fruit StandFruit Stand
  • Pricing
  • Documentation
  • API Reference
  • Pricing
Getting Started
    IntroductionQuickstart & recipesMCP setup
The Data
Using the API
Policies
Links
    Fruit Stand homeSnowflake Marketplace (bulk)
powered by Zuplo
Getting Started

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.

Get a free API key

Setup

Code
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

Code
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):

Code
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.

Code
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

Code
# 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):

Code
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

Code
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 for limits and pagination details, or the full API Reference for every parameter.

Last modified on September 8, 2026
IntroductionMCP setup
On this page
  • Setup
  • Latest trailing returns for one fund
  • A watchlist in one call → DataFrame
  • Calendar-year returns
  • Search / list the universe (with pagination)
  • Compare two funds