Skip to content
nikcli
Docs
Documentation

Loops

Loop definitions and their runs: list, upsert, generate from a description, toggle, run, pause, resume and abort, plus the run history.

Playground

Send these requests to your own running server. Values stay in this browser.

Not connected
How this works

Start a server with nikcli serve --hostname 127.0.0.1 --port 4096. Requests go straight from this page to that address — the browser allows it because loopback counts as a secure origin, and the server allows it because nikcli-ai.dev is in its CORS allowlist.

To try things without touching your real sessions or config, start the server with NIKCLI_TEST_HOME pointed at a throwaway directory — see the guided tour.

Operations

14 endpoints, generated from the server's own OpenAPI document. Every path also accepts the directory and workspace query parameters that select which instance handles the request.

get /loop loop.list
put /loop loop.upsert
get /loop/templates loop.templates
post /loop/generate loop.generate
get /loop/runs/recent loop.recentRuns
get /loop/{id} loop.get
post /loop/{id} loop.update
delete /loop/{id} loop.delete
post /loop/{id}/toggle loop.toggle
post /loop/{id}/run loop.run
post /loop/{id}/abort loop.abort
post /loop/{id}/pause loop.pause
post /loop/{id}/resume loop.resume
get /loop/{id}/runs loop.runs

Loop

get /loop # loop.list
Parameters
Name In Type Required
directory query string optional
workspace query string optional
Responses
200 LoopListOutput application/json
{
  loops: LoopDefinition[]
  runtimes: LoopRuntime[]
}
Example response captured
{
  "loops": [],
  "runtimes": []
}
SDK example
loop.list
ts
import { createNikcliClient } from "@nikcli-ai/sdk/httpapi"

const client = createNikcliClient()
await client.loop.list({
  ...
})
Try it
put /loop # loop.upsert
Parameters
Name In Type Required
directory query string optional
workspace query string optional
Request body · required · application/json
{
  name: string
  stages: LoopStage[]
  trigger: LoopTrigger
  maxRuns?: 
    | number
    | "Infinity" | "-Infinity" | "NaN"
  timeoutMs?: 
    | number
    | "Infinity" | "-Infinity" | "NaN"
  createPR?: boolean
  sandbox?: boolean
  worktree?: LoopWorktree
  paused?: boolean
  enabled?: boolean
}
Responses
200 LoopDefinition application/json
{
  id: string
  name: string
  stages: LoopStage[]
  trigger: LoopTrigger
  maxRuns?: 
    | number
    | "Infinity" | "-Infinity" | "NaN"
  timeoutMs?: 
    | number
    | "Infinity" | "-Infinity" | "NaN"
  createPR?: boolean
  sandbox?: boolean
  worktree?: LoopWorktree
  paused?: boolean
  enabled: boolean
  createdAt: 
    | number
    | "Infinity" | "-Infinity" | "NaN"
}
400 LoopValidationError application/json
{
  name: "ValidationError"
  data: { [key: string]: unknown }
}
Example response from schema
{
  "id": "",
  "name": "",
  "stages": [
    {
      "name": "",
      "agent": "",
      "model": "",
      "objective": "",
      "tokenBudget": 0
    }
  ],
  "trigger": {
    "kind": "manual"
  },
  "maxRuns": 0,
  "timeoutMs": 0,
  "createPR": false,
  "sandbox": false,
  "worktree": {
    "name": "",
    "branch": "",
    "directory": ""
  },
  "paused": false,
  "enabled": false,
  "createdAt": 0
}

Generated from the response schema — field values are placeholders, the shape is real.

SDK example
loop.upsert
ts
import { createNikcliClient } from "@nikcli-ai/sdk/httpapi"

const client = createNikcliClient()
await client.loop.upsert({
  ...
})
Try it

Templates

get /loop/templates # loop.templates
Parameters
Name In Type Required
directory query string optional
workspace query string optional
Responses
200 LoopTemplatesOutput application/json
{
  templates: LoopTemplate[]
}
Example response from schema
{
  "templates": [
    {
      "id": "",
      "title": "",
      "description": "",
      "draft": {
        "name": null,
        "stages": null,
        "intervalMs": null,
        "maxRuns": null
      }
    }
  ]
}

