Django icon

Django

Put the render call behind a Django view and your API key never reaches the browser. Imejis returns the finished image directly in the response, so the handler is a single upstream request, with nothing to poll and no webhook to receive.

Set two environment variables first: IMEJIS_API_KEY (from your dashboard) and IMEJIS_DESIGN_ID (any design's id). The fields you override are component.property pairs: headline.text, title.color, image.image; only what you send changes, and a design's overridable fields are listed in its Share / API panel.

Each handler below comes from a real Django app that is booted in Docker and called end to end before this page ships.

An endpoint that returns the generated imageAn endpoint that returns the generated image

A route in your app that renders on demand and streams the image straight back, so your render key never reaches the browser. This is the shape to use behind an <img src> on a page you control. ?format= picks png, jpeg, webp or pdf, and the route answers with the content type that matches it. A PDF labelled image/png is a file the browser, the email client and Slack all refuse to open.

Django: An endpoint that returns the generated image
import os
 
import requests
from django.http import HttpResponse, JsonResponse
 
API_KEY = os.environ["IMEJIS_API_KEY"]
DESIGN_ID = os.environ["IMEJIS_DESIGN_ID"]
BASE = os.environ.get("IMEJIS_RENDER_URL", "https://render.imejis.io")
HEADERS = {"dma-api-key": API_KEY, "Content-Type": "application/json"}
 
# The formats the render API accepts. The routes below pass `?format=` through
# when one is asked for; with none, the render host's default (jpeg) applies.
FORMATS = ("png", "jpeg", "webp", "pdf")
 
# Render on demand and stream the image straight back. The render key stays on
# the server, so it never reaches the browser — point an <img src> at this.
#
# `?format=` decides what comes back — png · jpeg · webp · pdf — and the
# response carries whatever type the render API set for it. Pass that through
# instead of hardcoding one: a PDF labelled `image/png` is a file the browser,
# the email client and Slack all refuse to open.
 
 
def og_image(request):
    requested = request.GET.get("format", "")
    params = {"format": requested} if requested in FORMATS else {}
 
    upstream = requests.post(
        f"{BASE}/v1/{DESIGN_ID}",
        headers=HEADERS,
        params=params,
        json={
            "headline.text": request.GET.get("title", "Hello from Django"),
            "subtitle.text": request.GET.get("subtitle", ""),
        },
    )
    if not upstream.ok:
        return JsonResponse(
            {"error": f"render failed: {upstream.status_code}"}, status=502
        )
    response = HttpResponse(
        upstream.content,
        content_type=upstream.headers.get("content-type", "image/jpeg"),
    )
    # Renders are deterministic, so cache aggressively at the edge/CDN — and
    # every request to the render API is one metered render.
    response["Cache-Control"] = "public, immutable, no-transform, max-age=31536000"
    return response
 
 
# urls.py
# from django.urls import path
# from .views import og_image
#
# urlpatterns = [path("og", og_image)]

One endpoint every size and jpeg qualityOne endpoint, every size, and JPEG quality

A multi-size design carries several canvases: an OG image, a square post, a vertical story. ?variation=<id> picks which one the route renders, and omitting it gives the base size, so one endpoint serves every size your pages ask for. ?quality= trades file size for fidelity on JPEG only; png, webp and pdf ignore it.

Django: One endpoint, every size, and JPEG quality
import os
 
import requests
from django.http import HttpResponse, JsonResponse
 
API_KEY = os.environ["IMEJIS_API_KEY"]
DESIGN_ID = os.environ["IMEJIS_DESIGN_ID"]
BASE = os.environ.get("IMEJIS_RENDER_URL", "https://render.imejis.io")
HEADERS = {"dma-api-key": API_KEY, "Content-Type": "application/json"}
 
# The formats the render API accepts. The routes below pass `?format=` through
# when one is asked for; with none, the render host's default (jpeg) applies.
FORMATS = ("png", "jpeg", "webp", "pdf")
 
# One design, several canvases — an OG image, a square post, a vertical
# story. `?variation=<id>` picks which one you get; with none you get the
# base size. Map YOUR OWN size names to the ids (the design's Share / API
# panel lists them) so a page asks for "story", not for an id.
#
# `?quality=` trades file size for fidelity on JPEG only — png, webp and pdf
# ignore it.
SIZES = {"og": None, "story": os.environ.get("IMEJIS_VARIATION_ID", "story")}
 
 
def og_size(request):
    size = request.GET.get("size", "og")
    if size not in SIZES:
        return JsonResponse({"error": f"unknown size: {size}"}, status=404)
 
    params = {}
    if SIZES[size]:
        params["variation"] = SIZES[size]
    requested = request.GET.get("format", "")
    if requested in FORMATS:
        params["format"] = requested
    quality = request.GET.get("quality", "")
    if quality.isdigit() and 1 <= int(quality) <= 100:
        params["quality"] = quality
 
    upstream = requests.post(
        f"{BASE}/v1/{DESIGN_ID}",
        headers=HEADERS,
        params=params,
        json={"headline.text": request.GET.get("title", "Every size")},
    )
    if not upstream.ok:
        return JsonResponse(
            {"error": f"render failed: {upstream.status_code}"}, status=502
        )
    response = HttpResponse(
        upstream.content,
        content_type=upstream.headers.get("content-type", "image/jpeg"),
    )
    response["Cache-Control"] = "public, immutable, no-transform, max-age=31536000"
    return response
 
 
# urls.py
# from django.urls import path
# from .views import og_image
#
# urlpatterns = [path("og", og_image)]

An endpoint that returns a hosted or signed image urlAn endpoint that returns a hosted or signed image URL

Ask for ?delivery=hosted (a public URL) or ?delivery=signed (a private, expiring one) and hand the URL to the caller instead of the bytes, which is what you want for an email, a webhook payload, or a share card.

Django: An endpoint that returns a hosted or signed image URL
import os
 
import requests
from django.http import HttpResponse, JsonResponse
 
API_KEY = os.environ["IMEJIS_API_KEY"]
DESIGN_ID = os.environ["IMEJIS_DESIGN_ID"]
BASE = os.environ.get("IMEJIS_RENDER_URL", "https://render.imejis.io")
HEADERS = {"dma-api-key": API_KEY, "Content-Type": "application/json"}
 
# The formats the render API accepts. The routes below pass `?format=` through
# when one is asked for; with none, the render host's default (jpeg) applies.
FORMATS = ("png", "jpeg", "webp", "pdf")
 
# Ask for a stored URL instead of bytes. `hosted` is public and permanent;
# `signed` is private and expires (`expiresIn` is minutes).
def og_url(request):
    delivery = "signed" if request.GET.get("private") else "hosted"
    params = {"delivery": delivery}
    # The stored URL carries the extension of the format it was rendered in,
    # so pass the format through here too — a recipient that sniffs by
    # extension (an email client, a Slack unfurl) needs `.pdf` to say pdf.
    requested = request.GET.get("format", "")
    if requested in FORMATS:
        params["format"] = requested
    if delivery == "signed":
        params["expiresIn"] = 30
 
    upstream = requests.post(
        f"{BASE}/v1/{DESIGN_ID}",
        headers=HEADERS,
        params=params,
        json={"headline.text": request.GET.get("title", "Shared card")},
    )
    stored = upstream.json()
    return JsonResponse({
        "url": stored["url"],
        "format": stored.get("format"),
        "expiresAt": stored.get("expiresAt"),
    })
 
 
# urls.py
# from django.urls import path
# from .views import og_image
#
# urlpatterns = [path("og", og_image)]

Bulk image generation from a routeBulk image generation from a route

One POST to /bulk renders many images, a row per record or one per size, and returns a URL for each, in the order you sent them.

Django: Bulk image generation from a route
import os
 
import requests
from django.http import HttpResponse, JsonResponse
 
API_KEY = os.environ["IMEJIS_API_KEY"]
DESIGN_ID = os.environ["IMEJIS_DESIGN_ID"]
BASE = os.environ.get("IMEJIS_RENDER_URL", "https://render.imejis.io")
HEADERS = {"dma-api-key": API_KEY, "Content-Type": "application/json"}
 
# The formats the render API accepts. The routes below pass `?format=` through
# when one is asked for; with none, the render host's default (jpeg) applies.
FORMATS = ("png", "jpeg", "webp", "pdf")
 
# One POST renders many images and returns a URL for each, in the order sent —
# a row per record, or one per size.
def cards(request):
    import json
 
    rows = json.loads(request.body or b"{}").get("rows", [])
    upstream = requests.post(
        f"{BASE}/bulk",
        headers=HEADERS,
        json={
            "designId": DESIGN_ID,
            "delivery": "hosted",
            "format": "png",
            "data": [
                {"headline.text": row["name"], "subtitle.text": row["role"]}
                for row in rows
            ],
        },
    )
    bulk = upstream.json()
    return JsonResponse(
        {
            "summary": bulk["summary"],
            "urls": [item.get("url") for item in bulk["results"]],
        }
    )
 
 
# urls.py
# from django.urls import path
# from .views import og_image
#
# urlpatterns = [path("og", og_image)]

Using the image url directly in a templateUsing the image URL directly in a template

For a public design you can skip the proxy entirely and point an <img> at the render URL with the field values in the query string. Keep the proxy when the key must stay private. That is the right default.

Django: Using the image URL directly in a template
import os
 
import requests
from django.http import HttpResponse, JsonResponse
 
API_KEY = os.environ["IMEJIS_API_KEY"]
DESIGN_ID = os.environ["IMEJIS_DESIGN_ID"]
BASE = os.environ.get("IMEJIS_RENDER_URL", "https://render.imejis.io")
HEADERS = {"dma-api-key": API_KEY, "Content-Type": "application/json"}
 
# The formats the render API accepts. The routes below pass `?format=` through
# when one is asked for; with none, the render host's default (jpeg) applies.
FORMATS = ("png", "jpeg", "webp", "pdf")
 
# For a PUBLIC design you can skip the proxy and point an <img> straight at the
# render URL. Keep the proxy whenever the key must stay private — that is the
# right default, and what og_image above is for.
def card(request):
    url = requests.Request(
        "GET",
        f"{BASE}/v1/{DESIGN_ID}",
        params={
            "dma-api-key": API_KEY,
            "format": "png",
            "headline.text": request.GET.get("title", "Straight from a URL"),
        },
    ).prepare().url
    return HttpResponse(
        f'<img src="{url}" width="1200" height="630" alt="Generated card" />'
    )
 
 
# urls.py
# from django.urls import path
# from .views import og_image
#
# urlpatterns = [path("og", og_image)]

The response is the raw image (`image/jpeg` by default). Choose the format with `?format=png|jpeg|webp|pdf` (default jpeg) and set JPEG quality with `?quality=1-100`. Store the result and get a URL back with `?delivery=hosted` (or `?delivery=signed&expiresIn=60`), and render a specific size of a multi-size design with `?variation=<id>`.

ParameterValuesDefaultWhat it does
?format=png · jpeg · webp · pdfjpegOutput image format.
?quality=1–100noneCompression quality for lossy output; ignored for other formats. (JPEG only)
?delivery=stream · hosted · signedstreamstream = raw image bytes (default); hosted = store + return a public URL; signed = store + return a time-limited URL.
?expiresIn=minutes60Signed-URL lifetime in minutes (default 60, max 10080 = 7 days). (delivery=signed only)
?variation=<size id>masterWhich multi-dimension SIZE to render. Omit or use "master" for the base size; an unknown id falls back to the master. (multi-size designs only)
  • PNG (?format=png): Lossless, supports transparency. Largest file.
  • JPEG (?format=jpeg): Smaller, lossy, no transparency. Quality adjustable.
  • WebP (?format=webp): Smallest, supports transparency. Modern browsers.
  • PDF (?format=pdf): Vector page, for print and documents.

See the delivery modes and generate images with the API guides.

The same calls without a framework are on the Python page, and Flask is the shorter route-per-function version. Each view returns the whole image through HttpResponse, so it is buffered, and a render inside a request holds a worker; a busy site wants a cache or a queue in front. One render per call, 100 free a month, pricing for the tiers.