Skip to main content

Python SDK

The official Pictify SDK for Python provides a simple, Pythonic interface for the Pictify API — generate images, PDFs, and GIFs from raw HTML, live URLs, and reusable templates. It ships sync (Pictify) and async (AsyncPictify) clients with an identical surface.

Installation

pip install pictify

Quick Start

from pictify import Pictify

client = Pictify(api_key="your-api-key")

# Render raw HTML to a PNG
image = client.render_html(html="<div style='font-size:48px;padding:40px'>Hello World</div>")
print(image.url)

# Render a reusable template
result = client.render("XL13XACH2V", variables={"name": "Ada", "company": "Pictify"})
print(result.url)  # results[0].url

Async Usage

import asyncio
from pictify import AsyncPictify

async def main():
    async with AsyncPictify(api_key="your-api-key") as client:
        image = await client.render_html(html="<div>Hello World</div>")
        print(image.url)

asyncio.run(main())

Configuration

The client is constructed with a keyword api_key. Every request is sent with an Authorization: Bearer <API_KEY> header.
client = Pictify(
    api_key="your-api-key",
    base_url="https://api.pictify.io",  # optional: API base URL (default: https://api.pictify.io)
    timeout=30.0,                       # optional: request timeout in seconds (default: 30)
    max_retries=3,                      # optional: retries on 5xx / network errors (default: 3)
)
Keep your API key secret. Read it from an environment variable (e.g. os.environ["PICTIFY_API_KEY"]) and never expose it in client-side code or public repositories.
Both clients are context managers and expose close():
with Pictify(api_key="your-api-key") as client:
    ...

async with AsyncPictify(api_key="your-api-key") as client:
    ...

Render an Image from HTML

render_html(html, *, css=None, width=None, height=None, selector=None, format=None)POST /image. Returns an ImageResult with url, id, and created_at.
image = client.render_html(
    html="<div style='padding:40px'>Hello</div>",
    css="div { color: blue; }",   # optional — inlined into a <style> tag before the HTML
    width=1200,                    # optional (default: 1280)
    height=630,                    # optional (default: 720)
    selector="#card",             # optional — crop to a specific element
    format="png",                 # optional: 'png' | 'jpg' | 'jpeg' | 'webp' | 'pdf' (default: png)
)
print(image.url, image.id, image.created_at)
The /image endpoint accepts a single html field, so any css you pass is injected into a <style> tag prepended to the HTML. format is mapped to the endpoint’s fileExtension.

Screenshot a Live URL

render_url(url, *, width=None, height=None, selector=None, format=None)POST /image with url. Returns an ImageResult.
image = client.render_url(url="https://example.com", width=1280, height=720)
print(image.url)

Render a Template

render(template_id, *, variables=None, format=None, quality=None, width=None, height=None, layout=None, layouts=None)POST /templates/:uid/render. The response is a results envelope; result.url is a convenience accessor for results[0].url.
result = client.render(
    "XL13XACH2V",
    variables={"name": "Ada", "company": "Pictify"},
    format="png",   # optional: 'png' | 'jpg' | 'jpeg' | 'webp' | 'pdf' (default: png)
    quality=0.9,    # optional render quality, 0.1-1.0 (default: 0.9)
    width=1200,     # optional
    height=630,     # optional
)
print(result.url)
print(result.results[0])  # RenderResultItem: layout, url, width, height, format, name, id, created_at

Render a Specific Layout

result = client.render(
    "XL13XACH2V",
    variables={"name": "Ada"},
    layout="square",
)

Render Multiple Layouts

render_layouts(template_id, layouts, *, variables=None, format=None, quality=None, width=None, height=None)POST /templates/:uid/render with layouts (max 20). Each successful layout appears in results; missing/invalid layouts appear in errors.
result = client.render_layouts(
    "XL13XACH2V",
    layouts=["default", "square", "story"],
    variables={"name": "Ada"},
)
for item in result.results:
    print(item.layout, item.url, f"{item.width}x{item.height}")
for err in result.errors:
    print("failed:", err.layout, err.error)

Render an Animated GIF

render_gif(*, html=None, url=None, template_id=None, variables=None, width=None, height=None, quality=None)POST /gif. Provide exactly one source: html, url, or template_id. The {gif: {...}} envelope is flattened.
gif = client.render_gif(
    html="<style>@keyframes p{0%{opacity:.2}50%{opacity:1}100%{opacity:.2}}div{animation:p 2s infinite}</style><div>Hi</div>",
    width=400,         # optional (default: 800)
    height=200,        # optional (default: 600)
    quality="medium",  # optional: 'low' | 'medium' | 'high' (default: medium)
)
print(gif.url, gif.uid, gif.animation_length)