Generated from the response schema — field values are placeholders, the shape is real.

SDK example
loop.templates
ts
import { createNikcliClient } from "@nikcli-ai/sdk/httpapi"

const client = createNikcliClient()
await client.loop.templates({
  ...
})
Try it

Generate

post /loop/generate # loop.generate
Parameters
Name In Type Required
directory query string optional
workspace query string optional
Request body · required · application/json
{
  description: string
  model?: string
  agent?: string
  sessionID?: string
}
Responses
200 LoopDefinition application/json
{
  id: string
  name: string
  stages: LoopStage[]
  trigger: LoopTrigger
  maxRuns?: 
    | number
    | "Infinity" | "-Infinity" | "NaN"
  timeoutMs?: 
    | number
    | "Infinity" | "-Infinity" | "NaN"
  createPR?: boolean
  sandbox?: boolean
  worktree?: LoopWorktree
  paused?: boolean
  enabled: boolean
  createdAt: 
    | number
    | "Infinity" | "-Infinity" | "NaN"
}
400 LoopValidationError application/json
{
  name: "ValidationError"
  data: { [key: string]: unknown }
}
Example response from schema
{
  "id": "",
  "name": "",
  "stages": [
    {
      "name": "",
      "agent": "",
      "model": "",
      "objective": "",
      "tokenBudget": 0
    }
  ],
  "trigger": {
    "kind": "manual"
  },
  "maxRuns": 0,
  "timeoutMs": 0,
  "createPR": false,
  "sandbox": false,
  "worktree": {
    "name": "",
    "branch": "",
    "directory": ""
  },
  "paused": false,
  "enabled": false,
  "createdAt": 0
}

Generated from the response schema — field values are placeholders, the shape is real.

SDK example
loop.generate
ts
import { createNikcliClient } from "@nikcli-ai/sdk/httpapi"

const client = createNikcliClient()
await client.loop.generate({
  ...
})
Try it

Runs

get /loop/runs/recent # loop.recentRuns
Parameters
Name In Type Required
limit query string optional
directory query string optional
workspace query string optional
Responses
200 LoopRunsOutput application/json
{
  runs: LoopRun[]
}
Example response from schema
{
  "runs": [
    {
      "id": "",
      "loopID": "",
      "startedAt": 0,
      "endedAt": 0,
      "status": "running",
      "heartbeatAt": 0,
      "sessionID": "",
      "error": "",
      "ok": false,
      "pullRequest": {
        "number": null,
        "url": null,
        "branch": null,
        "base": null,
        "title": null,
        "action": null
      }
    }
  ]
}

Generated from the response schema — field values are placeholders, the shape is real.

SDK example
loop.recentRuns
ts
import { createNikcliClient } from "@nikcli-ai/sdk/httpapi"

const client = createNikcliClient()
await client.loop.recentRuns({
  ...
})
Try it

{Id}

get /loop/{id} # loop.get
Parameters
Name In Type Required
id path string required
directory query string optional
workspace query string optional
Responses
200 LoopGetOutput application/json
{
  loop: LoopDefinition
  runtime: LoopRuntime
}
404 LoopNotFound application/json
{
  name: "NotFound"
  data: { [key: string]: unknown }
}
Example response from schema
{
  "loop": {
    "id": "",
    "name": "",
    "stages": [
      {
        "name": null,
        "agent": null,
        "model": null,
        "objective": null,
        "tokenBudget": null
      }
    ],
    "trigger": {
      "kind": "manual"
    },
    "maxRuns": 0,
    "timeoutMs": 0,
    "createPR": false,
    "sandbox": false,
    "worktree": {
      "name": "",
      "branch": "",
      "directory": ""
    },
    "paused": false,
    "enabled": false,
    "createdAt": 0
  },
  "runtime": {
    "loopID": "",
    "status": "idle",
    "runs": 0,
    "lastRunAt": 0,
    "lastError": "",
    "sessionID": ""
  }
}

Generated from the response schema — field values are placeholders, the shape is real.

SDK example
loop.get
ts
import { createNikcliClient } from "@nikcli-ai/sdk/httpapi"

