← Blog

Claude API Function Calling Schema Examples That Work

2026-10-18 · 4 min read · SubToAPI Team

Claude's version of function calling is called tool use, and it works by passing a JSON Schema description of each function you want Claude to be able to call. Claude doesn't execute anything itself — it reads your schemas, decides when a tool is relevant, and returns a structured tool_use block with the arguments filled in. Your code runs the actual function and sends the result back.

This article focuses specifically on the schema part: how to write tool definitions that Claude parses correctly, with real examples for common cases (strings, enums, numbers, nested objects, arrays, optional fields) and the mistakes that cause Claude to send malformed or incomplete arguments.

The basic shape of a tool schema

Every tool you give Claude needs three things: a name, a description, and an input_schema written in JSON Schema. Here's the minimal structure:

{
  "name": "get_weather",
  "description": "Get the current weather for a specific city.",
  "input_schema": {
    "type": "object",
    "properties": {
      "city": {
        "type": "string",
        "description": "The city name, e.g. 'Berlin' or 'Tokyo'."
      },
      "unit": {
        "type": "string",
        "enum": ["celsius", "fahrenheit"],
        "description": "Temperature unit to return."
      }
    },
    "required": ["city"]
  }
}

A few details that matter more than they look:

Enums for constrained choices

If a parameter only accepts a fixed set of values, always use enum instead of a free-text description. This is the single biggest accuracy improvement for structured outputs:

{
  "name": "set_order_status",
  "description": "Update the status of an existing order.",
  "input_schema": {
    "type": "object",
    "properties": {
      "order_id": { "type": "string" },
      "status": {
        "type": "string",
        "enum": ["pending", "shipped", "delivered", "cancelled"]
      }
    },
    "required": ["order_id", "status"]
  }
}

Without the enum, Claude might return "Shipped", "SHIPPED", or "on the way" — all plausible English, none of which match your backend's expected values.

Nested objects and arrays

Real-world functions often need structured, not flat, input. Claude handles nested JSON Schema objects fine as long as the nesting is explicit:

{
  "name": "create_invoice",
  "description": "Create an invoice for a customer with one or more line items.",
  "input_schema": {
    "type": "object",
    "properties": {
      "customer_email": { "type": "string" },
      "line_items": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "description": { "type": "string" },
            "quantity": { "type": "integer", "minimum": 1 },
            "unit_price": { "type": "number" }
          },
          "required": ["description", "quantity", "unit_price"]
        }
      }
    },
    "required": ["customer_email", "line_items"]
  }
}

This is the pattern to use for anything resembling a form, order, or multi-field record. Avoid flattening nested data into a single string field like "line_items": "2x Widget @5, 1x Gadget @12" — Claude can produce it, but your parsing code has to reverse-engineer a format that was never strictly defined.

Optional fields and defaults

JSON Schema doesn't have a native "default" that Claude will reliably apply, so be explicit in the description instead of relying on omission:

{
  "name": "search_products",
  "input_schema": {
    "type": "object",
    "properties": {
      "query": { "type": "string" },
      "max_results": {
        "type": "integer",
        "description": "Number of results to return. Defaults to 10 if not specified."
      }
    },
    "required": ["query"]
  }
}

Your application code should still apply the actual default when the field is missing from the returned arguments — don't assume Claude will inject it.

Putting it together in a request

Tool schemas go in the tools array of your Messages request, and Claude's response includes a tool_use content block with the matching input object:

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": [ /* schema from above */ ],
    "messages": [
      {"role": "user", "content": "Ship order #4821 and mark it delivered"}
    ]
  }'

If you're routing Claude access through SubToAPI, the request shape is identical — you call https://api.subtoapi.app/v1/messages with Authorization: Bearer $SUBTOAPI_KEY instead of the Anthropic key, and the same tools and input_schema fields apply unchanged. That makes it easy to run the same tool-use code in dev against your personal Claude plan and in production against a dashboard-managed key with per-team usage tracking. Full request format is in the docs and Messages reference.

Common schema mistakes

Questions

Does Claude support OpenAI-style function calling syntax directly? No. Claude uses its own tools / input_schema format based on standard JSON Schema. The concepts are similar, but field names and response structure differ, so schemas need to be rewritten, not just copied.

Can one request include multiple tool schemas? Yes. The tools array can contain several tool definitions, and Claude will choose which one (if any) to call based on each tool's name and description.

What happens if Claude's returned arguments don't match my schema exactly? Claude generally respects required fields and types, but your code should still validate the input object before executing anything — schemas guide Claude's output, they don't enforce it at runtime.

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 →