Blog/Developer

Build a gold-price alert in Python

A complete, production-ready Python script that polls the live spot price, checks it against a threshold, and sends a notification. Uses the REST API with retry logic and a local TTL cache.

DeveloperUpdated June 19, 2026.md

This tutorial builds a standalone Python script that polls the live gold spot price and fires an alert when a threshold is crossed. It:

  1. Fetches the live XAU-USD spot price from the API
  2. Checks it against a configurable threshold
  3. Fires a notification when the threshold is crossed
  4. Caches responses locally to avoid burning quota
  5. Retries on transient errors with exponential backoff

No SDKs required—only the standard library plus requests.

Prerequisites

  • Python 3.9+
  • pip install requests
  • A free API key from goldprice.dev/pricing (1,000 calls/month on the free tier)

Step 1 — Fetch the live spot price

The spot endpoint returns the current price along with source metadata:

import requests

API_KEY = "YOUR_API_KEY"
BASE_URL = "https://api.goldprice.dev/v1"

def fetch_spot_price() -> float:
    """Return the current XAU-USD spot price in USD per troy ounce."""
    resp = requests.get(
        f"{BASE_URL}/prices",
        params={"symbol": "XAU-USD-SPOT"},
        headers={"Authorization": f"Bearer {API_KEY}"},
        timeout=10,
    )
    resp.raise_for_status()
    data = resp.json()
    # The API returns sources[]. Use the first live spot source.
    for source in data.get("sources", []):
        if source.get("type") == "spot" and not source.get("stale", True):
            return float(source["price"])
    raise ValueError("No live spot price in response")

The sources[] array lets you inspect which oracle is behind each price. If a source is flagged stale: true, skip it—the API may return multiple sources and some may be delayed.

Step 2 — Add a TTL cache

Polling every second against a REST endpoint is wasteful and burns quota. A simple in-memory cache with a configurable TTL is enough for most alert use cases:

import time
from typing import Optional

_cache: dict[str, tuple[float, float]] = {}  # key -> (value, expiry)

def cached_spot_price(ttl_seconds: int = 60) -> float:
    """Return a cached spot price, refreshing only when TTL has elapsed."""
    key = "XAU-USD-SPOT"
    now = time.monotonic()
    if key in _cache:
        value, expiry = _cache[key]
        if now < expiry:
            return value
    price = fetch_spot_price()
    _cache[key] = (price, now + ttl_seconds)
    return price

At 60-second TTL you use at most 1,440 calls per day—well within the free tier (1,000/month) for light testing, and comfortable for any paid plan. For a deeper look at cache strategy and rate-limit headers, see Caching gold prices and staying inside rate limits.

Step 3 — Retry on transient errors

Network failures and rate-limit responses (HTTP 429) are transient. Wrap the fetch with exponential backoff:

import time
import requests

MAX_RETRIES = 4
BACKOFF_BASE = 2.0  # seconds

def fetch_spot_price_with_retry() -> float:
    for attempt in range(MAX_RETRIES):
        try:
            return fetch_spot_price()
        except requests.exceptions.HTTPError as exc:
            if exc.response is not None and exc.response.status_code == 429:
                wait = BACKOFF_BASE ** attempt
                print(f"Rate limited. Retrying in {wait:.0f}s…")
                time.sleep(wait)
            else:
                raise
        except requests.exceptions.RequestException as exc:
            wait = BACKOFF_BASE ** attempt
            print(f"Request error: {exc}. Retrying in {wait:.0f}s…")
            time.sleep(wait)
    raise RuntimeError("Max retries exceeded fetching spot price")

Step 4 — The alert logic

Define thresholds and fire a notification. Here we use print as the notifier—swap in email, Slack, SMS, or any webhook:

def send_alert(message: str) -> None:
    """Replace this with your preferred notification channel."""
    print(f"[ALERT] {message}")

def check_and_alert(
    above: Optional[float] = None,
    below: Optional[float] = None,
) -> None:
    """Fire an alert if the live price crosses either threshold."""
    price = cached_spot_price(ttl_seconds=60)
    print(f"XAU-USD spot: ${price:,.2f}")

    if above is not None and price >= above:
        send_alert(f"Gold crossed ABOVE ${above:,.0f} — current: ${price:,.2f}")
    if below is not None and price <= below:
        send_alert(f"Gold fell BELOW ${below:,.0f} — current: ${price:,.2f}")

Step 5 — Poll on an interval

Wire everything into a polling loop:

import time

