Skip to main content

image2ppt — Python client

Official Python client for the image2ppt API. Turn a batch of images or PDF pages into one editable PowerPoint (.pptx).

Install

pip install image2ppt

Requires Python 3.9+. Depends on requests and Pillow (Pillow powers optional client-side image pre-compression — see below).

Get an API key

Sign in at image2ppt.com, open Developer / API from the account menu, and create a key (looks like i2p_live_xxxx). It's shown in full once — save it. API access is available to accounts with credits.

Server-side only. Keep your key on your backend. Never embed it in a browser, mobile app, or anything a user can inspect.

Quick start

One shot — submit, wait, download:

from image2ppt import Image2PPTClient

client = Image2PPTClient(api_key="i2p_live_your_key")

job = client.convert(
    ["slide1.png", "slide2.png", "report.pdf"],
    dest_path="out.pptx",
    locale="zh-CN",       # optional: "zh-CN" (default) or "en"
    aspect_ratio="16:9",  # optional: "auto" (default) / "16:9" / "4:3"
)
print("done — credits used:", job.credits_used, "refunded:", job.credits_refunded)

Step by step, if you want to control polling:

job = client.submit(["slide1.png"], aspect_ratio="4:3")
print("job:", job.job_id, "credits reserved:", job.credits_reserved)

job = client.wait(job.job_id, poll_interval=5, timeout=1800)
client.download(job.job_id, "out.pptx")

Check your balance:

info = client.account()
print(info["email"], "credits:", info["credits"])

How it works

  • Async. submit returns a job id immediately; conversion runs in the background. A single page typically takes ~2 minutes; 90% of jobs finish within 3.
  • One job = one PPTX. All files in a submission are merged into a single deck, in upload order.
  • Billed per page. 1 page = 1 credit, reserved at submit and settled on completion. If some pages fail but others succeed, the job still completeds with the good pages and the failed pages' credits are refunded (credits_refunded).
  • Limits. Each file ≤ 35MB; the files in one request ≤ 45MB in total; ≤ 50 pages per job (images count as 1, PDFs as their page count). All three are checked locally before upload — note the per-file limit is the stricter one, so a 40MB PDF is refused even though it fits a request. The sizes counted are the ones that actually go on the wire: for an image that is its size after client-side compression, so a 40MB PNG that compresses to 1MB is fine. (The Node SDK has no client-side compression, so it counts the size on disk and would refuse that same PNG — the two clients agree on the limits, not always on the verdict for one file.)
  • The check is never stricter than the documented limit. 45MB of file content is meant to be usable, so a submission sitting exactly on it goes through. Auto-batching is the one place that is deliberately conservative — it fills a batch only to 40MB, because starting one more batch costs nothing while refusing something the server would have accepted does not.
  • Only the formats the API accepts. png, jpg/jpeg, webp, gif, pdf. Anything else raises InvalidFileError locally — the batch calls check every file before submitting the first one, so an unsupported file at the end of the pile cannot leave you paying for the batches ahead of it.
  • The local page check is a lower bound. The client does not parse PDFs, so it counts each one as at least 1 page. That is enough to refuse combinations that can never work (50 images plus any PDF is already 51 pages), but a submission that passes locally can still come back TOO_MANY_SLIDES — a 30-page PDF counts as 1 here and 30 on the server.
  • Going over the request limit is not a polite error. Past that the connection is cut before the API can answer, so the caller sees a write timeout or a broken pipe instead of a status code. The client therefore checks locally before uploading and raises InvalidFileError (code="PAYLOAD_TOO_LARGE") without sending a byte.
  • A failed submission is never retried automatically. A connection error only tells you the exchange broke — not whether the request body arrived. The job may not exist, or it may exist with credits already reserved and only the response lost. Retrying the second case charges you twice, and there is no idempotency key to tell them apart, so the error is raised as-is. Check account() or your job list before resending. (Rate limits are retried by submit_all() / convert_all(): a 429 is the server saying it did not take the submission.)
  • Downloads are all-or-nothing. download() writes to a temporary file next to the destination and renames it into place at the end, so a dropped connection cannot leave a truncated .pptx behind — or destroy a good deck already sitting at that path.
  • Every request identifies the client with a User-Agent of image2ppt-python/<version>. The service uses this to tell SDK versions apart — it is not part of authentication and never changes a request's outcome.
  • A deprecated SDK version logs one warning. If this version is below the lowest the service still supports, the response carries a Deprecation header and the client warns once (logger image2ppt). Pass warn_on_deprecated=False to Image2PPTClient to silence it.
  • Client-side pre-compression. Images are compressed to the server's spec before upload (≤2000px, ≤1MB, JPEG), so the server's own pass is a no-op and you send fewer bytes. PDFs are uploaded as-is and rendered server-side.

More files than one request can hold

convert() is one job, one PPTX. For a pile too big for a single request, convert_all() splits it and writes one PPTX per batch (no server-side merge — N batches means N decks):

paths = client.convert_all(image_paths, dest_dir="decks/")
print(paths)  # ['decks/part-01.pptx', 'decks/part-02.pptx']

Batches hold at most 40MB of file content and at most 50 images; every PDF goes in a batch of its own, because the client does not parse PDFs and only the server knows their page count. submit_all() does the same splitting and hands back the jobs if you want to drive polling yourself. To see the plan without uploading anything, use plan_batches().

