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