LearnTutorial

Get exchange rates in Go with net/http

Build a small, typed Go client for exchangerate.dev with net/http, context timeouts, environment-based authentication, and explicit freshness fields.

8 Sep 2026 · 6 min read

The standard library is enough for a reliable Go integration. Keep the API key in the process environment, reject non-2xx responses before decoding JSON, and retain source, market_session, and data_updated_at beside every rate.

  • Use net/http with a bounded context instead of an unbounded request.
  • Decode rates into a typed struct and preserve the observation metadata.
  • Send the bearer key from EXCHANGERATE_API_KEY; never commit it.
  • Treat 401, 429, and other non-2xx responses as errors before decoding.

Create a typed client with net/http

Save this complete program as main.go. It requests three symbols in one call and preserves numeric tokens as json.Number; use an exact-decimal library before doing money arithmetic. The anonymous request is useful for evaluation; set EXCHANGERATE_API_KEY when the application has a key. context.WithTimeout prevents a stalled network connection from holding a worker forever.

go · main.go
package mainimport (  "context"  "encoding/json"  "fmt"  "net/http"  "log"  "os"  "regexp"  "strings"  "time")type Latest struct {  Base          string             `json:"base"`  Source        string             `json:"source"`  MarketSession string             `json:"market_session"`  UpdatedAt     time.Time          `json:"data_updated_at"`  Sources       map[string]string  `json:"sources"`  EffectiveAt   map[string]string  `json:"effective_at"`  Rates         map[string]json.Number `json:"rates"`}func LatestRates(ctx context.Context, base string) (Latest, error) {  base = strings.ToUpper(base)  if !regexp.MustCompile("^[A-Z]{3}$").MatchString(base) {    return Latest{}, fmt.Errorf("invalid base currency: %q", base)  }  url := "https://api.exchangerate.dev/v1/latest/" + base + "?symbols=EUR,GBP,JPY"  req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)  if err != nil { return Latest{}, err }  if key := os.Getenv("EXCHANGERATE_API_KEY"); key != "" {    req.Header.Set("Authorization", "Bearer "+key)  }  client := &http.Client{Timeout: 5 * time.Second, CheckRedirect: func(_ *http.Request, _ []*http.Request) error {    return http.ErrUseLastResponse  }}  res, err := client.Do(req)  if err != nil { return Latest{}, err }  defer res.Body.Close()  if res.StatusCode < 200 || res.StatusCode >= 300 {    return Latest{}, fmt.Errorf("exchange rate API: HTTP %s", res.Status)  }  var out Latest  if err := json.NewDecoder(res.Body).Decode(&out); err != nil { return Latest{}, err }  return out, nil}func main() {  ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)  defer cancel()  latest, err := LatestRates(ctx, "USD")  if err != nil { log.Fatal(err) }  fmt.Println(latest.Rates["EUR"], latest.Sources["EUR"], latest.EffectiveAt["EUR"])}

Call it with a timeout

shell · run main.go
go run main.go

Handle errors and freshness explicitly

Do not decode an error body as if it were a successful rate response. A 401 means the key or authorization header needs correction. A 429 means the caller should slow down and respect its plan. Retry only bounded, transient failures such as a network timeout or selected 5xx responses, with backoff.

Keep the rate and its meaning together

Persist source, market_session, and data_updated_at with the number. All 144 currencies update about every 60 seconds on trading days; ECB fixes provide daily fallback.

  • Reuse an http.Client with an explicit transport timeout in production.
  • Cache only successful responses.
  • Use decimal arithmetic for money calculations at the application boundary.
  • Never expose EXCHANGERATE_API_KEY in browser or mobile builds.

Questions

Does this Go example require an SDK?

No. It uses Go’s standard net/http, encoding/json, context, and os packages.

Where should the API key live?

Set EXCHANGERATE_API_KEY in the server or worker environment. Do not commit it or ship it in a browser or mobile application.

What should happen after HTTP 429?

Stop treating the response as rate data, back off, reduce duplicate polling, and respect the plan limit.

Can I use these rates for settlement?

No. They are indicative data for applications, estimates, reporting, and analysis, not executable or regulated settlement prices.

Keep reading

Start with a free API key

5,000 requests a month on the free plan. No credit card.

Get a free key