---
title: "Python Image API: Django, Flask & FastAPI Guide"
description: "Integrate image generation APIs in Python projects. Works with Django, Flask, FastAPI, and scripts. Includes async patterns and error handling."
url: "https://www.imejis.io/blogs/tutorials/python-image-api-integration"
published: "2026-02-25"
---

# Python Image API: Django, Flask & FastAPI Guide

[Python](https://docs.python.org/3/) is everywhere. Web apps, data pipelines, automation scripts, machine learning projects. If you're building something in Python and need to generate images programmatically, this guide is for you. (New to image APIs? Start with our [beginner guide to generating images with an API](/blogs/tutorials/how-to-generate-images-with-api).)

I'll cover the basics first, then show framework-specific integrations for Django, Flask, and FastAPI. By the end, you'll have production-ready code you can drop into any Python project. We also have language-specific guides for [Node.js](/blogs/node-js-integration) and [PHP/Laravel](/blogs/tutorials/php-image-generation-laravel).

## The Basics: Calling the API

Let's start simple. Here's a minimal Python script that generates an image:

```python
import requests

def generate_image(template_id, data):
    response = requests.post(
        f'https://render.imejis.io/v1/{template_id}',
        headers={
            'Authorization': f'Bearer {API_KEY}',
            'Content-Type': 'application/json'
        },
        json=data
    )

    # API returns image directly
    if response.status_code == 200:
        return response.content  # Binary image data
    else:
        raise Exception(f'API error: {response.status_code}')

# Usage
image_data = generate_image('your-template-id', {
    'headline': 'Hello World',
    'subtitle': 'Generated with Python'
})

# Save to file
with open('output.png', 'wb') as f:
    f.write(image_data)
```

That's it. Send JSON data, get image bytes back. The API returns the image directly, not a URL or JSON response.

## Environment Setup

Don't hardcode credentials. Use environment variables:

```python
import os
from dotenv import load_dotenv

load_dotenv()

API_KEY = os.environ['IMEJIS_API_KEY']
TEMPLATE_ID = os.environ['IMEJIS_TEMPLATE_ID']
```

Your `.env` file:

```bash
IMEJIS_API_KEY=your_api_key_here
IMEJIS_TEMPLATE_ID=your_template_id
```

Install dependencies:

```bash
pip install requests python-dotenv
```

## Production-Ready Module

Here's a more solid implementation you can use in real projects:

```python
# image_generator.py
import os
import requests
from typing import Dict, Any, Optional
from pathlib import Path

class ImageGenerator:
    BASE_URL = 'https://render.imejis.io/v1'

    def __init__(self, api_key: Optional[str] = None):
        self.api_key = api_key or os.environ.get('IMEJIS_API_KEY')
        if not self.api_key:
            raise ValueError('API key required')

    def generate(
        self,
        template_id: str,
        data: Dict[str, Any],
        timeout: int = 30
    ) -> bytes:
        """Generate an image from a template."""
        response = requests.post(
            f'{self.BASE_URL}/{template_id}',
            headers={
                'Authorization': f'Bearer {self.api_key}',
                'Content-Type': 'application/json'
            },
            json=data,
            timeout=timeout
        )

        response.raise_for_status()
        return response.content

    def generate_to_file(
        self,
        template_id: str,
        data: Dict[str, Any],
        output_path: str | Path,
        timeout: int = 30
    ) -> Path:
        """Generate an image and save to file."""
        image_data = self.generate(template_id, data, timeout)

        output_path = Path(output_path)
        output_path.parent.mkdir(parents=True, exist_ok=True)
        output_path.write_bytes(image_data)

        return output_path

# Usage
generator = ImageGenerator()
generator.generate_to_file(
    'template-123',
    {'headline': 'My Image'},
    'output/image.png'
)
```

## Async Support with httpx

For async applications (FastAPI, async scripts):

```python
# async_generator.py
import os
import httpx
from typing import Dict, Any

class AsyncImageGenerator:
    BASE_URL = 'https://render.imejis.io/v1'

    def __init__(self, api_key: str = None):
        self.api_key = api_key or os.environ.get('IMEJIS_API_KEY')

    async def generate(
        self,
        template_id: str,
        data: Dict[str, Any],
        timeout: float = 30.0
    ) -> bytes:
        """Generate an image asynchronously."""
        async with httpx.AsyncClient() as client:
            response = await client.post(
                f'{self.BASE_URL}/{template_id}',
                headers={
                    'Authorization': f'Bearer {self.api_key}',
                    'Content-Type': 'application/json'
                },
                json=data,
                timeout=timeout
            )

            response.raise_for_status()
            return response.content

# Usage
import asyncio

async def main():
    generator = AsyncImageGenerator()
    image_data = await generator.generate(
        'template-123',
        {'headline': 'Async Image'}
    )

    with open('output.png', 'wb') as f:
        f.write(image_data)

asyncio.run(main())
```

Install httpx:

```bash
pip install httpx
```

## Django Integration

### Basic View

```python
# views.py
from django.http import HttpResponse, JsonResponse
from django.views import View
from .image_generator import ImageGenerator

class GenerateImageView(View):
    def post(self, request):
        data = json.loads(request.body)

        generator = ImageGenerator()
        image_data = generator.generate(
            settings.IMEJIS_TEMPLATE_ID,
            {
                'headline': data.get('headline', ''),
                'subtitle': data.get('subtitle', '')
            }
        )

        return HttpResponse(
            image_data,
            content_type='image/png'
        )
```

### With Django REST Framework

```python
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from django.http import HttpResponse

class ImageGeneratorView(APIView):
    def post(self, request):
        serializer = ImageRequestSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        generator = ImageGenerator()
        image_data = generator.generate(
            settings.IMEJIS_TEMPLATE_ID,
            serializer.validated_data
        )

        return HttpResponse(
            image_data,
            content_type='image/png'
        )
```

### Celery Task for Background Generation

```python
# tasks.py
from celery import shared_task
from .image_generator import ImageGenerator
from .models import GeneratedImage

@shared_task
def generate_image_task(image_id, template_id, data):
    generator = ImageGenerator()

    try:
        image_data = generator.generate(template_id, data)

        # Save to storage (S3, local, etc.)
        file_path = f'generated/{image_id}.png'
        default_storage.save(file_path, ContentFile(image_data))

        # Update database record
        GeneratedImage.objects.filter(id=image_id).update(
            status='completed',
            file_path=file_path
        )
    except Exception as e:
        GeneratedImage.objects.filter(id=image_id).update(
            status='failed',
            error=str(e)
        )
```

## Flask Integration

### Basic Route

```python
# app.py
from flask import Flask, request, Response
from image_generator import ImageGenerator

app = Flask(__name__)
generator = ImageGenerator()

@app.route('/generate', methods=['POST'])
def generate_image():
    data = request.get_json()

    image_data = generator.generate(
        app.config['IMEJIS_TEMPLATE_ID'],
        {
            'headline': data.get('headline', ''),
            'subtitle': data.get('subtitle', '')
        }
    )

    return Response(
        image_data,
        mimetype='image/png'
    )
```

### With Flask-RESTful

```python
# resources.py
from flask_restful import Resource, reqparse
from image_generator import ImageGenerator

parser = reqparse.RequestParser()
parser.add_argument('headline', required=True)
parser.add_argument('subtitle')

class ImageResource(Resource):
    def __init__(self):
        self.generator = ImageGenerator()

    def post(self):
        args = parser.parse_args()

        image_data = self.generator.generate(
            current_app.config['IMEJIS_TEMPLATE_ID'],
            args
        )

        return Response(
            image_data,
            mimetype='image/png'
        )
```

### Saving to S3

```python
import boto3
from io import BytesIO

def generate_and_upload(template_id, data, bucket, key):
    generator = ImageGenerator()
    image_data = generator.generate(template_id, data)

    s3 = boto3.client('s3')
    s3.upload_fileobj(
        BytesIO(image_data),
        bucket,
        key,
        ExtraArgs={'ContentType': 'image/png'}
    )

    return f'https://{bucket}.s3.amazonaws.com/{key}'
```

## FastAPI Integration

FastAPI's async nature makes it ideal for image generation.

### Basic Endpoint

```python
# main.py
from fastapi import FastAPI, HTTPException
from fastapi.responses import Response
from pydantic import BaseModel
from async_generator import AsyncImageGenerator

app = FastAPI()
generator = AsyncImageGenerator()

class ImageRequest(BaseModel):
    headline: str
    subtitle: str = ''

@app.post('/generate')
async def generate_image(request: ImageRequest):
    try:
        image_data = await generator.generate(
            'your-template-id',
            request.dict()
        )

        return Response(
            content=image_data,
            media_type='image/png'
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))
```

### With Background Tasks

```python
from fastapi import BackgroundTasks
from fastapi.responses import JSONResponse

@app.post('/generate-async')
async def generate_async(
    request: ImageRequest,
    background_tasks: BackgroundTasks
):
    task_id = str(uuid.uuid4())

    background_tasks.add_task(
        process_image_generation,
        task_id,
        request.dict()
    )

    return JSONResponse({
        'task_id': task_id,
        'status': 'processing'
    })

async def process_image_generation(task_id: str, data: dict):
    image_data = await generator.generate('template-id', data)

    # Save result (to DB, file storage, etc.)
    await save_result(task_id, image_data)
```

### Streaming Response

For large images or when you want to start sending data immediately:

```python
from fastapi.responses import StreamingResponse
from io import BytesIO

@app.post('/generate-stream')
async def generate_stream(request: ImageRequest):
    image_data = await generator.generate(
        'template-id',
        request.dict()
    )

    return StreamingResponse(
        BytesIO(image_data),
        media_type='image/png',
        headers={
            'Content-Disposition': 'attachment; filename=image.png'
        }
    )
```

## Batch Processing

Generate multiple images efficiently:

```python
import asyncio
from typing import List, Dict

async def generate_batch(
    generator: AsyncImageGenerator,
    template_id: str,
    items: List[Dict],
    concurrency: int = 5
) -> List[bytes]:
    """Generate multiple images with controlled concurrency."""
    semaphore = asyncio.Semaphore(concurrency)

    async def generate_one(data):
        async with semaphore:
            return await generator.generate(template_id, data)

    tasks = [generate_one(item) for item in items]
    return await asyncio.gather(*tasks)

# Usage
items = [
    {'headline': 'Image 1'},
    {'headline': 'Image 2'},
    {'headline': 'Image 3'},
]

images = await generate_batch(generator, 'template-id', items)
```

## Error Handling

Reliable error handling for production. For a deeper dive into rate limiting and caching strategies, see our [API rate limits and caching guide](/blogs/tutorials/image-api-rate-limits-caching).

```python
import requests
from requests.exceptions import RequestException, Timeout
import logging

logger = logging.getLogger(__name__)

class ImageGenerationError(Exception):
    pass

class ImageGenerator:
    def generate(self, template_id: str, data: dict, retries: int = 3) -> bytes:
        last_error = None

        for attempt in range(retries):
            try:
                response = requests.post(
                    f'{self.BASE_URL}/{template_id}',
                    headers=self._get_headers(),
                    json=data,
                    timeout=30
                )

                if response.status_code == 200:
                    return response.content

                if response.status_code == 429:
                    # Rate limited - wait and retry
                    wait_time = int(response.headers.get('Retry-After', 5))
                    logger.warning(f'Rate limited, waiting {wait_time}s')
                    time.sleep(wait_time)
                    continue

                if response.status_code >= 500:
                    # Server error - retry
                    logger.warning(f'Server error {response.status_code}, retrying')
                    time.sleep(2 ** attempt)
                    continue

                # Client error - don't retry
                raise ImageGenerationError(
                    f'API error {response.status_code}: {response.text}'
                )

            except Timeout:
                logger.warning(f'Timeout on attempt {attempt + 1}')
                last_error = 'Request timeout'
            except RequestException as e:
                logger.error(f'Request error: {e}')
                last_error = str(e)

        raise ImageGenerationError(f'Failed after {retries} attempts: {last_error}')
```

## Caching

Cache generated images to avoid redundant API calls:

```python
import hashlib
from functools import lru_cache
import redis

class CachedImageGenerator(ImageGenerator):
    def __init__(self, api_key: str, redis_url: str = None):
        super().__init__(api_key)
        self.redis = redis.from_url(redis_url) if redis_url else None

    def _cache_key(self, template_id: str, data: dict) -> str:
        content = f'{template_id}:{json.dumps(data, sort_keys=True)}'
        return f'image:{hashlib.md5(content.encode()).hexdigest()}'

    def generate(self, template_id: str, data: dict) -> bytes:
        if self.redis:
            cache_key = self._cache_key(template_id, data)
            cached = self.redis.get(cache_key)

            if cached:
                return cached

        image_data = super().generate(template_id, data)

        if self.redis:
            # Cache for 24 hours
            self.redis.setex(cache_key, 86400, image_data)

        return image_data
```

## Cost Analysis

| Component       | Monthly Cost |
| --------------- | ------------ |
| Imejis.io Basic | $14.99       |
| Your hosting    | Variable     |
| **Total**       | ~$15+        |

At $14.99 for 1,000 images, you're paying about 1.5 cents per image. For batch processing jobs or high-volume applications, the Pro plan ($24.99 for 10,000 images) brings the cost down to 0.25 cents each. _Pricing reflects published rates as of July 2026; check the provider's site for current plans._

Check [Imejis.io pricing](/pricing) for exact rates.

## Getting Started

Here's your path:

1. **Install dependencies:** `pip install requests python-dotenv`
2. **Get API credentials** from [Imejis.io](https://www.imejis.io)
3. **Create a simple test script** using the basic example
4. **Integrate into your framework** (Django, Flask, or FastAPI)
5. **Add error handling and caching** for production

Start with the minimal example. Once that works, add the complexity your project needs.

Python makes image generation easy. A few lines of code, and you're generating dynamic images programmatically. Start with the free tier and scale up as your needs grow.

[Get your Imejis.io API key](https://www.imejis.io)

## FAQ

### Which HTTP library should I use?

For synchronous code, `requests` is the standard choice. For async applications (FastAPI, async scripts), use `httpx`. Both work well with the API.

### How do I handle large batch jobs?

Use async with controlled concurrency. The `generate_batch` example above processes multiple images in parallel while respecting rate limits. For very large jobs (10,000+ images), consider queuing with Celery or similar. See also our [batch image generation from CSV](/blogs/tutorials/batch-image-generation-csv) tutorial.

### Can I use this with Jupyter notebooks?

Yes. The basic examples work directly in notebooks. For async code in Jupyter, use `nest_asyncio` to allow async calls in the notebook environment.

### What's the maximum image size I can generate?

Imejis.io supports images up to 4096x4096 pixels. The response size depends on image dimensions and complexity. Most images are under 1MB.

### How do I upload generated images to cloud storage?

Use boto3 for S3, google-cloud-storage for GCS, or azure-storage-blob for Azure. All accept bytes directly, so just pass the API response to the upload function.
