Pipelines

Pipelines are reusable ZPL transformation recipes. Define a sequence of steps once and apply them to any ZPL document.

Contents: Pipeline Steps · Create · List · Get One · Delete · Apply · Preview (Free) · Scope Reference

Tip: POST /v1/pipelines/preview is free — test your steps without spending tokens and get a PNG visual render of the output.


Per-endpoint scope requirements

Every endpoint on this page is gated by a tenant-level feature (pipelines) AND a per-API-key scope. Scopes are enforced when deny-by-default mode is active. Until then, legacy wildcard keys are allowed through.

Method Path Scope ID Public MCP tool
POST /v1/pipelines pipelines:write no createPipeline
GET /v1/pipelines pipelines:read no listPipelines
GET /v1/pipelines/{id} pipelines:read no getPipeline
DELETE /v1/pipelines/{id} pipelines:write no deletePipeline
POST /v1/pipelines/preview pipelines:apply no applyPipeline
POST /v1/pipelines/{id}/apply pipelines:apply no applyPipeline
GET /v1/pipelines/prepackaged pipelines:prepackaged:read yes (none)
GET /v1/pipelines/prepackaged/{id} pipelines:prepackaged:read yes (none)
POST /v1/pipelines/prepackaged/{id}/adopt pipelines:prepackaged:read yes (none)

PUBLIC markers mean the scope is granted automatically to every key (including locked keys with no other scope grants). A locked key can list, view, and adopt prepackaged pipelines without any other grant — useful for read-only auditors and onboarding flows.

Missing any non-public scope returns:

{
  "error_code": "INSUFFICIENT_SCOPE",
  "message": "API key is missing required scope 'pipelines:apply'",
  "required_scope": "pipelines:apply",
  "documentation_url": "https://docs.zplflow.io/docs/authentication#api-key-scopes-v11"
}

See Authentication for the full state machine, and Self-Service API Keys for programmatic key management.


Pipeline Steps

Each step has a type and type-specific parameters. Variables use {{placeholder}} syntax.

Type Cost Key Parameters Description
rotate 2 orientation: N, R, I, B Rotate label
replace_text 1 search, replace String replacement. Supports {{vars}}
add_barcode 1 barcode_type: code128/code39, value, x, y, height Linear barcode
add_gs1_128 2 x, y, height, elements[{ai,value}] GS1-128 barcode
add_text 1 x, y, font, font_height, value, block_width Text field. Supports {{vars}}
add_qrcode 1 x, y, value, magnification, error_correction QR Code
add_datamatrix 1 x, y, value, symbol_height DataMatrix
add_box 1 x, y, width, height, thickness, color Rectangle
add_line 1 x, y, length, orientation, thickness Line
replace_barcode_value 1 old_value, new_value Barcode content replacement
remove_graphics 1 - Strip embedded images
set_font 1 from_font, to_font, height, width Font replacement
add_timestamp 1 format, timezone, x, y Current date/time
format_date 1 field_pattern, input_format, output_format Date reformatting
regex_replace 1 pattern, replace Regex search/replace
set_print_params 1 darkness, print_speed, media_type Printer settings
set_label_dimensions 1 width_dots, length_dots Label size override
scale 2 target_width_mm, target_height_mm Resize
add_margin 2 top_dots, right_dots, bottom_dots, left_dots Padding
crop 2 x, y, width_dots, height_dots Crop region
mirror 2 axis: horizontal/vertical Mirror
add_image 3 image_base64, x, y, width_dots, height_dots Embed image
repeat count count Print quantity

POST /v1/pipelines — Create

{
  "name": "Warehouse Label",
  "steps": [
    { "type": "add_text", "x": 50, "y": 100, "font": "0", "font_height": 30, "value": "SKU: {{sku}}" },
    { "type": "add_barcode", "barcode_type": "code128", "value": "{{sku}}", "x": 50, "y": 200, "height": 80 }
  ]
}

Response (201):

{
  "pipeline_id": "d4e5f6a7-b8c9-0123-4567-890abcdef0",
  "name": "Warehouse Label",
  "steps": [...],
  "created_at": 1747132800
}

Errors: 403 INSUFFICIENT_SCOPE

Required API key scopes

Scope ID Tenant feature
pipelines:write pipelines

A key lacking pipelines:write receives 403 INSUFFICIENT_SCOPE. Wildcard legacy keys are permitted until deny-by-default mode is active; locked keys are denied outright.


