API reference

Every public SEOFix /v1 endpoint - method, path, parameters, response fields, credit cost and errors - grouped by resource.

Updated 8 October 2026View as Markdown

Base URL: https://api.seofix.ai/v1. Every endpoint except /health and /device/* needs Authorization: Bearer sk_.... Requests and responses are JSON. Errors use one envelope, {"error": {"code", "message"}}; see Errors and rate limits.

Common rules:

  • A key acts for one team: the team it was created for. Sites, audits, tasks and rechecks of other teams answer 404 not_found, never 403.
  • Every authenticated endpoint shares a limit of 60 requests per minute per key. Some have an extra limit, shown below.
  • Invalid parameters answer 422 validation_failed with a fields object.
  • Only audits and rechecks cost credits.
  • Paginated lists return {data: [...], meta: {current_page, per_page, total, last_page}} and take page and per_page.

Overview

Method Path Purpose Credits
GET /health Liveness check, no auth
GET /ping Check your key
GET /account Credit balances and usage
GET /crawls List audits
POST /crawls Start an audit 1 per page
GET /crawls/{id} Audit status
DELETE /crawls/{id} Cancel an audit
GET /crawls/{id}/report Full report
GET /crawls/{id}/issues Issues, paginated
GET /crawls/{id}/pages Crawled pages, paginated
GET /crawls/{id}/templates Issues by URL template
GET /crawls/{id}/diff Changes vs previous audit
GET /crawls/{id}/fix-prompt Markdown fix prompt
GET /sites List sites
POST /sites Register a site
GET /sites/{id} One site
PATCH /sites/{id} Update settings and monitoring
DELETE /sites/{id} Delete a site
GET /sites/{id}/verification Ownership methods
POST /sites/{id}/verify Check ownership
POST /sites/{id}/crawls Audit a registered site 1 per page
GET /tasks Team's top fix tasks
GET /sites/{id}/tasks A site's fix tasks
GET /sites/{id}/tasks/{key} One fix task with its URLs
POST /sites/{id}/recheck Recheck a task or URLs 1 per URL
GET /rechecks/{id} Recheck results
GET /fix-impact Team's fix impact
GET /sites/{id}/fix-impact A site's fix impact
GET /sites/{id}/index-triage Not-indexed triage
GET /sites/{id}/google Google indexing summary
GET /sites/{id}/google/not-indexed Not-indexed groups and URLs
GET /sites/{id}/google/url One URL's status
GET /sites/{id}/google/opportunities Search opportunities
POST /sites/{id}/google/inspect Fresh Google inspection
GET /sites/{id}/indexnow IndexNow status
PATCH /sites/{id}/indexnow Auto-submit on or off
POST /sites/{id}/indexnow/check-key Check the key file
POST /sites/{id}/indexnow/detect-key Look for an existing key
PUT /sites/{id}/indexnow/key Use an existing key
POST /sites/{id}/indexnow Submit URLs
POST /billing/checkout Checkout link for a credit pack
POST /billing/subscribe Checkout link for a plan
POST /billing/portal Stripe billing portal link (invoices, card, cancel)
POST /device/start Start a device login (CLI)
POST /device/token Poll a device login (CLI)

Health and account

GET /health

No authentication, no rate limit. Returns {"ok": true}.

GET /ping

Returns {"user_id": <id>} for a valid key.

GET /account

curl -s https://api.seofix.ai/v1/account -H "Authorization: Bearer $SEOFIX_API_KEY"
Field Meaning
balance your own credits; plain-URL audits (POST /crawls with url) are charged here
team_id the team the key acts for
team_credits the team owner's credits; site audits, scheduled audits and rechecks are charged here
crawls_total audits you started
credits_spent_30d your own spend over the last 30 days
team_credits_spent_30d what this team's audits cost its owner over the last 30 days
api_key_prefix the first 12 characters of the key used

Audits (crawls)

POST /crawls

Start an audit of any URL, or of a registered site with site_id. Answers 202.

