# Go and the CurrencyCore API

> Call /v1/convert and /v1/rates from Go with net/http, a context timeout, typed structs, and error handling.

Canonical: https://currency-core.com/docs/guides/go/
Updated: 2026-11-11
Published: 2026-11-11

---
There is no official Go SDK, so the `net/http` client below is the supported path. A Go client needs three things beyond the request: a context deadline, a bounded `http.Client`, and an error type that carries the API code. This example keeps all three so a caller can distinguish a bad key from a plan gate or a rate limit.

The example needs Go 1.20 or later. Free includes 1,000 requests per month at 60 per minute. Live keys require a paid plan; test keys use the same reference data and quota, capped at 60 per minute. Read the key from `CURRENCYCORE_API_KEY`, never from a flag default.

```go
package main

	"context"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/url"
	"os"
	"time"
)

type convertResponse struct {
	From    string  `json:"from"`
	Amount  float64 `json:"amount"`
	Date    string  `json:"date"`
	Results []struct {
		To     string  `json:"to"`
		Result float64 `json:"result"`
		Rate   float64 `json:"rate"`
	} `json:"results"`
}

type ratesResponse struct {
	Base  string             `json:"base"`
	Date  string             `json:"date"`
	Rates map[string]float64 `json:"rates"`
}

type APIError struct {
	Status  int
	Code    string
	Message string
}

func (e *APIError) Error() string {
	return fmt.Sprintf("%s: %s (HTTP %d)", e.Code, e.Message, e.Status)
}

var client = &http.Client{Timeout: 10 * time.Second}

func getJSON(ctx context.Context, key, path string, out any) error {
	req, err := http.NewRequestWithContext(
		ctx, http.MethodGet, "https://api.currency-core.com/v1"+path, nil,
	)
	if err != nil {
		return err
	}
	req.Header.Set("Authorization", "Bearer "+key)
	req.Header.Set("Accept", "application/json")

	res, err := client.Do(req)
	if err != nil {
		return err
	}
	defer res.Body.Close()

	body, err := io.ReadAll(res.Body)
	if err != nil {
		return err
	}

	if res.StatusCode < 200 || res.StatusCode >= 300 {
		var envelope struct {
			Error struct {
				Code    string `json:"code"`
				Message string `json:"message"`
			} `json:"error"`
		}
		if json.Unmarshal(body, &envelope) == nil && envelope.Error.Code != "" {
			return &APIError{
				Status:  res.StatusCode,
				Code:    envelope.Error.Code,
				Message: envelope.Error.Message,
			}
		}
		return &APIError{Status: res.StatusCode, Code: "http_error", Message: "unexpected response"}
	}

	return json.Unmarshal(body, out)
}

func main() {
	key := os.Getenv("CURRENCYCORE_API_KEY")
	if key == "" {
		log.Fatal("Set CURRENCYCORE_API_KEY")
	}

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	params := url.Values{"from": {"USD"}, "to": {"INR"}, "amount": {"100"}}
	var conversion convertResponse
	if err := getJSON(ctx, key, "/convert?"+params.Encode(), &conversion); err != nil {
		log.Fatal(err)
	}

	var rates ratesResponse
	if err := getJSON(ctx, key, "/rates/EUR", &rates); err != nil {
		log.Fatal(err)
	}

	fmt.Println(conversion.Results[0].Result, rates.Rates["USD"])
}
```

`/v1/convert` returns one entry per target; `/v1/rates/{base}` returns a map with the base at `1`:

```json
{
  "from": "USD",
  "amount": 100,
  "date": "2026-11-10",
  "results": [{ "to": "INR", "result": 8345.0, "rate": 83.45 }]
}
```

```json
{ "base": "EUR", "date": "2026-11-10", "rates": { "EUR": 1, "USD": 1.0861 } }
```

Use `errors.As` to branch on `*APIError`: `400` is input, `401` is auth, `403` is a plan or history window, and `429` is a limit. Retry `429` and `5xx` with backoff; do not retry the other 4xx responses.

Cache by `date` and base. Historical values are stable for normal caching, so a long TTL is reasonable. Keep the observation date. Today's snapshot should use a TTL that matches how much staleness your product can tolerate.

## Next steps

- [API reference](/docs/api/) and [OpenAPI specification](/docs/openapi/)
- [Authentication](/docs/authentication/) and [error codes](/docs/errors/)
- [Retries guide](/docs/guides/retries/) for backoff rules
- [All developer guides](/docs/guides/)