# From a template
gif = client.render_gif(template_id="XL13XACH2V", variables={"name": "Ada"})

# From a live URL
gif = client.render_gif(url="https://example.com/animated-page")
The source HTML/URL must contain motion (e.g. a CSS animation). Static content produces no frames and returns a render error (HTTP 422).

Batch Rendering (async)

render_batch(template_id, variable_sets, *, format=None, quality=None, concurrency=None, layout=None, layouts=None)POST /templates/:uid/batch-render. Submitting returns immediately (HTTP 202) with a batch_id. Poll get_batch_results for progress.
job = client.render_batch(
    "XL13XACH2V",
    variable_sets=[
        {"name": "Ada", "company": "Pictify"},
        {"name": "Grace", "company": "Pictify"},
    ],  # max 100 per batch
    format="png",     # optional
    quality=0.9,      # optional, 0.1-1.0
    concurrency=5,    # optional, 1-10 (default: 5)
)
print(job.batch_id, job.status, job.total_items)
get_batch_results(batch_id)GET /templates/batch/:batchId/results.
status = client.get_batch_results(job.batch_id)
print(status.status, status.completed_items, "/", status.total_items)
for item in status.results:
    print(item.index, item.success, item.variables)  # no URLs (see webhook)
Rendered URLs are not returned by the poll endpoint. Per-item records carry {index, success, variables} (and error on failures). Final image URLs are delivered via the render.completed webhook — subscribe to webhooks to collect batch output.

Templates

Get a Template

get_template(template_id)GET /templates/:uid.
template = client.get_template("XL13XACH2V")
print(template.uid, template.name)
print([v.name for v in (template.variable_definitions or [])])

List Templates

list_templates(*, page=None, limit=None, sort=None)GET /templates. Returns a ListTemplatesResult with templates and pagination.
result = client.list_templates(page=1, limit=20, sort="newest")
for t in result.templates:
    print(t.uid, t.name)
print(result.pagination.total, result.pagination.has_next)

Create a Template

create_template(html, *, name=None, width=None, height=None, variable_definitions=None, output_format=None)POST /templates. Variables are auto-discovered from {{variableName}} tokens.
template = client.create_template(
    html="<div>Hi {{first_name}}</div>",
    name="Welcome Card",
    width=600,
    height=200,
    output_format="image",  # optional: 'image' | 'pdf'
)
print(template.uid)

Error Handling

from pictify import (
    Pictify,
    PictifyError,
    AuthenticationError,
    TemplateNotFoundError,
    RateLimitError,
    QuotaExceededError,
    RenderError,
    ServerError,
)

client = Pictify(api_key="your-api-key")

try:
    result = client.render("XL13XACH2V", variables={"name": "Ada"})
except AuthenticationError:
    print("Invalid API key")
except TemplateNotFoundError:
    print("Template not found")
except RateLimitError as e:
    print(f"Rate limited. Retry after: {e.retry_after}s")
except QuotaExceededError:
    print("Render quota exceeded")
except RenderError as e:
    print(f"Render/validation failed: {e.message}; field errors: {e.errors}")
except ServerError as e:
    print(f"Server error: {e.message}")
except PictifyError as e:
    print(f"Error: {e.message}")
Status → error mapping: 401 → AuthenticationError, 402 → QuotaExceededError, 404 → TemplateNotFoundError, 422 → RenderError (with field-level errors), 429 → QuotaExceededError when code == "quota_exceeded" else RateLimitError, other 4xx → RenderError, 5xx → ServerError. Only 5xx and network errors are retried.

Type Hints

The SDK ships full type hints and Pydantic result models:
from pictify import (
    Pictify,
    AsyncPictify,
    ImageResult,
    RenderResult,
    RenderResultItem,
    GifRenderResult,
    BatchRenderResult,
    BatchResults,
    Template,
    ListTemplatesResult,
    ImageFormat,
    GifQuality,
)

Django Integration

# views.py
from django.conf import settings
from django.http import HttpResponseRedirect
from pictify import Pictify

client = Pictify(api_key=settings.PICTIFY_API_KEY)

def og_image(request, slug):
    post = Post.objects.get(slug=slug)

    result = client.render(
        "og-image-template",
        variables={
            "title": post.title,
            "description": post.excerpt,
            "author": post.author.name,
        },
    )

    return HttpResponseRedirect(result.url)

API Reference

See the API Reference for full endpoint documentation.