Skip to content

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 controls which origins can access the API from browsers. The plugin uses an allowlist approach to only permit requests from approved domains.

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-MethodsGET, POST, OPTIONS
  • Access-Control-Allow-HeadersAuthorization, Content-Type, X-Site-Key
  • Access-Control-Max-Age3600 (1 hour)
  • VaryOrigin

OPTIONS requests are handled automatically for CORS preflight. These requests check whether the actual request is allowed before it’s sent.

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.

If the origin is not allowlisted, the Access-Control-Allow-Origin header is omitted, causing the browser to block the response.

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.

  • Limit: 300 requests per minute per IP
  • Purpose: Prevents obvious abuse across all endpoints
  • Applies to: All requests to the seovaultai/v1 namespace
  • 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
  • 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

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

When a rate limit is exceeded, the API returns:

  • HTTP status: 429 (Too Many Requests)
  • Error code: seovaultai_rate_limited
  • Header: Retry-After with seconds until retry

For auth lockouts:

  • HTTP status: 429 (Too Many Requests)
  • Error code: seovaultai_auth_lockout
  • Header: Retry-After with lockout duration

Rate limiting is skipped for:

  • OPTIONS preflight requests — Always allowed
  • Same-origin admin import — When importing from WordPress admin with manage_options capability and matching origin

Rate limits can be customized via filters:

// Override all rate limits
add_filter( 'seovaultai_api_rate_limits', function( $limits ) {
$limits['read']['limit'] = 200;
$limits['read']['window'] = 60;
return $limits;
} );
// Exempt specific requests
add_filter( 'seovaultai_api_rate_limit_exempt', function( $exempt, $request ) {
// Return true to exempt this request
return $exempt;
}, 10, 2 );

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 )

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.

The plugin creates a custom table wp_seovault_api_audit with columns:

  • id — Primary key
  • created_at — Timestamp
  • route — REST route path
  • method — HTTP method
  • ip_hash — SHA-256 hash of requester IP
  • user_agent_hash — SHA-256 hash of user agent
  • origin — Sanitized Origin header
  • success — Whether request succeeded (1 or 0)
  • status_code — HTTP response status
  • failure_reason — Machine-readable failure reason
  • event_type — Event classification

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)

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.

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.

By default, only write operations and auth failures are logged. Read success logging can be enabled:

// Enable read success logging
add_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
} );

Audit log behavior can be customized:

// Override max rows
add_filter( 'seovaultai_api_audit_max_rows', function( $max ) {
return 1000;
} );
// Override retention days
add_filter( 'seovaultai_api_audit_retention_days', function( $days ) {
return 60;
} );
// Modify log entry before writing
add_filter( 'seovaultai_api_audit_log_entry', function( $entry, $request ) {
// Redact or modify entry data
return $entry;
}, 10, 2 );
  • 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
  • Implement exponential backoff when receiving 429 responses
  • Respect the Retry-After header
  • Use read keys for read-only operations
  • Cache API responses where appropriate to reduce request volume