def run_alert_loop(
    above: Optional[float] = None,
    below: Optional[float] = None,
    poll_interval: int = 60,
) -> None:
    """
    Poll forever, checking thresholds on each tick.

    Args:
        above: Fire when price >= this value (USD/oz). None = no upper alert.
        below: Fire when price <= this value (USD/oz). None = no lower alert.
        poll_interval: Seconds between checks (minimum 60 recommended).
    """
    print(f"Starting gold alert. above={above}, below={below}, interval={poll_interval}s")
    while True:
        try:
            check_and_alert(above=above, below=below)
        except Exception as exc:
            print(f"Error during check: {exc}")
        time.sleep(poll_interval)

if __name__ == "__main__":
    # Alert if gold goes above $3,500 or below $3,000
    run_alert_loop(above=3500, below=3000, poll_interval=60)

Complete file

Putting it all together in gold_alert.py:

"""
gold_alert.py — Poll the live gold spot price and fire threshold alerts.
Requires: pip install requests
"""
import time
from typing import Optional
import requests

API_KEY = "YOUR_API_KEY"
BASE_URL = "https://api.goldprice.dev/v1"
MAX_RETRIES = 4
BACKOFF_BASE = 2.0

_cache: dict[str, tuple[float, float]] = {}


def fetch_spot_price() -> float:
    resp = requests.get(
        f"{BASE_URL}/prices",
        params={"symbol": "XAU-USD-SPOT"},
        headers={"Authorization": f"Bearer {API_KEY}"},
        timeout=10,
    )
    resp.raise_for_status()
    data = resp.json()
    for source in data.get("sources", []):
        if source.get("type") == "spot" and not source.get("stale", True):
            return float(source["price"])
    raise ValueError("No live spot price in response")


def fetch_spot_price_with_retry() -> float:
    for attempt in range(MAX_RETRIES):
        try:
            return fetch_spot_price()
        except requests.exceptions.HTTPError as exc:
            if exc.response is not None and exc.response.status_code == 429:
                wait = BACKOFF_BASE ** attempt
                time.sleep(wait)
            else:
                raise
        except requests.exceptions.RequestException:
            time.sleep(BACKOFF_BASE ** attempt)
    raise RuntimeError("Max retries exceeded")


def cached_spot_price(ttl_seconds: int = 60) -> float:
    key = "XAU-USD-SPOT"
    now = time.monotonic()
    if key in _cache:
        value, expiry = _cache[key]
        if now < expiry:
            return value
    price = fetch_spot_price_with_retry()
    _cache[key] = (price, now + ttl_seconds)
    return price


def send_alert(message: str) -> None:
    print(f"[ALERT] {message}")


def check_and_alert(
    above: Optional[float] = None,
    below: Optional[float] = None,
) -> None:
    price = cached_spot_price(ttl_seconds=60)
    print(f"XAU-USD spot: ${price:,.2f}")
    if above is not None and price >= above:
        send_alert(f"Gold crossed ABOVE ${above:,.0f} — current: ${price:,.2f}")
    if below is not None and price <= below:
        send_alert(f"Gold fell BELOW ${below:,.0f} — current: ${price:,.2f}")


def run_alert_loop(
    above: Optional[float] = None,
    below: Optional[float] = None,
    poll_interval: int = 60,
) -> None:
    print(f"Starting gold alert. above={above}, below={below}, interval={poll_interval}s")
    while True:
        try:
            check_and_alert(above=above, below=below)
        except Exception as exc:
            print(f"Error: {exc}")
        time.sleep(poll_interval)


if __name__ == "__main__":
    run_alert_loop(above=3500, below=3000, poll_interval=60)

Extending the notifier

The send_alert function is the only thing you need to swap out. A few common patterns:

Slack webhook:

import requests as req

def send_alert(message: str) -> None:
    req.post(
        "https://hooks.slack.com/services/YOUR/WEBHOOK",
        json={"text": message},
        timeout=5,
    )

Email via SMTP:

import smtplib
from email.message import EmailMessage

def send_alert(message: str) -> None:
    msg = EmailMessage()
    msg["Subject"] = "Gold Price Alert"
    msg["From"] = "alerts@yourdomain.com"
    msg["To"] = "you@yourdomain.com"
    msg.set_content(message)
    with smtplib.SMTP_SSL("smtp.yourdomain.com", 465) as s:
        s.login("alerts@yourdomain.com", "YOUR_PASSWORD")
        s.send_message(msg)

Quota guidance

PlanCalls/monthAt 60s pollDays of coverage
Free1,000~1,440/day< 1 day (demo only)
Basicincluded~1,440/day30 days
Proincluded~1,440/day30 days

For continuous 60-second polling, any paid plan is appropriate. Free tier is useful for testing the integration before upgrading. See the pricing page for plan details.

// related guides

// goldprice.dev

Live gold prices, historical OHLC, and multi-source aggregation — available via REST and SSE.