zplflow logo

Jobs API

Jobs handle asynchronous conversion. Upload documents, start processing, and poll for results.

Contents: Create · Upload · Start · Status · List · Cancel · Full Workflow

Warning: Presigned upload URLs expire after 15 minutes. Upload documents immediately after creating the job. Results expire after 1 hour — download promptly.


POST /v1/jobs — Create a Job

Idempotent: yes (requires Idempotency-Key header)

Creates a job and returns presigned URLs for document upload.

Request

{
  "operation": "pdf_to_zpl",
  "params": {
    "dpi": 203,
    "max_kb": 64,
    "fit": "contain",
    "width": 101.6,
    "height": 152.4,
    "unit": "mm"
  },
  "documents": [
    {
      "content_type": "application/pdf",
      "page_count": 3,
      "metadata": { "order_id": "ORD-12345" }
    }
  ]
}
Field Type Description
operation string pdf_to_zpl, zpl_to_pdf, pipeline_run
params object See Conversions for params
documents array 1 or more documents
documents[].content_type string application/pdf, text/plain, application/zpl
documents[].page_count int Page count hint (improves token accuracy)

Response (201 Created)

{
  "job_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "created",
  "tokens_reserved": 6,
  "uploads": [
    {
      "index": 0,
      "bucket": "zplflow-inputs",
      "key": "inputs/tenant/a1b2c3d4.../0.pdf",
      "upload_url": "https://s3.amazonaws.com/...?X-Amz-Signature=...",
      "expires_at": "2026-05-13T15:30:00Z"
    }
  ]
}
Field Description
job_id UUID for the job
uploads[].upload_url Presigned PUT URL, 15-minute expiry
tokens_reserved Tokens held for this job

Errors: 400 INVALID_PARAMS, 409 INSUFFICIENT_TOKENS, 409 IDEMPOTENCY_CONFLICT


Upload Documents

Use the presigned upload_url to upload each document via HTTP PUT.

curl -X PUT "$UPLOAD_URL" \
  -H "Content-Type: application/pdf" \
  --data-binary @label.pdf
import requests

with open("label.pdf", "rb") as f:
    resp = requests.put(upload_url, data=f, headers={"Content-Type": "application/pdf"})
# 200 OK
package main

import (
    "bytes"
    "io"
    "net/http"
    "os"
)

func main() {
    pdf, _ := os.ReadFile("label.pdf")
    req, _ := http.NewRequest("PUT", uploadURL, io.NopCloser(bytes.NewReader(pdf)))
    req.Header.Set("Content-Type", "application/pdf")
    http.DefaultClient.Do(req)
}
<?php
$ch = curl_init($uploadUrl);
curl_setopt($ch, CURLOPT_PUT, true);
curl_setopt($ch, CURLOPT_INFILE, fopen("label.pdf", "r"));
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Content-Type: application/pdf"]);
curl_exec($ch);

### AS400 / HTTPAPI (RPG)

dcl-s response varchar(32000);
dcl-s rc int(10);

rc = http_put_raw(
    %trim(uploadUrl)
    : '/path/to/label.pdf'
    : 'application/pdf'
    : %trim(response)
    : *null
    : *null
    : 30000);

POST /v1/jobs/{id}/start — Start Processing

Triggers async processing. Job must be in created status and all documents uploaded.

curl -X POST https://api.zplflow/v1/jobs/a1b2c3d4-.../start \
  -H "Authorization: Bearer lb_live_xxxx"
import requests

resp = requests.post(
    f"https://api.zplflow/v1/jobs/{job_id}/start",
    headers={"Authorization": "Bearer lb_live_xxxx"}
)
package main

import (
    "net/http"
)

func main() {
    req, _ := http.NewRequest("POST",
        "https://api.zplflow/v1/jobs/"+jobID+"/start", nil)
    req.Header.Set("Authorization", "Bearer lb_live_xxxx")
    http.DefaultClient.Do(req)
}
<?php
$ch = curl_init("https://api.zplflow/v1/jobs/$jobId/start");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer lb_live_xxxx"]);
curl_exec($ch);

AS400 / SQL Native (IBM i)


SELECT SYSTOOLS.HTTPPOSTCLOB(
    'https://api.zplflow/v1/jobs/a1b2c3d4-e5f6-7890-abcd-ef1234567890/start',
    NULL,
    '{"Authorization":"Bearer lb_live_xxxx"}'
) AS response
FROM SYSIBM.SYSDUMMY1;

AS400 / HTTPAPI (RPG)


dcl-s response varchar(32000);
dcl-s rc int(10);

rc = http_req('POST'
    : 'https://api.zplflow/v1/jobs/a1b2c3d4-e5f6-7890-abcd-ef1234567890/start'
    : *null
    : %trim(response)
    : 'Authorization: Bearer lb_live_xxxx'
    : *null
    : *null
    : 30000
    : *null);

Response (202 Accepted):

{ "job_id": "a1b2c3d4-...", "status": "queued" }

Errors: 409 JOB_NOT_STARTABLE, 409 JOB_INPUT_NOT_FOUND


GET /v1/jobs/{id} — Get Job Status

Poll this endpoint to check progress and download results.

curl -H "Authorization: Bearer lb_live_xxxx" \
  https://api.zplflow/v1/jobs/a1b2c3d4-...
import requests

resp = requests.get(
    f"https://api.zplflow/v1/jobs/{job_id}",
    headers={"Authorization": "Bearer lb_live_xxxx"}
)
data = resp.json()
print(f"Status: {data['status']}")
if data.get("results"):
    print(f"Result URL: {data['results'][0]['result_url']}")
package main

import (
    "encoding/json"
    "fmt"
    "net/http"
)

func main() {
    req, _ := http.NewRequest("GET",
        "https://api.zplflow/v1/jobs/"+jobID, nil)
    req.Header.Set("Authorization", "Bearer lb_live_xxxx")
    resp, _ := http.DefaultClient.Do(req)
    defer resp.Body.Close()

    var data struct {
        Status  string `json:"status"`
        Results []struct {
            ResultURL string `json:"result_url"`
        } `json:"results"`
    }
    json.NewDecoder(resp.Body).Decode(&data)
    fmt.Printf("Status: %s\n", data.Status)
    if len(data.Results) > 0 {
        fmt.Printf("Result URL: %s\n", data.Results[0].ResultURL)
    }
}
<?php
$ch = curl_init("https://api.zplflow/v1/jobs/$jobId");
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer lb_live_xxxx"]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$data = json_decode(curl_exec($ch), true);
echo "Status: {$data['status']}\n";
if (isset($data['results'][0])) {
    echo "Result URL: {$data['results'][0]['result_url']}\n";
}

AS400 / SQL Native (IBM i)


SELECT SYSTOOLS.HTTPGETCLOB(
    'https://api.zplflow/v1/jobs/a1b2c3d4-e5f6-7890-abcd-ef1234567890',
    NULL,
    '{"Authorization":"Bearer lb_live_xxxx"}'
) AS response
FROM SYSIBM.SYSDUMMY1;

AS400 / HTTPAPI (RPG)


dcl-s response varchar(32000);
dcl-s rc int(10);

rc = http_req('GET'
    : 'https://api.zplflow/v1/jobs/a1b2c3d4-e5f6-7890-abcd-ef1234567890'
    : *null
    : %trim(response)
    : 'Authorization: Bearer lb_live_xxxx'
    : *null
    : *null
    : 30000
    : *null);

Succeeded

{
  "job_id": "a1b2c3d4-...",
  "status": "succeeded",
  "results": [
    {
      "result_url": "https://s3.amazonaws.com/...?X-Amz-Signature=...",
      "expires_at": "2026-05-13T12:30:00Z",
      "content_type": "application/zpl"
    }
  ]
}

Failed

{
  "job_id": "a1b2c3d4-...",
  "status": "failed",
  "error": "INVALID_INPUT: invalid PDF content"
}

In Progress

{
  "job_id": "a1b2c3d4-...",
  "status": "queued",
  "tokens_reserved": 6
}

Errors: 404 NOT_FOUND


GET /v1/jobs — List Jobs

curl -H "Authorization: Bearer lb_live_xxxx" \
  "https://api.zplflow/v1/jobs?status=succeeded&limit=10"
import requests

resp = requests.get(
    "https://api.zplflow/v1/jobs?status=succeeded&limit=10",
    headers={"Authorization": "Bearer lb_live_xxxx"}
)
jobs = resp.json()
for job in jobs:
    print(job["job_id"], job["status"])
package main

import (
    "encoding/json"
    "fmt"
    "net/http"
)

