Docs
Dashboard

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:

  1. Nothing is published. /v1 serves 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.
  2. 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.
  3. The filter matched nothing. ?tag=setup with 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-After is 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 400 naming the fields that exist means you used a field key that is not on the type. response_shape in /v1/introspection lists them.
  • A 400 naming operators means the operator does not apply to that field type. contains on 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. Check did_you_mean in 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