Perplexity API 400 Bad Request

Perplexity validates both the shape of your conversation and the search filters attached to it — and the filters are usually the one part of the request your own users control.

11 min read
Staff Pick

📡 Monitor your APIs — know when they go down before your users do

Better Stack checks uptime every 30 seconds with instant Slack, email & SMS alerts. Free tier available.

Start Free →

Affiliate link — we may earn a commission at no extra cost to you

Live Perplexity status right now

Check this first only to close it off. A 400 is never an outage — a service that is down cannot read your payload well enough to object to it — but ruling the platform out in thirty seconds is cheaper than arguing about it for an hour.

A 400 is the only refusal that is genuinely about what you sent. The connection succeeded, TLS completed, the credential was accepted, your quota was never in question — and then Perplexity read the body and could not use it. No tokens were generated. Nothing was billed. Nothing half-happened, which is the one mercy this error offers compared with a 500.

That makes it the most tractable error in the catalogue and the one most often debugged in the wrong place. Teams open the status page, check the billing dashboard and re-read the retry policy, when the entire fault surface is a few hundred bytes of JSON that are sitting in a variable one stack frame away. The response body names the offending field. Almost nobody reads it, because the SDK threw an exception before anyone got the chance.

30-second triage: serialise the exact body your client was about to send, write it to a file, and replay it with curl. The raw response will name the field Perplexity objected to. If curl reproduces the 400, the bug is in how you built the request and no amount of retrying, failing over or checking live Perplexity status will change it. If curl succeeds with the same bytes, your client is not sending what you think it is sending — look at middleware, a proxy, or a serialiser that drops or adds fields.

400, 401, 404, 422 and 429 are five different problems

These get collapsed into “the API rejected us” and handled by one branch, which is how a malformed body ends up in a retry loop and a validation bug ends up being escalated as an incident. The distinction is how far the request got before it stopped.

 400 Bad Request401 Unauthorized404 Not Found422 Unprocessable429 Too Many Requests
What was refusedThe shape of what you sentWho you areThe thing you namedThe values you sentHow much you asked for
Was the body parsed?Attempted, then rejectedNever looked atRouted before parsingParsed successfullyUsually not reached
Does retrying help?Never — deterministicNeverNeverNeverYes, after a backoff
Who fixes itWhoever builds the requestSecret-store ownerWhoever pins model idsWhoever owns the input dataWhoever controls pacing
Counts as Perplexity downtime?NeverNeverNeverNeverNever

The row that costs the most time is the third. A client that applies one retry policy to every non-2xx response will hammer a deterministic refusal until its deadline expires, converting an error you could have shown in milliseconds into a request that hangs for the full retry budget and then times out — and a timeout is the one symptom that genuinely does look like an outage. That is how a malformed body gets escalated to an on-call engineer. The full map is in the Perplexity API error codes reference.

📡
Recommended

Separate Your Bad Requests From Their Bad Days

External checks run from outside your infrastructure with their own known-good payload, so a 400 in your application is never debugged as a possible provider incident — you can see the provider answering a valid request perfectly well at the same moment.

Try Better Stack Free →

Read the response body, not the SDK exception

A 400 is the error where the provider actually tells you the answer, and the SDK is the thing most likely to throw it away. Most clients collapse a validation failure into a generic exception with a message that has been through two layers of wrapping: the status code survives, and the JSON body naming the offending field frequently does not. Replay the call at the wire level before forming a theory:

# Write the exact body your client builds to a file, then replay it verbatim:
curl -i -s -X POST https://api.perplexity.ai/chat/completions \
  -H "Authorization: Bearer $PERPLEXITY_API_KEY" \
  -H "Content-Type: application/json" \
  --data-binary @body.json

# Prove the bytes are valid JSON before blaming the schema:
python3 -m json.tool < body.json > /dev/null && echo "parses"

Those two commands split the category in half. If the file does not parse, you have a serialisation bug and the schema is irrelevant. If it parses and Perplexity still objects, the response body names the field, and you are done. The structural fix is to log the outgoing body — redacted, and only on a 400 — so the next occurrence is diagnosed from a log line instead of a reproduction. See the Perplexity API testing guide for wiring that into a contract test rather than discovering it in production.

The Perplexity trap: strict alternation, and filters your users can set