GET /v1/pipelines — List All

curl -H "Authorization: Bearer lb_xxxx" https://api.zplflow.io/v1/pipelines
import requests

resp = requests.get(
    "https://api.zplflow.io/v1/pipelines",
    headers={"Authorization": "Bearer lb_xxxx"}
)
pipelines = resp.json()
for p in pipelines:
    print(f"{p['pipeline_id']}: {p['name']}")
package main

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

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

    var pipelines []struct {
        PipelineID string `json:"pipeline_id"`
        Name       string `json:"name"`
    }
    json.NewDecoder(resp.Body).Decode(&pipelines)
    for _, p := range pipelines {
        fmt.Printf("%s: %s\n", p.PipelineID, p.Name)
    }
}
<?php
$ch = curl_init("https://api.zplflow.io/v1/pipelines");
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer lb_xxxx"]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$pipelines = json_decode(curl_exec($ch), true);
foreach ($pipelines as $p) {
    echo "{$p['pipeline_id']}: {$p['name']}\n";
}

AS400 / SQL Native (IBM i)


SELECT SYSTOOLS.HTTPGETCLOB(
    'https://api.zplflow.io/v1/pipelines',
    NULL,
    '{"Authorization":"Bearer lb_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.io/v1/pipelines'
    : *null
    : %trim(response)
    : 'Authorization: Bearer lb_xxxx'
    : *null
    : *null
    : 30000
    : *null);

Returns an array of pipelines.

Errors: 403 INSUFFICIENT_SCOPE

Required API key scopes

Scope ID Tenant feature
pipelines:read pipelines

A key lacking pipelines:read receives 403 INSUFFICIENT_SCOPE once deny-by-default mode is active.

Response

[
  {
    "pipeline_id": "d4e5f6a7-b8c9-0123-4567-890abcdef0",
    "name": "Warehouse Label",
    "step_count": 2,
    "created_at": 1747132800,
    "updated_at": 1747132800
  }
]

GET /v1/pipelines/{id} — Get One

curl -H "Authorization: Bearer lb_xxxx" https://api.zplflow.io/v1/pipelines/d4e5f6a7-...
import requests

resp = requests.get(
    "https://api.zplflow.io/v1/pipelines/d4e5f6a7-b8c9-0123-4567-890abcdef0",
    headers={"Authorization": "Bearer lb_xxxx"}
)
pipeline = resp.json()
print(pipeline["name"], pipeline["steps"])
package main

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

func main() {
    req, _ := http.NewRequest("GET",
        "https://api.zplflow.io/v1/pipelines/d4e5f6a7-b8c9-0123-4567-890abcdef0", nil)
    req.Header.Set("Authorization", "Bearer lb_xxxx")
    resp, _ := http.DefaultClient.Do(req)
    defer resp.Body.Close()

    var pipeline struct {
        PipelineID string `json:"pipeline_id"`
        Name       string `json:"name"`
    }
    json.NewDecoder(resp.Body).Decode(&pipeline)
    fmt.Printf("%s\n", pipeline.Name)
}
<?php
$ch = curl_init("https://api.zplflow.io/v1/pipelines/d4e5f6a7-b8c9-0123-4567-890abcdef0");
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer lb_xxxx"]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$pipeline = json_decode(curl_exec($ch), true);
echo $pipeline['name'] . "\n";

AS400 / SQL Native (IBM i)


SELECT SYSTOOLS.HTTPGETCLOB(
    'https://api.zplflow.io/v1/pipelines/d4e5f6a7-b8c9-0123-4567-890abcdef0',
    NULL,
    '{"Authorization":"Bearer lb_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.io/v1/pipelines/d4e5f6a7-b8c9-0123-4567-890abcdef0'
    : *null
    : %trim(response)
    : 'Authorization: Bearer lb_xxxx'
    : *null
    : *null
    : 30000
    : *null);

Errors: 403 INSUFFICIENT_SCOPE

Required API key scopes

Scope ID Tenant feature
pipelines:read pipelines

A key lacking pipelines:read receives 403 INSUFFICIENT_SCOPE once deny-by-default mode is active.


DELETE /v1/pipelines/{id} — Delete

curl -X DELETE https://api.zplflow.io/v1/pipelines/d4e5f6a7-... \
  -H "Authorization: Bearer lb_xxxx"
import requests