const client = createNikcliClient()
await client.loop.get({
  ...
})
Try it
post /loop/{id} # loop.update
Parameters
Name In Type Required
id path string required
directory query string optional
workspace query string optional
Request body · required · application/json
{
  name: string
  stages: LoopStage[]
  trigger: LoopTrigger
  maxRuns?: 
    | number
    | "Infinity" | "-Infinity" | "NaN"
  timeoutMs?: 
    | number
    | "Infinity" | "-Infinity" | "NaN"
  createPR?: boolean
  sandbox?: boolean
  worktree?: LoopWorktree
  paused?: boolean
  enabled: boolean
  createdAt: 
    | number
    | "Infinity" | "-Infinity" | "NaN"
}
Responses
200 LoopDefinition application/json
{
  id: string
  name: string
  stages: LoopStage[]
  trigger: LoopTrigger
  maxRuns?: 
    | number
    | "Infinity" | "-Infinity" | "NaN"
  timeoutMs?: 
    | number
    | "Infinity" | "-Infinity" | "NaN"
  createPR?: boolean
  sandbox?: boolean
  worktree?: LoopWorktree
  paused?: boolean
  enabled: boolean
  createdAt: 
    | number
    | "Infinity" | "-Infinity" | "NaN"
}
400 LoopValidationError application/json
{
  name: "ValidationError"
  data: { [key: string]: unknown }
}
404 LoopNotFound application/json
{
  name: "NotFound"
  data: { [key: string]: unknown }
}
Example response from schema
{
  "id": "",
  "name": "",
  "stages": [
    {
      "name": "",
      "agent": "",
      "model": "",
      "objective": "",
      "tokenBudget": 0
    }
  ],
  "trigger": {
    "kind": "manual"
  },
  "maxRuns": 0,
  "timeoutMs": 0,
  "createPR": false,
  "sandbox": false,
  "worktree": {
    "name": "",
    "branch": "",
    "directory": ""
  },
  "paused": false,
  "enabled": false,
  "createdAt": 0
}

Generated from the response schema — field values are placeholders, the shape is real.

SDK example
loop.update
ts
import { createNikcliClient } from "@nikcli-ai/sdk/httpapi"

const client = createNikcliClient()
await client.loop.update({
  ...
})
Try it
delete /loop/{id} # loop.delete
Parameters
Name In Type Required
id path string required
directory query string optional
workspace query string optional
Responses
200 LoopBooleanResult application/json
boolean
404 LoopNotFound application/json
{
  name: "NotFound"
  data: { [key: string]: unknown }
}
Example response from schema
false

Generated from the response schema — field values are placeholders, the shape is real.

SDK example
loop.delete
ts
import { createNikcliClient } from "@nikcli-ai/sdk/httpapi"

const client = createNikcliClient()
await client.loop.delete({
  ...
})
Try it

This changes or removes server state, against whatever base URL is set above.

post /loop/{id}/toggle # loop.toggle
Parameters
Name In Type Required
id path string required
directory query string optional
workspace query string optional
Request body · required · application/json
{
  enabled: boolean
}
Responses
200 LoopDefinition application/json
{
  id: string
  name: string
  stages: LoopStage[]
  trigger: LoopTrigger
  maxRuns?: 
    | number
    | "Infinity" | "-Infinity" | "NaN"
  timeoutMs?: 
    | number
    | "Infinity" | "-Infinity" | "NaN"
  createPR?: boolean
  sandbox?: boolean
  worktree?: LoopWorktree
  paused?: boolean
  enabled: boolean
  createdAt: 
    | number
    | "Infinity" | "-Infinity" | "NaN"
}
404 LoopNotFound application/json
{
  name: "NotFound"
  data: { [key: string]: unknown }
}
Example response from schema
{
  "id": "",
  "name": "",
  "stages": [
    {
      "name": "",
      "agent": "",
      "model": "",
      "objective": "",
      "tokenBudget": 0
    }
  ],
  "trigger": {
    "kind": "manual"
  },
  "maxRuns": 0,
  "timeoutMs": 0,
  "createPR": false,
  "sandbox": false,
  "worktree": {
    "name": "",
    "branch": "",
    "directory": ""
  },
  "paused": false,
  "enabled": false,
  "createdAt": 0
}

