← Blog

Claude API Function Calling with JSON Schema: Guide

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

Claude's version of "function calling" is built entirely on JSON Schema. When you want Claude to call a function in your codebase — look up a customer, run a calculation, query a database — you describe that function as a tool with a name, a description, and an input_schema written in JSON Schema. Claude reads the schema, decides when a tool is needed, and returns a structured JSON object matching that schema instead of free text.

This article covers how the schema actually works, what Claude supports, and how to structure it so tool calls come back clean and predictable — no partial JSON, no hallucinated fields.

How Function Calling Works in the Claude API

Unlike some APIs where "function calling" is a separate mode, Claude uses a single tools array passed alongside your normal message request. Each tool looks like this:

{
  "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. 'Lisbon'"
      },
      "unit": {
        "type": "string",
        "enum": ["celsius", "fahrenheit"]
      }
    },
    "required": ["city"]
  }
}

When Claude decides a tool is relevant, it responds with a tool_use content block containing the tool name and an input object that conforms to your input_schema. Your application executes the actual function, then sends the result back as a tool_result block in the next message so Claude can continue the conversation with that data.

The model never executes anything itself — it only produces schema-valid JSON describing what it wants to run. Execution, validation, and error handling are entirely your responsibility.

Anatomy of a Good input_schema

Claude supports a practical subset of JSON Schema: type, properties, required, enum, items, description, and nested objects/arrays. Keep these points in mind:

Example: A Multi-Parameter Tool

{
  "name": "create_invoice",
  "description": "Create a draft invoice for a customer",
  "input_schema": {
    "type": "object",
    "properties": {
      "customer_id": { "type": "string" },
      "line_items": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "description": { "type": "string" },
            "quantity": { "type": "integer" },
            "unit_price": { "type": "number" }
          },
          "required": ["description", "quantity", "unit_price"]
        }
      },
      "currency": {
        "type": "string",
        "enum": ["EUR", "USD", "GBP"]
      }
    },
    "required": ["customer_id", "line_items", "currency"]
  }
}

Array-of-objects schemas like this work reliably as long as every nested field has its own required list. If line_items items are missing required fields in the schema, Claude sometimes omits fields inconsistently across calls.

Sending a Tool Call Request

A minimal request with the tools array attached:

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": [ ... ],
    "messages": [
      { "role": "user", "content": "What is the weather in Lisbon?" }
    ]
  }'

The response contains a stop_reason of tool_use and a content block like:

{
  "type": "tool_use",
  "id": "toolu_01A...",
  "name": "get_weather",
  "input": { "city": "Lisbon" }
}

You then run your actual weather lookup and send the result back with role: "user" and a tool_result block referencing that same id, so Claude can finish generating its answer using the real data.

If you're routing this traffic through SubToAPI instead of managing raw Anthropic credentials, the request shape is identical — just point it at https://api.subtoapi.app/v1/messages with your sub_live_... key:

curl https://api.subtoapi.app/v1/messages \
  -H "Authorization: Bearer $SUBTOAPI_KEY" \
  -H "content-type: application/json" \
  -d '{
    "model": "claude-sonnet-4-5",
    "max_tokens": 1024,
    "tools": [ ... ],
    "messages": [ { "role": "user", "content": "What is the weather in Lisbon?" } ]
  }'

Every tool call, its usage metadata, and which team member's key generated it show up in the same dashboard, which matters once more than one developer is building against tool use in the same project. Full request/response shapes are in the tool use docs and messages reference.

Common Pitfalls

Getting Started

If you're prototyping tool use and don't want to manage separate Anthropic billing per environment, sign up for a free trial, generate a sub_live_... key, and test the same JSON Schema tool definitions against the SubToAPI endpoint. Plans start at €9/month for solo use, with team seats on the pricing page once you need shared keys and usage visibility across a team.

questions

Does Claude support the full JSON Schema specification? No. Claude supports a practical subset — type, properties, required, enum, items, nested objects/arrays, and description. Advanced keywords like oneOf, allOf, or $ref are not reliably supported and should be avoided in input_schema.

Can I define multiple tools in one request? Yes. Pass an array of tool objects in the tools field. Claude will choose which one (if any) to call based on the user's message and each tool's description.

What happens if Claude's tool input doesn't match my schema? Claude generally produces schema-conforming JSON, but you should still validate the input object in your code before executing anything — treat it like any other untrusted input from an external caller.

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 →