Docs
Search docs

How long a workflow's job usually takes, by phase

GET/api/workflows/{workflow_id}/jobs/{job_key}/duration-stats

getJobDurationStats

p50/p90 of one job's duration and of its capacity and boot phases, over its last 50 succeeded runs. Computed on read; a phase is null when no job in the window had it.

Authentication optional — this operation also accepts anonymous requests. When authenticating, use a bearer token or a session cookie.

curl

curl -X GET 'https://neosource.dev/api/workflows/WORKFLOW_ID/jobs/JOB_KEY/duration-stats' \
  -H 'Authorization: Bearer $NEOSOURCE_TOKEN'

fetch

fetch("https://neosource.dev/api/workflows/WORKFLOW_ID/jobs/JOB_KEY/duration-stats", {
  method: "GET",
  headers: {
    Authorization: "Bearer $NEOSOURCE_TOKEN",
  },
});

Path parameters

workflow_idrequired

string

job_keyrequired

The job's key (`WorkflowRunJobResponse.job_key`, matrix suffix included).

string

Responses

200Duration percentiles

application/json

JobDurationStatsResponse

object

How long one job of one workflow usually takes (`GET /api/workflows/{workflow_id}/jobs/{job_key}/duration-stats`), over its last `window` SUCCEEDED runs. Computed on read, never stored. Each phase is `null` when no job in the window had it: `capacity` and `boot` need a k8s runner that reports pod times, so they are `null` for history before that and for other backends. Pod times have one-second precision, and a phase that reads negative for that reason counts as zero.

boot
one of
  • DurationPercentilesResponse

    object

    The 50th and 90th percentile of one phase of a job, in milliseconds.

    p50_msrequired

    integerint64

    p90_msrequired

    integerint64

    samplesrequired

    integerint64

    How many jobs in the window had this phase.

  • null

capacity
one of
  • DurationPercentilesResponse

    object

    The 50th and 90th percentile of one phase of a job, in milliseconds.

    p50_msrequired

    integerint64

    p90_msrequired

    integerint64

    samplesrequired

    integerint64

    How many jobs in the window had this phase.

  • null

duration
one of
  • DurationPercentilesResponse

    object

    The 50th and 90th percentile of one phase of a job, in milliseconds.

    p50_msrequired

    integerint64

    p90_msrequired

    integerint64

    samplesrequired

    integerint64

    How many jobs in the window had this phase.

  • null

job_keyrequired

string

sample_sizerequired

integerint64

Succeeded jobs actually found (at most `window`).

windowrequired

integerint64

The most recent succeeded jobs considered.

workflow_idrequired

string

403Forbidden — one of: forbidden, needs_scope

application/json

one of
  • ErrorForbidden
  • NeedsScopeError

    object

    `403` body returned when a classic OAuth token is asked for something its granted scopes do not cover — an explicit private listing without `repo`, or the one-click GitHub mirror without `admin:public_key`. `scope` names the missing grant so the client can say exactly what the token lacks. There is no elevation route to widen it any more (a re-link goes through the GitHub App, whose tokens carry no scopes), so the client's answer is to say so, never to start a handshake.

    errorrequired

    string

    Always `needs_scope` — this body exists to carry the extra fields that kind needs.

    "needs_scope"

    scoperequired

    string

    The provider scope to request via elevation (e.g. `"repo"`).

Standard errors

Bodies documented once for the whole API — see standard errors.

  • 400Bad Request — one of: invalid_input
  • 404Not Found — one of: not_found
  • 429Rate limited — retry after the `Retry-After` header
  • 500Internal server error
  • 503Service temporarily unavailable / at capacity — retry after the `Retry-After` header
  • 504Gateway timeout — the request exceeded the server's handling budget