Getting Started with Anthropic API

Introduction & Goals

Welcome to your first lesson in building effective agents with Claude! Whether you're completely new to the Anthropic API or have some experience with it, this lesson will ensure you have a solid foundation for the advanced agent-building techniques we'll cover later in the path.

In this lesson, you'll learn how to send messages to Claude using the Anthropic API and understand the complete response structure. By the end, you'll be able to create a Ruby script that communicates with Claude and examine the full JSON response. This understanding is crucial because throughout this course, we'll be working with different parts of Claude's responses — from basic text content to tool usage metadata and conversation flow control.

This foundation is essential because later lessons will extend this same pattern to develop more complex workflows with Claude.

Environment and Setup

To communicate with Claude, you'll need two things: the anthropic gem and an API key from Anthropic. The gem handles all the technical details of making API requests, and you'd normally install it using gem install anthropic. The API key authenticates your requests, and the Anthropic client automatically looks for it in the ANTHROPIC_API_KEY environment variable.

In CodeSignal, we've already configured everything for you — the gem is pre-installed and your API key is set up, so you can focus on learning the core concepts without worrying about setup details.

How Claude Messaging Works

Every interaction with Claude follows a structured conversation pattern. Understanding this structure is key to building effective agents, as you'll need to manage conversation state and interpret various response components throughout this course.

In the Messages API, Claude conversation history uses two message roles: user and assistant. Separately, the top-level system parameter sets the context and instructions for Claude's behavior — essentially Claude's job description for the conversation. The user role represents messages from you or your application users, and the assistant role represents Claude's responses. This structure helps Claude maintain context and understand conversation flow, which becomes critical when building multi-step agent workflows.

When you send a request to Claude, you package several pieces of information: the model you want to use, a system prompt that defines Claude's behavior, an array of messages representing the conversation history, and a max_tokens limit for the response length. Tokens roughly correspond to words, so max_tokens: 2000 allows Claude to respond with approximately 1,500 – 2,000 words.

The request flows to Anthropic's servers, where Claude processes your messages and generates a response. That response returns as a structured JSON object containing Claude's message plus metadata about the interaction — information we'll use extensively in later lessons for tool usage tracking, conversation management, and error handling.

Setting Up the Client and Configuration

Let's build our first Claude interaction by examining each component. We'll start with the basic imports and client initialization, then define our model and system_prompt:

Ruby
require "anthropic"

# Initialize the Anthropic client (reads ANTHROPIC_API_KEY from env by default)
client = Anthropic::Client.new

# Choose a model to use
model = "claude-sonnet-4-6"

# Short system prompt starting with "You are"
system_prompt = "You are a helpful assistant. Answer questions very briefly."

The client automatically finds your API key in the environment variables. The system_prompt influences how Claude responds throughout the conversation — it's like setting Claude's personality and expertise for the entire interaction. Understanding system prompts is crucial because later in the course, we'll use them to define how Claude should use tools and handle complex agent workflows.

Creating Your First Message

Now we'll create the messages array representing our conversation:

Ruby
# Create an array of messages to send to Claude
messages = [
  { role: "user", content: "What is the main difference between cats and dogs as pets" }
]

Each message is a hash with role and content keys. Even for a single message, we use an array because conversations can have multiple exchanges.

Sending the Message to Claude

With our message prepared, we can now send it to Claude:

Ruby
# Send the messages to Claude
response = client.messages.create(
  model: model,
  max_tokens: 2000,
  system: system_prompt,
  messages: messages
)

The client.messages.create method sends an HTTP request to Anthropic's servers, where Claude processes your message according to the system_prompt and returns a structured response.

Note that max_tokens is a required parameter that limits how long Claude's response can be. Think of Claude as having two token limits: a context window (how much total conversation history it can remember) and a response limit (how much it can write back to you). The context window for Claude Sonnet is around 200,000 tokens, which can hold roughly 150,000 words of conversation history. The max_tokens parameter controls the response limit — setting it to 2000 means Claude can respond with up to about 1,500 – 2,000 words, leaving the rest of the context window available for your conversation history.

Examining the Complete Response Structure

To understand what Claude returns, let's examine the complete response structure:

Ruby
require "json"

# Print the whole response as JSON
puts JSON.pretty_generate(response.to_h)

The response.to_h method converts Claude's response into a Ruby hash, and JSON.pretty_generate formats it as readable JSON. You'll see output like this:

