Back to SDKs
Node.js

Node.js SDK Guide

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

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 features the relational use_cases table containing the dynamic schema_data.

sql

-- Database Schema for Zywrap Offline SDK v1.0 (PostgreSQL)

CREATE TABLE "ai_models" (
  "code" VARCHAR(255) PRIMARY KEY,
  "name" VARCHAR(255) NOT NULL,
  "status" BOOLEAN DEFAULT TRUE,
  "ordering" INT
);

CREATE TABLE "categories" (
  "code" VARCHAR(255) PRIMARY KEY,
  "name" VARCHAR(255) NOT NULL,
  "status" BOOLEAN DEFAULT TRUE,
  "ordering" INT
);

CREATE TABLE "languages" (
  "code" VARCHAR(10) PRIMARY KEY,
  "name" VARCHAR(255) NOT NULL,
  "status" BOOLEAN DEFAULT TRUE,
  "ordering" INT
);

CREATE TABLE "use_cases" (
  "code" VARCHAR(255) PRIMARY KEY,
  "name" VARCHAR(255) NOT NULL,
  "description" TEXT,
  "category_code" VARCHAR(255) REFERENCES categories(code) ON DELETE SET NULL,
  "schema_data" JSONB,
  "status" BOOLEAN DEFAULT TRUE,
  "ordering" BIGINT
);

CREATE TABLE "wrappers" (
  "code" VARCHAR(255) PRIMARY KEY,
  "name" VARCHAR(255) NOT NULL,
  "description" TEXT,
  "use_case_code" VARCHAR(255) REFERENCES use_cases(code) ON DELETE SET NULL,
  "featured" BOOLEAN DEFAULT FALSE,
  "base" BOOLEAN DEFAULT FALSE,
  "status" BOOLEAN DEFAULT TRUE,
  "ordering" BIGINT
);

CREATE TABLE "block_templates" (
  "type" VARCHAR(50) NOT NULL,
  "code" VARCHAR(255) NOT NULL,
  "name" VARCHAR(255) NOT NULL,
  "status" BOOLEAN DEFAULT TRUE,
  PRIMARY KEY ("type", "code")
);

CREATE TABLE "settings" (
  "setting_key" VARCHAR(255) PRIMARY KEY,
  "setting_value" TEXT
);

CREATE TABLE "usage_logs" (
  "id" BIGSERIAL PRIMARY KEY,
  "trace_id" VARCHAR(255),
  "wrapper_code" VARCHAR(255),
  "model_code" VARCHAR(255),
  "prompt_tokens" INT DEFAULT 0,
  "completion_tokens" INT DEFAULT 0,
  "total_tokens" INT DEFAULT 0,
  "credits_used" BIGINT DEFAULT 0,
  "latency_ms" INT DEFAULT 0,
  "status" VARCHAR(50) DEFAULT 'success',
  "error_message" TEXT,
  "created_at" TIMESTAMPTZ DEFAULT NOW()
);

-- Indexes for performance
CREATE INDEX idx_usage_wrapper ON usage_logs(wrapper_code);
CREATE INDEX idx_usage_model ON usage_logs(model_code);
CREATE INDEX idx_use_case_cat ON use_cases(category_code);
CREATE INDEX idx_wrapper_uc ON wrappers(use_case_code);

Step 3: Database Connection

Save this as db.js. We recommend using the pg package for PostgreSQL.

javascript

// FILE: db.js
// Uses the popular 'pg' library for PostgreSQL
// npm install pg

const { Pool } = require('pg');

// Replace with your actual database credentials
const pool = new Pool({
  user: 'postgres',
  host: 'localhost',
  database: 'zywrap_db',
  password: 'password',
  port: 5432,
});

module.exports = pool;

Step 4: Import Tabular Data

This script parses the compressed V1 JSON and securely bulk-inserts it into your database.

javascript

// FILE: import.js
// USAGE: node import.js
// This script assumes you have 'zywrap-data.json' in the same directory.
// Tip: To prevent memory limits on massive JSON files, run with: node --max-old-space-size=4096 import.js

const fs = require('fs/promises');
const pool = require('./db');

