{"id":550,"date":"2026-03-15T15:13:29","date_gmt":"2026-03-15T14:13:29","guid":{"rendered":"https:\/\/techbygiusi.com\/?p=550"},"modified":"2026-03-29T16:25:24","modified_gmt":"2026-03-29T14:25:24","slug":"building-a-site-scoped-ai-agent-for-wordpress","status":"publish","type":"post","link":"https:\/\/techbygiusi.com\/index.php\/guide\/building-a-site-scoped-ai-agent-for-wordpress\/","title":{"rendered":"Building a Site &#8211; Scoped AI Agent for WordPress"},"content":{"rendered":"\n<p>A while ago I started thinking about adding a small AI assistant to this website \u2014 something visitors could ask about my projects, my homelab setup, or my blog posts. Not a generic chatbot, but something that strictly knows only this website and nothing else.<\/p>\n\n\n\n<p>What followed was a weekend of building a custom WordPress plugin from scratch, a lot of debugging, and quite a few iterations. This post covers how it works, every security measure that\u2019s in place, and how you can add it to your own WordPress site.<\/p>\n\n\n\n<p><strong><a href=\"#download\" data-type=\"internal\" data-id=\"#download\">\u2193 Skip to the bottom for the download link.<\/a><\/strong><\/p>\n\n\n\n<h5 class=\"wp-block-heading\">The Idea<\/h5>\n\n\n\n<p>The concept is simple: a floating chat widget that lets visitors ask questions about the website. If someone asks <em>\u201cWhat\u2019s your homelab setup?\u201d<\/em> or <em>\u201cDo you have a guide on Proxmox?\u201d<\/em> The agent answers based on the actual content of the site. If someone asks about something unrelated, it politely refuses.<\/p>\n\n\n\n<p>The agent is powered by <strong>Anthropic\u2019s Claude API<\/strong>. It runs as a WordPress plugin \u2014 no external services, no third-party tracking, no ads.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"#lb-1-44e0f66f\" class=\"lb-thumb\" aria-label=\"Bild vergr\u00f6\u00dfern\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"722\" src=\"https:\/\/techbygiusi.com\/wp-content\/uploads\/2026\/03\/image-1024x722.png\" alt=\"\" class=\"wp-image-561\" srcset=\"https:\/\/techbygiusi.com\/wp-content\/uploads\/2026\/03\/image-1024x722.png 1024w, https:\/\/techbygiusi.com\/wp-content\/uploads\/2026\/03\/image-300x212.png 300w, https:\/\/techbygiusi.com\/wp-content\/uploads\/2026\/03\/image-768x541.png 768w, https:\/\/techbygiusi.com\/wp-content\/uploads\/2026\/03\/image-1536x1083.png 1536w, https:\/\/techbygiusi.com\/wp-content\/uploads\/2026\/03\/image-2048x1444.png 2048w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\"><\/a><\/figure>\n\n\n\n<h5 class=\"wp-block-heading\">Architecture<\/h5>\n\n\n\n<p>Three parts work together:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>The widget<\/strong> \u2013 a floating chat button and window on the frontend<\/li>\n\n\n\n<li><strong>The PHP proxy<\/strong> \u2013 a WordPress AJAX endpoint that forwards messages to Claude<\/li>\n\n\n\n<li><strong>The admin panel<\/strong> \u2013 a settings page to configure everything<\/li>\n<\/ul>\n\n\n\n<p>The critical design decision: <strong>never call the Claude API directly from the browser<\/strong>. Anyone who opens DevTools would see the API key. Instead, every chat message goes through a server-side proxy:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Browser \u2192 WordPress AJAX (PHP) \u2192 Anthropic API \u2192 PHP \u2192 Browser<\/code><\/pre>\n\n\n\n<p>The API key never leaves the server.<\/p>\n\n\n\n<style>\n.ft-wrap{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;border:1px solid #e4e4e7;border-radius:8px;overflow:hidden;background:#fff;margin:1.5rem 0}\n.ft-header{display:flex;align-items:center;justify-content:space-between;padding:10px 14px;border-bottom:1px solid #e4e4e7;background:#f4f4f5}\n.ft-title{font-size:13px;font-weight:600;color:#09090b}\n.ft-badge{font-size:11px;color:#71717a;background:#fff;padding:2px 8px;border-radius:20px;border:1px solid #e4e4e7}\n.ft-row{display:flex;align-items:center;gap:8px;padding:7px 14px;border-bottom:1px solid #f4f4f5;font-size:13px;transition:background 0.1s}\n.ft-row:last-child{border-bottom:none}\n.ft-row:hover{background:#f9fafb}\n.ft-icon{width:16px;height:16px;flex-shrink:0}\n.ft-name{flex:1;color:#09090b}\n.ft-name.folder{font-weight:600}\n.ft-desc{font-size:12px;color:#71717a;flex:2}\n.ft-indent{padding-left:32px}\n<\/style>\n\n<div class=\"ft-wrap\">\n  <div class=\"ft-header\">\n    <span class=\"ft-title\">simple-wp-ai-agent<\/span>\n    <span class=\"ft-badge\">v2.2.0<\/span>\n  <\/div>\n\n  <div class=\"ft-row\">\n    <svg class=\"ft-icon\" viewbox=\"0 0 16 16\" fill=\"none\"><path d=\"M1.75 1h5.5l1.5 1.5h5.5v11H1.75z\" fill=\"#93c5fd\" stroke=\"#3b82f6\" stroke-width=\"1\"><\/path><\/svg>\n    <span class=\"ft-name folder\">admin\/<\/span>\n    <span class=\"ft-desc\"><\/span>\n  <\/div>\n  <div class=\"ft-row ft-indent\">\n    <svg class=\"ft-icon\" viewbox=\"0 0 16 16\" fill=\"none\"><rect x=\"2\" y=\"1.5\" width=\"12\" height=\"13\" rx=\"1\" fill=\"#f4f4f5\" stroke=\"#a1a1aa\" stroke-width=\"1\"><\/rect><line x1=\"4.5\" y1=\"5\" x2=\"11.5\" y2=\"5\" stroke=\"#a1a1aa\"><\/line><line x1=\"4.5\" y1=\"7.5\" x2=\"11.5\" y2=\"7.5\" stroke=\"#a1a1aa\"><\/line><line x1=\"4.5\" y1=\"10\" x2=\"8.5\" y2=\"10\" stroke=\"#a1a1aa\"><\/line><\/svg>\n    <span class=\"ft-name\">settings.php<\/span>\n    <span class=\"ft-desc\">Admin settings page \u2014 all config fields, import\/export<\/span>\n  <\/div>\n\n  <div class=\"ft-row\">\n    <svg class=\"ft-icon\" viewbox=\"0 0 16 16\" fill=\"none\"><path d=\"M1.75 1h5.5l1.5 1.5h5.5v11H1.75z\" fill=\"#93c5fd\" stroke=\"#3b82f6\" stroke-width=\"1\"><\/path><\/svg>\n    <span class=\"ft-name folder\">assets\/<\/span>\n    <span class=\"ft-desc\"><\/span>\n  <\/div>\n  <div class=\"ft-row ft-indent\">\n    <svg class=\"ft-icon\" viewbox=\"0 0 16 16\" fill=\"none\"><rect x=\"2\" y=\"1.5\" width=\"12\" height=\"13\" rx=\"1\" fill=\"#f4f4f5\" stroke=\"#a1a1aa\" stroke-width=\"1\"><\/rect><line x1=\"4.5\" y1=\"5\" x2=\"11.5\" y2=\"5\" stroke=\"#a1a1aa\"><\/line><line x1=\"4.5\" y1=\"7.5\" x2=\"11.5\" y2=\"7.5\" stroke=\"#a1a1aa\"><\/line><line x1=\"4.5\" y1=\"10\" x2=\"8.5\" y2=\"10\" stroke=\"#a1a1aa\"><\/line><\/svg>\n    <span class=\"ft-name\">admin.css<\/span>\n    <span class=\"ft-desc\">Admin panel styles<\/span>\n  <\/div>\n  <div class=\"ft-row ft-indent\">\n    <svg class=\"ft-icon\" viewbox=\"0 0 16 16\" fill=\"none\"><rect x=\"2\" y=\"1.5\" width=\"12\" height=\"13\" rx=\"1\" fill=\"#f4f4f5\" stroke=\"#a1a1aa\" stroke-width=\"1\"><\/rect><line x1=\"4.5\" y1=\"5\" x2=\"11.5\" y2=\"5\" stroke=\"#a1a1aa\"><\/line><line x1=\"4.5\" y1=\"7.5\" x2=\"11.5\" y2=\"7.5\" stroke=\"#a1a1aa\"><\/line><line x1=\"4.5\" y1=\"10\" x2=\"8.5\" y2=\"10\" stroke=\"#a1a1aa\"><\/line><\/svg>\n    <span class=\"ft-name\">widget.css<\/span>\n    <span class=\"ft-desc\">Frontend chat widget styles<\/span>\n  <\/div>\n  <div class=\"ft-row ft-indent\">\n    <svg class=\"ft-icon\" viewbox=\"0 0 16 16\" fill=\"none\"><rect x=\"2\" y=\"1.5\" width=\"12\" height=\"13\" rx=\"1\" fill=\"#f4f4f5\" stroke=\"#a1a1aa\" stroke-width=\"1\"><\/rect><line x1=\"4.5\" y1=\"5\" x2=\"11.5\" y2=\"5\" stroke=\"#a1a1aa\"><\/line><line x1=\"4.5\" y1=\"7.5\" x2=\"11.5\" y2=\"7.5\" stroke=\"#a1a1aa\"><\/line><line x1=\"4.5\" y1=\"10\" x2=\"8.5\" y2=\"10\" stroke=\"#a1a1aa\"><\/line><\/svg>\n    <span class=\"ft-name\">widget.js<\/span>\n    <span class=\"ft-desc\">Chat logic, proxy calls, markdown renderer<\/span>\n  <\/div>\n\n  <div class=\"ft-row\">\n    <svg class=\"ft-icon\" viewbox=\"0 0 16 16\" fill=\"none\"><rect x=\"2\" y=\"1.5\" width=\"12\" height=\"13\" rx=\"1\" fill=\"#f4f4f5\" stroke=\"#a1a1aa\" stroke-width=\"1\"><\/rect><line x1=\"4.5\" y1=\"5\" x2=\"11.5\" y2=\"5\" stroke=\"#a1a1aa\"><\/line><line x1=\"4.5\" y1=\"7.5\" x2=\"11.5\" y2=\"7.5\" stroke=\"#a1a1aa\"><\/line><line x1=\"4.5\" y1=\"10\" x2=\"8.5\" y2=\"10\" stroke=\"#a1a1aa\"><\/line><\/svg>\n    <span class=\"ft-name\">simple-wp-ai-agent.php<\/span>\n    <span class=\"ft-desc\">Main plugin \u2014 proxy, rate limiting, AJAX handlers<\/span>\n  <\/div>\n  <div class=\"ft-row\">\n    <svg class=\"ft-icon\" viewbox=\"0 0 16 16\" fill=\"none\"><rect x=\"2\" y=\"1.5\" width=\"12\" height=\"13\" rx=\"1\" fill=\"#f4f4f5\" stroke=\"#a1a1aa\" stroke-width=\"1\"><\/rect><line x1=\"4.5\" y1=\"5\" x2=\"11.5\" y2=\"5\" stroke=\"#a1a1aa\"><\/line><line x1=\"4.5\" y1=\"7.5\" x2=\"11.5\" y2=\"7.5\" stroke=\"#a1a1aa\"><\/line><line x1=\"4.5\" y1=\"10\" x2=\"8.5\" y2=\"10\" stroke=\"#a1a1aa\"><\/line><\/svg>\n    <span class=\"ft-name\">readme.txt<\/span>\n    <span class=\"ft-desc\">WordPress plugin readme<\/span>\n  <\/div>\n<\/div>\n\n\n\n<h5 class=\"wp-block-heading\">How It Works<\/h5>\n\n\n\n<h6 class=\"wp-block-heading\">The Widget<\/h6>\n\n\n\n<p>A single <code>widget.js<\/code> file injects a floating FAB button and chat window into every page. When a visitor opens the chat, it builds a message history and sends each new message to the WordPress AJAX endpoint.<\/p>\n\n\n\n<p>Responses are rendered with a lightweight markdown parser \u2014 <strong>bold<\/strong>, <em>italic<\/em>, <code>code<\/code>, bullet lists, and clickable links all work out of the box.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>async function callProxy() {\n    const params = new URLSearchParams({\n        action:   'swpa_chat',\n        nonce:    CFG.nonce,\n        messages: JSON.stringify(history),\n    });\n    const res = await fetch('\/wp-admin\/admin-ajax.php', {\n        method:  'POST',\n        headers: { 'Content-Type': 'application\/x-www-form-urlencoded' },\n        body:    params.toString(),\n    });\n    \/\/ handle response...\n}<\/code><\/pre>\n\n\n\n<h6 class=\"wp-block-heading\">The PHP Proxy<\/h6>\n\n\n\n<p>The proxy is a WordPress AJAX action that:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Validates the WordPress nonce<\/li>\n\n\n\n<li>Checks the request origin against the site\u2019s own domain<\/li>\n\n\n\n<li>Checks the per-IP rate limit<\/li>\n\n\n\n<li>Builds the system prompt server-side (including live post content)<\/li>\n\n\n\n<li>Forwards the conversation to Claude via <code>wp_remote_post<\/code><\/li>\n\n\n\n<li>Returns the reply as JSON<\/li>\n<\/ol>\n\n\n\n<pre class=\"wp-block-code\"><code>$response = wp_remote_post( 'https:\/\/api.anthropic.com\/v1\/messages', [\n    'timeout' =&gt; 30,\n    'headers' =&gt; [\n        'Content-Type'      =&gt; 'application\/json',\n        'x-api-key'         =&gt; $api_key,\n        'anthropic-version' =&gt; '2023-06-01',\n    ],\n    'body' =&gt; wp_json_encode( $payload ),\n] );<\/code><\/pre>\n\n\n\n<h6 class=\"wp-block-heading\">The System Prompt<\/h6>\n\n\n\n<p>Built dynamically on every request. It tells Claude:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Which website it represents<\/li>\n\n\n\n<li>That it must <strong>only<\/strong> answer questions about this site<\/li>\n\n\n\n<li>What to say when someone asks something off-topic<\/li>\n\n\n\n<li>All current published posts and pages (title, date, URL, excerpt)<\/li>\n<\/ul>\n\n\n\n<p>The agent always has up-to-date knowledge of your content without any manual syncing.<\/p>\n\n\n\n<h5 class=\"wp-block-heading\">Security<\/h5>\n\n\n\n<p><strong>API key never exposed to the browser<\/strong><br>Stored in WordPress options (<code>wp_options<\/code>), only used in PHP. The frontend receives zero credentials.<\/p>\n\n\n\n<p><strong>WordPress nonce validation<\/strong><br>Every AJAX request is verified with <code>check_ajax_referer()<\/code>. Missing or invalid nonce \u2192 403. The nonce is generated fresh on every page load and tied to the session.<\/p>\n\n\n\n<p><strong>Origin \/ Referer check<\/strong><br>The proxy compares <code>HTTP_ORIGIN<\/code> against the site\u2019s own domain. Requests from any other origin are rejected. This prevents someone from copying the AJAX endpoint URL and calling it from their own domain.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$site_host = wp_parse_url( get_site_url(), PHP_URL_HOST );\n$origin    = wp_parse_url( $_SERVER['HTTP_ORIGIN'], PHP_URL_HOST );\nif ( $origin !== $site_host ) {\n    wp_send_json_error( [ 'message' =&gt; 'Forbidden.' ], 403 );\n}<\/code><\/pre>\n\n\n\n<p><strong>Rate limiting per IP<\/strong><br>Each visitor is limited to a configurable number of API requests per hour. The default is 10 per hour, which is plenty for genuine visitors but enough to stop someone hammering the endpoint. When the limit is hit, the request is rejected server-side before it ever reaches the Claude API.<\/p>\n\n\n\n<p>The counter is stored as a WordPress transient and expires automatically after one hour. No cron jobs, no cleanup needed.<\/p>\n\n\n\n<p>IP detection works in layers:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>function tbg_get_client_ip() {\n    foreach ( [ 'HTTP_CF_CONNECTING_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'REMOTE_ADDR' ] as $key ) {\n        if ( ! empty( $_SERVER[ $key ] ) ) {\n            $ip = trim( explode( ',', $_SERVER[ $key ] )[0] );\n            if ( filter_var( $ip, FILTER_VALIDATE_IP ) ) return $ip;\n        }\n    }\n    return '0.0.0.0';\n}<\/code><\/pre>\n\n\n\n<p>It checks <code>CF-Connecting-IP<\/code> first (Cloudflare\u2019s real visitor IP header), then <code>X-Forwarded-For<\/code>, then <code>X-Real-IP<\/code>, then falls back to <code>REMOTE_ADDR<\/code>. This means the rate limit targets the actual visitor even behind a proxy or CDN.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$cache_key = 'swpa_rl_' . md5( $ip );\n$count     = (int) get_transient( $cache_key );\nif ( $count &gt;= $limit ) {\n    wp_send_json_error( [ 'message' =&gt; 'Rate limit reached. Please try again later.' ], 429 );\n}\nset_transient( $cache_key, $count + 1, HOUR_IN_SECONDS );<\/code><\/pre>\n\n\n\n<p>The limit is configurable from the admin panel under Widget Settings. Setting it to something like 5 on a low-traffic personal site keeps API costs near zero even if someone tries to abuse it.<\/p>\n\n\n\n<p><strong>Input sanitization<\/strong><br>All user input passes through <code>sanitize_text_field()<\/code> and <code>sanitize_textarea_field()<\/code> before touching the API payload. Message roles are whitelisted to <code>user<\/code> and <code>assistant<\/code> only.<\/p>\n\n\n\n<p><strong>Strict topic scoping<\/strong><br>The system prompt instructs Claude to refuse any off-topic question. Combined with the server-side controls above, it works reliably in practice.<\/p>\n\n\n\n<p><strong>Clean uninstall<\/strong><br>When the plugin is deleted through WordPress, it removes its own <code>wp_options<\/code> entry via <code>register_uninstall_hook<\/code>. No leftover data.<\/p>\n\n\n\n<h5 class=\"wp-block-heading\">Admin Panel<\/h5>\n\n\n\n<p>Everything is configurable from Settings \u2192 <strong>Simple WP AI Agent<\/strong>:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"#lb-2-dcfb4868\" class=\"lb-thumb\" aria-label=\"Bild vergr\u00f6\u00dfern\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"572\" src=\"https:\/\/techbygiusi.com\/wp-content\/uploads\/2026\/03\/image-1-1024x572.png\" alt=\"\" class=\"wp-image-563\" srcset=\"https:\/\/techbygiusi.com\/wp-content\/uploads\/2026\/03\/image-1-1024x572.png 1024w, https:\/\/techbygiusi.com\/wp-content\/uploads\/2026\/03\/image-1-300x168.png 300w, https:\/\/techbygiusi.com\/wp-content\/uploads\/2026\/03\/image-1-768x429.png 768w, https:\/\/techbygiusi.com\/wp-content\/uploads\/2026\/03\/image-1-1536x858.png 1536w, https:\/\/techbygiusi.com\/wp-content\/uploads\/2026\/03\/image-1.png 1758w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\"><\/a><\/figure>\n\n\n\n<p><strong>API Configuration<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>API key (stored server-side, never exposed)<\/li>\n\n\n\n<li>Response language (English \/ German)<\/li>\n\n\n\n<li>AI model (Haiku \/ Sonnet \/ Opus)<\/li>\n<\/ul>\n\n\n\n<p><strong>Content &amp; Knowledge<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Which post types the agent should read (posts, pages, custom types)<\/li>\n\n\n\n<li>Max number of posts to load<\/li>\n\n\n\n<li>Free-text extra context for things not published on the site (homelab specs, contact details, anything you want the agent to know)<\/li>\n<\/ul>\n\n\n\n<p><strong>Widget Settings<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Bot name. The header shows \u201cAsk [name]!\u201d<\/li>\n\n\n\n<li>Position (bottom right or left)<\/li>\n\n\n\n<li>Rate limit per IP per hour<\/li>\n\n\n\n<li>Welcome message, input placeholder, quick question chips<\/li>\n<\/ul>\n\n\n\n<p><strong>Colors<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Full color picker for every element: accent, text, background, bot message background, border, muted text<\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"#lb-3-97355029\" class=\"lb-thumb\" aria-label=\"Bild vergr\u00f6\u00dfern\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"852\" src=\"https:\/\/techbygiusi.com\/wp-content\/uploads\/2026\/03\/image-4-1024x852.png\" alt=\"\" class=\"wp-image-577\" srcset=\"https:\/\/techbygiusi.com\/wp-content\/uploads\/2026\/03\/image-4-1024x852.png 1024w, https:\/\/techbygiusi.com\/wp-content\/uploads\/2026\/03\/image-4-300x250.png 300w, https:\/\/techbygiusi.com\/wp-content\/uploads\/2026\/03\/image-4-768x639.png 768w, https:\/\/techbygiusi.com\/wp-content\/uploads\/2026\/03\/image-4.png 1396w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\"><\/a><\/figure>\n\n\n\n<p><strong>Import \/ Export<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Export all settings as a <code>config.json<\/code> file. Useful for backups or migrating between sites<\/li>\n\n\n\n<li>Import a previously exported file to restore everything in one click<\/li>\n\n\n\n<li>Note: the export includes the API key, so keep the file private<\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"#lb-4-06e5be8a\" class=\"lb-thumb\" aria-label=\"Bild vergr\u00f6\u00dfern\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"288\" src=\"https:\/\/techbygiusi.com\/wp-content\/uploads\/2026\/03\/image-3-1024x288.png\" alt=\"\" class=\"wp-image-565\" srcset=\"https:\/\/techbygiusi.com\/wp-content\/uploads\/2026\/03\/image-3-1024x288.png 1024w, https:\/\/techbygiusi.com\/wp-content\/uploads\/2026\/03\/image-3-300x84.png 300w, https:\/\/techbygiusi.com\/wp-content\/uploads\/2026\/03\/image-3-768x216.png 768w, https:\/\/techbygiusi.com\/wp-content\/uploads\/2026\/03\/image-3-1536x432.png 1536w, https:\/\/techbygiusi.com\/wp-content\/uploads\/2026\/03\/image-3.png 1774w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\"><\/a><\/figure>\n\n\n\n<h5 class=\"wp-block-heading\">Model Selection &amp; Cost<\/h5>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Model<\/th><th>Input<\/th><th>Output<\/th><th>Best for<\/th><\/tr><\/thead><tbody><tr><td>Haiku 4.5 <em>(default)<\/em><\/td><td>$1\/MTok<\/td><td>$5\/MTok<\/td><td>Personal sites, high volume<\/td><\/tr><tr><td>Sonnet 4.5<\/td><td>$3\/MTok<\/td><td>$15\/MTok<\/td><td>Balanced, better reasoning<\/td><\/tr><tr><td>Sonnet 4.6<\/td><td>$3\/MTok<\/td><td>$15\/MTok<\/td><td>Latest balanced model<\/td><\/tr><tr><td>Opus 4.5<\/td><td>$5\/MTok<\/td><td>$25\/MTok<\/td><td>Maximum capability<\/td><\/tr><tr><td>Opus 4.6<\/td><td>$5\/MTok<\/td><td>$25\/MTok<\/td><td>Latest, most capable<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>At ~10 chats per day with Haiku, you\u2019re looking at well under $0.05\/month. The $5 in free credits Anthropic gives you when signing up lasts months on a personal blog.<\/p>\n\n\n\n<h5 class=\"wp-block-heading\">Installation<\/h5>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Download the ZIP below<\/li>\n\n\n\n<li>WordPress Admin \u2192 Plugins \u2192 Add New \u2192 Upload ZIP \u2192 Activate<\/li>\n\n\n\n<li>Settings \u2192 <strong>Simple WP AI Agent<\/strong><\/li>\n\n\n\n<li>Get an API key at <a href=\"https:\/\/console.anthropic.com\">console.anthropic.com<\/a> \u2013 free to sign up, $5 starting credits<\/li>\n\n\n\n<li>Paste the key, select your post types, adjust the widget to match your site<\/li>\n\n\n\n<li>Save. The chat button appears on every page<\/li>\n<\/ol>\n\n\n\n<h5 class=\"wp-block-heading\" id=\"download\">Download<\/h5>\n\n\n\n<p>The plugin is free. No hardcoded content, no tracking, no external dependencies beyond the Anthropic API. Works on any WordPress site.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p><strong><a href=\"https:\/\/techbygiusi.com\/wp-content\/uploads\/2026\/03\/simple-wp-ai-agent.zip\">\u2b07 Download Simple WP \u2013 AI Agent (.zip)<\/a><\/strong><\/p>\n<\/blockquote>\n\n\n\n<p>Upload the ZIP to your WordPress, enter your own API key, set your bot name, pick your colors, add some context about your site. Done.<\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>A while ago I started thinking about adding a small AI assistant to this website \u2014 something visitors could ask about my projects, my homelab setup, or my blog posts. Not a generic chatbot, but something that strictly knows only this website and nothing else. What followed was a weekend of building a custom WordPress [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[15],"tags":[33,32,31],"class_list":["post-550","post","type-post","status-publish","format-standard","hentry","category-guide","tag-ai","tag-automation","tag-wordpress"],"_links":{"self":[{"href":"https:\/\/techbygiusi.com\/index.php\/wp-json\/wp\/v2\/posts\/550","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/techbygiusi.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/techbygiusi.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/techbygiusi.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/techbygiusi.com\/index.php\/wp-json\/wp\/v2\/comments?post=550"}],"version-history":[{"count":12,"href":"https:\/\/techbygiusi.com\/index.php\/wp-json\/wp\/v2\/posts\/550\/revisions"}],"predecessor-version":[{"id":588,"href":"https:\/\/techbygiusi.com\/index.php\/wp-json\/wp\/v2\/posts\/550\/revisions\/588"}],"wp:attachment":[{"href":"https:\/\/techbygiusi.com\/index.php\/wp-json\/wp\/v2\/media?parent=550"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/techbygiusi.com\/index.php\/wp-json\/wp\/v2\/categories?post=550"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/techbygiusi.com\/index.php\/wp-json\/wp\/v2\/tags?post=550"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}