JSON
{
  "id": "msg_01W4Mr5iRVLxk6eSzPfaWwrE",
  "content": [
    {
      "text": "Here are the main differences:\n\n- **Independence**: Cats are more independent; dogs need more attention and interaction\n- **Training**: Dogs are generally easier to train and more eager to please\n- **Exercise**: Dogs require regular walks; cats exercise on their own\n- **Affection**: Dogs tend to be more openly affectionate; cats are more selective\n- **Noise**: Dogs bark; cats are generally quieter (occasional meowing)\n- **Space**: Cats adapt better to small spaces/apartments\n- **Time commitment**: Dogs require significantly more time and care\n\n**In short:** Dogs are social and dependent; cats are independent and low-maintenance.",
      "type": "text"
    }
  ],
  "model": "claude-sonnet-4-6",
  "role": "assistant",
  "stop_reason": "end_turn",
  "stop_sequence": null,
  "type": "message",
  "usage": {
    "cache_creation": {
      "ephemeral_1h_input_tokens": 0,
      "ephemeral_5m_input_tokens": 0
    },
    "cache_creation_input_tokens": 0,
    "cache_read_input_tokens": 0,
    "inference_geo": "global",
    "input_tokens": 30,
    "output_tokens": 143,
    "service_tier": "standard"
  }
}

This JSON structure contains everything you need to understand how Claude processed your request and what it returned.

Understanding Response Fields

Understanding this response structure is essential for the rest of the course. Key fields include:

  • id — Provides a unique identifier useful for logging and debugging.
  • content — Contains Claude's response as an array of content blocks. Notice it's an array because responses can contain multiple blocks of different types — text blocks like we see here, but also thinking blocks, tool usage blocks, and other content types we'll explore later.
  • stop_reason — Tells you why Claude stopped generating text. "end_turn" means Claude naturally concluded its response, but you'll encounter other values like "tool_use" in later lessons when Claude decides to call a function.
  • usage — Provides detailed token consumption information, which becomes important for monitoring agent performance and costs.

