← Back to Documentation

API Reference

Complete reference for the SEOgent REST API. All endpoints return JSON and use Bearer token authentication.

Base URL: https://seogent.ai/api


Authentication

Every request must include your API token in the Authorization header:

Authorization: Bearer sk_your_api_key_here

Create tokens at Settings > API Tokens in the SEOgent dashboard. See the Getting Started guide for a walkthrough.

Requests without a valid token receive a 401 Unauthorized response.


Rate Limiting

All authenticated endpoints are rate-limited. The scan creation endpoint (POST /scans) has a stricter limit than read endpoints. Rate-limited requests receive a 429 Too Many Requests response with a Retry-After header.


Endpoints

Create a Scan

POST /api/scans

Start a new SEO scan. Returns immediately with a scan_id — the scan runs asynchronously.

Request Body:

Field Type Required Description
domain string Required without urls Domain to crawl (e.g. example.com or https://example.com)
urls string[] Required without domain Specific URLs to scan (max 1,000)
crawl_mode string No discover (default for domain) or manual (default for urls)
max_pages integer Required for domain scans Max pages to crawl (1–10,000)
performance_scan boolean No Include Core Web Vitals (LCP, CLS, INP, etc.)
accessibility_scan boolean No Run WCAG 2.1 AA accessibility audit
link_check boolean No Check for dead links and broken images
webhook_url string No URL to POST full results when scan completes

You must provide either domain or urls (not both). When providing domain, max_pages is required.

Example — scan a domain:

curl -X POST https://seogent.ai/api/scans \
  -H "Authorization: Bearer sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "domain": "example.com",
    "max_pages": 100,
    "performance_scan": true,
    "link_check": true
  }'

Example — scan specific URLs:

curl -X POST https://seogent.ai/api/scans \
  -H "Authorization: Bearer sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "urls": [
      "https://example.com/",
      "https://example.com/about",
      "https://example.com/pricing"
    ]
  }'

Response 201 Created:

{
  "scan_id": "9e1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
  "status": "pending",
  "domain": "example.com",
  "crawl_mode": "Discover",
  "total_urls": 0,
  "estimated_credits": 500,
  "webhook_url": null,
  "message": "Scan queued successfully"
}

Validation errors 422:

{
  "message": "Either a domain or a list of URLs is required.",
  "errors": {
    "domain": ["Either a domain or a list of URLs is required."]
  }
}

Additional validation checks:


Get Scan Status

GET /api/scans/{scan_id}

Check the progress of a scan.

Example:

curl https://seogent.ai/api/scans/9e1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c \
  -H "Authorization: Bearer sk_your_api_key_here"

Response 200 OK:

{
  "scan_id": "9e1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
  "domain": "example.com",
  "url": "https://example.com",
  "source": "api",
  "status": "crawling",
  "crawl_mode": "Discover",
  "progress": {
    "total_urls": 45,
    "urls_crawled": 23,
    "urls_analyzed": 15,
    "percentage": 51
  },
  "timestamps": {
    "created_at": "2026-03-12T12:00:00+00:00",
    "started_at": "2026-03-12T12:00:02+00:00",
    "crawling_completed_at": null,
    "completed_at": null
  }
}

Status values: pendingcrawlinganalyzingcompleted | failed


Get Scan Results

GET /api/scans/{scan_id}/results

Retrieve full results for a completed scan. Results are cursor-paginated.

Query Parameters:

Parameter Type Default Description
issues_only boolean false Only return pages that have failed checks
min_severity string Filter by score threshold: critical (<50), high (<70), medium (<90)
per_page integer 100 Results per page (max 200)
cursor string Cursor for the next page of results

Example:

curl "https://seogent.ai/api/scans/9e1a2b3c.../results?issues_only=true&per_page=50" \
  -H "Authorization: Bearer sk_your_api_key_here"

Response 200 OK:

