Code Examples
Go and the CurrencyCore API
Call /v1/convert and /v1/rates from Go with net/http, a context timeout, typed structs, and error handling.
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.
package main
import (
"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:
{
"from": "USD",
"amount": 100,
"date": "2026-11-10",
"results": [{ "to": "INR", "result": 8345.0, "rate": 83.45 }]
}
{ "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 and OpenAPI specification
- Authentication and error codes
- Retries guide for backoff rules
- All developer guides