Pay special attention to the content array structure. Each block has a type field (here it's "text") and the actual content.

Extracting the Text Response

Most of the time, you'll want to access just Claude's text response rather than the full JSON structure. Let's see how to extract the clean text content:

Ruby
# Print the text response (first text block)
puts response.content.first.text

Since our response contains only one content block, we can access it directly with response.content.first.text. This is the easiest approach for simple interactions, but it won't always be this straightforward — later in the course, you'll encounter responses with multiple content blocks of different types that require more sophisticated handling.

This produces the clean text output:

text
Here are the main differences:

- **Independence**: Cats are more independent; dogs need more attention and interaction
- **Training**: Dogs are generally easier to train and more eager to please
- **Exercise**: Dogs require regular walks; cats exercise on their own
- **Affection**: Dogs tend to be more openly affectionate; cats are more selective
- **Noise**: Dogs bark; cats are generally quieter (occasional meowing)
- **Space**: Cats adapt better to small spaces/apartments
- **Time commitment**: Dogs require significantly more time and care

**In short:** Dogs are social and dependent; cats are independent and low-maintenance.

When we access response.content.first.text, we're getting the text from the first content block. This understanding of content block filtering will be crucial as we progress to more complex agent workflows.

Building Multi-Turn Conversations

Real conversations don't end after one exchange. To continue our conversation with Claude, we need to maintain the conversation history by adding Claude's response to our messages array, then append our follow-up question:

Ruby
# Append Claude's response to messages (store as blocks)
messages << { role: "assistant", content: response.content }

# Append a new user message
messages << { role: "user", content: "Which one is easier to train?" }

# Send the second request
second_response = client.messages.create(
  model: model,
  max_tokens: 2000,
  system: system_prompt,
  messages: messages
)

Notice how we append Claude's entire content array to maintain the conversation structure. This preserves all content blocks and their types, which becomes crucial when working with responses that contain multiple content types. Now let's see Claude's response to our follow-up question:

Ruby
# Print the text response
puts second_response.content.first.text

This produces output like:

text
**Dogs are generally easier to train** because:

- They are **pack animals** and naturally follow a leader
- They are **eager to please** their owners
- They respond well to **praise and rewards**
- They have been **bred for centuries** to follow human commands
- They can learn a **wide variety of commands** and tricks

**Cats can be trained** but it's more challenging because:

- They are **independent** and less motivated to please
- They get **bored quickly**
- They respond mainly to **food rewards**
- They do things on **their own terms**

**Bottom line:** Dogs are much easier to train, which is why they are used as service animals, police dogs, and therapy animals. Cats can learn basic behaviors but rarely reach the same level of obedience.

The conversation continues naturally because Claude can see the full context of our previous exchange, allowing it to provide a focused answer about training specifically.

Enabling Extended Thinking

Claude can also show its reasoning process through thinking — internal deliberation that helps it provide better responses. Let's continue our conversation with thinking enabled to see how Claude works through problems:

Ruby
# Append Claude's second response to messages
messages << { role: "assistant", content: second_response.content }

# Append a third user message
messages << { role: "user", content: "What about grooming requirements?" }

# Send the third request with thinking enabled
third_response = client.messages.create(
  model: model,
  max_tokens: 16_000,
  system: system_prompt,
  messages: messages,
  thinking: {
    type: "enabled",
    budget_tokens: 10_000
  }
)

When thinking is enabled, Claude's response can contain multiple content blocks of different types.

Examining Thinking Responses

When you enable thinking, Claude's response structure becomes more complex — it may contain multiple content blocks with different type values. Let's examine the full response structure to understand what we're working with:

Ruby
# Print the whole response as JSON first
puts JSON.pretty_generate(third_response.to_h)

This produces an output like:

JSON
{
  "id": "msg_014uNpComxt9TVxhr8LUTsBi",
  "content": [
    {
      "signature": "Ev0BClsIDBgCKkBWeEZRGVFFYFdTr2oqu1Fh...",
      "thinking": "The user is asking about grooming requirements for cats vs dogs.",
      "type": "thinking"
    },
    {
      "text": "Here is a comparison of grooming requirements:\n\n## Cats\n- **Self-cleaning**...",
      "type": "text"
    }
  ],
  "model": "claude-sonnet-4-6",
  "role": "assistant",
  "stop_reason": "end_turn",
  "stop_sequence": null,
  "type": "message",
  "usage": {
    "input_tokens": 425,
    "output_tokens": 297,
    ...
  }
}

Notice the content array now contains two blocks with different type fields: a "thinking" block representing Claude's internal reasoning process, and a "text" block containing the final response. The "thinking" block also includes a "signature" field that Anthropic uses to verify the integrity of the thinking content. The key insight here is that responses with thinking enabled are multi-block responses where different blocks serve different purposes.

This is why understanding how to filter and process content blocks by type is crucial — you need to identify which blocks contain the information you want to extract or display.

Processing Multiple Content Blocks

When Claude's response contains multiple content blocks (like thinking and text), we need a way to extract just the parts we want. Let's filter the response to get only the text content that we'd show to a user:

Ruby
# Get all text content from the response
text_contents = third_response.content
  .select { |block| block.type.to_s == "text" }
  .map(&:text)

# Join all text contents and print them
puts text_contents.join("\n")

This code filters through all content blocks in the response, selects only those with type == "text", extracts their text content, and joins them together. This produces the clean final output:

text
## Grooming Requirements: Cats vs Dogs

### Cats 🐱
- **Self-grooming** - they clean themselves regularly
- Occasional **brushing** needed (more for long-haired breeds)
- Rarely need **baths**
- **Nail trimming** needed periodically
- Generally **low maintenance** for grooming

### Dogs 🐶
- Cannot self-groom effectively
- Regular **brushing** needed (frequency depends on breed)
- Regular **baths** required
- **Nail trimming** needed regularly
- Many breeds need **professional grooming**
- **Ear cleaning** may be required
- Some breeds need **haircuts**

### Key Factors That Affect Grooming:
- **Coat length** - longer hair = more grooming
- **Breed type** - some breeds shed more than others
- **Activity level** - more active pets get dirtier faster

### Bottom Line:
- **Cats** are largely self-sufficient with grooming
- **Dogs** require significantly **more time and money** spent on grooming
- Some dog breeds (like Poodles) can cost a lot for **regular professional grooming**
- Short-haired dogs are the **lowest maintenance** option if grooming is a concern

This pattern of filtering content blocks by type is essential for building robust agents. Later in the course, you'll encounter responses with tool usage blocks, multiple text blocks, and other content types that require similar filtering and processing techniques.

Summary & Next Steps

You've now successfully understood how to interact with Claude using the Anthropic API, examined the complete response structure, built multi-turn conversations, and explored extended thinking capabilities. You understand how to structure conversations with roles, send requests with system prompts, maintain conversation context, and interpret response metadata, including different content block types.

The key concepts you've learned — conversation management, content block filtering, and response structure analysis — form the foundation for the advanced agent-building techniques we'll cover throughout this course.

In the upcoming practices, you'll get hands-on experience building on these concepts and exploring different ways to interact with Claude. This foundation will serve you well as we progress through more advanced topics in the course!

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal