# Skill: API Integration

## Identity
- **Skill ID**: `api-integration`
- **Domain**: External API Communication & Webhooks
- **Technologies**: WordPress HTTP API, REST API, Webhooks
- **Source Agent**: `api-integration-expert.md`

## When to Load This Skill
- Task involves external API calls
- Implementing webhooks (sending/receiving)
- Connecting to translation services
- Working with WPML integration hooks
- Files matching: `includes/**/Api/*.php`, `includes/**/class-*-sender.php`

## Core Patterns

### Secure External API Request
```php
function presszone_multilingual_api_request($endpoint, $data = [], $method = 'POST') {
    $api_key = presszone_multilingual_get_api_key();
    if (!$api_key) {
        return new WP_Error('no_api_key', __('API key not configured', 'multilingual-press-zone'));
    }
    
    $args = [
        'method' => $method,
        'timeout' => 30,
        'headers' => [
            'Authorization' => 'Bearer ' . $api_key,
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ];
    
    if (!empty($data) && in_array($method, ['POST', 'PUT', 'PATCH'], true)) {
        $args['body'] = wp_json_encode($data);
    }
    
    $response = wp_remote_request($endpoint, $args);
    
    if (is_wp_error($response)) {
        error_log('API request failed: ' . $response->get_error_message());
        return $response;
    }
    
    $code = wp_remote_retrieve_response_code($response);
    $body = wp_remote_retrieve_body($response);
    $decoded = json_decode($body, true);
    
    if ($code >= 400) {
        $message = $decoded['message'] ?? __('API request failed', 'multilingual-press-zone');
        return new WP_Error('api_error', $message, ['status' => $code]);
    }
    
    return $decoded;
}
```

### Webhook Receiver with Signature Validation
```php
function presszone_multilingual_handle_webhook() {
    // Verify request method
    if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
        wp_send_json_error(['message' => 'Invalid method'], 405);
    }
    
    // Get raw payload
    $payload = file_get_contents('php://input');
    if (empty($payload)) {
        wp_send_json_error(['message' => 'Empty payload'], 400);
    }
    
    // Verify signature
    $signature = isset($_SERVER['HTTP_X_WEBHOOK_SIGNATURE']) 
        ? sanitize_text_field($_SERVER['HTTP_X_WEBHOOK_SIGNATURE']) 
        : '';
    
    $secret = get_option('presszone_multilingual_webhook_secret');
    $expected = hash_hmac('sha256', $payload, $secret);
    
    if (!hash_equals($expected, $signature)) {
        error_log('Webhook signature mismatch');
        wp_send_json_error(['message' => 'Invalid signature'], 401);
    }
    
    // Parse and validate payload
    $data = json_decode($payload, true);
    if (json_last_error() !== JSON_ERROR_NONE) {
        wp_send_json_error(['message' => 'Invalid JSON'], 400);
    }
    
    // Process webhook
    $event = sanitize_key($data['event'] ?? '');
    
    switch ($event) {
        case 'translation.completed':
            presszone_multilingual_process_translation_complete($data);
            break;
        case 'translation.failed':
            presszone_multilingual_process_translation_failed($data);
            break;
        default:
            wp_send_json_error(['message' => 'Unknown event'], 400);
    }
    
    wp_send_json_success(['message' => 'Webhook processed']);
}
```