// Helper to expand tabular JSON data into arrays of objects
function extractTabular(tabularData) {
    if (!tabularData || !tabularData.cols || !tabularData.data) return [];
    const cols = tabularData.cols;
    return tabularData.data.map(row => {
        const obj = {};
        cols.forEach((col, i) => obj[col] = row[i]);
        return obj;
    });
}

async function main() {
    console.log('Starting lightning-fast v1.0 data import...');

    let data;
    try {
        const jsonFile = await fs.readFile('zywrap-data.json', 'utf8');
        data = JSON.parse(jsonFile);
    } catch (e) {
        console.error('FATAL: zywrap-data.json not found or invalid.', e.message);
        process.exit(1);
    }

    const client = await pool.connect();
    try {
        await client.query('BEGIN');

        console.log('Clearing tables...');
        await client.query('TRUNCATE wrappers, use_cases, categories, languages, block_templates, ai_models, settings RESTART IDENTITY CASCADE');

        // 1. Import Categories
        if (data.categories) {
            for (const c of extractTabular(data.categories)) {
                await client.query(
                    'INSERT INTO categories (code, name, status, ordering) VALUES ($1, $2, TRUE, $3)', 
                    [c.code, c.name, c.ordering ?? 99999]
                );
            }
            console.log('Categories imported successfully.');
        }
        
        // 2. Import Use Cases
        if (data.useCases) {
            for (const uc of extractTabular(data.useCases)) {
                const schemaJson = uc.schema ? JSON.stringify(uc.schema) : null;
                await client.query(
                    'INSERT INTO use_cases (code, name, description, category_code, schema_data, status, ordering) VALUES ($1, $2, $3, $4, $5, TRUE, $6)', 
                    [uc.code, uc.name, uc.desc, uc.cat, schemaJson, uc.ordering ?? 999999999]
                );
            }
            console.log('Use Cases imported successfully.');
        }

        // 3. Import Wrappers
        if (data.wrappers) {
            for (const w of extractTabular(data.wrappers)) {
                await client.query(
                    'INSERT INTO wrappers (code, name, description, use_case_code, featured, base, status, ordering) VALUES ($1, $2, $3, $4, $5, $6, TRUE, $7)',
                    [w.code, w.name, w.desc, w.usecase, !!w.featured, !!w.base, w.ordering ?? 999999999]
                );
            }
            console.log('Wrappers imported successfully.');
        }

        // 4. Import Languages
        if (data.languages) {
            let ord = 1;
            for (const l of extractTabular(data.languages)) {
                await client.query(
                    'INSERT INTO languages (code, name, status, ordering) VALUES ($1, $2, TRUE, $3)', 
                    [l.code, l.name, ord++]
                );
            }
            console.log('Languages imported successfully.');
        }

        // 5. Import AI Models
        if (data.aiModels) {
            for (const m of extractTabular(data.aiModels)) {
                await client.query(
                    'INSERT INTO ai_models (code, name, status, ordering) VALUES ($1, $2, TRUE, $3)',
                    [m.code, m.name, m.ordering ?? 99999]
                );
            }
            console.log('AI Models imported successfully.');
        }

        // 6. Import Block Templates
        if (data.templates) {
            for (const [type, tabular] of Object.entries(data.templates)) {
                for (const tpl of extractTabular(tabular)) {
                    await client.query(
                        'INSERT INTO block_templates (type, code, name, status) VALUES ($1, $2, $3, TRUE)', 
                        [type, tpl.code, tpl.name]
                    );
                }
            }
            console.log('Block templates imported successfully.');
        }

        // 7. Store Version
        if (data.version) {
            await client.query(
                "INSERT INTO settings (setting_key, setting_value) VALUES ('data_version', $1) ON CONFLICT (setting_key) DO UPDATE SET setting_value = EXCLUDED.setting_value",
                [data.version]
            );
            console.log('Data version saved to settings table.');
        }

        await client.query('COMMIT');
        console.log(`\n✅ v1.0 Import complete! Version: ${data.version || 'N/A'}`);

    } catch (e) {
        await client.query('ROLLBACK');
        console.error('FATAL: Database error during import. Transaction rolled back.', e.message);
        process.exit(1);
    } finally {
        client.release();
    }
}

main();

Step 5: The Dynamic API & Playground

