How To Use The Fxmacrodata Api With Go banner image

Implementation

How-To Guides

How To Use The Fxmacrodata Api With Go

A step-by-step guide to calling the FXMacroData REST API from Go — covering net/http client setup, query-parameter authentication, JSON decoding, and reusable helpers for announcements, the release calendar, and FX spot rates.

Go is increasingly popular for financial tooling — its fast compilation, predictable performance, and first-class concurrency primitives make it a natural fit for data pipelines and trading automation. This guide walks through everything you need to call the FXMacroData REST API from Go, from a simple one-off request to a reusable client that handles timeouts, JSON decoding, and optional date filtering. By the end you will have idiomatic Go code that fetches macro indicator announcements, pulls an upcoming-release calendar, and queries FX spot rates — all in fewer than 150 lines.

What You Will Build

A lightweight Go HTTP client that authenticates against the FXMacroData REST API using query-parameter API-key auth, decodes structured JSON responses into typed Go structs, and exposes reusable helper functions for the three most common endpoint families: announcements, release calendar, and FX spot rates.

Prerequisites

  • Go 1.21 or later installed (go.dev/dl)
  • A FXMacroData API key — sign up at /subscribe to receive one
  • Basic familiarity with Go modules and the standard library

No third-party HTTP or JSON libraries are needed — the standard net/http and encoding/json packages handle everything.

Step 1 — Understand the API Shape

Every FXMacroData indicator endpoint follows the same URL pattern:

GET https://fxmacrodata.com/api/v1/announcements/{currency}/{indicator}?api_key=YOUR_API_KEY

The response is a JSON object with a top-level data array. Each element contains a date string, a numeric val, and optionally an announcement_datetime that gives second-level precision for when the figure was officially released. For example:

curl "https://fxmacrodata.com/api/v1/announcements/usd/wages?api_key=YOUR_API_KEY&start=2024-01-01"
{
  "data": [
    { "date": "2025-03-14", "val": 4.0, "announcement_datetime": "2025-03-14T12:30:00Z" },
    { "date": "2025-02-07", "val": 4.1, "announcement_datetime": "2025-02-07T13:30:00Z" },
    { "date": "2025-01-10", "val": 3.9, "announcement_datetime": "2025-01-10T13:30:00Z" }
  ]
}

The same envelope structure applies to the calendar and forex endpoints, making it straightforward to write a single generic decoder and specialise per-endpoint types on top of it.

Step 2 — Initialise Your Module

Create a new directory for the project and initialise a Go module:

mkdir fxmd-go && cd fxmd-go
go mod init fxmd

Store your API key in an environment variable rather than hard-coding it. You can export it for the current shell session:

export FXMD_API_KEY="your_actual_api_key_here"

🔒 Security tip

Never hard-code API keys in source files or commit them to version control. In production use a secrets manager or a CI/CD environment variable. The pattern shown here reads the key at startup and fails fast if the variable is missing.

Step 3 — Define Response Types

Create a file called fxmd.go. Start by defining the Go structs that map to the JSON shapes returned by the three endpoint families you will use:

package main

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"time"
)

const baseURL = "https://fxmacrodata.com/api/v1"

// DataPoint represents a single indicator observation.
type DataPoint struct {
	Date                string  `json:"date"`
	Val                 float64 `json:"val"`
	AnnouncementDatetime string  `json:"announcement_datetime,omitempty"`
}

// AnnouncementsResponse is the envelope for indicator announcement endpoints.
type AnnouncementsResponse struct {
	Data []DataPoint `json:"data"`
}

// CalendarEvent represents one upcoming or recent release on the calendar.
type CalendarEvent struct {
	Date      string  `json:"date"`
	Indicator string  `json:"indicator"`
	Expected  float64 `json:"expected,omitempty"`
	Prior     float64 `json:"prior,omitempty"`
	Actual    float64 `json:"actual,omitempty"`
}

// CalendarResponse is the envelope for the release calendar endpoint.
type CalendarResponse struct {
	Data []CalendarEvent `json:"data"`
}

// ForexPoint represents a single FX spot-rate observation.
type ForexPoint struct {
	Date string  `json:"date"`
	Rate float64 `json:"rate"`
}

