response_format parameter with JSON schemas. You will learn practical patterns for product data extraction, research findings, comparison tables, and building reliable data pipelines — all with guaranteed schema conformance.
The Agent API enforces your JSON schema at generation time, so responses always conform to the specified structure. For the full parameter reference, see Output Control.
Prerequisites
Install the Perplexity SDK:pip install perplexityai
npm install @perplexity-ai/perplexity_ai
Get your Perplexity API Key
Navigate to the API Keys tab in the API Portal and generate a new key.
export PERPLEXITY_API_KEY="your-api-key"
How Structured Outputs Work
When you passresponse_format with type: "json_schema", the Agent API constrains the model’s output to match your schema exactly. The response in output_text is a valid JSON string you can parse directly.
The schema format follows JSON Schema with a few constraints specific to the Perplexity API:
- No recursive schemas. The schema cannot reference itself.
- No unconstrained objects. Avoid
additionalProperties: trueor bareobjecttypes without defined properties. - Named schemas required. Each schema needs a
namefield for identification.
Basic: Extracting a Single Entity
Extract structured data about a single topic with web search grounding.import json
from perplexity import Perplexity
client = Perplexity()
response = client.responses.create(
model="openai/gpt-5.4",
input="What is the current market cap, CEO, and founding year of NVIDIA?",
tools=[{"type": "web_search"}],
response_format={
"type": "json_schema",
"json_schema": {
"name": "company_profile",
"schema": {
"type": "object",
"properties": {
"company_name": {"type": "string"},
"ticker": {"type": "string"},
"ceo": {"type": "string"},
"founded_year": {"type": "integer"},
"market_cap_usd": {"type": "string"},
"sector": {"type": "string"},
"headquarters": {"type": "string"},
},
"required": ["company_name", "ticker", "ceo", "founded_year", "market_cap_usd", "sector", "headquarters"],
"additionalProperties": false,
},
},
},
)
company = json.loads(response.output_text)
print(f"{company['company_name']} ({company['ticker']})")
print(f" CEO: {company['ceo']}")
print(f" Founded: {company['founded_year']}")
print(f" Market Cap: {company['market_cap_usd']}")
print(f" Sector: {company['sector']}")
import Perplexity from '@perplexity-ai/perplexity_ai';
const client = new Perplexity();
const response = await client.responses.create({
model: "openai/gpt-5.4",
input: "What is the current market cap, CEO, and founding year of NVIDIA?",
tools: [{ type: "web_search" }],
response_format: {
type: "json_schema",
json_schema: {
name: "company_profile",
schema: {
type: "object",
properties: {
company_name: { type: "string" },
ticker: { type: "string" },
ceo: { type: "string" },
founded_year: { type: "integer" },
market_cap_usd: { type: "string" },
sector: { type: "string" },
headquarters: { type: "string" },
},
required: ["company_name", "ticker", "ceo", "founded_year", "market_cap_usd", "sector", "headquarters"],
},
},
},
});
const company = JSON.parse(response.output_text);
console.log(`${company.company_name} (${company.ticker})`);
console.log(` CEO: ${company.ceo}`);
console.log(` Founded: ${company.founded_year}`);
console.log(` Market Cap: ${company.market_cap_usd}`);
console.log(` Sector: ${company.sector}`);
Extracting Lists: Product Comparisons
Extract a structured comparison of multiple items from a single query.import json
from perplexity import Perplexity
client = Perplexity()
response = client.responses.create(
model="openai/gpt-5.4",
input="Compare the top 3 electric vehicles under $40,000 available in the US in 2026",
tools=[{"type": "web_search"}],
response_format={
"type": "json_schema",
"json_schema": {
"name": "ev_comparison",
"schema": {
"type": "object",
"properties": {
"vehicles": {
"type": "array",
"items": {
"type": "object",
"properties": {
"make": {"type": "string"},
"model": {"type": "string"},
"year": {"type": "integer"},
"starting_price_usd": {"type": "integer"},
"range_miles": {"type": "integer"},
"battery_kwh": {"type": "number"},
"pros": {"type": "array", "items": {"type": "string"}},
"cons": {"type": "array", "items": {"type": "string"}},
},
"required": ["make", "model", "year", "starting_price_usd", "range_miles", "battery_kwh", "pros", "cons"],
"additionalProperties": false,
},
},
"comparison_date": {"type": "string"},
},
"required": ["vehicles", "comparison_date"],
"additionalProperties": false,
},
},
},
)
data = json.loads(response.output_text)
print(f"EV Comparison (as of {data['comparison_date']})\n")
for v in data["vehicles"]:
print(f"{v['year']} {v['make']} {v['model']}")
print(f" Price: ${v['starting_price_usd']:,}")
print(f" Range: {v['range_miles']} mi | Battery: {v['battery_kwh']} kWh")
print(f" Pros: {', '.join(v['pros'])}")
print(f" Cons: {', '.join(v['cons'])}")
print()
import Perplexity from '@perplexity-ai/perplexity_ai';
const client = new Perplexity();
const response = await client.responses.create({
model: "openai/gpt-5.4",
input: "Compare the top 3 electric vehicles under $40,000 available in the US in 2026",
tools: [{ type: "web_search" }],
response_format: {
type: "json_schema",
json_schema: {
name: "ev_comparison",
schema: {
type: "object",
properties: {
vehicles: {
type: "array",
items: {
type: "object",
properties: {
make: { type: "string" },
model: { type: "string" },
year: { type: "integer" },
starting_price_usd: { type: "integer" },
range_miles: { type: "integer" },
battery_kwh: { type: "number" },
pros: { type: "array", items: { type: "string" } },
cons: { type: "array", items: { type: "string" } },
},
required: ["make", "model", "year", "starting_price_usd", "range_miles", "battery_kwh", "pros", "cons"],
},
},
comparison_date: { type: "string" },
},
required: ["vehicles", "comparison_date"],
},
},
},
});
const data = JSON.parse(response.output_text);
console.log(`EV Comparison (as of ${data.comparison_date})\n`);
for (const v of data.vehicles) {
console.log(`${v.year} ${v.make} ${v.model}`);
console.log(` Price: $${v.starting_price_usd.toLocaleString()}`);
console.log(` Range: ${v.range_miles} mi | Battery: ${v.battery_kwh} kWh`);
console.log(` Pros: ${v.pros.join(", ")}`);
console.log(` Cons: ${v.cons.join(", ")}`);
console.log();
}
Research Findings Extraction
Parse search-grounded research into a structured format suitable for reports or databases.import json
from perplexity import Perplexity
client = Perplexity()
response = client.responses.create(
model="openai/gpt-5.4",
input="What are the most recent clinical trial results for GLP-1 receptor agonists in treating obesity?",
tools=[{"type": "web_search"}],
instructions="Provide findings from the most recent clinical trials. Include specific numbers and trial names where available.",
response_format={
"type": "json_schema",
"json_schema": {
"name": "research_findings",
"schema": {
"type": "object",
"properties": {
"topic": {"type": "string"},
"findings": {
"type": "array",
"items": {
"type": "object",
"properties": {
"trial_name": {"type": "string"},
"drug": {"type": "string"},
"phase": {"type": "string"},
"key_result": {"type": "string"},
"sample_size": {"type": "string"},
"publication_year": {"type": "integer"},
},
"required": ["trial_name", "drug", "phase", "key_result", "sample_size", "publication_year"],
"additionalProperties": false,
},
},
"summary": {"type": "string"},
},
"required": ["topic", "findings", "summary"],
"additionalProperties": false,
},
},
},
)
data = json.loads(response.output_text)
print(f"Topic: {data['topic']}\n")
print(f"Summary: {data['summary']}\n")
for finding in data["findings"]:
print(f" {finding['trial_name']} ({finding['drug']}, Phase {finding['phase']})")
print(f" Result: {finding['key_result']}")
print(f" N={finding['sample_size']}, Published: {finding['publication_year']}")
print()
import Perplexity from '@perplexity-ai/perplexity_ai';
const client = new Perplexity();
const response = await client.responses.create({
model: "openai/gpt-5.4",
input: "What are the most recent clinical trial results for GLP-1 receptor agonists in treating obesity?",
tools: [{ type: "web_search" }],
instructions: "Provide findings from the most recent clinical trials. Include specific numbers and trial names where available.",
response_format: {
type: "json_schema",
json_schema: {
name: "research_findings",
schema: {
type: "object",
properties: {
topic: { type: "string" },
findings: {
type: "array",
items: {
type: "object",
properties: {
trial_name: { type: "string" },
drug: { type: "string" },
phase: { type: "string" },
key_result: { type: "string" },
sample_size: { type: "string" },
publication_year: { type: "integer" },
},
required: ["trial_name", "drug", "phase", "key_result", "sample_size", "publication_year"],
},
},
summary: { type: "string" },
},
required: ["topic", "findings", "summary"],
},
},
},
});
const data = JSON.parse(response.output_text);
console.log(`Topic: ${data.topic}\n`);
console.log(`Summary: ${data.summary}\n`);
for (const finding of data.findings) {
console.log(` ${finding.trial_name} (${finding.drug}, Phase ${finding.phase})`);
console.log(` Result: ${finding.key_result}`);
console.log(` N=${finding.sample_size}, Published: ${finding.publication_year}`);
console.log();
}
Building a Data Pipeline
Chain structured output extraction into a pipeline that queries, extracts, and stores structured data.import json
import csv
import io
from perplexity import Perplexity
client = Perplexity()
SCHEMA = {
"type": "json_schema",
"json_schema": {
"name": "startup_funding",
"schema": {
"type": "object",
"properties": {
"companies": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {"type": "string"},
"round": {"type": "string"},
"amount_usd": {"type": "string"},
"lead_investor": {"type": "string"},
"sector": {"type": "string"},
"date": {"type": "string"},
},
"required": ["name", "round", "amount_usd", "lead_investor", "sector", "date"],
"additionalProperties": false,
},
},
},
"required": ["companies"],
"additionalProperties": false,
},
},
}
def extract_funding_rounds(sector: str) -> list[dict]:
"""Query the API and return structured funding data for a sector."""
response = client.responses.create(
model="openai/gpt-5.4",
input=f"List the 5 largest startup funding rounds in {sector} from the past 3 months",
tools=[{"type": "web_search"}],
response_format=SCHEMA,
)
data = json.loads(response.output_text)
return data["companies"]
def pipeline(sectors: list[str]) -> str:
"""Run extraction across multiple sectors and produce a CSV."""
all_rows = []
for sector in sectors:
print(f"Extracting: {sector}...")
rows = extract_funding_rounds(sector)
for row in rows:
row["query_sector"] = sector
all_rows.append(row)
# Convert to CSV
output = io.StringIO()
writer = csv.DictWriter(output, fieldnames=["query_sector", "name", "round", "amount_usd", "lead_investor", "sector", "date"])
writer.writeheader()
writer.writerows(all_rows)
return output.getvalue()
if __name__ == "__main__":
csv_output = pipeline(["AI infrastructure", "climate tech", "biotech"])
print(csv_output)
Schema Design Constraints
The Agent API enforces these constraints on JSON schemas:
additionalPropertiesmust befalse. Every"type": "object"in the schema must include"additionalProperties": false. This applies to the top-level schema and all nested objects.- No recursive schemas. A schema cannot reference itself with
$refpointing to its own definition. - No unconstrained dicts. Avoid
"type": "object"withoutproperties. Every object type must have explicitly defined properties. - All properties should be
required. While optional properties are allowed, making all properties required ensures consistent output structure. - No
$refto external schemas. All definitions must be inline.
Patterns That Work
// ✅ Flat object with typed fields
{
"type": "object",
"properties": {
"name": { "type": "string" },
"count": { "type": "integer" },
"tags": { "type": "array", "items": { "type": "string" } }
},
"required": ["name", "count", "tags"]
}
// ✅ Array of typed objects
{
"type": "array",
"items": {
"type": "object",
"properties": {
"key": { "type": "string" },
"value": { "type": "number" }
},
"required": ["key", "value"]
}
}
// ✅ Enum for constrained values
{
"type": "string",
"enum": ["low", "medium", "high"]
}
Patterns to Avoid
// ❌ Recursive schema (self-referencing)
{
"type": "object",
"properties": {
"children": { "$ref": "#" }
}
}
// ❌ Unconstrained object
{
"type": "object",
"additionalProperties": true
}
// ❌ Bare dict/map type
{
"type": "object"
}
Combining Structured Output with Function Calling
You can useresponse_format alongside custom tools. The model calls your functions first, then formats the final response according to your schema.
import json
from perplexity import Perplexity
client = Perplexity()
tools = [
{"type": "web_search"},
{
"type": "function",
"name": "get_internal_price",
"description": "Look up the internal wholesale price for a product SKU.",
"parameters": {
"type": "object",
"properties": {
"sku": {"type": "string", "description": "Product SKU"}
},
"required": ["sku"]
},
},
]
def get_internal_price(sku: str) -> dict:
prices = {"SKU-A100": 8500, "SKU-H100": 25000, "SKU-4090": 1600}
return {"sku": sku, "wholesale_price_usd": prices.get(sku, 0)}
response = client.responses.create(
model="openai/gpt-5.4",
tools=tools,
input="Get the current retail price for the NVIDIA H100 GPU from the web, and also look up our internal wholesale price for SKU-H100. Compare them.",
response_format={
"type": "json_schema",
"json_schema": {
"name": "price_comparison",
"schema": {
"type": "object",
"properties": {
"product": {"type": "string"},
"retail_price_usd": {"type": "string"},
"wholesale_price_usd": {"type": "integer"},
"margin_percent": {"type": "string"},
"source": {"type": "string"},
},
"required": ["product", "retail_price_usd", "wholesale_price_usd", "margin_percent", "source"],
"additionalProperties": false,
},
},
},
)
# Handle function calls
while any(item.type == "function_call" for item in response.output):
next_input = [item.model_dump() for item in response.output]
for item in response.output:
if item.type == "function_call":
args = json.loads(item.arguments)
result = get_internal_price(**args)
next_input.append({
"type": "function_call_output",
"call_id": item.call_id,
"output": json.dumps(result),
})
response = client.responses.create(
model="openai/gpt-5.4",
tools=tools,
input=next_input,
response_format={
"type": "json_schema",
"json_schema": {
"name": "price_comparison",
"schema": {
"type": "object",
"properties": {
"product": {"type": "string"},
"retail_price_usd": {"type": "string"},
"wholesale_price_usd": {"type": "integer"},
"margin_percent": {"type": "string"},
"source": {"type": "string"},
},
"required": ["product", "retail_price_usd", "wholesale_price_usd", "margin_percent", "source"],
"additionalProperties": false,
},
},
},
)
data = json.loads(response.output_text)
print(f"Product: {data['product']}")
print(f"Retail: {data['retail_price_usd']} (from {data['source']})")
print(f"Wholesale: ${data['wholesale_price_usd']:,}")
print(f"Margin: {data['margin_percent']}")
When combining structured outputs with function calling, pass the same
response_format in every turn of the multi-turn loop. The schema is only enforced on the final text output, not on function call arguments.Next Steps
Output Control
Full reference for response_format, streaming, and output shaping.
Function Calling
Combine structured outputs with multi-turn function calling.
Agent API Quickstart
Get started with the Agent API in minutes.
Agent API Models
Choose the right model for structured extraction tasks.