---
title: Salidas estructuradas
url: https://platform.claude.com/docs/es/build-with-claude/structured-outputs
description: Obtén resultados JSON validados de flujos de trabajo de agentes
featureMetadata:
  status: ga
  zdr:
    eligibility: eligible
    note: Excludes [Covered Models](https://platform.claude.com/docs/en/manage-claude/api-and-data-retention#model-specific-data-retention-requirements).
  supportedModels:
    - claude-fable-5-1
    - claude-mythos-5-1
    - claude-fable-5
    - claude-mythos-5
    - claude-mythos-preview
    - claude-opus-5-5
    - claude-opus-5
    - claude-opus-4-8
    - claude-opus-4-7
    - claude-opus-4-6
    - claude-sonnet-5-5
    - claude-sonnet-5
    - claude-sonnet-4-6
    - claude-sonnet-4-5-20250929
    - claude-opus-4-5-20251101
    - claude-haiku-5-5
    - claude-haiku-4-5-20251001
  supportedPlatforms:
    Claude API: ga
    Claude Platform on AWS: ga
    Amazon Bedrock:
      availability: ga
      note: En Amazon Bedrock, las salidas estructuradas están disponibles en la integración heredada [Amazon Bedrock (Opus 4.6 y anteriores)](https://platform.claude.com/docs/es/build-with-claude/claude-on-amazon-bedrock-legacy) para Claude Opus 4.6, Claude Sonnet 4.6, Claude Sonnet 4.5, Claude Opus 4.5 y Claude Haiku 4.5, y no en [Claude en Amazon Bedrock](https://platform.claude.com/docs/es/build-with-claude/claude-in-amazon-bedrock).
    Google Cloud: ga
    Microsoft Foundry: ga
---

Las "structured outputs" (salidas estructuradas) restringen las respuestas de Claude para que sigan un esquema específico, garantizando una salida válida y analizable para el procesamiento posterior. Las salidas estructuradas proporcionan dos funciones complementarias:

* **Salidas JSON** (`output_config.format`): Obtén la respuesta de Claude en un formato JSON específico, por ejemplo, para extraer datos de imágenes o texto, generar informes estructurados o dar formato a respuestas de API. Esta página trata sobre las salidas JSON.
* **Uso estricto de herramientas** (`strict: true`): Garantiza la validación del esquema en los nombres y las entradas de las herramientas. Consulta [Uso estricto de herramientas](https://platform.claude.com/docs/es/agents-and-tools/tool-use/strict-tool-use).

Puedes usar estas funciones de forma independiente o [juntas en la misma solicitud](https://platform.claude.com/docs/es/build-with-claude/structured-outputs#using-both-features-together).

## Por qué usar salidas estructuradas

Sin salidas estructuradas, Claude puede generar respuestas JSON mal formadas o entradas de herramientas inválidas que rompen tus aplicaciones. Incluso con prompts cuidadosos, puedes encontrarte con:

* Errores de análisis por sintaxis JSON inválida
* Campos obligatorios faltantes
* Tipos de datos inconsistentes
* Violaciones de esquema que requieren manejo de errores y reintentos

Las salidas estructuradas garantizan respuestas que cumplen con el esquema mediante decodificación restringida:

* **Siempre válidas:** No más errores de `JSON.parse()`
* **Seguridad de tipos:** Tipos de campo y campos obligatorios garantizados
* **Confiables:** No se necesitan reintentos por violaciones de esquema

## Cómo funciona

<Steps>
  <Step title="Define tu esquema">
    Describe la estructura que quieres como un esquema JSON o como un tipo en tu lenguaje. El esquema sigue JSON Schema, con algunas [limitaciones](https://platform.claude.com/docs/es/build-with-claude/structured-outputs#json-schema-limitations).
  </Step>

  <Step title="Envíalo en output_config.format">
    La solicitud lleva el esquema en `output_config.format` con `type: "json_schema"`. Los helpers del SDK lo configuran por ti.
  </Step>

  <Step title="Lee la respuesta">
    Claude devuelve JSON válido que coincide con tu esquema en el bloque de contenido de texto de la respuesta. Los helpers del SDK lo analizan y lo convierten en tu tipo.
  </Step>
</Steps>

## Uso

<Tabs>
  <Tab title="cURL">
    ```bash
    curl https://api.anthropic.com/v1/messages \
      -H "content-type: application/json" \
      -H "x-api-key: $ANTHROPIC_API_KEY" \
      -H "anthropic-version: 2023-06-01" \
      -d '{
        "model": "claude-opus-5-5",
        "max_tokens": 1024,
        "messages": [
          {
            "role": "user",
            "content": "Extract the key information from this email: John Smith (john@example.com) is interested in our Enterprise plan and wants to schedule a demo for next Tuesday at 2pm."
          }
        ],
        "output_config": {
          "format": {
            "type": "json_schema",
            "schema": {
              "type": "object",
              "properties": {
                "name": {"type": "string"},
                "email": {"type": "string"},
                "plan_interest": {"type": "string"},
                "demo_requested": {"type": "boolean"}
              },
              "required": ["name", "email", "plan_interest", "demo_requested"],
              "additionalProperties": false
            }
          }
        }
      }'
    ```

    Claude devuelve JSON como este en el bloque de contenido de texto de la respuesta:

    ```json Output
    {
      "name": "John Smith",
      "email": "john@example.com",
      "plan_interest": "Enterprise",
      "demo_requested": true
    }
    ```
  </Tab>

  <Tab title="CLI">
    ```bash
    ant messages create \
      --transform 'content.#(type=="text").text|@fromstr' \
      --format jsonl <<'YAML'
    model: claude-opus-5-5
    max_tokens: 1024
    messages:
      - role: user
        content: >-
          Extract the key information from this email: John Smith
          (john@example.com) is interested in our Enterprise plan and wants
          to schedule a demo for next Tuesday at 2pm.
    output_config:
      format:
        type: json_schema
        schema:
          type: object
          properties:
            name: {type: string}
            email: {type: string}
            plan_interest: {type: string}
            demo_requested: {type: boolean}
          required: [name, email, plan_interest, demo_requested]
          additionalProperties: false
    YAML
    ```

    Para imprimir solo algunas propiedades de la salida JSON, pasa una [ruta GJSON](https://platform.claude.com/docs/es/cli-sdks-libraries/cli/using#transform-output-with-gjson) a `--transform`.

    El ejemplo anterior produce:

    ```text Output wrap
    {"name":"John Smith","email":"john@example.com","plan_interest":"Enterprise","demo_requested":true}
    ```
  </Tab>

  <Tab title="Python">
    ```python
    from pydantic import BaseModel
    # ...


    class ContactInfo(BaseModel):
        name: str
        email: str
        plan_interest: str
        demo_requested: bool


    client = Anthropic()

    response = client.messages.parse(
        model="claude-opus-5-5",
        max_tokens=1024,
        messages=[
            {
                "role": "user",
                "content": (
                    "Extract the key information from this email: "
                    "John Smith (john@example.com) is interested in our Enterprise plan "
                    "and wants to schedule a demo for next Tuesday at 2pm."
                ),
            }
        ],
        output_format=ContactInfo,
    )

    print(response.parsed_output)
    ```

    Pasa un modelo de [Pydantic](https://docs.pydantic.dev/) a `client.messages.parse()`, el método recomendado, como `output_format`. El SDK transforma el esquema del modelo, lo envía como `output_config.format`, valida la respuesta y devuelve el modelo analizado en `parsed_output`.

    El ejemplo anterior produce:

    ```text Output wrap
    name='John Smith' email='john@example.com' plan_interest='Enterprise' demo_requested=True
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript
    import { z } from "zod";
    import { zodOutputFormat } from "@anthropic-ai/sdk/helpers/zod";

    const ContactInfoSchema = z.object({
      name: z.string(),
      email: z.string(),
      plan_interest: z.string(),
      demo_requested: z.boolean()
    });

    const client = new Anthropic();

    const response = await client.messages.parse({
      model: "claude-opus-5-5",
      max_tokens: 1024,
      messages: [
        {
          role: "user",
          content:
            "Extract the key information from this email: " +
            "John Smith (john@example.com) is interested in our Enterprise plan " +
            "and wants to schedule a demo for next Tuesday at 2pm."
        }
      ],
      output_config: { format: zodOutputFormat(ContactInfoSchema) }
    });

    // Analizado y validado automáticamente
    console.log(response.parsed_output);
    ```

    Envuelve un esquema de [Zod](https://zod.dev/) en `zodOutputFormat()` y pásalo a `client.messages.parse()` como `output_config.format`. `zodOutputFormat()` transforma el esquema, y `parse()` valida la respuesta y devuelve el resultado analizado en `parsed_output`, con un tipo que coincide con el esquema.

    El ejemplo anterior produce:

    ```javascript Output
    {
      name: 'John Smith',
      email: 'john@example.com',
      plan_interest: 'Enterprise',
      demo_requested: true
    }
    ```
  </Tab>

  <Tab title="C#">
    ```csharp
    using Anthropic;
    using Anthropic.Models.Messages;
    using Anthropic.Services;

    var client = new AnthropicClient();

    var response = await client.Messages.Create<ContactInfo>(new MessageCreateParams
    {
        Model = Model.ClaudeOpus5_5,
        MaxTokens = 1024,
        Messages = [new() {
            Role = Role.User,
            Content = "Extract the key information from this email: "
                + "John Smith (john@example.com) is interested in our Enterprise plan "
                + "and wants to schedule a demo for next Tuesday at 2pm."
        }],
    });

    if (response.Content.Select(block => block.Parsed()).OfType<ContactInfo>().FirstOrDefault() is { } contact)
    {
        Console.WriteLine(contact);
    }

    public record ContactInfo
    {
        public string Name { get; set; } = "";
        public string Email { get; set; } = "";
        public string PlanInterest { get; set; } = "";
        public bool DemoRequested { get; set; }
    }
    ```

    Pasa una clase o un record simple de C# a la sobrecarga genérica `Create<T>()`. El SDK deriva un esquema JSON del tipo, lo transforma y analiza cada bloque de texto para convertirlo de nuevo en ese tipo.

    El ejemplo anterior produce:

    ```text Output wrap
    ContactInfo { Name = John Smith, Email = john@example.com, PlanInterest = Enterprise, DemoRequested = True }
    ```
  </Tab>

  <Tab title="Go">
    ```go
    type ContactInfo struct {
    	Name          string `json:"name"`
    	Email         string `json:"email"`
    	PlanInterest  string `json:"plan_interest"`
    	DemoRequested bool   `json:"demo_requested"`
    }

    func main() {
    	client := anthropic.NewClient()

    	var contact ContactInfo
    	_, err := client.Beta.Messages.New(context.Background(), anthropic.BetaMessageNewParams{
    		Model:     anthropic.ModelClaudeOpus5_5,
    		MaxTokens: 1024,
    		Messages: []anthropic.BetaMessageParam{
    			anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock(
    				"Extract the key information from this email: " +
    					"John Smith (john@example.com) is interested in our Enterprise plan " +
    					"and wants to schedule a demo for next Tuesday at 2pm.",
    			)),
    		},
    		OutputConfig: anthropic.BetaOutputConfigParam{
    			Format: anthropic.BetaJSONOutputFormatParam{Schema: &contact},
    		},
    	})
    	if err != nil {
    		panic(err)
    	}

    	fmt.Printf("%#v\n", contact)
    }
    ```

    <Note>
      Pasar un struct como esquema está en beta en Go, por lo que este ejemplo usa `client.Beta.Messages.New`. Para usar `client.Messages.New` en GA, pasa un esquema JSON como se muestra en [Usar un esquema JSON sin procesar](https://platform.claude.com/docs/es/build-with-claude/structured-outputs#using-a-raw-json-schema).
    </Note>

    El SDK genera y transforma el esquema JSON a partir del struct y deserializa la respuesta en él.

    El ejemplo anterior produce:

    ```text Output wrap
    main.ContactInfo{Name:"John Smith", Email:"john@example.com", PlanInterest:"Enterprise", DemoRequested:true}
    ```
  </Tab>

  <Tab title="Java">
    ```java
    static class ContactInfo {
        public String name;
        public String email;
        public String plan_interest;
        public boolean demo_requested;
    }

    void main() throws Exception {
        AnthropicClient client = AnthropicOkHttpClient.fromEnv();

        StructuredMessageCreateParams<ContactInfo> params = MessageCreateParams.builder()
            .model(Model.CLAUDE_OPUS_5_5)
            .maxTokens(1024)
            .addUserMessage("Extract the key information from this email: "
                + "John Smith (john@example.com) is interested in our Enterprise plan "
                + "and wants to schedule a demo for next Tuesday at 2pm.")
            .outputConfig(ContactInfo.class)
            .build();

        StructuredMessage<ContactInfo> response = client.messages().create(params);
        ContactInfo contact = response.content().stream()
            .flatMap(block -> block.text().stream())
            .findFirst().orElseThrow().text();
        IO.println(new ObjectMapper().writeValueAsString(contact));
    }
    ```

    Cuando pasas una clase de Java a `outputConfig()`, el SDK deriva un esquema JSON de ella y valida el esquema. Luego, el builder produce un `StructuredMessageCreateParams<T>`. En lugar de transformar el esquema, la validación local del SDK rechaza las [restricciones no compatibles con la API](https://platform.claude.com/docs/es/build-with-claude/structured-outputs#json-schema-limitations). Lee el resultado analizado con `response.content().stream().flatMap(block -> block.text().stream()).findFirst().orElseThrow().text()`.

    El ejemplo anterior produce:

    ```text Output wrap
    {"name":"John Smith","email":"john@example.com","plan_interest":"Enterprise","demo_requested":true}
    ```

    <Note>
      Declara tus clases de esquema como clases de nivel superior o clases anidadas `static`. Este requisito proviene de la biblioteca Jackson Databind (`com.fasterxml.jackson.databind`), que el SDK usa para deserializar respuestas JSON en instancias de tus clases y que no puede instanciar clases internas no estáticas.
    </Note>

    <Accordion title="Borrado de tipos genéricos">
      Java conserva la información de tipos genéricos para los campos en los metadatos de la clase, pero el borrado de tipos genéricos se aplica en otros ámbitos. Aunque se puede derivar un esquema JSON de un campo `BookList.books` con tipo `List<Book>`, no se puede derivar un esquema JSON válido de una variable local de ese mismo tipo.

      Si ocurre un error al convertir una respuesta JSON en una instancia de clase Java, el mensaje de error incluye la respuesta JSON para ayudar en el diagnóstico. Si tu respuesta JSON puede contener información sensible, evita registrarla directamente o asegúrate de ocultar cualquier detalle sensible del mensaje de error.
    </Accordion>

    <Accordion title="Validación local de esquemas">
      Las salidas estructuradas admiten un [subconjunto del lenguaje JSON Schema](https://platform.claude.com/docs/es/build-with-claude/structured-outputs#json-schema-limitations). El SDK genera esquemas automáticamente a partir de clases para alinearse con este subconjunto. El método `outputConfig(Class<T>)` realiza una verificación de validación sobre el esquema derivado de la clase especificada.

      Puntos clave:

      * **La validación local** ocurre sin enviar solicitudes al modelo de IA remoto.
      * **La validación remota** también la realiza el modelo de IA al recibir el esquema JSON.
      * **Compatibilidad de versiones:** La validación local puede fallar mientras la validación remota tiene éxito si la versión del SDK está desactualizada.
      * **Desactivar la validación local:** Pasa `JsonSchemaLocalValidation.NO` si encuentras problemas de compatibilidad:

      ```java
      import com.anthropic.core.JsonSchemaLocalValidation;
      // ...

      static class BookList {
          public List<String> books;
      }

      void main() {
          StructuredMessageCreateParams<BookList> createParams = MessageCreateParams.builder()
              .model(Model.CLAUDE_OPUS_5_5)
              .maxTokens(2048)
              .outputConfig(BookList.class, JsonSchemaLocalValidation.NO)
              .addUserMessage("List some famous late twentieth century novels.")
              .build();
      }
      ```
    </Accordion>

    <Accordion title="Streaming">
      Las salidas estructuradas también funcionan con streaming. Como las respuestas llegan en eventos de stream, necesitas acumular la respuesta completa antes de deserializar el JSON.

      Usa `MessageAccumulator` para recopilar las cadenas JSON del stream. Una vez acumuladas, llama a `MessageAccumulator.message(Class<T>)` para convertir el `Message` acumulado en un `StructuredMessage`, que deserializa automáticamente el JSON en tu clase Java.
    </Accordion>

    <Accordion title="Propiedades del esquema JSON">
      Cuando el SDK deriva un esquema JSON de tus clases Java, incluye de forma predeterminada todas las propiedades representadas por campos `public` o métodos getter `public` y excluye los campos y métodos getter que no son `public`.

      Puedes controlar la visibilidad con anotaciones:

      * `@JsonIgnore` excluye un campo o método getter `public`
      * `@JsonProperty` incluye un campo o método getter que no es `public`

      Si defines campos `private` con métodos getter `public`, el SDK deriva el nombre de la propiedad del getter (por ejemplo, el campo `private` `myValue` con el método `public` `getMyValue()` produce una propiedad `"myValue"`). Para usar un nombre de getter no convencional, anota el método con `@JsonProperty`.

      Cada clase debe definir al menos una propiedad para el esquema JSON. Se produce un error de validación si ningún campo o método getter puede producir propiedades de esquema, como cuando:

      * No hay campos ni métodos getter en la clase
      * Todos los miembros `public` están anotados con `@JsonIgnore`
      * Todos los miembros que no son `public` carecen de anotaciones `@JsonProperty`
      * Un campo usa un tipo `Map`, lo que produce un campo `"properties"` vacío
    </Accordion>

    <Accordion title="Composición y herencia">
      Tus clases Java pueden usar composición y herencia para compartir estructura al definir esquemas JSON. Cada patrón afecta la estructura de salida de manera diferente.

      **La composición** produce una salida JSON anidada. Al derivar un esquema de la clase `Composed` que compone `A` y `B`:

      ```java
      static class A {
          public String a;
      }

      static class B {
          public String b;
      }

      static class Composed {
          public A composedA;
          public B composedB;
      }
      ```

      La salida JSON tiene esta estructura anidada:

      ```json
      {
        "composedA": { "a": "hello" },
        "composedB": { "b": "world" }
      }
      ```

      **La herencia** produce una salida JSON plana. Al derivar un esquema de la clase `Derived` que extiende `Base`:

      ```java
      static class Base {
          public String a;
      }

      static class Derived extends Base {
          public String b;
      }
      ```

      La salida JSON tiene esta estructura plana:

      ```json
      {
        "a": "hello",
        "b": "world"
      }
      ```
    </Accordion>

    <Accordion title="Anotaciones (Jackson y Swagger)">
      Puedes usar anotaciones de Jackson Databind para enriquecer el esquema JSON derivado de tus clases Java:

      ```java
      import com.fasterxml.jackson.annotation.JsonClassDescription;
      import com.fasterxml.jackson.annotation.JsonIgnore;
      import com.fasterxml.jackson.annotation.JsonPropertyDescription;

      static class Person {

        @JsonPropertyDescription("The first name and surname of the person")
        public String name;

        public int birthYear;

        @JsonPropertyDescription("The year the person died, or 'present' if the person is living.")
        public String deathYear;
      }

      @JsonClassDescription("The details of one published book")
      static class Book {

        public String title;
        public Person author;

        @JsonPropertyDescription("The year in which the book was first published.")
        public int publicationYear;

        @JsonIgnore
        public String genre;
      }

      static class BookList {
        public List<Book> books;
      }
      ```

      Resumen de anotaciones:

      * `@JsonClassDescription`: Agrega una descripción a una clase
      * `@JsonPropertyDescription`: Agrega una descripción a un campo o método getter
      * `@JsonIgnore`: Excluye un campo o getter `public` del esquema
      * `@JsonProperty`: Incluye un campo o getter que no es `public` en el esquema

      Si usas `@JsonProperty(required = false)`, el SDK ignora el valor `false`. Los esquemas derivados de clases siempre marcan todas las propiedades como obligatorias.

      También puedes usar las anotaciones `@Schema` y `@ArraySchema` de Swagger Core (OpenAPI 3) para restricciones específicas de tipo:

      ```java
      import io.swagger.v3.oas.annotations.media.ArraySchema;
      import io.swagger.v3.oas.annotations.media.Schema;

      static class Article {

        @ArraySchema(minItems = 1)
        public List<String> authors;

        public String title;

        @Schema(format = "date")
        public String publicationDate;

        public int pageCount;
      }
      ```

      La validación local verifica que no hayas usado ninguna palabra clave de restricción no compatible, pero los valores de las restricciones no se validan localmente. Por ejemplo, un valor de `"format"` no compatible puede pasar la validación local pero causar un error remoto.

      Si usas anotaciones tanto de Jackson como de Swagger para establecer el mismo campo del esquema, la anotación de Jackson tiene prioridad.
    </Accordion>
  </Tab>

  <Tab title="PHP">
    ```php
    use Anthropic\Lib\Concerns\StructuredOutputModelTrait;
    use Anthropic\Lib\Contracts\StructuredOutputModel;

    $client = new Client();

    class ContactInfo implements StructuredOutputModel
    {
        use StructuredOutputModelTrait;

        public string $name;
        public string $email;
        public string $plan_interest;
        public bool $demo_requested;
    }

    $message = $client->messages->create(
        maxTokens: 1024,
        messages: [
            [
                'role' => 'user',
                'content' => 'Extract the key information from this email: '
                    . 'John Smith (john@example.com) is interested in our Enterprise plan '
                    . 'and wants to schedule a demo for next Tuesday at 2pm.',
            ],
        ],
        model: 'claude-opus-5-5',
        outputConfig: ['format' => ContactInfo::class],
    );

    $contact = $message->parsedOutput();
    if ($contact instanceof ContactInfo) {
        var_dump($contact);
    }
    ```

    Define una clase que implemente `StructuredOutputModel` y use `StructuredOutputModelTrait`, y pasa su nombre en `outputConfig: ['format' => MyClass::class]`. El SDK deriva un esquema JSON de los tipos de propiedad de PHP 8 de la clase, lo transforma y devuelve una instancia tipada desde `$message->parsedOutput()`.

    `parsedOutput()` devuelve la instancia de tu modelo en caso de éxito, o `null` (o un arreglo de error) si el análisis falla. Usa `instanceof` para restringir el tipo antes de acceder a los campos.

    El ejemplo anterior produce:

    ```text Output wrap
    object(ContactInfo)#42 (4) {
      ["name"]=>
      string(10) "John Smith"
      ["email"]=>
      string(16) "john@example.com"
      ["plan_interest"]=>
      string(10) "Enterprise"
      ["demo_requested"]=>
      bool(true)
    }
    ```

    <Accordion title="Inferencia de tipos">
      El SDK mapea los tipos de propiedad nativos de PHP 8 a JSON Schema:

      | Tipo PHP                                     | JSON Schema                            |
      | -------------------------------------------- | -------------------------------------- |
      | `string`                                     | `"string"`                             |
      | `int`                                        | `"integer"`                            |
      | `float`                                      | `"number"`                             |
      | `bool`                                       | `"boolean"`                            |
      | `array`                                      | `"array"` (consulta la siguiente nota) |
      | `?type` (anulable)                           | Campo opcional                         |
      | Clase que implementa `StructuredOutputModel` | Objeto anidado                         |

      Para propiedades `array`, el SDK agrega un esquema `items` solo cuando el tipo de elemento es un `StructuredOutputModel` anidado, declarado con `#[Constrained(itemClass: MyModel::class)]` o un docblock `/** @var MyModel[] */`. Los arreglos de escalares (`string[]`, `int[]`) emiten un `{"type":"array"}` sin restricciones.

      Todas las propiedades no anulables se convierten en campos obligatorios.
    </Accordion>

    <Accordion title="Restricciones con el atributo #[Constrained]">
      Agrega restricciones con el atributo `#[Constrained]`:

      ```php
      use Anthropic\Lib\Attributes\Constrained;
      use Anthropic\Lib\Concerns\StructuredOutputModelTrait;
      use Anthropic\Lib\Contracts\StructuredOutputModel;

      class Address implements StructuredOutputModel { use StructuredOutputModelTrait; public string $street; }

      class Profile implements StructuredOutputModel
      {
          use StructuredOutputModelTrait;

          #[Constrained(description: 'Age in years', minimum: 0, maximum: 150)]
          public int $age;

          #[Constrained(format: 'email')]
          public string $email;

          #[Constrained(itemClass: Address::class, minItems: 1)]
          public array $addresses;
      }
      ```

      **Restricciones aplicadas por la API** (enviadas en el esquema): `description`, `format`, `const`, `itemClass`, `minItems` (solo 0 o 1).

      **Restricciones validadas por el SDK** (eliminadas del esquema enviado, agregadas a la descripción y validadas contra la respuesta): `minimum`, `maximum`, `multipleOf`, `minLength`, `maxLength`.
    </Accordion>
  </Tab>

  <Tab title="Ruby">
    ```ruby
    client = Anthropic::Client.new

    class ContactInfo < Anthropic::BaseModel
      required :name, String
      required :email, String
      required :plan_interest, String
      required :demo_requested, Anthropic::Boolean
    end

    message = client.messages.create(
      model: "claude-opus-5-5",
      max_tokens: 1024,
      messages: [{
        role: "user",
        content: "Extract the key information from this email: " \
          "John Smith (john@example.com) is interested in our Enterprise plan " \
          "and wants to schedule a demo for next Tuesday at 2pm."
      }],
      output_config: {format: ContactInfo}
    )

    contact = message.parsed_output
    puts contact
    ```

    Define una clase que extienda `Anthropic::BaseModel` y pásala a `messages.create()` como `output_config: {format: Model}`. El SDK deriva un esquema JSON de la clase, lo transforma y asigna el resultado analizado al atributo `parsed_output` de la respuesta como un objeto de Ruby tipado.

    El ejemplo anterior produce:

    ```text Output wrap
    {name: "John Smith", email: "john@example.com", plan_interest: "Enterprise", demo_requested: true}
    ```

    <Accordion title="Funciones avanzadas de modelos">
      El SDK de Ruby admite funciones adicionales de definición de modelos para esquemas más ricos:

      * **Palabra clave `doc:`:** Agrega descripciones a los campos para una salida de esquema más informativa
      * **`Anthropic::ArrayOf[T]`:** Arreglos tipados. Pasa las restricciones a nivel de arreglo (`min_items:`, `max_items:`) como palabras clave en `required`/`optional`, no en `ArrayOf` en sí
      * **`Anthropic::EnumOf[:a, :b]`:** Campos enum con valores restringidos
      * **`Anthropic::UnionOf[T1, T2]`:** Tipos unión mapeados a `anyOf`

      ```ruby
      class FamousNumber < Anthropic::BaseModel
        required :value, Float
        optional :reason, String, doc: "why is this number mathematically significant?"
      end

      class Output < Anthropic::BaseModel
        required :numbers, Anthropic::ArrayOf[FamousNumber], min_items: 3, max_items: 5
      end

      message = client.messages.create(
        model: "claude-opus-5-5",
        max_tokens: 1024,
        messages: [{role: "user", content: "give me some famous numbers"}],
        output_config: {format: Output}
      )

      message.parsed_output
      # => #<Output numbers=[#<FamousNumber value=3.14159... reason="Pi es...">...]>
      ```
    </Accordion>
  </Tab>
</Tabs>

## Cómo funciona la transformación del SDK

La mayoría de los helpers del SDK transforman los esquemas que usan funciones no compatibles. Los pasos de la transformación:

1. **Eliminar las restricciones no compatibles** (por ejemplo, `minimum`, `maximum`, `minLength`, `maxLength`)
2. **Actualizar las descripciones** agregando cada restricción no compatible a la descripción del campo (por ejemplo, `{minimum: 100}`)
3. **Agregar `additionalProperties: false`** a todos los objetos
4. **Filtrar los formatos de cadena** para dejar solo la lista compatible
5. **Validar las respuestas** contra tu esquema original y todas sus restricciones, si el helper valida respuestas

Claude recibe un esquema simplificado, pero un helper que valida respuestas sigue aplicando cada restricción en tu código.

**Ejemplo:** Un campo con `minimum: 100` se convierte en un entero simple en el esquema enviado, y el SDK agrega `{minimum: 100}` a la descripción del campo. Un helper que valida respuestas sigue comprobando la respuesta contra `minimum: 100`.

## Usar un esquema JSON sin procesar

Para usar un esquema JSON de un archivo, de una especificación OpenAPI o de código que lo construye en tiempo de ejecución, pásalo en `output_config.format`.

<Tabs>
  <Tab title="cURL">
    ```bash
    curl https://api.anthropic.com/v1/messages \
      -H "content-type: application/json" \
      -H "x-api-key: $ANTHROPIC_API_KEY" \
      -H "anthropic-version: 2023-06-01" \
      -d '{
        "model": "claude-opus-5-5",
        "max_tokens": 1024,
        "messages": [
          {
            "role": "user",
            "content": "Extract the key information from this email: John Smith (john@example.com) is interested in our Enterprise plan and wants to schedule a demo for next Tuesday at 2pm."
          }
        ],
        "output_config": {
          "format": {
            "type": "json_schema",
            "schema": {
              "type": "object",
              "properties": {
                "name": {"type": "string"},
                "email": {"type": "string"},
                "plan_interest": {"type": "string"},
                "demo_requested": {"type": "boolean"}
              },
              "required": ["name", "email", "plan_interest", "demo_requested"],
              "additionalProperties": false
            }
          }
        }
      }'
    ```

    Claude devuelve JSON como este en el bloque de contenido de texto de la respuesta:

    ```json Output
    {
      "name": "John Smith",
      "email": "john@example.com",
      "plan_interest": "Enterprise",
      "demo_requested": true
    }
    ```
  </Tab>

  <Tab title="CLI">
    ```bash
    ant messages create \
      --transform 'content.#(type=="text").text|@fromstr' \
      --format jsonl <<'YAML'
    model: claude-opus-5-5
    max_tokens: 1024
    messages:
      - role: user
        content: >-
          Extract the key information from this email: John Smith
          (john@example.com) is interested in our Enterprise plan and wants
          to schedule a demo for next Tuesday at 2pm.
    output_config:
      format:
        type: json_schema
        schema:
          type: object
          properties:
            name: {type: string}
            email: {type: string}
            plan_interest: {type: string}
            demo_requested: {type: boolean}
          required: [name, email, plan_interest, demo_requested]
          additionalProperties: false
    YAML
    ```

    El ejemplo anterior produce:

    ```text Output wrap
    {"name":"John Smith","email":"john@example.com","plan_interest":"Enterprise","demo_requested":true}
    ```
  </Tab>

  <Tab title="Python">
    ```python
    client = anthropic.Anthropic()

    response = client.messages.create(
        model="claude-opus-5-5",
        max_tokens=1024,
        messages=[
            {
                "role": "user",
                "content": (
                    "Extract the key information from this email: "
                    "John Smith (john@example.com) is interested in our Enterprise plan "
                    "and wants to schedule a demo for next Tuesday at 2pm."
                ),
            }
        ],
        output_config={
            "format": {
                "type": "json_schema",
                "schema": {
                    "type": "object",
                    "properties": {
                        "name": {"type": "string"},
                        "email": {"type": "string"},
                        "plan_interest": {"type": "string"},
                        "demo_requested": {"type": "boolean"},
                    },
                    "required": ["name", "email", "plan_interest", "demo_requested"],
                    "additionalProperties": False,
                },
            }
        },
    )
    text = next(block.text for block in response.content if block.type == "text")
    contact = json.loads(text)
    print(contact)
    ```

    El ejemplo anterior produce:

    ```text Output wrap
    {'name': 'John Smith', 'email': 'john@example.com', 'plan_interest': 'Enterprise', 'demo_requested': True}
    ```

    Para mover las [restricciones no compatibles con la API](https://platform.claude.com/docs/es/build-with-claude/structured-outputs#json-schema-limitations) a las descripciones de los campos, pasa el esquema por `transform_schema()` del paquete `anthropic` antes de enviarlo, y edita el resultado si lo necesitas. `transform_schema()` también acepta un modelo de Pydantic. A diferencia de `client.messages.parse()`, devuelve el esquema transformado en lugar de enviarlo.
  </Tab>

  <Tab title="TypeScript">
    ```typescript
    import { jsonSchemaOutputFormat } from "@anthropic-ai/sdk/helpers/json-schema";

    const client = new Anthropic();

    const response = await client.messages.parse({
      model: "claude-opus-5-5",
      max_tokens: 1024,
      messages: [
        {
          role: "user",
          content: "Extract contact info: John Smith, john@example.com, interested in the Pro plan"
        }
      ],
      output_config: {
        format: jsonSchemaOutputFormat({
          type: "object",
          properties: {
            name: { type: "string" },
            email: { type: "string" },
            planInterest: { type: "string" }
          },
          required: ["name", "email", "planInterest"],
          additionalProperties: false
        } as const)
      }
    });

    // response.parsed_output tiene el tipo { name: string; email: string; planInterest: string } | null
    console.log(response.parsed_output);
    ```

    Usa `jsonSchemaOutputFormat()` para pasar un esquema JSON simple a `parse()`, sin instalar Zod. La API devuelve una respuesta que cumple con el esquema y, si declaras el esquema con `as const`, TypeScript infiere automáticamente el tipo de `parsed_output` según tu esquema. Para un esquema importado o generado, `parsed_output` tiene el tipo `unknown`.

    De forma predeterminada, el helper [transforma el esquema](https://platform.claude.com/docs/es/build-with-claude/structured-outputs#how-sdk-transformation-works) de la misma manera que `zodOutputFormat()`. Pasa `{ transform: false }` como segundo argumento para enviarlo sin cambios. A diferencia de `zodOutputFormat()`, no valida la respuesta, por lo que no detectará infracciones de [restricciones no compatibles con la API](https://platform.claude.com/docs/es/build-with-claude/structured-outputs#json-schema-limitations), como `minimum`.

    El ejemplo anterior produce:

    ```javascript Output
    { name: 'John Smith', email: 'john@example.com', planInterest: 'Pro' }
    ```
  </Tab>

  <Tab title="C#">
    ```csharp
    using System.Text.Json;
    using Anthropic;
    using Anthropic.Models.Messages;

    var client = new AnthropicClient();

    var response = await client.Messages.Create(new MessageCreateParams
    {
        Model = Model.ClaudeOpus5_5,
        MaxTokens = 1024,
        Messages = [new() {
            Role = Role.User,
            Content = "Extract the key information from this email: "
                + "John Smith (john@example.com) is interested in our Enterprise plan "
                + "and wants to schedule a demo for next Tuesday at 2pm."
        }],
        OutputConfig = new OutputConfig
        {
            Format = new JsonOutputFormat
            {
                Schema = new Dictionary<string, JsonElement>
                {
                    ["type"] = JsonSerializer.SerializeToElement("object"),
                    ["properties"] = JsonSerializer.SerializeToElement(new
                    {
                        name = new { type = "string" },
                        email = new { type = "string" },
                        plan_interest = new { type = "string" },
                        demo_requested = new { type = "boolean" },
                    }),
                    ["required"] = JsonSerializer.SerializeToElement(
                        new[] { "name", "email", "plan_interest", "demo_requested" }),
                    ["additionalProperties"] = JsonSerializer.SerializeToElement(false),
                },
            },
        },
    });

    if (response.Content.Select(b => b.Value).OfType<TextBlock>().FirstOrDefault() is { } textBlock)
    {
        // Se garantiza que el JSON coincide con el esquema
        var contact = JsonSerializer.Deserialize<JsonElement>(textBlock.Text);
        Console.WriteLine(contact);
    }
    ```

    El ejemplo anterior produce:

    ```text Output wrap
    {"name":"John Smith","email":"john@example.com","plan_interest":"Enterprise","demo_requested":true}
    ```
  </Tab>

  <Tab title="Go">
    ```go
    type ContactInfo struct {
    	Name          string `json:"name"`
    	Email         string `json:"email"`
    	PlanInterest  string `json:"plan_interest"`
    	DemoRequested bool   `json:"demo_requested"`
    }

    func main() {
    	client := anthropic.NewClient()

    	message, _ := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
    		Model:     anthropic.ModelClaudeOpus5_5,
    		MaxTokens: 1024,
    		Messages: []anthropic.MessageParam{
    			anthropic.NewUserMessage(anthropic.NewTextBlock(
    				"Extract the key information from this email: " +
    					"John Smith (john@example.com) is interested in our Enterprise plan " +
    					"and wants to schedule a demo for next Tuesday at 2pm.",
    			)),
    		},
    		OutputConfig: anthropic.OutputConfigParam{
    			Format: anthropic.JSONOutputFormatParam{
    				Schema: map[string]any{
    					"type": "object",
    					"properties": map[string]any{
    						"name":           map[string]string{"type": "string"},
    						"email":          map[string]string{"type": "string"},
    						"plan_interest":  map[string]string{"type": "string"},
    						"demo_requested": map[string]string{"type": "boolean"},
    					},
    					"required":             []string{"name", "email", "plan_interest", "demo_requested"},
    					"additionalProperties": false,
    				},
    			},
    		},
    	})

    	for _, block := range message.Content {
    		switch variant := block.AsAny().(type) {
    		case anthropic.TextBlock:
    			var contact ContactInfo
    			json.Unmarshal([]byte(variant.Text), &contact)
    			fmt.Printf("%#v\n", contact)
    		}
    	}
    }
    ```

    El ejemplo anterior produce:

    ```text Output wrap
    main.ContactInfo{Name:"John Smith", Email:"john@example.com", PlanInterest:"Enterprise", DemoRequested:true}
    ```

    Para que el SDK transforme un esquema de tipo map, pásalo por `BetaJSONSchemaOutputFormat()` en la API beta.
  </Tab>

  <Tab title="Java">
    ```java
    import com.anthropic.core.JsonValue;
    import com.anthropic.models.messages.JsonOutputFormat;
    // ...
    import com.anthropic.models.messages.OutputConfig;
    import com.fasterxml.jackson.databind.JsonNode;
    import com.fasterxml.jackson.databind.ObjectMapper;

    void main() throws Exception {
        AnthropicClient client = AnthropicOkHttpClient.fromEnv();

        JsonOutputFormat.Schema schema = JsonOutputFormat.Schema.builder()
            .putAdditionalProperty("type", JsonValue.from("object"))
            .putAdditionalProperty("properties", JsonValue.from(Map.of(
                "name", Map.of("type", "string"),
                "email", Map.of("type", "string"),
                "plan_interest", Map.of("type", "string"))))
            .putAdditionalProperty("required", JsonValue.from(
                List.of("name", "email", "plan_interest")))
            .putAdditionalProperty("additionalProperties", JsonValue.from(false))
            .build();

        OutputConfig outputConfig = OutputConfig.builder()
            .format(JsonOutputFormat.builder().schema(schema).build())
            .build();

        MessageCreateParams createParams = MessageCreateParams.builder()
            .model(Model.CLAUDE_OPUS_5_5)
            .maxTokens(1024)
            .outputConfig(outputConfig)
            .addUserMessage(
                "John Smith (john@example.com) is interested in our Enterprise plan.")
            .build();

        String json = client.messages().create(createParams).content().stream()
            .flatMap(contentBlock -> contentBlock.text().stream())
            .findFirst()
            .orElseThrow()
            .text();

        JsonNode contact = new ObjectMapper().readTree(json);
        IO.println(contact);
    }
    ```

    El ejemplo anterior produce:

    ```text Output wrap
    {"name":"John Smith","email":"john@example.com","plan_interest":"Enterprise"}
    ```

    Para ver un ejemplo más extenso que construye un esquema anidado con arrays y descripciones, consulta [`StructuredOutputsRawExample.java`](https://github.com/anthropics/anthropic-sdk-java/blob/main/anthropic-java-example/src/main/java/com/anthropic/example/StructuredOutputsRawExample.java) en el repositorio del SDK.
  </Tab>

  <Tab title="PHP">
    ```php
    use Anthropic\Messages\OutputConfig;
    use Anthropic\Messages\JSONOutputFormat;

    $client = new Client();

    $message = $client->messages->create(
        maxTokens: 1024,
        messages: [
            [
                'role' => 'user',
                'content' => 'Extract the key information from this email: '
                    . 'John Smith (john@example.com) is interested in our Enterprise plan.',
            ],
        ],
        model: 'claude-opus-5-5',
        outputConfig: OutputConfig::with(format: JSONOutputFormat::with(schema: [
            'type' => 'object',
            'properties' => [
                'name' => ['type' => 'string'],
                'email' => ['type' => 'string'],
                'plan_interest' => ['type' => 'string'],
            ],
            'required' => ['name', 'email', 'plan_interest'],
            'additionalProperties' => false,
        ])),
    );

    $textBlock = array_find($message->content, static fn ($block): bool => $block->type === 'text');
    $contact = json_decode($textBlock->text, associative: true);
    var_dump($contact);
    ```

    Pasa el esquema como un array asociativo a `OutputConfig::with()` y decodifica la respuesta con `json_decode()`.

    El ejemplo anterior produce:

    ```text Output wrap
    array(3) {
      ["name"]=>
      string(10) "John Smith"
      ["email"]=>
      string(16) "john@example.com"
      ["plan_interest"]=>
      string(10) "Enterprise"
    }
    ```
  </Tab>

  <Tab title="Ruby">
    ```ruby
    client = Anthropic::Client.new

    response = client.messages.create(
      model: "claude-opus-5-5",
      max_tokens: 1024,
      messages: [
        {
          role: "user",
          content: "Extract the key information from this email: " \
            "John Smith (john@example.com) is interested in our Enterprise plan " \
            "and wants to schedule a demo for next Tuesday at 2pm."
        }
      ],
      output_config: {
        format: {
          type: "json_schema",
          schema: {
            type: "object",
            properties: {
              name: { type: "string" },
              email: { type: "string" },
              plan_interest: { type: "string" },
              demo_requested: { type: "boolean" }
            },
            required: ["name", "email", "plan_interest", "demo_requested"],
            additionalProperties: false
          }
        }
      }
    )

    text = response.content.find { it.type == :text }.text
    contact = JSON.parse(text)
    p contact
    ```

    El ejemplo anterior produce:

    ```text Output wrap
    {"name" => "John Smith", "email" => "john@example.com", "plan_interest" => "Enterprise", "demo_requested" => true}
    ```
  </Tab>
</Tabs>

## Casos de uso comunes

<AccordionGroup>
  <Accordion title="Extracción de datos">
    Extrae datos estructurados de texto no estructurado:

    <CodeGroup>
      ```bash cURL
      curl https://api.anthropic.com/v1/messages \
        -H "content-type: application/json" \
        -H "x-api-key: $ANTHROPIC_API_KEY" \
        -H "anthropic-version: 2023-06-01" \
        -d '{
          "model": "claude-opus-5-5",
          "max_tokens": 4096,
          "messages": [
            {
              "role": "user",
              "content": "Extract invoice data from: Invoice #12345, Date: 2024-01-15, Total: $500.00"
            }
          ],
          "output_config": {
            "format": {
              "type": "json_schema",
              "schema": {
                "type": "object",
                "properties": {
                  "invoice_number": {"type": "string"},
                  "date": {"type": "string"},
                  "total_amount": {"type": "number"},
                  "line_items": {
                    "type": "array",
                    "items": {"type": "object", "additionalProperties": false}
                  },
                  "customer_name": {"type": "string"}
                },
                "required": ["invoice_number", "date", "total_amount", "line_items", "customer_name"],
                "additionalProperties": false
              }
            }
          }
        }'
      ```

      ```bash CLI
      ant messages create \
        --transform 'content.#(type=="text").text|@fromstr' \
        --format jsonl <<'YAML'
      model: claude-opus-5-5
      max_tokens: 4096
      messages:
        - role: user
          content: "Extract invoice data from: Invoice #12345, Date: 2024-01-15, Total: $500.00"
      output_config:
        format:
          type: json_schema
          schema:
            type: object
            properties:
              invoice_number: {type: string}
              date: {type: string}
              total_amount: {type: number}
              line_items:
                type: array
                items: {type: object, additionalProperties: false}
              customer_name: {type: string}
            required: [invoice_number, date, total_amount, line_items, customer_name]
            additionalProperties: false
      YAML
      ```

      ```python Python
      from pydantic import BaseModel


      class Invoice(BaseModel):
          invoice_number: str
          date: str
          total_amount: float
          line_items: list[dict]
          customer_name: str


      client = anthropic.Anthropic()
      invoice_text = "Invoice #12345, Date: 2024-01-15, Total: $500.00"

      response = client.messages.parse(
          model="claude-opus-5-5",
          max_tokens=4096,
          output_format=Invoice,
          messages=[
              {"role": "user", "content": f"Extract invoice data from: {invoice_text}"}
          ],
      )

      print(response.parsed_output)
      ```

      ```typescript TypeScript
      import { z } from "zod";
      import { zodOutputFormat } from "@anthropic-ai/sdk/helpers/zod";

      const client = new Anthropic();

      const InvoiceSchema = z.object({
        invoice_number: z.string(),
        date: z.string(),
        total_amount: z.number(),
        line_items: z.array(z.record(z.string(), z.any())),
        customer_name: z.string()
      });

      const invoiceText = "Invoice #12345, Date: 2024-01-15, Total: $500.00";
      const response = await client.messages.parse({
        model: "claude-opus-5-5",
        max_tokens: 4096,
        output_config: { format: zodOutputFormat(InvoiceSchema) },
        messages: [{ role: "user", content: `Extract invoice data from: ${invoiceText}` }]
      });
      console.log(response.parsed_output);
      ```

      ```csharp C#
      AnthropicClient client = new();

      string invoiceText = "Invoice #12345, Date: 2024-01-15, Total: $500.00";

      var parameters = new MessageCreateParams
      {
          Model = Model.ClaudeOpus5_5,
          MaxTokens = 4096,
          OutputConfig = new OutputConfig
          {
              Format = new JsonOutputFormat
              {
                  Schema = new Dictionary<string, JsonElement>
                  {
                      ["type"] = JsonSerializer.SerializeToElement("object"),
                      ["properties"] = JsonSerializer.SerializeToElement(new
                      {
                          invoice_number = new { type = "string" },
                          date = new { type = "string" },
                          total_amount = new { type = "number" },
                          line_items = new
                          {
                              type = "array",
                              items = new
                              {
                                  type = "object",
                                  additionalProperties = false,
                              },
                          },
                          customer_name = new { type = "string" },
                      }),
                      ["required"] = JsonSerializer.SerializeToElement(new[] { "invoice_number", "date", "total_amount", "line_items", "customer_name" }),
                      ["additionalProperties"] = JsonSerializer.SerializeToElement(false),
                  },
              },
          },
          Messages = [new() { Role = Role.User, Content = $"Extract invoice data from: {invoiceText}" }]
      };

      var message = await client.Messages.Create(parameters);
      Console.WriteLine(message);
      ```

      ```go Go
      client := anthropic.NewClient()

      invoiceText := "Invoice #12345, Date: 2024-01-15, Total: $500.00"

      schema := map[string]any{
      	"type":                 "object",
      	"additionalProperties": false,
      	"properties": map[string]any{
      		"invoice_number": map[string]any{"type": "string"},
      		"date":           map[string]any{"type": "string"},
      		"total_amount":   map[string]any{"type": "number"},
      		"line_items": map[string]any{
      			"type": "array",
      			"items": map[string]any{
      				"type":                 "object",
      				"additionalProperties": false,
      				"properties": map[string]any{
      					"description": map[string]any{"type": "string"},
      					"quantity":    map[string]any{"type": "number"},
      					"unit_price":  map[string]any{"type": "number"},
      				},
      				"required": []string{"description", "quantity", "unit_price"},
      			},
      		},
      		"customer_name": map[string]any{"type": "string"},
      	},
      	"required": []string{"invoice_number", "date", "total_amount", "line_items", "customer_name"},
      }

      response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
      	Model:     anthropic.ModelClaudeOpus5_5,
      	MaxTokens: 4096,
      	OutputConfig: anthropic.OutputConfigParam{
      		Format: anthropic.JSONOutputFormatParam{
      			Schema: schema,
      		},
      	},
      	Messages: []anthropic.MessageParam{
      		anthropic.NewUserMessage(anthropic.NewTextBlock(fmt.Sprintf("Extract invoice data from: %s", invoiceText))),
      	},
      })
      if err != nil {
      	log.Fatal(err)
      }
      for _, block := range response.Content {
      	switch variant := block.AsAny().(type) {
      	case anthropic.TextBlock:
      		fmt.Println(variant.Text)
      	}
      }
      ```

      ```java Java
      import com.fasterxml.jackson.annotation.JsonProperty;

      static class LineItem {
          @JsonProperty("description")
          public String description;

          @JsonProperty("quantity")
          public int quantity;

          @JsonProperty("unit_price")
          public double unitPrice;
      }

      static class Invoice {
          @JsonProperty("invoice_number")
          public String invoiceNumber;

          @JsonProperty("date")
          public String date;

          @JsonProperty("total_amount")
          public double totalAmount;

          @JsonProperty("line_items")
          public List<LineItem> lineItems;

          @JsonProperty("customer_name")
          public String customerName;
      }

      void main() {
          AnthropicClient client = AnthropicOkHttpClient.fromEnv();

          String invoiceText = "Invoice #12345, Date: 2024-01-15, Total: $500.00";

          StructuredMessageCreateParams<Invoice> params = MessageCreateParams.builder()
              .model(Model.CLAUDE_OPUS_5_5)
              .maxTokens(4096L)
              .outputConfig(Invoice.class)
              .addUserMessage("Extract invoice data from: " + invoiceText)
              .build();

          StructuredMessage<Invoice> response = client.messages().create(params);
          Invoice invoice = response.content().stream()
              .flatMap(block -> block.text().stream())
              .findFirst().orElseThrow().text();
          IO.println(invoice.invoiceNumber + ": $" + invoice.totalAmount);
      }
      ```

      ```php PHP
      use Anthropic\Lib\Concerns\StructuredOutputModelTrait;
      use Anthropic\Lib\Contracts\StructuredOutputModel;

      $client = new Client();

      class Invoice implements StructuredOutputModel
      {
          use StructuredOutputModelTrait;

          public string $invoice_number;
          public string $date;
          public float $total_amount;
          public array $line_items;
          public string $customer_name;
      }

      $invoiceText = "Invoice #12345, Date: 2024-01-15, Total: $500.00";

      $message = $client->messages->create(
          maxTokens: 4096,
          messages: [
              ['role' => 'user', 'content' => "Extract invoice data from: $invoiceText"]
          ],
          model: 'claude-opus-5-5',
          outputConfig: ['format' => Invoice::class],
      );

      $invoice = $message->parsedOutput();
      if ($invoice instanceof Invoice) {
          echo "Invoice {$invoice->invoice_number}: \${$invoice->total_amount}\n";
      }
      ```

      ```ruby Ruby
      client = Anthropic::Client.new

      class LineItem < Anthropic::BaseModel
        required :description, String
        required :amount, Float
      end

      class Invoice < Anthropic::BaseModel
        required :invoice_number, String
        required :date, String
        required :total_amount, Float
        required :line_items, Anthropic::ArrayOf[LineItem]
        required :customer_name, String
      end

      invoice_text = "Invoice #12345, Date: 2024-01-15, Total: $500.00"

      message = client.messages.create(
        model: "claude-opus-5-5",
        max_tokens: 4096,
        output_config: {format: Invoice},
        messages: [
          {role: "user", content: "Extract invoice data from: #{invoice_text}"}
        ]
      )

      invoice = message.parsed_output
      puts "Invoice #{invoice.invoice_number}: $#{invoice.total_amount}"
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="Clasificación">
    Clasifica contenido con categorías estructuradas:

    <CodeGroup>
      ```bash cURL
      curl https://api.anthropic.com/v1/messages \
        -H "content-type: application/json" \
        -H "x-api-key: $ANTHROPIC_API_KEY" \
        -H "anthropic-version: 2023-06-01" \
        -d '{
          "model": "claude-opus-5-5",
          "max_tokens": 1024,
          "messages": [
            {
              "role": "user",
              "content": "Classify this feedback: Great product, fast shipping!"
            }
          ],
          "output_config": {
            "format": {
              "type": "json_schema",
              "schema": {
                "type": "object",
                "properties": {
                  "category": {"type": "string"},
                  "confidence": {"type": "number"},
                  "tags": {"type": "array", "items": {"type": "string"}},
                  "sentiment": {"type": "string"}
                },
                "required": ["category", "confidence", "tags", "sentiment"],
                "additionalProperties": false
              }
            }
          }
        }'
      ```

      ```bash CLI
      ant messages create \
        --transform 'content.#(type=="text").text|@fromstr' \
        --format jsonl <<'YAML'
      model: claude-opus-5-5
      max_tokens: 1024
      messages:
        - role: user
          content: "Classify this feedback: Great product, fast shipping!"
      output_config:
        format:
          type: json_schema
          schema:
            type: object
            properties:
              category:
                type: string
              confidence:
                type: number
              tags:
                type: array
                items:
                  type: string
              sentiment:
                type: string
            required:
              - category
              - confidence
              - tags
              - sentiment
            additionalProperties: false
      YAML
      ```

      ```python Python
      from pydantic import BaseModel

      client = Anthropic()


      class Classification(BaseModel):
          category: str
          confidence: float
          tags: list[str]
          sentiment: str


      feedback_text = "Great product, but the delivery was slow."
      response = client.messages.parse(
          model="claude-opus-5-5",
          max_tokens=1024,
          output_format=Classification,
          messages=[{"role": "user", "content": f"Classify this feedback: {feedback_text}"}],
      )

      print(response.parsed_output)
      ```

      ```typescript TypeScript
      import { z } from "zod";
      import { zodOutputFormat } from "@anthropic-ai/sdk/helpers/zod";

      const client = new Anthropic();

      const ClassificationSchema = z.object({
        category: z.string(),
        confidence: z.number(),
        tags: z.array(z.string()),
        sentiment: z.string()
      });

      const feedbackText = "Great product, but the delivery was slow.";
      const response = await client.messages.parse({
        model: "claude-opus-5-5",
        max_tokens: 1024,
        output_config: { format: zodOutputFormat(ClassificationSchema) },
        messages: [{ role: "user", content: `Classify this feedback: ${feedbackText}` }]
      });

      console.log(response.parsed_output);
      ```

      ```csharp C#
      string feedbackText = "Great product, fast shipping!";

      var parameters = new MessageCreateParams
      {
          Model = Model.ClaudeOpus5_5,
          MaxTokens = 1024,
          Messages = [new() { Role = Role.User, Content = $"Classify this feedback: {feedbackText}" }],
          OutputConfig = new OutputConfig
          {
              Format = new JsonOutputFormat
              {
                  Schema = new Dictionary<string, JsonElement>
                  {
                      ["type"] = JsonSerializer.SerializeToElement("object"),
                      ["properties"] = JsonSerializer.SerializeToElement(new
                      {
                          category = new { type = "string" },
                          confidence = new { type = "number" },
                          tags = new { type = "array", items = new { type = "string" } },
                          sentiment = new { type = "string" },
                      }),
                      ["required"] = JsonSerializer.SerializeToElement(new[] { "category", "confidence", "tags", "sentiment" }),
                      ["additionalProperties"] = JsonSerializer.SerializeToElement(false),
                  },
              },
          },
      };

      var message = await client.Messages.Create(parameters);
      Console.WriteLine(message);
      ```

      ```go Go
      feedbackText := "Great product, fast shipping!"

      schema := map[string]any{
      	"type": "object",
      	"properties": map[string]any{
      		"category":   map[string]any{"type": "string"},
      		"confidence": map[string]any{"type": "number"},
      		"tags":       map[string]any{"type": "array", "items": map[string]any{"type": "string"}},
      		"sentiment":  map[string]any{"type": "string"},
      	},
      	"required":             []string{"category", "confidence", "tags", "sentiment"},
      	"additionalProperties": false,
      }

      response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
      	Model:     anthropic.ModelClaudeOpus5_5,
      	MaxTokens: 1024,
      	OutputConfig: anthropic.OutputConfigParam{
      		Format: anthropic.JSONOutputFormatParam{
      			Schema: schema,
      		},
      	},
      	Messages: []anthropic.MessageParam{
      		anthropic.NewUserMessage(anthropic.NewTextBlock(fmt.Sprintf("Classify this feedback: %s", feedbackText))),
      	},
      })
      if err != nil {
      	log.Fatal(err)
      }
      for _, block := range response.Content {
      	switch variant := block.AsAny().(type) {
      	case anthropic.TextBlock:
      		var result map[string]any
      		json.Unmarshal([]byte(variant.Text), &result)
      		fmt.Println(result)
      	}
      }
      ```

      ```java Java
      import com.fasterxml.jackson.annotation.JsonProperty;

      static class Classification {
          @JsonProperty("category")
          public String category;

          @JsonProperty("confidence")
          public double confidence;

          @JsonProperty("tags")
          public List<String> tags;

          @JsonProperty("sentiment")
          public String sentiment;
      }

      void main() {
          AnthropicClient client = AnthropicOkHttpClient.fromEnv();

          String feedbackText = "Great product, fast shipping!";

          StructuredMessageCreateParams<Classification> params = MessageCreateParams.builder()
              .model(Model.CLAUDE_OPUS_5_5)
              .maxTokens(1024L)
              .outputConfig(Classification.class)
              .addUserMessage("Classify this feedback: " + feedbackText)
              .build();

          StructuredMessage<Classification> response = client.messages().create(params);
          Classification result = response.content().stream()
              .flatMap(block -> block.text().stream())
              .findFirst().orElseThrow().text();
          IO.println(result.category + " (" + result.confidence + ")");
      }
      ```

      ```php PHP
      use Anthropic\Lib\Concerns\StructuredOutputModelTrait;
      use Anthropic\Lib\Contracts\StructuredOutputModel;

      $client = new Client();

      class Classification implements StructuredOutputModel
      {
          use StructuredOutputModelTrait;

          public string $category;
          public float $confidence;
          public array $tags;
          public string $sentiment;
      }

      $feedbackText = "Great product, fast shipping!";

      $message = $client->messages->create(
          maxTokens: 1024,
          messages: [
              ['role' => 'user', 'content' => "Classify this feedback: {$feedbackText}"]
          ],
          model: 'claude-opus-5-5',
          outputConfig: ['format' => Classification::class],
      );

      $result = $message->parsedOutput();
      if ($result instanceof Classification) {
          echo "{$result->category} ({$result->confidence}): {$result->sentiment}\n";
      }
      ```

      ```ruby Ruby
      client = Anthropic::Client.new

      class Classification < Anthropic::BaseModel
        required :category, String
        required :confidence, Float
        required :tags, Anthropic::ArrayOf[String]
        required :sentiment, String
      end

      feedback_text = "Great product, fast shipping!"

      message = client.messages.create(
        model: "claude-opus-5-5",
        max_tokens: 1024,
        output_config: {format: Classification},
        messages: [
          {role: "user", content: "Classify this feedback: #{feedback_text}"}
        ]
      )
      puts message.parsed_output
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="Formateo de respuestas de API">
    Genera respuestas listas para API:

    <CodeGroup>
      ```bash cURL
      curl https://api.anthropic.com/v1/messages \
        -H "content-type: application/json" \
        -H "x-api-key: $ANTHROPIC_API_KEY" \
        -H "anthropic-version: 2023-06-01" \
        -d '{
          "model": "claude-opus-5-5",
          "max_tokens": 1024,
          "messages": [
            {
              "role": "user",
              "content": "Process this request: ..."
            }
          ],
          "output_config": {
            "format": {
              "type": "json_schema",
              "schema": {
                "type": "object",
                "properties": {
                  "status": {"type": "string"},
                  "data": {"type": "object", "additionalProperties": false},
                  "errors": {
                    "type": "array",
                    "items": {"type": "object", "additionalProperties": false}
                  },
                  "metadata": {"type": "object", "additionalProperties": false}
                },
                "required": ["status", "data", "metadata"],
                "additionalProperties": false
              }
            }
          }
        }'
      ```

      ```bash CLI
      ant messages create \
        --transform 'content.#(type=="text").text' \
        --raw-output <<'YAML'
      model: claude-opus-5-5
      max_tokens: 1024
      output_config:
        format:
          type: json_schema
          schema:
            type: object
            properties:
              status:
                type: string
              data:
                type: object
                additionalProperties: false
              errors:
                type: array
                items:
                  type: object
                  additionalProperties: false
              metadata:
                type: object
                additionalProperties: false
            required:
              - status
              - data
              - metadata
            additionalProperties: false
      messages:
        - role: user
          content: "Process this request: ..."
      YAML
      ```

      ```python Python
      from pydantic import BaseModel

      client = Anthropic()


      class APIResponse(BaseModel):
          status: str
          data: dict
          errors: list[dict] | None
          metadata: dict


      response = client.messages.parse(
          model="claude-opus-5-5",
          max_tokens=1024,
          output_format=APIResponse,
          messages=[{"role": "user", "content": "Process this request: ..."}],
      )

      print(response.parsed_output)
      ```

      ```typescript TypeScript
      import { z } from "zod";
      import { zodOutputFormat } from "@anthropic-ai/sdk/helpers/zod";

      const client = new Anthropic();

      const APIResponseSchema = z.object({
        status: z.string(),
        data: z.record(z.string(), z.any()),
        errors: z.array(z.record(z.string(), z.any())).optional(),
        metadata: z.record(z.string(), z.any())
      });

      const response = await client.messages.parse({
        model: "claude-opus-5-5",
        max_tokens: 1024,
        output_config: { format: zodOutputFormat(APIResponseSchema) },
        messages: [{ role: "user", content: "Process this request..." }]
      });

      console.log(response.parsed_output);
      ```

      ```csharp C#
      var parameters = new MessageCreateParams
      {
          Model = Model.ClaudeOpus5_5,
          MaxTokens = 1024,
          Messages = [new() { Role = Role.User, Content = "Process this request: ..." }],
          OutputConfig = new OutputConfig
          {
              Format = new JsonOutputFormat
              {
                  Schema = new Dictionary<string, JsonElement>
                  {
                      ["type"] = JsonSerializer.SerializeToElement("object"),
                      ["properties"] = JsonSerializer.SerializeToElement(new
                      {
                          status = new { type = "string" },
                          data = new { type = "object", additionalProperties = false },
                          errors = new
                          {
                              type = "array",
                              items = new { type = "object", additionalProperties = false },
                          },
                          metadata = new { type = "object", additionalProperties = false },
                      }),
                      ["required"] = JsonSerializer.SerializeToElement(new[] { "status", "data", "metadata" }),
                      ["additionalProperties"] = JsonSerializer.SerializeToElement(false),
                  },
              },
          },
      };

      var message = await client.Messages.Create(parameters);
      Console.WriteLine(message);
      ```

      ```go Go
      client := anthropic.NewClient()

      response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
      	Model:     anthropic.ModelClaudeOpus5_5,
      	MaxTokens: 1024,
      	OutputConfig: anthropic.OutputConfigParam{
      		Format: anthropic.JSONOutputFormatParam{
      			Schema: map[string]any{
      				"type":                 "object",
      				"additionalProperties": false,
      				"properties": map[string]any{
      					"status": map[string]any{
      						"type": "string",
      					},
      					"data": map[string]any{
      						"type":                 "object",
      						"additionalProperties": false,
      					},
      					"errors": map[string]any{
      						"type": "array",
      						"items": map[string]any{
      							"type":                 "object",
      							"additionalProperties": false,
      						},
      					},
      					"metadata": map[string]any{
      						"type":                 "object",
      						"additionalProperties": false,
      					},
      				},
      				"required": []string{"status", "data", "metadata"},
      			},
      		},
      	},
      	Messages: []anthropic.MessageParam{
      		anthropic.NewUserMessage(anthropic.NewTextBlock("Process this request: ...")),
      	},
      })
      if err != nil {
      	log.Fatal(err)
      }
      for _, block := range response.Content {
      	switch variant := block.AsAny().(type) {
      	case anthropic.TextBlock:
      		fmt.Println(variant.Text)
      	}
      }
      ```

      ```java Java
      import com.fasterxml.jackson.annotation.JsonProperty;

      static class APIData {
          @JsonProperty("message")
          public String message;

          @JsonProperty("resource_id")
          public String resourceId;
      }

      static class APIError {
          @JsonProperty("code")
          public String code;

          @JsonProperty("message")
          public String message;
      }

      static class APIMetadata {
          @JsonProperty("request_id")
          public String requestId;

          @JsonProperty("timestamp")
          public String timestamp;
      }

      static class APIResponse {
          @JsonProperty("status")
          public String status;

          @JsonProperty("data")
          public APIData data;

          @JsonProperty("errors")
          public List<APIError> errors;

          @JsonProperty("metadata")
          public APIMetadata metadata;
      }

      void main() {
          AnthropicClient client = AnthropicOkHttpClient.fromEnv();

          StructuredMessageCreateParams<APIResponse> params = MessageCreateParams.builder()
              .model(Model.CLAUDE_OPUS_5_5)
              .maxTokens(1024L)
              .outputConfig(APIResponse.class)
              .addUserMessage("Process this request: ...")
              .build();

          StructuredMessage<APIResponse> response = client.messages().create(params);
          APIResponse result = response.content().stream()
              .flatMap(block -> block.text().stream())
              .findFirst().orElseThrow().text();
          IO.println(result.status);
      }
      ```

      ```php PHP
      use Anthropic\Lib\Attributes\Constrained;
      use Anthropic\Lib\Concerns\StructuredOutputModelTrait;
      use Anthropic\Lib\Contracts\StructuredOutputModel;

      $client = new Client();

      class Payload implements StructuredOutputModel { use StructuredOutputModelTrait; public string $message; }

      class APIError implements StructuredOutputModel { use StructuredOutputModelTrait; public string $code; public string $detail; }

      class Metadata implements StructuredOutputModel { use StructuredOutputModelTrait; public string $request_id; }

      class APIResponse implements StructuredOutputModel
      {
          use StructuredOutputModelTrait;

          public string $status;
          public Payload $data;
          #[Constrained(itemClass: APIError::class)]
          public ?array $errors;
          public Metadata $metadata;
      }

      $message = $client->messages->create(
          maxTokens: 1024,
          messages: [
              ['role' => 'user', 'content' => 'Process this request: ...']
          ],
          model: 'claude-opus-5-5',
          outputConfig: ['format' => APIResponse::class],
      );

      $result = $message->parsedOutput();
      if ($result instanceof APIResponse) {
          echo "{$result->status}: {$result->data->message}\n";
      }
      ```

      ```ruby Ruby
      client = Anthropic::Client.new

      class Payload < Anthropic::BaseModel
        required :message, String
      end

      class APIError < Anthropic::BaseModel
        required :code, String
        required :detail, String
      end

      class Metadata < Anthropic::BaseModel
        required :request_id, String
      end

      class APIResponse < Anthropic::BaseModel
        required :status, String
        required :data, Payload
        optional :errors, Anthropic::ArrayOf[APIError]
        required :metadata, Metadata
      end

      message = client.messages.create(
        model: "claude-opus-5-5",
        max_tokens: 1024,
        output_config: {format: APIResponse},
        messages: [
          {role: "user", content: "Process this request: ..."}
        ]
      )
      puts message.parsed_output
      ```
    </CodeGroup>
  </Accordion>
</AccordionGroup>

## Uso estricto de herramientas

Para aplicar el cumplimiento de JSON Schema en las entradas de herramientas con muestreo restringido por gramática, consulta [Uso estricto de herramientas](https://platform.claude.com/docs/es/agents-and-tools/tool-use/strict-tool-use).

### Usar ambas funciones juntas

Las salidas JSON y el uso estricto de herramientas resuelven problemas diferentes y funcionan juntos:

* **Las salidas JSON** controlan el formato de respuesta de Claude (lo que Claude dice)
* **El uso estricto de herramientas** valida los parámetros de las herramientas (cómo Claude llama a tus funciones)

Cuando se combinan, Claude puede llamar a herramientas con parámetros garantizados como válidos Y devolver respuestas JSON estructuradas. Esto es útil para flujos de trabajo agénticos donde necesitas tanto llamadas a herramientas confiables como salidas finales estructuradas.

<CodeGroup>
  ```bash cURL
  curl https://api.anthropic.com/v1/messages \
    -H "content-type: application/json" \
    -H "x-api-key: $ANTHROPIC_API_KEY" \
    -H "anthropic-version: 2023-06-01" \
    -d '{
      "model": "claude-opus-5-5",
      "max_tokens": 1024,
      "messages": [
        {
          "role": "user",
          "content": "Help me plan a trip to Paris departing May 15, 2026"
        }
      ],
      "output_config": {
        "format": {
          "type": "json_schema",
          "schema": {
            "type": "object",
            "properties": {
              "summary": {"type": "string"},
              "next_steps": {"type": "array", "items": {"type": "string"}}
            },
            "required": ["summary", "next_steps"],
            "additionalProperties": false
          }
        }
      },
      "tools": [
        {
          "name": "search_flights",
          "strict": true,
          "input_schema": {
            "type": "object",
            "properties": {
              "destination": {"type": "string"},
              "date": {"type": "string", "format": "date"}
            },
            "required": ["destination", "date"],
            "additionalProperties": false
          }
        }
      ]
    }'
  ```

  ```bash CLI
  ant messages create <<'YAML'
  model: claude-opus-5-5
  max_tokens: 1024
  messages:
    - role: user
      content: Help me plan a trip to Paris departing May 15, 2026
  # Salidas JSON: formato de respuesta estructurado
  output_config:
    format:
      type: json_schema
      schema:
        type: object
        properties:
          summary:
            type: string
          next_steps:
            type: array
            items:
              type: string
        required: [summary, next_steps]
        additionalProperties: false
  # Uso estricto de herramientas: parámetros de herramientas garantizados
  tools:
    - name: search_flights
      strict: true
      input_schema:
        type: object
        properties:
          destination:
            type: string
          date:
            type: string
            format: date
        required: [destination, date]
        additionalProperties: false
  YAML
  ```

  ```python Python
  response = client.messages.create(
      model="claude-opus-5-5",
      max_tokens=1024,
      messages=[
          {
              "role": "user",
              "content": "Help me plan a trip to Paris departing May 15, 2026",
          }
      ],
      # Salidas JSON: formato de respuesta estructurado
      output_config={
          "format": {
              "type": "json_schema",
              "schema": {
                  "type": "object",
                  "properties": {
                      "summary": {"type": "string"},
                      "next_steps": {"type": "array", "items": {"type": "string"}},
                  },
                  "required": ["summary", "next_steps"],
                  "additionalProperties": False,
              },
          }
      },
      # Uso estricto de herramientas: parámetros de herramientas garantizados
      tools=[
          {
              "name": "search_flights",
              "strict": True,
              "input_schema": {
                  "type": "object",
                  "properties": {
                      "destination": {"type": "string"},
                      "date": {"type": "string", "format": "date"},
                  },
                  "required": ["destination", "date"],
                  "additionalProperties": False,
              },
          }
      ],
  )

  print(response)
  ```

  ```typescript TypeScript
  const response = await client.messages.create({
    model: "claude-opus-5-5",
    max_tokens: 1024,
    messages: [{ role: "user", content: "Help me plan a trip to Paris departing May 15, 2026" }],
    // Salidas JSON: formato de respuesta estructurado
    output_config: {
      format: {
        type: "json_schema",
        schema: {
          type: "object",
          properties: {
            summary: { type: "string" },
            next_steps: { type: "array", items: { type: "string" } }
          },
          required: ["summary", "next_steps"],
          additionalProperties: false
        }
      }
    },
    // Uso estricto de herramientas: parámetros de herramienta garantizados
    tools: [
      {
        name: "search_flights",
        description: "Search for available flights to a destination on a specific date",
        strict: true,
        input_schema: {
          type: "object",
          properties: {
            destination: { type: "string" },
            date: { type: "string", format: "date" }
          },
          required: ["destination", "date"],
          additionalProperties: false
        }
      }
    ]
  });

  // Claude puede llamar primero a la herramienta (tool_use) o responder con JSON (texto)
  console.log("Stop reason:", response.stop_reason);
  for (const block of response.content) {
    switch (block.type) {
      case "tool_use":
        console.log(`Tool call: ${block.name}(${JSON.stringify(block.input)})`);
        break;
      case "text":
        console.log("Response:", block.text);
        break;
    }
  }
  ```

  ```csharp C#
  var parameters = new MessageCreateParams
  {
      Model = Model.ClaudeOpus5_5,
      MaxTokens = 1024,
      Messages = [new() { Role = Role.User, Content = "Help me plan a trip to Paris departing May 15, 2026" }],
      // Salidas JSON: formato de respuesta estructurado
      OutputConfig = new OutputConfig
      {
          Format = new JsonOutputFormat
          {
              Schema = new Dictionary<string, JsonElement>
              {
                  ["type"] = JsonSerializer.SerializeToElement("object"),
                  ["properties"] = JsonSerializer.SerializeToElement(new
                  {
                      summary = new { type = "string" },
                      next_steps = new { type = "array", items = new { type = "string" } },
                  }),
                  ["required"] = JsonSerializer.SerializeToElement(new[] { "summary", "next_steps" }),
                  ["additionalProperties"] = JsonSerializer.SerializeToElement(false),
              },
          },
      },
      // Uso estricto de herramientas: parámetros de herramientas garantizados
      Tools =
      [
          new Tool
          {
              Name = "search_flights",
              Strict = true,
              InputSchema = new InputSchema(new Dictionary<string, JsonElement>
              {
                  ["properties"] = JsonSerializer.SerializeToElement(new Dictionary<string, object>
                  {
                      ["destination"] = new { type = "string" },
                      ["date"] = new { type = "string", format = "date" },
                  }),
                  ["required"] = JsonSerializer.SerializeToElement(new[] { "destination", "date" }),
                  ["additionalProperties"] = JsonSerializer.SerializeToElement(false),
              }),
          }
      ],
  };

  var message = await client.Messages.Create(parameters);
  Console.WriteLine(message);
  ```

  ```go Go
  client := anthropic.NewClient()

  response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
  	Model:     anthropic.ModelClaudeOpus5_5,
  	MaxTokens: 1024,
  	Messages: []anthropic.MessageParam{
  		anthropic.NewUserMessage(anthropic.NewTextBlock("Help me plan a trip to Paris departing May 15, 2026")),
  	},
  	// Salidas JSON: formato de respuesta estructurado
  	OutputConfig: anthropic.OutputConfigParam{
  		Format: anthropic.JSONOutputFormatParam{
  			Schema: map[string]any{
  				"type":                 "object",
  				"additionalProperties": false,
  				"properties": map[string]any{
  					"summary":    map[string]any{"type": "string"},
  					"next_steps": map[string]any{"type": "array", "items": map[string]any{"type": "string"}},
  				},
  				"required": []string{"summary", "next_steps"},
  			},
  		},
  	},
  	// Uso estricto de herramientas: parámetros de herramientas garantizados
  	Tools: []anthropic.ToolUnionParam{
  		{OfTool: &anthropic.ToolParam{
  			Name:   "search_flights",
  			Strict: anthropic.Bool(true),
  			InputSchema: anthropic.ToolInputSchemaParam{
  				Properties: map[string]any{
  					"destination": map[string]any{"type": "string"},
  					"date":        map[string]any{"type": "string", "format": "date"},
  				},
  				Required: []string{"destination", "date"},
  				ExtraFields: map[string]any{
  					"additionalProperties": false,
  				},
  			}}},
  	},
  })
  if err != nil {
  	log.Fatal(err)
  }
  fmt.Println(response.Content)
  ```

  ```java Java
  AnthropicClient client = AnthropicOkHttpClient.fromEnv();

  // Salidas JSON: formato de respuesta estructurado
  JsonOutputFormat.Schema outputSchema = JsonOutputFormat.Schema.builder()
      .putAdditionalProperty("type", JsonValue.from("object"))
      .putAdditionalProperty("properties", JsonValue.from(Map.of(
          "summary", Map.of("type", "string"),
          "next_steps", Map.of("type", "array", "items", Map.of("type", "string"))
      )))
      .putAdditionalProperty("required", JsonValue.from(List.of("summary", "next_steps")))
      .putAdditionalProperty("additionalProperties", JsonValue.from(false))
      .build();

  // Uso estricto de herramientas: parámetros de herramientas garantizados
  InputSchema toolSchema = InputSchema.builder()
      .properties(JsonValue.from(Map.of(
          "destination", Map.of("type", "string"),
          "date", Map.of("type", "string", "format", "date")
      )))
      .putAdditionalProperty("required", JsonValue.from(List.of("destination", "date")))
      .putAdditionalProperty("additionalProperties", JsonValue.from(false))
      .build();

  MessageCreateParams params = MessageCreateParams.builder()
      .model(Model.CLAUDE_OPUS_5_5)
      .maxTokens(1024L)
      .addUserMessage("Help me plan a trip to Paris departing May 15, 2026")
      .outputConfig(OutputConfig.builder()
          .format(JsonOutputFormat.builder().schema(outputSchema).build())
          .build())
      .addTool(Tool.builder()
          .name("search_flights")
          .description("Search for available flights to a destination on a specific date")
          .strict(true)
          .inputSchema(toolSchema)
          .build())
      .build();

  Message response = client.messages().create(params);
  IO.println(response);
  ```

  ```php PHP
  use Anthropic\Lib\Concerns\StructuredOutputModelTrait;
  use Anthropic\Lib\Contracts\StructuredOutputModel;
  use Anthropic\Messages\ToolUseBlock;

  $client = new Client();

  class TripPlan implements StructuredOutputModel
  {
      use StructuredOutputModelTrait;

      public string $summary;
      public array $next_steps;
  }

  $message = $client->messages->create(
      maxTokens: 1024,
      messages: [
          ['role' => 'user', 'content' => 'Help me plan a trip to Paris departing May 15, 2026']
      ],
      model: 'claude-opus-5-5',
      // Salidas JSON: formato de respuesta estructurado
      outputConfig: ['format' => TripPlan::class],
      // Uso de herramientas estricto: parámetros de herramienta garantizados
      tools: [
          [
              'name' => 'search_flights',
              'strict' => true,
              'input_schema' => [
                  'type' => 'object',
                  'properties' => [
                      'destination' => ['type' => 'string'],
                      'date' => ['type' => 'string', 'format' => 'date']
                  ],
                  'required' => ['destination', 'date'],
                  'additionalProperties' => false
              ]
          ]
      ],
  );

  // Claude puede llamar primero a la herramienta (tool_use) o responder con JSON (texto)
  $plan = $message->parsedOutput();
  if ($plan instanceof TripPlan) {
      echo $plan->summary, "\n";
  } elseif ($toolUse = array_find($message->content, fn($block) => $block instanceof ToolUseBlock)) {
      echo "Tool call: {$toolUse->name}(", json_encode($toolUse->input), ")\n";
  }
  ```

  ```ruby Ruby
  client = Anthropic::Client.new

  message = client.messages.create(
    model: "claude-opus-5-5",
    max_tokens: 1024,
    messages: [
      {role: "user", content: "Help me plan a trip to Paris departing May 15, 2026"}
    ],
    # Salidas JSON: formato de respuesta estructurado
    output_config: {
      format: {
        type: :json_schema,
        schema: {
          type: "object",
          properties: {
            summary: {type: "string"},
            next_steps: {type: "array", items: {type: "string"}}
          },
          required: ["summary", "next_steps"],
          additionalProperties: false
        }
      }
    },
    # Uso estricto de herramientas: parámetros de herramientas garantizados
    tools: [
      {
        name: "search_flights",
        strict: true,
        input_schema: {
          type: "object",
          properties: {
            destination: {type: "string"},
            date: {type: "string", format: "date"}
          },
          required: ["destination", "date"],
          additionalProperties: false
        }
      }
    ]
  )
  puts message
  ```
</CodeGroup>

## Consideraciones importantes

### Compilación y almacenamiento en caché de gramáticas

Las salidas estructuradas usan muestreo restringido con artefactos de gramática compilados. Esto introduce algunas características de rendimiento que debes tener en cuenta:

* **Latencia de la primera solicitud:** La primera vez que usas un esquema específico, hay latencia adicional mientras se compila la gramática

* **Almacenamiento en caché automático:** Las gramáticas compiladas se almacenan en caché durante 24 horas desde el último uso, lo que hace que las solicitudes posteriores sean mucho más rápidas

* **Invalidación de la caché:** La caché se invalida si cambias:

  * La estructura del esquema JSON
  * El conjunto de herramientas en tu solicitud (cuando usas tanto salidas estructuradas como uso de herramientas)
  * Cambiar solo los campos `name` o `description` no invalida la caché

### Modificación del prompt y costos de tokens

Al usar salidas estructuradas, Claude recibe automáticamente una indicación del sistema adicional que explica el formato de salida esperado. Esto significa:

* Tu recuento de tokens de entrada es ligeramente mayor
* El prompt inyectado te cuesta tokens como cualquier otra indicación del sistema
* Cambiar el parámetro `output_config.format` invalidará cualquier [caché de prompts](https://platform.claude.com/docs/es/build-with-claude/prompt-caching) para ese hilo de conversación

### Limitaciones de JSON Schema

Las salidas estructuradas admiten JSON Schema estándar con algunas limitaciones. Tanto las salidas JSON como el uso estricto de herramientas comparten estas limitaciones.

<Accordion title="Funciones compatibles">
  * Todos los tipos básicos: object, array, string, integer, number, boolean, null
  * `enum` (solo cadenas, números, booleanos o nulos; no tipos complejos). Para una advertencia sobre el uso de mayúsculas, consulta [Salidas inválidas](https://platform.claude.com/docs/es/build-with-claude/structured-outputs#invalid-outputs).
  * `const`
  * `anyOf` y `allOf` (con limitaciones; `allOf` con `$ref` no es compatible)
  * `$ref`, `$def` y `definitions` (`$ref` externo no es compatible)
  * Propiedad `default` para todos los tipos compatibles
  * `required` y `additionalProperties` (debe establecerse en `false` para objetos)
  * Formatos de cadena: `date-time`, `time`, `date`, `duration`, `email`, `hostname`, `uri`, `ipv4`, `ipv6`, `uuid`
  * `minItems` de arreglos (solo se admiten los valores 0 y 1)
</Accordion>

<Accordion title="No compatible">
  * Esquemas recursivos
  * Tipos complejos dentro de enums
  * `$ref` externo (por ejemplo, `'$ref': 'http://...'`)
  * Restricciones numéricas (como `minimum`, `maximum`, `multipleOf`)
  * Restricciones de cadena (`minLength`, `maxLength`)
  * Restricciones de arreglos más allá de `minItems` de 0 o 1
  * `additionalProperties` establecido en cualquier valor distinto de `false`

  Si usas una función no compatible, recibirás un error 400 con detalles.
</Accordion>

<Accordion title="Compatibilidad con patrones (regex)">
  **Funciones de regex compatibles:**

  * Coincidencia completa (`^...$`) y coincidencia parcial
  * Cuantificadores: `*`, `+`, `?`, casos simples de `{n,m}`
  * Clases de caracteres: `[]`, `.`, `\d`, `\w`, `\s`
  * Grupos: `(...)`

  **NO compatibles:**

  * Referencias inversas a grupos (por ejemplo, `\1`, `\2`)
  * Aserciones lookahead/lookbehind (por ejemplo, `(?=...)`, `(?!...)`)
  * Límites de palabra: `\b`, `\B`
  * Cuantificadores `{n,m}` complejos con rangos grandes

  Los patrones regex simples funcionan bien. Los patrones complejos pueden producir errores 400.
</Accordion>

<Tip>
  Los helpers del SDK pueden transformar esquemas con [restricciones no compatibles con la API](https://platform.claude.com/docs/es/build-with-claude/structured-outputs#json-schema-limitations). Consulta [Cómo funciona la transformación del SDK](https://platform.claude.com/docs/es/build-with-claude/structured-outputs#how-sdk-transformation-works).
</Tip>

### Orden de las propiedades

Al usar salidas estructuradas, las propiedades de los objetos mantienen el orden definido en tu esquema, con una advertencia importante: **las propiedades obligatorias aparecen primero, seguidas de las propiedades opcionales**.

Por ejemplo, dado este esquema:

```json
{
  "type": "object",
  "properties": {
    "notes": { "type": "string" },
    "name": { "type": "string" },
    "email": { "type": "string" },
    "age": { "type": "integer" }
  },
  "required": ["name", "email"],
  "additionalProperties": false
}
```

La salida ordenará las propiedades así:

1. `name` (obligatoria, en el orden del esquema)
2. `email` (obligatoria, en el orden del esquema)
3. `notes` (opcional, en el orden del esquema)
4. `age` (opcional, en el orden del esquema)

Esto significa que la salida podría verse así:

```json
{
  "name": "John Smith",
  "email": "john@example.com",
  "notes": "Interested in enterprise plan",
  "age": 35
}
```

Si el orden de las propiedades en la salida es importante para tu aplicación, marca todas las propiedades como obligatorias o ten en cuenta este reordenamiento en tu lógica de análisis.

### Salidas inválidas

Aunque las salidas estructuradas garantizan el cumplimiento del esquema en la mayoría de los casos, hay escenarios en los que la salida puede no coincidir con tu esquema:

**Rechazos** (`stop_reason: "refusal"`)

Claude mantiene sus propiedades de seguridad y utilidad incluso al usar salidas estructuradas. Si Claude rechaza una solicitud por razones de seguridad:

* La respuesta tiene `stop_reason: "refusal"`
* Recibirás un código de estado 200
* Se te facturarán los tokens generados
* La salida puede no coincidir con tu esquema porque el mensaje de rechazo tiene prioridad sobre las restricciones del esquema

Una propiedad que solicite el pensamiento del modelo o su razonamiento paso a paso puede provocar un rechazo `reasoning_extraction`. En su lugar, pide una explicación breve. Consulta [Mantén el razonamiento en bloques de pensamiento](https://platform.claude.com/docs/es/build-with-claude/refusals-and-fallback#keep-reasoning-in-thinking-blocks).

**Límite de tokens alcanzado** (`stop_reason: "max_tokens"`)

Si la respuesta se corta por alcanzar el límite de `max_tokens`:

* La respuesta tiene `stop_reason: "max_tokens"`
* La salida puede estar incompleta y no coincidir con tu esquema
* Reintenta con un valor de `max_tokens` más alto para obtener la salida estructurada completa

**Mayúsculas en valores enum**

Las salidas estructuradas no garantizan el uso de mayúsculas en los valores de cadena de `enum` y `const`: Claude puede devolver un valor que difiera de tu esquema solo en el uso de mayúsculas, normalmente en la primera letra de una palabra que sigue a un espacio. Por ejemplo, dado este esquema:

```json
{
  "type": "string",
  "enum": ["Conversation Topic 1", "Conversation Topic 2", "Conversation topic 3"]
}
```

La salida puede contener `"Conversation Topic 3"` ("T" mayúscula) aunque ese valor exacto no esté en el enum. La respuesta se completa normalmente, sin error y sin un `stop_reason` especial. Esto se aplica tanto a las salidas JSON como al uso estricto de herramientas. Compara los valores enum sin distinguir mayúsculas de minúsculas y evita valores enum que difieran solo en el uso de mayúsculas.

### Límites de complejidad del esquema

Las salidas estructuradas funcionan compilando tus esquemas JSON en una gramática que restringe la salida de Claude. Los esquemas más complejos producen gramáticas más grandes que tardan más en compilarse. Para protegerse contra tiempos de compilación excesivos, la API aplica varios límites de complejidad.

#### Límites explícitos

Los siguientes límites se aplican a todas las solicitudes con `output_config.format` o `strict: true`:

| Límite                               | Valor | Descripción                                                                                                                                                                                                                |
| ------------------------------------ | ----- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Herramientas estrictas por solicitud | 20    | Número máximo de herramientas con `strict: true`. Las herramientas no estrictas no cuentan para este límite.                                                                                                               |
| Parámetros opcionales                | 24    | Total de parámetros opcionales en todos los esquemas de herramientas estrictas y esquemas de salida JSON. Cada parámetro no incluido en `required` cuenta para este límite.                                                |
| Parámetros con tipos unión           | 16    | Total de parámetros que usan `anyOf` o arreglos de tipos (por ejemplo, `"type": ["string", "null"]`) en todos los esquemas estrictos. Estos son especialmente costosos porque generan un costo de compilación exponencial. |

<Note>
  Estos límites se aplican al total combinado de todos los esquemas estrictos en una sola solicitud. Por ejemplo, si tienes 4 herramientas estrictas con 6 parámetros opcionales cada una, alcanzarás el límite de 24 parámetros aunque ninguna herramienta individual parezca compleja.
</Note>

#### Límites internos adicionales

Más allá de los límites explícitos de la tabla anterior, existen límites internos adicionales sobre el tamaño de la gramática compilada. Estos límites existen porque la complejidad del esquema no se reduce a una sola dimensión: funciones como los parámetros opcionales, los tipos unión, los objetos anidados y el número de herramientas interactúan entre sí de maneras que pueden hacer que la gramática compilada sea desproporcionadamente grande.

Cuando se superan estos límites, recibirás un error 400 con el mensaje "Schema is too complex for compilation." Estos errores significan que la complejidad combinada de tus esquemas supera lo que se puede compilar de manera eficiente, incluso si se cumple cada límite individual de la tabla anterior. Como medida final de contención, la API también aplica un **tiempo de espera de compilación de 180 segundos**. Los esquemas que pasan todas las verificaciones explícitas pero producen gramáticas compiladas muy grandes pueden alcanzar este tiempo de espera.

#### Consejos para reducir la complejidad del esquema

Si estás alcanzando los límites de complejidad, prueba estas estrategias en orden:

1. **Marca solo las herramientas críticas como estrictas.** Si tienes muchas herramientas, resérvalo para las herramientas donde las violaciones de esquema causan problemas reales y confía en la adherencia natural de Claude para las herramientas más simples.

2. **Reduce los parámetros opcionales.** Haz que los parámetros sean `required` cuando sea posible. Cada parámetro opcional aproximadamente duplica una parte del espacio de estados de la gramática. Si un parámetro siempre tiene un valor predeterminado razonable, considera hacerlo obligatorio y que Claude proporcione ese valor predeterminado explícitamente.

3. **Simplifica las estructuras anidadas.** Los objetos profundamente anidados con campos opcionales multiplican la complejidad. Aplana las estructuras cuando sea posible.

4. **Divide en varias solicitudes.** Si tienes muchas herramientas estrictas, considera dividirlas en solicitudes separadas o subagentes.

Para problemas persistentes con esquemas válidos, [contacta con soporte](https://support.claude.com/en/articles/9015913-how-to-get-support) con la definición de tu esquema.

## Migrar desde la beta

El parámetro `output_format` se ha movido a `output_config.format`, y los encabezados beta ya no son necesarios. El parámetro `output_format` está obsoleto y se eliminará en el futuro. Para usarlo de todos modos, agrega el encabezado beta `structured-outputs-2025-11-13`. Sin él, la API devuelve un error 400.

El SDK de Python (v1.0 y posteriores) no acepta `output_format={...}` en `client.beta.messages.create()` ni en `count_tokens()` y genera un `TypeError`. Usa `output_config` en su lugar. Consulta [Usar un esquema JSON sin procesar](https://platform.claude.com/docs/es/build-with-claude/structured-outputs#using-a-raw-json-schema) para ver la forma actualizada de la API.

## Retención de datos

Los prompts y las respuestas se procesan con ZDR al usar salidas estructuradas. Sin embargo, el esquema JSON en sí se almacena temporalmente en caché hasta 24 horas desde el último uso con fines de optimización. No se retiene ningún dato de prompts ni respuestas más allá de la respuesta de la API.

Las salidas estructuradas son elegibles para HIPAA, pero **no se debe incluir PHI en las definiciones de esquemas JSON**. La API compila los esquemas JSON en gramáticas que se almacenan en caché por separado del contenido de los mensajes, y estos esquemas en caché no reciben las mismas protecciones de PHI que los prompts y las respuestas. No incluyas PHI en los nombres de propiedades del esquema, valores `enum`, valores `const` ni expresiones regulares `pattern`. La PHI solo debe aparecer en el contenido de los mensajes (prompts y respuestas), donde está protegida bajo las salvaguardas de HIPAA.

Para la elegibilidad de ZDR y HIPAA en todas las funciones, consulta [API y retención de datos](https://platform.claude.com/docs/es/manage-claude/api-and-data-retention).

## Compatibilidad de funciones

**Funciona con:**

* **[Procesamiento por lotes](https://platform.claude.com/docs/es/build-with-claude/batch-processing):** Procesa salidas estructuradas a escala con un 50% de descuento
* **[Conteo de tokens](https://platform.claude.com/docs/es/build-with-claude/token-counting):** Cuenta tokens sin compilación
* **[Streaming](https://platform.claude.com/docs/es/build-with-claude/streaming):** Transmite salidas estructuradas por streaming como respuestas normales
* **[Uso combinado](https://platform.claude.com/docs/es/build-with-claude/structured-outputs#using-both-features-together):** Usa salidas JSON (`output_config.format`) y uso estricto de herramientas (`strict: true`) juntos en la misma solicitud

**Incompatible con:**

* **[Citas](https://platform.claude.com/docs/es/build-with-claude/citations):** Las citas requieren intercalar bloques de citas con texto, lo que entra en conflicto con las restricciones estrictas del esquema JSON. Devuelve un error 400 si las citas están habilitadas con `output_config.format`.
* **Prellenado de mensajes:** Incompatible con las salidas JSON

<Tip>
  **Alcance de la gramática:** Las gramáticas se aplican solo a la salida directa de Claude, no a las llamadas de uso de herramientas, los resultados de herramientas ni las etiquetas de pensamiento (al usar [pensamiento](https://platform.claude.com/docs/es/build-with-claude/thinking)). El estado de la gramática se reinicia entre secciones, lo que permite a Claude pensar libremente mientras sigue produciendo una salida estructurada en la respuesta final.
</Tip>

## Próximos pasos

<CardGroup cols={2}>
  <Card title="Citas" icon="book-bookmark" href="https://platform.claude.com/docs/es/build-with-claude/citations">
    Haz que Claude cite sus fuentes al responder preguntas sobre documentos proporcionados.
  </Card>

  <Card title="Uso estricto de herramientas" icon="check" href="https://platform.claude.com/docs/es/agents-and-tools/tool-use/strict-tool-use">
    Aplica el cumplimiento de JSON Schema en las entradas de herramientas de Claude con muestreo restringido por gramática.
  </Card>

  <Card title="Uso de herramientas con Claude" icon="wrench" href="https://platform.claude.com/docs/es/agents-and-tools/tool-use/overview">
    Conecta Claude a herramientas y API externas. Aprende dónde se ejecutan las herramientas y cómo funciona el bucle agéntico.
  </Card>

  <Card title="Precios" icon="calculator" href="https://platform.claude.com/docs/es/about-claude/pricing">
    Conoce la estructura de precios de Anthropic para modelos y funciones.
  </Card>
</CardGroup>
