Back to blog

API & SDKGuide

First request in Go with openai-go: base URL, key, usage

A complete Go program that calls a CheapTokens model through openai-go and prints the answer, finish_reason and token usage, with retries and a timeout.

By CheapTokens TeamPublished 6 min read

Cover: First request in Go with the openai-go SDK, a terminal with go run and the answer, finish_reason and usage
On this page
  1. What you need before you start
  2. The whole program
  3. Read the answer, finish_reason and usage
  4. Retries and timeouts: three different limits
  5. What a thousand of these calls cost
  6. If it does not work

You already have Go code that talks to an OpenAI-style API, or you are about to write some. To send it to CheapTokens you change two options on the client and one string, the model id. The SDK stays the same: the official openai-go package, v3.

Below is the whole program, then what each line does, how to read the answer and the token counts, and how to stop a stuck request from hanging your service. Versions checked against the openai-go README on 3 November 2026: the latest tag on that page is v3.91.1, and it needs Go 1.26 or newer.

What you need before you start#

  • Go 1.26 or newer. On Go 1.25 pin v3.74.0; on Go 1.22–1.24 pin v3.44.0 (the README says the older line gets no guaranteed fixes).
  • A CheapTokens API key. Keep it in an environment variable, not in the source file.
  • The API address: https://api.cheaptokens.dev/v1. It speaks the OpenAI Chat Completions format, so openai-go works without a wrapper. The docs have the same address and the key page.
  • A model id. We use deepseek-ai/DeepSeek-V4-Flash-0731: 380K context and up to 32,768 tokens in one answer. Use the full id; the short name from price tables is not accepted by the API.

Step by step

Set up the project

Install the SDK

In an empty folder:

bash
go mod init example.com/first-request
go get -u 'github.com/openai/openai-go/[email protected]'

The import path is github.com/openai/openai-go/v3, the package name is openai.

Put the key in the environment

bash
export CHEAPTOKENS_API_KEY="paste-your-key-here"

On Windows PowerShell: $env:CHEAPTOKENS_API_KEY = "paste-your-key-here". The placeholder is not a real key.

Create main.go and run it

Paste the program from the next section into main.go, then run go run ..

The whole program#

go
package main

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

	"github.com/openai/openai-go/v3"
	"github.com/openai/openai-go/v3/option"
)

func main() {
	client := openai.NewClient(
		option.WithBaseURL("https://api.cheaptokens.dev/v1"),
		option.WithAPIKey(os.Getenv("CHEAPTOKENS_API_KEY")),
		option.WithMaxRetries(2),
		option.WithRequestTimeout(30*time.Second),
	)

	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
	defer cancel()

	resp, err := client.Chat.Completions.New(ctx, openai.ChatCompletionNewParams{
		Model: "deepseek-ai/DeepSeek-V4-Flash-0731",
		Messages: []openai.ChatCompletionMessageParamUnion{
			openai.UserMessage("Explain in two sentences what a token is."),
		},
	})
	if err != nil {
		log.Fatal(err)
	}
	if len(resp.Choices) == 0 {
		log.Fatal("no choices in the response")
	}

	fmt.Println(resp.Choices[0].Message.Content)
	fmt.Println("finish_reason:", resp.Choices[0].FinishReason)
	fmt.Println("input tokens:", resp.Usage.PromptTokens)
	fmt.Println("output tokens:", resp.Usage.CompletionTokens)
	fmt.Println("total tokens:", resp.Usage.TotalTokens)
}
The path of one request: key from the environment, openai-go client, chat completion, then content, finish_reason and usage
What the program does, in order

The pieces that matter:

  • option.WithBaseURL(...) is the only line that moves the SDK from OpenAI to CheapTokens. Use the address exactly as in the docs.
  • option.WithAPIKey(os.Getenv(...)) passes the key explicitly. Without it the client reads OPENAI_API_KEY, which is handy when you port existing code: set that variable to a CheapTokens key and the code needs no key change. The same goes for OPENAI_BASE_URL, but an explicit WithBaseURL overrides it, so there is no surprise if both exist.
  • Model is a plain string in the SDK (ChatModel is an alias of string), so any model id works, not only the constants the README shows.
  • openai.UserMessage(...) is one user turn. The README example uses DeveloperMessage and a GPT constant; here a user message and a string id are enough.

