# Building a Resilient AI Text Humanizer with Dual-Engine Fallback (PHP & OpenRouter

> Source: <https://dev.to/laing_lii_8c1214ffb0b60ec/building-a-resilient-ai-text-humanizer-with-dual-engine-fallback-php-openrouter-2b1>
> Published: 2026-11-04 00:19:55+00:00

If you are building an AI-powered SaaS or a side project, you already know the pain: **API downtime and rate limits.** 

Recently, I built a small, free webmaster toolbox (more on that later). One of the core features is an **AI Text Humanizer** that rewrites AI-generated text to bypass detectors like Turnitin or GPTZero. 

Since I'm bootstrapping this, I relied on free AI models (like those on OpenRouter and SiliconFlow). The problem? Free tiers get heavily rate-limited. If a user hits "Submit" and the API returns a `429 Too Many Requests`, a standard error message will immediately kill your conversion rate.

Here is how I built a seamless dual-engine fallback system using pure PHP (no heavy frameworks like Laravel, just vanilla PHP 8) to ensure 99% uptime for my users.

Instead of just calling one API endpoint, my backend logic acts as a traffic controller.

If Engine A takes too long, throws an error, or returns an empty response, the server instantly routes the exact same prompt to Engine B without the user ever knowing.

Here is the stripped-down version of my `humanize.php` logic. 

The key is setting a strict `CURLOPT_TIMEOUT`. You don't want your user staring at a loading spinner for 30 seconds while Engine A hangs.

```
php
<?php
// Prevent timeout from killing the request
set_time_limit(60);

$inputText = $_POST['text'] ?? '';

if (empty($inputText)) {
    die(json_encode(['error' => 'No text provided']));
}

// The prompt (Keep it strict so it doesn't output "Sure, here is...")
$prompt = "Rewrite the following text to sound completely human, bypassing AI detection. Do not add any conversational filler. Output ONLY the rewritten text:\n\n" . $inputText;

// --- ENGINE 1: OpenRouter ---
$response = callAIEngine('openrouter', $prompt);

// --- ENGINE 2: SiliconFlow (Fallback) ---
if ($response === false || empty(trim($response))) {
    error_log("OpenRouter failed or rate-limited. Switching to SiliconFlow.");
    $response = callAIEngine('siliconflow', $prompt);
}

if ($response === false) {
    die(json_encode(['error' => 'Our AI engines are currently overloaded. Please try again in 5 minutes.']));
}

echo json_encode(['result' => $response]);

/**
 * The core cURL request handler
 */
function callAIEngine($engine, $prompt) {
    $config = [
        'openrouter' => [
            'url' => 'https://openrouter.ai/api/v1/chat/completions',
            'key' => 'YOUR_OPENROUTER_KEY',
            'model' => 'meta-llama/llama-3-8b-instruct:free' // Example free model
        ],
        'siliconflow' => [
            'url' => 'https://api.siliconflow.cn/v1/chat/completions',
            'key' => 'YOUR_SILICONFLOW_KEY',
            'model' => 'Qwen/Qwen2.5-7B-Instruct'
        ]
    ];

    $settings = $config[$engine];

    $data = [
        'model' => $settings['model'],
        'messages' => [
            ['role' => 'user', 'content' => $prompt]
        ],
        'temperature' => 0.7
    ];

    $ch = curl_init($settings['url']);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Content-Type: application/json',
        'Authorization: Bearer ' . $settings['key']
    ]);

    // Crucial: Don't wait forever. 15 seconds max per engine.
    curl_setopt($ch, CURLOPT_TIMEOUT, 15);
    // Bypass SSL verification for cheap server environments (careful with this in prod)
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);

    $result = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($httpCode !== 200 || !$result) {
        return false;
    }

    $json = json_decode($result, true);
    return $json['choices'][0]['message']['content'] ?? false;
}

The Two Biggest Pitfalls I Hit
1. The "Sure, here is your text" problem
Free models often try to be polite. If the user copies your output, they don't want to paste "Sure, here is the humanized version:" into their essay.
Fix: I had to aggressively tweak the system prompt, but sometimes models still do it. My frontend now uses a simple Regex to strip out the first line if it starts with conversational filler.

2. The Rate Limit Trap (IP Spam)
When you offer a free AI tool, botnets will find it. They will spam your API until your free quota is dead.
Fix: I implemented a strict IP-based limit (5 free uses per day) stored in a JSON flat-file (since I'm using vanilla PHP, no Redis). It's simple but effective. You can see how I handled the UX for this in my AI Humanizer tool here.

The Result
By wrapping the API calls in this fallback logic, my server can handle sudden spikes or model timeouts gracefully. The user gets their result, and I don't get angry emails about the tool being broken.

I've bundled this AI Humanizer along with 20+ other free developer tools (WHOIS, DNS, JSON formatter, port scanners) into a single, ad-free directory at Site Tools Box.

If you are building AI wrappers, I highly recommend implementing fallback logic early. It saves you a lot of debugging headaches later.

Happy coding!
```


