Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.opper.ai/llms.txt

Use this file to discover all available pages before exploring further.

Schemas are how the JSON API knows what you want. You describe the shape of your input and your output; Opper validates the model’s response against that shape and gives you back a typed object. Three ways to write them:
  • Python: Pydantic models (recommended), dataclasses, TypedDicts, or raw JSON Schema dicts
  • TypeScript: raw JSON Schema objects
  • Anywhere else: raw JSON Schema sent as JSON over HTTP

output_schema: get typed JSON back

This is the one you’ll use most. Describe what you want, the model fills it in.
import os
from pydantic import BaseModel
from opperai import Opper

class Ticket(BaseModel):
    category: str
    priority: str
    confidence: float

opper = Opper(http_bearer=os.getenv("OPPER_API_KEY", ""))

result = opper.call(
    name="classify-ticket",
    instructions="Pick the best category and priority for the ticket.",
    input={"subject": "Login broken", "body": "Reset link doesn't work."},
    output_schema=Ticket,
)

print(result.json_payload["category"])   # str
print(result.json_payload["confidence"]) # float
If the model can’t produce something that matches the schema, the call fails with a validation error rather than silently returning malformed JSON.

input_schema: validate what goes in

Optional. You only need it in two cases:
  • To validate the caller, so the schema rejects malformed input before it reaches the model.
  • To mark a field as media, using the JSON Schema contentMediaType and contentEncoding keywords to tell the model that a string is a PDF or an image.
Python
result = opper.call(
    name="parse-receipt-image",
    instructions="Extract the merchant, total, and currency from the image.",
    input={"receipt": image_b64},
    input_schema={
        "type": "object",
        "properties": {
            "receipt": {
                "type": "string",
                "contentMediaType": "image/jpeg",
                "contentEncoding": "base64",
            },
        },
        "required": ["receipt"],
    },
    output_schema=Receipt,
)

Media types

Set contentMediaType on any string field to send media. The model handles the rest.
Media typeUse for
image/png, image/jpeg, image/webpPhotos, screenshots, scans
application/pdfDocuments
audio/wav, audio/mpeg, audio/oggRecordings, voice messages
video/mp4Short video clips (where the model supports it)
contentEncoding is base64 for inline media. The actual bytes go in the field as a base64 string. Not every model supports every type. See the models catalog for which models accept which inputs.

Common patterns

Optional fields

class Person(BaseModel):
    name: str
    age: int | None = None   # optional
    email: str | None = None # optional

Enums (one of a fixed set)

from typing import Literal

class Triage(BaseModel):
    priority: Literal["low", "medium", "high"]

Arrays

class ReceiptItems(BaseModel):
    items: list[str]

Nested objects

class LineItem(BaseModel):
    description: str
    price: float

class Receipt(BaseModel):
    merchant: str
    items: list[LineItem]
    total: float

Field descriptions

Help the model by describing what each field is. Especially useful for ambiguous ones.
from pydantic import BaseModel, Field

class Triage(BaseModel):
    priority: str = Field(description="One of: low, medium, high. Use 'high' when the user can't access their account.")
    confidence: float = Field(description="0 to 1. How sure you are about the priority.")

What’s next

Calls

The basics of the JSON API.

Streaming

Stream fields as they come back.

Hints

Pick cost vs quality vs speed without naming a model.

Control Plane

Govern, score, and improve every call.