Quickstart Guide

How to Get End-of-Day Stock Data via API

A complete walkthrough — from zero to a parsed array of daily OHLCV bars — using StashGamma's free EOD data API. No card required for any of this.*

* No credit card, ever — the free key is throttled under a fair-use policy of 300 requests/hour, 800/day, and 4,000/week (see Step 5).

Step 1 — Create an account and key

Sign up, then go to Profile → Developer API and click "Get free key". Your key looks like sg_live_....

Step 2 — Make your first request (curl)

curl -H "X-Api-Key: YOUR_API_KEY" \
  "https://www.stashgamma.com/api/dataapi/v1/eod/QQQ?from=2026-07-01&to=2026-08-01"

Step 3 — Same request in Python

import requests

resp = requests.get(
    "https://www.stashgamma.com/api/dataapi/v1/eod/QQQ",
    params={"from": "2026-07-01", "to": "2026-08-01"},
    headers={"X-Api-Key": "YOUR_API_KEY"},
)
bars = resp.json()["bars"]
print(bars[0])  # {'date': '2026-07-01', 'open': 611.2, 'high': 614.8, ...}

Step 4 — Same request in JavaScript (browser)

Plain fetch — no dependencies, works in any modern browser or a client-side React/Vue/Svelte component.

const res = await fetch(
  "https://www.stashgamma.com/api/dataapi/v1/eod/QQQ?from=2026-07-01&to=2026-08-01",
  { headers: { "X-Api-Key": "YOUR_API_KEY" } }
);
const { bars } = await res.json();
console.log(bars[0]); // { date: '2026-07-01', open: 611.2, high: 614.8, ... }

Step 4b — Node.js

Node 18+ has fetch built in, so the browser snippet above works unchanged in a .mjs file or any ESM-configured project. For an older Node runtime, or if you're already using axios elsewhere in the codebase:

// npm install axios
const axios = require('axios');

async function getEodBars(symbol, from, to) {
  const { data } = await axios.get(
    `https://www.stashgamma.com/api/dataapi/v1/eod/${symbol}`,
    {
      params: { from, to },
      headers: { 'X-Api-Key': process.env.STASHGAMMA_API_KEY },
    }
  );
  return data.bars;
}

getEodBars('QQQ', '2026-07-01', '2026-08-01').then((bars) => {
  console.log(bars[0]); // { date: '2026-07-01', open: 611.2, high: 614.8, ... }
});

Keep the key out of source control — read it from an environment variable (process.env.STASHGAMMA_API_KEY) rather than hardcoding it.

Step 4c — Java

Java 11+'s built-in java.net.http.HttpClient — no third-party HTTP library required.

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class StashGammaEodExample {
    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(
                "https://www.stashgamma.com/api/dataapi/v1/eod/QQQ?from=2026-07-01&to=2026-08-01"))
            .header("X-Api-Key", System.getenv("STASHGAMMA_API_KEY"))
            .GET()
            .build();

        HttpResponse<String> response =
            client.send(request, HttpResponse.BodyHandlers.ofString());

        System.out.println(response.statusCode()); // 200
        System.out.println(response.body());       // {"symbol":"QQQ","bars":[...]}
    }
}

Pair with a JSON library (Jackson, Gson) to deserialize response.body() into a typed Bar[] instead of parsing raw strings.

Step 4d — Go

Standard library only — net/http and encoding/json.

package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"os"
)

type EodResponse struct {
	Symbol string `json:"symbol"`
	Bars   []struct {
		Date   string  `json:"date"`
		Open   float64 `json:"open"`
		High   float64 `json:"high"`
		Low    float64 `json:"low"`
		Close  float64 `json:"close"`
		Volume int64   `json:"volume"`
	} `json:"bars"`
}

func main() {
	req, _ := http.NewRequest("GET",
		"https://www.stashgamma.com/api/dataapi/v1/eod/QQQ?from=2026-07-01&to=2026-08-01", nil)
	req.Header.Set("X-Api-Key", os.Getenv("STASHGAMMA_API_KEY"))

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	var eod EodResponse
	json.NewDecoder(resp.Body).Decode(&eod)
	fmt.Printf("%+v\n", eod.Bars[0])
}

Step 4e — Jupyter Notebook (pandas)

For research or backtesting, load the bars straight into a pandas DataFrame and plot close price against volume:

import pandas as pd
import requests

resp = requests.get(
    "https://www.stashgamma.com/api/dataapi/v1/eod/QQQ",
    params={"from": "2025-08-01", "to": "2026-08-01"},
    headers={"X-Api-Key": "YOUR_API_KEY"},
)
df = pd.DataFrame(resp.json()["bars"])
df["date"] = pd.to_datetime(df["date"])
df = df.set_index("date").sort_index()

df["daily_return"] = df["close"].pct_change()
df["close"].plot(title="QQQ close (via StashGamma EOD API)")

Full runnable notebook (price/volume chart, rolling volatility column, retry-on-429 handling) — download stashgamma_eod_quickstart.ipynb.

Step 4f — Excel

No macros, no add-ins — Excel's built-in Power Query can call the API and drop the bars straight into a table that refreshes on demand (Data → Get Data → From Other Sources → Blank Query, then Advanced Editor):

let
    Symbol = "QQQ",
    From = "2026-07-01",
    To = "2026-08-01",
    ApiKey = "YOUR_API_KEY",
    Url = "https://www.stashgamma.com/api/dataapi/v1/eod/" & Symbol
        & "?from=" & From & "&to=" & To,
    Source = Json.Document(
        Web.Contents(Url, [Headers=[#"X-Api-Key"=ApiKey]])
    ),
    Bars = Source[bars],
    ToTable = Table.FromList(Bars, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
    Expanded = Table.ExpandRecordColumn(
        ToTable, "Column1", {"date", "open", "high", "low", "close", "volume"},
        {"Date", "Open", "High", "Low", "Close", "Volume"}
    )
in
    Expanded

Prefer a plain formula over Power Query? Excel's native =WEBSERVICE() / =FILTERXML() combo works for a quick single-cell pull too, but Power Query is the better fit for anything you plan to refresh or chart, since it lands as a real table.

For Google Sheets instead: =IMPORTDATA() can't send a custom X-Api-Key header, so use an =IMPORTJSON() custom function or a small Apps Script UrlFetchApp.fetch() call that sets the header explicitly.

Step 5 — Handle rate limits gracefully

Every response carries X-RateLimit-Remaining-* headers for the hour/day/week windows. If you hit the free tier's limit you'll get a 429 with a Retry-After header — back off for that many seconds and retry. Full field-by-field reference is in the API documentation.

Ready to pull your first bars?

Free forever, no credit card — get a key and make your first call in minutes.

Get Started Free