Come Integrare Claude API in WordPress: Esempio PHP Completo

1 aprile 20268 minAutomazione
In breveAI

Scopri come integrare Claude API con WordPress utilizzando un wrapper PHP robusto e personalizzato. Impara a costruire un sistema di content analysis automatica e un endpoint REST per esporre le funzionalità AI alla dashboard WordPress. Migliora la tua esperienza di automazione AI su WordPress con codice PHP funzionante e pronto all'uso.

Claude API + WordPress: Il Setup Completo in PHP

L’articolo precedente ha coperto il perché e il cosa dell’automazione AI su WordPress. Questo articolo è il come. Codice PHP funzionante, pronto da copiare e adattare.

Costruiremo tre cose concrete: un wrapper PHP robusto per Claude API, un sistema di content analysis automatica, e un endpoint REST personalizzato che espone le funzionalità AI alla dashboard WordPress.

Il Wrapper PHP: Versione Production-Ready

Il wrapper base dell’articolo precedente funziona, ma per la produzione serve di più: gestione errori, retry, caching, rate limiting.

<?php
/**
 * Claude API Wrapper - Production Ready
 * wp-content/mu-plugins/claude-api-pro.php
 */

class Claude_API_Pro {
    private string $api_key;
    private string $model;
    private string $api_url = 'https://api.anthropic.com/v1/messages';
    private int $max_retries = 3;
    private int $rate_limit_per_hour = 100;
    
    public function __construct(string $model = 'claude-sonnet-4-20250514') {
        $this->api_key = defined('CLAUDE_API_KEY') ? CLAUDE_API_KEY : '';
        $this->model = $model;
        
        if (empty($this->api_key)) {
            error_log('Claude API: chiave API non configurata in wp-config.php');
        }
    }
    
    public function query(string $prompt, array $options = []): array {
        // Rate limiting con transient
        $calls = (int) get_transient('claude_api_calls_hour') ?: 0;
        if ($calls >= $this->rate_limit_per_hour) {
            return ['error' => 'Rate limit raggiunto. Riprova tra un\'ora.'];
        }
        
        // Check cache (stessa query = stessa risposta)
        $cache_key = 'claude_' . md5($prompt . ($options['system'] ?? ''));
        $cached = get_transient($cache_key);
        if ($cached !== false && !($options['skip_cache'] ?? false)) {
            return $cached;
        }
        
        $body = [
            'model'      => $options['model'] ?? $this->model,
            'max_tokens' => $options['max_tokens'] ?? 1024,
            'messages'   => [
                ['role' => 'user', 'content' => $prompt]
            ]
        ];
        
        if (!empty($options['system'])) {
            $body['system'] = $options['system'];
        }
        
        // Retry con exponential backoff
        $last_error = '';
        for ($attempt = 1; $attempt <= $this->max_retries; $attempt++) {
            $response = wp_remote_post($this->api_url, [
                'timeout' => $options['timeout'] ?? 30,
                'headers' => [
                    'Content-Type'      => 'application/json',
                    'x-api-key'         => $this->api_key,
                    'anthropic-version' => '2023-06-01'
                ],
                'body' => json_encode($body)
            ]);
            
            if (is_wp_error($response)) {
                $last_error = $response->get_error_message();
                sleep(pow(2, $attempt)); // 2, 4, 8 secondi
                continue;
            }
            
            $code = wp_remote_retrieve_response_code($response);
            $data = json_decode(wp_remote_retrieve_body($response), true);
            
            if ($code === 200) {
                $result = [
                    'content'     => $data['content'][0]['text'] ?? '',
                    'model'       => $data['model'] ?? '',
                    'input_tokens'=> $data['usage']['input_tokens'] ?? 0,
                    'output_tokens'=> $data['usage']['output_tokens'] ?? 0,
                    'cached'      => false,
                ];
                
                // Cache per 1 ora (default)
                set_transient($cache_key, $result, $options['cache_ttl'] ?? HOUR_IN_SECONDS);
                
                // Incrementa contatore rate limit
                set_transient('claude_api_calls_hour', $calls + 1, HOUR_IN_SECONDS);
                
                // Log per audit
                $this->log_call($prompt, $result, $options);
                
                return $result;
            }
            
            if ($code === 429) {
                // Rate limited da Anthropic, aspetta
                $retry_after = (int) wp_remote_retrieve_header($response, 'retry-after') ?: 10;
                sleep($retry_after);
                continue;
            }
            
            $last_error = $data['error']['message'] ?? "HTTP $code";
            break; // Non ritentare per errori non-transitori
        }
        
        return ['error' => $last_error];
    }
    
