Bulk SEO Audit

Submit up to 10 URLs in a single POST and get back a flat per-URL summary — score, meta presence, viewport, H1, image counts, and a per-row error field for pages that failed to fetch.

This is the batch counterpart to /v1/seo/audit. The same scoring engine runs for every URL, but the response is intentionally trimmed (no full heading hierarchy, no OG/Twitter detail) so it fits in a dashboard table or a CI summary. For a deeper look at any one row, re-run that URL through /v1/seo/audit. For side-by-side metric comparison, see /v1/seo/compare.

Endpoint

POST /v1/seo/bulk-audit

Base URL: https://seo.toolkitapi.io

Request Body

Field Type Required Description
urls string[] Yes 1–10 absolute URLs (http:// or https://) to audit.
{
  "urls": [
    "https://example.com",
    "https://example.com/pricing",
    "https://example.com/blog"
  ]
}

The endpoint enforces min_length=1 and max_length=10. Submitting more than 10 URLs returns a 422 Unprocessable Entity.

Response Fields

Field Type Description
total integer Number of URLs submitted (always equals len(urls)).
results object[] One entry per submitted URL, in input order.

Each results[i] object:

Field Type Description
url string Final URL after redirects (or the input URL if the fetch failed).
score integer (0–100) Overall SEO score from the shared audit engine. 0 if the page failed to load.
meta_title_present boolean Page has a non-empty <title>.
meta_description_present boolean Page has a non-empty <meta name="description">.
has_og_tags boolean At least an og:title was found.
has_h1 boolean Page has one or more <h1> tags.
has_viewport boolean Page has a <meta name="viewport">.
image_count integer Total number of <img> tags found.
images_without_alt integer Images missing or with empty alt text.
error string | null null on success. On failure, a short message (e.g. timeout / DNS / non-2xx).

A failed row is not an HTTP error — it shows up as a normal entry with error populated and zeroed-out fields. You always get exactly total rows back.

Examples

curl

curl -X POST "https://seo.toolkitapi.io/v1/seo/bulk-audit" \
  -H "x-api-key: $TOOLKIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "urls": [
      "https://example.com",
      "https://example.com/pricing",
      "https://example.com/blog"
    ]
  }'

Python

import requests

URLS = [
    "https://example.com",
    "https://example.com/pricing",
    "https://example.com/blog",
]

resp = requests.post(
    "https://seo.toolkitapi.io/v1/seo/bulk-audit",
    json={"urls": URLS},
    headers={"x-api-key": API_KEY},
    timeout=60,
)
data = resp.json()

# Surface anything below threshold or that errored
for row in data["results"]:
    if row["error"]:
        print(f"⚠️  {row['url']}: {row['error']}")
    elif row["score"] < 70:
        print(
            f"  {row['url']}: score={row['score']}, "
            f"images_without_alt={row['images_without_alt']}, "
            f"og={row['has_og_tags']}"
        )

JavaScript

const URLS = [
  "https://example.com",
  "https://example.com/pricing",
  "https://example.com/blog",
];

const resp = await fetch("https://seo.toolkitapi.io/v1/seo/bulk-audit", {
  method: "POST",
  headers: {
    "x-api-key": process.env.TOOLKIT_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ urls: URLS }),
});
const data = await resp.json();

const failing = data.results.filter((r) => r.error || r.score < 70);
console.table(failing);

Example Response

{
  "total": 2,
  "results": [
    {
      "url": "https://example.com/",
      "score": 82,
      "meta_title_present": true,
      "meta_description_present": true,
      "has_og_tags": true,
      "has_h1": true,
      "has_viewport": true,
      "image_count": 5,
      "images_without_alt": 0,
      "error": null
    },
    {
      "url": "https://example.com/missing",
      "score": 0,
      "meta_title_present": false,
      "meta_description_present": false,
      "has_og_tags": false,
      "has_h1": false,
      "has_viewport": false,
      "image_count": 0,
      "images_without_alt": 0,
      "error": "Error fetching URL: 404"
    }
  ]
}

Notes

  • URLs are audited sequentially server-side. A 10-URL batch can take ~10–30 s depending on target latency — set client timeouts accordingly.
  • Each URL goes through the same SSRF guard as /v1/seo/audit; private/loopback/link-local hosts are rejected (the row will land with an error).
  • For partial-success batches, look at the per-row error field; the HTTP status of the call itself stays 200 even if every URL fails.
  • Need richer per-page detail (heading hierarchy, missing-alt srcs, full OG/Twitter)? Re-issue offending URLs against /v1/seo/audit.