resp = requests.delete(
    "https://api.zplflow.io/v1/pipelines/d4e5f6a7-b8c9-0123-4567-890abcdef0",
    headers={"Authorization": "Bearer lb_xxxx"}
)
# 204 No Content
package main

import (
    "net/http"
)

func main() {
    req, _ := http.NewRequest("DELETE",
        "https://api.zplflow.io/v1/pipelines/d4e5f6a7-b8c9-0123-4567-890abcdef0", nil)
    req.Header.Set("Authorization", "Bearer lb_xxxx")
    http.DefaultClient.Do(req)
}
<?php
$ch = curl_init("https://api.zplflow.io/v1/pipelines/d4e5f6a7-b8c9-0123-4567-890abcdef0");
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer lb_xxxx"]);
curl_exec($ch);

AS400 / SQL Native (IBM i)


SELECT SYSTOOLS.HTTPDELETECLOB(
    'https://api.zplflow.io/v1/pipelines/d4e5f6a7-b8c9-0123-4567-890abcdef0',
    NULL,
    '{"Authorization":"Bearer lb_xxxx"}'
) AS response
FROM SYSIBM.SYSDUMMY1;

AS400 / HTTPAPI (RPG)


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

rc = http_req('DELETE'
    : 'https://api.zplflow.io/v1/pipelines/d4e5f6a7-b8c9-0123-4567-890abcdef0'
    : *null
    : %trim(response)
    : 'Authorization: Bearer lb_xxxx'
    : *null
    : *null
    : 30000
    : *null);

Response: 204 No Content

Note: Pipelines cannot be modified in-place. To update a pipeline, delete the existing one and create a new one with the updated steps. Existing jobs using the old pipeline ID are unaffected.

Errors: 403 INSUFFICIENT_SCOPE

Required API key scopes

Scope ID Tenant feature
pipelines:write pipelines

A key lacking pipelines:write receives 403 INSUFFICIENT_SCOPE once deny-by-default mode is active. Deleting a pipeline does not require any other scope — the operation is independent of pipelines:read or pipelines:apply.


POST /v1/pipelines/{id}/apply — Apply to Documents

Idempotent: yes
Cost: sum of step costs × document count

Applies a saved pipeline to 1-5 ZPL documents concurrently.

{
  "documents": [
    {
      "zpl_base64": "XlpB...",
      "variables": {
        "sku": "ABC-001",
        "batch": "BATCH-001"
      }
    }
  ]
}

Response:

{
  "tokens_charged": 4,
  "documents": [
    { "zpl_base64": "XlpB..." }
  ]
}

Errors: 400 INVALID_PARAMS, 409 INSUFFICIENT_TOKENS, 403 INSUFFICIENT_SCOPE

Required API key scopes

Scope ID Tenant feature
pipelines:apply pipelines

A key lacking pipelines:apply receives 403 INSUFFICIENT_SCOPE. Wildcard legacy keys are permitted until deny-by-default mode is active; locked keys are denied outright.


POST /v1/pipelines/preview — Free Preview

Test pipeline steps without token charge. Returns transformed ZPL and PNG render.

{
  "zpl": "^XA^FO50,50^FDHello^FS^XZ",
  "steps": [{ "type": "replace_text", "search": "Hello", "replace": "World" }],
  "page": 0
}

Response:

{
  "zpl": "^XA\n^FO50,50^FDWorld^FS\n^XZ",
  "png_base64": "iVBORw0KGgo...",
  "page_count": 1,
  "current_page": 0
}

Errors: 403 INSUFFICIENT_SCOPE

Required API key scopes

Scope ID Tenant feature
pipelines:apply pipelines

Same gate as /v1/pipelines/{id}/apply. Although the endpoint is token-free, it is still subject to scope enforcement; a key without pipelines:apply is rejected before any rendering happens.


Prepackaged pipelines

The endpoints under /v1/pipelines/prepackaged* are public within the auth context — they require a valid API key but do not require any specific scope. They are reachable by locked keys (no scope grants), which makes them usable by audit and onboarding consumers.

Method Path Required scope
GET /v1/pipelines/prepackaged none (public)
GET /v1/pipelines/prepackaged/{id} none (public)
POST /v1/pipelines/prepackaged/{id}/adopt none (public)

The marker in the catalog is public: true; the runtime middleware grants these scopes implicitly to every authenticated key.


Next Steps