Perplexity's chat endpoint expects at most one leading system message followed by strictly alternating user and assistant turns, ending on a user turn. That is a tighter contract than most chat APIs enforce, and RAG harnesses violate it constantly — by injecting a second system message for retrieved context, by appending the retrieved passages as an extra user turn after the real question, or by prepending a persona block to a conversation that already had one. The result is a 400 rather than a degraded answer, which is genuinely the better outcome and still surprising the first time.

The rule to internalise is that retrieved context is not a message. Fold it into the single system message or into the user turn it belongs to, and keep the array's alternation intact regardless of how many things your pipeline wants to say. A harness that builds the array by appending whatever each stage produces will pass its unit tests, where only one stage runs, and fail in production, where three do.

The second shape is different in kind, and it is the one worth designing around: search_recency_filter and search_domain_filter take a fixed vocabulary and a bounded list, so an arbitrary date string or an over-long domain list is rejected outright. Those values are almost always dynamic — a customer's configuration, a tenant setting, a value pasted into a form — which means the 400 arrives from a customer rather than from your deploy, on a schedule you do not control, and reproduces only with that tenant's settings loaded. Validate them at the edge where they enter your system, not at the provider, so a bad value is rejected by your own form with your own message instead of surfacing as an API error three layers down.

The six causes, in the order worth checking

Work down this list rather than across it. Each check is cheap, and the ordering reflects which answers turn out to be correct most often rather than which are most interesting.

  • The message array does not strictly alternate, or does not end on a user turn. Retrieved context appended as its own turn is the usual cause.
  • The JSON never parsed. A trailing comma, a NaN, or an unescaped control character that arrived inside user-supplied text. The body looks fine in a log because the log printed the object, not the bytes.
  • A field has the right name and the wrong type. max_tokens as a string, temperature as a string, a boolean as "true". Anything that has been through a form, a query string or an environment variable is a suspect.
  • A content bound was exceeded. max_tokens above the model’s window, too many messages, an attachment over the size cap. These are input-dependent, so they fire for one request in a thousand and look intermittent.
  • A tool or function schema is not valid JSON Schema. Hand-written parameter definitions with a missing type or a malformed required array are rejected as part of the request, not at call time.
  • A null was sent where the field should have been omitted. Serialisers that emit every key make optional parameters explicit nulls, and an explicit null is a value the validator has to reject.

Notice the split running through that list. The first two are deploy-shaped: they appear at a release, affect a clean fraction of traffic, and are identical every time. The last three are input-shaped: they fire on one request in a thousand, look intermittent, and are usually triggered by something a user typed. Which of those two patterns your error rate shows is worth more than any individual hypothesis, and it is visible before you have read a single response body. If tool schemas are involved, the Perplexity tool calling guide and the structured output guide cover the shapes that are accepted.

Make the next one impossible instead of fast

A 400 is a ten-minute fix that recurs forever unless the conditions that produced it change. Four structural changes remove most of the category permanently:

  • Validate the body against a schema before it leaves your process. A local validator rejects the same request in microseconds, with a stack trace pointing at the line that built it, instead of a round trip and a field name. This is the single highest-leverage change and almost nobody makes it.
  • Build requests in one place, per provider. Most deploy-shaped 400s come from a request-builder shared across providers whose parameter sets have quietly diverged. One builder per provider makes the divergence a compile-time fact rather than a runtime discovery.
  • Log the outgoing body on a 400 only, redacted. The information needed to fix this error exists at the moment it happens and is discarded microseconds later. Capturing it turns every future occurrence from a reproduction exercise into a log lookup.
  • Alert on 4xx separately from 5xx. A 400 rolled into a general error-rate alert either pages the wrong person or hides in noise. It has a different owner, a different runbook and a different urgency — and unlike a 5xx, it will not resolve itself. The setup is in the Perplexity status alerts guide.
  • Pin the SDK, and read the changelog on majors. Parameter sets and request shapes change between major versions, and a 400 arriving the day after a dependency bump is not a coincidence. The Perplexity SDK version guide covers what moves.

Frequently Asked Questions

What does a 400 from the Perplexity API actually mean?

It means the request reached Perplexity, was parsed, and was found unusable on its contents. That is a narrower statement than it sounds, and the narrowness is the useful part: a 400 is decided after the credential was accepted and before any model work began, so it says nothing about your key, nothing about your quota, and nothing about capacity. Everything you would normally check when a call fails — the status page, the billing dashboard, your rate limiter — is on the wrong side of the point at which this one stopped. The entire fault surface is the bytes you sent.

Is a Perplexity 400 ever caused by Perplexity being down?

