jpg.now API
Convert images and documents programmatically. One REST endpoint submits a file, one polls it, one downloads the result — there is no SDK to install and nothing to configure beyond an API key.
How it works
Conversion is asynchronous, because a large RAW file takes longer than an HTTP request should. Every conversion is three steps:
- Submit —
POST /api/v1/convertwith the file and a converterslug. Returns202and ajob_idimmediately. - Poll —
GET /api/v1/jobs/{job_id}untilstatusiscompletedorfailed. Most conversions finish in under a second; poll about once a second. - Download —
GETthedownload_urlfrom the completed job.
Each submitted conversion costs credits — 1 normally, 2 for CPU-heavy formats. See Credits and pricing.
Authentication
Pass your API key in the X-Api-Key header on every request.
Keys are created on your
Dashboard → API Access
page and shown once, at creation — we store only a hash, so a lost key must
be regenerated rather than recovered. Regenerating invalidates the old key
immediately.
X-Api-Key: jpgnow_your_key_here
Requests without a valid key return 401 and are never
charged.
Base URL
https://jpg.now — all endpoints below are relative to it.
HTTPS only.
Quickstart
A complete conversion, in the language of your choice.
# 1. Submit
curl -X POST https://jpg.now/api/v1/convert \
-H "X-Api-Key: $API_KEY" \
-F "[email protected]" \
-F "slug=png-to-jpg"
# -> {"job_id":"abc123","status":"queued","credits_charged":1,"credits_remaining":999}
# 2. Poll until it is done
curl https://jpg.now/api/v1/jobs/abc123 -H "X-Api-Key: $API_KEY"
# -> {"status":"completed","download_url":"/download/abc123"}
# 3. Download
curl -L https://jpg.now/download/abc123 -o converted.jpg
import os, time, requests
BASE = "https://jpg.now"
HEAD = {"X-Api-Key": os.environ["API_KEY"]}
# 1. Submit
with open("photo.png", "rb") as fh:
r = requests.post(f"{BASE}/api/v1/convert", headers=HEAD,
files={"file": fh}, data={"slug": "png-to-jpg"})
r.raise_for_status()
job_id = r.json()["job_id"]
# 2. Poll. Give up eventually rather than looping forever on a stuck job.
for _ in range(120):
job = requests.get(f"{BASE}/api/v1/jobs/{job_id}", headers=HEAD).json()
if job["status"] in ("completed", "failed"):
break
time.sleep(1)
else:
raise TimeoutError(job_id)
if job["status"] == "failed":
raise RuntimeError(job.get("error", "conversion failed"))
# 3. Download
out = requests.get(BASE + job["download_url"], headers=HEAD)
open("converted.jpg", "wb").write(out.content)
import fs from "node:fs";
const BASE = "https://jpg.now";
const HEAD = { "X-Api-Key": process.env.API_KEY };
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
// 1. Submit
const form = new FormData();
form.append("file", new Blob([fs.readFileSync("photo.png")]), "photo.png");
form.append("slug", "png-to-jpg");
const res = await fetch(`${BASE}/api/v1/convert`, {
method: "POST", headers: HEAD, body: form,
});
if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).error}`);
const { job_id } = await res.json();
// 2. Poll, with a ceiling so a stuck job cannot hang the process
let job;
for (let i = 0; i < 120; i++) {
job = await (await fetch(`${BASE}/api/v1/jobs/${job_id}`, { headers: HEAD })).json();
if (job.status === "completed" || job.status === "failed") break;
await sleep(1000);
}
if (job.status !== "completed") throw new Error(job.error ?? "conversion failed");
// 3. Download
const file = await fetch(BASE + job.download_url, { headers: HEAD });
fs.writeFileSync("converted.jpg", Buffer.from(await file.arrayBuffer()));
The full list of converter slugs is on the supported formats page.
Endpoints
POST/api/v1/convert
Submit a file for conversion. Send as multipart/form-data.
| Field | Required | Description |
|---|---|---|
file | Yes | The file to convert |
slug | Yes | Converter ID - E.g. jpg-to-png |
quality | No | Output quality 10–100 (default 90) |
width / height | No | Resize output in pixels |
Returns 202 with {"job_id": "…", "status": "queued", "poll_url": "/api/v1/jobs/…"}
GET/api/v1/jobs/{job_id}
Poll job status. When status is "completed" a download_url is included.
GET/download/{job_id}
Download the converted file. Returns the binary directly.
GET/api/v1/usage
Your current allowance and consumption, so an integration can back off
before it hits the ceiling rather than discovering it by failing.
Returns quota_used, quota_included,
quota_remaining and resets_at.
Rate limits and quota
Two separate limits apply. The rate limit is a burst
ceiling per minute; exceeding it returns 429 and you can retry
shortly after. The monthly quota is your included
allowance; exhausting it returns 402 until the period resets.
Over-quota requests are refused, not billed — there is no
overage charge, so a runaway script cannot produce a surprise invoice.
Requests that we reject (401, 429,
402) do not count against your allowance.
Every call, accepted or rejected, appears in your usage dashboard with its status code and result.
Credits and pricing
The API runs on credits. One credit is one conversion. CPU-heavy formats — RAW camera files, PDF and vector rasterising, PSD, TIFF — cost 2 credits, because they take several times the CPU of an ordinary transcode. Everything else costs 1.
| Conversion | Cost | Examples |
|---|---|---|
| Standard | 1 credit | jpg-to-png, png-to-jpg, jpg-to-webp, compression, resizing |
| CPU-heavy | 2 credits | raw-to-jpg, pdf-to-jpg, psd-to-jpg, svg-to-jpg, jpg-to-pdf, jpg-to-tiff |
Only work we actually queue is charged. Requests we reject — a bad key, a rate limit, an unknown converter — cost nothing, and if we take a credit and then fail to queue the job, it is refunded automatically.
Two ways to buy
Credits can be bought outright or granted monthly by a subscription:
- One-off — bought once, never expire. Best for bursty or unpredictable volume.
- Monthly subscription — 30% cheaper per credit, but unused credits are reset at each renewal rather than rolling over. Best for steady volume.
You can hold both. Subscription credits are always spent first, so the ones you bought outright are never lost to a renewal.
| Credits | One-off | Monthly | Per credit (monthly) | |
|---|---|---|---|---|
| 1,000 | $12.00 | $8.40/mo | $0.0084 | Buy → |
| 5,000 | $60.00 | $42.00/mo | $0.0084 | Buy → |
| 25,000 | $300.00 | $210.00/mo | $0.0084 | Buy → |
| 100,000 | $1200.00 | $840.00/mo | $0.0084 | Buy → |
Any amount from 500 credits upwards can be bought from the API dashboard. Premium and Premium Plus subscribers get API access with no credit cost at all.
Errors
| Status | Meaning | What to do |
|---|---|---|
400 | Bad request — unknown slug, missing file, or a file we cannot accept | Fix the request; nothing was charged |
401 | Missing or invalid X-Api-Key | Check the header and that the key has not been revoked |
402 | Out of credits | Top up; the response says how many the call needed |
403 | Account cannot use the API | Create a key and buy credits |
404 | Job not found, or not yours | Check the job_id |
429 | Rate limit exceeded | Slow down and retry; your limit is in /api/v1/usage |
503 | We could not queue the job | Retry shortly; the credit was refunded |