    private function log_call(string $prompt, array $result, array $options): void {
        if (!defined('CLAUDE_API_LOG') || !CLAUDE_API_LOG) return;
        
        $log = [
            'timestamp'    => current_time('mysql'),
            'model'        => $result['model'],
            'input_tokens' => $result['input_tokens'],
            'output_tokens'=> $result['output_tokens'],
            'system'       => substr($options['system'] ?? '', 0, 100),
            'prompt_hash'  => md5($prompt),
        ];
        
        // Salva in custom table o option
        $logs = get_option('claude_api_logs', []);
        $logs[] = $log;
        
        // Tieni solo gli ultimi 500
        if (count($logs) > 500) {
            $logs = array_slice($logs, -500);
        }
        
        update_option('claude_api_logs', $logs, false);
    }
    
    public function get_monthly_cost(): float {
        $logs = get_option('claude_api_logs', []);
        $month = date('Y-m');
        $total_input = 0;
        $total_output = 0;
        
        foreach ($logs as $log) {
            if (strpos($log['timestamp'], $month) === 0) {
                $total_input += $log['input_tokens'];
                $total_output += $log['output_tokens'];
            }
        }
        
        // Prezzi Claude Sonnet (marzo 2026)
        return ($total_input / 1000000 * 3) + ($total_output / 1000000 * 15);
    }
}

$GLOBALS['claude'] = new Claude_API_Pro();

Aggiungi a wp-config.php:

define('CLAUDE_API_KEY', 'sk-ant-api03-...');
define('CLAUDE_API_LOG', true); // Abilita logging

Caso d’Uso 1: Content Analyzer

Analizza automaticamente ogni post prima della pubblicazione e suggerisce miglioramenti per SEO e GEO.

// Hook su save_post per analisi automatica
add_action('save_post', function(int $post_id, WP_Post $post) {
    // Solo per post pubblicati o in revisione
    if (!in_array($post->post_status, ['publish', 'pending'])) return;
    if (wp_is_post_revision($post_id)) return;
    if ($post->post_type !== 'post') return;
    
    // Evita analisi duplicate (una volta al giorno max)
    $last_analysis = get_post_meta($post_id, '_ai_analysis_date', true);
    if ($last_analysis === date('Y-m-d')) return;
    
    $claude = $GLOBALS['claude'];
    $content = wp_strip_all_tags($post->post_content);
    $title = $post->post_title;
    
    $prompt = "Analizza questo articolo WordPress per SEO e GEO (Generative Engine Optimization).\n\n"
        . "Titolo: {$title}\n\n"
        . "Contenuto (prime 2000 parole):\n" . substr($content, 0, 8000) . "\n\n"
        . "Fornisci:\n"
        . "1. Keyword primaria identificata\n"
        . "2. Score GEO stimato (0-100) con motivazione\n"
        . "3. Numero di statistiche con fonte trovate\n"
        . "4. Presenza di tabelle comparative (sì/no)\n"
        . "5. Le 3 migliorie più impattanti per la citabilità AI\n"
        . "Rispondi in formato JSON.";
    
    $result = $claude->query($prompt, [
        'system' => 'Sei un SEO/GEO specialist. Rispondi solo in JSON valido.',
        'max_tokens' => 1024
    ]);
    
    if (!isset($result['error'])) {
        update_post_meta($post_id, '_ai_content_analysis', $result['content']);
        update_post_meta($post_id, '_ai_analysis_date', date('Y-m-d'));
    }
}, 10, 2);

Caso d’Uso 2: Endpoint REST per la Dashboard

Esponi le funzionalità AI come endpoint REST, così puoi usarle dalla dashboard WordPress o da un frontend React/Next.js.

add_action('rest_api_init', function() {
    register_rest_route('ai/v1', '/analyze', [
        'methods'  => 'POST',
        'callback' => function(WP_REST_Request $request) {
            $claude = $GLOBALS['claude'];
            $content = $request->get_param('content');
            $type = $request->get_param('type') ?: 'general';
            
            if (empty($content)) {
                return new WP_Error('no_content', 'Contenuto richiesto', ['status' => 400]);
            }
            
            $prompts = [
                'seo' => "Analizza questo testo per SEO: keyword, meta description suggerita, heading ottimizzati.\n\n{$content}",
                'geo' => "Analizza questo testo per GEO (citabilità AI): statistiche presenti, fonti citate, struttura.\n\n{$content}",
                'security' => "Analizza questo log WordPress per problemi di sicurezza:\n\n{$content}",
                'general' => "Analizza questo contenuto WordPress e fornisci raccomandazioni:\n\n{$content}",
            ];
            
            $result = $claude->query(
                $prompts[$type] ?? $prompts['general'],
                ['max_tokens' => 2048]
            );
            
            return rest_ensure_response($result);
        },
        'permission_callback' => function() {
            return current_user_can('edit_posts');
        }
    ]);
    
    // Endpoint per i costi
    register_rest_route('ai/v1', '/stats', [
        'methods'  => 'GET',
        'callback' => function() {
            $claude = $GLOBALS['claude'];
            return rest_ensure_response([
                'monthly_cost' => round($claude->get_monthly_cost(), 4),
                'calls_today'  => (int) get_transient('claude_api_calls_hour') ?: 0,
            ]);
        },
        'permission_callback' => function() {
            return current_user_can('manage_options');
        }
    ]);
});

