404. To ask for access, email hello@editframe.com.A pipeline is a named, versioned graph of steps. A run processes one video file through one version of a pipeline. Each step starts when the steps it needs have finished, so independent steps run in parallel.
Editframe runs the built-in steps: probe, transcription, frame sampling, scene detection, redaction and publishing. An endpoint step sends the media and earlier results to your own service. Filter and review steps decide whether the rest of a branch runs.
Every step writes its output to a namespace on the file. Later steps, filters and your own code read outputs by namespace. See Pipeline runs to monitor, cancel, retry and review runs.
Quick start
The pipeline SDK is the @editframe/api/pipelines entry point of @editframe/api. It is experimental and can change without a major version.
import {
Client,
createPipeline,
createPipelineRun,
getPipelineRun,
isPipelineRunTerminal,
} from "@editframe/api/pipelines";
const client = new Client(process.env.EDITFRAME_API_KEY);
const pipeline = await createPipeline(client, {
name: "speech-check",
definition: {
steps: {
probe: { type: "probe" },
transcript: { type: "transcribe", needs: ["probe"] },
has_speech: {
type: "filter",
needs: ["transcript"],
when: { path: "transcript.metadata.word_count", op: "gt", value: 0 },
},
publish: { type: "publish", needs: ["has_speech"] },
},
},
});
const { run } = await createPipelineRun(
client,
{ pipeline_id: pipeline.id, file_id: "your-video-file-id" },
{ idempotencyKey: "speech-check:your-video-file-id" },
);
let current = run;
while (!isPipelineRunTerminal(current)) {
await new Promise((resolve) => setTimeout(resolve, 5000));
current = await getPipelineRun(client, run.id);
}
console.log(current.status); // "succeeded", "failed", "filtered" or "cancelled"The same flow with the CLI, which reads the API key from EDITFRAME_TOKEN:
export EDITFRAME_TOKEN="$EDITFRAME_API_KEY"
npx editframe pipelines create speech-check --definition speech-check.json
npx editframe pipelines runs create PIPELINE_ID FILE_ID --watch--watch polls the run until it finishes and exits with status 1 when the run fails or is cancelled. See CLI: pipelines for every command.
Definitions
A definition is a JSON object with one field, steps. Each key of steps is a step key: 1–63 characters of lowercase letters, digits and underscores, starting with a letter. A definition has 1–64 steps.
| Field | Required | Description |
|---|---|---|
type | Yes | One of the step types |
needs | No | Keys of the steps that must finish first, at most 16. Default []. The graph cannot have cycles. |
namespace | No | Where the step's output is stored on the file. Same format as a step key, unique in the definition. Default: the step key. |
granularity | No | { "mode": "file" } (default) or { "mode": "segment", "window_ms", "overlap_ms" }. See Segment steps. |
with | Depends | Options of the step type |
when | Filters only | The predicate a filter step evaluates |
Unknown fields cause a validation error. Editframe stores the normalized definition: it records every default, sorts needs, kinds and formats, and computes a digest (sha256: followed by the SHA-256 of the canonical JSON). Equivalent definitions have the same digest.
Step types
| Type | Granularity | with | Output |
|---|---|---|---|
probe | file | Not allowed | Metadata: duration_ms, width, height, has_video, has_audio, source, tracks |
transcribe | file or segment | Not allowed | speech.segment and speech.word annotations with text. Metadata: track_id, segment_count, word_count, segments_included, words_included, truncated |
sample_frames | file or segment | Optional: every_ms | JPEG frames of the first video track. Metadata: every_ms, track_id, frame_count, and the frame list |
detect_scenes | file or segment | Optional: threshold | scene.cut annotations, with the scene score as confidence. Metadata: threshold, track_id, cut_count |
endpoint | file or segment | Required: endpoint_id, optional max_concurrency | What your service returns. See Endpoint steps. |
filter | file | Not allowed | No output. The step succeeds or is skipped. |
review | file | Optional: instructions, expires_after_ms, on_expiry | The decision and any corrected annotations. See Review steps. |
redact_render | file | Required: kinds | Metadata: render_id, status, output_url, file_id, kinds, annotation_count, box_count, muted_span_count |
publish | file | Optional: formats | Metadata: source_url, formats, and hls_url or dash_url for each format |
| Option | Step | Range | Default |
|---|---|---|---|
every_ms | sample_frames | 100–600,000 | 1000 |
threshold | detect_scenes | 0.01–1 | 0.3 |
endpoint_id | endpoint | An endpoint ID of your organization that is not archived | — |
max_concurrency | endpoint | 1–1024 requests of this step at a time | 8 |
instructions | review | 1–2000 characters | None |
expires_after_ms | review | 60,000 (one minute) to 2,592,000,000 (30 days) | 604,800,000 (seven days) |
on_expiry | review | fail, approve or reject | fail |
kinds | redact_render | 1–32 distinct annotation kinds | — |
formats | publish | One or both of hls and dash | ["hls"] |
Notes on the built-in steps:
probeis final: it runs once the whole file is available.transcribereads the first audio track. When an output would exceed the output limits, it keeps segments in time order and includes words only if all of them fit.truncatedreports that annotations were left out.sample_framestakes a frame at each multiple ofevery_ms. Endpoint steps that need it receive the frame URLs.redact_renderblurs every annotation of the selectedkindsthat has keyframes, and mutes the audio of every one without keyframes. It reads the annotations of every step it needs, directly or indirectly, with review corrections applied. It creates an ordinary render in your organization, which uses render minutes like any other render. When nothing matches, the step succeeds without rendering, and the output'sstatusisskipped. A failed render fails the step without an automatic retry.publishreturns playback URLs for the run's input file, not for aredact_renderresult. Useoutput_urlof theredact_renderoutput for the redacted video. Requests to the URLs need an API key, a session or a signed URL token.
Segment steps
A step with granularity { "mode": "segment", "window_ms": 60000, "overlap_ms": 2000 } processes the file in windows instead of all at once. window_ms is 1000–3,600,000. overlap_ms is 0 to half of window_ms, default 0. Only transcribe, sample_frames, detect_scenes and endpoint steps can use segment mode.
Windows start on fragment boundaries, so a window can be longer than window_ms and an overlap can be longer than overlap_ms. The last window ends at the end of the file. Each window has its own output, and its annotations must be inside the window.
Segment steps can start before an upload finishes. With a streamed upload, a segment step processes each window as soon as the media for it has arrived. This is true only when every step it needs is also a segment step: a whole-file step it needs must finish first, and whole-file steps wait for the complete file.
Branching with filters
A filter step evaluates its when predicate against the outputs of earlier steps. When the predicate is true, the filter succeeds. When it is false, the filter is skipped, and so is every step that needs it, directly or indirectly.
{
"type": "filter",
"needs": ["detector"],
"when": {
"any": [
{ "path": "detector.annotations", "op": "non_empty" },
{ "path": "detector.metadata.flagged", "op": "eq", "value": true }
]
}
}A predicate is a leaf or a combination of predicates:
{ "all": [...] }is true when every child is true.{ "any": [...] }is true when one child is true. Each takes 1–16 children.{ "not": predicate }inverts its child.- A leaf is
{ "path", "op", "value" }. Predicates nest at most 4 levels deep.
A path is <namespace>.metadata.<field>... or <namespace>.annotations..., with at most 16 segments. A numeric segment selects an array item, for example detector.annotations.0.kind. A filter can read only the namespaces of steps it needs, directly or indirectly.
op | value | True when the value at path |
|---|---|---|
exists | None | is not null |
non_empty | None | is a string, array or object that is not empty |
eq, ne | A string, number, boolean or null | equals, or does not equal, value |
gt, gte, lt, lte | A number | is a number greater than, at least, less than, or at most value |
in | 1–64 scalars | is a scalar in value |
contains | A scalar | is an array that contains value, or a string that contains the string value |
Every leaf is false when nothing is at path, including ne. Strings in value have at most 1024 characters.
A filter reads a merged view of each segment step: the annotations of every window, and each metadata field from the last window that sets it.
To branch, give two filters complementary predicates. The branch that is not taken is skipped, so the run ends filtered. A step that needs steps from both branches is always skipped. See Run statuses.
Review steps
A review step pauses its branch until a person approves or rejects the results of the steps before it.
{
"type": "review",
"needs": ["has_faces"],
"with": {
"instructions": "Check every face box. Remove false detections.",
"expires_after_ms": 86400000,
"on_expiry": "reject"
}
}When the review step starts, Editframe opens a review task, and the step waits in waiting_review. The decision changes the run:
- Approve: the step succeeds. The approval can include corrected annotations, which replace the original annotations of their kinds for every later step.
- Reject: the step is skipped, so the steps after it are skipped, and the run ends
filtered. - Expiry: when nobody decides within
expires_after_ms,on_expiryapplies.approvesucceeds the step with the decisionexpired.rejectskips the step.failfails the step and the run, and a run retry opens a new review task.
Decide reviews through the API, the SDK, the CLI or the dashboard. See Reviews.
Endpoint steps
An endpoint step sends a signed HTTP request to your service and uses the response as the step's output. First register the endpoint:
import { createPipelineEndpoint } from "@editframe/api/pipelines";
const endpoint = await createPipelineEndpoint(client, {
url: "https://models.example.com/editframe/faces",
headers: { Authorization: `Bearer ${process.env.MODEL_TOKEN}` },
timeout_ms: 30000,
callback_timeout_ms: 3600000,
max_concurrency: 16,
});
// Store this secret now. Editframe returns it only once.
console.log(endpoint.signing_secret); // "whsec_..."| Field | Range | Default | Description |
|---|---|---|---|
url | HTTPS, at most 2048 characters | — | Must resolve to a public address |
headers | At most 16 | {} | Headers sent with every request. Editframe returns only their names. |
timeout_ms | 1000–600,000 | 30,000 | How long Editframe waits for a response |
callback_timeout_ms | 1000–2,592,000,000 | 3,600,000 | How long Editframe waits for a callback after a 202 response |
max_concurrency | 1–1024 | 8 | Requests in flight to this endpoint, across all runs and steps |
You cannot set headers that Editframe sets itself: host, connection, keep-alive, upgrade, te, trailer, transfer-encoding, expect, content-length, content-type, content-encoding, user-agent, or any header that starts with proxy- or webhook-.
Endpoints cannot be changed. To change one or to rotate its secret, create an endpoint, create a pipeline version that uses it, and then archive the old endpoint. A new version cannot name an archived endpoint, and a run whose version names an archived endpoint fails with endpoint_not_found.
Then reference the endpoint from a step:
{
"type": "endpoint",
"needs": ["frames", "transcript"],
"granularity": { "mode": "segment", "window_ms": 60000 },
"with": { "endpoint_id": "6f1c2a9e-3b4d-4e5f-8a7b-1c2d3e4f5a6b", "max_concurrency": 16 }
}Request
Editframe sends POST with a JSON body and the User-Agent Editframe-Pipelines/1:
{
"pipeline_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
"pipeline_version": 12,
"pipeline_run_id": "3d0f6c1a-5b2e-4c7d-9f8a-1b2c3d4e5f60",
"step_run_id": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f",
"step_key": "detector",
"namespace": "detector",
"attempt": 1,
"idempotency_key": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f:1:14",
"external_id": "truck-1182/2026-10-01",
"deadline_at": "2026-10-02T04:15:30.000Z",
"file": {
"id": "7b8c9d0e-1f2a-4b3c-8d4e-5f6a7b8c9d0e",
"filename": "truck-1182.mp4",
"status": "receiving",
"duration_ms": null,
"tracks": [
{ "track_id": 1, "type": "video", "codec": "h264", "duration_ms": null },
{ "track_id": 2, "type": "audio", "codec": "aac", "duration_ms": null }
]
},
"window": { "index": 14, "start_ms": 840000, "end_ms": 900000, "overlap_ms": 0 },
"media": {
"expires_at": "2026-10-02T04:15:30.000Z",
"source_url": "https://editframe.com/api/v1/files/7b8c9d0e-…/tracks/1?media_token=…",
"index_url": "https://editframe.com/api/v1/files/7b8c9d0e-…/index?media_token=…",
"audio_url": "https://editframe.com/api/v1/files/7b8c9d0e-…/tracks/2?media_token=…",
"frames": [
{ "t_ms": 840000, "url": "https://editframe.com/api/v1/files/7b8c9d0e-…/pipeline-frames/…/840000?media_token=…" }
]
},
"metadata": {
"transcript": { "metadata": { "word_count": 112 }, "annotations": [] },
"frames": { "metadata": { "every_ms": 1000, "frame_count": 60 }, "annotations": [] }
},
"callback": {
"url": "https://editframe.com/api/v1/pipeline-callbacks/…",
"expires_at": "2026-10-02T04:15:30.000Z"
}
}windowisnullfor a file step.file.statusisreceivingwhile a streamed upload is still arriving, andreadyafter that. Durations arenulluntil the upload is sealed. A file that was uploaded whole also has the probedwidthandheightof video tracks, andsample_rateandchannelsof audio tracks.deadline_atandcallback.expires_atarecallback_timeout_msafter the request.metadataholds the outputs of every step this step needs, directly or indirectly, by namespace, with review corrections applied. For a segment step, annotations are limited to the window.mediaURLs need no API key. They work untilmedia.expires_at, at most four hours after the request.frameslists the frames of everysample_framesstep the step needs, at most 1000 per request.idempotency_keyis the same for every delivery of one attempt. Use it to deduplicate.
Signatures
Requests are signed with Standard Webhooks headers: webhook-id, webhook-timestamp and webhook-signature (v1, followed by a base64 HMAC-SHA256). Verify them with the endpoint's whsec_ secret and the raw request body:
import express from "express";
import { postPipelineCallback, verifyPipelineEndpointRequest } from "@editframe/api/pipelines";
const app = express();
app.post("/editframe/faces", express.raw({ type: "application/json" }), async (req, res) => {
let request;
try {
request = await verifyPipelineEndpointRequest({
secret: process.env.EDITFRAME_ENDPOINT_SECRET,
headers: req.headers,
body: req.body,
});
} catch {
return res.status(401).end();
}
// Accept the work now and post the output to the callback URL when it is done.
res.status(202).end();
const annotations = await detectFaces(request.media); // your model
await postPipelineCallback(request.callback.url, {
status: "succeeded",
output: { metadata: { face_count: annotations.length }, annotations },
});
});For short work, answer with the output instead: res.json({ metadata: { … }, annotations: [ … ] }).
verifyPipelineEndpointRequest accepts a clock difference of 300 seconds by default (toleranceSeconds) and throws PipelineSignatureError when the signature is missing, stale or wrong.
Responses
| Response | Result |
|---|---|
2xx except 202 | The body is the step's output. An empty body is an empty output. |
202 | The step waits for a callback, until callback_timeout_ms passes. |
408, 429, 5xx, timeout or connection error | Editframe retries the attempt. A Retry-After header of up to one hour sets the delay. |
Redirect or other 4xx | The step fails without a retry. |
An output larger than the output limit, or one that is not valid, fails the step.
Callbacks
After a 202, post one of these bodies to callback.url. The URL authenticates the attempt, so send no API key.
{ "status": "heartbeat" }
{ "status": "succeeded", "output": { "metadata": {}, "annotations": [] } }
{ "status": "failed", "error": { "message": "Model unavailable", "code": "model_unavailable", "retryable": true } }- A heartbeat extends the deadline to
callback_timeout_msfrom now. Send one before the deadline when work takes longer. error.messagehas 1–1000 characters.error.codeis optional: lowercase letters, digits and underscores, starting with a letter, at most 64 characters. A retryable failure starts another attempt when attempts remain.- A callback can arrive before the
202response. Editframe records it.
| Callback response | Meaning |
|---|---|
202 { "recorded": true } | Editframe recorded the callback. |
202 { "recorded": false } | Editframe already had this result. |
409 { "error": "step_not_waiting" } | The attempt is not waiting anymore, for example because the run was cancelled or the attempt timed out. |
422 { "error": "invalid_callback", "issues": [...] } | The body is not valid. |
Concurrency
Each endpoint step sends at most with.max_concurrency requests at a time, and each endpoint receives at most its own max_concurrency across all steps. An attempt that answered 202 does not hold a slot while it waits for the callback.
Versions
A pipeline has versions numbered from 1. createPipeline creates version 1, and versions cannot be changed. To change a pipeline, create a version:
import { createPipelineVersion } from "@editframe/api/pipelines";
const { version, created } = await createPipelineVersion(client, pipeline.id, definition);
// created is false when an existing version has the same digest; that version is returned.npx editframe pipelines versions create PIPELINE_ID --definition definition.jsonA run uses the latest version unless you pass version. Pipeline names are unique among the pipelines of your organization that are not archived. An archived pipeline accepts no new versions or runs (409 pipeline_archived).
Runs
Create a run with a pipeline and a video file. The Idempotency-Key header is optional, at most 255 characters, and unique in your organization: a repeated request with the same key and body returns the original run with status 200, and one with a different body returns 409 idempotency_conflict.
curl -X POST https://editframe.com/api/v1/pipeline-runs \
-H "Authorization: Bearer $EDITFRAME_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: speech-check:7b8c9d0e" \
-d '{"pipeline_id": "9a8b7c6d-…", "file_id": "7b8c9d0e-…", "external_id": "truck-1182"}'| Field | Required | Description |
|---|---|---|
pipeline_id | Yes | The pipeline |
version | No | A version number. Default: the latest version. |
file_id | Yes | A file of type video |
external_id | No | Your own identifier, at most 255 characters. Editframe returns it on the run, in webhooks and in endpoint requests. |
The response is the run with all of its steps. Steps start once the file's media is available. See Pipeline runs to follow the run.
Batches
A batch starts one run for each of up to 1000 files, with a limit on the runs in flight:
import { createPipelineBatch, getPipelineBatch } from "@editframe/api/pipelines";
const { batch } = await createPipelineBatch(
client,
{
pipeline_id: pipeline.id,
runs: fileIds.map((file_id) => ({ file_id })),
max_in_flight: 50,
},
{ idempotencyKey: "nightly-2026-10-01" },
);
const progress = await getPipelineBatch(client, batch.id);
// { status: "running", run_count: 4000, succeeded_count: …, failed_count: …, filtered_count: …, cancelled_count: … }npx editframe pipelines batches create PIPELINE_ID --files file-ids.txt --max-in-flight 50runshas 1–1000 entries with distinctfile_idvalues, each with an optionalexternal_id.max_in_flightis 1–100,000, default 100. Other runs staypendingand start oldest first as runs finish.- The batch is
completedwhen every run has finished. Unknown files return404 { "error": "file_not_found", "file_ids": [...] }. - In a
--filesfile, the CLI reads one file ID per line and ignores text after#.
Streamed uploads
A streamed upload sends a video as verified fragments, so a run can start before the upload finishes. Segment steps process each window as its media arrives. The file has the status receiving until the upload is sealed.
import { Client, createPipelineRun } from "@editframe/api/pipelines";
import { uploadFragmented } from "@editframe/api/pipelines/upload";
const result = await uploadFragmented(client, "dashcam.mp4", {
onCreated: async (file) => {
await createPipelineRun(
client,
{ pipeline_id: pipeline.id, file_id: file.id },
{ idempotencyKey: `upload:${file.id}` },
);
},
onProgress: (progress) => console.log(progress.watermarkMs), // verified media time
});
console.log(result.file.status); // "processing" after the sealnpx editframe pipelines upload dashcam.mp4 --run PIPELINE_ID --watchuploadFragmented accepts a file path, a Blob or File, or a stream. When an upload stops, resume it with fileId (SDK) or --resume FILE_ID (CLI) and the same media. A file has 1–8 tracks and at most one video track. In a browser, direct uploads need CORS on the storage bucket.
Outputs
A step output is a JSON object with metadata (an object, nested at most 32 levels) and annotations (an array). Both are optional. An output has at most 1 MiB and 10,000 annotations.
| Annotation field | Required | Description |
|---|---|---|
kind | Yes | Dot-separated lowercase names, for example face or speech.word, at most 128 characters |
start_ms, end_ms | Yes | Media time in milliseconds. end_ms is at least start_ms. |
label | No | At most 256 characters |
text | No | At most 10,000 characters |
confidence | No | 0–1 |
track_id | No | The track the annotation describes |
keyframes | No | At most 1000 boxes: { "t_ms", "x", "y", "w", "h" } |
Keyframe boxes use normalized frame coordinates from 0 to 1, with x + w and y + h at most 1. Their t_ms values increase and are between start_ms and end_ms.
Read the outputs of a run, one page per call, with each window's output for segment steps:
import { getPipelineRunOutputs, getFilePipelineMetadata } from "@editframe/api/pipelines";
const page = await getPipelineRunOutputs(client, run.id, "detector");
for (const { window, output } of page.outputs) {
console.log(window?.start_ms, output.annotations.length);
}
// The latest outputs a step wrote to a namespace on a file, from any run.
const latest = await getFilePipelineMetadata(client, fileId, "detector");listFilePipelineMetadata(client, fileId) lists the namespaces that steps have written on a file. Use iteratePipelineRunOutputs and iterateFilePipelineMetadata to read every page.
Limits
| Limit | Value |
|---|---|
| Steps per definition | 1–64 |
needs per step | 16 |
| Request body | 1 MiB |
| Pipeline name | 200 characters |
external_id, Idempotency-Key | 255 characters |
| Runs per batch | 1000 |
| Step output | 1 MiB, 10,000 annotations |
| Frames per endpoint request | 1000 |
| Attempts per step or window | 3, then 3 more for each retry of the run |
| List page size | 1–100, default 25 |
Errors
Validation errors return 422 with the field paths and issue codes:
{
"error": "invalid_definition",
"issues": [{ "path": "steps.gate.when.path", "code": "not_upstream" }]
}| Issue code | Meaning |
|---|---|
required | The field is missing. |
invalid_type | The value has the wrong JSON type. |
invalid_value | The value is not accepted, for example an unknown or archived endpoint_id. |
out_of_range | A number, length or count is outside its range. |
not_allowed | The field is not allowed here, for example with on a probe step. |
unknown_field | The field does not exist. |
duplicate | The value repeats, for example a namespace. |
unknown_step | needs names a step that does not exist. |
cycle | The steps need each other in a cycle. |
not_upstream | A filter reads a namespace of a step it does not need. |
The error field is invalid_definition for definitions and invalid_request for other request bodies. Other errors return { "error": code }, for example 409 pipeline_name_taken, 404 pipeline_not_found, 404 pipeline_version_not_found or 404 file_not_found. In the SDK, failed requests throw PipelineApiError with status, code, issues and fileIds.
Routes
All routes are under https://editframe.com and use an API key as a Bearer token, except the callback route. Lists take limit and cursor and return next_cursor, which is null on the last page.
| Route | SDK function |
|---|---|
POST /api/v1/pipelines | createPipeline |
GET /api/v1/pipelines | listPipelines, iteratePipelines |
GET /api/v1/pipelines/:id | getPipeline |
POST /api/v1/pipelines/:id/archive | archivePipeline |
POST /api/v1/pipelines/:id/versions | createPipelineVersion |
GET /api/v1/pipelines/:id/versions | listPipelineVersions, iteratePipelineVersions |
GET /api/v1/pipelines/:id/versions/:version | getPipelineVersion |
POST /api/v1/pipeline-endpoints | createPipelineEndpoint |
GET /api/v1/pipeline-endpoints | listPipelineEndpoints, iteratePipelineEndpoints |
GET /api/v1/pipeline-endpoints/:id | getPipelineEndpoint |
POST /api/v1/pipeline-endpoints/:id/archive | archivePipelineEndpoint |
POST /api/v1/pipeline-runs | createPipelineRun |
GET /api/v1/pipeline-runs | listPipelineRuns, iteratePipelineRuns |
GET /api/v1/pipeline-runs/:id | getPipelineRun |
POST /api/v1/pipeline-runs/:id/cancel | cancelPipelineRun |
POST /api/v1/pipeline-runs/:id/retry | retryPipelineRun |
GET /api/v1/pipeline-runs/:id/outputs/:namespace | getPipelineRunOutputs, iteratePipelineRunOutputs |
POST /api/v1/pipeline-batches | createPipelineBatch |
GET /api/v1/pipeline-batches | listPipelineBatches, iteratePipelineBatches |
GET /api/v1/pipeline-batches/:id | getPipelineBatch |
GET /api/v1/pipeline-reviews | listPipelineReviews, iteratePipelineReviews |
GET /api/v1/pipeline-reviews/:id | getPipelineReview |
POST /api/v1/pipeline-reviews/:id/decision | approvePipelineReview, rejectPipelineReview |
GET /api/v1/files/:id/pipeline-metadata | listFilePipelineMetadata |
GET /api/v1/files/:id/pipeline-metadata/:namespace | getFilePipelineMetadata, iterateFilePipelineMetadata |
POST /api/v1/pipeline-callbacks/:token | postPipelineCallback |
GET /api/v1/pipelines and GET /api/v1/pipeline-endpoints take include_archived=true. To be notified of run, step and batch changes, see Webhooks.