Securely Redirecting...

Connecting to Stripe

How to Use the Release Calendar API to Schedule Indicator Fetches banner image

Article

How to Use the Release Calendar API to Schedule Indicator Fetches

Stop polling every endpoint on a timer. Learn how to query the FXMacroData release calendar to find the exact announcement time for any indicator, then schedule a single targeted API call for that moment — in Python and JavaScript.

By the end of this guide you will have a Python script (and a JavaScript equivalent) that queries the FXMacroData release calendar once to find the exact announcement_datetime of your chosen indicator, then schedules a single targeted API call for that moment — eliminating continuous polling and reducing unnecessary requests to zero between releases.

Prerequisites

  • • A FXMacroData API key — USD is free without a key; all other currencies require one. Obtain yours at fxmacrodata.com
  • • Python 3.9+ (or Node.js 18+ for the JavaScript variant)
  • • The requests library installed (pip install requests)
  • • Basic familiarity with ISO 8601 datetimes and Unix timestamps

Why schedule instead of poll?

The naive approach to keeping macro data fresh is a simple polling loop: check the indicator endpoint every five or ten minutes and update your model whenever a new value appears. This works, but it wastes requests — and more importantly, it introduces latency. A CPI release at 08:30 UTC could be missed entirely if your poller last ran at 08:28 and next runs at 08:33.

The release calendar endpoint returns the precise UTC timestamp of every upcoming announcement. With that timestamp in hand you can sleep until one second before the release, fire a single targeted indicator call, and then immediately sleep until the following release. You make exactly one call per release and you receive the data within seconds of publication — with no wasted requests in between.

Core workflow

  1. Call /api/release_calendar/{currency} — returns all upcoming releases (up to 30 days ahead).
  2. Find the next entry for your target indicator and read its announcement_datetime.
  3. Sleep until release time − a small offset (e.g. 2 seconds).
  4. Fetch /api/{currency}/{indicator} — you will receive the freshly published value.
  5. Loop back to step 1 to schedule the release after that.

Step 1 — Understand the release calendar response

The release calendar endpoint returns a JSON object with a data array. Each element represents one upcoming announcement, identified by the indicator's release name and a Unix announcement_datetime timestamp.

curl "https://fxmacrodata.com/api/release_calendar/usd"

Response shape:

{
  "currency": "usd",
  "data": [
    {
      "release": "cpi",
      "announcement_datetime": 1741860600
    },
    {
      "release": "gdp",
      "announcement_datetime": 1742119800
    },
    {
      "release": "unemployment_rate",
      "announcement_datetime": 1742378400
    }
  ]
}

announcement_datetime is a Unix timestamp in seconds (UTC). The releases are sorted chronologically, nearest first. For non-USD currencies, append your API key:

https://fxmacrodata.com/api/release_calendar/aud?api_key=YOUR_API_KEY

You can browse all supported release names in the API documentation — each indicator page lists the exact release string you will filter on.


Step 2 — Find the next release time for a specific indicator

Once you understand the shape of the response you can write a helper that fetches the calendar and picks out the soonest upcoming entry for a chosen indicator.

import time
import requests

BASE = "https://fxmacrodata.com/api"

def next_release(currency: str, indicator: str, api_key: str | None = None) -> float | None:
    """Return the Unix timestamp (seconds, UTC) of the next scheduled release,
    or None if no upcoming entry is found within the calendar window."""
    url = f"{BASE}/release_calendar/{currency}"
    params = {}
    if api_key:
        params["api_key"] = api_key

    resp = requests.get(url, params=params, timeout=10)
    resp.raise_for_status()

    payload = resp.json()
    events = payload.get("data", [])
    now = time.time()

    for event in events:
        if event.get("release") == indicator:
            ts = event.get("announcement_datetime")
            if ts and float(ts) > now:
                return float(ts)

    return None

The function iterates the calendar array — which is already sorted by date — and returns the announcement_datetime of the first entry that matches your indicator and lies in the future. It returns None when no upcoming release is found in the current calendar window (typically 30 days).


Step 3 — Sleep until the release, then fetch the indicator

With the target timestamp available you can pause execution until just before the release fires, then call the indicator endpoint to retrieve the freshly published value.

