DataForSEO for the Gateway
Step-by-step guide: a DataForSEO login (DATAFORSEO_API_KEY) on the Gateway for OpenSEO and the DataForSEO MCP app: the account and its balance, the key, the check, rotation.
DataForSEO is the SEO data provider behind OpenSEO: keyword metrics, SERPs,
backlinks, local business and AI answer data, and scheduled rank checks.
The Gateway holds one DataForSEO credential (DATAFORSEO_API_KEY) and puts
it on every call OpenSEO makes to api.dataforseo.com (or the free
sandbox.dataforseo.com); the app never sees it. At the end the person has
a DataForSEO account with credit on it, the Gateway holds the credential,
and a keyword lookup in OpenSEO returns real numbers.
Settings
| NAME | required or optional | what it holds |
|---|---|---|
DATAFORSEO_API_KEY |
required by OpenSEO and by the DataForSEO MCP app | login:password: the API login and API password from the dashboard's API Access page, joined by a colon. The base64 of that string works too. |
The API password is generated by DataForSEO and is not the account's sign-in password. The Gateway base64-encodes a value containing a colon and sends the result as HTTP Basic auth; a value without a colon is sent as is.
The same credential serves the DataForSEO MCP app, which offers
DataForSEO's tools to members' AI clients through the Gateway's /mcp with
no page and nothing to deploy: pnpm everyapp mcp on dataforseo --json turns
it on (before or after the key is set; mcp list shows both), and members an
admin grants see dataforseo_<tool> tools in their connected client.
Costs and plans
As of October 2026: a free account gets a $1 trial credit on registration,
with no time limit, and the Sandbox is free for any registered user (it
answers like production with made-up data). Real data is pay as you go, no
subscription; the minimum top-up is $50 and funds do not expire. Daily
spending limits can be set per account or per endpoint (API Settings). The
docs state 10,000 API calls per minute and 30 simultaneous requests (the
older FAQ still says 2,000 per minute); the account's own limits are in the
user_data response. Each keyword lookup costs a few cents: a Google Ads
search-volume request is $0.09 live, a DataForSEO Labs request starts at
$0.012 plus $0.00012 per result, a live SERP is $0.002. Since 2026-07-01 the
Backlinks API is pay as you go like the rest (DataForSEO removed its $100
monthly commitment; prices went up 20%). No card is asked for at sign-up
(dataforseo.com/apis/serp-api: "No credit card required"); a card or other
payment comes in only at Add Funds.
Only a person can
- Register the DataForSEO account and accept its terms.
- Read the API login and API password off the API Access page (or their email) and type them at the hidden prompt.
- Add funds, pick a daily spending limit, or clear an old IP Access list.
- Decide to replace a credential the Gateway already holds.
Before you start
pnpm everyapp secret list openseo --json, from any directory (the app id isopenseo; a fork may have changed the manifest'sid): theDATAFORSEO_API_KEYentry indata.app.setup.unmet(names,need.kindrequired,holdergateway,setupsetup/dataforseo.md) means this guide applies, and OpenSEO doesn't open until it is set: the Gateway lists it as needing setup, sends a browser openinghttps://openseo.<app base domain>to its setup dialog and answers anything else 503app_needs_setup.data.app.setup.commandispnpm everyapp configure openseo, which asks for every missing setting (data.nextrepeats it; the OpenRouter key is among them while it is missing: openrouter.md; Context is optional for OpenSEO, soCONTEXT_API_KEYsits indata.app.setup.settingswithset: falseand never inunmet, and configure offers it after the required ones: context.md). The same entry indata.app.setup.settingswith"set": truemeans it is set already: ask before replacing; a value can't be read back, so "replace" means the person fetches the credential again. The older fields still say the same:data.gateway.neededhas{ "name": "DATAFORSEO_API_KEY", "required": true, "set": … }anddata.gateway.missingnames it while unset. This is how an agent lands here:status(under "openseo needs setup") andsecret list openseo(under the unmet setting) printGuide: https://everyapp.com/docs/agent/setup/dataforseo.md, andsecret list --jsoncarries the path as the entry'ssetup(in the checkout the file islanding-page/content/agent/setup/dataforseo.md). The errorAPP_NOT_FOUND("openseo isn't deployed to this Gateway yet", exit 1): deploy it first. OpenSEO lives in its own repository, which publishes a release on every push:pnpm everyapp install https://github.com/every-app/open-seo --jsonfrom this checkout installs it (any repository or tarball URL works;NO_RELEASEmeans that one publishes none). From a clone instead:pnpm installthere, then this checkout'spnpm everyapp deploy --jsonfrom that directory (a clone's own CLI copy can't deploy; itspnpm everyappis for dev). The key can be set before that deploy all the same (pnpm everyapp configurein OpenSEO's directory reads itseveryapp.config.tsinstead of the registry and asks for the Gateway's settings before the first deploy; step 5's root list is then the check, and so ispnpm everyapp secret list --jsonin that directory).pnpm everyapp status --json:data.installedanddata.claimedare true;data.gatewayUrlishttps://<gateway host>;data.appBaseDomainis<app base domain>. The entry indata.appswith"id": "openseo"has"status": "active","bound": trueand the samesetupas 1; while a required setting is missingdata.fixeshasAPP_NEEDS_SETUPfor it (severitywarning, headline "openseo needs setup",runthe same command). Not installed, or no owner: gateway.md first.
Steps
One step per message: give the step, wait for the words it ends with ("I'm in", "I can see them", "done"), then the next. Not a checklist to send at once: steps 1 and 2 catch an unconfirmed email and an IP list before the value is set.
- Person: Open with the one question: do they already have a DataForSEO account, or has OpenSEO run anywhere else? No account: open https://app.dataforseo.com/register and create it (goal: a DataForSEO account; the API login is whatever the API Access page shows in step 2: DataForSEO generates it, on older accounts it is the sign-up email). Confirm the email if asked: until it is confirmed the API answers 40104. When it worked, the dashboard at app.dataforseo.com opens and shows a $1 balance. Already registered: sign in at https://app.dataforseo.com. Ran elsewhere: an OpenSEO deployed outside EveryApp held the base64 form of the same credential, which works unchanged here (step 4's note), and if that deployment whitelisted its server's IP on API Access, the list has to be emptied (step 2). Wait for "I'm in".
- Person: Open https://app.dataforseo.com/api-access (API Access in the
left menu). It shows the API login, the API password and, for the first 24
hours after registration, a ready-made Base64 string of
login:password. After 24 hours the password is hidden: click Send by e-mail (goal: get the password again) and read it from the email. Leave the IP Access list on this page empty: the Gateway calls from Cloudflare's addresses, which change, and one entry makes DataForSEO refuse every other address (40207). Keep the page or email open; nothing is typed yet. Wait for "I can see them". - Person: Optional now, needed before real use: Add Funds in the Billing section or the top bar (goal: credit for live calls; minimum $50), and a daily limit under API Settings, https://app.dataforseo.com/api-settings (counted per 24 hours UTC), so a runaway job stops at a number they chose. The $1 trial credit covers the free smoke check and about ten keyword lookups (at $0.09 a Google Ads request).
- Person: the setup rules' first way, the person types it. In a second
terminal in the
every-appfolder:pnpm everyapp configure openseo(from OpenSEO's directory,pnpm everyapp configure, which works before its first deploy too), which asks for this key among OpenSEO's other unset settings (the OpenRouter key while it is missing: openrouter.md says where it comes from; the optional Context key is offered after, and Enter skips it), orpnpm everyapp secret set DATAFORSEO_API_KEYfor just this one. At the hidden prompt (DATAFORSEO_API_KEY, under lines saying what it is for and where it comes from;secret setasksValue for DATAFORSEO_API_KEY), paste the API login, a colon, and the API password (login:password): the form to recommend, since Verify 2's check takes it as is. Nothing is echoed. It ends with the setting live on the Gateway (Set DATAFORSEO_API_KEY on the Gateway; configure then saysopenseo is ready.or what it still needs). Wait for "done". (The page's Base64 string, or the value an older OpenSEO held, works too; Verify 2 then needs itsheader =form.) Alternative, the setup rules' third way, when they want the smoke check (Verify- as well: in their terminal the hidden read (bash or zsh
read -rs DATAFORSEO_API_KEY && export DATAFORSEO_API_KEY, fishread -s -x DATAFORSEO_API_KEY), pastelogin:password, Enter, then therepnpm everyapp configure openseo --from-env --json(every unset setting whose variable is exported, at once) orpnpm everyapp secret set DATAFORSEO_API_KEY --from-env DATAFORSEO_API_KEY --json; never a typedexport …=, which the shell history keeps. No DataForSEO CLI or API returns the credential, so the second way does not apply.
- as well: in their terminal the hidden read (bash or zsh
- You:
pnpm everyapp secret list --json:DATAFORSEO_API_KEYis indata.gateway.names. Then the Verify section.
Verify
pnpm everyapp secret list openseo --json, from anywhere: theDATAFORSEO_API_KEYentry indata.app.setup.settingshas"set": trueand has leftdata.app.setup.unmet;data.app.setup.readyis true once nothing else of OpenSEO's is missing (its OpenRouter key counts too, Context doesn't; until thencommandanddata.nextname only what is left). In the older fields, itsdata.gateway.neededentry has"set": trueand the name leftdata.gateway.missing. Thenpnpm everyapp status --json: theopenseoentry'ssetup.readyis true anddata.fixeshas noAPP_NEEDS_SETUPfor it, once everything else is set. OpenSEO not deployed yet (APP_NOT_FOUND): step 5's root list is the check until it is.- Smoke check, in the person's terminal (the endpoint is account-wide, so
the Gateway refuses it for apps; from the terminal it is the right check).
It needs the variable in that shell, holding the same form they pasted
in step 4: after the hidden prompt nothing is exported, so either have
them read it in hidden again for this check (the setup rules' third way:
read -rs DATAFORSEO_API_KEY && export DATAFORSEO_API_KEY, paste, Enter), or let the app be the check (3). The credential reaches curl as a config file on stdin, never on the command line (printfis a shell builtin, so no process carries it):printf 'user = "%s"\n' "$DATAFORSEO_API_KEY" | curl -s -K - https://api.dataforseo.com/v3/appendix/user_data | jq '{status: .status_code, message: .status_message, balance: .tasks[0].result[0].money.balance}'Expected:"status": 20000,"message": "Ok.", and the balance in USD (1 on a fresh account). The call is free. A wrong credential prints"status": 40100and"balance": null; the Base64 form inuser =prints the same, so when the variable holds Base64 (step 4's note), give them this line instead, before reading 40100 as a wrong login or password:printf 'header = "Authorization: Basic %s"\n' "$DATAFORSEO_API_KEY" | curl -s -K - https://api.dataforseo.com/v3/appendix/user_data | jq …Thenunset DATAFORSEO_API_KEY. - The app: open
https://<app>.<app base domain>(the id fromdata.apps, usuallyopenseo), open a project, then Keyword Research (under Research in the sidebar; the URL ends in/keywords), and search one keyword. Search volume and difficulty appear; each lookup costs a few cents. A browser that lands on the Gateway's setup dialog instead (https://<gateway host>/?setup=openseo) means a required setting is still unset: Verify 1 says which. OpenSEO shows one message for a missing key and a rejected one alike: "DataForSEO isn't set up on your EveryApp gateway yet, or rejected the key it holds. An owner or admin sets it with pnpm everyapp secret set DATAFORSEO_API_KEY." Tell them apart withpnpm everyapp secret list --json:DATAFORSEO_API_KEYabsent fromdata.gateway.namesmeans step 4 did not land (behind the Gateway's setup gate the app doesn't open in that state, so expect the second); present means the value is wrong (Verify 2 says which way). The Gateway's log tells them apart too (4). - Logs, if that helps:
cfhas no Worker tail (cf workers --helplists none). In the Cloudflare dashboard, the setup rules' path: Workers & Pages →every-app-gateway→ Observability (Logs on older accounts) → Live for a stream, or the stored Workers Logs on the same page. Each call that reached DataForSEO is one JSON line withevt: "integration.proxy.call",capability: "dataforseo", the path, the upstreamstatusand DataForSEO'scost; the keys are indexed, so filter onevt. A wrong value showsstatus: 401there. A key the Gateway does not hold is refused before any call, so no event appears at all;evt: "integration.proxy.upstream_failed"means DataForSEO could not be reached. Without the dashboard,cf o11y telemetry query --view events --limit 50 --timeframe-from <ms> --timeframe-to <ms> --body '<query JSON>'reads the same stored logs (read-only); the query body that filters onevtis not worked out yet (unverified).
If it fails
| error or symptom | meaning | what to do |
|---|---|---|
SECRET_VALUE_REQUIRED from secret set --json |
Nothing was piped and there is no terminal to prompt in | One of the setup rules' three ways: the person's own terminal, or --from-env |
SECRET_VALUE_REQUIRED from pnpm everyapp configure openseo --json |
Nothing was piped (with --from-env, stdin isn't read), no variable of a missing name was found, and there is no terminal to ask in; the message names what OpenSEO still needs |
The person's own terminal (step 4), NAME=value lines on stdin, or the hidden read then its data.rerun, pnpm everyapp configure openseo --from-env |
SECRET_SEVERAL_NAMES |
--from-env or a pipe with more than one name |
One name per call |
SECRET_EMPTY |
The --from-env variable is empty or not exported in that shell |
The hidden read (the setup rules' third way) in that same terminal first, then rerun |
A browser opening OpenSEO is sent to https://<gateway host>/?setup=openseo; any other request gets 503 with the body {"error":"app_needs_setup","app":"openseo"} |
The Gateway's setup gate: a required setting of OpenSEO is still unset (this key or the OpenRouter one; Context is optional) | data.app.setup.command, pnpm everyapp configure openseo (step 4), then Verify 1 |
integration_not_configured (503, header x-everyapp-error) |
The Gateway holds no DATAFORSEO_API_KEY; OpenSEO shows it as "DataForSEO isn't set up on your EveryApp gateway yet, or rejected the key it holds. An owner or admin sets it with pnpm everyapp secret set DATAFORSEO_API_KEY." (behind the setup gate the app doesn't open in this state) |
Steps 4 and 5; check the name's spelling in data.gateway.names |
capability_not_requested (403) |
The deployed manifest does not list capabilities.dataforseo |
OpenSEO's does; a fork must add it to everyapp.config.ts and deploy again |
integration_path_not_allowed (403) |
The app called an account-wide path (appendix/user_data, appendix/webhook_resend, tasks_ready, id_list, errors) |
By design: one key serves every app. Check balance and limits from the terminal (Verify 2) |
integration_method_not_allowed (405) |
A method other than GET or POST | DataForSEO reads with GET and POST only |
integration_upstream_failed (502) |
The Gateway could not reach DataForSEO | Retry; a persisting failure is on DataForSEO's side |
status_code 40100, or HTTP 401, "you are not authorized to access this resource" |
Wrong login or password: the sign-in password instead of the API password, a typo, whitespace | Re-read API Access (or Send by e-mail), set it again (step 4); the user form needs login:password, not base64 |
| 40104 "please verify your account before using the api" | The sign-up email is not confirmed yet | Open DataForSEO's confirmation email, click its link, rerun Verify 2 |
| 40200 "payment required", 40210 "insufficient funds" | The balance is too low for the request | Add Funds (minimum $50) |
| 40203 "the cost limit has been exceeded" | The daily spending limit in API Settings was reached (counted per 24 hours UTC) | Raise it at https://app.dataforseo.com/api-settings, or wait for 00:00 UTC |
| 40202 "rate-limit per minute has been exceeded", 40209 "too many simultaneous queries" | Over the per-minute or 30-concurrent limit | Retry after a pause; OpenSEO's scheduled checks spread out on their own |
| 40204 "access denied. visit plans and subscriptions" | Left over from the Backlinks commitment DataForSEO removed on 2026-07-01; a funded pay-as-you-go account should not see it | Contact DataForSEO, https://dataforseo.com/contact |
| 40207 "access denied. your ip is not whitelisted" | API Access has an IP list; the Gateway's Cloudflare addresses are not on it | The person removes every entry under IP Access on https://app.dataforseo.com/api-access (step 2) |
| 40201 "unusual activity" | DataForSEO paused the account | The person contacts DataForSEO at https://dataforseo.com/contact (a form; the page says support is available 24/7) |
| 40400 or 40402 "invalid path" | The endpoint path is wrong | An app bug, not a setup problem: compare with docs.dataforseo.com |
curl prints nothing, or jq reports that the input is not JSON |
The variable is empty, or the response is not JSON (a redirect or block page) | printf … | curl -s -K - -o /dev/null -w '%{http_code}\n' … with the same stdin config to see the HTTP status; never print the variable |
Rerun, rotate, remove
- Rerun. Every step is safe to repeat: the dashboard shows what exists,
configureandsecret setreplace the value in one new Gateway version (once OpenSEO is ready, configure's menu changes one setting at a time), and the Verify section says where to continue after an interruption. - Rotate. The person changes the API login in API Access (documented:
the old
login:passwordand its base64 stop working). A new API password needs DataForSEO's support (https://dataforseo.com/contact): the help center mentions regenerating it from the dashboard but documents no control for it, and Send by e-mail resends the same one. Then step 4 again with the new value, then Verify. A value that went through the chat is compromised: rotate it, then set it one of the three ways. - Remove.
pnpm everyapp secret unset DATAFORSEO_API_KEY --json. Without--yesit exits 1 withCONFIRMATION_REQUIRED: that is the ask; rerun with--yesonly once the person has said yes. OpenSEO then needs setup again: the Gateway closes it until the key is back (its manifest marksdataforseorequired;statussays so, with this guide's path). Then revoke on DataForSEO's side: change the API login (or have support issue a new password), or close the account if nothing else uses it.
Sources
- https://docs.dataforseo.com/v3/auth/ (API login and password, Basic auth, API Access page)
- https://docs.dataforseo.com/v3/appendix/user_data/ (the free account endpoint;
resultis an array,money.balance,rates.limits;backlinks_subscription_expiry_datedeprecated) - https://docs.dataforseo.com/v3/appendix/errors/ (status codes 20000 to 50000, 40104, 40207; the 40204 note still names the old Backlinks commitment)
- https://docs.dataforseo.com/v3/appendix-sandbox/ (sandbox host, free, dummy data, limits)
- https://dataforseo.com/pricing (pay as you go, $50 minimum, $1 trial credit, funds do not expire)
- https://dataforseo.com/pricing/keywords-data/google-ads ($0.09 per live task, $0.06 standard, billed per task) and https://dataforseo.com/pricing/dataforseo-labs ($0.012 per task plus $0.00012 per item)
- https://dataforseo.com/apis/serp-api ("No credit card required", $1 credit, $0.002 per live SERP)
- https://dataforseo.com/update/pricing-update-in-dataforseo-apis (2026-07-01: the $100 Backlinks and LLM Mentions commitment removed, +20%)
- https://dataforseo.com/faq (2,000 calls per minute, $50 minimum, trial credit)
- https://dataforseo.com/help-center/dfs-dashboard-how-to-find-everything (API login is generated and changed in API Access, Send by e-mail, Payments, API Settings; no regenerate control)
- https://dataforseo.com/help-center/how-to-authenticate-with-dataforseo-google-search-serp-api-api-key-setup-and-code-examples (sign-up link, 24-hour visibility; calls the login the sign-up email)
- https://dataforseo.com/help-center/how-does-your-free-unlimited-trial-work (trial terms, one account per user)
- https://dataforseo.com/help-center/whitelist-ips (IP Access on the API Access page, 40207, static IPs only, empty list means no restriction)
- https://dataforseo.com/help-center/limit-my-payments (API Settings, per API or endpoint, 24 hours UTC, 40203)
- https://dataforseo.com/contact (form and phone; "support available 24/7"; no support@ address anywhere on it)
- https://developers.cloudflare.com/workers/observability/logs/real-time-logs/ (Workers & Pages, Overview, Worker, Logs, Live;
npx wrangler tail) and https://developers.cloudflare.com/workers/observability/logs/workers-logs/ (Observability tab; JSON log keys are indexed as fields) - https://app.dataforseo.com/register and https://app.dataforseo.com/api-access (the pages the person opens; both load a signed-in app, so their text was not read)
- In code:
packages/sdk/src/internal/appSettings.ts(AppSetup:unmet,settings,groups,command;setup/dataforseo.mdfrom the capability registry,packages/sdk/src/internal/capabilities.ts),packages/cli/src/commands/secret/index.ts(secret list <app>from anywhere,data.app.setup, the keptdata.gateway.neededandmissing,APP_NOT_FOUND),packages/cli/src/commands/configure/index.ts(configure <app>: the prompts,NAME=valuelines on stdin,--from-env,SECRET_VALUE_REQUIRED),packages/cli/src/lib/app/settings.ts(theGuide:line),packages/cli/src/commands/status/problems.ts(APP_NEEDS_SETUP),apps/every-app-gateway/src/server/app-needs-setup.ts(the setup gate: the redirect to/?setup=<app id>and 503app_needs_setup); OpenSEO'severyapp.config.ts(dataforseorequired, with itswhy)
Cloudflare AI Gateway in front of the model providers
Step-by-step guide: put Cloudflare AI Gateway in front of every model call (everyapp ai gateway on, AI_GATEWAY_ID, the optional authenticated gateway key), check it and turn it off.
Context.dev for the Gateway
Step-by-step guide: a Context.dev key (CONTEXT_API_KEY) on the Gateway for OpenCRM and OpenSEO's rendered audits: the account, the key, the check, rotation.