REST Lens allows organizations to define their own API evaluation rules beyond the built-in rule set. Custom rules let you enforce organization-specific standards and import existing Spectral rulesets.
Overview
Custom rules are defined at the organization level and can be used across all projects within that organization. Each rule uses REST Lens YAML format to define what to check and how to report violations.
Creating Custom Rules
Navigate to your organization's Custom Rules page from the sidebar. Click New Rule to create a new custom rule.
Rule Structure
Each custom rule consists of:
- Name: A human-readable name for the rule
- Slug: A unique identifier (auto-generated from the name)
- Description: What the rule checks for
- Severity:
error,warning, orinfo - YAML Content: The rule definition
REST Lens YAML Format
Basic Structure
slug: rule-id-in-kebab-case
name: Human Readable Name
description: What this rule checks for
severity: error|warning|info
target: paths|operations|responses|parameters|schemas|headers|tags|info
filter: # Optional - restrict which items to check
method: GET|POST|PUT|DELETE|PATCH
pattern: "regex"
status: "200|201|4XX"
in: path|query|header|cookie
when: # Optional - only check if condition passes
- field: fieldName
matches: "regex"
check: # Validate a field on the CURRENT element ("does this have X?")
field: fieldName
matches: "regex"
require: # Validate RELATED elements exist ("does Y exist for this?")
responses:
has: "201" # Operation must have this response (supports wildcards: "2XX", "4XX")
hasAnyOf: ["200", "201", "2XX"] # At least one must exist
hasOneOf: ["200", "201"] # Exactly one must exist
headers:
has: "Location" # Response must have this header
siblings: # Path must have sibling operation
- method: GET
parameters: # Operation must have this parameter
has:
name: "id"
in: path
consistent: # Cross-spec consistency
group_by: [status, content_type]
field: schema
message: "Error message with {{path}}, {{method}}, {{status}}, {{segment}}, {{name}} placeholders"
Targets
| Target | Description |
|---|---|
paths | Iterates over all path segments |
operations | Iterates over all operations (GET /users, POST /users, etc.) |
responses | Iterates over all response definitions |
parameters | Iterates over all parameter definitions |
schemas | Iterates over all schema definitions |
headers | Iterates over all header definitions |
tags | Iterates over all tags defined in the spec |
info | Evaluates the API info object (title, version, description, etc.) |
Check Operators
| Operator | Description |
|---|---|
exists | Field exists (not null/undefined) |
notExists | Field does NOT exist (null/undefined) |
empty | Field is null or empty |
notEmpty | Field is NOT null and NOT empty |
matches | Regex pattern match |
notMatches | Regex pattern does NOT match |
equals | Exact equality |
notEquals | Value does NOT equal |
contains | Substring match |
notContains | Substring NOT found |
startsWith | String starts with value |
endsWith | String ends with value |
oneOf | Value is in list |
notOneOf | Value is NOT in list |
includes | Array contains element |
notIncludes | Array does NOT contain element |
every | All array elements match regex pattern |
some | At least one array element matches regex pattern |
lengthMin | Minimum string/array length |
lengthMax | Maximum string/array length |
lengthEquals | Exact length |
alphabetical | Array is alphabetically sorted |
unique | All values in array are unique (no duplicates) |
xor | Exactly one of specified fields must exist |
Tip: Operators come in pairs (e.g.,
exists/notExists,matches/notMatches). Using the negated operator is equivalent to using the base operator withnegate: true.
To negate any operator, add negate: true:
check:
field: description
exists: true
negate: true # Field must NOT exist
Pre-Filtering Arrays with where
Use where to pre-filter array elements before applying the operator. Supports two forms:
Regex (string) — filters by element text, or by name field on object arrays:
# At least 2 error responses (4xx or 5xx)
check:
field: responses
where: "^[45]"
lengthMin: 2
Property match (mapping) — filters object arrays where all specified properties match exactly:
# At most 5 query parameters
check:
field: parameters
where:
in: query
lengthMax: 5
The where filter reduces the array first, then the operator applies to the filtered result. Works with all array operators (lengthMin, lengthMax, includes, every, some, etc.).
Boolean Combinators
Combine multiple conditions with anyOf, allOf, and not in both check and when blocks:
| Combinator | Description |
|---|---|
anyOf | At least one condition must pass (OR) |
allOf | All conditions must pass (AND) — same as listing multiple conditions |
not | Inner condition must fail (NOT) |
# Description OR summary must exist
check:
anyOf:
- field: description
notEmpty: true
- field: summary
notEmpty: true
Combinators can be nested:
check:
allOf:
- not:
field: deprecated
equals: true
- anyOf:
- field: description
notEmpty: true
- field: summary
notEmpty: true
Conditional Branching (if/then/else)
Apply different checks based on a condition:
# POST needs description, others need summary
check:
if:
field: method
equals: "post"
then:
field: description
notEmpty: true
else:
field: summary
notEmpty: true
thenis required whenifis presentelseis optional — when omitted and theifcondition fails, the check passes
Filter Options
Filters restrict which elements to check:
filter:
method: POST # For operations: GET, POST, PUT, DELETE, PATCH
pattern: "^/api/v[0-9]+" # For paths: regex pattern
status: "2XX" # For responses: exact or wildcard (2XX, 4XX, 5XX)
in: query # For parameters: path, query, header, cookie
isParameter: true # For paths: true matches {param} segments, false matches static segments
content_type: "application/json" # For responses/operations: check content type
| Filter | Target | Description |
|---|---|---|
method | operations | HTTP method (GET, POST, PUT, DELETE, PATCH) |
pattern | paths | Regex pattern to match path |
status | responses | Status code, exact or wildcard (200, 2XX, 4XX) |
in | parameters | Parameter location (path, query, header, cookie) |
isParameter | paths | true for path parameters like {id}, false for static segments |
content_type | responses, operations | Media type (e.g., application/json) |
Variables in Messages
Use placeholders in messages for dynamic context:
| Variable | Description |
|---|---|
{{path}} | Full path (e.g., /users/{id}) |
{{segment}} | Current path segment |
{{method}} | HTTP method (e.g., GET) |
{{operation}} | Operation ID (alias for {{operation_id}}) |
{{operation_id}} | The operationId value |
{{status}} | Response status code (e.g., 200) |
{{name}} | Parameter name |
{{in}} | Parameter location (path, query, header, cookie) |
{{tag}} | Tag name (alias for {{tag_name}}) |
{{tag_name}} | Tag name |
{{version}} | API version from info object (only for info target) |
{{title}} | API title from info object (only for info target) |
{{api_description}} | API description from info object (only for info target) |
Accessible Fields
Fields you can access with check.field depend on the target type:
paths
segment- Current path segmentpath- Full pathkey- Same as segmentvariable_for- True if segment is a path parameter
operations
summary- Operation summarydescription- Operation descriptionresponses- List of response status codes (array, e.g.["200", "404"])parameters- List of parameter objects (array of{name, in, required, description?})tags- Operation tags (array)trailing_slash- Whether path has trailing slashrequest_body/requestBody- The request body objectdeprecated- Whether operation is deprecated (boolean)
responses
description- Response descriptioncontent- Content types and schemasheaders- Response header objects (array of{name, description?})
parameters
name- Parameter namein- Location (path, query, header, cookie)required- Whether requireddescription- Parameter descriptionschema- Parameter schema
schemas
type- Schema type (string, object, array, etc.)enum- Enum values arrayproperties- Object propertiesformat- Format (date, uuid, email, etc.)items- Array item schemaany_of- anyOf schemasdefault- Default valuetitle- Schema titleref- $ref referenceadditional_properties- Additional properties schema
headers
description- Header descriptionschema- Header schema
tags
name- Tag namedescription- Tag description
info
title- API titledescription- API descriptionversion- API version- Any other property in the info object
check vs require
check | require | |
|---|---|---|
| Purpose | Validate a field on the current element | Validate that related elements exist |
| Question | "Does THIS element have X?" | "Does related element Y exist?" |
| Example | "Does this operation have a description?" | "Does this operation have a 201 response?" |
Relational Checks (require)
The require block validates relationships between API elements.
require.responses
Check that an operation has specific responses. Supports wildcards (2XX, 4XX, 5XX):
slug: post-returns-201
name: POST Should Return 201
description: POST operations should return 201 for creation
severity: warning
target: operations
filter:
method: POST
require:
responses:
has: "201" # Or use "2XX" for any 2xx response
message: "POST {{path}} should return 201 for successful creation"
Operators:
has: Single status code or wildcard (e.g.,"201","2XX")hasAnyOf: At least one must exist (e.g.,["200", "201", "2XX"])hasOneOf: Exactly one must exist - not zero, not multiple (e.g.,["200", "201"])
# Example: Operation must have at least one success response
require:
responses:
hasAnyOf: ["200", "201", "204"]
# Example: Operation must have exactly one of 200 or 201 (not both)
require:
responses:
hasOneOf: ["200", "201"]
require.headers
Check that a response includes specific headers:
slug: 201-has-location
name: 201 Must Have Location Header
description: 201 responses should include Location header
severity: error
target: responses
filter:
status: "201"
require:
headers:
has: "Location"
message: "201 response at {{path}} should include Location header"
require.siblings
Check that a path has sibling operations (e.g., if you have POST, you should also have GET):
slug: post-needs-get
name: POST Paths Need GET
description: Paths with POST should also have GET for listing
severity: info
target: operations
filter:
method: POST
require:
siblings:
- method: GET
message: "{{path}} has POST but no GET operation for listing resources"
require.parameters
Check that an operation has specific parameters:
slug: collection-needs-pagination
name: Collection Endpoints Need Pagination
description: GET collection endpoints should have pagination parameters
severity: warning
target: operations
filter:
method: GET
when:
- field: path
matches: "^/[^{]+$" # Paths without path params (likely collections)
require:
parameters:
has:
name: "limit"
in: query
message: "GET {{path}} should have pagination parameter 'limit'"
Conditional Checks (when)
The when block runs the check only if conditions are met:
slug: versioned-paths-need-header
name: Versioned APIs Need Accept Header
description: APIs with version in path should document Accept header
severity: info
target: operations
when:
- field: path
matches: "/v[0-9]+/"
require:
parameters:
has:
name: "Accept"
in: header
message: "{{method}} {{path}} is versioned but doesn't document Accept header"
Consistency Checks
Check for consistency across similar elements:
slug: consistent-error-schema
name: Consistent Error Responses
description: All error responses should use the same schema
severity: warning
target: responses
filter:
status: "4XX"
consistent:
group_by: [status]
field: schema
message: "Error response schemas should be consistent"
More Examples
Enforce kebab-case in paths
slug: kebab-case-paths
name: Kebab Case Paths
description: Path segments must use kebab-case
severity: error
target: paths
check:
field: segment
matches: "^[a-z0-9]+(-[a-z0-9]+)*$|^\\{[a-zA-Z]+\\}$"
message: "Path segment '{{segment}}' should use kebab-case"
204 responses must be empty
slug: 204-no-content
name: 204 Must Be Empty
description: 204 No Content responses should not define content
severity: error
target: responses
filter:
status: "204"
check:
field: content
empty: true
message: "204 response at {{path}} should not have content"
Require operation descriptions
slug: require-operation-description
name: Require Operation Description
description: All operations must have a description
severity: warning
target: operations
check:
field: description
exists: true
message: "{{method}} {{path}} is missing a description"
DELETE should return 204
slug: delete-returns-204
name: DELETE Should Return 204
description: DELETE operations should return 204 on success
severity: info
target: operations
filter:
method: DELETE
require:
responses:
has: "204"
message: "DELETE {{path}} should return 204 for successful deletion"
Path parameters should be documented
slug: document-path-params
name: Document Path Parameters
description: Path parameters must have a description
severity: warning
target: parameters
filter:
in: path
check:
field: description
exists: true
message: "Path parameter '{{name}}' is missing a description"
Tags must have descriptions
slug: tag-description-required
name: Tag Description Required
description: All tags must have a description
severity: warning
target: tags
check:
field: description
exists: true
message: "Tag '{{tag}}' is missing a description"
Path parameters should use camelCase
slug: path-param-camel-case
name: Path Parameters Must Be camelCase
description: Path parameter names should use camelCase
severity: error
target: paths
filter:
isParameter: true # Only check path parameter segments like {userId}
check:
field: segment
matches: "^\\{[a-z][a-zA-Z0-9]*\\}$"
message: "Path parameter '{{segment}}' should use camelCase"
Enum values must be unique
slug: unique-enum-values
name: Unique Enum Values
description: Enum arrays should not contain duplicate values
severity: error
target: schemas
check:
field: enum
unique: true
message: "Schema enum contains duplicate values"
API must have contact info
slug: api-contact-required
name: API Contact Required
description: API info must include contact information
severity: info
target: info
check:
field: contact
exists: true
message: "API info should include contact information"
API title must not be empty
slug: api-title-not-empty
name: API Title Required
description: API must have a non-empty title
severity: error
target: info
check:
field: title
empty: true
negate: true # Must NOT be empty
message: "API title must not be empty"
API version must follow semver
slug: api-version-semver
name: API Version Must Be Semver
description: API version must follow semantic versioning
severity: warning
target: info
check:
field: version
matches: "^[0-9]+\\.[0-9]+\\.[0-9]+(-[a-zA-Z0-9]+)?$"
message: "API version '{{version}}' must follow semantic versioning (e.g., 1.0.0)"
No deprecated operations allowed
slug: no-deprecated-operations
name: No Deprecated Operations
description: Deprecated operations should be removed
severity: warning
target: operations
check:
field: deprecated
equals: true
negate: true # deprecated must NOT be true
message: "{{method}} {{path}} is deprecated and should be removed"
Object schemas should have properties
slug: object-needs-properties
name: Object Schemas Need Properties
description: Object type schemas should define their properties
severity: warning
target: schemas
when:
- field: type
equals: object
check:
field: properties
exists: true
message: "Schema '{{segment}}' is type 'object' but has no properties defined"
JSON responses only
slug: json-responses-only
name: Responses Must Be JSON
description: All responses should use application/json content type
severity: error
target: responses
filter:
content_type: "application/json"
check:
field: content
exists: true
message: "Response {{status}} at {{path}} should use application/json"
AI-Powered Rule Generation
REST Lens can generate custom rules from natural language descriptions using AI. This feature helps you quickly create rules without manually writing YAML.
How to Use
- Navigate to Custom Rules page
- Click New Rule
- Click Generate with AI
- Describe what you want to check in plain English
- Review and edit the generated YAML
- Save the rule
Example Prompts
- "Check that all POST operations return a 201 status code"
- "Ensure all path parameters have descriptions"
- "Verify that DELETE operations return 204"
- "Make sure error responses use consistent schema"
Rate Limits
AI rule generation is rate limited to 20 requests per minute per organization.
Notes
- Generated rules may need minor adjustments
- Always test generated rules before enabling organization-wide
- The AI uses AWS Bedrock (Claude 3 Haiku) for generation
Importing Spectral Rulesets
REST Lens supports importing rules from Spectral rulesets. Click Import Spectral on the Custom Rules page and paste your Spectral YAML configuration.
Supported Spectral Features
- Basic rule definitions
- JSONPath targeting
- Pattern matching functions
- Common Spectral functions (
truthy,falsy,pattern, etc.)
Translation Limitations
Not all Spectral rules can be fully translated to REST Lens format. Complex JSONPath expressions or custom functions may require manual adjustment. The import process will flag partially translated rules for review.
Example Import
rules:
operation-description:
description: Operations must have a description
given: "$.paths[*][*]"
severity: warn
then:
field: description
function: truthy
Git Integration
REST Lens provides git access for managing custom rules, allowing you to use your favorite editor, version control, and CI/CD workflows.
Overview
Each organization has a virtual git repository for custom rules at:
https://restlens.com/git/{org-slug}/rules.git
You can clone this repository, edit rule files locally, and push changes back to sync with REST Lens.
Setting Up Git Access
- Navigate to Organization Settings
- Find the Git Access section
- Click Create Token to generate an access token
- Choose permissions:
- Read & Write: Clone and push changes
- Read Only: Clone only (for CI/CD verification)
Cloning the Repository
# Clone the rules repository
git clone https://restlens.com/git/{org-slug}/rules.git
# When prompted for credentials:
# Username: anything (e.g., "git")
# Password: your git access token
Repository Structure
rules/
├── my-rule.yaml # filename becomes the rule slug
├── kebab-case-paths.yaml
└── require-description.yaml
README.md
Rule File Format
Each YAML file in the rules/ directory represents a custom rule:
slug: my-rule
name: My Custom Rule
description: What this rule checks for
severity: error # or warning, info
enabled: true # optional, defaults to true
target: operations
check:
field: description
exists: true
message: "{{method}} {{path}} is missing a description"
The slug field in the file must match the filename (without .yaml).
Pushing Changes
cd rules
# Create or edit a rule
vim rules/my-new-rule.yaml
# Commit and push
git add .
git commit -m "Add my-new-rule"
git push
Changes are applied immediately when pushed. The push will fail if:
- YAML syntax is invalid
- Rule validation fails (missing required fields, invalid operators)
- Slug doesn't match filename
Git Workflow Benefits
- Version Control: Track rule changes over time with full git history
- Code Review: Use pull requests to review rule changes before deployment
- CI/CD Integration: Automate rule validation and deployment
- Team Collaboration: Multiple team members can work on rules simultaneously
- Backup: Git repository serves as a backup of all custom rules
- Offline Editing: Work on rules without internet, push when ready
Token Management
Tokens can be managed in Organization Settings:
- Create Token: Generate new tokens with descriptive names
- Revoke Token: Delete tokens that are no longer needed
- Track Usage: See when each token was last used
Best practices:
- Create separate tokens for different use cases (CI/CD, local dev)
- Use read-only tokens for CI/CD verification jobs
- Rotate tokens periodically
- Revoke tokens when team members leave
Managing Rules
Enable/Disable Rules
Toggle rules on or off without deleting them. Disabled rules won't be evaluated.
Lock Settings
Organization admins can lock rule settings to prevent project-level overrides:
- Enabled Lock: Projects cannot disable the rule
- Severity Lock: Projects cannot change the severity level
Project Overrides
By default, projects inherit organization rule settings. Unless locked, project admins can:
- Disable specific rules for their project
- Change severity levels to match project requirements
Permissions & Limits
Who Can Create Rules
Only organization owners and admins can create, edit, or delete custom rules. Organization members have read-only access.
Rule Limits
Each organization can have up to 100 custom rules. This includes both manually created rules and imported Spectral rules.
Rate Limits
| Endpoint | Limit |
|---|---|
| Create/Update/Delete | 5 requests per hour |
| Validate YAML | 30 requests per minute |
| Import Spectral | 5 requests per hour |
| Generate with AI | 20 requests per minute |
Best Practices
- Start with warnings: Set new rules to
warningseverity initially to assess impact - Use clear messages: Include actionable guidance in violation messages with placeholders
- Test incrementally: Enable rules one at a time to identify issues
- Document conventions: Add descriptions explaining why the rule exists
- Review before locking: Ensure rules work correctly before locking at org level
- Use filters wisely: Narrow down targets with filters to avoid false positives
- Keep rules focused: Each rule should check one specific concern
- Test with sample specs: Validate rules against sample specifications before enabling organization-wide
- Version control: Consider maintaining rule definitions in version control for team collaboration
- Monitor effectiveness: Review rule violations regularly to ensure rules remain relevant