Python Image API Integration: Django, Flask & FastAPI Examples

Python Image API Integration: Django, Flask & FastAPI Examples

Python 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.

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.

The basics calling the apiThe Basics: Calling the API

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

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 setupEnvironment Setup

Don't hardcode credentials. Use environment variables:

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:

IMEJIS_API_KEY=your_api_key_here
IMEJIS_TEMPLATE_ID=your_template_id

Install dependencies:

pip install requests python-dotenv

Production ready moduleProduction-Ready Module

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

# 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 httpxAsync Support with httpx

For async applications (FastAPI, async scripts):

# 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:

pip install httpx

Django integrationDjango Integration

Basic viewBasic View

# 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 frameworkWith Django REST Framework

# 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 generationCelery Task for Background Generation

# 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 integrationFlask Integration

Basic routeBasic Route

# 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 restfulWith Flask-RESTful

# 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 s3Saving to S3

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 integrationFastAPI Integration

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

Basic endpointBasic Endpoint

# 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 tasksWith Background Tasks

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 responseStreaming Response

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

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 processingBatch Processing

Generate multiple images efficiently:

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 handlingError Handling

Robust error handling for production:

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}')

CachingCaching

Cache generated images to avoid redundant API calls:

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 analysisCost Analysis

ComponentMonthly Cost
Imejis.io Basic$14.99
Your hostingVariable
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.

Check Imejis.io pricing for exact rates.

Getting startedGetting Started

Here's your path:

  1. Install dependencies: pip install requests python-dotenv
  2. Get API credentials from 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.

Get your Imejis.io API key

FaqFAQ

Which http library should i useWhich 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 jobsHow 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.

Can i use this with jupyter notebooksCan 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.

Whats the maximum image size i can generateWhat'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 storageHow 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.