{
  "scan_id": "9e1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
  "domain": "example.com",
  "status": "completed",
  "average_score": 74,
  "summary": {
    "excellent": 2,
    "good": 12,
    "needs_work": 15,
    "poor": 3
  },
  "performance_summary": { "..." : "included if performance_scan was enabled" },
  "accessibility_summary": { "..." : "included if accessibility_scan was enabled" },
  "site_checks": {
    "checks": [
      {
        "key": "robots_txt",
        "name": "robots.txt",
        "status": "passed",
        "message": "robots.txt is accessible and valid",
        "category": "crawlability"
      }
    ],
    "duplicate_titles": {
      "count": 2,
      "found": true,
      "duplicates": [
        {
          "title": "Example Site",
          "pages": [
            "https://example.com/about",
            "https://example.com/contact"
          ]
        }
      ]
    },
    "duplicate_descriptions": {
      "count": 0,
      "found": false,
      "duplicates": []
    }
  },
  "top_issues": [
    { "issue": "Missing canonical tag", "count": 28 },
    { "issue": "Missing Open Graph tags", "count": 30 }
  ],
  "results": {
    "data": [
      {
        "url": "https://example.com/",
        "score": 82,
        "grade": "B",
        "checks": 23,
        "failed_checks": [
          "No structured data found",
          "Missing Open Graph tags"
        ],
        "warnings": [
          "Title could be longer (28 characters, recommend 30-60)"
        ],
        "all_checks": [
          {
            "name": "Canonical Tag",
            "key": "canonical",
            "status": "failed",
            "message": "No canonical tag found. Add a self-referencing canonical.",
            "category": "indexability",
            "weight": 8
          }
        ],
        "metadata": {
          "title": "Example Site",
          "description": "An example website",
          "canonical": null
        },
        "performance": {
          "score": 85,
          "lcp": 1.8,
          "fcp": 0.9,
          "cls": 0.05,
          "fid": 12,
          "inp": 95,
          "ttfb": 0.3,
          "tbt": 120
        },
        "accessibility": {
          "score": 72,
          "violations": [
            {
              "id": "color-contrast",
              "impact": "serious",
              "help": "Elements must have sufficient color contrast",
              "help_url": "https://dequeuniversity.com/rules/axe/4.4/color-contrast",
              "wcag_tags": ["wcag2aa", "wcag143"],
              "nodes": [
                {
                  "target": [".hero-text"],
                  "html": "<p class=\"hero-text\">Welcome</p>",
                  "failure_summary": "Element has insufficient color contrast ratio of 3.2:1"
                }
              ]
            }
          ]
        },
        "analyzed_at": "2026-03-12T12:05:00+00:00"
      }
    ],
    "next_cursor": "eyJzY29yZSI6ODIsIl9wb2ludHNUb05leHRJdGVtcyI6dHJ1ZX0",
    "prev_cursor": null,
    "per_page": 100
  }
}

The performance and accessibility fields are null for pages where those scans were not enabled or not yet completed.

Pagination: Use next_cursor to fetch the next page:

curl "https://seogent.ai/api/scans/9e1a2b3c.../results?cursor=eyJzY29yZSI6..." \
  -H "Authorization: Bearer sk_your_api_key_here"

List Scans

GET /api/scans

List your scans, newest first. Uses page-based pagination.

Query Parameters:

Parameter Type Default Description
page integer 1 Page number

Example:

curl https://seogent.ai/api/scans \
  -H "Authorization: Bearer sk_your_api_key_here"

Response 200 OK:

{
  "data": [
    {
      "scan_id": "9e1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
      "domain": "example.com",
      "source": "api",
      "status": "completed",
      "crawl_mode": "Discover",
      "progress": {
        "total_urls": 32,
        "urls_analyzed": 32,
        "percentage": 100
      },
      "average_score": 74,
      "created_at": "2026-03-12T12:00:00+00:00",
      "completed_at": "2026-03-12T12:10:00+00:00"
    }
  ],
  "meta": {
    "current_page": 1,
    "last_page": 3,
    "per_page": 20,
    "total": 47
  }
}

