Code
source("downloads/call_claude_AIMECON26.R")An API key is a unique identifier that allows software programs to communicate securely with external services, such as a large language model hosted by a provider. The key acts like a digital credential: it tells the system who you are, verifies that you have permission to use the service, and tracks how much you use it for billing and security purposes. Unfortunately, if you have a subscription to an LLM provider (e.g., Anthropic, OpenAI, etc.), this does not automatically come with API access. You generally have to set up the API account separately.
For users integrating LLMs into R or other analytic workflows, the API key serves as the bridge between your local environment and the remote model. When your script makes an API call, the key authenticates that request and allows the provider to return the model’s response. Because API keys grant direct access to paid and potentially sensitive resources, they should be stored securely—never shared publicly, committed to version control, or embedded directly in reproducible code examples.
In essence, the API key is what allows researchers to treat a model as a callable function, enabling controlled, programmatic access to a complex system running on remote infrastructure. Understanding how to manage this key responsibly is a foundational step in incorporating LLMs into reproducible research and educational applications.
In practice, an API key is a long string of letters and numbers that you include in your code whenever you send a request to the model. I asked OpenAI’s ChatGPT to generate a fake API key for demonstrative purposes. They generally look something like this:
sk-1234567890abcdefGHIJKLMNOPQRSTUVWXYZ1234.
The actual prefixes and length of the random alphanumeric characters usually vary by provider.
When you have an API account, you can usually generate additional keys fairly easily. This can be helpful when you want to track usage for different projects or team members. You may also be able to associate different projects or billing arrangements with different keys, which can be helpful when using APIs for different clients.
In my opinion, there is no definitive choice for the “best” generative AI model. Each provider, and the different models offered by each provider, has its own strengths and weaknesses. Some models may perform better on certain tasks than others. I have chosen to use Anthropic’s Claude because I took a workshop from Hadley Wickham a few years ago in which he said Claude was the best assistant for R programming. Although a lot has changed since then, and all models have significantly improved their ability to help with coding, I’ve stuck with Anthropic. Who am I to argue with Hadley Wickham about something related to R? (or anything, really)
If you must use a specific provider or model due to institutional policy or other agreements, any of the foundational flagship models from the major providers can now complete most tasks reasonably well, provided that you are using them effectively (the purpose of this workshop!). If you do have some freedom in model selection, I encourage you to empirically test which provider and model combination gives you output that is most aligned with what you want.
Links to documentation for API keys:
We used a very simple function to call an Anthropic Claude model earlier. The canonical helper is available in call_claude_AIMECON26.R; source it below if you want to inspect or reuse it from this section.
One important thing to note is the max_output_tokens argument. I’ve set a fairly high default (4096, which is approximately 3000–3200 words). When using a reasoning model, especially one where you’re increasing the reasoning.effort parameter, the model may use tokens during intermediate reasoning before producing the final response. This means that the total tokens required for a reasoning-model response may include both the tokens used during intermediate inference and the tokens in the visible output.
If you set max_output_tokens too low, the model may use all available tokens during inference and fail to produce a final result. I learned this the hard way when trying to produce short text outputs with a high effort level; the model spent all of the available tokens “thinking” and did not have enough left to generate a response. You still get charged for those tokens, so be sure to test the number of tokens needed for both the expected output length and the selected reasoning effort to ensure that you get what you need.
source("downloads/call_claude_AIMECON26.R")Here is a version of a function you can use to call OpenAI’s GPT-6 Astra, OpenAI’s current flagship model. This example uses the Responses API, which OpenAI recommends for new projects. The reasoning.effort parameter is similar to the effort parameter in the Claude model.
See OpenAI’s latest model guide, model catalog, and Responses API reference for current model and parameter details.
library(httr2)
call_gpt6_astra <- function(prompt,
instructions = NULL,
model = "gpt-6-astra",
max_output_tokens = 4096,
reasoning_effort = "low",
verbosity = "medium") {
api_key <- Sys.getenv("OPENAI_API_KEY")
if (api_key == "") {
stop("OpenAI API key is not set. Please set OPENAI_API_KEY in your environment.")
}
request_body <- list(
model = model,
input = prompt,
max_output_tokens = max_output_tokens,
reasoning = list(effort = reasoning_effort),
text = list(verbosity = verbosity)
)
if (!is.null(instructions)) {
request_body$instructions <- instructions
}
response <- request("https://api.openai.com/v1/responses") |>
req_headers(Authorization = paste("Bearer", api_key)) |>
req_body_json(request_body) |>
req_perform()
result <- resp_body_json(response)
text_blocks <- unlist(lapply(result$output, function(item) {
if (!identical(item$type, "message")) return(character())
vapply(item$content, function(content) {
if (identical(content$type, "output_text")) content$text else ""
}, character(1))
}))
paste(text_blocks[text_blocks != ""], collapse = "")
}
# Example usages:
# 1. Default call
# cat(call_gpt6_astra("Explain item response theory in simple terms."))
# 2. Higher reasoning effort
# cat(call_gpt6_astra("Explain item response theory in simple terms.",
# reasoning_effort = "high"))
# 3. Lower response verbosity
# cat(call_gpt6_astra("Explain item response theory in simple terms.",
# verbosity = "low"))
# 4. With a system prompt
# cat(call_gpt6_astra("Explain item response theory in simple terms.",
# instructions = "You are an expert in educational measurement. Respond concisely."))