Read the answer, finish_reason and usage#

All three come from fields the SDK already parses for you:

  • resp.Choices[0].Message.Content is the text (a string).
  • resp.Choices[0].FinishReason is also a string. stop means the model finished on its own; length means the answer hit the token limit and was cut. If you see length, read why an answer stops mid-sentence.
  • resp.Usage holds PromptTokens (what you sent), CompletionTokens (what the model wrote) and TotalTokens, all int64.

The output will look like this: the answer, then finish_reason: with a value, then three token counts. The numbers differ on every run, so log them. They are what you pay for, and output tokens are the more expensive side.

Try it yourselfTry it: what changes if you swap the model?Show answer

Change the Model string to zai-org/GLM-5.3-Flash and run again. Nothing else in the program moves. The answer text and the three usage numbers will differ, which is the quickest way to compare two models on your own prompt. GLM-5.3-Flash has a 390K context and up to 8,192 tokens in one answer.

Retries and timeouts: three different limits#

This is where Go services usually get hurt: by default the SDK has no timeout at all, so a stalled connection can wait forever. Three settings fix that, and they work at different levels:

  • option.WithMaxRetries(2): the SDK retries connection errors, 408, 409, 429 and 5xx with a short exponential wait. Two retries is already the default; 0 turns retries off. You can set it on the client, or on a single call.
  • option.WithRequestTimeout(30*time.Second): the limit for one attempt.
  • The context deadline: the limit for the whole call, retries and waits included.

So with 2 retries there can be up to 3 attempts: 3 × 30 = 90 seconds of waiting at worst, plus the short pauses between attempts. A two-minute context covers that. If you want a hard answer-within-20-seconds rule for a web handler, shorten the context, not just the per-attempt timeout.

Defaults compared with the settings in the program: two retries in both, no timeout by default versus a 30 second attempt timeout
Defaults versus the program above

For a longer background on picking these numbers, the Python version of the same advice is in OpenAI Python SDK timeout and retries. The logic carries over to Go.

What a thousand of these calls cost#

Say you run a small service that makes 1,000 requests a day, each with about 800 input tokens and 300 output tokens. On deepseek-ai/DeepSeek-V4-Flash-0731 that day costs $0.042 (1,000 × 800 input tokens, 1,000 × 300 output tokens). The table shows the same load across our models; counted in total, with input and output together, it is cheaper than OpenRouter.

Model Per request 1,000 requests OpenRouter, 1,000 requests
GLM-5.3-Flash $0.000028 $0.028 $0.0517
DeepSeek-V4-Flash $0.000042 $0.042 $0.098
MiniMax-M2.7 $0.000085 $0.085 $1.02

800 input and 300 output tokens per request. Prices per 1M tokens are current and change together with our tariffs; the OpenRouter column shows reference prices for the same workload.

Plug in your own numbers. Your first run prints the real PromptTokens and CompletionTokens, so you do not have to guess the average:

Cost calculator

One day of a small Go service

Input tokens per request: 800 · Output tokens per request: 300 · Requests per day: 1,000

ModelPer dayPer month
GLM-5.3-Flash$0.028$0.84
DeepSeek-V4-Flash$0.042$1.26
MiniMax-M2.7$0.085$2.55

Prices on the pricing page and the models page are live and use one fixed price per model, with no peak hours.

If it does not work#

  • The error mentions 401: the key is empty or wrong. os.Getenv returns an empty string for an unset variable, so check echo $CHEAPTOKENS_API_KEY in the same terminal where you run go run ..
  • The error mentions 404 or an unknown model: the id. Copy it in full, with the deepseek-ai/ prefix. You can list ids in the models list call.
  • go: module requires Go 1.26: your toolchain is older. Pin an older SDK tag as shown at the top.
  • The call hangs and then fails: that is the timeout doing its job. Check the network path first, then raise WithRequestTimeout if the model really needs more time for a long answer.

If the base URL idea is new to you, what base_url is and how to switch explains it with no code.