def wait_and_fetch(currency: str, indicator: str, api_key: str | None = None) -> dict | None:
    """Block until the next release of `indicator` for `currency`, then return
    the latest indicator data immediately after the announcement."""
    release_ts = next_release(currency, indicator, api_key)
    if release_ts is None:
        print(f"No upcoming release found for {currency}/{indicator} in the next 30 days.")
        return None

    # Wake up 2 seconds before the scheduled announcement
    wake_at = release_ts - 2
    sleep_secs = max(0.0, wake_at - time.time())
    print(f"Sleeping {sleep_secs:.0f}s until {currency.upper()} {indicator} release…")
    time.sleep(sleep_secs)

    # Fetch the indicator — released data will be available within a few seconds
    url = f"{BASE}/{currency}/{indicator}"
    params = {"api_key": api_key} if api_key else {}
    resp = requests.get(url, params=params, timeout=10)
    resp.raise_for_status()
    return resp.json()
Tip: Waking up 2 seconds early gives you a small buffer to account for network round-trip time and any clock skew between your host and the API server. Adjust this offset to suit your infrastructure — most production setups use 1–5 seconds.

Step 4 — Build a continuous scheduling loop

Combine the helper functions into a loop that refreshes the calendar after each release and immediately schedules the next call. This pattern handles consecutive releases cleanly and never leaves gaps in your data feed.

import time
import requests

BASE = "https://fxmacrodata.com/api"
API_KEY = "YOUR_API_KEY"     # omit or set to None for free USD endpoints

CURRENCY  = "usd"
INDICATOR = "cpi"

def next_release(currency, indicator, api_key=None):
    url = f"{BASE}/release_calendar/{currency}"
    params = {"api_key": api_key} if api_key else {}
    resp = requests.get(url, params=params, timeout=10)
    resp.raise_for_status()
    now = time.time()
    for event in resp.json().get("data", []):
        if event.get("release") == indicator:
            ts = event.get("announcement_datetime")
            if ts and float(ts) > now:
                return float(ts)
    return None

def on_release(data):
    """Called immediately after each release — replace with your own logic."""
    latest = data[-1] if data else {}
    print(f"  New value: {latest.get('value')}  announced at {latest.get('announcement_datetime')}")

def run():
    print(f"Starting scheduler for {CURRENCY.upper()} {INDICATOR}")
    while True:
        release_ts = next_release(CURRENCY, INDICATOR, API_KEY)
        if release_ts is None:
            print("No release found within 30-day window — retrying in 24 hours.")
            time.sleep(86_400)
            continue

        sleep_secs = max(0.0, release_ts - 2 - time.time())
        print(f"Next release in {sleep_secs / 3600:.1f}h  ({time.strftime('%Y-%m-%d %H:%M UTC', time.gmtime(release_ts))})")
        time.sleep(sleep_secs)

        url = f"{BASE}/{CURRENCY}/{INDICATOR}"
        params = {"api_key": API_KEY} if API_KEY else {}
        resp = requests.get(url, params=params, timeout=10)
        if resp.ok:
            on_release(resp.json())
        else:
            print(f"  Fetch failed: {resp.status_code}")

        # Brief pause before re-querying the calendar so the API registers the new release
        time.sleep(5)

if __name__ == "__main__":
    run()

The 5-second pause after each fetch gives the API time to register the new release before you re-query the calendar. Without it you might accidentally schedule against the release you just fetched if it still appears in the “future” slice of the calendar.


Step 5 — Track multiple indicators across multiple currencies

If you need to monitor several indicator/currency pairs simultaneously, run one scheduler per pair in a separate thread or async task. The pattern below uses Python's threading module — no external scheduler or queue is required.

import threading

WATCHES = [
    {"currency": "usd", "indicator": "cpi"},
    {"currency": "usd", "indicator": "gdp"},
    {"currency": "aud", "indicator": "policy_rate"},
    {"currency": "gbp", "indicator": "unemployment_rate"},
]

threads = []
for watch in WATCHES:
    t = threading.Thread(
        target=run,
        kwargs=watch,
        name=f"{watch['currency']}-{watch['indicator']}",
        daemon=True,
    )
    t.start()
    threads.append(t)

for t in threads:
    t.join()

Each thread independently queries the release calendar for its own currency and sleeps until its next event, so the threads do not interfere with each other. AUD and GBP endpoints require a paid API key — see the AUD policy rate docs and GBP unemployment docs for field details.


Step 6 — JavaScript / Node.js variant

