← Blog

Claude API Function Calling: JSON Schema Examples

2026-09-25 · 5 min read · SubToAPI Team

Claude's function calling (called "tool use" in Anthropic's docs) lets the model decide when to call a function you've defined, and it returns structured, schema-validated arguments instead of free text you have to parse with regex. You define each tool with a JSON Schema describing its inputs, pass those tools in your API request, and Claude responds with a tool_use block containing the function name and a JSON object of arguments that match your schema.

This article walks through a complete, working example: defining a tool schema, sending it to Claude, and handling the response — including the parts that trip people up, like nested objects, enums, and multi-turn tool conversations.

The basic shape of a tool definition

A tool in the Claude API has three fields: name, description, and input_schema. The input_schema is standard JSON Schema (draft 2020-12 subset), the same format used by OpenAPI and most other JSON Schema tooling.

{
  "name": "get_weather",
  "description": "Get the current weather for a given city",
  "input_schema": {
    "type": "object",
    "properties": {
      "city": {
        "type": "string",
        "description": "City name, e.g. 'Paris'"
      },
      "unit": {
        "type": "string",
        "enum": ["celsius", "fahrenheit"],
        "description": "Temperature unit"
      }
    },
    "required": ["city"]
  }
}

The description fields matter more than people expect — Claude uses them to decide both whether to call the tool and how to fill in each argument. Vague descriptions produce vague or wrong tool calls, especially for enums and optional fields.

Full request example

curl https://api.anthropic.com/v1/messages \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{
    "model": "claude-sonnet-4-5",
    "max_tokens": 1024,
    "tools": [
      {
        "name": "get_weather",
        "description": "Get the current weather for a given city",
        "input_schema": {
          "type": "object",
          "properties": {
            "city": { "type": "string" },
            "unit": { "type": "string", "enum": ["celsius", "fahrenheit"] }
          },
          "required": ["city"]
        }
      }
    ],
    "messages": [
      { "role": "user", "content": "What is the weather like in Lisbon right now?" }
    ]
  }'

If Claude decides to use the tool, the response includes a tool_use content block instead of (or alongside) plain text:

{
  "id": "msg_01...",
  "content": [
    {
      "type": "tool_use",
      "id": "toolu_01...",
      "name": "get_weather",
      "input": { "city": "Lisbon", "unit": "celsius" }
    }
  ],
  "stop_reason": "tool_use"
}

Note stop_reason: "tool_use" — that's how you programmatically detect that Claude wants a function call rather than checking for the presence of a content block type manually every time.

Completing the loop: sending the result back

Function calling is a round trip. Once your code executes get_weather("Lisbon", "celsius"), you send the result back as a tool_result block in a new user message, referencing the original tool_use id:

curl https://api.anthropic.com/v1/messages \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{
    "model": "claude-sonnet-4-5",
    "max_tokens": 1024,
    "tools": [ ... same tool definition ... ],
    "messages": [
      { "role": "user", "content": "What is the weather like in Lisbon right now?" },
      { "role": "assistant", "content": [
        { "type": "tool_use", "id": "toolu_01...", "name": "get_weather", "input": { "city": "Lisbon", "unit": "celsius" } }
      ]},
      { "role": "user", "content": [
        { "type": "tool_result", "tool_use_id": "toolu_01...", "content": "18°C, partly cloudy" }
      ]}
    ]
  }'

Claude then generates a final natural-language answer using that result. This is the pattern behind nearly every agent, RAG pipeline, and structured-output workflow built on Claude.

Nested objects and arrays in the schema

JSON Schema supports nesting, and Claude handles it well as long as the schema is valid and not excessively deep:

{
  "name": "create_order",
  "description": "Create a customer order",
  "input_schema": {
    "type": "object",
    "properties": {
      "customer": {
        "type": "object",
        "properties": {
          "name": { "type": "string" },
          "email": { "type": "string" }
        },
        "required": ["name", "email"]
      },
      "items": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "sku": { "type": "string" },
            "quantity": { "type": "integer", "minimum": 1 }
          },
          "required": ["sku", "quantity"]
        }
      }
    },
    "required": ["customer", "items"]
  }
}

Keep required arrays accurate — Claude uses them to decide which fields it must fill before calling the tool, and loose or missing required lists are a common cause of hallucinated or incomplete arguments.

Forcing a tool call

By default Claude chooses whether to call a tool. If you need a guaranteed function call — for example, using tool use purely as a structured-output mechanism — set tool_choice:

"tool_choice": { "type": "tool", "name": "get_weather" }

Use {"type": "any"} to force some tool call (any of them), or {"type": "auto"} (the default) to let Claude decide, including replying with plain text.

Using this through SubToAPI

If you're already routing Claude traffic through SubToAPI to turn your Claude access into a team API with per-key usage tracking, tool use works exactly the same way — the request and response shapes for tools, tool_use, and tool_result are unchanged, you just point your client at https://api.subtoapi.app/v1/messages with a sub_live_... key instead of a raw Anthropic key. That's useful if multiple engineers or services are calling function-calling endpoints and you want visibility into which key is generating which tool calls without building your own metering layer. See the tool use docs and messages endpoint reference for the exact request format, or the quickstart if you're setting this up for the first time.

FAQs

Does Claude support the OpenAI-style functions field? No. Claude uses tools with an input_schema field (not parameters), and returns tool_use content blocks rather than a function_call field. The JSON Schema itself is largely compatible, but the surrounding request/response structure is different, so a straight copy-paste from OpenAI code won't work without adapting the field names.

Can Claude call multiple tools in one response? Yes. A single response can contain several tool_use blocks if the task requires it. You execute each one and return all results as separate tool_result blocks in the next user message, matched by tool_use_id.

What JSON Schema features does Claude actually support? The core subset: type, properties, required, enum, items, description, and basic numeric constraints like minimum/maximum. Avoid relying on advanced features like $ref, oneOf, or allOf — flatten your schemas where possible for more reliable tool calls.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →