One Call to Run Your Whole SEO Operation: the check API
POST /api/v1/check kicks off indexing, verification, AEO tracking, and deep investigation in one shot — here's the exact shape.
Most of yeet-seo's surface — the MCP tools, the dashboard, the scheduled agent — collapses into one primitive: the check. A check is a single call that fans out into everything the platform does for a workspace: indexing eligibility, fix verification, AEO citation and prompt-rank tracking, and (when warranted) a deep investigation pass. This is the endpoint to reach for if you're wiring yeet into a CI pipeline or an agent loop — see agentic SEO workflows for the scheduling patterns built on top of it. If you haven't connected an MCP client yet, start with the MCP setup guide; the MCP equivalent of this endpoint is the run_check / get_check tool pair.
The reason there's one primitive instead of separate "run the verifier" / "check indexing" / "track AEO prompts" endpoints is deliberate: those signals aren't independent. A page that just got reindexed changes what the verifier should expect from a pending card; a citation change on a tracked prompt can be the very evidence that promotes a card to verified. Fanning all of it out from one call means the report you get back is internally consistent as of one timestamp, rather than four separately-timed snapshots you'd have to reconcile yourself.
Kicking off a check
POST /api/v1/check
Authorization: Bearer yseo_your_key_here
Idempotency-Key: 2026-07-11-nightly-check
Content-Type: application/json
{}
This requires a write-scoped key. The response is 202 Accepted:
{
"check_id": "1f4c7c1e-9d2a-4c1b-8f4e-2a7b9c3d5e6f",
"status": "running",
"since": "2026-07-04T02:00:00Z",
"poll": "/api/v1/check/1f4c7c1e-9d2a-4c1b-8f4e-2a7b9c3d5e6f"
}
since marks the last completed check this one is diffing against — that's what makes verified_since_last in the final report meaningful rather than a re-listing of everything ever found.
Idempotency
Pass an Idempotency-Key header on any mutating call — run_check included. The server caches the response for 24 hours keyed on that value, so a retried request (a flaky network, a CI job re-triggered, a webhook redelivery) returns the original check_id instead of starting a duplicate run. Use a deterministic key — a date plus a job name, like above — rather than a random UUID, or you lose the dedupe benefit entirely.
Polling to completion
GET /api/v1/check/1f4c7c1e-9d2a-4c1b-8f4e-2a7b9c3d5e6f
Authorization: Bearer yseo_your_key_here
Poll until status is complete or partial. A check has a hard 20-minute deadline — if the fan-out (see below) hasn't finished by then, the endpoint returns whatever finished as partial rather than blocking indefinitely. Treat partial as usable but incomplete: act on what's there, and expect the next scheduled check to pick up the rest.
The full report shape
{
"check_id": "1f4c7c1e-9d2a-4c1b-8f4e-2a7b9c3d5e6f",
"status": "complete",
"since": "2026-07-04T02:00:00Z",
"found": {
"cards": [
{
"id": "card_44a1",
"subcategory": "meta_tags",
"confidence": 0.91,
"predicted_delta": "+0.6 CTR pts",
"url": "/playbook/programmatic-seo"
}
],
"candidates_open": 7
},
"did": {
"submissions": [
{ "url": "/playbook/programmatic-seo", "status": "submitted" }
],
"prs_opened": [
{ "pr_url": "https://github.com/acme/site/pull/214", "card_id": "card_44a1" }
]
},
"awaiting_approval": {
"hypotheses": [
{ "id": "hyp_7712", "summary": "Cannibalization between /pricing and /pricing-enterprise" }
],
"prs_awaiting_merge": [
{ "pr_url": "https://github.com/acme/site/pull/213", "card_id": "card_441f" }
]
},
"verified_since_last": [
{ "card_id": "card_402c", "outcome_state": "verified", "actual_delta": "+0.8 CTR pts" }
]
}
found is what's new since since. did is what the platform took action on autonomously, gated by whatever autonomy mode the workspace is in. awaiting_approval is the human (or agent) queue — hypotheses that need approve_hypothesis/dismiss_hypothesis, and PRs sitting above the auto-merge confidence floor but not yet merged (see the auto-apply deep dive for exactly why a given PR lands here instead of merging itself). verified_since_last is where prior fixes get their outcome confirmed or walked back — see how yeet verifies fixes and the outcome priors guide for how that feeds back into future confidence scores.
What actually fans out
Under one check call:
- Indexing eligibility — tracked URLs re-evaluated against Search Console and the Indexing API, resubmitted if stale or newly eligible
- Verifier pass — a live re-scrape plus GSC/AI-citation corroboration against every card still in a pending outcome state, promoting cards to
verifiedat ≥50% of predicted delta or flaggingregressed - AEO citation and prompt checks — tracked prompts re-run against AI answer engines, citation state updated
- Deep investigation — when the diff since the last check crosses a threshold, the investigator agent runs a full pass and emits new cards/hypotheses rather than just surface signals
Not every check triggers every stage at full depth. Indexing eligibility and the verifier pass run on effectively every check since they're cheap relative to a full investigation; the investigator only runs a deep pass when warranted, which is why found.cards can legitimately be empty on a check that still returns complete — nothing new was worth surfacing, not that the check failed to look.
run_check vs. the narrower tools
If you already know exactly what you want — submit five specific URLs, or pull current AI citation state for one prompt — it's cheaper and faster to call the targeted tool directly (submit_urls, get_ai_citations) rather than a full check. Reach for run_check when you want the platform's own judgment about what's changed and what needs attention, not when you already know the answer. In an agent context this usually means: targeted tools for actions you're deciding, run_check for the periodic "what happened since I last looked" sweep.
Handling 402s
If the workspace is out of credits for the action a check would trigger, you get:
HTTP/1.1 402 Payment Required
{
"error": "insufficient_credits"
}
Treat this as a hard stop signal, not a retry-with-backoff case — retrying won't produce credits. In an agent loop, catch 402 specifically and either halt the automation with a clear message, or route to a human/billing action. See agentic SEO workflows for budget-aware patterns that avoid hitting this mid-run.
Webhooks instead of polling
If you'd rather not poll, register a webhook for check.started and check.completed via POST /api/v1/webhooks. Deliveries are signed:
X-YeetSeo-Event: check.completed
X-YeetSeo-Webhook-Id: whk_88c1
X-YeetSeo-Signature: sha256=<hmac>
Verify the HMAC against your endpoint's whsec_ secret before trusting the payload. Delivery has a 10-second timeout on your endpoint, so ack fast and process async.
Quick FAQ
Q: Can I run more than one check concurrently on the same workspace?
A: Use distinct Idempotency-Key values only if you genuinely mean distinct runs — reusing a key within 24h returns the cached original rather than starting a second one, which is usually what you want.
Q: What's the difference between partial and complete?
A: complete means every fan-out step finished inside the 20-minute window; partial means the deadline hit first — act on what's populated and let the next check fill gaps.
Q: Does a check cost credits even if nothing is found?
A: Yes — running a check itself is 1 credit regardless of outcome; see the budgets section of the workflows guide for the full action-cost table.