func main() {
    req, _ := http.NewRequest("GET",
        "https://api.zplflow/v1/jobs?status=succeeded&limit=10", nil)
    req.Header.Set("Authorization", "Bearer lb_live_xxxx")
    resp, _ := http.DefaultClient.Do(req)
    defer resp.Body.Close()

    var jobs []struct {
        JobID  string `json:"job_id"`
        Status string `json:"status"`
    }
    json.NewDecoder(resp.Body).Decode(&jobs)
    for _, j := range jobs {
        fmt.Printf("%s: %s\n", j.JobID, j.Status)
    }
}
<?php
$ch = curl_init("https://api.zplflow/v1/jobs?status=succeeded&limit=10");
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer lb_live_xxxx"]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$jobs = json_decode(curl_exec($ch), true);
foreach ($jobs as $job) {
    echo "{$job['job_id']}: {$job['status']}\n";
}

AS400 / SQL Native (IBM i)


SELECT SYSTOOLS.HTTPGETCLOB(
    'https://api.zplflow/v1/jobs?status=succeeded&limit=10',
    NULL,
    '{"Authorization":"Bearer lb_live_xxxx"}'
) AS response
FROM SYSIBM.SYSDUMMY1;

AS400 / HTTPAPI (RPG)


dcl-s response varchar(32000);
dcl-s rc int(10);

rc = http_req('GET'
    : 'https://api.zplflow/v1/jobs?status=succeeded&limit=10'
    : *null
    : %trim(response)
    : 'Authorization: Bearer lb_live_xxxx'
    : *null
    : *null
    : 30000
    : *null);
Parameter Default Description
limit 100 Max items
cursor - Pagination cursor
status - Filter: created, queued, running, succeeded, failed, canceled

Response

{
  "jobs": [
    {
      "job_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "status": "succeeded",
      "operation": "pdf_to_zpl",
      "tokens_reserved": 6,
      "tokens_charged": 6,
      "created_at": "2026-05-13T12:00:00Z",
      "updated_at": "2026-05-13T12:05:00Z"
    }
  ],
  "next_cursor": "eyJsYXN0X2lkIjoiLi4uIn0="
}

An empty next_cursor means the last page.


POST /v1/jobs/{id}/cancel — Cancel a Job

Cancels a job in created or queued status. Reserved tokens are released.

curl -X POST https://api.zplflow/v1/jobs/a1b2c3d4-.../cancel \
  -H "Authorization: Bearer lb_live_xxxx"
import requests

resp = requests.post(
    f"https://api.zplflow/v1/jobs/{job_id}/cancel",
    headers={"Authorization": "Bearer lb_live_xxxx"}
)
package main

import (
    "net/http"
)

func main() {
    req, _ := http.NewRequest("POST",
        "https://api.zplflow/v1/jobs/"+jobID+"/cancel", nil)
    req.Header.Set("Authorization", "Bearer lb_live_xxxx")
    http.DefaultClient.Do(req)
}
<?php
$ch = curl_init("https://api.zplflow/v1/jobs/$jobId/cancel");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer lb_live_xxxx"]);
curl_exec($ch);

AS400 / SQL Native (IBM i)


SELECT SYSTOOLS.HTTPPOSTCLOB(
    'https://api.zplflow/v1/jobs/a1b2c3d4-e5f6-7890-abcd-ef1234567890/cancel',
    NULL,
    '{"Authorization":"Bearer lb_live_xxxx"}'
) AS response
FROM SYSIBM.SYSDUMMY1;

AS400 / HTTPAPI (RPG)


dcl-s response varchar(32000);
dcl-s rc int(10);

rc = http_req('POST'
    : 'https://api.zplflow/v1/jobs/a1b2c3d4-e5f6-7890-abcd-ef1234567890/cancel'
    : *null
    : %trim(response)
    : 'Authorization: Bearer lb_live_xxxx'
    : *null
    : *null
    : 30000
    : *null);

Errors: 409 ALREADY_FINALIZED


Full Async Workflow

# 1. Create
JOB_ID=$(curl -s -X POST https://api.zplflow/v1/jobs \
  -H "Authorization: Bearer lb_live_xxxx" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"operation":"pdf_to_zpl","params":{"dpi":203},"documents":[{"content_type":"application/pdf"}]}' \
  | jq -r '.job_id')

# 2. Upload document
UPLOAD_URL=$(curl -s ... | jq -r '.uploads[0].upload_url')
curl -X PUT "$UPLOAD_URL" -H "Content-Type: application/pdf" --data-binary @label.pdf