// ForexResponse is the envelope for the FX spot-rate endpoint.
type ForexResponse struct {
	Data []ForexPoint `json:"data"`
}

Step 4 — Build a Reusable Client

A thin wrapper around net/http keeps boilerplate out of the calling code. The client reads the API key once at construction, injects it as a query parameter on every request, and enforces a timeout so a slow upstream can never stall your program indefinitely:

// Client is a lightweight FXMacroData API client.
type Client struct {
	apiKey     string
	httpClient *http.Client
}

// NewClient creates a Client that reads the API key from the FXMD_API_KEY environment variable.
// It panics at startup if the variable is not set — fail-fast is safer than silent empty results.
func NewClient() *Client {
	key := os.Getenv("FXMD_API_KEY")
	if key == "" {
		panic("FXMD_API_KEY environment variable is not set")
	}
	return &Client{
		apiKey: key,
		httpClient: &http.Client{
			Timeout: 15 * time.Second,
		},
	}
}

// get performs a GET request to the given path with optional query parameters.
// It decodes the JSON body into dest.
func (c *Client) get(path string, params url.Values, dest any) error {
	if params == nil {
		params = url.Values{}
	}
	params.Set("api_key", c.apiKey)

	u := baseURL + path + "?" + params.Encode()
	resp, err := c.httpClient.Get(u)
	if err != nil {
		return fmt.Errorf("http get %s: %w", path, err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(resp.Body)
		return fmt.Errorf("api error %d on %s: %s", resp.StatusCode, path, body)
	}

	if err := json.NewDecoder(resp.Body).Decode(dest); err != nil {
		return fmt.Errorf("json decode %s: %w", path, err)
	}
	return nil
}

Using json.NewDecoder on the response body (rather than reading the body into a byte slice first) is the idiomatic approach in Go: it streams the decoding directly from the HTTP connection, avoiding an intermediate allocation for large payloads.

Step 5 — Add Typed Helpers for Each Endpoint Family

Wrap the generic get method with typed helpers so callers never have to construct paths or decode envelopes manually:

// GetAnnouncements retrieves indicator announcement data for the given currency and indicator slug.
// start and end are optional date strings in "YYYY-MM-DD" format; pass "" to omit them.
//
// Example: client.GetAnnouncements("usd", "wages", "2024-01-01", "")
// Full indicator catalogue: https://fxmacrodata.com/api-data-docs/usd/wages
func (c *Client) GetAnnouncements(currency, indicator, start, end string) (*AnnouncementsResponse, error) {
	params := url.Values{}
	if start != "" {
		params.Set("start", start)
	}
	if end != "" {
		params.Set("end", end)
	}
	var out AnnouncementsResponse
	err := c.get("/announcements/"+currency+"/"+indicator, params, &out)
	return &out, err
}

// GetCalendar retrieves the upcoming release calendar for the given currency code.
//
// Example: client.GetCalendar("usd")
func (c *Client) GetCalendar(currency string) (*CalendarResponse, error) {
	var out CalendarResponse
	err := c.get("/calendar/"+currency, nil, &out)
	return &out, err
}

// GetForex retrieves FX spot-rate history for a currency pair.
// start and end are optional date strings in "YYYY-MM-DD" format.
//
// Example: client.GetForex("eur", "usd", "2024-01-01", "")
func (c *Client) GetForex(base, quote, start, end string) (*ForexResponse, error) {
	params := url.Values{}
	if start != "" {
		params.Set("start", start)
	}
	if end != "" {
		params.Set("end", end)
	}
	var out ForexResponse
	err := c.get("/forex/"+base+"/"+quote, params, &out)
	return &out, err
}

Step 6 — Wire It All Together in main

With the client and helpers in place, calling the API is concise and type-safe. Add a main function to fxmd.go that exercises all three endpoint families:

func main() {
	client := NewClient()

	// --- Announcements: US wages ---
	fmt.Println("=== US Wages (last 5 releases) ===")
	wages, err := client.GetAnnouncements("usd", "wages", "2024-01-01", "")
	if err != nil {
		fmt.Println("error:", err)
	} else {
		for i, pt := range wages.Data {
			if i >= 5 {
				break
			}
			fmt.Printf("  %s  val=%.2f  released=%s\n", pt.Date, pt.Val, pt.AnnouncementDatetime)
		}
	}

	// --- Release calendar: upcoming USD events ---
	fmt.Println("\n=== Upcoming USD Releases ===")
	cal, err := client.GetCalendar("usd")
	if err != nil {
		fmt.Println("error:", err)
	} else {
		for i, ev := range cal.Data {
			if i >= 5 {
				break
			}
			fmt.Printf("  %s  %s  prior=%.2f  expected=%.2f\n",
				ev.Date, ev.Indicator, ev.Prior, ev.Expected)
		}
	}

	// --- FX spot rates: EUR/USD ---
	fmt.Println("\n=== EUR/USD Spot Rate (last 5 days) ===")
	fx, err := client.GetForex("eur", "usd", "2025-01-01", "")
	if err != nil {
		fmt.Println("error:", err)
	} else {
		for i, pt := range fx.Data {
			if i >= 5 {
				break
			}
			fmt.Printf("  %s  rate=%.5f\n", pt.Date, pt.Rate)
		}
	}
}

Run the program:

go run fxmd.go

You should see output similar to:

=== US Wages (last 5 releases) ===
  2025-03-14  val=4.00  released=2025-03-14T12:30:00Z
  2025-02-07  val=4.10  released=2025-02-07T13:30:00Z
  2025-01-10  val=3.90  released=2025-01-10T13:30:00Z
  2024-12-06  val=4.00  released=2024-12-06T13:30:00Z
  2024-11-01  val=4.00  released=2024-11-01T12:30:00Z

=== Upcoming USD Releases ===
  2025-04-25  gdp  prior=2.30  expected=0.40
  2025-05-02  non_farm_payrolls  prior=228.00  expected=135.00
  2025-05-13  inflation  prior=2.40  expected=2.30

=== EUR/USD Spot Rate (last 5 days) ===
  2025-04-17  rate=1.13452
  2025-04-16  rate=1.13680
  2025-04-15  rate=1.13590
  2025-04-14  rate=1.13320
  2025-04-11  rate=1.13580

Tip — Concurrent Fetches with goroutines

Because the client is safe for concurrent use, you can fan out multiple indicator requests in parallel using goroutines and sync.WaitGroup or errgroup. This is especially useful when building a dashboard that pulls several currencies at once — wall-clock time stays near the latency of the slowest single request rather than the sum of all requests.

Step 7 — Handle Pagination and Date Ranges

By default the announcements endpoint returns all available history. For long-running series like M2 money supply or GDP you will often want to limit the window using start and end query parameters — both helpers already support them. Here is a focused example that fetches only the most recent quarter of US part-time employment data:

// Fetch US part-time employment for the last 90 days
end := time.Now().Format("2006-01-02")
start := time.Now().AddDate(0, 0, -90).Format("2006-01-02")

pt, err := client.GetAnnouncements("usd", "part_time_employment", start, end)
if err != nil {
    log.Fatal(err)
}
for _, dp := range pt.Data {
    fmt.Printf("%s  %.0f\n", dp.Date, dp.Val)
}

Go's standard time package formats dates as "2006-01-02" (the reference time is a mnemonic: 01/02 03:04:05 PM '06 -0700), which matches the ISO-8601 date strings expected by the API.

