> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.asi1.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.asi1.ai/_mcp/server.

# Structured Data

> Get structured JSON responses from ASI:One using response_format and JSON schemas

Send a JSON schema with `response_format` and the reply conforms to it, so your
code can parse the answer instead of pulling values back out of prose. On
`/v1/responses` the same schema goes in `text.format`, covered
[below](#on-the-responses-api).

## Quick start with the OpenAI SDK

With the OpenAI SDK, `response_format` takes the schema directly:

#### cURL

```bash
curl -X POST https://api.asi1.ai/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ASI_ONE_API_KEY" \
  -d '{
    "model": "asi1",
    "messages": [
      {"role": "system", "content": "Extract the requested information as JSON."},
      {"role": "user", "content": "Extract: John Smith is a 32 year old software engineer from San Francisco."}
    ],
    "response_format": {
      "type": "json_schema",
      "json_schema": {
        "name": "person_info",
        "strict": true,
        "schema": {
          "type": "object",
          "properties": {
            "name": {"type": "string"},
            "age": {"type": "integer"},
            "occupation": {"type": "string"},
            "city": {"type": "string"}
          },
          "required": ["name", "age", "occupation", "city"],
          "additionalProperties": false
        }
      }
    }
  }'
```

#### Python

```python
import os
import json
from openai import OpenAI

client = OpenAI(
    api_key=os.getenv("ASI_ONE_API_KEY"),
    base_url="https://api.asi1.ai/v1"
)

response = client.chat.completions.create(
    model="asi1",
    messages=[
        {"role": "system", "content": "Extract the requested information as JSON."},
        {"role": "user", "content": "Extract: John Smith is a 32 year old software engineer from San Francisco."}
    ],
    response_format={
        "type": "json_schema",
        "json_schema": {
            "name": "person_info",
            "strict": True,
            "schema": {
                "type": "object",
                "properties": {
                    "name": {"type": "string"},
                    "age": {"type": "integer"},
                    "occupation": {"type": "string"},
                    "city": {"type": "string"}
                },
                "required": ["name", "age", "occupation", "city"],
                "additionalProperties": False
            }
        }
    }
)

result = json.loads(response.choices[0].message.content)
print(f"Name: {result['name']}")
print(f"Age: {result['age']}")
print(f"Occupation: {result['occupation']}")
print(f"City: {result['city']}")
```

> **Tip**
>
> If you already use LangChain, `with_structured_output` takes a Pydantic model
> and returns a typed object, which is often cleaner than handling
> `response_format` yourself. See [Using ASI:One with LangChain](/documentation/tutorials/langchain).

---

## Complex schema example

Nested objects and arrays work the same way, and `additionalProperties: false`
applies to every object in the schema rather than only the outermost one:

```python
import os
import json
from openai import OpenAI

client = OpenAI(
    api_key=os.getenv("ASI_ONE_API_KEY"),
    base_url="https://api.asi1.ai/v1"
)

# Complex schema with nested objects
response_format = {
    "type": "json_schema",
    "json_schema": {
        "name": "order_summary",
        "strict": True,
        "schema": {
            "type": "object",
            "additionalProperties": False,
            "properties": {
                "order_id": {"type": "string"},
                "customer": {
                    "type": "object",
                    "additionalProperties": False,
                    "properties": {
                        "name": {"type": "string"},
                        "email": {"type": "string"}
                    },
                    "required": ["name", "email"]
                },
                "items": {
                    "type": "array",
                    "items": {
                        "type": "object",
                        "additionalProperties": False,
                        "properties": {
                            "sku": {"type": "string"},
                            "name": {"type": "string"},
                            "quantity": {"type": "integer"},
                            "unit_price": {"type": "number"}
                        },
                        "required": ["sku", "name", "quantity", "unit_price"]
                    }
                },
                "total": {"type": "number"},
                "currency": {"type": "string"}
            },
            "required": ["order_id", "customer", "items", "total", "currency"]
        }
    }
}

response = client.chat.completions.create(
    model="asi1",
    messages=[
        {"role": "system", "content": "Generate order data matching the schema."},
        {"role": "user", "content": "Create a sample order for customer Jane Doe (jane@example.com) with 2 items totaling $150."}
    ],
    response_format=response_format
)

order = json.loads(response.choices[0].message.content)
print(json.dumps(order, indent=2))
```

---

## Best practices

### Schema design

Use `strict: true` so the model follows your schema exactly rather than treating
it as a strong suggestion. Two things are required alongside it:

1. `additionalProperties` must be `false` on every object in the schema.
2. Every key in `properties` must be listed in `required`. To make a field
   optional, keep it in `required` and add `"null"` to its type.

The same rules apply to tool schemas. See [Strict mode](/documentation/build-with-asi-one/tool-calling#strict-mode) for a worked
example.

Beyond that:

| Practice                        | Why                                           |
| ------------------------------- | --------------------------------------------- |
| Add `description` to properties | Tells the model what each field is for        |
| Give examples in the prompt     | Improves accuracy on complex extraction tasks |

### Validation

```python
import json
from pydantic import BaseModel, ValidationError

class Person(BaseModel):
    name: str
    age: int
    city: str

# Always validate model output
try:
    content = response.choices[0].message.content
    data = json.loads(content)
    person = Person(**data)  # Pydantic validation
except json.JSONDecodeError:
    print("Model returned invalid JSON")
except ValidationError as e:
    print(f"Schema validation failed: {e}")
```

Always validate and sanitize structured output before using it in production. A
schema constrains the shape of the reply, not the truth of its contents.

## On the Responses API

Everything above uses `response_format` on `/v1/chat/completions`. On
`/v1/responses` the equivalent is `text.format`, which takes the same JSON schema
in a **flat** shape rather than nesting it under `json_schema`:

#### cURL

```bash
curl -X POST https://api.asi1.ai/v1/responses \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ASI_ONE_API_KEY" \
  -d '{
    "model": "asi1",
    "input": "Extract the order details from this email: ...",
    "text": {
      "format": {
        "type": "json_schema",
        "name": "order_summary",
        "strict": true,
        "schema": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "order_id": {"type": "string"},
            "total": {"type": "number"}
          },
          "required": ["order_id", "total"]
        }
      }
    }
  }'
```

#### Python

```python
response = client.responses.create(
    model="asi1",
    input="Extract the order details from this email: ...",
    text={
        "format": {
            "type": "json_schema",
            "name": "order_summary",
            "strict": True,
            "schema": {
                "type": "object",
                "additionalProperties": False,
                "properties": {
                    "order_id": {"type": "string"},
                    "total": {"type": "number"},
                },
                "required": ["order_id", "total"],
            },
        }
    },
)

order = json.loads(response.output_text)
```

Both reuse the `client` built in the quick start above, and `name` is required
on `text.format`. The schema itself, including `strict` and
`additionalProperties`, is identical to the Chat Completions version, so you can
reuse it as-is.

## Next steps

1. **[Tool Calling](/documentation/build-with-asi-one/tool-calling)** - Let the model call your own functions
2. **[Responses API](/documentation/build-with-asi-one/responses)** - Where `text.format` applies, and the rest of that endpoint
3. **[Using ASI:One with LangChain](/documentation/tutorials/langchain)** - `with_structured_output` instead of raw JSON schemas
4. **[OpenAI Compatibility](/documentation/build-with-asi-one/openai-compatibility)** - Which parameters apply on which endpoint