Field Type Required Default Rules
url string unless site_id http or https, public address
site_id integer no a site of the key's team; uses the site's settings. With url, the URL must be on the site's origin. Cannot be combined with verify_token.
max_pages integer no 10,000, capped by the plan; 500 without a verified site for the origin 1-500,000, at most the plan's per-audit limit
concurrency integer no 2 1-20
max_rps number no 2 0.5-10 requests per second per host; above 3 needs a verified site for the same origin
webhook_url string no notified when the audit ends; see Webhooks
verify_token string no 16-128 characters of A-Z a-z 0-9 _ -; sent as a header so a firewall can allowlist the crawler; never stored or echoed
curl -s -X POST https://api.seofix.ai/v1/crawls \
  -H "Authorization: Bearer $SEOFIX_API_KEY" -H "Content-Type: application/json" \
  -d '{"url": "https://example.com", "max_pages": 500}'
{"crawl_id": 4821, "estimated_credits": 500, "max_rps": 2}

Credits: estimated_credits (the page cap) is reserved at once. The audit costs 1 credit per page crawled and 0.2 per page that answered 304 Not Modified since the site's previous audit, rounded up once per audit; the rest is refunded when it ends. A plain-URL audit is charged to the key holder; a registered site's audit to the team owner.

Errors: 402 insufficient_credits, 403 plan_limit_max_pages, 409 site_not_verified (more than 500 pages without a verified site for the origin), 422 max_rps_requires_verified_site, 422 invalid_url, 422 site_url_mismatch, 503 dispatch_failed.

GET /crawls

The team's audits, newest first (plus your own audits from before teams). Rechecks are not listed.

