Skip to content

fix(dataforseo): surface account-access refusals instead of a generic error - #306

Open
eduyubero wants to merge 1 commit into
every-app:mainfrom
eduyubero:fix/dataforseo-account-error-classification
Open

eduyubero wants to merge 1 commit into
every-app:mainfrom
eduyubero:fix/dataforseo-account-error-classification

Conversation

@eduyubero

Copy link
Copy Markdown

Closes #305.

Problem

A DataForSEO account that has not completed verification answers every data endpoint with an HTTP 200 whose envelope carries:

{ "status_code": 40104,
  "status_message": "Please verify your account before using the API. …",
  "tasks": null, "tasks_count": 0, "cost": 0 }

That reaches the user as "An unexpected error occurred. Please check server logs and try again."

Three things line up to hide the cause:

  • it is not an HTTP 401, so core.ts's auth mapping never sees it;
  • the section classifiers are path-scoped (/backlinks/, AI Optimization), so /dataforseo_labs/ and /keywords_data/ get no classification and fall through to INTERNAL_ERROR;
  • the Worker error event carried only a minified stack with no message — so the server logs the message tells you to check do not contain the reason either.

Diagnosing it currently means reproducing the API call by hand outside the app. It is a day-one blocker for new self-hosters, because DataForSEO issues working credentials before verification and /v3/appendix/user_data succeeds in that state — the key validates while every data endpoint refuses.

Change

  • Classify the refusal on the envelope as DATAFORSEO_AUTH_FAILED, carrying DataForSEO's own status_message.
  • Add DATAFORSEO_AUTH_FAILED to CLIENT_DETAIL_ERROR_CODES so that message reaches the user, matching the existing rationale for that set — setup problems whose text names the fix, where a generic card makes every misconfiguration look the same. The static fallback in error-messages.ts only guesses at a malformed key, so the provider's sentence is strictly better guidance.
  • console.warn the refusal, for the reason the neighbouring upstream branch already documents: the error handlers log only what they capture, so this is the sole record of the message.

Notes for review

Matched by exact code, not by 401xx prefix. My first attempt used the range and your existing test caught it: DataForSEO reuses that range for task-level search-engine failures — 40101 "Internal SE Server Error" and 40103 "Task execution failed" are already in UPSTREAM_FAILURE_STATUS_CODES and mean the opposite (retry, don't go fix the account). The set starts at 40104 alone and is easy to extend.

Envelope only, not the task ladder. Account refusals arrive with no task at all, and the 401xx collision above lives entirely at task level, so classifying only the envelope keeps the two apart.

Ordering. The check sits before the charged-task branch, per that module's existing instruction: "Do not throw this for access / balance failures; classify those first even when DataForSEO includes billing metadata on the failed task." These refusals cost $0.

Reusing DATAFORSEO_AUTH_FAILED rather than adding a code also gets the right local-SEO behaviour for free: GRID_ABORT_ERROR_CODES already treats it as "every remaining call would fail the same way", which is exactly true of an unverified account.

Deliberately out of scope: balance failures (40200) on paths without a section classifier still surface as INTERNAL_ERROR. Fixing those means either a generic billing code or unscoping the two existing section-specific ones — a design call I did not want to make inside a bug fix. Happy to follow up if you have a preference.

Verification

  • Reproduced against a live unverified account, then confirmed fixed after verifying it.
  • pnpm ci:check passes.
  • pnpm test: the one failure (oauth-provider.test.ts, "binds tokens and protected-resource metadata to the canonical MCP URL") reproduces identically on a clean origin/main and passes in isolation — pre-existing and unrelated.
  • Added one test covering the envelope shape DataForSEO actually returns.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EZja8vK2mx6gf2BfZMAXDj

… error

An unverified DataForSEO account answers every data endpoint with a 200
whose envelope carries status_code 40104 ("Please verify your account
before using the API") and no task. That is not an HTTP 401, so core.ts's
auth mapping never fires, and the section classifiers are path-scoped, so
Labs and Keywords Data calls fell through to INTERNAL_ERROR — rendering as
"An unexpected error occurred. Please check server logs and try again."
while the actionable sentence was discarded. The Worker log kept only a
stack with no message, so the server logs that message points at did not
contain the reason either.

Classify the refusal on the envelope as DATAFORSEO_AUTH_FAILED carrying
DataForSEO's own status_message, and allow that code's detail through
toClientError so the user reads the fix. The code also aborts local-SEO
grids mid-run, which is correct here: every remaining point would be
refused identically.

Matched by exact code rather than the 401xx prefix — DataForSEO reuses
that range for task-level search-engine failures (40101, 40103) that
UPSTREAM_FAILURE_STATUS_CODES already classifies and that mean the
opposite.

Closes every-app#305

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZja8vK2mx6gf2BfZMAXDj
ywwenjin163 pushed a commit to ywwenjin163/open-seo that referenced this pull request Sep 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DataForSEO account-level errors (40104 unverified) surface as "An unexpected error occurred"

1 participant