Cancel a Scan

POST /api/scans/{scan_id}/cancel

Cancel a scan that is still in progress (pending, crawling, or analyzing).

Example:

curl -X POST https://seogent.ai/api/scans/9e1a2b3c.../cancel \
  -H "Authorization: Bearer sk_your_api_key_here"

Response 200 OK:

{
  "message": "Scan cancelled successfully."
}

Error — scan already finished 422:

{
  "error": "This scan has already completed or failed.",
  "error_code": "scan_already_terminal"
}

List Domains

GET /api/domains

List all domains you have scanned.

Example:

curl https://seogent.ai/api/domains \
  -H "Authorization: Bearer sk_your_api_key_here"

Response 200 OK:

{
  "data": [
    {
      "id": 1,
      "domain": "example.com",
      "total_scans": 5,
      "last_scanned_at": "2026-03-12T12:10:00+00:00"
    }
  ]
}

Get Credit Balance

GET /api/credits

Check your remaining credits and auto-renew status.

Example:

curl https://seogent.ai/api/credits \
  -H "Authorization: Bearer sk_your_api_key_here"

Response 200 OK:

{
  "balance": 1500,
  "auto_renew": true
}

Response Objects

Check Object

Found in results.data[].all_checks[] and site_checks.checks[].

Field Type Description
name string Human-readable check name
key string Machine identifier (e.g. canonical, meta_title)
status string passed, failed, or warning
message string Description of the finding
category string meta, indexability, crawlability, performance, content
weight number Scoring impact (page-level checks only)

Score Grades

Grade Score Range
A 90–100 (excellent)
B 70–89 (good)
C 50–69 (needs work)
D / F 0–49 (poor)

Error Responses

All errors return a JSON object with an errors field for validation failures.

Status Error Code Description
401 Missing or invalid API token
404 scan_not_found Scan does not exist or does not belong to you
422 Validation error (see errors object for details)
422 scan_already_terminal Attempted to cancel a completed or failed scan
429 Rate limited — check Retry-After header

Webhooks

When you provide a webhook_url on scan creation, SEOgent sends a POST request to that URL when the scan completes. The payload is the same JSON structure as the Get Scan Results endpoint.


Typical Workflows

Poll until complete, then fetch results

# 1. Start a scan
SCAN_ID=$(curl -s -X POST https://seogent.ai/api/scans \
  -H "Authorization: Bearer $SEOGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"domain":"example.com","max_pages":100}' | jq -r '.scan_id')

# 2. Poll status
curl -s https://seogent.ai/api/scans/$SCAN_ID \
  -H "Authorization: Bearer $SEOGENT_API_KEY" | jq '.status'

# 3. Fetch results when completed
curl -s https://seogent.ai/api/scans/$SCAN_ID/results \
  -H "Authorization: Bearer $SEOGENT_API_KEY" | jq '.top_issues'

CI/CD quality gate

SCAN_ID=$(curl -s -X POST https://seogent.ai/api/scans \
  -H "Authorization: Bearer $SEOGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"domain":"example.com","max_pages":50}' | jq -r '.scan_id')

while true; do
  sleep 15
  STATUS=$(curl -s https://seogent.ai/api/scans/$SCAN_ID \
    -H "Authorization: Bearer $SEOGENT_API_KEY" | jq -r '.status')
  [ "$STATUS" = "completed" ] && break
  [ "$STATUS" = "failed" ] && echo "Scan failed" && exit 1
done

SCORE=$(curl -s https://seogent.ai/api/scans/$SCAN_ID/results \
  -H "Authorization: Bearer $SEOGENT_API_KEY" | jq '.average_score')

[ "$SCORE" -lt 70 ] && echo "SEO score $SCORE is below threshold" && exit 1

Fire-and-forget with webhook

curl -X POST https://seogent.ai/api/scans \
  -H "Authorization: Bearer $SEOGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "domain": "example.com",
    "max_pages": 100,
    "webhook_url": "https://your-server.com/seo-callback"
  }'