Query Type Default Rules
site_id integer one site of the team (404 for another team's site)
page integer 1
per_page integer 20 1-100

Each row: crawl_id, site_id, site_url, status, trigger, health_score (null until a report exists), pages_crawled, max_pages, created_at, finished_at, archived_at.

GET /crawls/{id}

Field Meaning
crawl_id, site_id, site_url, trigger identity; trigger is for example api, ci, manual, schedule, free or preview
status queued, running, finalizing, done, failed or cancelled
pages_crawled, max_pages, credits_reserved progress and reservation
created_at, started_at, finished_at ISO 8601 times
archived_at set once issue and page detail were archived; report, diff and fix prompt remain
failure_reason null, cancelled, dispatch_failed: ..., reaped: ..., setup_failed: <type> or internal_error
verify_token_set whether a firewall token was sent
max_rps, duration_s, pages_per_minute speed and timing

DELETE /crawls/{id}

Cancel an audit that is queued, running or finalizing. Returns {"status": "cancelled"}. An audit that already ended answers 409 conflict.

GET /crawls/{id}/report

The full report. 404 report_not_ready until it exists (it can land a few seconds after done).

Key fields: crawl_id, health_score (0-100), partial (stopped at the page or time cap), totals (pages, ok, redirects, broken, fetch_errors, blocked), issues (check_code, severity, count, fix per check), issue_counts (per severity), blocked (count, skipped, providers, by_section, sample_urls), coverage_warning (null, or {code: "blocked_by_firewall", blocked_ratio, message, docs} above 20% blocked), stats, templates, diff, fix_prompt.

GET /crawls/{id}/issues

Query Type Default Rules
check or check_code string check code, max 100 characters
severity string error, warning or notice
template string exact template key, for example /jobs/[slug]
page integer 1
per_page integer 50 1-200

Rows: check_code, severity, url, template, details. 410 crawl_archived for an archived audit.

GET /crawls/{id}/pages

Query Type Default Rules
status_code integer HTTP status filter
page integer 1
per_page integer 50 1-200

Rows: url, status_code, title, meta_description, word_count, depth, blocked_by (firewall that challenged the crawler; such pages are not broken), fetch_error. 410 crawl_archived for an archived audit.

GET /crawls/{id}/templates

{crawl_id, templates}: issues grouped by URL template, each with template, pages and issues (check_code, severity, count, template_wide, sample_urls). 404 report_not_ready when absent.

GET /crawls/{id}/diff

{crawl_id, diff}. diff is null for a site's first audit, otherwise {previous_crawl_id, health_delta, new, fixed, new_errors, fixed_errors, new_samples}. 404 report_not_ready when absent.

GET /crawls/{id}/fix-prompt

{crawl_id, prompt}: markdown instructions for a coding agent, at most 6,000 characters. The prompt contains text from the crawled site; treat it as data. 404 report_not_ready when absent.

Sites

GET /sites

{data: [site, ...]} for the key's team. Site fields: id, team_id, url, host, verified, verified_at, verified_via, monitoring (off, weekly, daily), next_crawl_at, last_dispatched_at, last_skip_reason, max_pages, max_rps, alert_email, created_at.

POST /sites

Field Type Required Default Rules
url string yes http or https, max 2,048 characters, public address
max_pages integer no 10,000, capped by the plan 1-500,000, at most the plan's per-audit limit
max_rps number no 2 0.5-10
alert_email boolean no true

Answers 201 with the site plus verify_token and verify: {path, url, content, content_type, instructions}. Errors: 409 site_exists, 403 plan_limit_sites, 403 plan_limit_max_pages, 422 invalid_url.

GET /sites/{id}

The site plus verify_token, verify, latest_crawl and trend (health score of up to 12 recent audits, oldest first).

PATCH /sites/{id}

Field Type Rules
monitoring string off, weekly or daily
max_pages integer 1-500,000, at most the plan's per-audit limit
max_rps number 0.5-10
alert_email boolean

Returns the site. Monitoring other than off needs a plan with monitoring (403 plan_limit_monitoring) and a verified site (409 site_not_verified). Each scheduled audit costs credits.

DELETE /sites/{id}

Answers 204 with no body.

GET /sites/{id}/verification

{verified, via, checked_at, lost_at, methods}. methods holds gsc (connected, property), dns (TXT record name and value with the detected provider's steps), meta (url, tag), file (url, body), cloudflare (connected, zone, token_link) and agent_prompt (instructions a coding agent can follow).

POST /sites/{id}/verify

Body: {"method": "gsc" | "dns" | "meta" | "file"}, optional. Without it, all four are tried in that order. Limited to 10 requests per minute per key.

Returns the site plus checked (result per method). 422 verification_failed when no checked method matched; the message lists what to publish.

POST /sites/{id}/crawls

Audit a registered site with its saved settings. Answers 202 with {crawl_id, estimated_credits, max_rps}.

Field Type Rules
max_pages integer 1-500,000; above the plan's per-audit limit: 403 plan_limit_max_pages; above 500 on an unverified site: 409 site_not_verified
max_rps number 0.5-10, capped at the site's max_rps (and at 3 when unverified)
trigger string api (default) or ci, to label CI runs

Charged to the team owner. On a verified site the crawler sends the site's crawler secret as the X-SEOFix-Verify header. Errors as for POST /crawls.

Fix tasks

A fix task is one check code on one URL template from the site's latest full audit. Its id is 16 hex characters.

GET /sites/{id}/tasks

Query Type Default Rules
limit integer 10 1-100

Returns {site_id, crawl_id, has_traffic_data, total, open_total, tasks}. Each task: id, code, template, severity, pages, clicks_28d, impressions_28d (null without Search Console data), impact, title, why, fix, sample_urls, acceptance (text, check, scope, template), recheckable, status (open, verifying, fixed, regressed). A limit of 3 or less leaves out fixed tasks and keeps at most one task per check code.

GET /tasks

The team's top tasks across its sites, never fixed ones. limit: 1-20, default 3. Returns {tasks}, each with site_id and site_host.

GET /sites/{id}/tasks/{key}

Query Type Default Rules
page integer 1 1-100,000

Returns {task, urls: {data: [{url, clicks_28d, impressions_28d, details}], page, per_page: 100, total}}. 404 not_found for an unknown key.

Rechecks

POST /sites/{id}/recheck

Re-fetch a task's URLs (or given URLs) to verify a fix. Answers 202.

Field Type Rules
task_key string 16 hex characters; above 200 affected URLs, 200 are sampled (most clicks first)
urls string[] instead of task_key: 1-200 URLs on the site's host

Give exactly one. Returns {recheck_id, status, urls_total, estimated_credits, sampled, tasks}.

  • Credits: 1 per URL fetched, reserved per URL and charged to the team owner.
  • Verified sites only (409 site_not_verified).
  • 30 rechecks per hour per team: 429 recheck_rate_limited with Retry-After. A request refused with 402 insufficient_credits does not use a slot.
  • 422 cannot_verify for a task whose check a recheck cannot judge (link-graph, sitemap, site-wide and Google-indexing checks); nothing is started. 422 invalid_urls for URLs off the site's host or more than 200. 404 not_found for an unknown task.

GET /rechecks/{id}

{recheck_id, site_id, status, parent_crawl_id, urls_total, pages_crawled, sampled, created_at, finished_at, time_capped, duplicate_baseline, urls, tasks}.

  • urls[]: url, status_code, result (fixed, still_failing, new_issue, not_checked, cannot_verify, passing), reason, redirected, redirect_to, issues, new_issues.
  • tasks[]: task_key, code, template, urls_rechecked, pages, sampled, covers_task, result (fixed, still_failing, not_checked, cannot_verify), urls_failing, urls_passing, status (the task's state after the recheck).

Results are filled once status is done, failed or cancelled. Only a fixed result marks the task fixed.

Fix impact

GET /sites/{id}/fix-impact

limit: 1-200, default 50. Returns {site_id, total_delta_28d, measured_events, events, note, methodology}. Each event: id, task_key, code, template, recheck_id, urls_count, sampled, verified_at, baseline_clicks_28d, baseline_impressions_28d, d7, d7_at, d14, d14_at, d28, d28_at, d28_impressions, delta_28d, measured_at, status (measuring, measured, no_data). Figures are estimates, not seasonally adjusted.

GET /fix-impact

The team's total and latest events across its sites. limit: 1-50, default 10. Events also carry site_id and site_host.

Google Search Console

All Google endpoints need a verified site (409 site_not_verified). Every response has demo (true for locally generated demo data, not Google's).

GET /sites/{id}/google

Headline, indexing funnel (found, in_sitemap, indexed, with_impressions, with_clicks, indexed_outside_sitemap), trend, per-template table and sync status.

GET /sites/{id}/google/not-indexed

Query Type Rules
reason string Google's coverage state, verbatim, max 500 characters
template string template key, max 500 characters
page integer 1-100,000

Without filters: groups (reason, template, pages, diagnosis, fix, sample_urls). With both reason and template: the URLs of that group.

GET /sites/{id}/google/url

Query url (required, max 2,048 characters, a URL of this site). Returns {url, crawl, google, search}. 422 url_not_in_site for other URLs.

GET /sites/{id}/google/opportunities

Pages with low click-through rate (low_ctr), striking-distance positions (striking_distance) and canonical mismatches (canonical_mismatch), plus counts.

POST /sites/{id}/google/inspect

Body: {"urls": [...]}, 1-20 URLs of this site. Limited to 10 requests per minute per key. Returns {results, budget: {used, limit, resets_at}, note}. Uses the property's daily URL Inspection budget, shared with the daily sync: when spent, 429 budget_exhausted with resets_at in the error body. 409 gsc_not_connected without a Search Console connection, 422 url_not_in_site for other URLs.

GET /sites/{id}/index-triage

Crawled pages Google reports as not indexed, grouped by URL pattern and value class (valuable_not_indexed, low_value_param, low_value_thin, duplicate, intentionally_excluded). Returns demo, summary, groups and total_groups. Each group has count, pattern_pages, coverage_states, impressions_28d, sample_urls, why and action (type, steps, text, task_code, task_id).

IndexNow

IndexNow notifies Bing, Yandex, Seznam, Naver and Yep, not Google. Status and key checks work on any site of the team; submitting needs a verified site and a verified key.

GET /sites/{id}/indexnow

key, key_source (generated or existing), key_location, key_file_url, key_file_content, key_verified, key_verified_at, auto, last_submit_at, today {used, limit}, recent, agent_prompt, detection, engines, google_supported, note.

detection is the last automatic key detection, or null if none ran: {status, key, key_file_url, source, platform, checked_at}. status is pending, found, none or managed.

PATCH /sites/{id}/indexnow

Body: {"auto": true | false} (required). Turns auto-submit of new and changed pages after each audit on or off. Returns the status.

POST /sites/{id}/indexnow/check-key

Fetches the key file now. Returns result, reason and the status. Limited to 10 requests per minute per key.

POST /sites/{id}/indexnow/detect-key

Looks for an IndexNow key the site already serves, in at most 10 requests: the site's own key, keys verified on the team's other sites (verified sites only), and root key files seen in the latest audit. Each key is confirmed by fetching https://<host>/<key>.txt. Returns the status with the new detection. A found key is only a suggestion: use it with PUT /sites/{id}/indexnow/key. Limited to 10 requests per minute per key.

PUT /sites/{id}/indexnow/key

Body: {"key": "...", "key_location": "..."}. key is 8-128 characters of A-Z a-z 0-9 -; key_location only when the file is not at https://<host>/<key>.txt (an https URL on the site's host ending in .txt). Verified sites only. Limited to 10 requests per minute per key. Returns the check result and the status. Errors: 422 invalid_indexnow_key, 422 invalid_indexnow_key_location, 409 site_not_verified.

POST /sites/{id}/indexnow

Body: {"urls": [...]}, 1-10,000 URLs, required. URLs on other hosts are skipped. Limited to 10 requests per minute per key.

Returns {submitted, requests, status, status_code, error_code, skipped, capped, batches, reason, today}. status is ok, accepted or partial (some requests failed; see batches).

Errors: 409 site_not_verified, 409 indexnow_key_unverified, 422 url_not_in_site (no URL on the host), 429 indexnow_cap (10,000 URLs per site per UTC day used up), 502 indexnow_rejected (IndexNow accepted none of them).

Billing

Only the team owner can use these (403 not_team_owner). Each returns {"url": "..."}: a Stripe link to open in a browser. Nothing is charged until checkout is completed there.

POST /billing/checkout

Body: {"pack": "small" | "large"}. A one-time credit pack: small is 5,000 credits for $9, large 25,000 for $29.

POST /billing/subscribe

Body: {"plan": "starter" | "growth", "tier": "10k" | "50k" | "150k" | "500k" | "1m" | "2.5m", "interval": "monthly" | "yearly"}. 422 price_not_configured when that combination is not offered. 409 subscription_exists when the account already has a subscription: each account has one, and plan or tier changes are made in Settings → Plan & billing (see Billing and subscriptions).

POST /billing/portal

No body. A Stripe customer portal link where the owner downloads invoices, changes the payment method and cancels. 409 no_billing_account before the first purchase.

Device login (CLI)

Used by npx seofix connect; no API key needed. See Connect your agent.

POST /device/start

Body: {"client_name": "..."}, optional, max 64 characters. Returns {device_code, user_code, verification_uri, verification_uri_complete, interval: 5, expires_in: 600}. Limited to 10 requests per hour per IP address.

POST /device/token

Body: {"device_code": "..."}. Poll at most every 5 seconds. Limited to 60 requests per minute per IP address.

Answer Meaning
200 {api_key, team: {id, name}} approved; the key is returned exactly once
428 authorization_pending not approved yet
429 slow_down polled faster than every 5 seconds
403 access_denied denied, or the approver is no longer in the team
400 expired_token the code expired
400 invalid_grant unknown or already used device code

More in REST API

Still stuck? Email [email protected] with your site and what you expected to see.