This Express server reads from your local DB, handles dynamic schemas, forwards the final request to the Zywrap API, and logs local usage automatically.

javascript

// FILE: app.js
// A simple Express server to replicate the 'api.php' V1 playground backend.
//
// REQUIREMENTS:
// npm install express pg axios cors
//
// USAGE:
// 1. Save this as 'app.js'
// 2. Run: node app.js
// 3. Open 'playground.html' in your browser.

const express = require('express');
const cors = require('cors');
const axios = require('axios');
const pool = require('./db'); 

const app = express();
const port = 3000;

app.use(cors()); 
app.use(express.json());

const ZYWRAP_API_KEY = "YOUR_ZYWRAP_API_KEY";
const ZYWRAP_PROXY_URL = 'https://api.zywrap.com/v1/proxy';

// --- Database Helper Functions ---

async function getCategories(client) {
    const { rows } = await client.query("SELECT code, name FROM categories WHERE status = TRUE ORDER BY ordering ASC");
    return rows;
}

async function getLanguages(client) {
    const { rows } = await client.query("SELECT code, name FROM languages WHERE status = TRUE ORDER BY ordering ASC");
    return rows;
}

async function getAiModels(client) {
    const { rows } = await client.query("SELECT code, name FROM ai_models WHERE status = TRUE ORDER BY ordering ASC");
    return rows;
}

async function getBlockTemplates(client) {
    const { rows } = await client.query("SELECT type, code, name FROM block_templates WHERE status = TRUE ORDER BY type, name ASC");
    const grouped = {};
    for (const row of rows) {
        if (!grouped[row.type]) grouped[row.type] = [];
        grouped[row.type].push({ code: row.code, name: row.name });
    }
    return grouped;
}

async function getWrappersByCategory(client, categoryCode) {
    const { rows } = await client.query(
        `SELECT w.code, w.name, w.featured, w.base 
         FROM wrappers w 
         JOIN use_cases uc ON w.use_case_code = uc.code 
         WHERE uc.category_code = $1 AND w.status = TRUE AND uc.status = TRUE
         ORDER BY w.ordering ASC`,
        [categoryCode]
    );
    return rows;
}

async function getSchemaByWrapper(client, wrapperCode) {
    const { rows } = await client.query(
        `SELECT uc.schema_data 
         FROM use_cases uc 
         JOIN wrappers w ON w.use_case_code = uc.code 
         WHERE w.code = $1 AND w.status = TRUE AND uc.status = TRUE`,
        [wrapperCode]
    );
    return rows.length > 0 ? rows[0].schema_data : null;
}

