Back to SDKs
PHP

PHP SDK Guide

A complete, runnable example for integrating Zywrap's V1 offline data (including dynamic schemas) into your PHP application.

Option 1: Cloud Proxy (Quick Start)

Don't want to host the offline data yourself? Use our official Composer package to connect directly to the Zywrap Cloud Proxy in seconds.

bash
composer require zywrapai/zywrap-php
php
<?php

require 'vendor/autoload.php';

use Zywrap\Zywrap;

// Initialize the client (verifySsl bypassed for local testing)
$zywrap = new Zywrap('YOUR_API_KEY', ['verifySsl' => false]);

try {
    $response = $zywrap->execute([
        'wrapperCodes' => ['mc_seo_meta_titles_descriptions_base'],
        'variables' => [
            'pageTopic' => "AI project management software",
            'pageIntent' => "Compare software options",
            'primaryKeyword' => "agency project management",
            'brandVoice' => "clear and trustworthy"
        ],
        'model' => 'openai-gpt-5.4'
    ]);

    print_r($response['data']);
    
} catch (\Exception $e) {
    echo "Execution failed: " . $e->getMessage();
}
OR

Option 2: Advanced Local Database Sync

Self-host the wrapper data to ensure zero-latency lookups, complete offline capability, and highly customized API integrations.

Step 1: Download Your Data Bundle

Download the V1 ZIP file containing the highly compressed Tabular JSON data. Unzip the zywrap-data.json file from the bundle to use in the import script.

You must be logged in to download the SDK data bundle.

Step 2: Database Setup

Run this SQL. It now features the relational use_cases table containing the dynamic schema_data.

sql

-- Database Schema for Zywrap Offline SDK V1 (MySQL/MariaDB)

