Getting started
Troubleshooting
The failures people actually hit, and what each one is really telling you.
“It returns an empty list and I know there is content”
An empty list means your query was valid and matched nothing. Three usual causes:
- Nothing is published.
/v1serves published content only. A draft is invisible here and visible in the dashboard, which is exactly how it should look while somebody is still writing. - You are on the wrong release. Custom content is release-scoped. Without
?release=you are reading the default release, which may not be the one your colleague just edited. The built-in modules are release-independent, so this never explains a missing blog or knowledge base article. See Releases. - The filter matched nothing.
?tag=setupwith no such tag is an empty list, not an error, because a tag is free-form and “no articles carry this” is a real answer.
“I get 401 and the key is right”
401 means the key did not resolve. Call GET /v1/ping, which confirms the
key and names its project. That separates “wrong key” from “wrong request”.
If ping succeeds and your real request still fails, you are not looking at an authentication problem.
“It works in curl and fails in the browser”
origin_not_allowed. A publishable key is locked to the origins listed on it,
and curl does not send an Origin header, so it is never checked there.
Add the origin to the key in the dashboard, with scheme, host and port exactly
as the browser sends it. www. is ignored when matching, so
https://example.com covers https://www.example.com. http://localhost:3000
and http://localhost:5173 are different origins.
“My image URLs stopped working”
Your project’s bucket is private, so image URLs are presigned and expire.
The object carries url_expires_at saying exactly when.
That is fine for a page fetching at runtime and fatal for a static build, which
bakes the URL into HTML that outlives it. Pass ?media_urls=permanent and the
API will refuse rather than hand you links that die, turning a silent
breakage weeks later into an error at build time. If you need permanent URLs,
make the bucket public. See Media.
“I get 429 and I am not making many requests”
Check which budget. The header names it: X-RateLimit-Remaining-read,
-write or -vote. Budgets are separate, so reading all day never uses up
your allowance to vote.
Two things that surprise people:
- A publishable key is counted per key and per address. If your server is making requests with a publishable key, every one of them shares a single address budget. Use a secret key server-side.
Retry-Afteris when the refusal lifts, not when the counting window rolls. Wait exactly that long. Retrying sooner earns another 429.
See Rate limits.
“where[…] seems to be ignored”
Two possibilities, and the response tells you which:
- A
400naming the fields that exist means you used a field key that is not on the type.response_shapein/v1/introspectionlists them. - A
400naming operators means the operator does not apply to that field type.containson a boolean is refused rather than silently matching nothing, because answering it with “0 results” would hide the mistake.
where only exists on /v1/content. The built-in modules take named filters
instead, because their schema is fixed: category, author, tag and q
for the blog; collection, section and q for the knowledge base.
“Sorting changed how paging works”
It did, and the response says so. A cursor encodes a position in the default
order, so asking /v1/content for a sort switches it to offset paging:
next_cursor goes null and next_offset fills in.
Read both keys and pass back whichever is set. That one loop works everywhere.
“The knowledge base ignores ?sort=”
There is deliberately no sort anywhere in the knowledge base. Every listing
comes back in the order somebody dragged it into: collection, then section,
then the manual order inside it. A help centre in date order is not a help
centre.
If you need another order, sort client-side, knowing you are overriding an editorial decision.
“A slug I know exists returns 404”
Read the code:
kb_article_not_published/blog_article_not_published: it exists and is a draft. Publish it.kb_article_archived: it was retired on purpose. Restore it, or remove the link.unknown_kb_article/unknown_blog_article: no such slug. Checkdid_you_meanin the error; it is usually a typo.
Still stuck
Every response carries X-Request-Id, and every error repeats it as
request_id. Quote it, and it leads straight to the log lines for that exact
request.
Updated at, Wednesday, September 2, 2026