How to Get Oil Prices in Go (Golang API Tutorial)

11 min read

Fetch Brent, WTI, and natural gas prices in idiomatic Go using the official OilPriceAPI SDK — then keep fetching them, on a schedule, with a production ticker poller that logs source-timestamped prices to CSV. Every example on this page was compiled and run against the live API.

Why Go for Commodity Data?

Go is what you reach for when the integration has to stay up: pricing services, fuel-surcharge calculators, market-data collectors that run for months. The official OilPriceAPI Go SDK fits that: standard library only (plus gorilla/websocket for streaming), full context.Context support, typed errors, and automatic retries with exponential backoff.

What You'll Build

  • ✓ Fetch live demo prices with zero setup (no API key)
  • ✓ Pull a week of Brent history with your key
  • ✓ Read the latest spot price with its source timestamp
  • ✓ A ticker-based poller that appends prices to CSV on an interval — ready for systemd, Docker, or cron

A note on freshness: every API response carries its own created_at/updated_at source timestamps. Spot prices update at market cadence during trading hours; series like retail diesel update daily or weekly. Persist those timestamps — the poller below does.

Step 1: Install the SDK

mkdir oilprices && cd oilprices
go mod init oilprices
go get github.com/OilpriceAPI/oilpriceapi-go

The module path is github.com/OilpriceAPI/oilpriceapi-go (v1.3+ at the time of writing). API reference on pkg.go.dev.

Step 2: First Prices — No API Key Needed

The SDK ships a keyless demo endpoint, so you can see real prices before signing up:

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/OilpriceAPI/oilpriceapi-go"
)

func main() {
    // Demo endpoint - no API key needed!
    client := oilpriceapi.NewClient("")

    prices, err := client.GetDemoPrices(context.Background())
    if err != nil {
        log.Fatal(err)
    }

    for _, p := range prices.Data.Prices {
        fmt.Printf("%s: $%.2f %s\n", p.Name, p.Price, p.Currency)
    }
}

Running this returns nine live demo commodities (Brent, WTI, diesel, gasoline, natural gas, gold, heating oil, EUR/USD, GBP/USD).

Get Your API Key

For the full catalog and history, sign up at oilpriceapi.com — 7-day free trial, then a free tier of 200 requests per month. Export it as an environment variable rather than hardcoding it:

export OILPRICEAPI_KEY="your_api_key_here"

Step 3: Historical Prices with Your Key

GetHistoricalPrices takes the commodity code plus a fixed lookback period (day, week, month, year) or a custom date range:

package main

import (
    "context"
    "fmt"
    "log"
    "os"
    "time"

    "github.com/OilpriceAPI/oilpriceapi-go"
)

func main() {
    client := oilpriceapi.NewClient(os.Getenv("OILPRICEAPI_KEY"),
        oilpriceapi.WithTimeout(10*time.Second),
        oilpriceapi.WithRetries(3),
    )

    // Past week of Brent prices
    hist, err := client.GetHistoricalPrices(context.Background(),
        "BRENT_CRUDE_USD",
        oilpriceapi.WithPeriod("week"),
    )
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("data points: %d\n", len(hist.Data.Prices))
    first := hist.Data.Prices[0]
    fmt.Printf("first: $%.2f at %s (source %s)\n",
        first.Price, first.CreatedAt, first.Source)
}

Verified against the live API: this returned 100 data points for the past week, each with its own source timestamp, e.g. $71.81 at 2026-07-05T22:06:56Z.

For custom ranges, use WithStartDate, WithEndDate, and WithInterval("daily") for daily aggregation.

Need this data in your app?

GET /v1/prices/latest?by_code=BRENT_CRUDE_USD timestamped JSON
Free API Key

Step 4: Latest Spot Price

The /v1/prices/latest endpoint returns a single price envelope. The cleanest way to consume it in Go is the SDK's Raw escape hatch with a small struct — you keep the client's auth, retries, and typed errors:

package main

import (
    "context"
    "fmt"
    "log"
    "net/url"
    "os"
    "time"

    "github.com/OilpriceAPI/oilpriceapi-go"
)

// latestPrice matches the /v1/prices/latest response envelope.
type latestPrice struct {
    Status string `json:"status"`
    Data   struct {
        Price     float64 `json:"price"`
        Currency  string  `json:"currency"`
        Code      string  `json:"code"`
        CreatedAt string  `json:"created_at"`
        Unit      string  `json:"unit"`
        Source    string  `json:"source"`
    } `json:"data"`
}

func main() {
    client := oilpriceapi.NewClient(os.Getenv("OILPRICEAPI_KEY"),
        oilpriceapi.WithTimeout(10*time.Second),
    )

    var out latestPrice
    err := client.Raw(context.Background(), "GET", "/v1/prices/latest",
        url.Values{"by_code": {"BRENT_CRUDE_USD"}}, &out)
    if err != nil {
        log.Fatal(err)
    }

    d := out.Data
    fmt.Printf("%s: $%.2f per %s (as of %s, source %s)\n",
        d.Code, d.Price, d.Unit, d.CreatedAt, d.Source)
}

Verified output: BRENT_CRUDE_USD: $71.80 per barrel (as of 2026-07-05T22:14:26Z, source oilprice.ft)

Production: A Scheduled Price Poller

A one-off fmt.Println isn't an integration. Here's the production ending: a long-running poller on a time.Ticker that appends every reading to CSV — both your polled-at time and the API's own source timestamp — with graceful shutdown on SIGINT/SIGTERM. Run it under systemd, in a container, or swap the loop for a single fetch and let cron do the scheduling.

package main

import (
    "context"
    "encoding/csv"
    "fmt"
    "log"
    "net/url"
    "os"
    "os/signal"
    "strconv"
    "syscall"
    "time"

    "github.com/OilpriceAPI/oilpriceapi-go"
)

