DEV Community

local ai
local ai

Posted on

How to Batch-Convert Diagram Images to VSDX with an API and Webhooks

LayerBack

One diagram screenshot is a manual task. Five hundred legacy architecture images are a migration project. A batch converter must do more than loop over files: it needs asynchronous jobs, durable job IDs, bounded concurrency, verified downloads, and a record that prevents the same image from being charged twice.

This guide uses the LayerBack conversion API, which accepts PNG, JPEG, and WebP and produces VSDX, PPTX, draw.io, SVG, structured IR, and a preview from one conversion.

API lifecycle

The workflow has three phases:

  1. POST /api/v1/convert with raw image bytes and receive a job_id.
  2. Poll GET /api/v1/jobs/{job_id} or provide a completion webhook.
  3. Download each required artifact from /download?format=....

Jobs move through queued → running → succeeded or failed. Conversion typically takes 30–60 seconds, so it should not occupy a normal synchronous request.

Create an API key under Settings → API Keys and keep it in an environment variable:

export LAYERBACK_API_KEY="replace-with-your-key"
Enter fullscreen mode Exit fullscreen mode

Do not put the key in source control, shell history, client-side JavaScript, or screenshots.

Submit one image

curl -X POST https://layerback.com/api/v1/convert \
  -H "x-api-key: $LAYERBACK_API_KEY" \
  -H "Content-Type: application/octet-stream" \
  --data-binary @diagram.png
Enter fullscreen mode Exit fullscreen mode

The response contains a durable identifier:

{"job_id":"5c048e6f-ec4d-43ad-9eee-0697414d5cef"}
Enter fullscreen mode Exit fullscreen mode

Store the source path, file hash, job ID, and submission time immediately. If the process crashes after submission but before saving the ID, an automatic retry may create another paid conversion.

Poll status and download safely

curl https://layerback.com/api/v1/jobs/$JOB_ID \
  -H "x-api-key: $LAYERBACK_API_KEY"
Enter fullscreen mode Exit fullscreen mode

A successful status includes available formats. Download with redirect following enabled:

curl -L \
  "https://layerback.com/api/v1/jobs/$JOB_ID/download?format=vsdx" \
  -H "x-api-key: $LAYERBACK_API_KEY" \
  -o diagram.vsdx
Enter fullscreen mode Exit fullscreen mode

The endpoint may redirect to a short-lived object-storage URL, so -L is required. After download, verify that the file exists, is non-empty, and can be opened as a ZIP/OOXML package before marking the row complete.

A small Python batch worker

The following pattern submits images sequentially, persists a JSON Lines manifest, polls with a fixed interval, and downloads VSDX. It intentionally favors recoverability over maximum throughput.

import hashlib
import json
import os
import time
from pathlib import Path

import requests

BASE = "https://layerback.com/api/v1"
KEY = os.environ["LAYERBACK_API_KEY"]
HEADERS = {"x-api-key": KEY}
INPUT = Path("legacy-diagrams")
OUTPUT = Path("converted-vsdx")
OUTPUT.mkdir(exist_ok=True)

def sha256(path: Path) -> str:
    h = hashlib.sha256()
    with path.open("rb") as stream:
        for chunk in iter(lambda: stream.read(1024 * 1024), b""):
            h.update(chunk)
    return h.hexdigest()

def append_event(event: dict) -> None:
    with open("conversion-manifest.jsonl", "a", encoding="utf-8") as f:
        f.write(json.dumps(event, ensure_ascii=False) + "\n")

for source in sorted(INPUT.iterdir()):
    if source.suffix.lower() not in {".png", ".jpg", ".jpeg", ".webp"}:
        continue

    digest = sha256(source)
    response = requests.post(
        f"{BASE}/convert",
        headers={**HEADERS, "Content-Type": "application/octet-stream"},
        data=source.read_bytes(),
        timeout=90,
    )
    response.raise_for_status()
    job_id = response.json()["job_id"]
    append_event({"source": str(source), "sha256": digest,
                  "job_id": job_id, "state": "submitted"})

    while True:
        status = requests.get(
            f"{BASE}/jobs/{job_id}", headers=HEADERS, timeout=30
        )
        status.raise_for_status()
        payload = status.json()
        if payload["status"] in {"succeeded", "failed"}:
            break
        time.sleep(5)

    if payload["status"] == "failed":
        append_event({"job_id": job_id, "state": "failed",
                      "error": payload.get("error")})
        continue

    target = OUTPUT / f"{source.stem}.vsdx"
    artifact = requests.get(
        f"{BASE}/jobs/{job_id}/download?format=vsdx",
        headers=HEADERS,
        timeout=90,
        allow_redirects=True,
    )
    artifact.raise_for_status()
    target.write_bytes(artifact.content)
    append_event({"job_id": job_id, "state": "downloaded",
                  "target": str(target), "bytes": target.stat().st_size})
Enter fullscreen mode Exit fullscreen mode

Before running it in production, load the manifest at startup and skip hashes already marked submitted, succeeded, or downloaded. Add a bounded worker pool only after the recovery path is proven.

Use webhooks for larger queues

Polling is simple, but a webhook avoids repeated status calls:

curl -X POST \
  "https://layerback.com/api/v1/convert?callback_url=https://example.com/hooks/layerback" \
  -H "x-api-key: $LAYERBACK_API_KEY" \
  -H "Content-Type: application/octet-stream" \
  --data-binary @diagram.png
Enter fullscreen mode Exit fullscreen mode

The callback contains the job ID, status, elapsed time, error, and format list. The current API documentation says delivery is attempted twice. Treat the webhook as a notification, then verify important results with the job-status endpoint before downloading.

Make the handler idempotent by keying on job_id. Return success only after the event is durably stored. Do not start another conversion merely because a webhook arrives twice or arrives late.

Production checklist

  • Accept only PNG, JPEG, and WebP up to 20 MB.
  • Respect the current default limit of 20 conversions per hour per account.
  • Use 402, 413, 415, and 429 responses to distinguish balance, size, format, and rate-limit problems.
  • Persist a source hash and job ID before polling.
  • Limit concurrency instead of launching the whole archive at once.
  • Retry network reads, but do not blindly resubmit a conversion with an unknown outcome.
  • Download promptly; artifacts are retained for at least 72 hours.
  • Validate VSDX and PPTX packages before deleting source images.
  • Keep failures and manual-review notes in the migration manifest.

One conversion costs 10 credits and includes every output format, so download all formats needed by downstream teams from the same job rather than submitting the same source repeatedly.

For agent workflows, LayerBack also provides an MCP server; for ordinary migrations, the REST job model is easier to monitor and audit. Start with ten representative diagrams, measure cleanup time, then scale the queue using the same manifest and validation rules.

Top comments (0)