Google Search Console API: How I Launched 5 Sites in 2 Days Without a Dashboard

You bought a domain, built the page, deployed it, and then lost the rest of the afternoon to dashboards. Add a property in Search Console. Copy a TXT record. Paste it into your DNS. Wait. Click Verify. Do it all again in Bing Webmaster Tools. Remember IndexNow exists. Then do it for the next site, and the one after that. If you launch more than a site or two a year, the dashboards are the slow part, not the building.

Everything on that list has an API. Here is the exact pipeline I use to take a site from “domain not bought” to “verified in Google and Bing with its sitemap submitted”, every call and command included, plus the one Google permission problem that silently kept four of my sites out of Search Console.

The short answer. The Google Search Console API can add a brand-new domain property and submit its sitemap, but it cannot verify one. Verification goes through a second API, the Site Verification API, and your OAuth token needs the siteverification scope on top of the usual webmasters scope. With both, the whole launch runs from a terminal: buy the domain with the Cloudflare Registrar API, deploy with wrangler, add the Google TXT record through the Cloudflare DNS API, verify, add the sc-domain: property, submit the sitemap, then register the site with the Bing Webmaster API and ping IndexNow.

What I launched in two days

Between 29 and 30 September 2026, Claude Code and I put five static sites live. Claude Code did the typing: it wrote the build scripts, bought the domains, deployed them, and registered each one with Google and Bing through their APIs. I did two things by hand. I said yes to each domain and its price before it was bought, and I clicked Allow once on a Google consent screen.

Site What it is Mobile Lighthouse / PSI
dictationappformac.com One-page landing for a Mac dictation search 99 performance, 100 on the other three
safetoquit.com Site for Safe to Quit, a Mac menu bar app 98 performance, 100 on the other three
earlydaysclub.com Landing page for a new community idea 99 to 100
newsletters.ltd Directory of verified newsletters, 20 URLs 100
taskmanagermac.com Five-page guide site 100 (LCP 1.6s, TBT 0ms, CLS 0)

Four of those domains were bought through the API at $10.46 a year each. All five are plain static HTML on Cloudflare Pages, with no framework, which is most of the reason the speed scores look like that.

The five-step pipeline from buying a domain to a site registered with Google and Bing

The honest part: the first four sites went live without Google Search Console, and nobody flagged it until the next day. Bing was done. Google was not, because of a permission problem I explain in step 3. The fix turned into the checklist in step 5, and that checklist is the most useful thing in this post.

Step 1: Get four credentials, then buy the domain

The accounts, tokens and scopes

You need four credentials. Get them once and every future launch reuses them.

What Where you get it What it must be allowed to do
Cloudflare API token Cloudflare dashboard, My Profile or Account, API Tokens Registrar, DNS edit, Pages edit, and redirect rules on your zones
Google OAuth client Google Cloud console, APIs and Services, Credentials, type “Desktop app” Used once to mint a refresh token
Google refresh token One consent run with that client Scopes https://www.googleapis.com/auth/webmasters and https://www.googleapis.com/auth/siteverification
Bing Webmaster API key Bing Webmaster Tools, Settings, API access (Microsoft’s guide) Add, verify and submit for sites in your account

In the same Google Cloud project, switch on two APIs: the Google Search Console API and the Site Verification API. Miss the second one and verification fails with an error that says the service is disabled.

I use one account-owned Cloudflare token. One gotcha there: an account-owned token fails the usual /user/tokens/verify check, so test it against the account instead:

curl -s "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/tokens/verify" \
  -H "Authorization: Bearer $CF_API_TOKEN"

Keep all four out of your code and out of your chat window. I keep mine in one local secrets file and run scripts through a small injector that loads them into the environment, so the values never enter the model’s context. Every snippet below reads them from environment variables.

Buy the domain with the Cloudflare Registrar API

Cloudflare’s Registrar API is still in beta, but it worked on every purchase I made. First check the domain is registrable and see the price:

curl -s -X POST "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/registrar/domain-check" \
  -H "Authorization: Bearer $CF_API_TOKEN" -H "Content-Type: application/json" \
  --data '{"domains":["example.com"]}'

Each result tells you whether the domain is registrable, its tier, and pricing.registration_cost. My script refuses anything that is not a standard tier or costs more than $15, so a premium domain can never be bought by accident. Then register:

curl -s -X POST "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/registrar/registrations" \
  -H "Authorization: Bearer $CF_API_TOKEN" -H "Content-Type: application/json" \
  --data '{"domain_name":"example.com","auto_renew":true}'

Set auto_renew yourself. Cloudflare’s registration API leaves auto-renew off unless you ask for it, and a site you forget to renew is a site someone else owns next year. Registration is not instant, so poll GET .../registrar/registrations/example.com/registration-status every few seconds until the state is succeeded. The zone then shows up in your Cloudflare account, ready for DNS.

My rule: the token can buy, but the agent may not buy without my yes on the exact domain and price. That one sentence lives in the instructions Claude Code reads at the start of every session.

Step 2: Deploy to Cloudflare Pages and attach the domain

Create the Pages project once, then every deploy is one command:

npx wrangler pages project create example-site --production-branch main
CLOUDFLARE_ACCOUNT_ID=$CF_ACCOUNT_ID npx wrangler pages deploy ./site \
  --project-name example-site --branch main --commit-dirty=true

I put that second line in package.json as npm run deploy, after the build step. If wrangler suddenly says you are not logged in, its login expires every few days; npx wrangler whoami refreshes it.

Now attach the domain. Tell Pages about it, then point DNS at the project:

# 1. add the custom domain to the Pages project (repeat for www.example.com if Pages should serve it)
curl -s -X POST "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/pages/projects/example-site/domains" \
  -H "Authorization: Bearer $CF_API_TOKEN" -H "Content-Type: application/json" \
  --data '{"name":"example.com"}'

# 2. proxied CNAMEs for the apex and www
for NAME in example.com www.example.com; do
  curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records" \
    -H "Authorization: Bearer $CF_API_TOKEN" -H "Content-Type: application/json" \
    --data "{\"type\":\"CNAME\",\"name\":\"$NAME\",\"content\":\"example-site.pages.dev\",\"proxied\":true,\"ttl\":1}"
done

Pick one host and 301 the other to it. I send www to the apex with a zone redirect rule:

curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets/phases/http_request_dynamic_redirect/entrypoint" \
  -H "Authorization: Bearer $CF_API_TOKEN" -H "Content-Type: application/json" \
  --data '{"rules":[{"action":"redirect","description":"www to apex",
    "expression":"(http.host eq \"www.example.com\")",
    "action_parameters":{"from_value":{"status_code":301,"preserve_query_string":true,
      "target_url":{"expression":"concat(\"https://example.com\", http.request.uri.path)"}}}}]}'

That PUT replaces every rule in the phase. On a zone that already has redirect rules, read the ruleset first and append yours, or you will delete someone’s redirects. Cloudflare documents the rule format in its single redirects API guide.

Before you register anywhere, check five things, because submitting a broken site just gets the broken version crawled:

  • The apex returns 200 and www returns 301 to it.
  • /robots.txt allows crawling and has a Sitemap: line.
  • /sitemap.xml returns real XML that lists every public URL. On a single-page app, check it is not your fallback HTML page served with a 200.
  • No noindex meta tag and no X-Robots-Tag header on public pages.
  • Every page has a title, meta description, canonical and an og:image.

Step 3: Verify and add the site with the Google Search Console API

This is where my four sites fell through, so start with the trap.

I already had Google refresh tokens with the webmasters scope. They could read every report, add properties and submit sitemaps. They could not verify a domain I owned, because verification is a different API with a different scope, siteverification. So the new sites got Bing and quietly skipped Google.

A Search Console token without the siteverification scope can read every report and still cannot add a single new domain. The fix is one consent run with both scopes. Send yourself through Google’s OAuth URL with a loopback redirect, which is Google’s documented flow for desktop apps:

https://accounts.google.com/o/oauth2/v2/auth
  ?client_id=YOUR_DESKTOP_CLIENT_ID
  &redirect_uri=http://127.0.0.1:8766/
  &response_type=code
  &scope=https://www.googleapis.com/auth/webmasters https://www.googleapis.com/auth/siteverification
  &access_type=offline
  &prompt=consent

