Back to blog
Tutorial·9 min read·1790 words

How to Detect AI-Generated Content in Your App: Developer Tutorial

Build a practical AI-generated content detection pipeline with Node.js. Learn text analysis, C2PA image provenance checking, and how to display authenticity badges in your app.

How to Detect AI-Generated Content in Your App: Developer Tutorial — illustration

The EU's AI content labeling law is now in effect, and users are more skeptical than ever about whether the content they see online is real. For developers, this creates both a compliance challenge and a product opportunity: building AI-generated content detection into your application.

This tutorial walks through building a practical AI content detection pipeline using modern AI APIs. You'll learn how to detect AI-generated text, check image provenance metadata, and build a user-facing "content authenticity" badge — all with code you can run today.

What You'll Build

By the end of this tutorial, you'll have a Node.js service that:

  1. Analyzes text to estimate whether it was AI-generated
  2. Checks images for C2PA provenance metadata
  3. Combines signals into a confidence score
  4. Returns a JSON response your frontend can display as an authenticity badge

Prerequisites

  • Node.js 18+ and npm
  • An API key for an AI model provider (we'll use Qubax AI for unified access to multiple models)
  • Basic familiarity with Express.js

Step 1: Set Up Your Project

bash
mkdir ai-content-detector && cd ai-content-detector
npm init -y
npm install express multer openai c2pa

Create your project structure:

code
ai-content-detector/
├── server.js
├── detectors/
│   ├── textDetector.js
│   ├── imageDetector.js
│   └── authenticityScorer.js
└── package.json

Step 2: Build the Text Detection Module

The most practical approach to detecting AI-generated text is using a capable LLM to analyze writing patterns. While no detector is perfect, modern models can identify common AI writing signatures with reasonable accuracy.

Create detectors/textDetector.js:

javascript
const OpenAI = require('openai');

const client = new OpenAI({
  baseURL: process.env.AI_BASE_URL || 'https://api.qubax.ai/v1',
  apiKey: process.env.AI_API_KEY,
});

async function detectAIText(text) {
  if (text.length < 50) {
    return { score: 0, confidence: 'low', reason: 'Text too short to analyze reliably' };
  }

  const systemPrompt = 'You are an expert in detecting AI-generated text. ' +
    'Analyze the following text and determine if it was likely written by an AI or a human. ' +
    'Consider: uniform sentence structure, lack of personal voice, repetitive patterns, ' +
    'overly formal tone, and absence of genuine errors or colloquialisms. ' +
    'Respond as JSON: {"ai_probability": 0-100, "confidence": "low|medium|high", ' +
    '"indicators": ["reason1", "reason2"], "explanation": "brief summary"}';

  const response = await client.chat.completions.create({
    model: process.env.DETECTION_MODEL || 'gpt-5.5',
    messages: [
      { role: 'system', content: systemPrompt },
      { role: 'user', content: text }
    ],
    response_format: { type: 'json_object' },
    temperature: 0.1
  });

  return JSON.parse(response.choices[0].message.content);
}

module.exports = { detectAIText };

Key Design Decisions

  • Temperature 0.1: We want consistent, low-creativity analysis. High temperature would make the detector itself behave unpredictably.
  • Minimum length check: Short texts are unreliable to classify. We return a low-confidence result rather than a misleading score.
  • Structured JSON output: Using response_format: { type: 'json_object' } ensures we get parseable, consistent results.

Step 3: Build the Image Provenance Checker

For images, the most reliable detection method isn't pixel analysis — it's provenance metadata. The C2PA standard (used by Adobe, Microsoft, and others) embeds cryptographically signed information about how an image was created.

Create detectors/imageDetector.js:

javascript
const { C2pa } = require('c2pa');
const fs = require('fs');

async function checkImageProvenance(imageBuffer) {
  try {
    const tempPath = '/tmp/check_' + Date.now() + '.jpg';
    fs.writeFileSync(tempPath, imageBuffer);

    const c2pa = new C2pa();
    const manifest = await c2pa.read(tempPath);
    fs.unlinkSync(tempPath);

    if (!manifest) {
      return {
        hasProvenance: false,
        isAI: 'unknown',
        reason: 'No C2PA metadata found — image origin cannot be verified'
      };
    }

    const assertions = manifest.claim?.assertions || [];
    const aiActions = assertions.filter(a =>
      a.label.includes('generative') ||
      a.label.includes('ai') ||
      a.label.includes('synthetic')
    );

    return {
      hasProvenance: true,
      isAI: aiActions.length > 0,
      tool: manifest.claim?.tool || 'unknown',
      createdAt: manifest.claim?.createdAt,
      assertions: aiActions.map(a => a.label),
      reason: aiActions.length > 0
        ? 'C2PA metadata confirms AI generation'
        : 'C2PA metadata present, no AI generation claims detected'
    };
  } catch (error) {
    return {
      hasProvenance: false,
      isAI: 'error',
      reason: 'Could not read image metadata: ' + error.message
    };
  }
}

module.exports = { checkImageProvenance };

Why Provenance Beats Pixel Analysis

Pixel-based AI image detectors (looking for artifacts, frequency patterns, etc.) have high false-positive rates and are quickly outpaced by improving image models. Provenance metadata is more reliable because:

  1. It's cryptographically signed — hard to forge
  2. It travels with the image — survives resizing and reformatting
  3. It's required by the EU — so compliant AI tools will include it

The limitation: provenance can be stripped (e.g., by screenshots). That's why we combine it with other signals.

Step 4: Build the Authenticity Scorer

Now let's combine text and image signals into a unified score.

Create detectors/authenticityScorer.js:

javascript
function calculateAuthenticityScore(textResult, imageResult) {
  let score = 100;
  const signals = [];

  if (textResult) {
    const aiProb = textResult.ai_probability || 0;
    if (aiProb > 70) {
      score -= 40;
      signals.push({ type: 'text', severity: 'high', detail: textResult.explanation });
    } else if (aiProb > 40) {
      score -= 20;
      signals.push({ type: 'text', severity: 'medium', detail: textResult.explanation });
    } else if (aiProb > 20) {
      score -= 5;
      signals.push({ type: 'text', severity: 'low', detail: textResult.explanation });
    }
  }

  if (imageResult) {
    if (imageResult.isAI === true) {
      score -= 50;
      signals.push({ type: 'image', severity: 'high', detail: 'AI-generated image detected via C2PA' });
    } else if (imageResult.isAI === 'unknown' && !imageResult.hasProvenance) {
      score -= 10;
      signals.push({ type: 'image', severity: 'low', detail: 'No provenance metadata — origin unverified' });
    }
  }

  score = Math.max(0, Math.min(100, score));

  let label;
  if (score >= 80) label = 'Likely Authentic';
  else if (score >= 50) label = 'Uncertain';
  else if (score >= 25) label = 'Likely AI-Generated';
  else label = 'AI-Generated';

  return { score, label, signals };
}

module.exports = { calculateAuthenticityScore };

Step 5: Build the API Server

Now let's wire everything together with an Express server.

Create server.js:

javascript
const express = require('express');
const multer = require('multer');
const { detectAIText } = require('./detectors/textDetector');
const { checkImageProvenance } = require('./detectors/imageDetector');
const { calculateAuthenticityScore } = require('./detectors/authenticityScorer');

const app = express();
const upload = multer({ storage: multer.memoryStorage() });
app.use(express.json({ limit: '10mb' }));

app.post('/api/analyze/text', async (req, res) => {
  try {
    const { text } = req.body;
    if (!text) return res.status(400).json({ error: 'Text required' });
    const textResult = await detectAIText(text);
    const score = calculateAuthenticityScore(textResult, null);
    res.json({ ...score, textAnalysis: textResult });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

app.post('/api/analyze/image', upload.single('image'), async (req, res) => {
  try {
    if (!req.file) return res.status(400).json({ error: 'Image file required' });
    const imageResult = await checkImageProvenance(req.file.buffer);
    const score = calculateAuthenticityScore(null, imageResult);
    res.json({ ...score, imageAnalysis: imageResult });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

app.post('/api/analyze', upload.single('image'), async (req, res) => {
  try {
    const textResult = req.body.text ? await detectAIText(req.body.text) : null;
    const imageResult = req.file ? await checkImageProvenance(req.file.buffer) : null;
    const assessment = calculateAuthenticityScore(textResult, imageResult);
    res.json({ ...assessment, ...(textResult && { textAnalysis: textResult }), ...(imageResult && { imageAnalysis: imageResult }) });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

const PORT = process.env.PORT || 3001;
app.listen(PORT, () => console.log('Content detector running on port ' + PORT));

Step 6: Test Your Detector

Start the server:

bash
export AI_API_KEY="your-qubax-api-key"
export AI_BASE_URL="https://api.qubax.ai/v1"
node server.js

Test with a text snippet:

bash
curl -X POST http://localhost:3001/api/analyze/text \
  -H "Content-Type: application/json" \
  -d '{"text": "In today'"'"'s rapidly evolving digital landscape, it is crucial to leverage cutting-edge solutions to drive innovation."}'

Expected response:

json
{
  "score": 25,
  "label": "Likely AI-Generated",
  "signals": [
    {
      "type": "text",
      "severity": "high",
      "detail": "Overly formal tone, generic corporate vocabulary, uniform sentence structure..."
    }
  ],
  "textAnalysis": {
    "ai_probability": 82,
    "confidence": "high",
    "indicators": ["generic phrasing", "lack of personal voice", "uniform structure"],
    "explanation": "Text exhibits classic AI writing patterns..."
  }
}

Step 7: Display Results in Your Frontend

Here's a React component that displays the authenticity badge:

jsx
function AuthenticityBadge({ result }) {
  const colors = {
    'Likely Authentic': { bg: '#dcfce7', text: '#166534', icon: '✓' },
    'Uncertain': { bg: '#fef3c7', text: '#92400e', icon: '?' },
    'Likely AI-Generated': { bg: '#fee2e2', text: '#991b1b', icon: '⚠' },
    'AI-Generated': { bg: '#fecaca', text: '#7f1d1d', icon: '⚠' },
  };

  const style = colors[result.label] || colors['Uncertain'];

  return (
    <div style={{
      display: 'inline-flex', alignItems: 'center', gap: '8px',
      padding: '6px 14px', borderRadius: '20px',
      backgroundColor: style.bg, color: style.text,
      fontSize: '14px', fontWeight: 600,
    }}>
      <span>{style.icon}</span>
      <span>{result.label}</span>
      <span style={{ opacity: 0.7, fontSize: '12px' }}>({result.score}% authentic)</span>
    </div>
  );
}

Limitations to Keep in Mind

No AI content detector is 100% accurate. Be transparent with your users:

  • False positives: Human writing can be flagged as AI-generated, especially formal or formulaic text
  • Evasion: Determined users can rewrite AI text to evade detection
  • Metadata stripping: Screenshots and re-uploads remove C2PA provenance
  • Language bias: Detectors are less reliable for non-English text

Always present detection results as "indicators" rather than definitive proof, and give users a way to dispute or appeal flags.

Going Further

To improve your detection system:

  1. Add watermark detection — models like SynthID embed invisible watermarks you can scan for
  2. Track edit history — if available, analyze how content was modified over time
  3. Batch analysis — analyze an author's entire body of work rather than single pieces
  4. Community signals — let users report suspected AI content as an additional signal

For production use, the Qubax AI API gives you access to multiple detection models, content provenance tools, and a unified billing layer — so you can experiment with different approaches without managing multiple API keys.

FAQ

Can AI content detection be 100% accurate?

No. Every detection method has false positives and false negatives. The best systems combine multiple signals (text analysis, provenance metadata, watermarks) and present results as probabilities, not certainties.

What is C2PA and why does it matter?

C2PA (Coalition for Content Provenance and Authenticity) is a standard for embedding cryptographically signed metadata in images and video. It records how content was created and modified. The EU's new labeling rules effectively require C2PA-style provenance.

How much does it cost to run AI content detection?

Cost depends on the model and volume. Using the Qubax AI API, text analysis typically costs less than $0.01 per document. Image provenance checking is free (it's local processing).

Should I block AI-generated content on my platform?

That depends on your platform's goals. Rather than blocking, consider labeling — let users know content may be AI-generated and let them decide. This aligns with the EU's approach.

Can I use this for academic integrity (detecting AI-written essays)?

You can, but with significant caution. False positive rates for academic text are high because formal academic writing resembles AI output. Always use detection as one signal alongside other evidence.

How do I handle false positives?

Provide an appeals process. Let users flag incorrect assessments and use that feedback to improve your detection thresholds. Transparency about your detection methods also builds trust.

Article tags

#AI detection#content authenticity#C2PA#Node.js#developer tutorial
Share:Post on XTelegramLinkedInYHacker NewsReddit
Qubax AI

Qubax AI

AI Models at up to 99% off · Pay with crypto

Access GPT, Claude, Gemini, GLM & 340+ models through one OpenAI-compatible API. Up to 99% off. Pay with 200+ cryptocurrencies. Get $1 free credits — no credit card needed.

Related articles