Frase Rulesets API
Manage content rule sets for automated content evaluation
Manage content rule sets for automated content evaluation
Every API here is available over the APIs.io API and to AI agents over MCP.
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
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.curl "https://apis.io/api/v1/apis/frase-io:frase-io-rulesets-api"
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.
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: 3.2.0
info:
title: Frase Rule Sets 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: Rulesets
description: Manage content rule sets for automated content evaluation
paths:
/rule-sets/{id}/evaluate:
post:
operationId: evaluateRuleSetAgainstContent
summary: Evaluate one rule set against a content row
description: 'Run a single rule set against an existing Frase content row. The outcome is persisted by default (pass `save_results: false` to preview without writing). Use `POST /content/{id}/evaluate-rules` to run every applicable rule set at once.'
tags:
- Rulesets
security:
- ApiKeyAuth: []
parameters:
- name: id
in: path
required: true
description: Rule set ID
schema:
type: string
format: uuid
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- content_id
properties:
content_id:
type: string
format: uuid
description: Required. The content row to evaluate.
site_id:
type: string
format: uuid
description: Optional. Scopes site-scoped rules and score computation.
save_results:
type: boolean
description: Persist the evaluation. Defaults to true; pass false for a preview.
responses:
'200':
description: Evaluation result for the rule set
content:
application/json:
schema:
type: object
properties:
data:
type: object
properties:
rule_set_id:
type: string
format: uuid
rule_set_name:
type: string
content_id:
type: string
format: uuid
overall_status:
type: string
enum:
- pass
- warning
- fail
summary:
nullable: true
results:
type: array
items:
type: object
properties:
rule_id:
type: string
format: uuid
rule_name:
type: string
condition_type:
type: string
severity:
type: string
enum:
- error
- warning
- info
passed:
type: boolean
skipped:
type: boolean
description: The rule could not run (e.g. a score_threshold rule with no target keywords). A skipped rule is neither a pass nor a failure - never read it as a genuine pass.
value:
nullable: true
threshold:
nullable: true
message:
type: string
nullable: true
fix_hint:
type: string
nullable: true
request_id:
type: string
'400':
$ref: '#/components/responses/ValidationError'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
'429':
description: Rate limited, or the org's monthly brief limit blocked minting the refreshed brief (only the FIRST supersession of a brief is billing-exempt; repeats meter normally)
/rule-sets/{id}/rules:
post:
operationId: addRuleToRuleSet
summary: Add a rule to a rule set
description: Attach an existing rule to a rule set at an optional sort position. System rule sets cannot be modified.
tags:
- Rulesets
security:
- ApiKeyAuth: []
parameters:
- name: id
in: path
required: true
description: Rule set ID
schema:
type: string
format: uuid
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- rule_id
properties:
rule_id:
type: string
format: uuid
description: Required. The rule to attach.
sort_order:
type: integer
description: Optional position of the rule within the set.
responses:
'201':
description: Rule attached to the set
content:
application/json:
schema:
type: object
properties:
data:
type: object
properties:
id:
type: string
format: uuid
description: The rule-set-to-rule link ID.
rule_set_id:
type: string
format: uuid
rule_id:
type: string
format: uuid
sort_order:
type: integer
nullable: true
request_id:
type: string
'400':
$ref: '#/components/responses/ValidationError'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
description: System rule set cannot be modified
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
$ref: '#/components/responses/NotFound'
'409':
description: The rule is already attached to this set
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'429':
description: Rate limited, or the org's monthly brief limit blocked minting the refreshed brief (only the FIRST supersession of a brief is billing-exempt; repeats meter normally)
delete:
operationId: removeRuleFromRuleSet
summary: Remove a rule from a rule set
description: Detach a rule from a rule set. The rule itself is not deleted. System rule sets cannot be modified.
tags:
- Rulesets
security:
- ApiKeyAuth: []
parameters:
- name: id
in: path
required: true
description: Rule set ID
schema:
type: string
format: uuid
- name: rule_id
in: query
required: true
description: The rule to detach from the set
schema:
type: string
format: uuid
responses:
'200':
description: Rule detached from the set
content:
application/json:
schema:
type: object
properties:
data:
type: object
properties:
removed:
type: boolean
request_id:
type: string
'400':
$ref: '#/components/responses/ValidationError'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
description: System rule set cannot be modified
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
$ref: '#/components/responses/NotFound'
'429':
description: Rate limited, or the org's monthly brief limit blocked minting the refreshed brief (only the FIRST supersession of a brief is billing-exempt; repeats meter normally)
/rule-sets:
get:
operationId: listRuleSets
summary: List rule sets
description: Retrieve all rule sets for your organization, optionally filtered by site.
tags:
- Rulesets
security:
- ApiKeyAuth: []
parameters:
- name: page
in: query
schema:
type: integer
default: 1
- name: page_size
in: query
schema:
type: integer
default: 20
maximum: 100
- name: site_id
in: query
description: Filter rule sets by site
schema:
type: string
format: uuid
responses:
'200':
description: List of rule sets
content:
application/json:
schema:
$ref: '#/components/schemas/PaginatedRuleSets'
'401':
$ref: '#/components/responses/Unauthorized'
'429':
$ref: '#/components/responses/RateLimited'
/rule-sets/{id}:
get:
operationId: getRuleSet
summary: Get rule set
description: Retrieve a single rule set by ID, including full rule details.
tags:
- Rulesets
security:
- ApiKeyAuth: []
parameters:
- name: id
in: path
required: true
description: Rule set ID
schema:
type: string
format: uuid
responses:
'200':
description: Rule set details
content:
application/json:
schema:
type: object
properties:
data:
$ref: '#/components/schemas/RuleSetDetail'
request_id:
type: string
'400':
$ref: '#/components/responses/ValidationError'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
delete:
operationId: deleteRuleSet
summary: Delete rule set
description: Delete a rule set. System rule sets cannot be deleted.
tags:
- Rulesets
security:
- ApiKeyAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
description: Rule set ID
responses:
'200':
description: Rule set deleted
content:
application/json:
schema:
type: object
properties:
data:
type: object
properties:
deleted:
type: boolean
request_id:
type: string
'401':
$ref: '#/components/responses/Unauthorized'
'403':
description: System rule set cannot be deleted
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/RateLimited'
components:
schemas:
RuleSetSummary:
type: object
properties:
id:
type: string
format: uuid
name:
type: string
description:
type: string
nullable: true
is_system:
type: boolean
is_enabled:
type: boolean
rule_count:
type: integer
rules:
type: array
items:
$ref: '#/components/schemas/RuleSetRuleSummary'
created_at:
type: string
format: date-time
PaginatedRuleSets:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/RuleSetSummary'
pagination:
$ref: '#/components/schemas/Pagination'
request_id:
type: string
RuleSetRuleSummary:
type: object
properties:
id:
type: string
format: uuid
name:
type: string
condition_type:
type: string
severity:
type: string
sort_order:
type: integer
Error:
type: object
properties:
error:
type: object
properties:
type:
type: string
code:
type: string
message:
type: string
param:
type: string
doc_url:
type: string
request_id:
type: string
RuleSetDetail:
type: object
properties:
id:
type: string
format: uuid
name:
type: string
description:
type: string
nullable: true
is_system:
type: boolean
is_enabled:
type: boolean
rule_count:
type: integer
rules:
type: array
items:
$ref: '#/components/schemas/RuleSetRuleDetail'
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
nullable: true
Pagination:
type: object
properties:
page:
type: integer
page_size:
type: integer
total:
type: integer
has_more:
type: boolean
RuleSetRuleDetail:
type: object
properties:
id:
type: string
format: uuid
name:
type: string
description:
type: string
nullable: true
condition_type:
type: string
config:
type: object
nullable: true
severity:
type: string
fix_hint:
type: string
nullable: true
is_system:
type: boolean
sort_order:
type: integer
responses:
NotFound:
description: Resource not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
error:
type: invalid_request_error
code: resource_not_found
message: The requested resource was not found.
doc_url: https://docs.frase.io/api/errors#resource_not_found
request_id: req_abc123
Unauthorized:
description: Authentication failed
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
error:
type: authentication_error
code: invalid_api_key
message: Invalid API key. Check that you are using the correct key.
doc_url: https://docs.frase.io/api/errors#invalid_api_key
request_id: req_abc123
RateLimited:
description: Rate limit exceeded
headers:
Retry-After:
schema:
type: integer
description: Seconds until rate limit resets
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
error:
type: rate_limit_error
code: rate_limit_exceeded
message: Rate limit exceeded. Please retry after 60 seconds.
doc_url: https://docs.frase.io/api/errors#rate_limit_exceeded
request_id: req_abc123
ValidationError:
description: Validation failed
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
error:
type: invalid_request_error
code: validation_error
message: The request body is invalid.
param: prompt_text
doc_url: https://docs.frase.io/api/errors#validation_error
request_id: req_abc123
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.'