With OpenAI Structured Outputs, strict: true comes with a restricted JSON Schema subset. All object fields must be listed in required, objects must set additionalProperties: false, and some schema shapes are rejected outright.With dottxt, your schema is used as-is:
When your provider does not enforce the schema you actually want, you compensate with application code. Here’s what that often looks like in practice: not the API call itself, but everything around it.
# Without full schema enforcement: the schema says minLength, pattern, optional fields...# but your application still has to clean up and validate the result itselfimport jsonfrom datetime import datetimeresponse = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": text}], response_format={"type": "json_schema", "json_schema": { ... }})result = json.loads(response.choices[0].message.content)# Validate what the schema was supposed to enforceif not result.get("vendor") or len(result["vendor"]) > 120: raise ValueError("vendor missing or too long")# Normalize date to match the contract your application expectsraw_date = result.get("date", "")for fmt in ("%Y-%m-%d", "%B %d, %Y", "%m/%d/%Y", "%b %d %Y"): try: result["date"] = datetime.strptime(raw_date, fmt).date().isoformat() break except ValueError: continue# Normalize currency to the format your downstream code expectscurrency = result.get("currency", "").upper().strip()if currency in ("US DOLLARS", "USD$", "DOLLARS"): currency = "USD"result["currency"] = currency# Handle optional fields yourself when the response contract is not enforced directlyif result.get("notes") in ("", "N/A", "None", "n/a"): result["notes"] = None# Retry if the output still doesn't conformif not validate(result): # try again, hope for better luck response = client.chat.completions.create(...)
With dottxt, the same task:
import jsonimport osfrom openai import OpenAIfrom pydantic import BaseModel, Fieldclass Invoice(BaseModel): vendor: str = Field(min_length=1, max_length=120) date: str = Field(json_schema_extra={"format": "date"}) currency: str = Field(pattern=r"^[A-Z]{3}$") line_items: list[dict] = Field(min_length=1, max_length=50) total: float = Field(ge=0) notes: str | None = Field(default=None, max_length=300)client = OpenAI( base_url="https://api.dottxt.ai/v1", api_key=os.environ["DOTTXT_API_KEY"],)response = client.chat.completions.create( model="openai/gpt-oss-20b", messages=[{"role": "user", "content": text}], response_format={ "type": "json_schema", "json_schema": { "name": "invoice", "schema": Invoice.model_json_schema(), }, },)invoice = Invoice.model_validate_json(response.choices[0].message.content)# invoice.date is already "2026-02-12"# invoice.currency is already "USD"# invoice.notes is None when the source text doesn't contain any# no validation, no normalization, no retry
The validation code, the date normalization, the currency cleanup, the empty-string-to-None conversion, and the retry loop all exist because the schema wasn’t enforced. Delete the workarounds, keep the schema.
name is always non-empty. email matches the pattern. tags has 1–5 items. role is optional; it may or may not appear in the output. None of this works with OpenAI’s strict mode.