Date: 2026-08-15
Origin: Christian's idea, 2026-08-13. Idea 11 in the free-tier list.
Pillar: installs (net), then trials
Status: Draft. Needs approval. Blocked on a dependency, see below.
| Fact | Source |
|---|---|
| 29 of 58 deactivations happen inside 24 hours | plugin_installs.first_seen vs deactivated_reported_at |
| 17 of those 29 never started a trial | joined against site_trials |
| 22 of those 29 already ran Rank Math, Yoast or AIOSEO | deactivation_metadata.active_plugins |
A fresh install renders as locked | dashboard.php maps no_trial_no_keys to 'locked' |
| Generation throws without a key | ai-requests.php:412 |
| Installs run 1 to 2 per day, deactivations match | plugin_installs, 9 weeks |
A keyless user opens the plugin, sees a trial CTA and nothing else, and half of
them delete it the same day.
Run a real DataForSEO opportunity audit on the domain the moment the plugin
phones home. When the user opens the plugin, the answer is already there. The
first screen is not a CTA. It is their own ranking data.
**christpet/wp-autopost-premium#83 (CP-162: Seed the rank tracker from the
site's existing rankings) and christpet/wp-autopost#80 (CP-162: Link each
tracked keyword to the page that ranks)** are both OPEN as of 2026-08-15.
PR #83 (rank tracker seeding) already delivers:
| Piece | Detail |
|---|---|
ranked_keywords call | dataForSEOProvider.ts, +88 lines |
| Selection rule | 8 keywords, half wins and half opportunities, positions past 20 dropped |
| Seeding service | rankSeedService.ts, 256 lines |
| Admin trigger | POST /api/admin/seed-rank-tracker, supports {"domain":...} and {"all":true,"dryRun":true} |
| Tests | 10 new, 236/236 backend tests pass |
It also answers the cost question I could not. Verified against the live
API, not the docs:
| Domain | Keywords found | Best position | Cost |
|---|---|---|---|
| extrahelpmovers.com | 97 | 3 | $0.024 |
| googledrivedownloader.com | 484 | 14 | $0.018 |
About $0.02 per domain. At 1.5 installs/day that is roughly **$0.90 a
month**. Cost is not a reason to say no.
Do not duplicate any of this. This workplan starts after PR #83 merges.
PR #83 seeds at trial setup (automatedSetupService.ts). This idea needs it
at install, before any trial exists. Three gaps.
RankSeedService.seedFromExistingRankings(siteId, domain) takes a siteId and
writes site_keywords keyed on site_id. A keyless install has no sites row.
I measured the three options rather than pick by preference.
Why not A (create a sites row at install). I first thought the risk was
that many queries treat sites as "a customer". Only 4 of 62 reads run
without a row filter, and none of them is dangerous: a health check
(healthController.ts:41), a cron connectivity check
(trigger-wp-cron.ts:39), an upsert (siteContextService.ts:706), and
internal shadow-eval sampling capped at 50 (shadowEvalService.ts:1193).
So A is safer than I claimed. What kills it is the table's meaning:
| Table | Rows |
|---|---|
sites | 103 |
| of those, with a trial | 95 |
| of those, without a trial | 8, all test artefacts (raw IPs, mailbridge.app) |
site_trials | 112 |
plugin_installs | 251 |
sites currently means "a site that started a trial". Adding roughly 45
keyless installs a month inverts that within two months, and
shadowEvalService would begin sampling non-customers.
Why not C (a JSON blob on plugin_installs). Cheapest to write and useless
afterwards. We will want to ask which keywords we showed against who converted,
and a blob cannot answer that.
Why B. Extract the fetch and the selection out of rankSeedService (they are
effectively pure) and store keyless results in a new install_audits table,
unique on domain. Insert before the DataForSEO call so a repeated ping cannot
double-spend.
The one objection to B was that an audit user who later starts a trial would
need their data migrated into site_keywords. That objection is void. PR #83
(CP-162 rank tracker seeding) already seeds at trial setup through
automatedSetupService.ts, so the trial re-seeds from scratch. Nothing needs to
move. The audit is a pre-trial display artefact and nothing more.
Required whatever else we ship. Add to == Third Party Services ==, which today
lists OpenAI, Anthropic, Google AI, ClearPost Premium and ClearPost Analytics,
and omits DataForSEO entirely.
Proposed text, matching the existing entries:
**SEO Data Provider (Used by the Premium Add-On and the Free Site Audit)**
* **DataForSEO** - Supplies search volume, keyword difficulty and ranking data
- Service: https://dataforseo.com/
- Your site's domain is sent to DataForSEO to retrieve keyword opportunities
and current search rankings for that domain. No site content, no personal
information and no API keys are transmitted.
- This runs only when you request an audit or use premium SEO features
- Privacy Policy: https://dataforseo.com/privacy-policy
- Terms of Service: https://dataforseo.com/terms-and-conditions
Two points that must stay true, or the text becomes a lie:
keywords_for_site, ranked_keywords and bulk_keyword_difficulty, which all take a domain. Check any future endpoint
against this sentence before adding it.
This ships in the same plugin release as the audit, not after it.
The install ping already reaches us. saiap_schedule_context_updates
(site-context.php:1115) hooks wp_loaded and schedules the daily cron with
time(). updateSiteContext calls trackInstallActivity(domain) before
validatePremiumAccess, so we hold the domain even when the request 403s.
But wp-cron only fires when somebody loads a page. On a new site with no
traffic that first load is the admin opening wp-admin, which is the same moment
they open the plugin. The audit will not be ready on first open.
Fix, and both halves are needed:
Replace the locked-state widget (dashboard-widget.php:65,
saiap_widget_no_trial_no_keys) with the audit. Plugin PR #80 already links a
tracked keyword to the page that ranks for it, so that part is done.
Measured across all 251 install rows:
| Domain type | Count | Share |
|---|---|---|
| Real public domains | 225 | 90% |
Local, IP, .test, .local | 14 | 6% |
| Staging or auto-subdomain | 12 | 5% |
172.18.0.1:18090, findadoctor.test and wordpress.local return nothing from
DataForSEO. Detect them and skip the call rather than pay for an empty result.
I had this backwards. I built the plan around ranked_keywords, which shows
where the site stands today. Christian's point: the goal is to show their
top keyword opportunities and the traffic attached to them, so the user sees
the ROI of ClearPost, tailored to their site, before they click anything.
That inversion matters, and it dissolves the open question I raised. A brand
new site with no rankings still has opportunities. Opportunities exist for every
site. Rankings do not.
| Layer | Source | Status |
|---|---|---|
| What they could target, with volume | keywords_for_site, dataForSEOProvider.ts:386 | Already built. Returns up to 100 keywords with search_volume, cpc, competition, competition_index |
| How hard each one is | bulk_keyword_difficulty/live | PR #83 (CP-162 rank tracker seeding) adds it |
| Where they rank today, if at all | ranked_keywords/live | PR #83 (CP-162 rank tracker seeding) adds it |
keywords_for_site returns keyword_difficulty: null (dataForSEOProvider.ts:398),
which is exactly the gap bulk_keyword_difficulty fills. The two endpoints
compose.
keywords_for_site already returns cpc. Volume multiplied by CPC gives the
cost of buying that traffic on Google Ads. That is a real, checkable, tailored
number, and it is ROI without any forecast of our own performance:
These 14 keywords carry 3,400 searches a month. You rank for none of them.
Buying that traffic on Google Ads costs about $2,100 a month at current CPC.
ClearPost publishes 30 posts a month targeting them, for $99.
Every figure comes from DataForSEO or our own price list. Nothing is predicted.
I withdraw my earlier objection. I argued against "we think ClearPost can
drive X traffic in 30 days" because a traffic forecast needs a CTR curve we would
have to defend. The CPC framing gets the same persuasive force from data we
already fetch, so the disagreement disappears.
Christian asked me to research whether this needs an opt-in button. It does.
Three findings, all from our own files.
The readme has a == Third Party Services == section. It lists OpenAI,
Anthropic, Google AI, ClearPost Premium and ClearPost Analytics.
DataForSEO does not appear in it. The data path is plugin, to our server, to
DataForSEO, so the user's domain reaches a third party we never named.
This must be fixed regardless of the opt-in decision. WP.org requires every
external service to be disclosed. Add DataForSEO to that section before any of
this ships.
The exact heading is Premium Add-On Service (Fully Optional). An audit that
fires automatically on activation, and reaches our server, contradicts the words
we published.
saiap_track_event returns immediately unless saiap_tracking_consent === 'yes'
(analytics.php:39). We already gate anonymous analytics behind explicit
consent. Sending the user's domain to a paid third-party SEO API is a larger
step than the thing we already gate.
An explicit button. Not a checkbox buried in settings, and not automatic.
The button is not a cost. It is the offer:
Which keywords can your site actually rank for?
We check your domain against live search data and show you the keywords worth
targeting, how hard each one is, and what that traffic costs to buy on Google
Ads today.
[ Show me my keywords ]
Takes about a minute. No account or API key needed.
The headline is the question they already have. The friction line moves to the
bottom in small text, where it removes an objection instead of leading with one.
That converts a compliance requirement into the strongest CTA on the page. It
also answers "should we spend money on a domain whose owner agreed to nothing",
which was my other open question. The click is the agreement.
setup-stream.php and assets/js/setup-stream.js already render a **live
narration view driven by an SSE stream**, with a short-lived token flow
(saiap_ajax_setup_stream_token, 5 minutes, via /api/trial/setup/token). It
is what trial setup already uses instead of a bare spinner.
Gap: saiap_render_setup_stream_mount() returns early when there is no
licence key (setup-stream.php:114), and saiap_enqueue_setup_stream only
loads the bundle when a trial or licence is present. A keyless installer has
neither, so the stream must be given a keyless token path.
"Reading your site. Found 43 pages." → "Checking 1,200 keywords against live
search data." → "Scoring 14 by volume and difficulty." A minute of honest
narration is more convincing than an instant result.
notice, so they see it from any admin page, not only ours:
"Your ClearPost audit is ready. 14 keywords, 3,400 searches a month."
The audit finishes server-side regardless. On the next admin page load the
notice is waiting. saiap_admin_notices (analytics.php:84) already exists as
the pattern to follow.
Email is a later step, not part of this. A keyless user has given us no
email, and taking admin_email without asking repeats the mistake from the
auto-trial design.
A keyless user is shown "Start Free Trial" in three places today. None of
them needs to be created. Each needs its target changed.
| # | Surface | Where | Today |
|---|---|---|---|
| 1 | The plugin page | dashboard-widget.php:65, saiap_widget_no_trial_no_keys() | "Start Generating Content on Autopilot" + Start Free 7-Day Trial |
| 2 | The WordPress dashboard home | dashboard-widget.php:31, wp_dashboard_setup renders the same widget | the same CTA |
| 3 | The Plugins list row | simple-ai-auto-post.php:318, saiap_plugin_action_links | a "Start Free Trial" link |
Surface 3 is the one that matters most and I nearly missed it. The Plugins
list is the page a person opens to deactivate us. Half of our installs die
within 24 hours, and that is the screen where it happens. A link reading "Run my
free audit" sits inches from the Deactivate link.
Surface 2 is second. The WordPress dashboard home is the first screen after
login. It reaches people who never open our plugin at all.
Write the offer once and call it from all three. The scenario switch already
exists in each file, so this is a substitution rather than new plumbing.
no_trial_no_keys and audit not yet run → the audit offerno_trial_no_keys and audit complete → the result, plus "turn these intoposts" pointing at the trial
The plugin already hooks admin_notices five times. A sixth competes with the
other five and reads as nagging. The single completion notice specified earlier
replaces one of the existing keyless notices rather than adding to them.
admin_bar_menu is currently unhooked. Keep it that way. It appears on the
front end as well as in wp-admin, and it is not worth the intrusion.
Four edits, plus the DataForSEO disclosure already specified in gap 1b.
= Free Version Features (Free Forever) =Add the audit as the first item. It is now the only feature that needs neither a
key nor a trial, so it belongs at the top.
* **Free SEO opportunity audit** - find out which keywords your site can realistically rank for, how much traffic each one carries, and what that traffic would cost you on Google Ads. Runs on your domain in about a minute, with no account and no API key.
== Description ==, the opening paragraphThe first line currently sells autonomous publishing, which needs a key. Add one
sentence naming the question a visitor gets answered immediately:
Not sure what to write about? ClearPost checks your domain against live search
data and shows you which keywords you can realistically rank for, before you
configure anything.
This is the line that shows in every WP.org search result, so it is the highest
leverage copy we own. Candidate:
Find out which keywords your WordPress site can rank for, free. Then let AI write and publish the posts that target them.
That is 128 characters. It leads with the answer and keeps the autoblogging
promise behind it.
Do not ship this in the same release as anything else. Per the 2026-08-13
sweep, exp-1 and exp-3 shipped the same day and neither could ever be
attributed.
= Is this plugin free? = says the core is free "with your own AI providerAPI keys". That is no longer the whole truth once the audit ships.
= Do I need an API key to use this plugin? = says the free version requiresone. After this ships, the audit does not.
Both must change in the same release, or the readme is inaccurate.
A normal entry. Name DataForSEO in it, since that is where a reviewer looks.
Per the 2026-08-07 and 2026-08-13 keyword sweeps, WP.org rank tracks the plugin
NAME and almost nothing else, and tags do nothing. But the description and the
short description drive whether a listing view becomes an install, which is a
separate step from rank.
Lead with the question, not the absence of friction. "No API key needed"
describes what the audit does not cost. "Which keywords can your site actually
rank for" is the question a WordPress owner already has, and it is the thing
only we answer for their specific domain before they commit anything.
The friction line still earns its place, but as a supporting clause. It removes
the objection that 22 of our 29 day-one quitters plainly had, and it should not
be the headline. Worth measuring as its own listing change,
though the measurement rules from the 2026-08-13 sweep apply: at 1.97
installs/day the minimum detectable effect is 75% over 7 days and 31% over 42,
so it needs a long window and one change at a time.
rankSeedService. Add install_audits, unique on domain. Insert before the call so a repeat
ping cannot double-spend.
Window: 6 weeks, ending 2026-09-26. At 1.5 installs/day the minimum
detectable effect is 75% over 7 days and 31% over 42. Judging this early is how
exp-1 (short description) was called positive and exp-4 (tag retarget) negative,
both wrongly, one of them by me.
opportunities from keywords_for_site, so the audit is never empty. Worth one
live test against a brand new domain to confirm the endpoint returns something
useful when the site has almost no content.
never spend on a domain whose owner has not asked us to.