parth-dl serve starts a local Instagram downloader API that any app can call over
HTTP. Use it when your project is written in Node.js, Go, PHP, Rust, a browser, a
desktop app, or another language, but you still want parth-dl's downloader engine.
The server also hosts the built-in web UI. For the browser interface, read Web UI Usage.
parth-dl serveDefault address:
http://127.0.0.1:8000
Custom port and download folder:
parth-dl serve --port 9000 --dir ~/Videos/instagram --no-open| Flag | Default | Description |
|---|---|---|
--host |
127.0.0.1 |
Loopback host. Use 127.0.0.1, localhost, or ::1. |
--port |
8000 |
Port for the local API and web UI. |
--dir |
./downloads |
Folder where downloaded files are saved. |
--no-open |
off | Do not open the browser automatically. |
-v, --verbose |
off | Print verbose request and extraction logs. |
Most apps use this flow:
POST /api/infoto validate the URL and show metadata.POST /api/downloadto enqueue the download.GET /api/jobs/{id}every few hundred milliseconds until the job finishes.- Read the server-side file path from
files[].path, or fetch a browser copy fromfiles[].url.
curl http://127.0.0.1:8000/api/healthExample response:
{
"ok": true,
"version": "1.2.0",
"download_dir": "/absolute/path/downloads"
}Returns media metadata without downloading. The response follows the Metadata Schema.
curl -X POST http://127.0.0.1:8000/api/info \
-H "Content-Type: application/json" \
-d "{\"url\":\"https://www.instagram.com/reel/Cxyz123AbCd/\"}"A successful info response is cached briefly and reused by the next
POST /api/download for the same URL. This avoids extracting the same Instagram page
twice in the common preview-then-download flow.
Starts a queued download job and returns immediately with HTTP 202.
curl -X POST http://127.0.0.1:8000/api/download \
-H "Content-Type: application/json" \
-d "{\"url\":\"https://www.instagram.com/reel/Cxyz123AbCd/\",\"quality\":\"best\"}"Example response:
{
"job_id": "3f9a0c2f6e214d48"
}quality can be:
| Value | Meaning |
|---|---|
best |
Highest available format. This is the default. |
worst |
Smallest available format. Useful for previews or low storage. |
curl http://127.0.0.1:8000/api/jobsReturns the newest queued, running, completed, cancelled, and failed jobs:
{
"jobs": [
{
"id": "3f9a0c2f6e214d48",
"url": "https://www.instagram.com/reel/Cxyz123AbCd/",
"state": "running",
"percent": 62
}
]
}The server keeps a bounded history of recent jobs so the web UI can restore cards after a refresh. History is not persisted across server restarts.
curl http://127.0.0.1:8000/api/jobs/3f9a0c2f6e214d48Example running response:
{
"id": "3f9a0c2f6e214d48",
"url": "https://www.instagram.com/reel/Cxyz123AbCd/",
"quality": "best",
"state": "running",
"percent": 62,
"current_item": 1,
"total_items": 1,
"queue_position": null,
"message": "Downloading video",
"files": [],
"error": null
}Possible states:
| State | Meaning |
|---|---|
queued |
Waiting for a worker. Check queue_position. |
running |
Download is active. Check percent and message. |
done |
Download finished. Check files. |
cancelled |
Job was cancelled. It can be retried. |
error |
Job failed. Check error. |
Example completed response:
{
"id": "3f9a0c2f6e214d48",
"state": "done",
"message": "Download complete",
"files": [
{
"name": "parthmax-Cxyz123AbCd.mp4",
"path": "/absolute/path/downloads/parthmax-Cxyz123AbCd.mp4",
"url": "/files/parthmax-Cxyz123AbCd.mp4",
"existing": false
}
],
"error": null
}Use files[].path when your app runs on the same machine as the server. Use
files[].url when a browser needs to download a separate copy.
curl -X POST http://127.0.0.1:8000/api/jobs/3f9a0c2f6e214d48/cancel \
-H "Content-Type: application/json" \
-d "{}"Queued jobs cancel immediately. Running jobs cancel cooperatively. Partial .part
files are kept so a future download can resume when possible.
curl -X POST http://127.0.0.1:8000/api/jobs/3f9a0c2f6e214d48/retry \
-H "Content-Type: application/json" \
-d "{}"Returns a new job_id using the original URL and quality.
curl -L -o reel.mp4 http://127.0.0.1:8000/files/parthmax-Cxyz123AbCd.mp4This endpoint serves files directly inside the configured --dir. It cannot escape the
download directory.
Errors are JSON:
{
"error": "message"
}Branch on HTTP status codes instead of parsing messages.
| Status | Meaning |
|---|---|
400 |
Bad request, malformed JSON, unsupported URL, or invalid quality. |
403 |
Forbidden host header or unsafe file request. |
404 |
Unknown route/job, or Instagram content is private/deleted/unsupported. |
429 |
Instagram is throttling requests. Wait before retrying. |
502 |
Upstream transfer failed after retries. |
503 |
Download queue is full. Try again later. |
const BASE = "http://127.0.0.1:8000";
async function request(path, options) {
const response = await fetch(`${BASE}${path}`, options);
const body = await response.json();
if (!response.ok) throw new Error(body.error || `HTTP ${response.status}`);
return body;
}
async function downloadInstagram(url) {
const { job_id } = await request("/api/download", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ url, quality: "best" }),
});
while (true) {
const job = await request(`/api/jobs/${job_id}`);
if (job.state === "done") return job.files;
if (job.state === "error") throw new Error(job.error);
if (job.state === "cancelled") throw new Error(job.message || "cancelled");
await new Promise((resolve) => setTimeout(resolve, 500));
}
}The server is intentionally local-first:
- It binds to loopback by default.
- It rejects non-loopback
Hostheaders to reduce DNS rebinding risk. - It does not send permissive CORS headers.
/files/cannot serve paths outside the download directory.- Media redirects are validated before download.
- The queue is bounded and uses one shared rate limiter.
Do not expose parth-dl serve directly to the public internet. Put your own auth,
queueing, validation, and abuse controls in front of it if you build a hosted service.