// latestPrice matches the /v1/prices/latest response envelope.
type latestPrice struct {
    Status string `json:"status"`
    Data   struct {
        Price     float64 `json:"price"`
        Currency  string  `json:"currency"`
        Code      string  `json:"code"`
        CreatedAt string  `json:"created_at"`
        Unit      string  `json:"unit"`
        Source    string  `json:"source"`
    } `json:"data"`
}

func fetchAndAppend(ctx context.Context, client *oilpriceapi.Client,
    w *csv.Writer, code string) error {
    var out latestPrice
    err := client.Raw(ctx, "GET", "/v1/prices/latest",
        url.Values{"by_code": {code}}, &out)
    if err != nil {
        return err
    }
    d := out.Data
    if err := w.Write([]string{
        time.Now().UTC().Format(time.RFC3339), // polled_at
        d.Code,
        strconv.FormatFloat(d.Price, 'f', 2, 64),
        d.Currency,
        d.Unit,
        d.CreatedAt, // source timestamp from the API
        d.Source,
    }); err != nil {
        return err
    }
    w.Flush()
    log.Printf("%s $%.2f (source ts %s)", d.Code, d.Price, d.CreatedAt)
    return w.Error()
}

func main() {
    interval := 15 * time.Minute
    if v := os.Getenv("POLL_INTERVAL"); v != "" {
        var err error
        if interval, err = time.ParseDuration(v); err != nil {
            log.Fatalf("bad POLL_INTERVAL: %v", err)
        }
    }

    client := oilpriceapi.NewClient(os.Getenv("OILPRICEAPI_KEY"),
        oilpriceapi.WithTimeout(10*time.Second),
        oilpriceapi.WithRetries(3),
    )

    f, err := os.OpenFile("brent_prices.csv",
        os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
    if err != nil {
        log.Fatal(err)
    }
    defer f.Close()

    w := csv.NewWriter(f)
    if info, _ := f.Stat(); info != nil && info.Size() == 0 {
        w.Write([]string{"polled_at", "code", "price", "currency",
            "unit", "source_created_at", "source"})
        w.Flush()
    }

    ctx, stop := signal.NotifyContext(context.Background(),
        syscall.SIGINT, syscall.SIGTERM)
    defer stop()

    // Fetch immediately, then on every tick.
    if err := fetchAndAppend(ctx, client, w, "BRENT_CRUDE_USD"); err != nil {
        log.Printf("fetch failed: %v", err)
    }

    ticker := time.NewTicker(interval)
    defer ticker.Stop()

    fmt.Printf("polling every %s — Ctrl-C to stop\n", interval)
    for {
        select {
        case <-ctx.Done():
            log.Println("shutting down")
            return
        case <-ticker.C:
            if err := fetchAndAppend(ctx, client, w, "BRENT_CRUDE_USD"); err != nil {
                log.Printf("fetch failed: %v", err)
            }
        }
    }
}

Verified: run with a short interval, this produced:

polled_at,code,price,currency,unit,source_created_at,source
2026-07-05T22:16:23Z,BRENT_CRUDE_USD,71.80,USD,barrel,2026-07-05T22:14:26.318Z,oilprice.ft
2026-07-05T22:16:27Z,BRENT_CRUDE_USD,71.80,USD,barrel,2026-07-05T22:14:26.318Z,oilprice.ft

Deploying It

Build a static binary and run it as a service:

go build -o pricepoller .

# systemd unit (Restart=always survives network blips)
# /etc/systemd/system/pricepoller.service
[Unit]
Description=Oil price poller
After=network-online.target

[Service]
Environment=OILPRICEAPI_KEY=your_api_key_here
Environment=POLL_INTERVAL=15m
ExecStart=/opt/pricepoller/pricepoller
WorkingDirectory=/opt/pricepoller
Restart=always
RestartSec=30

[Install]
WantedBy=multi-user.target

Sizing the interval: a 15-minute poll of one commodity is ~2,900 requests/month — inside the Developer plan's 10,000-request monthly limit, while the free tier's 200 requests/month supports a comfortable 4-hour cadence. Match the interval to the series: spot prices update at market cadence during trading hours, so polling Brent every 10 seconds only re-reads the same quote (our verified run shows the same source timestamp on consecutive polls); retail series like diesel update daily or weekly.

Prefer SQLite over CSV? Swap the csv.Writer for modernc.org/sqlite (pure Go, no cgo) and an INSERT per tick — the fetch loop stays identical.

Frequently Asked Questions

Q: How do I get oil prices in Go?

A: Run go get github.com/OilpriceAPI/oilpriceapi-go, create a client with oilpriceapi.NewClient(apiKey), and call methods like GetHistoricalPrices(ctx, "BRENT_CRUDE_USD"). A demo method works without any API key.

Q: Is there a free oil price API for Go?

A: Yes. GetDemoPrices returns live prices for a limited commodity set with no key at all. A free account adds a 7-day trial, then a free tier of 200 requests per month.

Q: Does the SDK support context and retries?

A: Yes. Every call takes a context.Context, and the client supports WithTimeout and WithRetries with exponential backoff. Errors are typed: authentication, rate limit, not found, server.

Q: How do I run a Go price poller on a schedule?

A: Use a time.Ticker loop with signal handling for a long-running poller (systemd or a container), or build the binary and invoke it from cron for one fetch per run. Either way, persist the API's source timestamp alongside your own polled-at time.

Ship the Poller, Not Just the Print Statement

Ready to Get Started?

Install the SDK and get your free API key — 7-day trial, then 200 requests/month free. No credit card required.

go get github.com/OilpriceAPI/oilpriceapi-go

110+ commodities • context.Context support • Typed errors • Automatic retries