Step 8 — Parse announcement_datetime as a time.Time

The announcement_datetime field is a UTC RFC-3339 string. If your application needs to compute time-to-release countdowns, sort events, or bucket data by session, parse it into a time.Time value:

const rfc3339 = time.RFC3339

for _, dp := range wages.Data {
    if dp.AnnouncementDatetime == "" {
        continue
    }
    t, err := time.Parse(rfc3339, dp.AnnouncementDatetime)
    if err != nil {
        log.Printf("bad datetime %q: %v", dp.AnnouncementDatetime, err)
        continue
    }
    until := time.Until(t)
    fmt.Printf("%s  val=%.2f  in %s\n", dp.Date, dp.Val, until.Round(time.Minute))
}

Complete File Listing

For reference, here is the complete single-file implementation described in the steps above:

package main

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"time"
)

const baseURL = "https://fxmacrodata.com/api/v1"

type DataPoint struct {
	Date                string  `json:"date"`
	Val                 float64 `json:"val"`
	AnnouncementDatetime string  `json:"announcement_datetime,omitempty"`
}

type AnnouncementsResponse struct {
	Data []DataPoint `json:"data"`
}

type CalendarEvent struct {
	Date      string  `json:"date"`
	Indicator string  `json:"indicator"`
	Expected  float64 `json:"expected,omitempty"`
	Prior     float64 `json:"prior,omitempty"`
	Actual    float64 `json:"actual,omitempty"`
}

