Send a pre-release alert to a trading desk or on-call team

A desk that trims risk, an on-call engineer who pauses jobs and a shift lead who briefs staff need the same message before a release: the time in UTC and local time, the period it covers and the previous value. The Narwhal economic calendar returns all four, and a short scheduled script turns them into one line per release.

What a pre-release alert has to carry

Before a major number a desk trims risk, an on-call engineer pauses batch jobs and a shift lead briefs staff. All three act on the same facts: when the release lands, which period it covers and what the last figure was. Staff in another city also need the time in their own zone, because a message that says 13:30 UTC leaves them doing the sum under pressure. GET /v1/economics/calendar returns each of those facts. This request asks for the United States on 10 November.

curl, US releases on one day
curl -s -H "Authorization: Bearer $NARWHAL_API_KEY" \
  "https://api.narwhalapi.com/v1/economics/calendar?countries=USA&start_date=2026-11-10&end_date=2026-11-10&limit=100"
Response
{
  "releases": [
    {
      "release_id": "66f7f9f6-0318-4f25-8896-541c428ee07e",
      "revision": 2,
      "country": "USA",
      "series_id": "usa-wages-and-earnings",
      "name": "Wages and earnings",
      "topic": "wages",
      "family": "labour",
      "period": "2026-10",
      "status": "scheduled",
      "at": "2026-11-10T13:30:00Z",
      "timezone": "America/New_York",
      "values": [
        {
          "measure": "real_average_hourly_earnings_mom",
          "concept": "real_average_hourly_earnings",
          "unit": "percent",
          "transformation": "month_over_month",
          "previous": "-0.5"
        },
        {
          "measure": "real_average_hourly_earnings_yoy",
          "concept": "real_average_hourly_earnings",
          "unit": "percent",
          "transformation": "year_over_year",
          "previous": "-0.7"
        }
      ]
    },
    {
      "release_id": "f01e2289-2e17-49b3-9d64-d29118742dee",
      "revision": 3,
      "country": "USA",
      "series_id": "usa-consumer-prices",
      "name": "Consumer prices",
      "topic": "consumer_prices",
      "family": "prices",
      "period": "2026-10",
      "status": "scheduled",
      "at": "2026-11-10T13:30:00Z",
      "timezone": "America/New_York",
      "values": [
        {
          "measure": "core_cpi_yoy",
          "concept": "core_cpi",
          "unit": "percent",
          "transformation": "year_over_year",
          "previous": "2.6",
          "not_seasonally_adjusted": true
        },
        {
          "measure": "cpi_mom",
          "concept": "cpi",
          "unit": "percent",
          "transformation": "month_over_month",
          "previous": "0.6"
        },
        {
          "measure": "cpi_yoy",
          "concept": "cpi",
          "unit": "percent",
          "transformation": "year_over_year",
          "previous": "3.7",
          "not_seasonally_adjusted": true
        }
      ]
    }
  ],
  "next_cursor": null
}

at is the release time in UTC. period names the month the figures cover, and values[].previous holds the last published figure for each measure, with its unit. The response lists the previous figure and carries no forecast, so a desk that wants a consensus number adds it from its own source. status stays scheduled until the figures are out.

Write one alert line per release