Generated from the response schema — field values are placeholders, the shape is real.

SDK example
loop.toggle
ts
import { createNikcliClient } from "@nikcli-ai/sdk/httpapi"

const client = createNikcliClient()
await client.loop.toggle({
  ...
})
Try it
post /loop/{id}/run # loop.run
Parameters
Name In Type Required
id path string required
directory query string optional
workspace query string optional
Request body · required · application/json
{
  sessionID?: string
}
Responses
200 LoopBooleanResult application/json
boolean
404 LoopNotFound application/json
{
  name: "NotFound"
  data: { [key: string]: unknown }
}
Example response from schema
false

Generated from the response schema — field values are placeholders, the shape is real.

SDK example
loop.run
ts
import { createNikcliClient } from "@nikcli-ai/sdk/httpapi"

const client = createNikcliClient()
await client.loop.run({
  ...
})
Try it
post /loop/{id}/abort # loop.abort
Parameters
Name In Type Required
id path string required
directory query string optional
workspace query string optional
Responses
200 LoopBooleanResult application/json
boolean
404 LoopNotFound application/json
{
  name: "NotFound"
  data: { [key: string]: unknown }
}
Example response from schema
false

Generated from the response schema — field values are placeholders, the shape is real.

SDK example
loop.abort
ts
import { createNikcliClient } from "@nikcli-ai/sdk/httpapi"

const client = createNikcliClient()
await client.loop.abort({
  ...
})
Try it
post /loop/{id}/pause # loop.pause
Parameters
Name In Type Required
id path string required
directory query string optional
workspace query string optional
Responses
200 LoopBooleanResult application/json
boolean
404 LoopNotFound application/json
{
  name: "NotFound"
  data: { [key: string]: unknown }
}
Example response from schema
false

Generated from the response schema — field values are placeholders, the shape is real.

SDK example
loop.pause
ts
import { createNikcliClient } from "@nikcli-ai/sdk/httpapi"

const client = createNikcliClient()
await client.loop.pause({
  ...
})
Try it
post /loop/{id}/resume # loop.resume
Parameters
Name In Type Required
id path string required
directory query string optional
workspace query string optional
Responses
200 LoopBooleanResult application/json
boolean
404 LoopNotFound application/json
{
  name: "NotFound"
  data: { [key: string]: unknown }
}
Example response from schema
false

Generated from the response schema — field values are placeholders, the shape is real.

SDK example
loop.resume
ts
import { createNikcliClient } from "@nikcli-ai/sdk/httpapi"

const client = createNikcliClient()
await client.loop.resume({
  ...
})
Try it
get /loop/{id}/runs # loop.runs
Parameters
Name In Type Required
id path string required
limit query string optional
directory query string optional
workspace query string optional
Responses
200 LoopRunsOutput application/json
{
  runs: LoopRun[]
}
Example response from schema
{
  "runs": [
    {
      "id": "",
      "loopID": "",
      "startedAt": 0,
      "endedAt": 0,
      "status": "running",
      "heartbeatAt": 0,
      "sessionID": "",
      "error": "",
      "ok": false,
      "pullRequest": {
        "number": null,
        "url": null,
        "branch": null,
        "base": null,
        "title": null,
        "action": null
      }
    }
  ]
}

Generated from the response schema — field values are placeholders, the shape is real.

SDK example
loop.runs
ts
import { createNikcliClient } from "@nikcli-ai/sdk/httpapi"

const client = createNikcliClient()
await client.loop.runs({
  ...
})
Try it

Schemas

The 20 schemas reachable from these endpoints. Each is shown one level deep; nested types link to their own entry below. Names ending in _2, _3 and so on are the OpenAPI emitter disambiguating same-named schemas declared by different HttpApi groups — they are distinct shapes, not duplicates.

