Fix: Anthropic API 500 Error with strict: true Tools

Written by Michael Lip · Solo founder of Zovo · $400K+ on Upwork · 100% JSS Join 50+ builders · More at zovo.one

The Error

When using strict: true with the structured outputs beta, the API returns a 500 Internal Server Error for tool schemas with complex nested structures:

{
  "type": "error",
  "error": {
    "type": "api_error",
    "message": "Internal server error"
  },
  "request_id": "req_011CXYasMqMAq6EP1aGLry8N"
}

The same request works perfectly when strict: true is removed. The error occurs after 17-55 seconds of processing, suggesting a server-side crash during grammar compilation.

Quick Fix

Remove strict: true from tools with complex nested schemas:

const tools = [{
  name: "create_booking",
  // strict: true,  // Remove this
  input_schema: {
    type: "object",
    properties: { /* ... complex schema ... */ },
    required: ["field1", "field2"],
    additionalProperties: false
  }
}];

The model will still follow the schema in most cases. Validate the output yourself.

What’s Happening

When you set strict: true on a tool, the API compiles a constrained decoding grammar from your JSON Schema. This grammar ensures the model’s output is guaranteed to be valid according to your schema. The compilation process converts the JSON Schema into a finite-state automaton.

For complex schemas, this compilation can fail or exceed internal limits. The factors that cause grammar explosion:

1. Nested Arrays of Objects

// This pattern causes exponential grammar state growth:
{
  accommodationRate: {
    type: "array",
    items: {
      type: "object",
      properties: {
        roomId: { type: "string" },
        price: { type: "integer" }
      },
      required: ["roomId", "price"],
      additionalProperties: false
    }
  }
}

Each nested object with required fields and additionalProperties: false creates branching points in the grammar.

2. Multiple Complex Arrays in One Schema

Test results confirm the pattern:

Configuration Result
1 simple tool + strict: true Works (7.8s)
2 simple tools + strict: true Works (8.2s)
3 tools (including complex) + strict: true 500 Error (17.3s)
Same 3 tools WITHOUT strict: true Works (5.8s)
Complex tool + strict: true (no thinking) 500 Error

3. The Grammar Size Limit

The related error “compiled grammar is too large” (which returns a 400 instead of 500) triggers when the grammar exceeds a documented size threshold. The 500 error appears to be an unhandled edge case where the grammar compilation itself crashes before the size check.

Step-by-Step Solution

Option 1: Simplify the Schema

Flatten nested structures by using string-encoded JSON for complex inner types:

// BEFORE: Complex nested schema (causes 500)
const schema = {
  type: "object",
  properties: {
    rooms: {
      type: "array",
      items: {
        type: "object",
        properties: {
          id: { type: "string" },
          rate: { type: "integer" },
          extras: {
            type: "array",
            items: {
              type: "object",
              properties: {
                name: { type: "string" },
                cost: { type: "integer" }
              },
              required: ["name", "cost"]
            }
          }
        },
        required: ["id", "rate"]
      }
    }
  }
};

// AFTER: Flattened schema (works with strict: true)
const schema = {
  type: "object",
  properties: {
    rooms: {
      type: "array",
      items: {
        type: "object",
        properties: {
          id: { type: "string" },
          rate: { type: "integer" },
          extras_json: { type: "string" }  // JSON-encoded array
        },
        required: ["id", "rate", "extras_json"],
        additionalProperties: false
      }
    }
  },
  required: ["rooms"],
  additionalProperties: false
};

// Then parse extras_json yourself:
const extras = JSON.parse(result.rooms[0].extras_json);

Option 2: Split Into Multiple Tools

Instead of one tool with a complex schema, split it into sequential calls:

const tools = [
  {
    name: "create_booking_base",
    strict: true,
    input_schema: {
      type: "object",
      properties: {
        arrival: { type: "string" },
        departure: { type: "string" },
        firstName: { type: "string" },
        lastName: { type: "string" },
        numAdults: { type: "integer" }
      },
      required: ["arrival", "departure", "firstName", "lastName", "numAdults"],
      additionalProperties: false
    }
  },
  {
    name: "add_room_rates",
    strict: true,
    input_schema: {
      type: "object",
      properties: {
        bookingId: { type: "string" },
        roomId: { type: "string" },
        price: { type: "integer" }
      },
      required: ["bookingId", "roomId", "price"],
      additionalProperties: false
    }
  }
];

Option 3: Selective strict: true

Apply strict: true only to simple tools, and validate complex tools manually:

import { z } from "zod";

// Simple tool — use strict
const checkAvailability = {
  name: "check_availability",
  strict: true,
  input_schema: {
    type: "object",
    properties: {
      startDate: { type: "string" },
      endDate: { type: "string" }
    },
    required: ["startDate", "endDate"],
    additionalProperties: false
  }
};

// Complex tool — skip strict, validate with Zod
const createBooking = {
  name: "create_booking",
  // No strict: true
  input_schema: { /* complex schema */ }
};

// Manual validation for non-strict tools:
const BookingSchema = z.object({
  rooms: z.array(z.object({
    id: z.string(),
    rate: z.number(),
    extras: z.array(z.object({
      name: z.string(),
      cost: z.number()
    }))
  }))
});

function handleToolCall(name: string, input: unknown) {
  if (name === "create_booking") {
    const parsed = BookingSchema.safeParse(input);
    if (!parsed.success) {
      return { error: `Invalid input: ${parsed.error.message}` };
    }
    return processBooking(parsed.data);
  }
}

Option 4: Use output_config Instead

For single-response structured output (not tool use), output_config may handle complex schemas better:

const response = await client.messages.create({
  model: "claude-sonnet-4-5-20250929",
  max_tokens: 4096,
  messages: [{ role: "user", content: "Create a booking for..." }],
  output_config: {
    format: {
      type: "json_schema",
      schema: complexSchema
    }
  }
});

Prevention

Tools That Help

When debugging complex API integrations with structured outputs, a dev tool extension can help you inspect request/response payloads and identify which schema patterns trigger server-side failures.