> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dottxt.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrate from Other Providers

> Switch from other providers to dottxt with one stable request shape.

If you're migrating from OpenAI, Anthropic, Gemini, or another provider, use one stable path to dottxt.

## Shared migration pattern

1. Point your client at dottxt (`baseURL` + dottxt API key).
2. Send raw JSON Schema in `response_format` (avoid helper methods like `parse` that rewrite schemas).

This keeps the request shape explicit and avoids provider SDK transformations that can silently change optionality and constraints.

## OpenAI-compatible example: change two lines

<CodeGroup>
  ```python Before (OpenAI) theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  from openai import OpenAI

  client = OpenAI()

  response = client.chat.completions.create(
      model="gpt-4o",
      messages=[
          {"role": "user", "content": "Extract: John Smith <john@acme.com>, VP Engineering"}
      ],
      response_format={
          "type": "json_schema",
          "json_schema": {
              "name": "contact",
              "strict": True,
              "schema": {
                  "type": "object",
                  "properties": {
                      "name": {"type": "string"},
                      "email": {"type": "string"},
                      "role": {"type": "string"}
                  },
                  "required": ["name", "email", "role"],
                  "additionalProperties": False
              }
          }
      }
  )

  import json
  contact = json.loads(response.choices[0].message.content)
  ```

  ```python After (dottxt) theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  from openai import OpenAI

  client = OpenAI(
      base_url="https://api.dottxt.ai/v1",
      api_key="your-dottxt-api-key",
  )

  response = client.chat.completions.create(
      model="openai/gpt-oss-20b",
      messages=[
          {"role": "user", "content": "Extract: John Smith <john@acme.com>, VP Engineering"}
      ],
      response_format={
          "type": "json_schema",
          "json_schema": {
              "name": "contact",
              "schema": {
                  "type": "object",
                  "properties": {
                      "name": {"type": "string"},
                      "email": {"type": "string"},
                      "role": {"type": "string"}
                  },
                  "required": ["name", "email", "role"],
                  "additionalProperties": False
              }
          }
      }
  )

  import json
  contact = json.loads(response.choices[0].message.content)
  ```
</CodeGroup>

Two changes:

1. Set `base_url` and `api_key` to point at dottxt
2. Swap the model name

Everything else, including `response_format`, `messages`, and response parsing, stays the same.

## What changes when you switch

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:

| What you write             | OpenAI behavior            | dottxt behavior             |
| -------------------------- | -------------------------- | --------------------------- |
| A field not in `required`  | Not allowed in strict mode | Field is genuinely optional |
| `minLength: 3` on a string | Supported                  | Enforced during generation  |
| `pattern: "^[A-Z]{2}$"`    | Supported                  | Enforced during generation  |
| `minimum: 0` on a number   | Supported                  | Enforced during generation  |
| `anyOf` at root level      | Rejected                   | Supported                   |
| `if` / `then` / `else`     | Rejected                   | Supported                   |
| Unsupported feature        | Explicit error             | Explicit error              |

See the [full provider comparison](/providers-comparison) for details.

## Code you can delete

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.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Without full schema enforcement: the schema says minLength, pattern, optional fields...
# but your application still has to clean up and validate the result itself

import json
from datetime import datetime

response = 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 enforce
if not result.get("vendor") or len(result["vendor"]) > 120:
    raise ValueError("vendor missing or too long")

# Normalize date to match the contract your application expects
raw_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 expects
currency = 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 directly
if result.get("notes") in ("", "N/A", "None", "n/a"):
    result["notes"] = None

# Retry if the output still doesn't conform
if not validate(result):
    # try again, hope for better luck
    response = client.chat.completions.create(...)
```

With dottxt, the same task:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
import json
import os
from openai import OpenAI
from pydantic import BaseModel, Field

class 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.

## Richer schemas that now work

Once you're on dottxt, you can use JSON Schema patterns that OpenAI's `strict` mode rejects:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
response = client.chat.completions.create(
    model="openai/gpt-oss-20b",
    messages=[
        {"role": "user", "content": "Extract: John Smith <john@acme.com>, VP Engineering"}
    ],
    response_format={
        "type": "json_schema",
        "json_schema": {
            "name": "contact",
            "schema": {
                "type": "object",
                "properties": {
                    "name": {
                        "type": "string",
                        "minLength": 1
                    },
                    "email": {
                        "type": "string",
                        "pattern": "^[^@]+@[^@]+$"
                    },
                    "role": {
                        "type": "string"
                    },
                    "tags": {
                        "type": "array",
                        "items": {"type": "string"},
                        "minItems": 1,
                        "maxItems": 5
                    }
                },
                "required": ["name", "email"],
                "additionalProperties": false
            }
        }
    }
)
```

`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.

## TypeScript

The same approach works with any OpenAI-compatible TypeScript client:

<CodeGroup>
  ```typescript Before (OpenAI) theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  import OpenAI from "openai";

  const client = new OpenAI();

  const response = await client.chat.completions.create({
    model: "gpt-4o",
    messages: [
      { role: "user", content: "Extract: John Smith <john@acme.com>, VP Engineering" }
    ],
    response_format: {
      type: "json_schema",
      json_schema: {
        name: "contact",
        strict: true,
        schema: {
          type: "object",
          properties: {
            name: { type: "string" },
            email: { type: "string" },
            role: { type: "string" }
          },
          required: ["name", "email", "role"],
          additionalProperties: false
        }
      }
    }
  });

  const contact = JSON.parse(response.choices[0].message.content!);
  ```

  ```typescript After (dottxt) theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  import OpenAI from "openai";

  const client = new OpenAI({
    baseURL: "https://api.dottxt.ai/v1",
    apiKey: process.env.DOTTXT_API_KEY,
  });

  const response = await client.chat.completions.create({
    model: "openai/gpt-oss-20b",
    messages: [
      { role: "user", content: "Extract: John Smith <john@acme.com>, VP Engineering" }
    ],
    response_format: {
      type: "json_schema",
      json_schema: {
        name: "contact",
        schema: {
          type: "object",
          properties: {
            name: { type: "string" },
            email: { type: "string" },
            role: { type: "string" }
          },
          required: ["name", "email"],
          additionalProperties: false
        }
      }
    }
  });

  const contact = JSON.parse(response.choices[0].message.content!);
  ```

  ```bash curl theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  curl https://api.dottxt.ai/v1/chat/completions \
    -H "Authorization: Bearer $DOTTXT_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "model": "openai/gpt-oss-20b",
      "messages": [
        {"role": "user", "content": "Extract: John Smith <john@acme.com>, VP Engineering"}
      ],
      "response_format": {
        "type": "json_schema",
        "json_schema": {
          "name": "contact",
          "schema": {
            "type": "object",
            "properties": {
              "name": {"type": "string"},
              "email": {"type": "string"},
              "role": {"type": "string"}
            },
            "required": ["name", "email"],
            "additionalProperties": false
          }
        }
      }
    }'
  ```
</CodeGroup>