The script below is meant to run on a schedule, for example every morning, and prints one line for each release due in the next 24 hours.

  1. Set the desk zone and the countries DESK_TZ is the zone the staff read the message in, and COUNTRIES lists the economies the desk trades or depends on.
  2. Ask for a padded date window The date filter follows each publisher's local date, so the script asks from one day before to two days after today and filters on at itself.
  3. Keep what is still ahead A release is kept when its status is scheduled, it has an at time and that time falls between now and the lead time. NOW is an optional variable that lets you test with a fixed clock.
  4. Convert the time and add the previous figure astimezone turns the UTC time into the desk zone, so a clock change in either place needs no hand-made offset. The previous figures come from values.
    Python, a pre-release alert line
    import os
    from datetime import datetime, timedelta, timezone
    from zoneinfo import ZoneInfo
    import requests
    
    DESK_TZ = ZoneInfo("Europe/Zurich")
    COUNTRIES = ["USA", "EMU"]
    LEAD = timedelta(hours=24)
    now = datetime.fromisoformat(os.environ["NOW"]) if "NOW" in os.environ else datetime.now(timezone.utc)
    
    r = requests.get(
        "https://api.narwhalapi.com/v1/economics/calendar",
        params={
            "countries": COUNTRIES,
            "start_date": (now - timedelta(days=1)).date().isoformat(),
            "end_date": (now + timedelta(days=2)).date().isoformat(),
            "limit": 100,
        },
        headers={"Authorization": f"Bearer {os.environ['NARWHAL_API_KEY']}"},
        timeout=10,
    )
    r.raise_for_status()
    
    due = []
    for x in r.json()["releases"]:
        if x["status"] != "scheduled" or "at" not in x:
            continue
        at = datetime.fromisoformat(x["at"].replace("Z", "+00:00"))
        if now <= at < now + LEAD:
            due.append((at, x))
    
    for at, x in sorted(due, key=lambda t: t[0]):
        prev = ", ".join(f"{v['measure']} {v['previous']}" for v in x["values"] if "previous" in v)
        print(f"{at:%H:%M} UTC / {at.astimezone(DESK_TZ):%H:%M} {DESK_TZ.key} | "
              f"{x['country']} {x['name']} {x.get('period', '-')} | previous: {prev or 'none listed'}")

With NOW set to 09:00 UTC on 10 November, the script printed two lines. Zurich is one hour ahead of UTC on that date, which is why 13:30 UTC reads 14:30. The two US releases share one release time, so a desk that wants a single message can group the lines by their at value.

Output with NOW set to 2026-11-10T09:00:00+00:00
13:30 UTC / 14:30 Europe/Zurich | USA Wages and earnings 2026-10 | previous: real_average_hourly_earnings_mom -0.5, real_average_hourly_earnings_yoy -0.7
13:30 UTC / 14:30 Europe/Zurich | USA Consumer prices 2026-10 | previous: core_cpi_yoy 2.6, cpi_mom 0.6, cpi_yoy 3.7

Match the alert to the person who reads it

ReaderAction before the releaseWhat the alert needs
TraderTrim or hedge a positionUTC and local time, the previous figure
On-call engineerPause a deploy or a batch job around the minuteThe exact at time, and status to confirm it is still scheduled
Shift leadBrief staff on what is comingRelease name, period and the previous figure

Send the lines through whatever channel the team already watches. The API supplies the facts, and your own chat or mail tool delivers them. Run the script from a scheduler as in schedule a daily data pull, at least as often as the lead time you want.

Keep the alert true when a release moves

A release time can change after you sent the morning list. Each calendar item has a revision that rises when its details change, so a script that re-reads the calendar shortly before the release can compare the revision with the one it sent. For a push, webhooks report release.scheduled, release.rescheduled and release.cancelled; see receive webhooks. A webhook arrives when the schedule changes and carries no countdown, so the lead time still comes from your own timer.

Staff who only want a phone reminder can subscribe a calendar app to a feed created with POST /v1/calendars/subscriptions. The request takes countries, start_date and end_date, and the response has a url that is returned once, so store it as a secret. That call creates something, so this page describes it and does not run it. The calendar subscriptions documentation has the full request.

What if the release has no exact time yet?

Some releases carry date, between or by instead of at. The script skips them because it cannot place them on a clock. List them in a separate morning note so the desk knows a release may come without a fixed minute.

Can the API send the reminder itself?

No endpoint sends a timed reminder. The reminder comes from a calendar app, from a scheduled script of yours or from a webhook consumer that starts a timer of its own.

Why does the script ask for dates around today?

The date window follows the publisher's local date. A release in Tokyo or Sydney can sit on a different calendar date than its UTC time, so the request is padded and at decides what is due.

Published .

Next steps

Get these figures through the API

5,000 free requests every month. No card needed.