# 3. Start
curl -X POST "https://api.zplflow/v1/jobs/$JOB_ID/start" \
  -H "Authorization: Bearer lb_live_xxxx"

# 4. Poll
while true; do
  STATUS=$(curl -s https://api.zplflow/v1/jobs/$JOB_ID \
    -H "Authorization: Bearer lb_live_xxxx" | jq -r '.status')
  [ "$STATUS" = "succeeded" ] && break
  [ "$STATUS" = "failed" ] && echo "FAILED" && exit 1
  sleep 2
done
import requests, uuid, time

# 1. Create
resp = requests.post(
    "https://api.zplflow/v1/jobs",
    headers={
        "Authorization": "Bearer lb_live_xxxx",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={
        "operation": "pdf_to_zpl",
        "params": {"dpi": 203},
        "documents": [{"content_type": "application/pdf"}]
    }
)
data = resp.json()
job_id = data["job_id"]
upload_url = data["uploads"][0]["upload_url"]

# 2. Upload
with open("label.pdf", "rb") as f:
    requests.put(upload_url, data=f, headers={"Content-Type": "application/pdf"})

# 3. Start
requests.post(f"https://api.zplflow/v1/jobs/{job_id}/start",
    headers={"Authorization": "Bearer lb_live_xxxx"})

# 4. Poll
while True:
    status = requests.get(f"https://api.zplflow/v1/jobs/{job_id}",
        headers={"Authorization": "Bearer lb_live_xxxx"}).json()["status"]
    if status in ("succeeded", "failed"):
        break
    time.sleep(2)
package main

import (
    "bytes"
    "encoding/json"
    "io"
    "net/http"
    "os"
    "time"
    "github.com/google/uuid"
)

func main() {
    // 1. Create
    body, _ := json.Marshal(map[string]interface{}{
        "operation": "pdf_to_zpl",
        "params":    map[string]int{"dpi": 203},
        "documents": []map[string]string{{"content_type": "application/pdf"}},
    })
    req, _ := http.NewRequest("POST", "https://api.zplflow/v1/jobs", bytes.NewReader(body))
    req.Header.Set("Authorization", "Bearer lb_live_xxxx")
    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("Idempotency-Key", uuid.New().String())
    resp, _ := http.DefaultClient.Do(req)
    defer resp.Body.Close()

    var createResp struct {
        JobID   string `json:"job_id"`
        Uploads []struct {
            UploadURL string `json:"upload_url"`
        } `json:"uploads"`
    }
    json.NewDecoder(resp.Body).Decode(&createResp)
    jobID := createResp.JobID
    uploadURL := createResp.Uploads[0].UploadURL

    // 2. Upload
    pdf, _ := os.ReadFile("label.pdf")
    req2, _ := http.NewRequest("PUT", uploadURL, io.NopCloser(bytes.NewReader(pdf)))
    req2.Header.Set("Content-Type", "application/pdf")
    http.DefaultClient.Do(req2)

    // 3. Start
    req3, _ := http.NewRequest("POST",
        "https://api.zplflow/v1/jobs/"+jobID+"/start", nil)
    req3.Header.Set("Authorization", "Bearer lb_live_xxxx")
    http.DefaultClient.Do(req3)

    // 4. Poll
    for {
        req4, _ := http.NewRequest("GET",
            "https://api.zplflow/v1/jobs/"+jobID, nil)
        req4.Header.Set("Authorization", "Bearer lb_live_xxxx")
        r4, _ := http.DefaultClient.Do(req4)
        var statusResp struct {
            Status string `json:"status"`
        }
        json.NewDecoder(r4.Body).Decode(&statusResp)
        r4.Body.Close()
        if statusResp.Status == "succeeded" || statusResp.Status == "failed" {
            break
        }
        time.Sleep(2 * time.Second)
    }
}
<?php
// 1. Create
$ch = curl_init("https://api.zplflow/v1/jobs");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
    "operation" => "pdf_to_zpl",
    "params" => ["dpi" => 203],
    "documents" => [["content_type" => "application/pdf"]]
]));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    "Authorization: Bearer lb_live_xxxx",
    "Content-Type: application/json",
    "Idempotency-Key: " . uniqid("job_", true)
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$data = json_decode(curl_exec($ch), true);
$jobId = $data['job_id'];
$uploadUrl = $data['uploads'][0]['upload_url'];