// ✅ HYBRID PROXY EXECUTION
async function executeZywrapProxy(apiKey, model, wrapperCode, prompt, language = null, variables = {}, overrides = {}) {
    const payloadData = {
        model,
        wrapperCodes: [wrapperCode],
        prompt,
        variables,
        source: 'node_sdk' 
    };
    
    if (language) payloadData.language = language;
    if (overrides) Object.assign(payloadData, overrides);
    
    const headers = {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${apiKey.trim()}`,
        'User-Agent': 'ZywrapNodeSDK/1.1'
    };

    try {
        const response = await axios.post(ZYWRAP_PROXY_URL, payloadData, { 
            headers,
            responseType: 'text', 
            timeout: 300000 
        });

        const lines = response.data.split('\n');
        let finalJson = null;

        for (const line of lines) {
            const trimmed = line.trim();
            if (trimmed.startsWith('data: ')) {
                const jsonStr = trimmed.substring(6);
                try {
                    const data = JSON.parse(jsonStr);
                    if (data && (data.output || data.error)) {
                        finalJson = data;
                    }
                } catch (e) { }
            }
        }
        
        let statusCode = 200;
        if (finalJson && finalJson.error) {
            statusCode = 400;
        }

        return { status: statusCode, data: finalJson || { error: 'Failed to parse streaming response from Zywrap.' } };

    } catch (error) {
        const status = error.response?.status || 500;
        let errorData = { error: error.message };

        if (error.response?.data) {
            try {
                errorData = typeof error.response.data === 'string' 
                    ? JSON.parse(error.response.data) 
                    : error.response.data;
            } catch (e) {
                errorData = { error: error.response.data };
            }
        }
        return { status, data: errorData };
    }
}

// --- API Router ---
app.all('/api', async (req, res) => {
    const client = await pool.connect();
    try {
        if (req.method === 'GET') {
            const { action, category, wrapper } = req.query;
            switch (action) {
                case 'get_categories': return res.json(await getCategories(client));
                case 'get_languages': return res.json(await getLanguages(client));
                case 'get_ai_models': return res.json(await getAiModels(client));
                case 'get_block_templates': return res.json(await getBlockTemplates(client));
                case 'get_wrappers': return res.json(await getWrappersByCategory(client, category));
                case 'get_schema': return res.json(await getSchemaByWrapper(client, wrapper));
                default: return res.status(400).json({ error: 'Invalid action' });
            }
        }

        if (req.method === 'POST') {
            const { model, wrapperCode, prompt, language, variables, overrides } = req.body;
            
            const action = req.query.action || req.body.action; 
            
            if (action === 'execute') {
                // ⏱️ Start Local Timer
                const startTime = Date.now();
                
                const { data, status } = await executeZywrapProxy(
                    ZYWRAP_API_KEY, model, wrapperCode || '', prompt, language, variables, overrides
                );
                
                // ⏱️ End Local Timer
                const latencyMs = Date.now() - startTime;

                // --- 📝 LOGGING TO LOCAL DATABASE ---
                try {
                    const statusText = status === 200 ? 'success' : 'error';
                    const traceId = data.id || null;
                    const pTokens = data.usage?.prompt_tokens || 0;
                    const cTokens = data.usage?.completion_tokens || 0;
                    const tTokens = data.usage?.total_tokens || 0;
                    const creditsUsed = data.cost?.credits_used || 0;
                    
                    const rawErrorMsg = statusText === 'error' ? (data.error || 'Unknown Error') : null;
                    const errMsgStr = typeof rawErrorMsg === 'string' ? rawErrorMsg : JSON.stringify(rawErrorMsg);
                    const errMsg = errMsgStr ? (errMsgStr.length > 255 ? errMsgStr.substring(0, 255) + '...' : errMsgStr) : null;

                    await client.query(
                        `INSERT INTO usage_logs 
                        (trace_id, wrapper_code, model_code, prompt_tokens, completion_tokens, total_tokens, credits_used, latency_ms, status, error_message) 
                        VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`,
                        [traceId, wrapperCode, model || 'default', pTokens, cTokens, tTokens, creditsUsed, latencyMs, statusText, errMsg]
                    );
                } catch (logErr) {
                    console.error('Failed to write to usage_logs:', logErr.message);
                }

                return res.status(status).json(data);
            }
            return res.status(400).json({ error: 'Invalid action' });
        }
    } catch (e) {
        console.error(e);
        res.status(500).json({ error: e.message });
    } finally {
        client.release();
    }
});

app.listen(port, () => {
    console.log(`Zywrap Node.js SDK Playground backend listening at http://localhost:${port}`);
});

Step 6: Synchronize Data

Run this script on a cron job. It features automatic unzipping for FULL resets and ON CONFLICT optimized upserts for DELTA updates.

javascript

// FILE: zywrap-sync.js
// USAGE: node zywrap-sync.js
// DEPENDENCIES: npm install axios pg adm-zip

const axios = require('axios');
const fs = require('fs');
const AdmZip = require('adm-zip');
const { execSync } = require('child_process');
const pool = require('./db'); 

// --- CONFIGURATION ---
const API_KEY = 'YOUR_ZYWRAP_API_KEY_HERE'; 
const API_URL = 'https://api.zywrap.com/v1/sdk/v1/sync';

// --- HELPER FUNCTIONS ---

