In the previous article I covered the fundamentals of Anthropic’s Messages API: setting up a client, maintaining conversation history, and integrating tools. That was enough to build a working conversational agent. This article goes a level deeper — into the API parameters that shape what the model returns and how it thinks.
Two parameters stand out as particularly useful in production: output_config.format and output_config.effort. The
first gives you control over the structure of the model’s output. The second controls how much the model reasons before
responding — which turns out to matter more than you might expect once you start caring about latency and cost.
Messages API details #
The Messages API endpoint accepts a rich set of parameters. Most have sensible defaults and you will rarely touch them, but understanding what is available saves you from reaching for workarounds that already exist in the API. The table below covers a selection of the current parameters from the API reference:
| Parameter | Type | Description |
|---|---|---|
model |
string | ID of the model to use |
messages |
array | Conversation history as an ordered list of messages |
system |
string/array | System prompt that sets the model’s behaviour, kept separate from messages |
max_tokens |
integer | Maximum tokens the model may generate — required on every request |
output_config.format |
object | Constrains the response to a JSON Schema |
output_config.effort |
string | Reasoning depth: low, medium, high, xhigh, max |
thinking |
object | Enables and configures extended or adaptive thinking |
temperature |
number | Sampling temperature from 0 to 1; higher values produce more random output |
top_p |
number | Alternative to temperature; nucleus sampling probability mass |
top_k |
integer | Restricts sampling to the top K most likely tokens |
stop_sequences |
array | Custom sequences at which the API stops generating |
stream |
boolean | Stream partial responses as server-sent events |
tools |
array | List of tools the model may call |
tool_choice |
object | Controls which tool the model calls |
metadata |
object | Arbitrary metadata about the request, such as an end-user ID |
In this article we focus on output_config.format and output_config.effort — two parameters with a direct, visible
impact on production systems.
Information extraction with output_config.format #
The format field inside output_config controls how the model structures its output. By default, Claude replies with
plain text. Setting output_config.format to a json_schema document constrains the response to conform to that
schema — Anthropic calls this structured outputs.
Structured output matters whenever downstream code needs to parse the model’s response. Without it, you are parsing free text — which works until the model changes a field name or adds a sentence before the JSON block. With it, you get a contract. The model’s output either matches the schema or the call fails with an error you can handle, rather than silently producing malformed data.
A good production case for this is extracting company details from a website. I have used exactly this approach: given a company URL, extract name, description, and address as structured data, regardless of the language or layout of the page.
package main
import (
// ...
"github.com/anthropics/anthropic-sdk-go"
"github.com/anthropics/anthropic-sdk-go/option"
)
var client anthropic.Client
func extractWebsiteContent(websiteURL string) (string, error) {
// ...
}
func main() {
apiKey := os.Getenv("ANTHROPIC_API_KEY")
if apiKey == "" {
fmt.Fprintln(os.Stderr, "error: ANTHROPIC_API_KEY environment variable is not set")
os.Exit(1)
}
client = anthropic.NewClient(option.WithAPIKey(apiKey))
scanner := bufio.NewScanner(os.Stdin)
fmt.Println("Please provide the company's website URL:")
fmt.Println()
if !scanner.Scan() {
return
}
website := strings.TrimSpace(scanner.Text())
if website == "" {
return
}
companyInformation, err := extractWebsiteContent(website)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
return
}
// ...
}The structure is familiar from the previous article. We initialize the client, read a URL from standard input, and pass
it to extractWebsiteContent. That function is the first meaningful piece — it fetches the page and converts it into a
form the model can work with efficiently.
import (
// ...
"github.com/k3a/html2text"
)
func extractWebsiteContent(websiteURL string) (string, error) {
req, err := http.NewRequest("GET", websiteURL, nil)
if err != nil {
return "", err
}
httpClient := &http.Client{}
response, err := httpClient.Do(req)
if err != nil {
return "", err
}
defer response.Body.Close()
data, err := io.ReadAll(response.Body)
if err != nil {
return "", err
}
return html2text.HTML2Text(string(data)), nil
}We fetch the page using Go’s standard http package and convert the
HTML body to plain text using github.com/k3a/html2text. That conversion
step is more important than it looks. Raw HTML sent to the model is full of tags, scripts, and attributes that add
tokens without adding meaning. Stripping them down to plain text significantly reduces the size of the input, which
lowers cost and reduces the chance of the model getting distracted by irrelevant markup. Cleaner input tends to produce
more accurate output.
With the content in hand, we define the schema we want the model to conform to.
var jsonSchema = map[string]any{
"type": "object",
"properties": map[string]any{
"company": map[string]any{
"type": "object",
"description": "The company information.",
"properties": map[string]any{
"name": map[string]any{
"type": "string",
"description": "The name of the company.",
},
"description": map[string]any{
"type": "string",
"description": "The description of the company.",
},
"address": map[string]any{
"type": "object",
"description": "The address of the company.",
"properties": map[string]any{
"streetName": map[string]any{
"type": []string{"string", "null"},
"description": "The street name of the company.",
},
"streetNumber": map[string]any{
"type": []string{"string", "null"},
"description": "The street number of the company.",
},
"city": map[string]any{
"type": []string{"string", "null"},
"description": "The city of the company.",
},
},
"required": []string{
"streetName",
"streetNumber",
"city",
},
"additionalProperties": false,
},
},
"required": []string{
"name",
"description",
"address",
},
"additionalProperties": false,
},
},
"required": []string{
"company",
},
"additionalProperties": false,
}This is a standard JSON Schema document expressed as a Go map, passed as the
Schema field on JSONOutputFormatParam. Address fields like streetName and city use a union type of string or
null because not every company website publishes a full postal address — and Anthropic’s structured outputs require
every property to be listed in required, so optional fields have to be modelled as nullable rather than left out. The
additionalProperties: false constraint at each object level prevents the model from adding fields outside the schema.
func extractCompanyInformation(companyInformation string) (string, error) {
start := time.Now()
resp, err := client.Messages.New(context.Background(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus5,
MaxTokens: 4096,
System: []anthropic.TextBlockParam{
{Text: "You are a legal advisor. " +
"Given a company's information, extract the company's name, description, and address." +
"You must provide company description in English"},
},
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock(companyInformation)),
},
OutputConfig: anthropic.OutputConfigParam{
Effort: anthropic.OutputConfigEffortHigh,
Format: anthropic.JSONOutputFormatParam{
Schema: jsonSchema,
},
},
})
duration := time.Since(start)
fmt.Println("Duration: " + duration.String())
if err != nil {
return "", fmt.Errorf("API call failed: %w", err)
}
var text strings.Builder
for _, block := range resp.Content {
if variant, ok := block.AsAny().(anthropic.TextBlock); ok {
text.WriteString(variant.Text)
}
}
return text.String(), nil
}The OutputConfig field carries both Effort and Format for this request. Format is set to the
JSONOutputFormatParam we defined above, and Effort is set to High here — effort is only supported on Claude’s
Opus and Sonnet models, not Haiku, which is why this example and the ones that follow stick to claude-opus-5 and
claude-sonnet-4-6. The system prompt defines the persona — "You are a legal advisor." — which primes the model to
treat the task with the seriousness a legal context implies, rather than producing casual summaries. We also instruct
the model to respond in English regardless of the source page language, because company websites can be in any language
and we want consistent output. As in the previous article, we walk resp.Content for TextBlock values rather than
indexing into a single fixed field — a Messages API response is a list of content blocks, not one message.
func main() {
// ...
companyInformation, err := extractWebsiteContent(website)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
return
}
result, err := extractCompanyInformation(companyInformation)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
return
}
fmt.Println("Result: " + result)
}The main function sequences the two calls: fetch and convert the page, then extract the structured data. Each step returns an error that terminates the process cleanly. The result is printed directly — at this stage it is already valid JSON conforming to our schema, ready to be unmarshalled into a Go struct by any consuming code.
Please provide the company's website URL:
https://thinksurance.de/kontakt/
Duration: 6.91785975s
Result: {
"company":{
"address":{
"city":"Frankfurt am Main",
"streetName":"Niddastraße",
"streetNumber":"91"
},
"description":"Thinksurance is a digital platform for commercial insurance, providing software solutions (Advisory Suite, ConsultDirect, Data Suite) that support brokers, industrial brokers, pools and sales networks, insurers, underwriting agencies, banks and savings banks, and digital business models in advising, comparing, and placing commercial and industrial insurance policies.",
"name":"Thinksurance GmbH"
}
}
Process finished with the exit code 0The output is a clean JSON object with the company information extracted from a German-language page — delivered in English, as instructed. The model correctly identified that website is in German, followed the language instruction in the system message, and returned the address in the exact structure the schema required.
What is output_config.effort? #
The effort field inside output_config controls how much internal reasoning — thinking — the model does before
generating its response. It is available on Claude’s Opus and Sonnet models; Haiku does not support it. The options are
low, medium, high, and on the newest models xhigh and max. Higher effort means the model thinks longer,
produces more thorough analysis, and handles complex or ambiguous tasks better. Lower effort is faster and cheaper. For
straightforward extraction tasks with a well-defined schema, high effort is wasted compute.
This trade-off becomes concrete when you add timing to the same extraction request we built above.
func extractCompanyInformation(companyInformation string) (string, error) {
start := time.Now()
resp, err := client.Messages.New(context.Background(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus5,
MaxTokens: 4096,
System: []anthropic.TextBlockParam{
{Text: "You are a legal advisor. " +
"Given a company's information, extract the company's name, description, and address." +
"You must provide company description in English"},
},
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock(companyInformation)),
},
OutputConfig: anthropic.OutputConfigParam{
Effort: anthropic.OutputConfigEffortLow,
Format: anthropic.JSONOutputFormatParam{
Schema: jsonSchema,
},
},
})
duration := time.Since(start)
// ...
}Adding time.Now() before the call and time.Since(start) after gives us the wall-clock duration of the API round-trip.
With claude-opus-5 and Effort set to Low, the baseline looks like this:
Baseline Duration
Please provide the company's website URL:
https://thinksurance.de/kontakt/
Duration: 5.514082916s
Result: {
"company":{
"address":{
"city":"Frankfurt am Main",
"streetName":"Niddastraße",
"streetNumber":"91"
},
"description":"Thinksurance is a digital platform for commercial insurance, providing software solutions (Advisory Suite, ConsultDirect, Data Suite) that support brokers, industrial brokers, pools and sales networks, insurers, underwriting agencies, banks and savings banks, and digital business models in advising, comparing, and placing commercial and industrial insurance policies.",
"name":"Thinksurance GmbH"
}
}
Process finished with the exit code 0Around 5.5 seconds for a simple extraction — reasonable.
Now let’s switch to claude-sonnet-4-6 and drop the explicit Effort override, leaving it at its default.
func extractCompanyInformation(companyInformation string) (string, error) {
start := time.Now()
resp, err := client.Messages.New(context.Background(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeSonnet4_6,
MaxTokens: 4096,
System: []anthropic.TextBlockParam{
{Text: "You are a legal advisor. " +
"Given a company's information, extract the company's name, description, and address." +
"You must provide company description in English"},
},
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock(companyInformation)),
},
OutputConfig: anthropic.OutputConfigParam{
Format: anthropic.JSONOutputFormatParam{
Schema: jsonSchema,
},
},
})
duration := time.Since(start)
// ...
}The only change is the model and dropping the Effort override. Same task, same schema, same system prompt. The result
is noticeably slower — around fourteen seconds.
Please provide the company's website URL:
https://thinksurance.de/kontakt/
Duration: 13.965781239s
Result: {
"company": {
"address": {
"city": "Frankfurt am Main",
"streetName": "Niddastraße",
"streetNumber": "91"
},
"description": "Thinksurance GmbH is a company offering...",
"name": "Thinksurance GmbH"
}
}
Process finished with the exit code 0Effort defaults to High when the field is left unset, and for a task
as clear-cut as extracting a company name and address from a schema, the model is doing far more internal work than the
task requires. The output quality is no better; the time cost is far higher.
The fix is one line: set Effort to Low.
func extractCompanyInformation(companyInformation string) (string, error) {
start := time.Now()
resp, err := client.Messages.New(context.Background(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeSonnet4_6,
MaxTokens: 4096,
System: []anthropic.TextBlockParam{
{Text: "You are a legal advisor. " +
"Given a company's information, extract the company's name, description, and address." +
"You must provide company description in English"},
},
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock(companyInformation)),
},
OutputConfig: anthropic.OutputConfigParam{
Effort: anthropic.OutputConfigEffortLow,
Format: anthropic.JSONOutputFormatParam{
Schema: jsonSchema,
},
},
})
duration := time.Since(start)
// ...
}Here Effort is set to Low, telling the model to spend less on internal reasoning for this request.
Please provide the company's website URL:
https://thinksurance.de/kontakt/
Duration: 4.161644584s
Result: {
"company": {
"address": {
"city": "Frankfurt am Main",
"streetName": "Niddastraße",
"streetNumber": "91"
},
"description": "Thinksurance GmbH is a company offering...",
"name": "Thinksurance GmbH"
}
}
Process finished with the exit code 0Back to 4.1 seconds, with identical output quality. To be clear, the reasoning is not bad — it is that reasoning should
match the task. For complex analysis, multi-step problem solving, or ambiguous requirements, higher output_config.effort
earns its cost. For deterministic extraction with a well-defined schema, Low is the right setting. Matching the
effort level to the task is one of the more impactful tuning decisions you can make when running LLMs in production at volume.
Conclusion #
The Messages API gives you more control than most developers use. output_config.format with a JSON Schema turns the
model into a reliable data extraction tool — no parsing hacks, no fragile string matching, just structured output you
can trust. output_config.effort lets you tune the cost-latency trade-off for models that think by default, and the
difference between the wrong and right setting can be substantial. Neither parameter requires complex code changes;
both have an immediate effect on what the model produces and how quickly it produces it.