LoopBooleanResult
boolean
LoopCreateInput
{
  name: string
  stages: LoopStage[]
  trigger: LoopTrigger
  maxRuns?: 
    | number
    | "Infinity" | "-Infinity" | "NaN"
  timeoutMs?: 
    | number
    | "Infinity" | "-Infinity" | "NaN"
  createPR?: boolean
  sandbox?: boolean
  worktree?: LoopWorktree
  paused?: boolean
  enabled?: boolean
}
LoopDefinition
{
  id: string
  name: string
  stages: LoopStage[]
  trigger: LoopTrigger
  maxRuns?: 
    | number
    | "Infinity" | "-Infinity" | "NaN"
  timeoutMs?: 
    | number
    | "Infinity" | "-Infinity" | "NaN"
  createPR?: boolean
  sandbox?: boolean
  worktree?: LoopWorktree
  paused?: boolean
  enabled: boolean
  createdAt: 
    | number
    | "Infinity" | "-Infinity" | "NaN"
}
LoopGenerateInput
{
  description: string
  model?: string
  agent?: string
  sessionID?: string
}
LoopGetOutput
{
  loop: LoopDefinition
  runtime: LoopRuntime
}
LoopListOutput
{
  loops: LoopDefinition[]
  runtimes: LoopRuntime[]
}
LoopNotFound
{
  name: "NotFound"
  data: { [key: string]: unknown }
}
LoopPullRequestRef
{
  number: 
    | number
    | "Infinity" | "-Infinity" | "NaN"
  url: string
  branch: string
  base: string
  title?: string
  action: "created" | "updated"
}
LoopRun
{
  id: string
  loopID: string
  startedAt: 
    | number
    | "Infinity" | "-Infinity" | "NaN"
  endedAt?: 
    | number
    | "Infinity" | "-Infinity" | "NaN"
  status: 
    | "running"
    | "complete"
    | "error"
    | "timeout"
    | "cancelled"
    | "orphaned"
  heartbeatAt?: 
    | number
    | "Infinity" | "-Infinity" | "NaN"
  sessionID?: string
  error?: string
  ok: boolean
  pullRequest?: LoopPullRequestRef
}
LoopRunInput
{
  sessionID?: string
}
LoopRunsOutput
{
  runs: LoopRun[]
}
LoopRuntime
{
  loopID: string
  status: 
    | "idle"
    | "running"
    | "paused"
    | "error"
    | "cancelling"
  runs: 
    | number
    | "Infinity" | "-Infinity" | "NaN"
  lastRunAt?: 
    | number
    | "Infinity" | "-Infinity" | "NaN"
  lastError?: string
  sessionID?: string
}
LoopStage
{
  name: string
  agent: string
  model?: string
  objective: string
  tokenBudget?: 
    | number
    | "Infinity" | "-Infinity" | "NaN"
}
LoopTemplate
{
  id: string
  title: string
  description: string
  draft: {
    name?: string
    stages: ({
      name?: string
      agent?: string
      model?: string
      objective: string
      tokenBudget?: 
        | number
        | "Infinity" | "-Infinity" | "NaN"
    })[]
    intervalMs?: 
      | number
      | "Infinity" | "-Infinity" | "NaN"
    maxRuns?: 
      | number
      | "Infinity" | "-Infinity" | "NaN"
  }
}
LoopTemplatesOutput
{
  templates: LoopTemplate[]
}
LoopToggleInput
{
  enabled: boolean
}
LoopTrigger
  | {
  kind: "manual"
}
  | {
  kind: "interval"
  everyMs: 
    | number
    | "Infinity" | "-Infinity" | "NaN"
}
LoopUpdateInput
{
  name: string
  stages: LoopStage[]
  trigger: LoopTrigger
  maxRuns?: 
    | number
    | "Infinity" | "-Infinity" | "NaN"
  timeoutMs?: 
    | number
    | "Infinity" | "-Infinity" | "NaN"
  createPR?: boolean
  sandbox?: boolean
  worktree?: LoopWorktree
  paused?: boolean
  enabled: boolean
  createdAt: 
    | number
    | "Infinity" | "-Infinity" | "NaN"
}
LoopValidationError
{
  name: "ValidationError"
  data: { [key: string]: unknown }
}
LoopWorktree
{
  name: string
  branch?: string
  directory: string
}