No, and the reasoning is worth keeping because it inverts the usual instinct. A 400 is a considered answer: something on the far end read your payload, understood it well enough to judge it, and refused. A service that is down cannot do that. So a 400 should never be counted against Perplexity availability in your own metrics, and a monitor that treats every non-2xx response as downtime will page you for your own malformed request. The one edge case is a proxy, gateway or WAF in front of the API returning its own 400 — a header too large, a URL too long — which you can distinguish immediately because the body will not be Perplexity's JSON error shape.

Should I retry a 400?

Not with the same body, ever. A 400 is deterministic: the identical request will be refused identically every time, so a backoff loop converts one instant refusal into dozens of them plus the full retry budget in latency, and delivers a timeout to your user instead of an error you could have shown them in milliseconds. If your client applies one policy to all non-2xx responses, this is the error that proves 4xx and 5xx need splitting. Fail fast, log the response body — which names the offending field — and surface it to an engineer rather than to a customer. A 400 is only retryable after the body has actually changed.

How do I tell a Perplexity 400 from a 422?

By asking how far the request got before it was rejected. A 400 means the request could not be understood as a valid request at all: unparseable JSON, an unknown parameter, a field of the wrong type, a model id that is not a model id. A 422 means the request was understood perfectly and the values in it are not acceptable — well-formed, correctly typed, semantically wrong. The distinction matters because it tells you where to look. A 400 is usually a code or configuration bug you find by diffing your body against the schema; a 422 is usually a data bug you find by looking at the specific values that arrived. Providers are not always rigorous about which they return, so treat the response body as authoritative over the code.

Why does Perplexity reject my RAG request when the same messages work elsewhere?

Because Perplexity enforces strict alternation: at most one leading system message, then user and assistant turns alternating, ending on a user turn. RAG pipelines break this by construction — they append retrieved context as an extra message, or add a second system block for a persona, and the array that results has two consecutive same-role turns in it. Other providers tolerate that and quietly produce a worse answer. The fix is not to relax the harness but to fold retrieved context into the message it belongs to, which is what you wanted the model to see anyway; an extra turn is a structural hint that the passages are a separate speaker, and they are not.

A Perplexity 400 only happens for one customer. What is different about them?

Look at the search filters before you look at anything else. search_recency_filter and search_domain_filter accept a fixed vocabulary and a bounded list, and they are usually the only part of a Perplexity request populated from tenant configuration rather than from your code — which makes them the only part that can vary per customer. An arbitrary date string where an enum is expected, or a domain list longer than the cap, produces a 400 that is perfectly reproducible with that tenant's settings loaded and utterly irreproducible without them. Validate those fields where they enter your system, so the bad value is caught by your own form with your own error message.

Related Perplexity Guides

Stop Escalating Your Own Payload as a Perplexity Outage

API Status Check watches Perplexity and the rest of your stack from outside your infrastructure with its own known-good request — so a malformed body never gets escalated as an incident, and a real incident never gets dismissed as somebody’s bad JSON.

Start Your Free Trial →

Alert Pro

14-day free trial

Stop checking — get alerted instantly

Alert Pro checks the 60+ APIs we monitor every hour and emails you within the hour of a detected change.

  • Email alerts for up to 10 of the APIs we monitor
  • $0 charged today — card required to start
  • Cancel anytime — $9/mo after trial

🌐 Can't Access Perplexity?

If Perplexity is working for others but not for you, it might be an ISP or regional issue. A VPN can help bypass network-level blocks and routing problems.

🔒

Troubleshoot with a VPN

Connect from a different region to test if the issue is local to your network. Also protects your connection on public Wi-Fi.

Try NordVPN — 30-Day Money-Back Guarantee
🔑

Secure Your Perplexity Account

Service outages are a common time for phishing attacks. Use a password manager to keep unique, strong passwords for every account.

Try NordPass — Free Password Manager
Quick ISP test: Try accessing Perplexity on mobile data (Wi-Fi off). If it works, the issue is with your ISP or local network.

⏳ While You Wait — Try These Alternatives

🛠 Tools We Use & Recommend

Tested across our own infrastructure monitoring 200+ APIs daily

SEMrushBest for SEO

SEO & Site Performance Monitoring

Used by 10M+ marketers

Track your site health, uptime, search rankings, and competitor movements from one dashboard.

We use SEMrush to track how our API status pages rank and catch site health issues early.

From $129.95/moTry SEMrush Free
View full comparison & more tools →Affiliate links — we earn a commission at no extra cost to you