# openai.BadRequestError: Invalid schema for response_format: 'properties' must be an object

- **ID:** `llm/structured-output-schema-mismatch-on-retry`
- **Domain:** llm
- **Category:** config_error
- **Error Code:** `invalid_response_format`
- **Verification:** ai_generated
- **Fix Rate:** 88%

## Root Cause

OpenAI API enforces strict JSON Schema validation for response_format; an empty or malformed 'properties' field (e.g., after schema transformation or caching) triggers this error on retry.

## Version Compatibility

| Version | Status | Introduced | Deprecated |
|---------|--------|------------|------------|
| openai==1.30.0 | active | — | — |
| openai==1.35.0 | active | — | — |
| pydantic==2.7.0 | active | — | — |

## Workarounds

1. **Validate the schema before sending: ensure 'properties' is a non-empty object with valid JSON Schema types. Example: `if not isinstance(schema.get('properties'), dict) or len(schema['properties']) == 0: raise ValueError('Invalid schema')`** (90% success)
   ```
   Validate the schema before sending: ensure 'properties' is a non-empty object with valid JSON Schema types. Example: `if not isinstance(schema.get('properties'), dict) or len(schema['properties']) == 0: raise ValueError('Invalid schema')`
   ```
2. **Use a schema generation library like Pydantic to create the JSON Schema, then convert to dict with `model_json_schema()`. Example: `from pydantic import BaseModel; class MyModel(BaseModel): name: str; schema = MyModel.model_json_schema()`** (95% success)
   ```
   Use a schema generation library like Pydantic to create the JSON Schema, then convert to dict with `model_json_schema()`. Example: `from pydantic import BaseModel; class MyModel(BaseModel): name: str; schema = MyModel.model_json_schema()`
   ```
3. **If caching schemas, deep-copy the schema dict before modification to avoid mutating the cached version (which may become empty). Example: `import copy; schema = copy.deepcopy(cached_schema)`** (85% success)
   ```
   If caching schemas, deep-copy the schema dict before modification to avoid mutating the cached version (which may become empty). Example: `import copy; schema = copy.deepcopy(cached_schema)`
   ```

## Dead Ends

- **** — The schema is fundamentally broken; the API will reject it consistently until the schema is corrected. (95% fail)
- **** — Removing structured output forces the LLM to return free-form text, breaking downstream parsing logic that expects a JSON object. (85% fail)
- **** — The API expects the schema directly under 'response_format', not nested; this causes a different validation error. (90% fail)
