> ## Documentation Index
> Fetch the complete documentation index at: https://devs.surferseo.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Troubleshooting

> Find the fix for the response you are getting.

Match the status and body you received against a heading below. Every status and reason the API returns is listed on [Errors](/api/errors); this page covers the cases that table cannot settle by itself.

## Connecting and authenticating

Check the request URL and the `API-KEY` header first.

### An HTML page comes back instead of JSON

The request went to a path that no API endpoint serves, so it fell through to the web app's HTML not-found page. Check that the URL starts with `https://app.surferseo.com/api/v2/` and that the path matches the endpoint's reference page ([URLs and versions](/api/requests#urls-and-versions)).

A JSON `404` with `"reason": "route_not_found"` in `details` means the prefix is right and the rest of the path is wrong.

### `401` although the key is in the request

Surfer reads the key only from the `API-KEY` header, so a key sent as `Authorization: Bearer`, in a query parameter, or under another header name is not seen. Send it as `API-KEY: <your key>` ([Send the key with every request](/api/authentication#send-the-key-with-every-request)). If the header is right, check that you hold the current key ([Get your API key](/api/authentication#get-your-api-key)).

### `403` with a valid key

The key is recognized, but the plan behind it has no API access, API access is not enabled for your organization, or on a v1 tool endpoint the plan lacks that tool. [When authentication fails](/api/authentication#when-authentication-fails) has the check for each case and says when to write to support.

### `404` on a workspace path

The workspace in the path is not one your key can use: it belongs to another organization or is not `active`. Pick an `id` whose `state` is `active` from [List workspaces](/api/v2/workspaces/list-workspaces); [Workspaces in the path](/api/requests#workspaces-in-the-path) has the rule.

The same shape with `"entity": "content_editor"` means the editor id is not in that workspace.

## Requests that are rejected or come back empty

Use the response's `reason` and `details` to identify the rejected input.

### `409` with `Content editor must be in completed state`

The Content Editor has not finished its analysis. Check the editor's `state` with [Get Content Editor](/api/v2/content-editors/get-content-editor). Continue when it is `completed`; when it is `failed`, read its `error` before you create another editor, which spends a credit ([Content Editor states](/api/async-and-webhooks#content-editor-states), [Credits](/api/rate-limits#credits)).

### `413` on a content update, or `400` with `too_large` on an outline submit

The body is over the 1,000,000 bytes that [Update Content Editor content](/api/v2/content-editors/update-content-editor-content) and [Submit edited AI Article outline](/api/v2/ai-articles/submit-edited-ai-article-outline) each read. Trim the document, for example by removing embedded images and inline styles, and send it again.

### `422` with `quota_exceeded`

The organization has used up the allowance the operation spends, or holds as many templates or voices as its plan allows; the `message` names the allowance, for example `Content Editor quota exceeded` or `Auto-Optimize allowance exceeded`. [Credits](/api/rate-limits#credits) says what each operation spends and what to do when an allowance runs out.

### `429` on a burst of requests

The request went over the rate limit. Wait the number of seconds in the `Retry-After` header, then retry, and spread bulk work instead of bursting ([Over the limit](/api/rate-limits#over-the-limit)).

### An empty `data` list where Content Editors exist

Content Editor lists cover the 90 days before now unless you pass `filter[inserted_after]` or `filter[inserted_before]`, so older editors fall outside the window. Move the window to the dates the missing editors were created ([The 90-day window on Content Editor lists](/api/requests#the-90-day-window-on-content-editor-lists)).

### `422` from AI Detector or Humanizer on v1

Either `text` is outside the length the tool accepts, or the tool refused the run; the body says which.

* For [Detect AI content in a text](/api/v1/other/detect-ai-content-in-a-text), split text over 100,000 characters into shorter pieces.
* For [Humanize text](/api/v1/other/humanize-text), skip text shorter than 128 characters.
* When `text` is within its limit, read the JSON body. `Quota exceeded` means the Humanizer allowance is used up ([Credits](/api/rate-limits#credits)). `Unknown error` from AI Detector or `Failed generation, please try again later` from Humanizer means the run failed: retry once, then write to support.

### `500` on a content read or update

`GET .../content` and `PUT .../content` answer `500` with `"reason": "internal_error"` when Surfer could not reach or back up the editor's document.

1. Retry the request after a moment.
2. If it fails again for the same editor, write to support with the editor id.

## Work that does not finish

Check the resource's current state before you start another run.

### The AI Article or the Auto-Optimize job never reaches `completed`

An AI Article (an article Surfer writes inside a Content Editor) and an Auto-Optimize job (a rewrite Surfer applies to the editor's document) both run in the background, and `failed` is final for both. Read the state on [Get AI Article](/api/v2/ai-articles/get-ai-article) or [Get Auto-Optimize job](/api/v2/auto-optimize/get-auto-optimize-job), then:

* `waiting_for_user_input`: the article was created with `manual_outline: true` and waits for your outline. Submit it to resume writing; [Generate an article with outline review](/api/use-cases#generate-an-article-with-outline-review) has the steps.
* `new` for a long time: the article waits for the editor's guidelines to settle, and fails on its own if they never do.
* `failed`: start a new article or job. A new Auto-Optimize run counts toward your plan's Auto-Optimize allowance; a new article spends an AI Article credit on Enterprise and some older plans, while other plans include AI Articles in the Content Editor ([Credits](/api/rate-limits#credits)).

[AI Article states](/api/async-and-webhooks#ai-article-states) and [Auto-Optimize states](/api/async-and-webhooks#auto-optimize-states) give the meaning of every state.

### The SEO score never leaves `calculating`

A failed recalculation leaves `status` at `calculating`, so a poll without a deadline never ends ([Recognizing a fresh score](/api/async-and-webhooks#recognizing-a-fresh-score)).

1. When your polling deadline passes or `content_editor.seo_score.failed` arrives, write the content again with [Update Content Editor content](/api/v2/content-editors/update-content-editor-content) to start a new recalculation.
2. If the score stays at `calculating` again, write to support with the editor id.

### A v1 SERP Analyzer export answers `404 Not found.` or has rows with empty values

[Get search results](/api/v1/serp-analyzer/get-search-results) and [Get prominent terms](/api/v1/serp-analyzer/get-prominent-terms) answer the plain-text `404 Not found.` while the query is still running, when the id is not one of your organization's queries, and, for prominent terms, when no prominent words or phrases were found.

1. Poll [List SERP Analyzer queries for a workspace](/api/v1/serp-analyzer/list-serp-analyzer-queries-for-a-workspace) until the query's `state` is `completed`. `failed` is final; v1 has no retry, so create a new query.
2. Fetch the reports.

In the search results report, every row carries its own `state`: a `scheduled` row is still being crawled, a `failed` row was not crawled, and both have empty values in the factor columns. [SERP competitor exports](/api/use-cases#serp-competitor-exports) has the full recipe.

## Webhooks and integrations

Check the receiver's response status and its verification key.

### No webhook arrives

A delivery counts only when the registered HTTPS URL answers `2xx` in time. Check, in order:

1. Confirm the URL is public and uses HTTPS.
2. Confirm the receiver answers `2xx` before it does any work; [Delivery](/api/async-and-webhooks#delivery) has the deadline and the retry rules.
3. Check that the work you started sends the event you wait for ([Which work sends events](/api/async-and-webhooks#which-work-sends-events)).
4. Compare the key your receiver checks with the current verification key ([Set up a webhook](/api/async-and-webhooks#set-up-a-webhook)).

### Requests from Make or n8n fail authentication

Surfer reads the key only from the `API-KEY` header. In the tool's HTTP request, add a custom header named `API-KEY` with your key as its value instead of the tool's built-in authentication options, and use the URL from the endpoint's reference page ([Send the key with every request](/api/authentication#send-the-key-with-every-request)).

## When a fix does not hold

Remove your API key from anything you send, then write to [support@surferseo.com](mailto:support@surferseo.com) with:

* The method and path, for example `PUT /api/v2/workspaces/123/content_editors/456/content`.
* The time of the request, with its time zone.
* The full response: status, headers, and body. The `x-request-id` header lets support find the request in Surfer's logs.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.