async function upsertBatch(client, tableName, rows, columns, pk = 'code') {
    if (!rows.length) return;
    
    const BATCH_SIZE = 1000; 
    const colNames = columns.map(c => `"${c}"`).join(', ');
    const updateCols = columns
        .filter(c => c !== pk && c !== 'type')
        .map(c => `"${c}" = EXCLUDED."${c}"`)
        .join(', ');
    const conflictTarget = pk === 'compound_template' ? '(type, code)' : `("${pk}")`;

    for (let i = 0; i < rows.length; i += BATCH_SIZE) {
        const chunk = rows.slice(i, i + BATCH_SIZE);
        const values = [];
        const rowPlaceholders = [];
        let counter = 1;

        for (const row of chunk) {
            const rowPh = [];
            for (const cell of row) {
                rowPh.push(`$${counter++}`);
                values.push(cell);
            }
            rowPlaceholders.push(`(${rowPh.join(', ')})`);
        }

        const query = `
            INSERT INTO "${tableName}" (${colNames}) 
            VALUES ${rowPlaceholders.join(', ')}
            ON CONFLICT ${conflictTarget} 
            DO UPDATE SET ${updateCols}
        `;

        try {
            await client.query(query, values);
        } catch (e) {
            console.error(`\n   [!] Error upserting batch in ${tableName}:`, e.message);
            throw e; 
        }
    }
    console.log(`   [+] Upserted ${rows.length} records into '${tableName}'.`);
}

async function deleteBatch(client, tableName, ids, pk = 'code') {
    if (!ids.length) return;
    const BATCH_SIZE = 2000;
    for (let i = 0; i < ids.length; i += BATCH_SIZE) {
        const chunk = ids.slice(i, i + BATCH_SIZE);
        await client.query(`DELETE FROM "${tableName}" WHERE "${pk}" = ANY($1)`, [chunk]);
    }
    console.log(`   [-] Deleted ${ids.length} records from '${tableName}'.`);
}

// --- MAIN ---
(async () => {
    console.log('--- 🚀 Starting Zywrap V1 Sync ---');
    const client = await pool.connect();
    
    try {
        const verRes = await client.query("SELECT setting_value FROM settings WHERE setting_key = 'data_version'");
        const localVersion = verRes.rows[0]?.setting_value || '';
        console.log(`🔹 Local Version: ${localVersion || 'None'}`);

        const response = await axios.get(API_URL, {
            params: { fromVersion: localVersion },
            headers: { 'Authorization': `Bearer ${API_KEY}` }
        });
        
        const json = response.data;
        console.log(`🔹 Sync Mode: ${json.mode}`);

        if (json.mode === 'FULL_RESET') {
            const zipPath = 'zywrap-data.zip';
            const downloadUrl = json.wrappers.downloadUrl;

            console.log(`⬇️  Attempting automatic download from Zywrap...`);
            
            try {
                const dl = await axios({
                    url: downloadUrl,
                    method: 'GET',
                    responseType: 'arraybuffer',
                    headers: { 'Authorization': `Bearer ${API_KEY}` }
                });

                fs.writeFileSync(zipPath, dl.data);
                const mbSize = (fs.statSync(zipPath).size / (1024 * 1024)).toFixed(2);
                console.log(`✅ Data bundle downloaded successfully (${mbSize} MB).`);
                
                try {
                    console.log('📦 Attempting auto-unzip...');
                    const zip = new AdmZip(zipPath);
                    zip.extractAllTo(__dirname, true);
                    console.log('✅ Auto-unzip successful. Running import script...');
                    
                    fs.unlinkSync(zipPath);
                    
                    // Run the import script automatically
                    execSync('node import.js', { stdio: 'inherit' });
                    
                } catch (zErr) {
                    console.log("⚠️ Failed to auto-unzip (Check directory permissions).");
                    console.log("\n👉 ACTION REQUIRED:");
                    console.log(`   1. Please manually unzip '${zipPath}' in this folder.`);
                    console.log("   2. Then run: node import.js");
                }

            } catch (dlErr) {
                if (fs.existsSync(zipPath)) fs.unlinkSync(zipPath);
                console.log(`❌ Automatic download failed. HTTP Status: ${dlErr.response?.status || 'Unknown'}`);
            }

        } else if (json.mode === 'DELTA_UPDATE') {
            await client.query('BEGIN');
            const meta = json.metadata || {};

            // Categories
            if (meta.categories) {
                const rows = meta.categories.map(r => [r.code, r.name, r.status ?? true, r.position || r.displayOrder || r.ordering]);
                await upsertBatch(client, 'categories', rows, ['code', 'name', 'status', 'ordering']);
            }

            // Languages
            if (meta.languages) {
                const rows = meta.languages.map(r => [r.code, r.name, r.status ?? true, r.ordering]);
                await upsertBatch(client, 'languages', rows, ['code', 'name', 'status', 'ordering']);
            }

            // AI Models
            if (meta.aiModels) {
                const rows = meta.aiModels.map(r => [r.code, r.name, r.status ?? true, r.displayOrder || r.ordering]);
                await upsertBatch(client, 'ai_models', rows, ['code', 'name', 'status', 'ordering']);
            }

            // Templates
            if (meta.templates) {
                const rows = [];
                for (const [type, items] of Object.entries(meta.templates)) {
                    for (const i of items) rows.push([type, i.code, i.label || i.name, i.status ?? true]);
                }
                await upsertBatch(client, 'block_templates', rows, ['type', 'code', 'name', 'status'], 'compound_template');
            }

            // Use Cases
            if (json.useCases?.upserts?.length) {
                const rows = json.useCases.upserts.map(uc => [
                    uc.code, uc.name, uc.description, uc.categoryCode, 
                    uc.schema ? JSON.stringify(uc.schema) : null, 
                    uc.status ?? true, uc.displayOrder || uc.ordering
                ]);
                await upsertBatch(client, 'use_cases', rows, ['code', 'name', 'description', 'category_code', 'schema_data', 'status', 'ordering']);
            }

            // Wrappers
            if (json.wrappers?.upserts?.length) {
                const rows = json.wrappers.upserts.map(w => [
                    w.code, w.name, w.description, w.useCaseCode || w.categoryCode, 
                    !!(w.featured || w.isFeatured), !!(w.base || w.isBaseWrapper), 
                    w.status ?? true, w.displayOrder || w.ordering
                ]);
                await upsertBatch(client, 'wrappers', rows, ['code', 'name', 'description', 'use_case_code', 'featured', 'base', 'status', 'ordering']);
            }

            // Deletes
            if (json.wrappers?.deletes?.length) await deleteBatch(client, 'wrappers', json.wrappers.deletes);
            if (json.useCases?.deletes?.length) await deleteBatch(client, 'use_cases', json.useCases.deletes);
            
            if (json.newVersion) {
                await client.query("INSERT INTO settings (setting_key, setting_value) VALUES ('data_version', $1) ON CONFLICT (setting_key) DO UPDATE SET setting_value = EXCLUDED.setting_value", [json.newVersion]);
            }
            
            await client.query('COMMIT');
            console.log('✅ Delta Sync Complete.');
        } else {
            console.log('✅ No updates needed.');
        }

    } catch (e) {
        if (client) await client.query('ROLLBACK');
        console.error('\n❌ Sync Error:', e.message);
    } finally {
        if (client) client.release();
        await pool.end();
    }
})();

