How to migrate from Sonar
This guide maps a Sonar chat completions integration to the Agent API. You keep the same grounded web search, and gain an agent that runs multi-step research, executes its own code, and calls tools like finance and people search.
Use the migration skill
Section titled “Use the migration skill”The fastest path is to let your coding agent do the migration. Open it in the project you want to migrate and send it this:
Read https://github.com/perplexityai/api-platform-developers/blob/main/skills/migrate-sonar-to-agent-api/SKILL.md and install this skill, then use it to migrate this project from Sonar to the Agent API.See the repository README for supported coding agents and other installation options.
1. Update the endpoint and method
Section titled “1. Update the endpoint and method”Point requests at /v1/agent and switch from chat.completions.create() to responses.create().
For plain text, the Agent API takes the same role/content items, so the body barely changes: pass the array as input instead of messages and replace model with preset.
Sonar
from perplexity import Perplexity
client = Perplexity()
completion = client.chat.completions.create(
model="sonar",
messages=[
{"role": "system", "content": "You are a concise research assistant."},
{"role": "user", "content": "What are the latest developments in AI agents?"},
],
)
print(completion.choices[0].message.content)import Perplexity from '@perplexity-ai/perplexity_ai';
const client = new Perplexity();
const completion = await client.chat.completions.create({
model: 'sonar',
messages: [
{ role: 'system', content: 'You are a concise research assistant.' },
{ role: 'user', content: 'What are the latest developments in AI agents?' },
],
});
console.log(completion.choices[0].message.content);curl https://api.perplexity.ai/v1/sonar \
-H "Authorization: Bearer $PERPLEXITY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "sonar",
"messages": [
{"role": "system", "content": "You are a concise research assistant."},
{"role": "user", "content": "What are the latest developments in AI agents?"}
]
}' | jqAgent API
from perplexity import Perplexity
client = Perplexity()
response = client.responses.create(
preset="fast",
input=[
{"type": "message", "role": "system", "content": "You are a concise research assistant."},
{"type": "message", "role": "user", "content": "What are the latest developments in AI agents?"},
],
)
print(response.output_text)import Perplexity from '@perplexity-ai/perplexity_ai';
const client = new Perplexity();
const response = await client.responses.create({
preset: 'fast',
input: [
{ type: 'message', role: 'system', content: 'You are a concise research assistant.' },
{ type: 'message', role: 'user', content: 'What are the latest developments in AI agents?' },
],
});
console.log(response.output_text);curl https://api.perplexity.ai/v1/agent \
-H "Authorization: Bearer $PERPLEXITY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"preset": "fast",
"input": [
{"type": "message", "role": "system", "content": "You are a concise research assistant."},
{"type": "message", "role": "user", "content": "What are the latest developments in AI agents?"}
]
}' | jq2. Map messages to input
Section titled “2. Map messages to input”Sonar takes your prompt as a messages array; the Agent API takes it as input.
For simple single-turn prompts, pass a plain string.
To preserve a system prompt or a multi-turn transcript, pass an array of input items or use the top-level instructions field.
Sonar
from perplexity import Perplexity
client = Perplexity()
# Single user turn
completion = client.chat.completions.create(
model="sonar",
messages=[{"role": "user", "content": "What are the latest developments in AI agents?"}]
)
# System guidance plus a user turn
completion = client.chat.completions.create(
model="sonar",
messages=[
{"role": "system", "content": "You are a concise research assistant."},
{"role": "user", "content": "What are the latest developments in AI agents?"}
]
)
print(completion.choices[0].message.content)import Perplexity from '@perplexity-ai/perplexity_ai';
const client = new Perplexity();
// Single user turn
const simple = await client.chat.completions.create({
model: 'sonar',
messages: [{ role: 'user', content: 'What are the latest developments in AI agents?' }]
});
// System guidance plus a user turn
const completion = await client.chat.completions.create({
model: 'sonar',
messages: [
{ role: 'system', content: 'You are a concise research assistant.' },
{ role: 'user', content: 'What are the latest developments in AI agents?' }
]
});
console.log(completion.choices[0].message.content);# System guidance plus a user turn
curl https://api.perplexity.ai/v1/sonar \
-H "Authorization: Bearer $PERPLEXITY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "sonar",
"messages": [
{"role": "system", "content": "You are a concise research assistant."},
{"role": "user", "content": "What are the latest developments in AI agents?"}
]
}' | jqAgent API
from perplexity import Perplexity
client = Perplexity()
# Simple string input
response = client.responses.create(
preset="fast",
input="What are the latest developments in AI agents?"
)
# System guidance plus a user turn
response = client.responses.create(
preset="fast",
instructions="You are a concise research assistant.",
input=[
{"type": "message", "role": "user", "content": "What are the latest developments in AI agents?"}
]
)
print(response.output_text)import Perplexity from '@perplexity-ai/perplexity_ai';
const client = new Perplexity();
// Simple string input
const simple = await client.responses.create({
preset: 'fast',
input: 'What are the latest developments in AI agents?'
});
// System guidance plus a user turn
const response = await client.responses.create({
preset: 'fast',
instructions: 'You are a concise research assistant.',
input: [
{ type: 'message', role: 'user', content: 'What are the latest developments in AI agents?' }
]
});
console.log(response.output_text);# System guidance plus a user turn
curl https://api.perplexity.ai/v1/agent \
-H "Authorization: Bearer $PERPLEXITY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"preset": "fast",
"instructions": "You are a concise research assistant.",
"input": [
{"type": "message", "role": "user", "content": "What are the latest developments in AI agents?"}
]
}' | jq3. Update output handling
Section titled “3. Update output handling”Read the answer text from response.output_text — the drop-in replacement for Sonar's choices[0].message.content.
When you need more than the answer text (tool calls, search results), iterate the typed output array and branch on each item's type.
Sonar
from perplexity import Perplexity
client = Perplexity()
completion = client.chat.completions.create(
model="sonar",
messages=[{"role": "user", "content": "What are the latest developments in AI agents?"}],
)
print(completion.choices[0].message.content)import Perplexity from '@perplexity-ai/perplexity_ai';
const client = new Perplexity();
const completion = await client.chat.completions.create({
model: 'sonar',
messages: [{ role: 'user', content: 'What are the latest developments in AI agents?' }],
});
console.log(completion.choices[0].message.content);curl https://api.perplexity.ai/v1/sonar \
-H "Authorization: Bearer $PERPLEXITY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "sonar",
"messages": [
{"role": "user", "content": "What are the latest developments in AI agents?"}
]
}' | jq -r '.choices[0].message.content'Agent API
from perplexity import Perplexity
client = Perplexity()
response = client.responses.create(
preset="fast",
input="What are the latest developments in AI agents?",
)
print(response.output_text)import Perplexity from '@perplexity-ai/perplexity_ai';
const client = new Perplexity();
const response = await client.responses.create({
preset: 'fast',
input: 'What are the latest developments in AI agents?',
});
console.log(response.output_text);curl https://api.perplexity.ai/v1/agent \
-H "Authorization: Bearer $PERPLEXITY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"preset": "fast",
"input": "What are the latest developments in AI agents?"
}' | jqStreaming
Section titled “Streaming”Sonar streaming returns incremental chunks with a delta.content field on each choice.
The Agent API streams typed server-sent events, so update stream consumers to branch on each event's type.
For the answer text, consume response.output_text.delta events; tool calls and reasoning arrive as their own response.output_item.* and response.reasoning.* events, not as text deltas.
Sonar
from perplexity import Perplexity
client = Perplexity()
stream = client.chat.completions.create(
model="sonar",
messages=[{"role": "user", "content": "Explain quantum computing"}],
stream=True
)
for chunk in stream:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="")import Perplexity from '@perplexity-ai/perplexity_ai';
const client = new Perplexity();
const stream = await client.chat.completions.create({
model: 'sonar',
messages: [{ role: 'user', content: 'Explain quantum computing' }],
stream: true
});
for await (const chunk of stream) {
if (chunk.choices[0]?.delta?.content) {
process.stdout.write(chunk.choices[0].delta.content);
}
}curl https://api.perplexity.ai/v1/sonar \
-H "Authorization: Bearer $PERPLEXITY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "sonar",
"messages": [
{"role": "user", "content": "Explain quantum computing"}
],
"stream": true
}'Agent API
from perplexity import Perplexity
client = Perplexity()
stream = client.responses.create(
preset="fast",
input="Explain quantum computing",
stream=True
)
for event in stream:
if event.type == "response.output_text.delta":
print(event.delta, end="", flush=True)import Perplexity from '@perplexity-ai/perplexity_ai';
const client = new Perplexity();
const stream = await client.responses.create({
preset: 'fast',
input: 'Explain quantum computing',
stream: true
});
for await (const event of stream) {
if (event.type === 'response.output_text.delta') {
process.stdout.write(event.delta);
}
}curl https://api.perplexity.ai/v1/agent \
-H "Authorization: Bearer $PERPLEXITY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"preset": "fast",
"input": "Explain quantum computing",
"stream": true
}'4. Web search
Section titled “4. Web search”Perplexity's own search — the grounded web search that was built into Sonar — is available through the web_search tool.
The fast preset enables web search by default. Add web_search to the request when you need to configure it, as shown below; requests that select a model directly must add the tool to search the web.
Search controls move onto the tool, so Sonar's top-level search_recency_filter and search_domain_filter go into its filters.
See the Web Search reference for the full set of options.
Sonar
from perplexity import Perplexity
client = Perplexity()
completion = client.chat.completions.create(
model="sonar",
messages=[{"role": "user", "content": "Latest renewable energy policy updates"}],
search_recency_filter="month",
search_domain_filter=["iea.org", "energy.gov"]
)
print(completion.choices[0].message.content)import Perplexity from '@perplexity-ai/perplexity_ai';
const client = new Perplexity();
const completion = await client.chat.completions.create({
model: 'sonar',
messages: [{ role: 'user', content: 'Latest renewable energy policy updates' }],
search_recency_filter: 'month',
search_domain_filter: ['iea.org', 'energy.gov']
});
console.log(completion.choices[0].message.content);curl https://api.perplexity.ai/v1/sonar \
-H "Authorization: Bearer $PERPLEXITY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "sonar",
"messages": [
{"role": "user", "content": "Latest renewable energy policy updates"}
],
"search_recency_filter": "month",
"search_domain_filter": ["iea.org", "energy.gov"]
}' | jqAgent API
from perplexity import Perplexity
client = Perplexity()
response = client.responses.create(
preset="fast",
input="Latest renewable energy policy updates",
tools=[{"type": "web_search", "filters": {"search_domain_filter": ["iea.org", "energy.gov"], "search_recency_filter": "month"}}],
)
print(response.output_text)import Perplexity from '@perplexity-ai/perplexity_ai';
const client = new Perplexity();
const response = await client.responses.create({
preset: 'fast',
input: 'Latest renewable energy policy updates',
tools: [{ type: 'web_search' as const, filters: { search_domain_filter: ['iea.org', 'energy.gov'], search_recency_filter: 'month' } }],
});
console.log(response.output_text);curl https://api.perplexity.ai/v1/agent \
-H "Authorization: Bearer $PERPLEXITY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"preset": "fast",
"input": "Latest renewable energy policy updates",
"tools": [
{
"type": "web_search",
"filters": {
"search_domain_filter": ["iea.org", "energy.gov"],
"search_recency_filter": "month"
}
}
]
}' | jqInline citations
Section titled “Inline citations”Sonar returned inline [n] citations by default.
To make the numbered citation requirements explicit, override the preset's default instructions:
from perplexity import Perplexity
client = Perplexity()
response = client.responses.create(
preset="fast",
instructions=(
"Base every factual statement on the numbered web search results provided. "
"Before finalizing, verify each claim against the sources: re-read the cited results and "
"confirm they actually support the statement; if a claim is not directly supported, drop "
"it or soften it rather than guessing. "
"After each sentence that uses information from those results, cite the exact source "
"number(s) in square brackets right after the statement, like [1] or [1][2], with no "
"space before the bracket. Cite the one to three sources that most directly support the "
"statement, and only cite a source that actually contains that information. Do not cite a "
"source you did not use, do not invent source numbers, and do not add a separate "
"references section."
),
input="What are the latest developments in AI agents?",
tools=[{"type": "web_search"}],
)
print(response.output_text) # answer with inline [n] markersimport Perplexity from '@perplexity-ai/perplexity_ai';
const client = new Perplexity();
const response = await client.responses.create({
preset: 'fast',
instructions:
'Base every factual statement on the numbered web search results provided. ' +
'Before finalizing, verify each claim against the sources: re-read the cited results and ' +
'confirm they actually support the statement; if a claim is not directly supported, drop ' +
'it or soften it rather than guessing. ' +
'After each sentence that uses information from those results, cite the exact source ' +
'number(s) in square brackets right after the statement, like [1] or [1][2], with no ' +
'space before the bracket. Cite the one to three sources that most directly support the ' +
'statement, and only cite a source that actually contains that information. Do not cite a ' +
'source you did not use, do not invent source numbers, and do not add a separate ' +
'references section.',
input: 'What are the latest developments in AI agents?',
tools: [{ type: 'web_search' as const }],
});
console.log(response.output_text); // answer with inline [n] markerscurl https://api.perplexity.ai/v1/agent \
-H "Authorization: Bearer $PERPLEXITY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"preset": "fast",
"instructions": "Base every factual statement on the numbered web search results provided. Before finalizing, verify each claim against the sources: re-read the cited results and confirm they actually support the statement; if a claim is not directly supported, drop it or soften it rather than guessing. After each sentence that uses information from those results, cite the exact source number(s) in square brackets right after the statement, like [1] or [1][2], with no space before the bracket. Cite the one to three sources that most directly support the statement, and only cite a source that actually contains that information. Do not cite a source you did not use, do not invent source numbers, and do not add a separate references section.",
"input": "What are the latest developments in AI agents?",
"tools": [{ "type": "web_search" }]
}' | jqThe [n] markers are embedded directly in the answer text, not in a separate field.
The sources they point to come back separately in an output item with type: "search_results" — read them from its results, each carrying an id, and each [n] marker maps to the result whose id is n.
5. Map Sonar models to presets
Section titled “5. Map Sonar models to presets”Starting points we suggest: sonar → fast, sonar-pro → fast, sonar-reasoning-pro → low, sonar-deep-research → high.
See Presets for details.
Sonar
from perplexity import Perplexity
client = Perplexity()
completion = client.chat.completions.create(
model="sonar-deep-research",
messages=[{"role": "user", "content": "What are the latest developments in AI agents?"}]
)
print(completion.choices[0].message.content)import Perplexity from '@perplexity-ai/perplexity_ai';
const client = new Perplexity();
const completion = await client.chat.completions.create({
model: 'sonar-deep-research',
messages: [{ role: 'user', content: 'What are the latest developments in AI agents?' }]
});
console.log(completion.choices[0].message.content);curl https://api.perplexity.ai/v1/sonar \
-H "Authorization: Bearer $PERPLEXITY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "sonar-deep-research",
"messages": [
{"role": "user", "content": "What are the latest developments in AI agents?"}
]
}' | jqAgent API
from perplexity import Perplexity
client = Perplexity()
response = client.responses.create(
preset="high",
input="What are the latest developments in AI agents?",
)
print(response.output_text)import Perplexity from '@perplexity-ai/perplexity_ai';
const client = new Perplexity();
const response = await client.responses.create({
preset: 'high',
input: 'What are the latest developments in AI agents?',
});
console.log(response.output_text);curl https://api.perplexity.ai/v1/agent \
-H "Authorization: Bearer $PERPLEXITY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"preset": "high",
"input": "What are the latest developments in AI agents?"
}' | jq6. Migrate async requests
Section titled “6. Migrate async requests”Sonar's async API maps to Agent API background runs: submit with background: true and poll the response by id.
See Background Runs for the request shape, polling, reconnect, and the full lifecycle.
Parameter reference
Section titled “Parameter reference”Map the common Sonar parameters to their Agent API equivalents. Some are direct field moves; others are the closest Agent API pattern, and those rows call out the missing 1:1 equivalent.
Standard OpenAI parameters
Section titled “Standard OpenAI parameters”The stream parameter carries over unchanged. The Agent API also accepts temperature and top_p, but whether they affect generation depends on the selected model.
A couple of parameters change on the Agent API:
max_tokensis renamed tomax_output_tokens.response_formatkeeps the same shape forjson_schema, and structured outputs are not restricted to specific Perplexity models on the Agent API. Sonar'sresponse_format.type: "regex"has no Agent API equivalent — redesign regex flows around a JSON schema.
Sonar-specific parameters
Section titled “Sonar-specific parameters”Direct equivalents (some renamed or relocated):
| Sonar parameter | Agent API equivalent |
|---|---|
| Domain, recency, and date filters ( search_domain_filter, search_recency_filter, search_*_date_filter, last_updated_*_filter) |
Same names, inside the web_search tool's filters |
web_search_options.search_context_size |
search_context_size on the web_search tool, not in filters |
web_search_options.user_location |
user_location on the web_search tool, not in filters |
num_search_results |
max_results on the web_search tool — a cap on the total number of results |
reasoning_effort |
reasoning.effort |
No direct field — map to a tool or pattern:
| Sonar parameter | Agent API equivalent |
|---|---|
enable_search_classifier |
Provide the web_search tool and let the model decide when to search |
disable_search |
Omit the web_search tool. With a preset, the preset's tools stay enabled — tools: [] does not clear them and there is currently no public way to disable preset tools; max_tool_calls: 0 disables all tool calls as a blunt workaround |
search_mode |
web (the default) is implicit. academic has no direct equivalent; use web_search with domain filters and prompting. For sec, use Finance Search for structured finance data or web_search filtered to SEC sources for filing/source retrieval |
search_type (Pro Search) |
No direct equivalent — map "fast" to the fast preset (formerly fast-search) and "pro" to preset: "low" (formerly pro-search) for the multi-step Pro Search behavior, or set max_steps on an explicit model to bound multi-step search. There is no "auto" classifier equivalent |
return_related_questions |
Prompt the model to end with a few follow-up questions, optionally through a structured output schema |
No Agent API equivalent — drop these:
search_language_filter.stream_mode— Agent API streaming always emits typed SSE events with reasoning and tool activity as separate events, closest to Sonar'sconcisemode; there is nofull-mode inline-metadata format.- Image results (
return_images,num_images,image_domain_filter,image_format_filter,web_search_options.image_results_enhanced_relevance) — not supported; the Agent API returns noimages. - Video results (
return_videos,num_videos,media_response) — not supported; the Agent API returns novideos.
Multimodal input
Section titled “Multimodal input”| Sonar content part | Agent API |
|---|---|
image_url |
input_image content part (data URI or HTTPS URL) |
file_url / pdf_url |
No equivalent |
video_url |
No equivalent |