// 2. Upload
$ch2 = curl_init($uploadUrl);
curl_setopt($ch2, CURLOPT_PUT, true);
curl_setopt($ch2, CURLOPT_INFILE, fopen("label.pdf", "r"));
curl_setopt($ch2, CURLOPT_HTTPHEADER, ["Content-Type: application/pdf"]);
curl_exec($ch2);

// 3. Start
$ch3 = curl_init("https://api.zplflow/v1/jobs/$jobId/start");
curl_setopt($ch3, CURLOPT_POST, true);
curl_setopt($ch3, CURLOPT_HTTPHEADER, ["Authorization: Bearer lb_live_xxxx"]);
curl_exec($ch3);

// 4. Poll
while (true) {
    $ch4 = curl_init("https://api.zplflow/v1/jobs/$jobId");
    curl_setopt($ch4, CURLOPT_HTTPHEADER, ["Authorization: Bearer lb_live_xxxx"]);
    curl_setopt($ch4, CURLOPT_RETURNTRANSFER, true);
    $status = json_decode(curl_exec($ch4), true)['status'];
    if ($status === "succeeded" || $status === "failed") break;
    sleep(2);
}

AS400 / SQL Native (IBM i)


-- Step 1: Create job
SELECT SYSTOOLS.HTTPPOSTCLOB(
    'https://api.zplflow/v1/jobs',
    '{"operation":"pdf_to_zpl","params":{"dpi":203},"documents":[{"content_type":"application/pdf"}]}',
    '{"Authorization":"Bearer lb_live_xxxx","Content-Type":"application/json","Idempotency-Key":"job-1747000000"}'
) AS create_response
FROM SYSIBM.SYSDUMMY1;

-- Step 3: Start (replace JOB_ID)
SELECT SYSTOOLS.HTTPPOSTCLOB(
    'https://api.zplflow/v1/jobs/JOB_ID/start',
    NULL,
    '{"Authorization":"Bearer lb_live_xxxx"}'
) AS start_response
FROM SYSIBM.SYSDUMMY1;

-- Step 4: Poll (replace JOB_ID)
SELECT SYSTOOLS.HTTPGETCLOB(
    'https://api.zplflow/v1/jobs/JOB_ID',
    NULL,
    '{"Authorization":"Bearer lb_live_xxxx"}'
) AS poll_response
FROM SYSIBM.SYSDUMMY1;

AS400 / HTTPAPI (RPG)


dcl-s jobId varchar(80);
dcl-s uploadUrl varchar(500);
dcl-s response varchar(32000);
dcl-s status varchar(20);
dcl-s rc int(10);

// 1. Create
rc = http_req('POST'
    : 'https://api.zplflow/v1/jobs'
    : *null
    : %trim(response)
    : 'Authorization: Bearer lb_live_xxxx'
    : 'Content-Type: application/json'
    : 'Idempotency-Key: job-' + %char(%timestamp())
    : 30000
    : '{"operation":"pdf_to_zpl","params":{"dpi":203},"documents":[{"content_type":"application/pdf"}]}');

// Parse jobId and uploadUrl from response JSON (not shown)

// 2. Upload
rc = http_put_raw(
    %trim(uploadUrl)
    : '/path/to/label.pdf'
    : 'application/pdf'
    : %trim(response)
    : *null
    : *null
    : 30000);

// 3. Start
rc = http_req('POST'
    : 'https://api.zplflow/v1/jobs/' + %trim(jobId) + '/start'
    : *null
    : %trim(response)
    : 'Authorization: Bearer lb_live_xxxx'
    : *null
    : *null
    : 30000
    : *null);

// 4. Poll
dou status = 'succeeded' or status = 'failed';
    rc = http_req('GET'
        : 'https://api.zplflow/v1/jobs/' + %trim(jobId)
        : *null
        : %trim(response)
        : 'Authorization: Bearer lb_live_xxxx'
        : *null
        : *null
        : 30000
        : *null);
    // Parse status from response JSON
    status = 'succeeded';  // replace with actual parsed value
    if status <> 'succeeded' and status <> 'failed';
        rc = sleep(2);
    endif;
enddo;

Next Steps

  • Conversions — Sync alternatives for single-page real-time conversion
  • Pipelines — Transform ZPL documents with reusable pipelines
  • Best Practices — Idempotency, pagination, error handling
  • Code Examples — End-to-end async workflow snippets