snapdata

Quickstart

curl, JavaScript, Python, and pointing an agent at it. No key, no account, no rate limit.

Every example below works as written. There is nothing to sign up for.

curl

# Today's gold price in INR, from the edge
curl -s https://snapdata.dev/api/v1/gold/in/latest.json | jq '.observations[]'

# A permanent, immutable URL, the same bytes in 2029
curl -s https://snapdata.dev/api/v1/gold/in/2026-08-11.json

# Everything, one request
curl -s https://snapdata.dev/api/v1/all/latest.json

# Token-cheap plain text
curl -s https://snapdata.dev/api/v1/gold/in/latest.txt
# Gold, in, 2026-08-11
Status: provisional | Trading day: yes | Calendar: IN-BULLION
Source: India Bullion and Jewellers Association (IBJA), via cmsapi.groww.in

XAU.18K  ₹11,460.20 /g
XAU.22K  ₹13,996.80 /g
XAU.24K  ₹15,280.30 /g

Prev (2026-08-10): XAU.24K ₹15,063.60  (+1.44%)

Conditional requests

Every response carries an ETag. Send it back and a repeat poll costs nothing:

ETAG=$(curl -sI https://snapdata.dev/api/v1/all/latest.json | grep -i '^etag:' | cut -d' ' -f2- | tr -d '\r')
curl -s -o /dev/null -w '%{http_code}\n' -H "If-None-Match: $ETAG" \
  https://snapdata.dev/api/v1/all/latest.json
# 304

JavaScript

const res = await fetch("https://snapdata.dev/api/v1/all/latest.json");
const { observations, generated_at } = await res.json();

for (const o of observations) {
  // Never assume a value exists. A closed market emits an explicit row with
  // value: null and is_trading_day: false, that is the contract, not a bug.
  if (o.value === null) {
    console.log(`${o.instrument_id}, no value (${o.status})`);
    continue;
  }
  console.log(`${o.instrument_id}: ${o.value} [${o.status}]`);
}

Only directly observed values, no carried-forward ones:

const observed = observations.filter(
  (o) => o.value !== null && o.filled_from === null,
);

Python

import urllib.request, json

url = "https://snapdata.dev/api/v1/crude/world/series/30d.json"
with urllib.request.urlopen(url) as r:
    data = json.load(r)

# Non-trading days are present as rows, so a DataFrame gets a real NaN on a
# holiday rather than a silently missing index entry.
rows = [
    (o["date"], o["value"])
    for o in data["observations"]
    if o["instrument_id"] == "BRENT.USD.BBL"
]

for date, value in rows[-7:]:
    print(date, value if value is not None else "closed")

With pandas, .csv saves you a step, and the null handling is already right:

import pandas as pd

df = pd.read_csv("https://snapdata.dev/api/v1/crude/world/series/30d.csv")
df = df[df.instrument_id == "BRENT.USD.BBL"]
df["date"] = pd.to_datetime(df["date"])

# `value` is genuinely NaN on closed days, not a forward-filled duplicate.
print(df.set_index("date")["value"].resample("W").mean())

Agents

https://snapdata.dev/llms.txt        the URL grammar, date rules, instruments
https://snapdata.dev/llms-full.txt   the same, plus current values and source health
https://snapdata.dev/openapi.json    templated paths, four shapes, not thousands of URLs

/llms.txt is generated from the same registries the router uses, so it cannot describe an endpoint that does not exist.

For per-dataset output, .txt is the cheap format:

curl -s https://snapdata.dev/api/v1/all/latest.txt

Checking freshness

If you care whether the numbers are current, and for anything automated you should, read /api/v1/meta.json:

curl -s https://snapdata.dev/api/v1/meta.json | jq '{status, stale_datasets}'
{
  "status": "degraded",
  "stale_datasets": ["equity-indices"]
}

The edge will keep serving latest.json from cache for up to a week under stale-if-error if an upstream disappears. That is the resilience story, and it is only honest because this file says so out loud.

On this page