Dashboard Building
Build a real-time energy price dashboard backed by OilPriceAPI: fetch all prices efficiently, cache them, choose sensible update intervals, and render charts.
Problem
You want to display live oil, gas, and energy commodity prices on an internal or customer-facing dashboard. A naive implementation calls the API once per widget on every page load, which:
- Burns through your rate limit and request quota.
- Adds latency to every render.
- Hammers the API with redundant requests for data that only changes every few minutes.
The goal is a dashboard that feels instant, stays fresh, and uses a minimal number of API calls.
Solution Architecture
Put a small caching layer between your dashboard and OilPriceAPI:
Browser ──> Your backend (cache) ──> OilPriceAPI /prices/latest
^ |
└─ polls ───────┘ (served from cache, refreshed on an interval)
- Single batch fetch. Pull every commodity you need with one call to the "all prices" endpoint instead of one call per ticker.
- Server-side cache. Store the latest response in memory (or Redis) with a short TTL.
- Scheduled refresh. A background job refreshes the cache on a fixed interval; the browser reads from your cache, never directly from OilPriceAPI.
Code Implementation
Fetching all prices efficiently
/v1/prices/latest returns one price object by default. To batch, pass a comma-separated by_code list — up to 20 codes in a single call, billed as one request:
curl -H "Authorization: Token YOUR_API_KEY" \
"https://api.oilpriceapi.com/v1/prices/latest?by_code=BRENT_CRUDE_USD,WTI_USD,NATURAL_GAS_USD,DIESEL_USD"
A multi-code call returns data.prices[], one object per code. If you want every commodity we publish rather than a chosen list, use /v1/prices/all instead.
Caching strategies
A read-through cache with a short TTL keeps the dashboard snappy while bounding your API usage. Crude benchmarks update every few minutes during market hours, while refined products (gasoline, diesel) update roughly twice a day — so a 60-second TTL is only worth it for the crude tiles.
Plan fit: a 60-second refresh is 1,440 requests/day (~43,000/month). That exceeds the Free plan (50 requests/day) and the Developer plan (10,000/month); it fits Starter (50,000/month) and above. Two ways to run it cheaper: poll less often (a 5-minute refresh is ~8,600/month, inside Developer), and send If-None-Match on every poll — an unchanged price returns 304 Not Modified, which does not count against your quota.
import time
import requests
API_KEY = "YOUR_API_KEY"
BASE_URL = "https://api.oilpriceapi.com/v1"
CACHE_TTL_SECONDS = 60
CODES = "BRENT_CRUDE_USD,WTI_USD,NATURAL_GAS_USD,DIESEL_USD" # up to 20 codes
_cache = {"prices": None, "etag": None, "fetched_at": 0}
def get_latest_prices():
"""Return cached prices, refreshing only when the TTL has expired."""
now = time.time()
if _cache["prices"] is None or now - _cache["fetched_at"] > CACHE_TTL_SECONDS:
headers = {"Authorization": f"Token {API_KEY}"}
if _cache["etag"]:
headers["If-None-Match"] = _cache["etag"] # 304s don't count against quota
resp = requests.get(
f"{BASE_URL}/prices/latest",
headers=headers,
params={"by_code": CODES},
timeout=10,
)
if resp.status_code == 304:
_cache["fetched_at"] = now # unchanged — keep serving the cached copy
return _cache["prices"]
resp.raise_for_status()
_cache["prices"] = resp.json()["data"]["prices"] # one object per code
_cache["etag"] = resp.headers.get("ETag")
_cache["fetched_at"] = now
return _cache["prices"]
Update intervals
Match your refresh interval to how often the underlying data changes. Polling faster than the data updates wastes quota without improving freshness.
| Data type | Typical change frequency | Suggested refresh |
|---|---|---|
| Crude spot prices (Brent, WTI) | Every few minutes | 60 seconds |
| Refined products (gasoline, diesel) | Roughly twice a day | 15–30 minutes |
| Daily settlements | Once per day | 15–30 minutes |
| Historical/EOD | Once per day | On demand |
Chart integration
Feed the cached payload into any charting library. Below is a minimal Chart.js example that renders a sparkline of recent values.
async function renderChart() {
const res = await fetch("/api/dashboard/prices"); // your cached endpoint
// A multi-code /prices/latest call returns { data: { prices: [...] } } —
// one object per commodity code, not a time series.
const { data } = await res.json();
const byCode = Object.fromEntries(data.prices.map((p) => [p.code, p]));
const brent = byCode["BRENT_CRUDE_USD"];
new Chart(document.getElementById("brent"), {
type: "bar",
data: {
labels: data.prices.map((p) => p.code),
datasets: [
{
label: `Latest prices (Brent: $${brent.price}/bbl)`,
data: data.prices.map((p) => p.price),
},
],
},
options: { responsive: true, scales: { y: { beginAtZero: false } } },
});
}
Best Practices
- Serve the dashboard from your cached endpoint, never directly from the browser to OilPriceAPI (this also keeps your API key off the client).
- Use one batch request for all tickers instead of N per-ticker requests.
- Send
If-None-Match/ETagon every poll. An unchanged price returns304 Not Modified, which transfers no data and does not count against your request quota — see Conditional requests. - Show a "last updated" timestamp so users know how fresh the data is.
- Degrade gracefully: if a refresh fails, keep serving the last good cache entry rather than showing an error.
Common Pitfalls
- Polling faster than the data changes. A 1-second refresh against a source that publishes daily used to burn quota for nothing. Use conditional requests so unchanged polls cost you nothing, and check the response freshness metadata to see how current the value actually is.
- Caching per request instead of per process. A cache that lives inside a single request handler never gets a hit. Use a shared store (module-level, Redis, or memcached).
- Exposing your API key in frontend code. Always proxy through your backend.
- No error fallback. A transient API hiccup should not blank out the whole dashboard — serve stale-but-valid data and retry in the background.