type CalendarResponse struct {
	Data []CalendarEvent `json:"data"`
}

type ForexPoint struct {
	Date string  `json:"date"`
	Rate float64 `json:"rate"`
}

type ForexResponse struct {
	Data []ForexPoint `json:"data"`
}

type Client struct {
	apiKey     string
	httpClient *http.Client
}

func NewClient() *Client {
	key := os.Getenv("FXMD_API_KEY")
	if key == "" {
		panic("FXMD_API_KEY environment variable is not set")
	}
	return &Client{
		apiKey: key,
		httpClient: &http.Client{Timeout: 15 * time.Second},
	}
}

func (c *Client) get(path string, params url.Values, dest any) error {
	if params == nil {
		params = url.Values{}
	}
	params.Set("api_key", c.apiKey)
	u := baseURL + path + "?" + params.Encode()
	resp, err := c.httpClient.Get(u)
	if err != nil {
		return fmt.Errorf("http get %s: %w", path, err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(resp.Body)
		return fmt.Errorf("api error %d on %s: %s", resp.StatusCode, path, body)
	}
	return json.NewDecoder(resp.Body).Decode(dest)
}

func (c *Client) GetAnnouncements(currency, indicator, start, end string) (*AnnouncementsResponse, error) {
	params := url.Values{}
	if start != "" { params.Set("start", start) }
	if end != ""   { params.Set("end",   end)   }
	var out AnnouncementsResponse
	return &out, c.get("/announcements/"+currency+"/"+indicator, params, &out)
}

func (c *Client) GetCalendar(currency string) (*CalendarResponse, error) {
	var out CalendarResponse
	return &out, c.get("/calendar/"+currency, nil, &out)
}

func (c *Client) GetForex(base, quote, start, end string) (*ForexResponse, error) {
	params := url.Values{}
	if start != "" { params.Set("start", start) }
	if end != ""   { params.Set("end",   end)   }
	var out ForexResponse
	return &out, c.get("/forex/"+base+"/"+quote, params, &out)
}

func main() {
	client := NewClient()

	wages, err := client.GetAnnouncements("usd", "wages", "2024-01-01", "")
	if err != nil {
		fmt.Println("wages error:", err)
	} else {
		fmt.Println("=== US Wages ===")
		for i, pt := range wages.Data {
			if i >= 5 { break }
			fmt.Printf("  %s  %.2f\n", pt.Date, pt.Val)
		}
	}

	cal, err := client.GetCalendar("usd")
	if err != nil {
		fmt.Println("calendar error:", err)
	} else {
		fmt.Println("=== Upcoming USD Releases ===")
		for i, ev := range cal.Data {
			if i >= 5 { break }
			fmt.Printf("  %s  %s\n", ev.Date, ev.Indicator)
		}
	}

	fx, err := client.GetForex("eur", "usd", "2025-01-01", "")
	if err != nil {
		fmt.Println("forex error:", err)
	} else {
		fmt.Println("=== EUR/USD ===")
		for i, pt := range fx.Data {
			if i >= 5 { break }
			fmt.Printf("  %s  %.5f\n", pt.Date, pt.Rate)
		}
	}
}

What's Next

You now have a working Go client that covers the three most common FXMacroData endpoint families. A few natural next steps:

  • Extend the struct definitions to capture additional fields such as revised values from the calendar endpoint.
  • Add an errgroup-based fan-out to fetch multiple currencies concurrently — the Client is safe to share across goroutines because http.Client is concurrency-safe by design.
  • Persist results to a local SQLite database using database/sql for lightweight backtesting or signal research.
  • Explore the full indicator catalogue for USD at /api-data-docs/usd/wages and for other currencies across the dashboard — look for M2, part-time employment, and GDP as starting points given the demand signals that motivated this guide.