The same pattern translates directly to Node.js for server-side applications or edge functions. Use the native fetch API (Node.js 18+) and setTimeout in place of time.sleep.

const BASE = "https://fxmacrodata.com/api";
const API_KEY = "YOUR_API_KEY"; // set to "" for free USD endpoints

async function nextRelease(currency, indicator) {
  const url = API_KEY
    ? `${BASE}/release_calendar/${currency}?api_key=${API_KEY}`
    : `${BASE}/release_calendar/${currency}`;
  const resp = await fetch(url);
  if (!resp.ok) throw new Error(`Calendar fetch failed: ${resp.status}`);
  const { data = [] } = await resp.json();
  const nowSec = Date.now() / 1000;
  const match = data.find(ev => ev.release === indicator && ev.announcement_datetime > nowSec);
  return match ? match.announcement_datetime : null;
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function runScheduler(currency, indicator) {
  console.log(`Scheduler started for ${currency.toUpperCase()} ${indicator}`);
  while (true) {
    const releaseSec = await nextRelease(currency, indicator);
    if (!releaseSec) {
      console.log("No release found — retrying in 24h");
      await sleep(86_400_000);
      continue;
    }

    const sleepMs = Math.max(0, (releaseSec - 2) * 1000 - Date.now());
    const releaseDate = new Date(releaseSec * 1000).toISOString();
    console.log(`Next ${indicator} release at ${releaseDate} — sleeping ${(sleepMs / 3600000).toFixed(1)}h`);
    await sleep(sleepMs);

    const indicatorUrl = API_KEY
      ? `${BASE}/${currency}/${indicator}?api_key=${API_KEY}`
      : `${BASE}/${currency}/${indicator}`;
    const data = await (await fetch(indicatorUrl)).json();
    const latest = data[data.length - 1];
    console.log(`  New value: ${latest?.value}  announced at ${latest?.announcement_datetime}`);

    await sleep(5000); // brief pause before re-querying calendar
  }
}

runScheduler("usd", "cpi");
Note on timestamp units: The announcement_datetime field in the release calendar is always in seconds (Unix epoch). The same field in the indicator response may also appear as seconds — multiply by 1 000 to get milliseconds for JavaScript Date objects.

Step 7 — Handle edge cases robustly

A production scheduler should handle three edge cases that can appear in practice:

  • Release delayed or rescheduled. Central banks and statistical offices occasionally postpone announcements. Re-query the calendar if the scheduled time passes without a new value appearing in the indicator response (add a short retry loop after waking).
  • No upcoming release in the 30-day window. The calendar only covers roughly 30 days ahead. When next_release returns None, sleep for 24 hours and try again — the next release date is usually published a few weeks in advance.
  • Network errors. Wrap your HTTP calls in a retry loop with exponential back-off. A transient 5xx or timeout should not crash the scheduler — log the error and retry after a short delay rather than re-sleeping until the next calendar entry.
import time, requests
from requests.adapters import HTTPAdapter, Retry

def make_session():
    session = requests.Session()
    retries = Retry(
        total=5,
        backoff_factor=1,
        status_forcelist=[429, 500, 502, 503, 504],
    )
    session.mount("https://", HTTPAdapter(max_retries=retries))
    return session

SESSION = make_session()

Replace the bare requests.get() calls in earlier steps with SESSION.get() to gain automatic retries at no cost.


What you built

  • ✓ Queried the release calendar to find the precise UTC announcement time for any indicator
  • ✓ Built a wait_and_fetch helper that sleeps until just before the release and fires a targeted API call
  • ✓ Assembled a continuous scheduling loop that automatically advances to the next release after each one fires
  • ✓ Scaled to multiple indicator/currency pairs using threads (Python) or concurrent async tasks (JavaScript)
  • ✓ Added robust retry handling for network errors and delayed releases

Next steps

With a lean, event-driven data feed in place there are several natural extensions:

  • Persist the fetched values to a time-series database (InfluxDB, TimescaleDB, or even a simple SQLite file) so your models can back-reference historical readings without an additional API call.
  • Compute surprises on release. Store the previous field from the indicator response alongside your consensus forecast to calculate the deviation the moment a release fires.
  • Trigger downstream actions — push the fresh reading to a Slack/Discord webhook, update a live dashboard, or submit a trade signal to your broker's API — all within seconds of the official announcement.

— The FXMacroData Team