A tiny local server catches the code on 127.0.0.1, swaps it for tokens at https://oauth2.googleapis.com/token, and saves the refresh token. access_type=offline is what gets you a refresh token; prompt=consent makes Google issue a fresh one even if you approved the app before. After the swap, check the granted scopes with https://oauth2.googleapis.com/tokeninfo?access_token=..., because an unticked box on the consent screen gives you a token without the scope and no error. If your consent screen is External and in testing mode, Google expires those refresh tokens after seven days; an Internal consent screen on a Workspace account avoids that.

From here every launch is automatic. Get a short-lived access token from the refresh token:

ACCESS_TOKEN=$(curl -s https://oauth2.googleapis.com/token \
  -d client_id=$GOOGLE_CLIENT_ID -d client_secret=$GOOGLE_CLIENT_SECRET \
  -d refresh_token=$GSC_REFRESH_TOKEN -d grant_type=refresh_token | jq -r .access_token)

Ask the Site Verification API for a DNS token:

curl -s -X POST "https://www.googleapis.com/siteVerification/v1/token" \
  -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \
  --data '{"site":{"type":"INET_DOMAIN","identifier":"example.com"},"verificationMethod":"DNS_TXT"}'
# -> {"method":"DNS_TXT","token":"google-site-verification=..."}

Write that token as a TXT record on the apex with the Cloudflare DNS API:

curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records" \
  -H "Authorization: Bearer $CF_API_TOKEN" -H "Content-Type: application/json" \
  --data "{\"type\":\"TXT\",\"name\":\"example.com\",\"content\":\"$GOOGLE_TXT\",\"ttl\":300,\"comment\":\"Google Search Console verification\"}"

Do not call verify straight away. My script polls two public resolvers, Cloudflare’s https://cloudflare-dns.com/dns-query and Google’s https://dns.google/resolve, and only moves on when both return the record, for up to five minutes. Then it verifies, retrying every 20 seconds a few times:

curl -s -X POST "https://www.googleapis.com/siteVerification/v1/webResource?verificationMethod=DNS_TXT" \
  -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \
  --data '{"site":{"type":"INET_DOMAIN","identifier":"example.com"}}'

A 200 lists you as an owner. Now the Search Console API proper. Add the domain property, then submit the sitemap. Both are PUTs with an empty body, and both paths are URL-encoded:

PROP="sc-domain%3Aexample.com"
curl -s -X PUT "https://www.googleapis.com/webmasters/v3/sites/$PROP" \
  -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Length: 0"

SITEMAP="https%3A%2F%2Fexample.com%2Fsitemap.xml"
curl -s -X PUT "https://www.googleapis.com/webmasters/v3/sites/$PROP/sitemaps/$SITEMAP" \
  -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Length: 0"

# confirm: isPending true right after submission is normal
curl -s "https://www.googleapis.com/webmasters/v3/sites/$PROP/sitemaps" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

Use the sc-domain: (Domain) property rather than a URL-prefix one. One Domain property covers http, https, www and every subdomain, and DNS is the only way to verify it, which you are already doing. Google’s reference pages for sites.add, sitemaps.submit and webResource.insert have the full field lists.

Step 4: Bing Webmaster API and IndexNow

Bing’s API is older and uses an API key in the query string. Add the site, then read your sites back to get its verification code:

BING="https://ssl.bing.com/webmaster/api.svc/json"
curl -s -X POST "$BING/AddSite?apikey=$BING_API_KEY" \
  -H "Content-Type: application/json; charset=utf-8" --data '{"siteUrl":"https://example.com/"}'
curl -s "$BING/GetUserSites?apikey=$BING_API_KEY"

The Bing CNAME trap

Here is the Bing trap I fell into. Each site in that response has a DnsVerificationCode. The CNAME that verifies the site uses that per-site code as its host name, pointing at verify.bing.com. Your account also has an authentication code that looks just as official. That one is for the BingSiteAuth.xml file method, not DNS. Bing’s DNS verification CNAME must use each site’s own DnsVerificationCode, not the account-wide authentication code. I pointed three zones at the wrong one before I noticed; those records do no harm, and they do no good either.

# host = the part of DnsVerificationCode before the first dot; keep it DNS-only (not proxied)
curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records" \
  -H "Authorization: Bearer $CF_API_TOKEN" -H "Content-Type: application/json" \
  --data "{\"type\":\"CNAME\",\"name\":\"$BING_CODE.example.com\",\"content\":\"verify.bing.com\",\"proxied\":false,\"ttl\":300}"

curl -s -X POST "$BING/VerifySite?apikey=$BING_API_KEY" \
  -H "Content-Type: application/json; charset=utf-8" --data '{"siteUrl":"https://example.com/"}'
curl -s -X POST "$BING/SubmitFeed?apikey=$BING_API_KEY" \
  -H "Content-Type: application/json; charset=utf-8" \
  --data '{"siteUrl":"https://example.com/","feedUrl":"https://example.com/sitemap.xml"}'
curl -s -X POST "$BING/SubmitUrlBatch?apikey=$BING_API_KEY" \
  -H "Content-Type: application/json; charset=utf-8" \
  --data '{"siteUrl":"https://example.com/","urlList":["https://example.com/","https://example.com/guide/"]}'

VerifySite returns {"d": true} once Bing sees the record; if it says false, wait and retry. My script feeds SubmitUrlBatch every URL it finds in the live sitemap. The method list is in Microsoft’s IWebmasterApi reference.

Last, IndexNow. Generate a 32-character hex key, publish it as a text file at the site root whose content is the key itself, deploy, then ping:

KEY=$(openssl rand -hex 16)
echo -n "$KEY" > site/$KEY.txt   # deploy before pinging
curl -s -o /dev/null -w "%{http_code}\n" -X POST "https://api.indexnow.org/indexnow" \
  -H "Content-Type: application/json; charset=utf-8" \
  --data "{\"host\":\"example.com\",\"key\":\"$KEY\",\"keyLocation\":\"https://example.com/$KEY.txt\",\"urlList\":[\"https://example.com/\"]}"

A 202 means accepted, which is what taskmanagermac.com got for all five of its URLs. Two cautions. Curl the key file after deploying and read the body, because a single-page app fallback can answer that URL with your homepage and a 200. And ping again on every deploy that changes pages, not only at launch. The IndexNow documentation covers the rest.

Step 5: Wrap it in one script and a checklist

Nobody should paste those calls by hand. Claude Code folded steps 3 and 4 into one Python script of about 220 lines that takes a list of domains:

python3 register_site.py example.com another-site.com --dry-run
python3 register_site.py example.com another-site.com
python3 register_site.py example.com --only google

Three properties made it safe to run unattended. It is idempotent: it checks whether the property exists, the record is already there or the sitemap is already submitted before doing anything, so running it twice changes nothing. It only ever adds DNS records and never deletes one. And it never prints a secret, only the names of what it did. If the Google token is missing, the first line of its output says so, and my launch checklist turns that into a task for me instead of a silent skip, which is exactly what bit me the first time.

The script fixed the mechanics. The checklist fixed the process. A launch is not done until each of these is ticked or is a named task waiting on a person:

NEW SITE LAUNCH
[ ] apex 200, www 301 to apex
[ ] robots.txt allows crawl + Sitemap: line
[ ] sitemap.xml is real XML listing every public URL
[ ] no noindex, title/description/canonical/og:image on every page
[ ] Google: sc-domain property verified by DNS TXT, sitemap submitted
[ ] Bing: site added, verified by per-site CNAME, sitemap + URLs submitted
[ ] IndexNow: key file live (curl the body), URLs pinged
[ ] analytics, if the site has signups or sales
[ ] anything needing a human click = a named task, never a silent skip
[ ] recheck in 3 to 7 days: sitemap processed, pages indexed

If you drive this with Claude Code, here is the kind of prompt I give it. It works because it names the finish line and the limits:

Launch example.com as a static site on Cloudflare Pages.
1. Check the domain with the Registrar API. Show me the price and wait for my yes.
2. Build ./site, deploy with wrangler, attach apex + www, 301 www to apex.
3. Run the launch checklist. Fix anything that fails before registering.
4. Register with Google (sc-domain, DNS TXT) and Bing (per-site CNAME), submit
   the sitemap to both, publish an IndexNow key and ping every sitemap URL.
Never delete a DNS record. Never print a secret. End with a table of every
checklist line and its result.

What people get wrong about submitting a new site

These come from Reddit threads where people are stuck on exactly this, and from my own launches.

“I submitted the sitemap two weeks ago and nothing is indexed.” Submission gets a URL discovered, not indexed. A sitemap tells a search engine a URL exists; it does not make the engine want it. On a new domain with no links, waiting is normal. These five sites are days old as I write this, so I am not going to promise you a timeline; what the pipeline guarantees is that nothing on your side is the reason for the wait.

“Crawled but not indexed is a technical problem I can audit away.” Usually it is not. If the page loads, returns 200, is not blocked and is in the sitemap, the engine has seen it and chosen not to keep it yet. That is about the page and the site’s standing, not the submission. Registering faster does not fix it.

“Just request indexing through the API.” You cannot for normal pages. The Search Console URL Inspection API only reads a URL’s status; it has no “request indexing” call. Google’s Indexing API exists, but it is limited to pages with job posting or livestream structured data. For everything else the sitemap is the API route, and the Request Indexing button stays a manual click.

“Bing crawls my site but will not index it.” Check what the crawler actually receives. On one of my zones, Cloudflare answered the default Python user agent with a 403 while browsers got the page. My scripts now send a browser user agent, and it is worth checking that your security settings are not blocking bots you want. Reddit’s other common answer, a title tag that is too short or just “Home”, is worth fixing too.

“IndexNow means Google will pick it up faster.” Google does not take part in IndexNow. It reaches Bing and the other participating engines. Do it anyway, because it is one request per deploy, but do not expect it to change anything in Search Console.

FAQ

Is the Google Search Console API free?

Yes. There is no charge for the Search Console API or the Site Verification API. You need a Google Cloud project with both APIs enabled, and Google applies usage quotas, which a handful of launches will never get near.

Can the Search Console API add and verify a new website?

It can add one, with sites.add, but verification belongs to the separate Site Verification API. Use token to get a DNS TXT value, publish it, call webResource.insert, then sites.add with an sc-domain: property. Your OAuth token needs both the webmasters and siteverification scopes.

Should I use a Domain property or a URL-prefix property?

Domain (sc-domain:example.com) for a site you own outright. It covers every protocol and subdomain in one property and verifies by DNS, which is easy to automate when your DNS has an API. URL-prefix properties only make sense when you cannot touch DNS.

Does this work if my DNS is not on Cloudflare?

The Google and Bing calls are the same everywhere. Only the three DNS writes change: swap the Cloudflare DNS calls for your provider’s API. The registrar and Pages steps are Cloudflare-specific.

Can I use Google Search Console for SEO?

Yes, and every site you care about should be in it. Search Console is where Google tells you which pages it indexed, which it refused and why, what queries you show up for, and whether your sitemap was read. It does not rank you by itself; it tells you what Google sees, which is why registering on launch day matters.

Is there a free Google search API?

Not in the sense most people mean. The Search Console API returns data about your own site: queries, clicks, sitemaps and index status. It does not return Google search results for arbitrary queries.

How do I read the Search Console data once the site is in?

Through the Search Console API’s search analytics methods, or by connecting Search Console to an AI assistant. I wrote up both routes in how I connected Google Search Console to Claude. For my own sites I use Murkuz, the Search Console connector I built for Claude, because it needs no Google Cloud project on the reading side. The launch pipeline above is the one place I still want my own OAuth client, since it is the only way to get the verification scope.

Where this comes from

I’m Junaid Khalid. I build and run a portfolio of products, including Contextli, LigoSocial and Murkuz, and their back office runs on more than 30 scheduled Claude Code jobs, so a launch that needs an afternoon of dashboard clicking is a launch that does not happen. Every command above comes from the scripts that put dictationappformac.com, safetoquit.com, earlydaysclub.com, newsletters.ltd and taskmanagermac.com live on 29 and 30 September 2026. The four sites that went live without Search Console, the wrong Bing CNAMEs and the 403 to a Python user agent are my mistakes, not hypotheticals, and the checklist exists because of them. If you want the Claude Code side of the setup, the rules file and the schedules I run, start with how I use Claude Code to run a business, and the free skills I publish are at locul.ai/skills.