Ora puoi chiamare l’AI dalla dashboard WordPress o da qualsiasi client:

# Analisi GEO di un testo
curl -X POST https://tuosito.com/wp-json/ai/v1/analyze \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_APP_PASSWORD" \
  -d '{"content": "Il tuo testo qui...", "type": "geo"}'

# Statistiche costi
curl https://tuosito.com/wp-json/ai/v1/stats \
  -H "Authorization: Bearer YOUR_APP_PASSWORD"

Caso d’Uso 3: Smart Excerpt Generator

WordPress genera excerpt troncando le prime 55 parole. Risultato: excerpt inutili. L’AI genera un riassunto reale ottimizzato per schema markup e meta description.

add_filter('wp_insert_post_data', function(array $data): array {
    // Solo se l'excerpt è vuoto
    if (!empty($data['post_excerpt'])) return $data;
    if ($data['post_status'] !== 'publish') return $data;
    if ($data['post_type'] !== 'post') return $data;
    
    $content = wp_strip_all_tags($data['post_content']);
    if (strlen($content) < 200) return $data;
    
    $claude = $GLOBALS['claude'];
    $result = $claude->query(
        "Genera una meta description di esattamente 150-160 caratteri per questo articolo. "
        . "Deve contenere la keyword principale, un beneficio specifico, e invogliare al click.\n\n"
        . "Titolo: {$data['post_title']}\n\n"
        . "Contenuto (inizio): " . substr($content, 0, 3000),
        [
            'system' => 'Rispondi SOLO con il testo della meta description. Niente virgolette, niente prefissi.',
            'max_tokens' => 100
        ]
    );
    
    if (!isset($result['error']) && !empty($result['content'])) {
        $data['post_excerpt'] = sanitize_text_field($result['content']);
    }
    
    return $data;
});

Performance e Ottimizzazione

Le chiamate API aggiungono latenza. Ecco come minimizzarla:

  • Cache aggressiva: il wrapper usa transient WordPress. Stessa domanda = stessa risposta per 1 ora (configurabile). Per analisi di contenuto, il TTL può essere 24 ore
  • Async con Action Scheduler: per task non urgenti (report mensili, analisi batch), usa Action Scheduler di WooCommerce o wp_schedule_single_event(). L’analisi avviene in background
  • Prompt engineering: prompt più corti = meno token = meno costo e meno latenza. Un prompt ben scritto di 200 parole batte uno generico di 1000
  • Modello giusto per il task: Claude Haiku per task semplici (excerpt, classificazione). Claude Sonnet per analisi complesse. Non serve Opus per generare una meta description

FAQ

Questo codice funziona con WordPress multisite?

Sì. I mu-plugin sono caricati su tutti i siti della rete. La API key in wp-config.php è globale. Se vuoi rate limit separati per ogni sito nella rete, cambia la chiave del transient aggiungendo get_current_blog_id().

Posso usare GPT-4 invece di Claude?

Sì, con modifiche minime. Cambia l’endpoint (https://api.openai.com/v1/chat/completions), il formato degli header (Authorization: Bearer) e la struttura della risposta. La logica di caching, rate limiting e retry resta identica.

C’è un rischio di sicurezza nell’inviare contenuti all’API?

Il contenuto del blog è pubblico, quindi il rischio è basso. Non inviare mai: credenziali, dati personali dei clienti, informazioni finanziarie. Per i log di errore, sanitizza prima rimuovendo path assoluti e IP. Anthropic non usa le chiamate API per il training (policy dichiarata).

Quanto spazio occupano i log?

Il wrapper conserva gli ultimi 500 log come option WordPress. Circa 50-100KB. Trascurabile. Se vuoi più storico, sposta i log in una custom table con cleanup automatico.

Gestisci i siti WordPress dei tuoi clienti?

AgencyPilot ti dà report AI, uptime monitoring, backup e portale clienti in un’unica dashboard. Gratis per 3 siti.

Prova gratis
Leggi anche
Tutti gli articoli
Tutti gli articoli