CORS, Rate Limits, and API Activity Logs
The SEOvault AI REST API implements security features including CORS (Cross-Origin Resource Sharing) controls, rate limiting to prevent abuse, and audit logging for compliance and monitoring.
CORS (Cross-Origin Resource Sharing)
Section titled “CORS (Cross-Origin Resource Sharing)”CORS controls which origins can access the API from browsers. The plugin uses an allowlist approach to only permit requests from approved domains.
CORS headers
Section titled “CORS headers”When a request from an allowed origin is received, the API responds with:
Access-Control-Allow-Origin— The allowed origin (if allowlisted)Access-Control-Allow-Methods—GET, POST, OPTIONSAccess-Control-Allow-Headers—Authorization, Content-Type, X-Site-KeyAccess-Control-Max-Age—3600(1 hour)Vary—Origin
Preflight requests
Section titled “Preflight requests”OPTIONS requests are handled automatically for CORS preflight. These requests check whether the actual request is allowed before it’s sent.
Origin allowlist
Section titled “Origin allowlist”The plugin maintains an allowlist of permitted origins. Requests from origins not on the allowlist do not receive CORS headers and are blocked by browser security policies.
Disallowed origins
Section titled “Disallowed origins”If the origin is not allowlisted, the Access-Control-Allow-Origin header is omitted, causing the browser to block the response.
Rate limiting
Section titled “Rate limiting”Rate limiting prevents API abuse by limiting the number of requests from a single IP or site key. The plugin uses multiple rate limit tiers with WordPress transients for storage.
Rate limit tiers
Section titled “Rate limit tiers”Global IP limit
Section titled “Global IP limit”- Limit: 300 requests per minute per IP
- Purpose: Prevents obvious abuse across all endpoints
- Applies to: All requests to the
seovaultai/v1namespace
Auth failure limit
Section titled “Auth failure limit”- Limit: 20 failed authentication attempts per IP per 15 minutes
- Purpose: Prevents brute-force site key attacks
- Applies to: Requests with missing or invalid site keys
Auth lockout
Section titled “Auth lockout”- Warning threshold: 10 failed attempts per 15 minutes (audit log warning)
- Lockout threshold: 20 failed attempts per 15 minutes (hard lockout)
- Purpose: Progressive enforcement against repeated auth failures
- Duration: 15-minute lockout window
Per-key bucket limits
Section titled “Per-key bucket limits”After successful authentication, requests are limited per site key hash by route type:
- Read: 120 requests per minute (GET requests)
- Write: 40 requests per minute (POST, PUT, PATCH, DELETE)
- Import: Configurable limit for SEO import operations
- Media: Configurable limit for media uploads
Rate limit responses
Section titled “Rate limit responses”When a rate limit is exceeded, the API returns:
- HTTP status: 429 (Too Many Requests)
- Error code:
seovaultai_rate_limited - Header:
Retry-Afterwith seconds until retry
For auth lockouts:
- HTTP status: 429 (Too Many Requests)
- Error code:
seovaultai_auth_lockout - Header:
Retry-Afterwith lockout duration
Exemptions
Section titled “Exemptions”Rate limiting is skipped for:
- OPTIONS preflight requests — Always allowed
- Same-origin admin import — When importing from WordPress admin with
manage_optionscapability and matching origin
Customization
Section titled “Customization”Rate limits can be customized via filters:
// Override all rate limitsadd_filter( 'seovaultai_api_rate_limits', function( $limits ) { $limits['read']['limit'] = 200; $limits['read']['window'] = 60; return $limits;} );
// Exempt specific requestsadd_filter( 'seovaultai_api_rate_limit_exempt', function( $exempt, $request ) { // Return true to exempt this request return $exempt;}, 10, 2 );Clearing rate limits
Section titled “Clearing rate limits”Rate limit transients can be cleared:
- All limits: Via the Security tab “Clear API lockouts” control
- Per-IP limits: Programmatically via
RateLimiter::clear_rate_limits_for_ip( $ip )
Audit logging
Section titled “Audit logging”The audit log records API events for security monitoring and compliance. The log is privacy-conscious and never stores raw IP addresses or site keys.
Audit log table
Section titled “Audit log table”The plugin creates a custom table wp_seovault_api_audit with columns:
id— Primary keycreated_at— Timestamproute— REST route pathmethod— HTTP methodip_hash— SHA-256 hash of requester IPuser_agent_hash— SHA-256 hash of user agentorigin— Sanitized Origin headersuccess— Whether request succeeded (1 or 0)status_code— HTTP response statusfailure_reason— Machine-readable failure reasonevent_type— Event classification
Event types
Section titled “Event types”The audit log classifies events:
- auth_fail — Authentication failure (missing or invalid key)
- auth_ok — Successful authentication
- write_ok — Successful write operation (POST/PUT/PATCH/DELETE)
- lockout — IP locked out due to auth failures
- rate_limit — Rate limit exceeded
- read_ok — Successful read operation (optional, off by default)
Privacy protection
Section titled “Privacy protection”The audit log never stores:
- Raw IP addresses (only SHA-256 hashes)
- Raw site keys
- User agent strings (only SHA-256 hashes)
IPs are hashed with a site-specific salt, preventing cross-site correlation.
Automatic pruning
Section titled “Automatic pruning”The audit log automatically prunes old entries:
- Row count cap: Default 500 rows (filterable)
- Retention window: Default 30 days (filterable)
Pruning runs after every insert to keep the table size bounded.
Read success logging
Section titled “Read success logging”By default, only write operations and auth failures are logged. Read success logging can be enabled:
// Enable read success loggingadd_filter( 'seovaultai_api_audit_log_read_success', '__return_true' );
// Set sample rate (1 in N)add_filter( 'seovaultai_api_audit_read_sample_rate', function( $rate ) { return 50; // Log 1 in 50 read requests} );Customization
Section titled “Customization”Audit log behavior can be customized:
// Override max rowsadd_filter( 'seovaultai_api_audit_max_rows', function( $max ) { return 1000;} );
// Override retention daysadd_filter( 'seovaultai_api_audit_retention_days', function( $days ) { return 60;} );
// Modify log entry before writingadd_filter( 'seovaultai_api_audit_log_entry', function( $entry, $request ) { // Redact or modify entry data return $entry;}, 10, 2 );Security best practices
Section titled “Security best practices”For site administrators
Section titled “For site administrators”- Monitor the audit log regularly for suspicious activity
- Use read/write keys instead of the legacy key when possible
- Keep the web app origin allowlist up to date
- Review rate limit errors for signs of abuse
For developers
Section titled “For developers”- Implement exponential backoff when receiving 429 responses
- Respect the
Retry-Afterheader - Use read keys for read-only operations
- Cache API responses where appropriate to reduce request volume