Work with this as data
Every API here is available over the APIs.io API and to AI agents over MCP.
MCP server
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
Tools for apis
7 MCP tools reach this
find_apisBrowse and filter every API in the catalog.get_api_artifactsOne API's artifacts, grouped by type.get_openapiThe primary OpenAPI for this API.find_similar_apisAPIs that look like this one.apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.resolveTurn a domain, URL or GitHub org into the provider it belongs to.find_cohortsEvery scored population of providers in the catalog.
Call it yourself
curl for this page
This API
curl "https://apis.io/api/v1/apis/frase-io:frase-io-audits-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"
Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.
Get an API key
Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.
A second provider on the same verified email joins the account you already have.
OpenAPI Specification
openapi: 3.2.0
info:
title: Frase Audits API
version: 1.0.0
description: 'The Frase API provides programmatic access to your SEO content platform. Use it to manage content, research, audits, AI visibility tracking, and more. ## Authentication
All API requests require authentication using an API key. Include your key in the `X-API-KEY` header:
```
X-API-KEY: sk_live_your_api_key_here
```
For legacy compatibility, the `token` header is also supported. ## Rate Limits
Three independent limits apply, each with its own headers and error code. Headers are returned on `429` responses, not on successful ones. **Per-minute burst** - 60/min on trial and Starter, 120 Professional, 300 Scale, 1,000 Enterprise. Error code `rate_limit_exceeded`. - `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` (epoch seconds)
**Sustained hourly ceiling** - 2,000/hour trial, 3,000 Starter, 5,000 Professional, 15,000 Scale, 50,000 Enterprise. Error code `hourly_limit_exceeded`. - `X-RateLimit-Hourly-Limit`, `X-RateLimit-Hourly-Remaining`, `X-RateLimit-Hourly-Reset` (epoch seconds)
**Monthly request budget** - error code `monthly_budget_exceeded`, headers `X-Monthly-Limit`, `X-Monthly-Used`, `X-Monthly-Remaining`. Every `429` carries `Retry-After` in seconds. On the hourly ceiling this can be up to 3,600, so clients should honour it rather than retrying on a short fixed backoff - a retry sooner than `Retry-After` cannot succeed. ## Errors
The API uses standard HTTP status codes and returns errors in a consistent format:
```json
{
"error": {
"type": "authentication_error",
"code": "invalid_api_key",
"message": "Invalid API key provided.",
"doc_url": "https://docs.frase.io/api/errors#invalid_api_key"
}
}
```
## Changelog
Breaking changes are listed here, newest first. "Breaking" means a field that was always present with a value can now be `null`, or an existing value now means something different — not that a field was removed or renamed. Additive fields are not listed. ### 2026-08-31 — Fact `sources[]` may omit `url`, and minted facts default to `approved`
- `GET /briefs/{id}/facts`, `PATCH /briefs/{id}/facts/{factId}` → a `sources[]` entry may now carry only `title`, with no `url` or `domain`. Research names a publisher far more often than it captures a link (793 of 801 plan-evidence items in production), and those names were previously discarded rather than returned. Read `url` defensively. - `GET /briefs/{id}/facts` → `approval` on a newly minted fact is now `approved` rather than `pending`. A client polling for `pending` facts to review will see far fewer; rejection is unchanged and remains permanent. Facts the writer asserted in its own output (`provenance: generated`) are still minted `pending`. ### 2026-08-24 — Content Guard''s hasPublishedUrl is now recoverability, not the stored URL
- `GET /content/{id}/monitoring` → `hasPublishedUrl`: was a judgment on the STORED URL (false for a relative path even when the page was live). It now answers "could Guard watch this article right now" — the stored URL when absolute, or an address the server recovers (CMS inventory; FraseCMS re-derivation from current settings when a live published post exists). `true` no longer implies the stored URL itself is absolute, and a FraseCMS article published before domain setup flips to `true` when setup finishes, with no re-publish. It remains exactly the condition enabling enforces. - `publishedUrlIssue` is now the RESIDUAL after recovery failed, not a raw-column classification — "not_absolute" can also mean the FraseCMS post is not live or FraseCMS is disabled for the site. - `POST /content/{id}/publish` → `url` (frase_cms): now derived from the site''s current CMS settings rather than the adapter''s host-of-the-moment value; the same URL is stored on the content row and sent in the content.published webhook.'
contact:
name: Frase Support
url: https://frase.io
email: support@frase.io
license:
name: Proprietary
url: https://www.frase.io/company/terms
servers:
- url: https://next.frase.io/api/v1
description: Production
- url: /api/v1
description: This host
security:
- ApiKeyAuth: []
- BearerAuth: []
tags:
- name: Audits
description: Site audits and SEO analysis
paths:
/audits:
get:
operationId: listAudits
summary: List audits
description: Retrieve all site audits.
tags:
- Audits
parameters:
- name: page
in: query
schema:
type: integer
default: 1
- name: page_size
in: query
schema:
type: integer
default: 20
- name: site_id
in: query
schema:
type: string
format: uuid
- name: status
in: query
schema:
type: string
enum:
- pending
- crawling
- analyzing
- completed
- failed
responses:
'200':
description: List of audits
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
type: object
properties:
id:
type: string
format: uuid
name:
type: string
nullable: true
type:
type: string
status:
type: string
progress:
type: integer
nullable: true
pages_audited:
type: integer
total_issues:
type: integer
critical_issues:
type: integer
overall_score:
type: integer
nullable: true
site:
type: object
nullable: true
properties:
id:
type: string
name:
type: string
domain:
type: string
created_at:
type: string
format: date-time
completed_at:
type: string
format: date-time
nullable: true
request_id:
type: string
pagination:
$ref: '#/components/schemas/Pagination'
post:
operationId: startAudit
summary: Start audit
description: Start a new site audit.
tags:
- Audits
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- site_id
properties:
site_id:
type: string
format: uuid
name:
type: string
description: Custom audit name
type:
type: string
enum:
- full
- quick
- technical
max_pages:
type: integer
description: Maximum pages to crawl
selected_page_urls:
type: array
items:
type: string
description: Specific URLs to audit
settings:
type: object
description: Advanced audit settings
responses:
'201':
description: Audit started
content:
application/json:
schema:
type: object
properties:
data:
type: object
properties:
id:
type: string
format: uuid
name:
type: string
nullable: true
type:
type: string
status:
type: string
site:
type: object
nullable: true
properties:
id:
type: string
name:
type: string
domain:
type: string
created_at:
type: string
format: date-time
request_id:
type: string
/audits/{id}:
get:
operationId: getAudit
summary: Get audit
description: Retrieve audit details and summary.
tags:
- Audits
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
- name: include_pages
in: query
schema:
type: boolean
default: false
description: Include audited pages in response
- name: page_limit
in: query
schema:
type: integer
default: 50
description: Max pages to include (when include_pages=true)
responses:
'200':
description: Audit details
content:
application/json:
schema:
type: object
properties:
data:
type: object
properties:
id:
type: string
format: uuid
name:
type: string
nullable: true
type:
type: string
status:
type: string
progress:
type: integer
nullable: true
pages_audited:
type: integer
nullable: true
total_issues:
type: integer
nullable: true
critical_issues:
type: integer
nullable: true
overall_score:
type: integer
nullable: true
summary:
type: object
nullable: true
cannibalization:
type: array
nullable: true
description: Keyword-cannibalization groups from this audit. Null until the cannibalization pass has run. Each group carries the keyword and the competing pages; see GET /site-health/cannibalization for the same data with a stable per-group shape.
items:
type: object
internal_link_graph:
type: object
nullable: true
description: The crawl's internal link graph as stored by the audit pipeline. Null until the link-graph pass has run. Per-page link facts are on pages[] (internal_page_rank, internal_links_in/out, click_depth, is_orphan, is_dead_end); this object is the raw graph behind them.
properties: {}
site:
type: object
nullable: true
properties:
id:
type: string
name:
type: string
nullable: true
domain:
type: string
created_at:
type: string
format: date-time
completed_at:
type: string
format: date-time
nullable: true
pages:
type: array
nullable: true
description: Included when include_pages=true
items:
type: object
properties:
id:
type: string
url:
type: string
title:
type: string
nullable: true
status_code:
type: integer
nullable: true
description: 'Final HTTP status of the fetch, after redirects. Recorded on crawler-discovered pages AND on manually scoped (selected-URL) audits; null only where no status was captured, which is not evidence the page was reachable. A row with status_code >= 400 is a dead-page tombstone rather than a page: it is excluded, carries no score and no issues, and exists only so the site view can retire the URL. Dead URLs that other pages still link to also surface as broken_link issues on the linking page; unlinked ones surface in the app as a single site-wide dead_page finding, which no v1 endpoint emits today.'
score:
type: integer
nullable: true
description: 'Canonical page-v2 score: the equal mean of E-E-A-T, SEO and GEO. The same formula is used by crawl, API and synchronous single-page audits. Null when any required pillar is unmeasured — never replaced by a different formula. Formula version and coverage are recorded in analysis_provenance.score.'
issues:
type: array
items:
type: object
word_count:
type: integer
nullable: true
aeo_score:
type: integer
nullable: true
description: Answer-engine optimization score, 0-100.
technical_score:
type: integer
nullable: true
minimum: 0
maximum: 100
description: 'Derived from indexability directives, canonical, head metadata, social/viewport tags, structured data, image alt text and broken internal links. Uses the authoritative stored projection when present; for historical rows where that projection is absent, derives the same score at read time from evidence already stored on the row without modifying it. This endpoint and GET /site-health therefore report the same number for the same page. Null when no technical check could run at all — the fetch was unreliable, or no head was parsed and no head-independent check found anything — which is not the same as 0. A page whose head was never parsed is scored on the checks that never needed one (title, meta description, H1 structure, image alt text, broken links) rather than dropped. Does not include page speed or internal-link authority — see internal_page_rank for the latter. Additive: this field is new in this response, so no existing consumer loses a value. Guard for null before arithmetic.'
internal_page_rank:
type: integer
nullable: true
minimum: 0
maximum: 100
description: PageRank over the site's internal link graph, min-max normalized across the crawled set, so it is not comparable between runs of different sizes.
internal_links_in:
type: integer
nullable: true
internal_links_out:
type: integer
nullable: true
click_depth:
type: integer
nullable: true
description: Clicks from the site root. null = not measured (the crawl contained no site root). -1 = measured and unreachable. >= 0 = depth.
is_orphan:
type: boolean
nullable: true
description: null = the link graph was not measured; false genuinely means the page is not an orphan.
is_dead_end:
type: boolean
nullable: true
description: null = the link graph was not measured; false genuinely means the page is not a dead end.
eeat_score:
type: integer
nullable: true
geo_score:
type: integer
nullable: true
seo_score:
type: integer
nullable: true
agent_readiness_score:
type: integer
nullable: true
description: Null on pages audited before agent-readiness probing existed.
agent_readiness_breakdown:
type: object
nullable: true
scoring_version:
type: string
nullable: true
request_id:
type: string
/audits/{id}/export:
get:
operationId: exportAudit
summary: Export audit
description: Export audit report in JSON or CSV format.
tags:
- Audits
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
- name: format
in: query
schema:
type: string
enum:
- json
- csv
default: json
responses:
'200':
description: Exported audit
content:
application/json:
schema:
type: object
properties:
data:
type: object
properties:
id:
type: string
format: uuid
site_id:
type: string
nullable: true
site_domain:
type: string
nullable: true
type:
type: string
status:
type: string
summary:
type: object
properties:
total_pages:
type: integer
crawled_pages:
type: integer
avg_word_count:
type: number
total_issues:
type: integer
critical_issues:
type: integer
warning_issues:
type: integer
pages:
type: array
items:
type: object
created_at:
type: string
format: date-time
completed_at:
type: string
format: date-time
nullable: true
export_format:
type: string
exported_at:
type: string
format: date-time
request_id:
type: string
text/csv:
schema:
type: string
/audits/page:
post:
operationId: auditPage
summary: Audit a single page
description: Synchronously scrape and audit a single URL, returning E-E-A-T, GEO, and SEO scores along with detected issues. Every route uses the canonical page scorer; no background audit job is queued.
tags:
- Audits
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- url
properties:
url:
type: string
format: uri
description: The URL to audit. Must be http or https.
keyword:
type: string
description: Optional target keyword for SEO scoring context.
audit_page_id:
type: string
format: uuid
description: Optional audit page ID. When provided, re-audits the page within its site audit context, updating the auditPages row and recalculating the parent audit score.
site_audit_id:
type: string
format: uuid
description: Optional site audit ID. Used together with audit_page_id to specify which site audit the page belongs to.
site_id:
type: string
format: uuid
description: Optional site ID. When provided, the endpoint uses this site for routing instead of auto-detecting by domain.
confirm_target_keyword_change:
type: boolean
description: Retry with true only after the user explicitly confirms replacing the page's canonical target keyword following a 409 response.
responses:
'201':
description: 'Audit completed successfully (synchronous). Returned for routes: standalone, reaudit_in_site_audit, add_to_site_audit.'
content:
application/json:
schema:
type: object
properties:
route:
type: string
enum:
- standalone
- reaudit_in_site_audit
- add_to_site_audit
description: The routing path taken by the endpoint.
id:
type: string
format: uuid
url:
type: string
format: uri
status:
type: string
enum:
- completed
- failed
keyword:
type: string
nullable: true
content_type:
type: string
nullable: true
description: Canonical content type. Null when classification is not applicable or unmeasured.
enum:
- blog_post
- page
- faq
- glossary
- comparison
- pillar
- how_to
- landing
- listicle
- case_study
- tutorial
- news
- product_update
- data_report
- event
- resource
- alternative
- tool_review
- null
content_type_confidence:
type: string
nullable: true
description: Categorical confidence in the canonical content classification.
enum:
- high
- medium
- low
- null
content_type_status:
type: string
description: Whether canonical classification succeeded, used a deterministic fallback, was not applicable, or was unmeasured.
enum:
- classified
- fallback
- not_applicable
- unmeasured
content_type_method:
type: string
nullable: true
description: Classification method. Null when no classifier ran.
enum:
- llm
- deterministic
- null
content_type_version:
type: string
nullable: true
description: Internal classifier revision. Null when no classifier ran.
title:
type: string
nullable: true
meta_description:
type: string
nullable: true
word_count:
type: integer
overall_score:
type: integer
nullable: true
minimum: 0
maximum: 100
eeat_score:
type: integer
nullable: true
minimum: 0
maximum: 100
eeat_breakdown:
type: object
nullable: true
geo_score:
type: integer
nullable: true
minimum: 0
maximum: 100
geo_breakdown:
type: object
nullable: true
seo_score:
type: integer
nullable: true
minimum: 0
maximum: 100
seo_breakdown:
type: object
nullable: true
issues:
type: array
items:
type: object
headings:
type: array
items:
type: object
properties:
level:
type: integer
text:
type: string
outbound_links:
type: array
items:
type: string
author_name:
type: string
nullable: true
analyzed_at:
type: string
format: date-time
created_at:
type: string
format: date-time
'409':
description: Target keyword conflict. Confirm the change with the user, then retry with confirm_target_keyword_change=true.
'422':
description: Invalid URL
'429':
description: Audit page limit exceeded
components:
schemas:
Pagination:
type: object
properties:
page:
type: integer
page_size:
type: integer
total:
type: integer
has_more:
type: boolean
securitySchemes:
ApiKeyAuth:
type: apiKey
in: header
name: X-API-KEY
description: 'API key for authentication. Get your key from Settings > API Keys. You may also send the same key as `Authorization: Bearer <key>` — see BearerAuth.'
BearerAuth:
type: http
scheme: bearer
bearerFormat: Frase API key (sk_live_* / sk_test_*) or MCP access token (mcp_at_*)
description: 'Send either your API key or an MCP access token as `Authorization: Bearer <token>`. An API key sent this way is exactly equivalent to sending it in X-API-KEY — same identity, same scopes, same rate limits — so use whichever your HTTP client makes natural. X-API-KEY takes precedence if you somehow send both. Cookie/session auth is intentionally NOT supported on /api/v1/* endpoints.'