CREATE TABLE `ai_models` (
  `code` varchar(255) NOT NULL,
  `name` varchar(255) NOT NULL,
  `status` tinyint(1) DEFAULT 1,
  `ordering` int(11) DEFAULT NULL,
  PRIMARY KEY (`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `categories` (
  `code` varchar(255) NOT NULL,
  `name` varchar(255) NOT NULL,
  `status` tinyint(1) DEFAULT 1,
  `ordering` int(11) DEFAULT NULL,
  PRIMARY KEY (`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `languages` (
  `code` varchar(10) NOT NULL,
  `name` varchar(255) NOT NULL,
  `status` tinyint(1) DEFAULT 1,
  `ordering` int(11) DEFAULT NULL,
  PRIMARY KEY (`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `use_cases` (
  `code` varchar(255) NOT NULL,
  `name` varchar(255) NOT NULL,
  `description` text,
  `category_code` varchar(255) DEFAULT NULL,
  `schema_data` json DEFAULT NULL,
  `status` tinyint(1) DEFAULT 1,
  `ordering` bigint DEFAULT NULL,
  PRIMARY KEY (`code`),
  KEY `category_code` (`category_code`),
  CONSTRAINT `use_cases_ibfk_1` FOREIGN KEY (`category_code`) REFERENCES `categories` (`code`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `wrappers` (
  `code` varchar(255) NOT NULL,
  `name` varchar(255) NOT NULL,
  `description` text,
  `use_case_code` varchar(255) DEFAULT NULL,
  `featured` tinyint(1) DEFAULT NULL,
  `base` tinyint(1) DEFAULT NULL,
  `status` tinyint(1) DEFAULT 1,
  `ordering` bigint DEFAULT NULL,
  PRIMARY KEY (`code`),
  KEY `use_case_code` (`use_case_code`),
  CONSTRAINT `wrappers_ibfk_1` FOREIGN KEY (`use_case_code`) REFERENCES `use_cases` (`code`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `block_templates` (
  `type` varchar(50) NOT NULL,
  `code` varchar(255) NOT NULL,
  `name` varchar(255) NOT NULL,
  `status` tinyint(1) DEFAULT 1,
  PRIMARY KEY (`type`,`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `settings` (
  `setting_key` VARCHAR(255) NOT NULL,
  `setting_value` TEXT,
  PRIMARY KEY (`setting_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `usage_logs` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `trace_id` varchar(255) DEFAULT NULL,
  `wrapper_code` varchar(255) DEFAULT NULL,
  `model_code` varchar(255) DEFAULT NULL,
  `prompt_tokens` int(11) DEFAULT 0,
  `completion_tokens` int(11) DEFAULT 0,
  `total_tokens` int(11) DEFAULT 0,
  `credits_used` bigint DEFAULT 0,
  `latency_ms` int(11) DEFAULT 0,
  `status` varchar(50) DEFAULT 'success',
  `error_message` text,
  `created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `wrapper_idx` (`wrapper_code`),
  KEY `model_idx` (`model_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Step 3: Database Connection

Save this as db.php. It will be used by all other scripts.

php

<?php
// FILE: db.php
// Replace with your actual database credentials
$host = 'localhost';
$db   = 'zywrap_db';
$user = 'root';
$pass = 'password';
$charset = 'utf8mb4';

$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
$options = [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES   => false,
];

try {
     $pdo = new PDO($dsn, $user, $pass, $options);
} catch (\PDOException $e) {
     throw new \PDOException($e->getMessage(), (int)$e->getCode());
}
?>

Step 4: Import Tabular Data

This script uses a custom extractTabular() function to instantly parse and import the compressed V1 JSON.

php

<?php
// FILE: import.php
/**
 * Zywrap V1 SDK - Tabular Data Importer
 * USAGE: php import.php
 */
ini_set('max_execution_time', '300');
ini_set('memory_limit', '512M');
require_once 'db.php';

$jsonFile = 'zywrap-data.json';
if (!file_exists($jsonFile)) die("Error: Could not find 'zywrap-data.json'.\n");

$data = json_decode(file_get_contents($jsonFile), true);
if (!$data) die("Error: Could not parse JSON data.");

// Helper to expand tabular JSON data into associative arrays
function extractTabular($tabularData) {
    if (empty($tabularData['cols']) || empty($tabularData['data'])) return [];
    $cols = $tabularData['cols'];
    $result = [];
    foreach ($tabularData['data'] as $row) {
        $result[] = array_combine($cols, $row);
    }
    return $result;
}

try {
    echo "Starting full V1 data import...\n";
    
    $pdo->exec('SET FOREIGN_KEY_CHECKS = 0;');
    $pdo->exec('TRUNCATE TABLE wrappers; TRUNCATE TABLE use_cases; TRUNCATE TABLE categories;');
    $pdo->exec('TRUNCATE TABLE languages; TRUNCATE TABLE block_templates;');
    $pdo->exec('TRUNCATE TABLE ai_models; TRUNCATE TABLE settings;');
    $pdo->exec('SET FOREIGN_KEY_CHECKS = 1;');

    // START TRANSACTION (Makes imports 100x faster)
    $pdo->beginTransaction();
    echo "Clearing tables...\n";

    // 1. Categories
    if (isset($data['categories'])) {
        $stmt = $pdo->prepare("INSERT INTO categories (code, name, status, ordering) VALUES (?, ?, 1, ?)");
        foreach (extractTabular($data['categories']) as $c) {
            $stmt->execute([$c['code'], $c['name'], $c['ordering'] ?? 99999]);
        }
        echo "Categories imported successfully.\n";
    }
    
    // 2. Use Cases 
    if (isset($data['useCases'])) {
        $stmt = $pdo->prepare("INSERT INTO use_cases (code, name, description, category_code, schema_data, status, ordering) VALUES (?, ?, ?, ?, ?, 1, ?)");
        foreach (extractTabular($data['useCases']) as $uc) {
            $schemaJson = !empty($uc['schema']) ? json_encode($uc['schema']) : null;
            $stmt->execute([$uc['code'], $uc['name'], $uc['desc'], $uc['cat'], $schemaJson, $uc['ordering'] ?? 999999999]);
        }
        echo "Use Cases imported successfully.\n";
    }

    // 3. Wrappers
    if (isset($data['wrappers'])) {
        $stmt = $pdo->prepare("INSERT INTO wrappers (code, name, description, use_case_code, featured, base, status, ordering) VALUES (?, ?, ?, ?, ?, ?, 1, ?)");
        foreach (extractTabular($data['wrappers']) as $w) {
            $featured = !empty($w['featured']) ? 1 : 0;
            $base = !empty($w['base']) ? 1 : 0;
            $stmt->execute([$w['code'], $w['name'], $w['desc'], $w['usecase'], $featured, $base, $w['ordering'] ?? 999999999]);
        }
        echo "Wrappers imported successfully.\n";
    }

    // 4. Languages
    if (isset($data['languages'])) {
        $stmt = $pdo->prepare("INSERT INTO languages (code, name, status, ordering) VALUES (?, ?, 1, ?)");
        $ord = 1;
        foreach (extractTabular($data['languages']) as $l) {
            $stmt->execute([$l['code'], $l['name'], $ord++]);
        }
        echo "Languages imported successfully.\n";
    }
    
    // 5. AI Models
    if (isset($data['aiModels'])) {
        $stmt = $pdo->prepare("INSERT INTO ai_models (code, name, status, ordering) VALUES (?, ?, 1, ?)");
        foreach (extractTabular($data['aiModels']) as $m) {
            $stmt->execute([$m['code'], $m['name'], $m['ordering'] ?? 99999]);
        }
        echo "AI Models imported successfully.\n";
    }

    // 6. Block Templates 
    if (isset($data['templates'])) {
        $stmt = $pdo->prepare("INSERT INTO block_templates (type, code, name, status) VALUES (?, ?, ?, 1)");
        foreach ($data['templates'] as $type => $tabular) {
            foreach (extractTabular($tabular) as $tpl) {
                $stmt->execute([$type, $tpl['code'], $tpl['name']]);
            }
        }
        echo "Block templates imported successfully.\n";
    }

    // 7. Save Version
    if (isset($data['version'])) {
        $stmt = $pdo->prepare("INSERT INTO settings (setting_key, setting_value) VALUES ('data_version', ?) ON DUPLICATE KEY UPDATE setting_value = ?");
        $stmt->execute([$data['version'], $data['version']]);
        echo "Data version saved to settings table.\n";
    }
    
    // COMMIT TRANSACTION (Saves everything to hard drive instantly)
    $pdo->commit();

    echo "\n✅ V1 Import complete! Version: " . ($data['version'] ?? 'N/A') . "\n";

} catch (PDOException $e) {
    // If anything fails, undo all changes
    if ($pdo->inTransaction()) {
        $pdo->rollBack();
    }
    die("Database error during import: " . $e->getMessage() . "\n");
}
?>

Step 5: The Dynamic Playground

This brand new UI automatically renders "Core Inputs" and "Additional Context" fields based on the selected AI Template's schema.

Frontend: playground.html

html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Zywrap V1 Offline Playground</title>
    <style>
        :root {
            --primary: #2563eb; --primary-hover: #1d4ed8;
            --bg: #f8fafc; --card-bg: #ffffff;
            --text-main: #0f172a; --text-muted: #64748b;
            --border: #e2e8f0; --radius: 0.75rem;
        }
        * { box-sizing: border-box; margin: 0; padding: 0; }
        body { font-family: system-ui, -apple-system, sans-serif; background: var(--bg); color: var(--text-main); line-height: 1.5; padding: 2rem; }
        .container { max-width: 1200px; margin: 0 auto; display: grid; grid-template-columns: 1fr 1.8fr; gap: 2rem; }
        @media (max-width: 768px) { .container { grid-template-columns: 1fr; } }
        
        .card { background: var(--card-bg); border-radius: var(--radius); border: 1px solid var(--border); box-shadow: 0 1px 3px rgba(0,0,0,0.05); padding: 1.5rem; margin-bottom: 1.5rem; }
        .card-title { font-size: 1.25rem; font-weight: 700; margin-bottom: 1rem; color: var(--text-main); }
        
        .form-group { margin-bottom: 1rem; }
        label { display: block; font-size: 0.875rem; font-weight: 600; margin-bottom: 0.35rem; color: #334155; }
        
        .input, select, textarea { 
            width: 100%; padding: 0.625rem 0.75rem; border: 1px solid var(--border); 
            border-radius: 0.5rem; font-size: 0.875rem; background: #fff; transition: border-color 0.2s; 
        }
        .input:focus, select:focus, textarea:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px rgba(37,99,235,0.1); }
        textarea { resize: vertical; min-height: 80px; }
        
        .filters { display: flex; gap: 1rem; margin-top: 0.5rem; }
        .filter-label { font-size: 0.875rem; font-weight: normal; display: flex; align-items: center; gap: 0.3rem; color: var(--text-muted); cursor: pointer; }
        
        .grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; }
        
        .btn { 
            width: 100%; background: var(--primary); color: white; border: none; padding: 0.875rem; 
            border-radius: 0.5rem; font-weight: 600; font-size: 1rem; cursor: pointer; transition: background 0.2s; 
        }
        .btn:hover { background: var(--primary-hover); }
        .btn:disabled { opacity: 0.7; cursor: not-allowed; }
        
        .schema-section { background: #f8fafc; border: 1px solid var(--border); border-radius: 0.5rem; padding: 1rem; margin-bottom: 1rem; }
        .schema-title { font-size: 0.875rem; font-weight: 700; border-bottom: 1px solid var(--border); padding-bottom: 0.5rem; margin-bottom: 0.75rem; color: #475569; }
        
        #output { background: #f1f5f9; padding: 1.25rem; border-radius: 0.5rem; font-family: ui-monospace, monospace; font-size: 0.875rem; white-space: pre-wrap; min-height: 300px; border: 1px solid var(--border); overflow-x: auto; }
    </style>
</head>
<body>
    <div class="container">
        <div>
            <div class="card">
                <h2 class="card-title">Configuration</h2>
                
                <div class="form-group">
                    <label>1. Category</label>
                    <select id="category"><option value="">Loading...</option></select>
                </div>
                
                <div class="form-group">
                    <label>2. AI Solution</label>
                    <select id="usecase" disabled><option value="">-- Select Category First --</option></select>
                </div>

                <div class="form-group">
                    <label>3. Configuration Style</label>
                    <select id="wrapper" disabled><option value="">-- Select Solution First --</option></select>
                </div>

                <div class="form-group">
                    <label>4. AI Model</label>
                    <select id="model"><option value="">Loading...</option></select>
                </div>

                <div class="form-group">
                    <label>5. Target Language</label>
                    <select id="language"><option value="">Loading...</option></select>
                </div>
            </div>

            <div class="card">
                <h2 class="card-title" style="font-size: 1rem;">Advanced Overrides</h2>
                <div class="grid-2 overrides">
                    <div><label>Tone</label><select id="toneCode"><option value="">-- Default --</option></select></div>
                    <div><label>Style</label><select id="styleCode"><option value="">-- Default --</option></select></div>
                    <div><label>Formatting</label><select id="formatCode"><option value="">-- Default --</option></select></div>
                    <div><label>Complexity</label><select id="complexityCode"><option value="">-- Default --</option></select></div>
                    <div><label>Length</label><select id="lengthCode"><option value="">-- Default --</option></select></div>
                    <div><label>Audience</label><select id="audienceCode"><option value="">-- Default --</option></select></div>
                    <div><label>Goal</label><select id="responseGoalCode"><option value="">-- Default --</option></select></div>
                    <div><label>Output Type</label><select id="outputCode"><option value="">-- Default --</option></select></div>
                </div>
            </div>
        </div>

        <div>
            <div class="card">
                <div id="dynamic-schema-container"></div>
                
                <div class="form-group">
                    <label id="prompt-label">Prompt / Additional Context</label>
                    <textarea id="prompt" class="input" placeholder="Type your request or additional instructions here..."></textarea>
                </div>
                
                <button id="run-button" class="btn">Generate Response</button>
            </div>

            <div class="card">
                <h2 class="card-title">AI Response</h2>
                <pre id="output">Output will appear here...</pre>
            </div>
        </div>
    </div>

    <script>
        const API_ENDPOINT = 'api.php';
        
        const elements = {
            category: document.getElementById('category'),
            usecase: document.getElementById('usecase'), // 🚀 NEW
            wrapper: document.getElementById('wrapper'),
            model: document.getElementById('model'),
            language: document.getElementById('language'),
            schemaContainer: document.getElementById('dynamic-schema-container'),
            prompt: document.getElementById('prompt'),
            promptLabel: document.getElementById('prompt-label'),
            runBtn: document.getElementById('run-button'),
            output: document.getElementById('output')
        };

        async function fetchAPI(action, params = '') {
            const res = await fetch(`${API_ENDPOINT}?action=${action}&${params}`);
            return await res.json();
        }

        function populateSelect(el, data, placeholder = '-- Select --') {
            el.innerHTML = `<option value="">${placeholder}</option>`;
            data.forEach(item => {
                const opt = document.createElement('option');
                opt.value = item.code; 
                opt.textContent = item.name;
                el.appendChild(opt);
            });
            el.disabled = false;
        }

        async function init() {
            const [categories, models, langs, templates] = await Promise.all([
                fetchAPI('get_categories'),
                fetchAPI('get_ai_models'),
                fetchAPI('get_languages'),
                fetchAPI('get_block_templates')
            ]);
            
            populateSelect(elements.category, categories);
            populateSelect(elements.model, models, 'Default Model');
            populateSelect(elements.language, langs, 'English (Default)');

            const overrideMap = {
                tones: 'toneCode', styles: 'styleCode', formattings: 'formatCode',
                complexities: 'complexityCode', lengths: 'lengthCode', audienceLevels: 'audienceCode',
                responseGoals: 'responseGoalCode', outputTypes: 'outputCode'
            };
            for (const [type, elId] of Object.entries(overrideMap)) {
                if (templates[type]) populateSelect(document.getElementById(elId), templates[type], '-- Default --');
            }
        }

        // 🚀 CASCADING LOGIC 1: Category -> Load Use Cases
        elements.category.addEventListener('change', async () => {
            if (!elements.category.value) {
                elements.usecase.innerHTML = '<option value="">-- Select Category First --</option>';
                elements.usecase.disabled = true;
                elements.wrapper.innerHTML = '<option value="">-- Select Solution First --</option>';
                elements.wrapper.disabled = true;
                elements.schemaContainer.innerHTML = '';
                return;
            }
            elements.usecase.innerHTML = '<option value="">Loading...</option>';
            const useCases = await fetchAPI('get_use_cases', 'category=' + elements.category.value);
            populateSelect(elements.usecase, useCases, '-- Select a Solution --');
            
            // Reset wrapper
            elements.wrapper.innerHTML = '<option value="">-- Select Solution First --</option>';
            elements.wrapper.disabled = true;
            elements.schemaContainer.innerHTML = '';
        });

        // 🚀 CASCADING LOGIC 2: Use Case -> Load Wrappers
        elements.usecase.addEventListener('change', async () => {
            if (!elements.usecase.value) {
                elements.wrapper.innerHTML = '<option value="">-- Select Solution First --</option>';
                elements.wrapper.disabled = true;
                elements.schemaContainer.innerHTML = '';
                return;
            }
            
            elements.wrapper.innerHTML = '<option value="">Loading...</option>';
            let wrappers = await fetchAPI('get_wrappers', 'usecase=' + elements.usecase.value);
            
            // Format the labels cleanly (Base vs Variation)
            elements.wrapper.innerHTML = '<option value="">-- Select a Style --</option>';
            
            let autoSelectCode = null; // Track which wrapper to auto-select

            wrappers.forEach((w, index) => {
                const opt = document.createElement('option');
                opt.value = w.code;
                const parts = w.name.split('—');
                const displayName = w.base ? `✨ Base Template - ${parts[0].trim()}` : `↳ Variation: ${parts.length > 1 ? parts[1].trim() : w.name}`;
                opt.textContent = displayName;
                elements.wrapper.appendChild(opt);

                // Identify the Base template, or fallback to the first available variation
                if (w.base) {
                    autoSelectCode = w.code;
                } else if (index === 0 && !autoSelectCode) {
                    autoSelectCode = w.code;
                }
            });
            elements.wrapper.disabled = false;

            // 🚀 NEW: Auto-select and trigger schema load
            if (autoSelectCode) {
                elements.wrapper.value = autoSelectCode;
                elements.wrapper.dispatchEvent(new Event('change')); // Force the schema to load immediately
            }
        });

        // 🚀 CASCADING LOGIC 3: Wrapper -> Load Schema
        elements.wrapper.addEventListener('change', async () => {
            elements.schemaContainer.innerHTML = '';
            elements.promptLabel.textContent = "Prompt / Additional Context";
            if (!elements.wrapper.value) return;

            const schema = await fetchAPI('get_schema', 'wrapper=' + elements.wrapper.value);
            if (!schema || (!schema.req && !schema.opt)) return;

            let html = '';
            elements.promptLabel.textContent = "Additional Free-form Instructions";

            const buildSection = (title, data) => {
                if (!data || Object.keys(data).length === 0) return '';
                let sectionHtml = `<div class="schema-section"><h3 class="schema-title">${title}</h3><div class="grid-2">`;
                for (const [key, def] of Object.entries(data)) {
                    const isPlaceholder = def.p !== undefined ? def.p : false;
                    const defaultVal = def.d !== undefined ? def.d : '';
                    const placeholderAttr = isPlaceholder ? `placeholder="${defaultVal}"` : '';
                    const valueAttr = (!isPlaceholder && defaultVal) ? `value="${defaultVal}"` : '';
                    const label = key.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase());
                    
                    sectionHtml += `<div>
                        <label>${label}</label>
                        <input type="text" class="input schema-input" data-key="${key}" ${placeholderAttr} ${valueAttr}>
                    </div>`;
                }
                return sectionHtml + '</div></div>';
            };

            html += buildSection('Core Inputs', schema.req);
            html += buildSection('Additional Context', schema.opt);
            elements.schemaContainer.innerHTML = html;
        });

        // Execute
        elements.runBtn.addEventListener('click', async () => {
            if (!elements.wrapper.value) return alert("Please select a wrapper.");
            elements.output.textContent = 'Executing...';
            elements.runBtn.disabled = true;
            elements.runBtn.textContent = 'Generating...';
            
            // 1. Grab the exact user prompt
            let finalPrompt = elements.prompt.value.trim();
            
            // 2. Build the variables object natively
            const variables = {};
            document.querySelectorAll('.schema-input').forEach(input => {
                const val = input.value.trim();
                if (val !== '') {
                    variables[input.dataset.key] = val;
                }
            });

            // 3. Build overrides
            const overrides = {};
            document.querySelectorAll('.overrides select').forEach(sel => {
                if (sel.value) overrides[sel.id] = sel.value;
            });

            try {
                const response = await fetch(API_ENDPOINT + '?action=execute', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({
                        model: elements.model.value,
                        wrapperCode: elements.wrapper.value,
                        language: elements.language.value,
                        prompt: finalPrompt, // Only the raw text area
                        variables: variables, // Only the object (backend formats it!)
                        overrides: overrides
                    })
                });
                
                const text = await response.text();
                if (!response.ok) {
                    let errMsg = text;
                    try { errMsg = JSON.parse(text).error || errMsg; } catch(e) {}
                    throw new Error(errMsg);
                }
                
                const data = JSON.parse(text);
                elements.output.textContent = data.output || JSON.stringify(data, null, 2);
            } catch (error) {
                elements.output.textContent = 'Error: ' + error.message;
            } finally {
                elements.runBtn.disabled = false;
                elements.runBtn.textContent = 'Generate Response';
            }
        });

        init();
    </script>
</body>
</html>

Backend: api.php

php

<?php
// FILE: api.php
ini_set('max_execution_time', '300');
require 'db.php';
header('Content-Type: application/json');

$zywrapApiKey = "YOUR_ZYWRAP_API_KEY";

// --- V1 Backend Logic ---

function getCategories($pdo) {
    return $pdo->query("SELECT code, name FROM categories WHERE status = 1 ORDER BY ordering ASC")->fetchAll();
}

// 🚀 NEW: Fetch Solutions (Use Cases) by Category
function getUseCasesByCategory($pdo, $categoryCode) {
    $stmt = $pdo->prepare("
        SELECT code, name 
        FROM use_cases 
        WHERE category_code = ? AND status = 1 
        ORDER BY ordering ASC
    ");
    $stmt->execute([$categoryCode]);
    return $stmt->fetchAll();
}

// 🚀 UPDATED: Fetch Wrappers (Styles) by Use Case
function getWrappersByUseCase($pdo, $useCaseCode) {
    $stmt = $pdo->prepare("
        SELECT code, name, featured, base 
        FROM wrappers 
        WHERE use_case_code = ? AND status = 1
        ORDER BY ordering ASC
    ");
    $stmt->execute([$useCaseCode]);
    return $stmt->fetchAll();
}

function getSchemaByWrapper($pdo, $wrapperCode) {
    $stmt = $pdo->prepare("
        SELECT uc.schema_data 
        FROM use_cases uc 
        JOIN wrappers w ON w.use_case_code = uc.code 
        WHERE w.code = ?
    ");
    $stmt->execute([$wrapperCode]);
    $result = $stmt->fetchColumn();
    return $result ? json_decode($result, true) : null;
}

function getLanguages($pdo) { 
    return $pdo->query("SELECT code, name FROM languages WHERE status = 1 ORDER BY ordering ASC")->fetchAll(); 
}

function getAiModels($pdo) { 
    return $pdo->query("SELECT code, name FROM ai_models WHERE status = 1 ORDER BY ordering ASC")->fetchAll(); 
}

function getBlockTemplates($pdo) {
    $stmt = $pdo->query("SELECT type, code, name FROM block_templates WHERE status = 1 ORDER BY type, name ASC");
    $grouped = [];
    foreach ($stmt->fetchAll() as $row) {
        $grouped[$row['type']][] = ['code' => $row['code'], 'name' => $row['name']];
    }
    return $grouped;
}

// --- Execution ---
function executeZywrapProxy($apiKey, $model, $wrapperCode, $prompt, $language, $variables, $overrides) {
    $url = 'https://api.zywrap.com/v1/proxy'; 
    
    $payloadData = [
        'model' => $model, 
        'wrapperCodes' => [$wrapperCode], 
        'prompt' => $prompt,
        'variables' => $variables,
        'source' => 'php_sdk'
    ];
    if (!empty($language)) $payloadData['language'] = $language;
    if (!empty($overrides)) $payloadData = array_merge($payloadData, $overrides);

    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payloadData));
    curl_setopt($ch, CURLOPT_TIMEOUT, 300); 
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Content-Type: application/json',
        'Authorization: Bearer ' . $apiKey
    ]);

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

    if ($httpCode === 200) {
        $lines = explode("\n", $rawResponse);
        $finalJson = null;
        foreach ($lines as $line) {
            $line = trim($line);
            if (strpos($line, 'data: ') === 0) {
                $data = json_decode(substr($line, 6), true);
                if ($data && (isset($data['output']) || isset($data['error']))) $finalJson = substr($line, 6);
            }
        }
        
        $statusCode = 200;
        if ($finalJson) {
            $parsed = json_decode($finalJson, true);
            if (isset($parsed['error'])) $statusCode = 400;
        }
        
        return ['status' => $statusCode, 'response' => $finalJson ?: json_encode(['error' => 'Stream parse failed'])];
    }
    return ['status' => $httpCode, 'response' => $rawResponse];
}

// --- API Router ---
$action = $_GET['action'] ?? '';

switch ($action) {
    case 'get_categories': echo json_encode(getCategories($pdo)); break;
    case 'get_use_cases': echo json_encode(getUseCasesByCategory($pdo, $_GET['category'] ?? '')); break; // 🚀 NEW
    case 'get_wrappers': echo json_encode(getWrappersByUseCase($pdo, $_GET['usecase'] ?? '')); break; // 🚀 UPDATED
    case 'get_schema': echo json_encode(getSchemaByWrapper($pdo, $_GET['wrapper'] ?? '')); break;
    case 'get_languages': echo json_encode(getLanguages($pdo)); break;
    case 'get_ai_models': echo json_encode(getAiModels($pdo)); break;
    case 'get_block_templates': echo json_encode(getBlockTemplates($pdo)); break;
    
    case 'execute':
        $input = json_decode(file_get_contents('php://input'), true);
        
        $startTime = microtime(true);
        $result = executeZywrapProxy(
            $zywrapApiKey, 
            $input['model'] ?? null, 
            $input['wrapperCode'] ?? '',
            $input['prompt'] ?? '', 
            $input['language'] ?? null, 
            $input['variables'] ?? [],
            $input['overrides'] ?? []
        );
        $latencyMs = round((microtime(true) - $startTime) * 1000);

        // --- LOGGING ---
        try {
            $status = $result['status'] === 200 ? 'success' : 'error';
            $responseData = json_decode($result['response'], true);
            
            $traceId = $responseData['id'] ?? null;
            $promptTokens = $responseData['usage']['prompt_tokens'] ?? 0;
            $completionTokens = $responseData['usage']['completion_tokens'] ?? 0;
            $totalTokens = $responseData['usage']['total_tokens'] ?? 0;
            $creditsUsed = $responseData['cost']['credits_used'] ?? 0;
            
            $fallbackError = substr($result['response'], 0, 255) . (strlen($result['response']) > 255 ? '...' : '');
            $errorMessage = $status === 'error' ? ($responseData['error'] ?? $fallbackError) : null;

            $stmt = $pdo->prepare("INSERT INTO usage_logs (trace_id, wrapper_code, model_code, prompt_tokens, completion_tokens, total_tokens, credits_used, latency_ms, status, error_message) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
            $stmt->execute([$traceId, $input['wrapperCode'], $input['model'] ?? 'default', $promptTokens, $completionTokens, $totalTokens, $creditsUsed, $latencyMs, $status, $errorMessage]);
        } catch (Exception $e) {
            error_log("Failed to write to usage_logs: " . $e->getMessage());
        }

        http_response_code($result['status']);
        echo $result['response'];
        break;
}
?>

Step 6: Synchronize Your Data

Set this script to run on a cron job. It points to the new /v1/sync endpoint.

php

<?php
// FILE: zywrap-sync.php
/**
 * Zywrap V1 SDK - Smart Data Synchronizer
 *
 * STRATEGY: "Download & Reconcile"
 * 1. Downloads the latest full data bundle (zip) if FULL_RESET.
 * 2. Updates existing records and inserts new ones if DELTA_UPDATE.
 *
 * USAGE: php zywrap-sync.php
 */

// Increase execution time and memory limit for large data processing
ini_set('max_execution_time', '300');
ini_set('memory_limit', '512M'); // ✅ FIX: Added limit

// --- CONFIGURATION ---
$apiKey = "YOUR_ZYWRAP_API_KEY"; 
$apiUrl = 'https://api.zywrap.com/v1/sdk/v1/sync'; // V1 Sync Endpoint
require_once 'db.php'; 

// --- HELPER: UPSERT BATCH (For Delta Updates) ---
function upsertBatch(PDO $pdo, string $tableName, array $rows, array $columns, string $pk = 'code') {
    if (empty($rows)) return;
    
    $colList = implode(", ", $columns);
    $placeholders = implode(", ", array_fill(0, count($columns), "?"));
    
    $updateClause = [];
    foreach ($columns as $col) {
        if ($col !== $pk && $col !== 'type') { 
            $updateClause[] = "$col = VALUES($col)";
        }
    }
    $updateSql = implode(", ", $updateClause);
    $sql = "INSERT INTO $tableName ($colList) VALUES ($placeholders) ON DUPLICATE KEY UPDATE $updateSql";
    
    $stmt = $pdo->prepare($sql);
    $pdo->beginTransaction();
    try {
        foreach ($rows as $row) $stmt->execute($row);
        $pdo->commit();
        echo "   [+] Upserted " . count($rows) . " records into '$tableName'.\n";
    } catch (Exception $e) {
        $pdo->rollBack();
        echo "   [!] Error upserting $tableName: " . $e->getMessage() . "\n";
    }
}

// --- HELPER: DELETE BATCH ---
function deleteBatch(PDO $pdo, string $tableName, array $ids, string $pk = 'code') {
    if (empty($ids)) return;
    $pdo->beginTransaction();
    try {
        $stmt = $pdo->prepare("DELETE FROM $tableName WHERE $pk = ?");
        foreach ($ids as $id) $stmt->execute([$id]);
        $pdo->commit();
        echo "   [-] Deleted " . count($ids) . " records from '$tableName'.\n";
    } catch (Exception $e) {
        $pdo->rollBack();
        echo "   [!] Error deleting from $tableName: " . $e->getMessage() . "\n";
    }
}

// =================================================================
// MAIN LOGIC
// =================================================================

echo "--- 🚀 Starting Zywrap V1 Sync ---\n";

$stmt = $pdo->query("SELECT setting_value FROM settings WHERE setting_key = 'data_version'");
$localVersion = $stmt->fetchColumn() ?: '';
echo "🔹 Local Version: " . ($localVersion ?: 'None') . "\n";

// Call API
$url = $apiUrl . '?fromVersion=' . urlencode($localVersion);
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Accept: application/json',
    'Authorization: Bearer ' . $apiKey
]);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_TIMEOUT, 60);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($httpCode !== 200 || !$response) die("❌ API Error ($httpCode): $response\n");
$json = json_decode($response, true);
if (!$json) die("❌ Invalid JSON response.\n");

$mode = $json['mode'] ?? 'UNKNOWN';
echo "🔹 Sync Mode: $mode\n";

// --- SCENARIO A: FULL RESET ---
if ($mode === 'FULL_RESET') {
    // 🟢 1. Save it with the official name so the user recognizes it
    $zipFile = 'zywrap-data.zip'; 
    $downloadUrl = $json['wrappers']['downloadUrl'];

    echo "⬇️  Attempting automatic download from Zywrap...\n";
    
    $fp = fopen($zipFile, 'w+');
    $chDl = curl_init($downloadUrl);
    curl_setopt($chDl, CURLOPT_TIMEOUT, 300);
    curl_setopt($chDl, CURLOPT_FILE, $fp); 
    curl_setopt($chDl, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($chDl, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($chDl, CURLOPT_HTTPHEADER, ['Authorization: Bearer ' . $apiKey]);
    
    $success = curl_exec($chDl);
    $httpCodeDl = curl_getinfo($chDl, CURLINFO_HTTP_CODE);
    curl_close($chDl);
    fclose($fp);
    
    // 2. Check if the download succeeded
    if ($httpCodeDl === 200 && $success && file_exists($zipFile) && filesize($zipFile) > 0) {
        
        $mbSize = round(filesize($zipFile) / 1024 / 1024, 2);
        echo "✅ Data bundle downloaded successfully ({$mbSize} MB).\n";

        // 3. Attempt Auto-Unzip
        if (class_exists('ZipArchive')) {
            $zip = new ZipArchive;
            if ($zip->open($zipFile) === TRUE) {
                $zip->extractTo(__DIR__);
                $zip->close();
                echo "✅ Auto-unzip successful. Running import script...\n";
                @unlink($zipFile); // Clean up the zip file after successful extraction
                include 'import.php'; 
            } else {
                // If it fails due to folder permissions
                echo "⚠️ Failed to auto-unzip (Check directory permissions).\n";
                echo "\n👉 ACTION REQUIRED:\n";
                echo "   1. Please manually unzip 'zywrap-data.zip' in this folder.\n";
                echo "   2. Then run: php import.php\n";
            }
        } else {
            // 🟢 4. The graceful fallback if ZipArchive is missing!
            echo "⚠️ PHP 'ZipArchive' extension is missing. Auto-unzip skipped.\n";
            echo "\n👉 ACTION REQUIRED:\n";
            echo "   1. Please manually extract the 'zywrap-data.zip' file into this folder.\n";
            echo "   2. Once extracted, run this command to update your database:\n";
            echo "      php import.php\n";
        }
    } else {
        echo "❌ Automatic download failed. HTTP Status: $httpCodeDl\n";
        @unlink($zipFile); // Clean up broken/empty file
    }
}
// --- SCENARIO B: DELTA UPDATE ---
elseif ($mode === 'DELTA_UPDATE') {
    
    // 1. Categories (Prisma uses 'position' or 'displayOrder')
    $rows = [];
    foreach(($json['metadata']['categories']??[]) as $r) {
        $status = (!isset($r['status']) || $r['status']) ? 1 : 0;
        $rows[] = [$r['code'], $r['name'], $status, $r['position'] ?? $r['displayOrder'] ?? $r['ordering'] ?? null];
    }
    upsertBatch($pdo, 'categories', $rows, ['code', 'name', 'status', 'ordering']);

    // 2. Languages (Manually mapped in backend)
    $rows = [];
    foreach(($json['metadata']['languages']??[]) as $r) {
        $status = (!isset($r['status']) || $r['status']) ? 1 : 0;
        $rows[] = [$r['code'], $r['name'], $status, $r['ordering'] ?? null];
    }
    upsertBatch($pdo, 'languages', $rows, ['code', 'name', 'status', 'ordering']);

    // 3. AI Models (Prisma uses 'providerId' and 'displayOrder')
    $rows = [];
    foreach(($json['metadata']['aiModels']??[]) as $r) {
        $status = (!isset($r['status']) || $r['status']) ? 1 : 0;
        $rows[] = [
            $r['code'], 
            $r['name'], 
            $status,
            $r['displayOrder'] ?? $r['ordering'] ?? null
        ];
    }
    upsertBatch($pdo, 'ai_models', $rows, ['code', 'name', 'status', 'ordering']);

    // 4. Templates
    $rows = [];
    if (!empty($json['metadata']['templates'])) {
        foreach ($json['metadata']['templates'] as $type => $items) {
            foreach ($items as $item) {
                $status = (!isset($item['status']) || $item['status']) ? 1 : 0;
                $rows[] = [$type, $item['code'], $item['label'] ?? $item['name'] ?? null, $status];
            }
        }
    }
    upsertBatch($pdo, 'block_templates', $rows, ['type', 'code', 'name', 'status']);

    // 5. Use Cases & Schemas
    if (!empty($json['useCases']['upserts'])) {
        $rows = [];
        foreach($json['useCases']['upserts'] as $uc) {
            $schemaJson = !empty($uc['schema']) ? json_encode($uc['schema']) : null;
            $status = (!isset($uc['status']) || $uc['status']) ? 1 : 0;
            $rows[] = [
                $uc['code'], 
                $uc['name'], 
                $uc['description'] ?? null, 
                $uc['categoryCode'] ?? null, 
                $schemaJson, 
                $status,
                $uc['displayOrder'] ?? $uc['ordering'] ?? null
            ];
        }
        upsertBatch($pdo, 'use_cases', $rows, ['code', 'name', 'description', 'category_code', 'schema_data', 'status', 'ordering']);
    }

    // 6. Wrappers
    if (!empty($json['wrappers']['upserts'])) {
        $rows = [];
        foreach($json['wrappers']['upserts'] as $w) {
            $featured = !empty($w['featured'] ?? $w['isFeatured']) ? 1 : 0;
            $base = !empty($w['base'] ?? $w['isBaseWrapper']) ? 1 : 0;
            $status = (!isset($w['status']) || $w['status']) ? 1 : 0;
            
            $rows[] = [
                $w['code'], 
                $w['name'], 
                $w['description'] ?? null, 
                $w['useCaseCode'] ?? $w['categoryCode'] ?? null, 
                $featured, 
                $base, 
                $status,
                $w['displayOrder'] ?? $w['ordering'] ?? null
            ];
        }
        upsertBatch($pdo, 'wrappers', $rows, ['code', 'name', 'description', 'use_case_code', 'featured', 'base', 'status', 'ordering']);
    }

    // 7. Deletes
    if (!empty($json['wrappers']['deletes'])) deleteBatch($pdo, 'wrappers', $json['wrappers']['deletes']);
    if (!empty($json['useCases']['deletes'])) deleteBatch($pdo, 'use_cases', $json['useCases']['deletes']);

    // 8. Update Version
    if (!empty($json['newVersion'])) {
        $pdo->prepare("INSERT INTO settings (setting_key, setting_value) VALUES ('data_version', ?) ON DUPLICATE KEY UPDATE setting_value=?")->execute([$json['newVersion'], $json['newVersion']]);
    }
    
    echo "✅ Delta Sync Complete.\n";
}
?>

Programmatically Download

Use this script to fetch the latest V1 data bundle automatically.

php

<?php
// FILE: download-bundle.php
// USAGE: php download-bundle.php

$apiKey = 'YOUR_API_KEY';
$apiUrl = 'https://api.zywrap.com/v1/sdk/v1/download'; // V1 Download URL
$outputFile = 'zywrap-data.zip';

echo "Downloading latest V1 wrapper data from Zywrap...\n";

$fp = fopen($outputFile, 'w+');
$ch = curl_init($apiUrl);
curl_setopt($ch, CURLOPT_TIMEOUT, 300);
curl_setopt($ch, CURLOPT_FILE, $fp); // Let cURL write directly to the file
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Add this to prevent SSL errors
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer ' . $apiKey,
]);

// $success will be true/false, NOT the file contents!
$success = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

// Close curl AND close the file pointer to save it to disk!
curl_close($ch);
fclose($fp);

if ($httpCode !== 200 || !$success) {
    @unlink($outputFile); // ✅ FIX: Delete corrupted text file masquerading as ZIP on error
    die("Error: Failed to download file. Status code: {$httpCode}\n");
}

echo "✅ Sync complete. Data saved to {$outputFile}.\n";
echo "Run 'unzip {$outputFile}' to extract the data, then run 'php import.php'.\n";
?>
Ready to ship?

Start building with Zywrap today.

Stop wrestling with prompts. Integrate powerful, structured AI into your applications in minutes.