Programmatically Download

Stream the download directly to disk to prevent memory issues.

javascript

// FILE: download-bundle.js
const axios = require('axios');
const fs = require('fs');

const ZYWRAP_API_KEY = 'YOUR_API_KEY';
const API_ENDPOINT = 'https://api.zywrap.com/v1/sdk/v1/download';
const OUTPUT_FILE = './zywrap-data.zip';

async function downloadBundle() {
  console.log('Downloading latest V1 wrapper data from Zywrap...');
  
  try {
    const response = await axios.get(API_ENDPOINT, {
      headers: { 'Authorization': `Bearer ${ZYWRAP_API_KEY}` },
      responseType: 'stream'
    });

    const writer = fs.createWriteStream(OUTPUT_FILE);
    response.data.pipe(writer);

    return new Promise((resolve, reject) => {
      writer.on('finish', () => {
        console.log(`✅ Sync complete. Data saved to ${OUTPUT_FILE}.`);
        console.log("Run 'unzip zywrap-data.zip' to extract the 'zywrap-data.json' file, then run 'node import.js'.");
        resolve();
      });
      writer.on('error', reject);
      response.data.on('error', reject);
    });

  } catch (error) {
    if (fs.existsSync(OUTPUT_FILE)) fs.unlinkSync(OUTPUT_FILE);
    console.error(`Error downloading bundle: ${error.response?.status || error.message}`);
  }
}

downloadBundle();
Ready to ship?

Start building with Zywrap today.

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