Overview
Seonar is a technical SEO analysis API. Send a URL — get back a structured JSON audit covering up to 9 checks: robots.txt, sitemap, meta tags, canonical tags, structured data, security headers, redirect chains, hreflang, and internal links.
The API is HTTP-based, uses Bearer token authentication, and always returns the same predictable JSON shape — easy to parse in any language or pipeline.
Base URL
Authentication
All check requests require a Bearer token in the Authorization header.
Obtain your token from the Dashboard
after registering.
Authorization: Bearer sk_live_••••••••••••••••••••••
Keep your token secret. Never expose it in client-side code or public repositories.
Creating a token
Go to the Dashboard, enter a name for the token, and click Create token. The token is shown once — copy it immediately.
You can create multiple tokens (e.g., one per project) and revoke them individually.
Quick Start
Run your first full audit in under a minute:
Register for a free account and generate an API token in the Dashboard.
Run the request below — replace {token} and the URL.
Parse the JSON response — check summary.overall_score or iterate over results.
curl -X POST https://seonar.io/api/v1/check \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{"url": "https://your-site.com"}'
{
"url": "https://your-site.com",
"checked_at": "2026-02-25T10:00:00+00:00",
"results": {
"robots_txt": { "status": "pass", "score": 100 },
"sitemap": { "status": "pass", "score": 100 },
"meta_tags": { "status": "warn", "score": 70 },
"internal_links": { "status": "fail", "score": 40 }
// ... 5 more checks
},
"summary": {
"total_checks": 9, "passed": 6, "warnings": 2,
"failed": 1, "errors": 0, "overall_score": 82
}
}
POST /api/v1/check
Run a technical SEO audit on a URL. Requires authentication.
Request
| Field | Type | Required | Description |
|---|---|---|---|
| url | string | Yes | The URL to audit. Must start with http:// or https://. |
| checks | string[] | No | Array of check keys to run. Omit to run all 9 checks. See Checks Reference for valid values. |
POST /api/v1/check
Authorization: Bearer {token}
Content-Type: application/json
{
"url": "https://example.com",
"checks": ["robots_txt", "sitemap", "meta_tags"]
}
Response
Returns 200 OK with a JSON body. See Response Format for the full structure.
GET /api/v1/checks
Returns the list of all available check keys and their descriptions. No authentication required.
GET /api/v1/checks
{
"checks": [
{ "key": "robots_txt", "name": "robots.txt" },
{ "key": "sitemap", "name": "Sitemap" },
{ "key": "meta_tags", "name": "Meta Tags" },
{ "key": "canonical", "name": "Canonical" },
{ "key": "structured_data", "name": "Structured Data" },
{ "key": "security_headers", "name": "Security Headers" },
{ "key": "redirect_chain", "name": "Redirect Chain" },
{ "key": "hreflang", "name": "Hreflang" },
{ "key": "internal_links", "name": "Internal Links" }
]
}
Response Format
Every POST /check response follows this structure:
{
"url": "https://example.com", // audited URL
"checked_at": "2026-02-25T10:00:00+00:00", // ISO 8601
"results": { /* keyed by check name */ },
"summary": { /* aggregate stats */ }
}
Check result object
Each key in results contains:
| Field | Type | Description |
|---|---|---|
| status | string | pass warn fail error |
| score | integer | 0–100. Higher is better. pass always returns 100. |
| issues | Issue[] | Array of detected issues. Empty array when status is pass. |
| data | object | Raw extracted data specific to each check. Schema varies — see each check's reference below. |
Issue object
| Field | Type | Description |
|---|---|---|
| severity | string | critical warning info |
| message | string | Short human-readable description of the issue. |
| detail | string|null | Actionable explanation or recommendation. May be null. |
Summary object
| Field | Type | Description |
|---|---|---|
| total_checks | integer | Number of checks that were run. |
| passed | integer | Checks with status pass. |
| warnings | integer | Checks with status warn. |
| failed | integer | Checks with status fail. |
| errors | integer | Checks that could not be completed (connectivity issues, etc.). |
| overall_score | integer | Average score across all executed checks (0–100). |
Rate Limits
Rate limits are enforced per API token.
| Context | Limit |
|---|---|
| POST /api/v1/check | 20 requests / minute per token |
| GET /api/v1/checks | 3 requests / minute per IP (no auth required) |
When a rate limit is exceeded, the API returns 429 Too Many Requests. The response includes standard Laravel throttle headers:
X-RateLimit-Limit: 20
X-RateLimit-Remaining: 0
Retry-After: 42 # seconds until limit resets
Errors
The API uses standard HTTP status codes. Error responses always include a JSON body.
| HTTP Status | Meaning |
|---|---|
| 400 | Validation error — missing or invalid url field. |
| 401 | Missing or invalid Bearer token. |
| 422 | Unprocessable — request body is valid JSON but fails business validation. |
| 429 | Rate limit exceeded. See Rate Limits. |
| 500 | Server error. Contact hello@seonar.io if it persists. |
{
"message": "The url field is required.",
"errors": {
"url": ["The url field is required."]
}
}
A check-level status: "error" in a result object means that specific check could not be completed (e.g., timeout connecting to the target site). The HTTP response is still 200 OK — the error is scoped to that individual check.
Checks Reference
Detailed documentation for each check — what it tests, all data fields, and scoring logic.
robots_txt
Fetches /robots.txt from the target host and checks for correctness, crawler restrictions, and a Sitemap directive.
data fields
| Field | Type | Description |
|---|---|---|
| exists | boolean | Whether robots.txt was found (HTTP 200). |
| url | string | The full URL that was checked. |
| size_bytes | integer | File size in bytes. |
| disallows | string[] | All Disallow: directives found. |
| allows | string[] | All Allow: directives found. |
| sitemaps | string[] | Sitemap URLs declared via Sitemap: directive. |
| blocks_all | boolean | true if Disallow: / is present for any User-agent. |
Scoring
- 100 File exists, no block-all, has Sitemap directive.
- 70 File exists but missing Sitemap directive.
- 10
Disallow: /found — entire site blocked. - 0 File not found (HTTP 404).
sitemap
Discovers the sitemap URL from robots.txt or falls back to /sitemap.xml, then validates its XML structure and content.
data fields
| Field | Type | Description |
|---|---|---|
| exists | boolean | Whether a sitemap was found. |
| url | string | The sitemap URL that was fetched. |
| format | string | urlset or sitemap_index. |
| url_count | integer | Number of URLs (or child sitemaps for sitemap_index). |
| has_lastmod | boolean | Whether at least one <lastmod> date is present. |
| has_images | boolean | Whether image sitemap extensions (image: namespace) are used. |
Scoring
- 100 Sitemap found, valid XML, contains URLs.
- 60 Sitemap has more than 50,000 URLs (limit exceeded).
- 40 Sitemap contains no URLs.
- 0 Sitemap not found.
canonical
Checks for the presence of a <link rel="canonical"> tag, verifies it is self-referencing, and detects conflicting multiple canonicals.
data fields
| Field | Type | Description |
|---|---|---|
| is_present | boolean | Whether a canonical tag exists. |
| canonical_url | string|null | The href value of the canonical tag. |
| is_self_canonical | boolean | Whether the canonical points back to the same URL. |
| all_canonicals | string[] | All canonical href values found (multiple = problem). |
Scoring
- 100 Present and self-referencing.
- 70 Present but points to a different URL.
- 60 Canonical tag missing.
- 20 Multiple conflicting canonical tags.
structured_data
Finds all <script type="application/ld+json"> blocks, validates their JSON syntax, extracts schema types, and flags rich-snippet-eligible types.
data fields
| Field | Type | Description |
|---|---|---|
| count | integer | Total number of schema items found (including @graph items). |
| schemas[].type | string | The @type value (e.g. Article, Product). |
| schemas[].valid | boolean | Whether the JSON block was parseable. |
| schemas[].is_rich_eligible | boolean | Whether the type qualifies for Google rich snippets. |
Rich-eligible types
Article, BlogPosting, Product, Review, Recipe, Event, FAQPage, HowTo, Person, Organization, WebSite, BreadcrumbList, LocalBusiness, JobPosting, Course, VideoObject
security_headers
Issues a HEAD request and checks five security-related response headers, plus HTTPS enforcement. Relevant for SEO because Google gives a slight ranking signal to secure sites and headers affect trust signals.
data fields
| Field | Type | Description |
|---|---|---|
| https | boolean | Whether the URL uses HTTPS. |
| hsts | boolean | Strict-Transport-Security header present. |
| x_frame_options | boolean | X-Frame-Options header present. |
| x_content_type_options | boolean | X-Content-Type-Options header present. |
| content_security_policy | boolean | Content-Security-Policy header present. |
| referrer_policy | boolean | Referrer-Policy header present. |
Scoring
- 100 All headers present, HTTPS enforced.
- 80 Missing HSTS or X-Frame-Options (−10 each).
- ≤70 Site not on HTTPS (−30).
CSP, X-Content-Type-Options, and Referrer-Policy are info level — they reduce score indirectly only if combined with other issues.
redirect_chain
Follows redirects one hop at a time (max 10), recording each step. Detects loops, oversized chains, and non-HTTPS final destinations.
data fields
| Field | Type | Description |
|---|---|---|
| chain | array | Each step: {"url": "...", "status": 301}. |
| chain_length | integer | Total number of hops (including the final destination). |
| final_url | string | The URL at the end of the chain. |
| final_status | integer | HTTP status of the final destination. |
| is_https | boolean | Whether the final URL is HTTPS. |
Scoring
- 100 No redirects, or a single clean HTTP→HTTPS redirect.
- 70 3 hops.
- 20 More than 3 hops.
- 0 Redirect loop detected.
hreflang
Detects <link rel="alternate" hreflang="…"> tags, validates language codes against BCP 47, checks for duplicates, and verifies x-default presence.
If no hreflang tags are found, the check returns pass with an informational note — single-language sites don't need hreflang.
data fields
| Field | Type | Description |
|---|---|---|
| has_hreflang | boolean | Whether any hreflang tags were found. |
| count | integer | Number of language alternates (excluding x-default). |
| languages | array | Each entry: {"lang": "en-US", "href": "https://..."}. |
| has_x_default | boolean | Whether hreflang="x-default" is present. |
internal_links
Parses all <a href> tags, filters to internal links (same host), deduplicates, and checks up to 20 sampled links for HTTP errors (4xx/5xx).
data fields
| Field | Type | Description |
|---|---|---|
| total_links | integer | Total unique internal links found on the page. |
| checked_count | integer | Number of links actually probed (max 20). |
| broken | array | Broken links: {"url": "...", "status": 404}. |
Scoring
- 100 All sampled links returned 2xx/3xx.
- 85–70 1–2 broken links (−15 per broken link).
- 60 No internal links found on the page at all.
Ready to integrate? Get your free API key in 30 seconds.
Create free account