### Webhook Sender with Retry Logic
```php
function presszone_multilingual_send_webhook($url, $data, $secret) {
    $payload = wp_json_encode($data);
    $signature = hash_hmac('sha256', $payload, $secret);
    
    $response = wp_remote_post($url, [
        'timeout' => 15,
        'headers' => [
            'Content-Type' => 'application/json',
            'X-Webhook-Signature' => $signature,
            'X-Webhook-Event' => sanitize_key($data['event'] ?? 'unknown'),
        ],
        'body' => $payload,
    ]);
    
    if (is_wp_error($response)) {
        // Schedule retry
        wp_schedule_single_event(
            time() + 300, // 5 minutes
            'presszone_multilingual_webhook_retry',
            [$url, $data, $secret, 1]
        );
        return false;
    }
    
    $code = wp_remote_retrieve_response_code($response);
    return $code >= 200 && $code < 300;
}

// Retry handler with exponential backoff
function presszone_multilingual_webhook_retry($url, $data, $secret, $attempt) {
    $max_attempts = 5;
    
    if ($attempt >= $max_attempts) {
        error_log("Webhook failed after {$max_attempts} attempts: {$url}");
        return;
    }
    
    $success = presszone_multilingual_send_webhook($url, $data, $secret);
    
    if (!$success) {
        $delay = pow(2, $attempt) * 60; // Exponential backoff
        wp_schedule_single_event(
            time() + $delay,
            'presszone_multilingual_webhook_retry',
            [$url, $data, $secret, $attempt + 1]
        );
    }
}
```

### Rate Limiting
```php
function presszone_multilingual_check_rate_limit($identifier, $limit = 60, $window = 60) {
    $transient_key = 'presszone_multilingual_rate_' . md5($identifier);
    $current = get_transient($transient_key);
    
    if ($current === false) {
        set_transient($transient_key, 1, $window);
        return true;
    }
    
    if ($current >= $limit) {
        return false;
    }
    
    set_transient($transient_key, $current + 1, $window);
    return true;
}
```

## Anti-Patterns (Forbidden)

| Mistake | Fix |
|---------|-----|
| Using cURL directly | Use `wp_remote_*()` functions |
| Exposing API keys in logs | Never log sensitive data |
| No signature verification | Always verify webhook signatures |
| Hardcoded URLs | Use options or constants |
| No timeout | Always set reasonable timeouts |
| Missing error handling | Check `is_wp_error()` on all responses |
| No rate limiting | Implement rate limiting for external calls |

## WordPress.org Compliance

### HTTP Functions
```php
// CORRECT - WordPress HTTP API
wp_remote_get($url, $args);
wp_remote_post($url, $args);
wp_remote_request($url, $args);
wp_remote_retrieve_body($response);
wp_remote_retrieve_response_code($response);

// FORBIDDEN
curl_init();
file_get_contents($url);
```

### REST Endpoint for Webhooks
```php
register_rest_route('multilingual-press-zone/v1', '/webhook', [
    'methods' => 'POST',
    'callback' => 'presszone_multilingual_handle_webhook',
    'permission_callback' => '__return_true', // Webhooks are auth'd via signature
]);
```

## Integration with Other Skills
- **Often combined with**: `wordpress-php-integration`, `database-operations`
- **For job processing**: May trigger `queue-management` (backend)
- **For settings**: Load `settings-management` for API key retrieval

## Quick Reference

### Response Handling
```php
$response = wp_remote_post($url, $args);

// Check for WordPress errors
if (is_wp_error($response)) {
    return $response;
}

// Check HTTP status
$code = wp_remote_retrieve_response_code($response);
if ($code >= 400) {
    return new WP_Error('http_error', "HTTP {$code}", ['status' => $code]);
}

// Parse body
$body = wp_remote_retrieve_body($response);
$data = json_decode($body, true);
```

### Common Headers
```php
$headers = [
    'Authorization' => 'Bearer ' . $api_key,
    'Content-Type' => 'application/json',
    'Accept' => 'application/json',
    'X-Plugin-Version' => PRESSZONE_MULTILINGUAL_VERSION,
    'X-Site-URL' => home_url(),
];
```

## Validation Checklist
- [ ] Using `wp_remote_*()` functions (not cURL)
- [ ] API keys not logged or exposed
- [ ] Webhook signatures verified
- [ ] Timeouts set on all requests
- [ ] Error handling with `is_wp_error()`
- [ ] Rate limiting implemented
- [ ] Retry logic for failed webhooks
- [ ] Payloads sanitized before processing
