# Detect AI-Generated Images

> Detect AI-generated images with one synchronous Copyleaks API call. Submit via multipart and get an AI-vs-human summary and a pixel-level mask.

The Copyleaks [AI Image Detection](https://copyleaks.com/ai-detector/ai-image-detector) API is a powerful tool to determine if a given image was generated or partially generated by an AI. The API is synchronous, meaning you get the results in the same API call.

This guide will walk you through the process of submitting an image to the Copyleaks [AI Detector](https://copyleaks.com/ai-detector) using multipart/form-data format and understanding the results.

## Get started

<Steps>
  <Step>
    ### Before you begin
    Before you start, ensure you have the following:
    - An active Copyleaks account. If you don't have one, **[sign up for free](https://api.copyleaks.com/signup)**.
    - You can find your API key on the **[API Dashboard](https://api.copyleaks.com/dashboard)**.
  </Step>

  <Step>
    ### Installation
    <InstallSDKs />
  </Step>

  <Step>
    ### Login
    <GuideLogin />
  </Step>

  <Step>
    ### Submit for analysis
    Use the [AI Image Detector Endpoint](/reference/actions/ai-image-detector/check) to send an image for analysis. We suggest you provide a unique `scanId` for each submission.
    
    This guide uses **multipart/form-data** format, which sends the image file directly without base64 encoding overhead.

    <Tip title="Multipart vs JSON">
    Use `multipart/form-data` to send a binary image file. Use `application/json` when you need to submit image data as a base64-encoded string. Note that multipart only accepts binary files, base64-encoded images are not supported for multipart requests.
    </Tip>

    #### Image Requirements
    - **Size:** Minimum 512×512px, maximum 6000×4500px (27 megapixels)
    - **File size:** Less than 32MB
    - **Formats:** PNG, JPG, JPEG, BMP, WebP, HEIC/HEIF

    <Tip>
    For testing, set `"sandbox": true`. Sandbox mode is free and returns mock results.
    </Tip>
    
    <CodeGroup>
    ```http title="HTTP" icon="globe"
    POST https://api.copyleaks.com/v1/ai-image-detector/my-image-scan-1/check

    Headers
    Authorization: Bearer <YOUR_AUTH_TOKEN>
    Content-Type: multipart/form-data; boundary=----WebKitFormBoundary

    Body
    ------WebKitFormBoundary
    Content-Disposition: form-data; name="image"; filename="test-image.png"
    Content-Type: image/png

    [binary image data]
    ------WebKitFormBoundary
    Content-Disposition: form-data; name="filename"

    test-image.png
    ------WebKitFormBoundary
    Content-Disposition: form-data; name="sandbox"

    true
    ------WebKitFormBoundary
    Content-Disposition: form-data; name="model"

    ai-image-1-ultra
    ------WebKitFormBoundary--
    ```
      ```bash title="cURL" icon="terminal"
      curl -X POST "https://api.copyleaks.com/v1/ai-image-detector/my-image-scan-1/check" \
           -H "Authorization: Bearer <YOUR_AUTH_TOKEN>" \
           -F "image=@/path/to/test-image.png" \
           -F "filename=test-image.png" \
           -F "sandbox=true" \
           -F "model=ai-image-1-ultra"
      ```
      ```python title="Python" icon="python"
      import requests
      
      # Prepare the request
      url = 'https://api.copyleaks.com/v1/ai-image-detector/my-image-scan-1/check'
      headers = {
          'Authorization': 'Bearer YOUR_LOGIN_TOKEN'
      }
      
      # Prepare multipart form data
      with open('test-image.png', 'rb') as image_file:
          files = {
              'image': ('test-image.png', image_file, 'image/png')
          }
          data = {
              'filename': 'test-image.png',
              'sandbox': 'true',
              'model': 'ai-image-1-ultra'
          }
          
          # Send the request
          response = requests.post(url, files=files, data=data, headers=headers)
      
      result = response.json()
      print(f"AI Detection Summary: {result['summary']}")
      ```
      ```javascript title="JavaScript" icon="square-js"
      const imageFile = document.getElementById('fileInput').files[0];
      const formData = new FormData();
      
      formData.append('image', imageFile);
      formData.append('filename', imageFile.name);
      formData.append('sandbox', 'true');
      formData.append('model', 'ai-image-1-ultra');
      
      const response = await fetch('https://api.copyleaks.com/v1/ai-image-detector/my-image-scan-1/check', {
        method: 'POST',
        headers: {
          'Authorization': 'Bearer YOUR_LOGIN_TOKEN'
        },
        body: formData
      });
      
      const result = await response.json();
      console.log('AI Detection Result:', result);
      ```
      ```java title="Java" icon="java"
      import java.io.IOException;
      import java.net.URI;
      import java.net.http.HttpClient;
      import java.net.http.HttpRequest;
      import java.net.http.HttpResponse;
      import java.nio.file.Files;
      import java.nio.file.Path;
      import java.nio.file.Paths;
      import java.util.ArrayList;
      import java.util.List;

      public class AiImageDetectionMultipart {
          public static void main(String[] args) throws IOException, InterruptedException {
              String authToken = "YOUR_LOGIN_TOKEN";
              String imagePath = "path/to/your/test-image.png";
              String scanId = "my-java-scan-1";
              String boundary = "----WebKitFormBoundary" + System.currentTimeMillis();

              Path path = Paths.get(imagePath);
              byte[] imageBytes = Files.readAllBytes(path);
              String filename = "test-image.png";

              // Build multipart body
              List<byte[]> byteArrays = new ArrayList<>();
              
              // Image field
              String imagePart = "--" + boundary + "\r\n" +
                  "Content-Disposition: form-data; name=\"image\"; filename=\"" + filename + "\"\r\n" +
                  "Content-Type: image/png\r\n\r\n";
              byteArrays.add(imagePart.getBytes());
              byteArrays.add(imageBytes);
              byteArrays.add("\r\n".getBytes());
              
              // Other fields
              String otherFields = "--" + boundary + "\r\n" +
                  "Content-Disposition: form-data; name=\"filename\"\r\n\r\n" +
                  filename + "\r\n" +
                  "--" + boundary + "\r\n" +
                  "Content-Disposition: form-data; name=\"sandbox\"\r\n\r\n" +
                  "true\r\n" +
                  "--" + boundary + "\r\n" +
                  "Content-Disposition: form-data; name=\"model\"\r\n\r\n" +
                  "ai-image-1-ultra\r\n" +
                  "--" + boundary + "--\r\n";
              byteArrays.add(otherFields.getBytes());
              
              // Combine all parts
              byte[] multipartBody = combineByteArrays(byteArrays);

              HttpClient client = HttpClient.newHttpClient();
              HttpRequest request = HttpRequest.newBuilder()
                      .uri(URI.create("https://api.copyleaks.com/v1/ai-image-detector/" + scanId + "/check"))
                      .header("Authorization", "Bearer " + authToken)
                      .header("Content-Type", "multipart/form-data; boundary=" + boundary)
                      .POST(HttpRequest.BodyPublishers.ofByteArray(multipartBody))
                      .build();

              HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

              System.out.println("Status Code: " + response.statusCode());
              System.out.println("Response Body: " + response.body());
          }
          
          private static byte[] combineByteArrays(List<byte[]> arrays) {
              int totalLength = arrays.stream().mapToInt(a -> a.length).sum();
              byte[] result = new byte[totalLength];
              int offset = 0;
              for (byte[] array : arrays) {
                  System.arraycopy(array, 0, result, offset, array.length);
                  offset += array.length;
              }
              return result;
          }
      }
      ```
    </CodeGroup>
  </Step>

  <Step>
    ### Interpreting the response
    The API response contains:
    - A `summary` object with the overall percentage of AI vs. human pixels
    - A `result` object with a Run-Length Encoded (RLE) mask
    - `imageInfo` with the image dimensions and metadata (when available)
    - `scannedDocument` with scan details including credits used

    #### Understanding the RLE Mask
    Run-Length Encoding (RLE) is a compression method used to represent the AI-detected regions of the image efficiently. It provides an array of `starts` positions and `lengths` for each run of AI-detected pixels in a flattened 1D version of the image.

    You can decode this RLE data to create a binary mask. Below are implementations in different languages:

    <CodeGroup>
    ```python title="Python" icon="python"
    def decode_mask(rle_data, image_width, image_height):
        """
        Decode RLE mask data into a binary mask array.
        
        Args:
            rle_data (dict): Dictionary with 'starts' and 'lengths' arrays
            image_width (int): Width of the image in pixels
            image_height (int): Height of the image in pixels
            
        Returns:
            list: A 1D array where 1 represents AI-detected pixels
        """
        total_pixels = image_width * image_height
        mask = [0] * total_pixels
        
        starts = rle_data.get('starts', [])
        lengths = rle_data.get('lengths', [])
        
        for i in range(len(starts)):
            start = starts[i]
            length = lengths[i]
            
            for j in range(length):
                position = start + j
                if position < total_pixels:
                    mask[position] = 1
                    
        return mask
        
    # Example usage:
    # result = response.json()
    # binary_mask = decode_mask(
    #     result['result'], 
    #     result['imageInfo']['shape']['width'], 
    #     result['imageInfo']['shape']['height']
    # )
    ```
    ```javascript title="JavaScript" icon="square-js"
    function decodeMask(rleData, imageWidth, imageHeight) {
      const totalPixels = imageWidth * imageHeight;
      const mask = new Array(totalPixels).fill(0);

      const starts = rleData.starts || [];
      const lengths = rleData.lengths || [];

      for (let i = 0; i < starts.length; i++) {
        const start = starts[i];
        const length = lengths[i];

        for (let j = 0; j < length; j++) {
          const position = start + j;
          if (position < totalPixels) {
            mask[position] = 1;
          }
        }
      }
      return mask;
    }

    // Example usage:
    // const { result, imageInfo } = await response.json();
    // const binaryMask = decodeMask(result, imageInfo.shape.width, imageInfo.shape.height);
    ```
    ```java title="Java" icon="java"
    /**
     * Decodes RLE mask data into a binary mask array
     * 
     * @param rleMask The RLE mask with starts and lengths arrays
     * @param width Image width in pixels
     * @param height Image height in pixels
     * @return Binary mask where true represents AI-detected pixels
     */
    public static boolean[] decodeMask(RleMask rleMask, int width, int height) {
        int totalPixels = width * height;
        boolean[] mask = new boolean[totalPixels];
        
        if (rleMask == null || rleMask.starts() == null || rleMask.lengths() == null) {
            return mask;
        }
        
        for (int i = 0; i < rleMask.starts().length; i++) {
            int start = rleMask.starts()[i];
            int length = rleMask.lengths()[i];
            
            for (int j = 0; j < length; j++) {
                int position = start + j;
                if (position < totalPixels) {
                    mask[position] = true;
                }
            }
        }
        
        return mask;
    }
    
    // Example usage:
    // Response contains: { "result": { "starts": [0, 512...], "lengths": [256, 512...] }, "imageInfo": {...} }
    // boolean[] binaryMask = decodeMask(
    //     new RleMask(result.getJSONObject("result").getJSONArray("starts"), result.getJSONObject("result").getJSONArray("lengths")), 
    //     result.getJSONObject("imageInfo").getJSONObject("shape").getInt("width"),
    //     result.getJSONObject("imageInfo").getJSONObject("shape").getInt("height")
    // );
    ```
    </CodeGroup>

    The resulting binary mask is an array where a `1` (or `true` in Java) represents an AI-detected pixel. You can use this mask to create a visual overlay on the original image.

    #### Creating a Visual Overlay
    After decoding the RLE data, you can use the resulting mask to draw a semi-transparent overlay on the original image. Here are some examples of how to achieve this:

    <CodeGroup>
        ```python title="Python" icon="python"
        # Requires: pip install Pillow
        from PIL import Image
        import numpy as np
        def apply_overlay(image_path, mask_array, output_path):
            """
            Apply a red (1) and green (0) overlay to the image and save the result.
            Args:
                image_path (str): Path to the original image
                mask_array (np.ndarray): 2D numpy array with 1 (red) and 0 (green)
                output_path (str): Path to save the output image
            """
            height, width = mask_array.shape
            original_img = Image.open(image_path).convert('RGBA')
            overlay = Image.new('RGBA', (width, height), (0, 0, 0, 0))
            overlay_pixels = overlay.load()
            for y in range(height):
                for x in range(width):
                    if mask_array[y, x] == 1:
                        overlay_pixels[x, y] = (255, 0, 0, 120)  # Red, semi-transparent
                    else:
                        overlay_pixels[x, y] = (0, 255, 0, 120)  # Green, semi-transparent
            result_img = Image.alpha_composite(original_img, overlay)
            result_img.save(output_path)
        # Usage example:
        width = result['imageInfo']['shape']['width']
        height = result['imageInfo']['shape']['height']
        mask_array = np.array(binary_mask, dtype=np.uint8).reshape((height, width))
        apply_overlay('test-image.png', mask_array, 'output-with-overlay.png')
        ```
        ```javascript title="JavaScript" icon="square-js"
        // Assumes 'decodeMask' function from above is available
        /**
         * Creates a canvas with the original image and an overlay showing AI vs human regions
         * @param {HTMLImageElement} imageElement - The image element to overlay
         * @param {Object} rleData - The RLE mask data with starts and lengths arrays
         * @returns {HTMLCanvasElement} Canvas with the original image and overlay
         */
        function createOverlay(imageElement, rleData) {
          const canvas = document.createElement('canvas');
          const ctx = canvas.getContext('2d');
          const width = imageElement.width;
          const height = imageElement.height;
          canvas.width = width;
          canvas.height = height;

          // Draw original image
          ctx.drawImage(imageElement, 0, 0);
          
          // Get the binary mask
          const binaryMask = decodeMask(rleData, width, height);
          
          // Create an ImageData object to manipulate pixels directly
          const imageData = ctx.getImageData(0, 0, width, height);
          const data = imageData.data;
          
          // Apply overlay for each pixel
          for (let i = 0; i < binaryMask.length; i++) {
            const pixelIndex = i * 4; // RGBA data has 4 values per pixel
            
            if (binaryMask[i] === 1) {
              // AI-generated area (red overlay)
              data[pixelIndex] = data[pixelIndex] * 0.7 + 255 * 0.3; // R
              data[pixelIndex + 1] = data[pixelIndex + 1] * 0.7;     // G
              data[pixelIndex + 2] = data[pixelIndex + 2] * 0.7;     // B
              data[pixelIndex + 3] = 255;                           // A
            } else {
              // Human-generated area (green overlay)
              data[pixelIndex] = data[pixelIndex] * 0.7;             // R
              data[pixelIndex + 1] = data[pixelIndex + 1] * 0.7 + 255 * 0.3; // G
              data[pixelIndex + 2] = data[pixelIndex + 2] * 0.7;     // B
              data[pixelIndex + 3] = 255;                           // A
            }
          }
          
          // Put the modified image data back on the canvas
          ctx.putImageData(imageData, 0, 0);
          return canvas;
        }

        // Example usage:
        function displayImageWithOverlay(imagePath, apiResult) {
          // Create image element
          const img = new Image();
          img.crossOrigin = "Anonymous";
          
          // When image loads, create and display the overlay
          img.onload = () => {
            // Get image dimensions from the API result
            const width = apiResult.imageInfo.shape.width;
            const height = apiResult.imageInfo.shape.height;
            
            // Create the overlay canvas
            const canvas = createOverlay(img, apiResult.result);
            
            // Add to page and optionally download
            document.body.appendChild(canvas);
            
            // Optionally: convert to a downloadable image
            canvas.toBlob(blob => {
              const link = document.createElement('a');
              link.download = 'overlay-image.png';
              link.href = URL.createObjectURL(blob);
              link.textContent = 'Download Image with Overlay';
              document.body.appendChild(link);
            });
          };
          
          // Set the image source to load it
          img.src = imagePath;
        }
        ```
        ```java title="Java" icon="java"
        import java.awt.AlphaComposite;
        import java.awt.Color;
        import java.awt.Graphics2D;
        import java.awt.image.BufferedImage;
        import java.io.ByteArrayInputStream;
        import java.io.ByteArrayOutputStream;
        import java.io.File;
        import java.io.IOException;
        import java.nio.file.Files;
        import java.nio.file.Paths;
        import javax.imageio.ImageIO;

        // First, create a record for the RLE data:
        // For Java 16+:
        // public record RleMask(int[] starts, int[] lengths) {}
        // For earlier Java versions:
        public static class RleMask {
            private final int[] starts;
            private final int[] lengths;
            
            public RleMask(int[] starts, int[] lengths) {
                this.starts = starts;
                this.lengths = lengths;
            }
            
            public int[] starts() { return starts; }
            public int[] lengths() { return lengths; }
        }

        /**
         * Class to handle applying AI detection overlays to images
         */
        public class ImageOverlay {
            /**
             * Applies red (AI) and green (human) overlays to the image based on mask data
             * 
             * @param imagePath Path to the original image file
             * @param maskArray 2D boolean array where true represents AI-detected pixels
             * @param outputPath Path to save the output image
             * @return The overlaid image as a BufferedImage
             */
            public static BufferedImage applyOverlay(String imagePath, boolean[][] maskArray, String outputPath) 
                    throws IOException {
                // Read the original image
                BufferedImage original = ImageIO.read(new File(imagePath));
                int width = original.getWidth();
                int height = original.getHeight();
                
                // Create overlay image with transparent background
                BufferedImage overlay = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
                Graphics2D g = overlay.createGraphics();
                
                // Apply overlay for each pixel
                for (int y = 0; y < height; y++) {
                    for (int x = 0; x < width; x++) {
                        if (y < maskArray.length && x < maskArray[0].length) {
                            if (maskArray[y][x]) {
                                // AI-detected area - red overlay
                                g.setColor(new Color(255, 0, 0, 120)); // Red with 47% opacity
                            } else {
                                // Human-created area - green overlay
                                g.setColor(new Color(0, 255, 0, 120)); // Green with 47% opacity
                            }
                            g.fillRect(x, y, 1, 1);
                        }
                    }
                }
                g.dispose();
                
                // Combine original and overlay
                BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
                Graphics2D g2 = result.createGraphics();
                g2.drawImage(original, 0, 0, null);
                g2.drawImage(overlay, 0, 0, null);
                g2.dispose();
                
                // Save the resulting image
                ImageIO.write(result, "PNG", new File(outputPath));
                
                return result;
            }
            
            /**
             * Convert 1D mask array to 2D for easier processing
             * 
             * @param mask 1D boolean array representing the mask
             * @param width Image width
             * @param height Image height
             * @return 2D boolean array
             */
            public static boolean[][] convertTo2DMask(boolean[] mask, int width, int height) {
                boolean[][] result = new boolean[height][width];
                for (int i = 0; i < mask.length; i++) {
                    int y = i / width;
                    int x = i % width;
                    if (y < height && x < width) {
                        result[y][x] = mask[i];
                    }
                }
                return result;
            }
        }

        // Example usage:
        public static void main(String[] args) throws IOException {
            // Step 1: Get the API result with RLE data (simplified example)
            int[] starts = {0, 512, 1536, 2560};
            int[] lengths = {256, 512, 768, 1024};
            RleMask rleMask = new RleMask(starts, lengths);
            
            // Step 2: Decode the RLE mask
            int width = 1024;
            int height = 768;
            boolean[] binaryMask = decodeMask(rleMask, width, height);
            
            // Step 3: Convert to 2D array for easier processing
            boolean[][] mask2D = ImageOverlay.convertTo2DMask(binaryMask, width, height);
            
            // Step 4: Apply overlay and save
            String imagePath = "path/to/your/image.jpg";
            String outputPath = "output-with-overlay.png";
            ImageOverlay.applyOverlay(imagePath, mask2D, outputPath);
            
            System.out.println("Overlay applied and saved to " + outputPath);
        }
        ```
    </CodeGroup>
    
    For a complete breakdown of all fields in the response, see the [AI Image Detection Response](/reference/data-types/ai-detector/ai-image-detection-response) documentation.
  </Step>

  <Step>
    ### Summary
    You have successfully submitted an image for AI detection. You can now use the JSON response in your application to take further action based on the findings.
  </Step>
</Steps>

## Frequently asked questions

<AccordionGroup>
  <Accordion title="Is the AI image detection API synchronous?">
    Yes. You send the image to the [check endpoint](/reference/actions/ai-image-detector/check) and receive the results in the same API call, with no webhook required.
  </Accordion>
  <Accordion title="What are the image requirements?">
    Images must be between 512×512px and 6000×4500px (27 megapixels) and under 32MB. Supported formats are PNG, JPG, JPEG, BMP, WebP, and HEIC/HEIF.
  </Accordion>
  <Accordion title="Can I submit an image as base64 instead of a binary file?">
    Yes. Use `application/json` to send a base64-encoded image. The `multipart/form-data` method shown in this guide accepts only binary files, not base64.
  </Accordion>
  <Accordion title="What does the response contain?">
    A `summary` with the overall percentage of AI vs. human pixels, a `result` object with a Run-Length Encoded (RLE) mask of AI-detected regions, `imageInfo` with dimensions and metadata, and `scannedDocument` with scan details. See the [AI Image Detection Response](/reference/data-types/ai-detector/ai-image-detection-response) reference.
  </Accordion>
  <Accordion title="How do I see which parts of the image are AI-generated?">
    Decode the RLE mask into a binary mask (one value per pixel), then draw a semi-transparent overlay on the original image. This guide includes ready-to-use decode and overlay code in Python, JavaScript, and Java.
  </Accordion>
</AccordionGroup>

## Next steps
<CardGroup cols={2}>
    <Card title="API Reference" icon="code" href="/reference/actions/ai-image-detector/check">Explore the full API reference for the AI Image Detection endpoint.</Card>
    <Card title="AI Image Detector Response" icon="brackets-curly" href="/reference/data-types/ai-detector/ai-image-detection-response">Explore the full response for the AI Image Detection.</Card>
    <Card title="Performance Best Practices" icon="gauge-high" href="/concepts/performance/image-best-practices">Learn optimization strategies for performance and accuracy with image detection.</Card>
    <Card title="Testing Methodology" icon="award" href="https://copyleaks.com/ai-image-detector/testing-methodology">Learn about the accuracy and testing methodology of the AI Image Detection product.</Card>
</CardGroup>
