From collection to a repeatable workflow
Monitor official economic releases
Review confirmed schedules, retrieve stored release records and connect supported alerts, without maintaining a separate publisher scraper for each series.
Check This Task's CoverageAccess and evaluation
The coverage check and this example are public. Run these subscriber examples on your own computer using Python, the requests library and an API key held in the FXMD_API_KEY environment variable. Keep keys out of browser code and shared files.
Individual is USD 50/month for one person; Business is USD 250/month for an organisation of up to 10 people. Both include the same available data. Eligible new customers can take a 14-day trial with a payment method required. Review current pricing, eligibility and cancellation terms.
No-key evaluation covers recent USD data under the free-tier limits; it does not validate full history or other currencies. Customer-facing display requires the appropriate redistribution licence.
1. Choose the series and notification path
Check the series, then open the release calendar for exact official schedules. Configure supported email, push or chat alerts in the alert guide, or use webhooks and streams for your own code. The Python example does not create alert subscriptions.
2. Inspect a bounded release window
Run the example for the required observation periods. It makes a bounded paginated read and returns stored records with a check time. Your scheduler can repeat this read; it is not a continuous listener and does not infer the next release from cadence.
3. Record changes and inspect timing
Use announcement_id to group an observation, then inspect its retained vintages to detect revisions; an unchanged ID does not imply an unchanged value. Retain response evidence and consult the release-speed record when assessing measured delivery.
Run the demonstration
Dates below are example observation windows, not assertions of available data. Choose your required dates after checking coverage.
Download economic_workflows.py
python economic_workflows.py release-monitoring --currencies usd --indicator inflation --start 2026-09-01 --end 2026-10-31
Read the complete Python example
"""Customer-side FXMacroData workflow demonstrations.
Run on your own machine with requests available and FXMD_API_KEY in your
environment. Prints JSON; does not place orders or create alert subscriptions.
"""
import argparse
import json
import os
from datetime import date, datetime, timezone
import re
import requests
BASE = "https://api.fxmacrodata.com"
def get_json(path, params):
key = os.environ.get("FXMD_API_KEY", "")
if not key:
raise RuntimeError("Set FXMD_API_KEY for these subscriber workflows.")
response = requests.get(BASE + path, params=params, headers={"X-API-Key": key}, timeout=30)
# Avoid exceptions containing an authenticated URL or response body.
if response.status_code != 200:
raise RuntimeError(f"Request failed with HTTP {response.status_code}; review access and rate limits.")
return response.json()
def history(currency, indicator, start, end, *, as_of=None):
"""Retain complete response pages and stop if a dataset changes mid-read."""
pages, offset, version = [], 0, None
while True:
params = {"start_date": start, "end_date": end, "limit": 100, "offset": offset,
"value_mode": "source", "series_mode": "raw", "revisions": "all"}
if version:
params["dataset_version"] = version
if as_of:
params.update(as_of=as_of, availability="public", revisions="latest")
page = get_json(f"/v1/announcements/{currency}/{indicator}", params)
current = page.get("dataset_version")
if not isinstance(current, str) or not current:
raise RuntimeError("Dataset-version evidence is missing; do not combine pages.")
if version is not None and current != version:
raise RuntimeError("Dataset changed during pagination; restart the download.")
version = current
if as_of and page.get("data") and page.get("data_quality", {}).get("point_in_time_safe") is not True:
raise RuntimeError("Selected history is not verified for point-in-time replay.")
if page.get("data") and (page.get("value_mode") != "source" or page.get("value_metadata", {}).get("normalization_applied") is not False):
raise RuntimeError("Source-unit values were not confirmed.")
pages.append(page)
pagination = page.get("pagination")
if not isinstance(pagination, dict) or not isinstance(pagination.get("has_more"), bool):
raise RuntimeError("Pagination evidence is missing; do not treat this download as complete.")
if not pagination["has_more"]:
break
next_offset = pagination.get("next_offset")
if not isinstance(next_offset, int) or isinstance(next_offset, bool) or next_offset <= offset:
raise RuntimeError("Pagination did not advance.")
offset = next_offset
return {"currency": currency, "indicator": indicator, "as_of": as_of,
"downloaded_at": datetime.now(timezone.utc).isoformat(),
"row_count": sum(len(p.get("data", [])) for p in pages), "pages": pages}
def inflation_history(currencies, start, end):
# Keep national measures separate: matching slugs do not establish comparability.
return [history(currency, "inflation", start, end) for currency in currencies]
def point_in_time(currency, indicator, start, end, as_of):
if not as_of:
raise ValueError("Supply an explicit timezone-aware decision cutoff with --as-of.")
cutoff = datetime.fromisoformat(as_of.replace("Z", "+00:00"))
if cutoff.tzinfo is None:
raise ValueError("The decision cutoff must include a timezone.")
return history(currency, indicator, start, end, as_of=cutoff.isoformat())
def monitor_release(currency, indicator, start, end):
# One bounded read, intended to be run by your scheduler. No inferred next dates.
result = history(currency, indicator, start, end)
return {"checked_at": datetime.now(timezone.utc).isoformat(),
"records": result,
"interpretation": "Inspect announcement_id, retained vintages and publication evidence. An empty range is not proof that no release occurred; this read is not a latency measurement."}
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("workflow", choices=["inflation-history", "point-in-time", "release-monitoring"])
parser.add_argument("--currencies", default="usd,eur,gbp")
parser.add_argument("--indicator", default="inflation")
parser.add_argument("--start", required=True)
parser.add_argument("--end", required=True)
parser.add_argument("--as-of")
args = parser.parse_args()
currencies = [code.strip().lower() for code in args.currencies.split(",")]
if any(len(code) != 3 or not code.isascii() or not code.isalpha() for code in currencies):
parser.error("Use three-letter currency codes separated by commas.")
try:
if date.fromisoformat(args.start) > date.fromisoformat(args.end):
parser.error("The observation start must be on or before the end.")
except ValueError:
parser.error("Use valid YYYY-MM-DD observation dates.")
if not re.fullmatch(r"[a-z][a-z0-9_]{0,79}", args.indicator):
parser.error("Choose a valid indicator slug.")
if args.workflow != "inflation-history" and len(currencies) != 1:
parser.error("Choose one currency for a decision cutoff or monitoring read.")
if args.workflow == "inflation-history":
result = inflation_history(currencies, args.start, args.end)
elif args.workflow == "point-in-time":
result = point_in_time(currencies[0], args.indicator, args.start, args.end, args.as_of)
else:
result = monitor_release(currencies[0], args.indicator, args.start, args.end)
print(json.dumps(result, indent=2))
if __name__ == "__main__":
main()
What the output contains
A JSON object with checked_at and records, including announcement IDs, available values, retained revisions and timing metadata. The check time records when the client ran; it is not the publisher's actual release time.
Interpret the result
Display timing in this order: scheduled release time; actual publication bracket (published_after / published_by); database availability (available_at); latency from actual publication to database availability. If actual publication was not captured, latency is not measured. An empty lookup does not establish that no release occurred.