Rate limits are waited out, not raised. A pile big enough to need batching will hit the account's per-minute page quota (and its cap on concurrently active jobs). Both arrive as a 429 with a Retry-After; both are handled the same way — sleep that long, retry the same batch. Retrying a 429 is free: the server is saying it did not take the submission, so nothing was created and nothing was charged. Total waiting is capped by rate_limit_max_wait (default 30 min) — and only waiting counts against it, not the time the uploads themselves take, so a slow link cannot quietly turn the cap into "do not wait at all". A single batch is also retried at most 10 times, whatever the budget says: every retry re-uploads the whole batch, and a service still refusing after ten tries will not be talked round by more of them.

If a batch call does fail partway, the jobs it already created come back on the exception:

from image2ppt import Image2PPTError

try:
    paths = client.convert_all(image_paths, dest_dir="decks/")
except Image2PPTError as e:
    # These are already running with credits reserved — collect them, don't resubmit.
    for job in e.submitted_jobs:
        print("still running:", job.job_id)
    raise

Rate limits

Per account (all keys share the budget): ≤ 10 concurrent jobs, ≤ 60 pages/minute submitted. Over the limit returns 429 with a Retry-After hint. Only submissions are rate limited — polling job status is not.

submit_all() / convert_all() wait these out for you: a pile big enough to need batching is a pile big enough to hit the quota, so a 429 mid-pile is the normal path, not an error. submit() and convert() do not — they submit exactly once, so catch RateLimitedError and honor retry_after yourself:

import time
from image2ppt import RateLimitedError

while True:
    try:
        job = client.submit(paths)
        break
    except RateLimitedError as e:
        time.sleep(e.retry_after if e.retry_after is not None else 5)

Errors

Every exception subclasses Image2PPTError and carries status_code, code, and message. Branch on code, not message.

Exception HTTP code
AuthenticationError 401 / 403 INVALID_API_KEY, API_KEY_REQUIRED, ACCOUNT_DELETED
InvalidFileError 400 / 413 INVALID_FILE, INVALID_PDF, PAYLOAD_TOO_LARGE (the size checks also fire locally, before upload)
UploadAbortedError 400 UPLOAD_ABORTED — the body never finished arriving and the server took nothing, so resending the same files is safe
MalformedUploadError 400 MALFORMED_UPLOAD — the body was not valid multipart/form-data; resending identical bytes will not help
NoFilesError 400 NO_FILES — no files reached the server
InvalidAspectRatioError 400 INVALID_ASPECT_RATIO — use auto, 16:9, or 4:3
TooManySlidesError 400 TOO_MANY_SLIDES
PageRateExceededError 400 PAGE_RATE_EXCEEDED — this one submission has more pages than a minute's quota, so waiting will not help; split it
InsufficientCreditsError 402 INSUFFICIENT_CREDITS
RateLimitedError 429 RATE_LIMITED (has retry_after)
JobNotFoundError 404 JOB_NOT_FOUND
NotReadyError 409 NOT_READY
OutputExpiredError 410 OUTPUT_EXPIRED
JobFailedError job's error.code (raised by wait(); e.job is the snapshot)
Image2PPTTimeoutError — (wait() exceeded its timeout; job may still be running)
from image2ppt import Image2PPTError, JobFailedError

try:
    job = client.convert(paths, "out.pptx")
except JobFailedError as e:
    print("conversion failed:", e.code, e.message)
except Image2PPTError as e:
    print("request error:", e.status_code, e.code, e.message)

Full API reference

See ../docs/api.md for the complete HTTP contract (endpoints, fields, error codes). 中文版:../docs/api.zh.md

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

image2ppt-0.2.1.tar.gz (26.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

image2ppt-0.2.1-py3-none-any.whl (30.5 kB view details)

Uploaded Python 3

File details

Details for the file image2ppt-0.2.1.tar.gz.

File metadata

  • Download URL: image2ppt-0.2.1.tar.gz
  • Upload date:
  • Size: 26.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.1 {"installer":{"name":"uv","version":"0.11.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for image2ppt-0.2.1.tar.gz
Algorithm Hash digest
SHA256 3a57954d2a8620aa6ff4af337e865c857b15c2f86c5a18e98ceb89263bbd3c4f
MD5 89ccbe264de0ede0d64a1f528f8a5620
BLAKE2b-256 16abac1748fde4e113cff98f6cdc8d0470720d2503bfa3aa32c3b7e48cdf8a0f

See more details on using hashes here.

File details

Details for the file image2ppt-0.2.1-py3-none-any.whl.

File metadata

  • Download URL: image2ppt-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 30.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.1 {"installer":{"name":"uv","version":"0.11.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for image2ppt-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 b6b78e537eb49174a216a56610e87c1eadae863040461bb89b0aa5085fa952f0
MD5 6d5ad8f28412b54167693d582297a893
BLAKE2b-256 459e5d389c26f73b4da075d4993479419f7b71356ab5d4987648df7694dc7f0c

See more details on using hashes here.

Release history Release notifications | RSS feed

0.5.0

2 files

0.4.0

2 files

0.3.0

2 files

This release

0.2.1 This release

2 files

0.2.0

2 files

0.1.1

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page