{
  "openapi": "3.1.0",
  "info": {
    "title": "Piston API",
    "version": "2026-10-22",
    "description": "Piston is Motor's sandboxed code runner: Docker images go in, and results come out. Agents, not people, write its configs and read its results, so each error says what to do.\n\n- Pack the files as a tar.zst, and register it with `POST /api/piston/sources`. `PUT` its bytes to `upload_url`, unless `upload_url` is null.\n- Check the config, `piston.star`, with `POST /api/piston/plans`, if you like.\n- Start a run with `POST /api/piston/runs`, with an `Idempotency-Key`.\n- Stream the logs with `GET /api/piston/runs/{run_id}/logs/stream`, which ends with the run, or get the run until `finished_at` isn't null.\n- Read the results in the run: each task's `status`, its steps' exit codes, its test `reports`, and `failure`. Download the outputs.\n\nEach request acts in one team: `team_id` is a query parameter of a `GET` or a `DELETE`, and a field of the body of a `POST` or a `PUT`. An agent token acts in its own team if you don't say. Sources and runs are billable: a team that can't pay gets a 402. A run that is `errored` failed because of Piston, not your code: start it again. The conventions of every Motor API are at https://new.motor.dev/docs."
  },
  "servers": [
    {
      "url": "https://new.motor.dev"
    }
  ],
  "security": [
    {
      "bearer": []
    },
    {
      "dpop": []
    }
  ],
  "tags": [
    {
      "name": "Sources",
      "description": "A source is a tar.zst of files, which a run's tasks find in their working directory. Piston keeps it for 30 days."
    },
    {
      "name": "Plans",
      "description": "A plan is the tasks of a run. A Starlark config in the source, `piston.star`, evaluates to one, or you send one as JSON."
    },
    {
      "name": "Runs",
      "description": "A run is some tasks of a plan, with the tasks that they need, on one source. Each task runs on its own new instance."
    },
    {
      "name": "Logs",
      "description": "The lines that the steps write, in order, with a cursor for each."
    },
    {
      "name": "Outputs",
      "description": "The files and directories that tasks make, which other tasks use as inputs."
    },
    {
      "name": "Volumes",
      "description": "Named disks of the team, which tasks mount: dependency caches, and Docker's image stores and layer caches."
    },
    {
      "name": "Secrets",
      "description": "Values that tasks get as environment variables, only when their plans name them."
    }
  ],
  "paths": {
    "/api/piston/sources": {
      "post": {
        "operationId": "create_source",
        "summary": "Register a source",
        "description": "Registers a source of the team by its digest: a tar.zst of the files that the tasks find in their working directory. Make it from the files, and send its SHA-256 digest and size. If `upload_url` isn't null, `PUT` the tar.zst's bytes to it, with no Motor credential, before `upload_expires_at`; then start a run with the source's `id`. If `upload_url` is null, Piston has the source already. Registering the same digest again is safe, and gives a new `upload_url` until the upload is done. Needs `piston:run` for an agent, and a team that can pay.",
        "tags": [
          "Sources"
        ],
        "responses": {
          "200": {
            "description": "The source, and where to upload it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SourceUpload"
                },
                "example": {
                  "source": {
                    "id": "src_1BbA6sXxyUWHYvKqN5T3dM",
                    "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
                    "size": 1843200,
                    "repo": "motor/aragorn",
                    "uploaded": false,
                    "created_at": "2026-10-22T09:00:00.000Z"
                  },
                  "upload_url": "https://piston-data.s3.amazonaws.com/teams/team_1BbA6sXxyUWHYvKqN5T3dM/sources/9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08.tar.zst?X-Amz-Signature=…",
                  "upload_expires_at": "2026-10-22T09:15:00.000Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/InvalidField"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "team_id": {
                    "type": "string",
                    "description": "The team's ID. An agent token acts in its own team if you don't say; a session key or a user key must say."
                  },
                  "sha256": {
                    "type": "string",
                    "pattern": "^[0-9a-f]{64}$",
                    "description": "The SHA-256 digest of the tar.zst, in lowercase hex."
                  },
                  "size": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "The size of the tar.zst, in bytes."
                  },
                  "repo": {
                    "type": "string",
                    "description": "Where the files came from, such as `motor/aragorn`, for people who read the run."
                  }
                },
                "required": [
                  "sha256",
                  "size"
                ],
                "additionalProperties": false
              },
              "example": {
                "team_id": "team_1BbA6sXxyUWHYvKqN5T3dM",
                "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
                "size": 1843200,
                "repo": "motor/aragorn"
              }
            }
          }
        }
      }
    },
    "/api/piston/plans": {
      "post": {
        "operationId": "evaluate_plan",
        "summary": "Check a plan",
        "description": "Evaluates the config of an uploaded source (`source_id`, with `path`), or checks a plan that you send as JSON (`plan`), without running it. Use it to find the problems of a config before a run: a config with problems is a 422 `invalid_plan` with each problem in `diagnostics`. Send `tasks` to keep only those tasks and the tasks that they need. Needs `piston:run` for an agent.",
        "tags": [
          "Plans"
        ],
        "responses": {
          "200": {
            "description": "The plan, as a run would run it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanResult"
                },
                "example": {
                  "plan": {
                    "tasks": [
                      {
                        "name": "test",
                        "platform": "linux/amd64",
                        "image": "golang:1.26",
                        "cpu": 4,
                        "memory_gb": 16,
                        "caches": {
                          "/root/go/pkg/mod": "go_mod"
                        },
                        "steps": [
                          {
                            "name": "vet",
                            "run": "go vet ./..."
                          },
                          {
                            "name": "test",
                            "run": "go test -json ./... > test.json",
                            "report": {
                              "format": "go_test_json",
                              "path": "test.json"
                            }
                          }
                        ]
                      }
                    ]
                  },
                  "diagnostics": []
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/InvalidPlan"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "team_id": {
                    "type": "string",
                    "description": "The team's ID. An agent token acts in its own team if you don't say; a session key or a user key must say."
                  },
                  "source_id": {
                    "type": "string",
                    "description": "The source whose config to evaluate. Send `source_id` or `plan`."
                  },
                  "path": {
                    "type": "string",
                    "description": "The config's path in the source: `piston.star` if you don't say. Only with `source_id`."
                  },
                  "plan": {
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/Plan"
                      }
                    ],
                    "description": "A plan to check, as JSON. Send `source_id` or `plan`."
                  },
                  "tasks": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "The tasks to keep, with the tasks that they need. All the tasks if you don't say."
                  }
                },
                "required": [],
                "additionalProperties": false
              },
              "example": {
                "team_id": "team_1BbA6sXxyUWHYvKqN5T3dM",
                "source_id": "src_1BbA6sXxyUWHYvKqN5T3dM",
                "path": "piston.star",
                "tasks": [
                  "test"
                ]
              }
            }
          }
        }
      }
    },
    "/api/piston/runs": {
      "post": {
        "operationId": "create_run",
        "summary": "Start a run",
        "description": "Starts a run of an uploaded source: the tasks of its config (`path`), or of a plan that you send (`plan`). Each task runs on its own new instance, in a container of its image, with the source in its working directory, and Piston bills its size from its first step to its end. Send an `Idempotency-Key`, so that a retry doesn't start a second run. A config with problems is a 422 `invalid_plan`, with each problem in `diagnostics`. Then follow the run: stream its logs, or get the run until `finished_at` isn't null. Needs `piston:run` for an agent, and a team that can pay.",
        "tags": [
          "Runs"
        ],
        "responses": {
          "201": {
            "description": "The run, queued. `Location` has its URL. A replay of an Idempotency-Key has `Idempotency-Replayed: true`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Run"
                },
                "example": {
                  "id": "run_1BbA6sXxyUWHYvKqN5T3dM",
                  "team_id": "team_1BbA6sXxyUWHYvKqN5T3dM",
                  "agent_id": "agent_1BbA6sXxyUWHYvKqN5T3dM",
                  "user_id": "user_1BbA6sXxyUWHYvKqN5T3dM",
                  "source_id": "src_1BbA6sXxyUWHYvKqN5T3dM",
                  "status": "queued",
                  "reason": null,
                  "created_at": "2026-10-22T09:00:00.000Z",
                  "started_at": null,
                  "finished_at": null,
                  "vcpu_milliseconds": 0,
                  "memory_byte_milliseconds": "0",
                  "tasks": [
                    {
                      "name": "test",
                      "status": "queued",
                      "platform": "linux/amd64",
                      "cpu": 4,
                      "memory_gb": 16,
                      "key": null,
                      "cached_from_run_id": null,
                      "started_at": null,
                      "finished_at": null,
                      "steps": [],
                      "outputs": [],
                      "reports": [],
                      "failure": null
                    }
                  ],
                  "plan": {
                    "tasks": [
                      {
                        "name": "test",
                        "platform": "linux/amd64",
                        "image": "golang:1.26@sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
                        "cpu": 4,
                        "memory_gb": 16,
                        "caches": {
                          "/root/go/pkg/mod": "go_mod"
                        },
                        "steps": [
                          {
                            "name": "vet",
                            "run": "go vet ./..."
                          },
                          {
                            "name": "test",
                            "run": "go test -json ./... > test.json",
                            "report": {
                              "format": "go_test_json",
                              "path": "test.json"
                            }
                          }
                        ]
                      }
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/InvalidPlan"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "team_id": {
                    "type": "string",
                    "description": "The team's ID. An agent token acts in its own team if you don't say; a session key or a user key must say."
                  },
                  "source_id": {
                    "type": "string",
                    "description": "The uploaded source to run on."
                  },
                  "path": {
                    "type": "string",
                    "description": "The config's path in the source: `piston.star` if you don't say. Send `path` or `plan`."
                  },
                  "plan": {
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/Plan"
                      }
                    ],
                    "description": "A plan to run, as JSON, instead of the source's config."
                  },
                  "tasks": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "The tasks to run, with the tasks that they need. All the tasks if you don't say."
                  }
                },
                "required": [
                  "source_id"
                ],
                "additionalProperties": false
              },
              "example": {
                "team_id": "team_1BbA6sXxyUWHYvKqN5T3dM",
                "source_id": "src_1BbA6sXxyUWHYvKqN5T3dM",
                "tasks": [
                  "test"
                ]
              }
            }
          }
        }
      },
      "get": {
        "operationId": "list_runs",
        "summary": "List the runs",
        "description": "Returns the team's runs, the newest first, without their tasks and plans. Get a run for its results. Needs `piston:read` for an agent.",
        "tags": [
          "Runs"
        ],
        "responses": {
          "200": {
            "description": "A page of runs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RunPage"
                },
                "example": {
                  "data": [
                    {
                      "id": "run_1BbA6sXxyUWHYvKqN5T3dM",
                      "team_id": "team_1BbA6sXxyUWHYvKqN5T3dM",
                      "agent_id": "agent_1BbA6sXxyUWHYvKqN5T3dM",
                      "user_id": "user_1BbA6sXxyUWHYvKqN5T3dM",
                      "source_id": "src_1BbA6sXxyUWHYvKqN5T3dM",
                      "status": "failed",
                      "reason": null,
                      "created_at": "2026-10-22T09:00:00.000Z",
                      "started_at": "2026-10-22T09:00:05.250Z",
                      "finished_at": "2026-10-22T09:01:12.500Z",
                      "vcpu_milliseconds": 268000,
                      "memory_byte_milliseconds": "1151051235328000"
                    }
                  ],
                  "next_cursor": null
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/InvalidField"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/TeamQuery"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ]
      }
    },
    "/api/piston/runs/{run_id}": {
      "get": {
        "operationId": "get_run",
        "summary": "Get a run",
        "description": "Returns a run with its results: each task's status, each step's exit code, the parsed test reports, the outputs, and `failure`, with the end of the log of the step that failed. The run is over once `finished_at` isn't null. Needs `piston:read` for an agent.",
        "tags": [
          "Runs"
        ],
        "responses": {
          "200": {
            "description": "The run.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Run"
                },
                "example": {
                  "id": "run_1BbA6sXxyUWHYvKqN5T3dM",
                  "team_id": "team_1BbA6sXxyUWHYvKqN5T3dM",
                  "agent_id": "agent_1BbA6sXxyUWHYvKqN5T3dM",
                  "user_id": "user_1BbA6sXxyUWHYvKqN5T3dM",
                  "source_id": "src_1BbA6sXxyUWHYvKqN5T3dM",
                  "status": "failed",
                  "reason": null,
                  "created_at": "2026-10-22T09:00:00.000Z",
                  "started_at": "2026-10-22T09:00:05.250Z",
                  "finished_at": "2026-10-22T09:01:12.500Z",
                  "vcpu_milliseconds": 268000,
                  "memory_byte_milliseconds": "1151051235328000",
                  "tasks": [
                    {
                      "name": "test",
                      "status": "failed",
                      "platform": "linux/amd64",
                      "cpu": 4,
                      "memory_gb": 16,
                      "key": null,
                      "cached_from_run_id": null,
                      "started_at": "2026-10-22T09:00:05.250Z",
                      "finished_at": "2026-10-22T09:01:12.500Z",
                      "steps": [
                        {
                          "name": "vet",
                          "status": "succeeded",
                          "exit_code": 0,
                          "started_at": "2026-10-22T09:00:05.250Z",
                          "finished_at": "2026-10-22T09:00:20.000Z"
                        },
                        {
                          "name": "test",
                          "status": "failed",
                          "exit_code": 1,
                          "started_at": "2026-10-22T09:00:20.000Z",
                          "finished_at": "2026-10-22T09:01:12.500Z"
                        }
                      ],
                      "outputs": [],
                      "reports": [
                        {
                          "step": "test",
                          "format": "go_test_json",
                          "passed": 41,
                          "failed": 1,
                          "skipped": 0,
                          "failures": [
                            {
                              "name": "github.com/motorteam/aragorn/lib/ids.TestNew",
                              "message": "ids_test.go:12: got \"run_\", want \"src_\"",
                              "file": "lib/ids/ids_test.go",
                              "line": 12
                            }
                          ]
                        }
                      ],
                      "failure": {
                        "step": "test",
                        "message": "The step exited with 1.",
                        "log_tail": "--- FAIL: TestNew (0.00s)\n    ids_test.go:12: got \"run_\", want \"src_\"\nFAIL"
                      }
                    }
                  ],
                  "plan": {
                    "tasks": [
                      {
                        "name": "test",
                        "platform": "linux/amd64",
                        "image": "golang:1.26@sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
                        "cpu": 4,
                        "memory_gb": 16,
                        "caches": {
                          "/root/go/pkg/mod": "go_mod"
                        },
                        "steps": [
                          {
                            "name": "vet",
                            "run": "go vet ./..."
                          },
                          {
                            "name": "test",
                            "run": "go test -json ./... > test.json",
                            "report": {
                              "format": "go_test_json",
                              "path": "test.json"
                            }
                          }
                        ]
                      }
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/InvalidField"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/RunID"
          },
          {
            "$ref": "#/components/parameters/TeamQuery"
          }
        ]
      }
    },
    "/api/piston/runs/{run_id}/cancel": {
      "post": {
        "operationId": "cancel_run",
        "summary": "Cancel a run",
        "description": "Cancels the tasks of a run that haven't ended, and terminates their instances. A task that ended keeps its result. Canceling a run that is over changes nothing. Needs `piston:run` for an agent.",
        "tags": [
          "Runs"
        ],
        "responses": {
          "200": {
            "description": "The run.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Run"
                },
                "example": {
                  "id": "run_1BbA6sXxyUWHYvKqN5T3dM",
                  "team_id": "team_1BbA6sXxyUWHYvKqN5T3dM",
                  "agent_id": "agent_1BbA6sXxyUWHYvKqN5T3dM",
                  "user_id": "user_1BbA6sXxyUWHYvKqN5T3dM",
                  "source_id": "src_1BbA6sXxyUWHYvKqN5T3dM",
                  "status": "canceled",
                  "reason": "canceled",
                  "created_at": "2026-10-22T09:00:00.000Z",
                  "started_at": "2026-10-22T09:00:05.250Z",
                  "finished_at": "2026-10-22T09:01:12.500Z",
                  "vcpu_milliseconds": 268000,
                  "memory_byte_milliseconds": "1151051235328000",
                  "tasks": [
                    {
                      "name": "test",
                      "status": "canceled",
                      "platform": "linux/amd64",
                      "cpu": 4,
                      "memory_gb": 16,
                      "key": null,
                      "cached_from_run_id": null,
                      "started_at": "2026-10-22T09:00:05.250Z",
                      "finished_at": "2026-10-22T09:01:12.500Z",
                      "steps": [
                        {
                          "name": "vet",
                          "status": "succeeded",
                          "exit_code": 0,
                          "started_at": "2026-10-22T09:00:05.250Z",
                          "finished_at": "2026-10-22T09:00:20.000Z"
                        },
                        {
                          "name": "test",
                          "status": "failed",
                          "exit_code": 1,
                          "started_at": "2026-10-22T09:00:20.000Z",
                          "finished_at": "2026-10-22T09:01:12.500Z"
                        }
                      ],
                      "outputs": [],
                      "reports": [],
                      "failure": {
                        "step": "test",
                        "message": "The run was canceled.",
                        "log_tail": ""
                      }
                    }
                  ],
                  "plan": {
                    "tasks": [
                      {
                        "name": "test",
                        "platform": "linux/amd64",
                        "image": "golang:1.26@sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
                        "cpu": 4,
                        "memory_gb": 16,
                        "caches": {
                          "/root/go/pkg/mod": "go_mod"
                        },
                        "steps": [
                          {
                            "name": "vet",
                            "run": "go vet ./..."
                          },
                          {
                            "name": "test",
                            "run": "go test -json ./... > test.json",
                            "report": {
                              "format": "go_test_json",
                              "path": "test.json"
                            }
                          }
                        ]
                      }
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/InvalidField"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/RunID"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "team_id": {
                    "type": "string",
                    "description": "The team's ID. An agent token acts in its own team if you don't say; a session key or a user key must say."
                  }
                },
                "required": [],
                "additionalProperties": false
              },
              "example": {
                "team_id": "team_1BbA6sXxyUWHYvKqN5T3dM"
              }
            }
          }
        }
      }
    },
    "/api/piston/runs/{run_id}/logs": {
      "get": {
        "operationId": "list_logs",
        "summary": "Read the logs",
        "description": "Returns a run's log lines after the cursor `after`, in order. Poll with `next_cursor` as `after` until the run is over, or stream instead. Needs `piston:read` for an agent.",
        "tags": [
          "Logs"
        ],
        "responses": {
          "200": {
            "description": "A page of lines.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LogPage"
                },
                "example": {
                  "data": [
                    {
                      "task": "test",
                      "step": 1,
                      "stream": "stdout",
                      "text": "ok  \tgithub.com/motorteam/aragorn/lib/ids\t0.012s",
                      "at": "2026-10-22T09:00:05.250Z",
                      "cursor": "c_000000000000002a"
                    }
                  ],
                  "next_cursor": "c_000000000000002a",
                  "has_more": false
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/InvalidField"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/RunID"
          },
          {
            "$ref": "#/components/parameters/TeamQuery"
          },
          {
            "name": "task",
            "in": "query",
            "required": false,
            "description": "Only the lines of this task. The lines of all the tasks if you don't say.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "after",
            "in": "query",
            "required": false,
            "description": "The cursor of the last line that you have. The start of the log if you don't say.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "The most lines in the page, from 1 to 5000. 1000 if you don't say.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 5000
            }
          }
        ]
      }
    },
    "/api/piston/runs/{run_id}/logs/stream": {
      "get": {
        "operationId": "stream_logs",
        "summary": "Stream the logs",
        "description": "Streams a run's log lines as server-sent events while it runs, from after the cursor in `Last-Event-ID` or `after`, or from the start. Each line is `id: <cursor>`, `event: line`, and `data: <the line as JSON>`. When the run ends, `event: end` has the run as JSON, with its results, and the stream ends: don't connect again. `event: error` has an error body, and ends the stream: connect again with the last cursor if the error is `retryable`. A comment comes every 25 seconds. A stream ends after an hour; connect again with the cursor of the last line that you got. Needs `piston:read` for an agent.",
        "tags": [
          "Logs"
        ],
        "responses": {
          "200": {
            "description": "The stream.",
            "content": {
              "text/event-stream": {
                "schema": {
                  "type": "string"
                },
                "example": "id: c_000000000000002a\nevent: line\ndata: {\"task\":\"test\",\"step\":1,\"stream\":\"stdout\",\"text\":\"ok  \\tgithub.com/motorteam/aragorn/lib/ids\\t0.012s\",\"at\":\"2026-10-22T09:00:05.250Z\",\"cursor\":\"c_000000000000002a\"}\n\nevent: end\ndata: {\"id\":\"run_1BbA6sXxyUWHYvKqN5T3dM\",\"status\":\"succeeded\",…}\n\n"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/InvalidField"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/RunID"
          },
          {
            "$ref": "#/components/parameters/TeamQuery"
          },
          {
            "name": "task",
            "in": "query",
            "required": false,
            "description": "Only the lines of this task. The lines of all the tasks if you don't say.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "after",
            "in": "query",
            "required": false,
            "description": "The cursor of the last line that you have.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Last-Event-ID",
            "in": "header",
            "required": false,
            "description": "The cursor of the last line that you got; EventSource clients send it when they connect again.",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/piston/runs/{run_id}/outputs/{task}/{output}": {
      "get": {
        "operationId": "download_output",
        "summary": "Download an output",
        "description": "Redirects (302) to a download of an output of a task, which works for 15 minutes. Follow `Location` without your credential, such as with `curl -L`. A directory downloads as a tar.zst. A task that reused a cached result has the outputs of that result. Needs `piston:read` for an agent.",
        "tags": [
          "Outputs"
        ],
        "responses": {
          "302": {
            "description": "The output. `Location` has the URL of the download.",
            "headers": {
              "Location": {
                "description": "The URL that downloads the output.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OutputDownload"
                },
                "example": {
                  "output": {
                    "name": "motor",
                    "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
                    "size": 31457280,
                    "directory": false,
                    "download_url": "https://new.motor.dev/api/piston/runs/run_1BbA6sXxyUWHYvKqN5T3dM/outputs/build_amd64/motor"
                  },
                  "url": "https://piston-data.s3.amazonaws.com/teams/team_1BbA6sXxyUWHYvKqN5T3dM/blobs/9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08?X-Amz-Signature=…"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/InvalidField"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/RunID"
          },
          {
            "name": "task",
            "in": "path",
            "required": true,
            "description": "The task's name.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "output",
            "in": "path",
            "required": true,
            "description": "The output's name in the task.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/TeamQuery"
          }
        ]
      }
    },
    "/api/piston/volumes": {
      "get": {
        "operationId": "list_volumes",
        "summary": "List the volumes",
        "description": "Returns the team's volumes: the caches of tasks, and the image stores and layer caches of their Docker daemons. A task gets a copy of its volume, and a task that succeeds commits its changes. Needs `piston:read` for an agent.",
        "tags": [
          "Volumes"
        ],
        "responses": {
          "200": {
            "description": "The volumes.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VolumePage"
                },
                "example": {
                  "data": [
                    {
                      "id": "vol_1BbA6sXxyUWHYvKqN5T3dM",
                      "key": "go_mod",
                      "size": 734003200,
                      "committed_by_run_id": "run_1BbA6sXxyUWHYvKqN5T3dM",
                      "committed_at": "2026-10-22T09:01:12.500Z",
                      "used_at": "2026-10-22T09:01:12.500Z"
                    }
                  ],
                  "next_cursor": null
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/InvalidField"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/TeamQuery"
          }
        ]
      }
    },
    "/api/piston/volumes/{volume_id}": {
      "delete": {
        "operationId": "delete_volume",
        "summary": "Delete a volume",
        "description": "Deletes a volume, such as a cache that has bad files. The next task with its key starts with an empty volume. Needs `piston:run` for an agent.",
        "tags": [
          "Volumes"
        ],
        "responses": {
          "204": {
            "description": "The volume is deleted."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/InvalidField"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "name": "volume_id",
            "in": "path",
            "required": true,
            "description": "The volume's ID.",
            "schema": {
              "type": "string",
              "pattern": "^vol_"
            }
          },
          {
            "$ref": "#/components/parameters/TeamQuery"
          }
        ]
      }
    },
    "/api/piston/secrets": {
      "get": {
        "operationId": "list_secrets",
        "summary": "List the secrets",
        "description": "Returns the names of the team's secrets, never their values. A task gets a secret only when its plan names it in `secrets`. Needs `piston:secrets` for an agent.",
        "tags": [
          "Secrets"
        ],
        "responses": {
          "200": {
            "description": "The secrets.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SecretPage"
                },
                "example": {
                  "data": [
                    {
                      "name": "registry_token",
                      "updated_at": "2026-10-22T09:00:00.000Z"
                    }
                  ],
                  "next_cursor": null
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/InvalidField"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/TeamQuery"
          }
        ]
      }
    },
    "/api/piston/secrets/{name}": {
      "put": {
        "operationId": "put_secret",
        "summary": "Set a secret",
        "description": "Sets a secret of the team, or replaces its value. Piston encrypts the value, and gives it only to the tasks whose plans name it; nobody reads it back. Needs `piston:secrets` for an agent.",
        "tags": [
          "Secrets"
        ],
        "responses": {
          "200": {
            "description": "The secret, without its value.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Secret"
                },
                "example": {
                  "name": "registry_token",
                  "updated_at": "2026-10-22T09:00:00.000Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/InvalidField"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "required": true,
            "description": "The secret's name, in snake case, such as `registry_token`.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z][a-z0-9_]*$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "team_id": {
                    "type": "string",
                    "description": "The team's ID. An agent token acts in its own team if you don't say; a session key or a user key must say."
                  },
                  "value": {
                    "type": "string",
                    "maxLength": 65536,
                    "description": "The secret's value, at most 64 KiB."
                  }
                },
                "required": [
                  "value"
                ],
                "additionalProperties": false
              },
              "example": {
                "team_id": "team_1BbA6sXxyUWHYvKqN5T3dM",
                "value": "ghp_…"
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "delete_secret",
        "summary": "Delete a secret",
        "description": "Deletes a secret of the team. A run that starts later and names it fails its check. Needs `piston:secrets` for an agent.",
        "tags": [
          "Secrets"
        ],
        "responses": {
          "204": {
            "description": "The secret is deleted."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/InvalidField"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "required": true,
            "description": "The secret's name, in snake case, such as `registry_token`.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z][a-z0-9_]*$"
            }
          },
          {
            "$ref": "#/components/parameters/TeamQuery"
          }
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "A session key (`motor_sk_`), a user key (`motor_uk_`), or an agent token (`motor_tk_`)."
      },
      "dpop": {
        "type": "http",
        "scheme": "DPoP",
        "description": "A session key that is bound to a device key, with a `DPoP` header that holds a proof of the device key (RFC 9449)."
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "A key, 1 to 128 printable characters, that makes the request happen once: for 24 hours, the same key and request get the first answer again.",
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128
        }
      },
      "Limit": {
        "name": "limit",
        "in": "query",
        "required": false,
        "description": "The most items in the page, from 1 to 100. 50 if you don't say.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100
        }
      },
      "Cursor": {
        "name": "cursor",
        "in": "query",
        "required": false,
        "description": "The `next_cursor` of the last page.",
        "schema": {
          "type": "string"
        }
      },
      "TeamQuery": {
        "name": "team_id",
        "in": "query",
        "required": false,
        "description": "The team's ID. An agent token acts in its own team if you don't say; a session key or a user key must say.",
        "schema": {
          "type": "string",
          "pattern": "^team_"
        }
      },
      "RunID": {
        "name": "run_id",
        "in": "path",
        "required": true,
        "description": "The run's ID.",
        "schema": {
          "type": "string",
          "pattern": "^run_"
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "The body isn't a JSON object, or the request can't go on.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "invalid_json",
                "message": "The body isn't a JSON object: unexpected EOF.",
                "retryable": false
              }
            }
          }
        }
      },
      "Unauthorized": {
        "description": "The credential is missing, unknown, revoked, or expired, or a bound session key has no valid DPoP proof.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "unauthorized",
                "message": "The credential is unknown, revoked, or expired.",
                "retryable": false
              }
            }
          }
        }
      },
      "Forbidden": {
        "description": "The credential can't do this: `insufficient_scope` (the agent needs the scope that the message names: ask for an agent with it), `forbidden`, or `team_suspended`.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "insufficient_scope",
                "message": "The agent's scopes don't include piston:run.",
                "retryable": false
              }
            }
          }
        }
      },
      "NotFound": {
        "description": "There is no such resource in the team, or the credential can't see it. Check the ID, and `team_id`.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "not_found",
                "message": "There is no run run_1BbA6sXxyUWHYvKqN5T3dM in the team.",
                "retryable": false
              }
            }
          }
        }
      },
      "Conflict": {
        "description": "The state of things doesn't allow the request, such as a run of a source that isn't uploaded yet: the message says what to do. `idempotency_conflict`: the Idempotency-Key was used with another request; use a new key. `request_in_progress`: the first request with the key hasn't ended; retry after `retry_after_seconds`.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "conflict",
                "message": "The source src_1BbA6sXxyUWHYvKqN5T3dM isn't uploaded yet: PUT it to its upload_url first.",
                "retryable": false
              }
            }
          }
        }
      },
      "InvalidField": {
        "description": "A field or a parameter has a value that Motor or Piston can't accept: `field` names it, and the message says what to send.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "invalid_field",
                "message": "`sha256` must be the SHA-256 digest of the source's tar.zst, as 64 lowercase hex digits.",
                "retryable": false,
                "field": "sha256"
              }
            }
          }
        }
      },
      "RateLimited": {
        "description": "The credential made more than 600 requests in a minute, or Piston is at its capacity. Wait `retry_after_seconds`, and retry.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "rate_limited",
                "message": "Too many requests: the limit is 600 a minute.",
                "retryable": true,
                "retry_after_seconds": 12
              }
            }
          }
        }
      },
      "PaymentRequired": {
        "description": "The team can't pay for new work: `billing_required` (the team needs a card: an admin adds one in the console) or `over_limit` (the team or the agent spent its spending limit for the month: an admin raises the team's limit, or the person or agent that started the agent starts one with a higher limit). Retrying doesn't help until that changes.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "over_limit",
                "message": "The team spent its spending limit for this month, so Piston doesn't start new work for it. An admin raises the limit at https://new.motor.dev/console, or the work waits until next month.",
                "retryable": false
              }
            }
          }
        }
      },
      "InvalidPlan": {
        "description": "`invalid_plan`: the config or the plan has problems, each in `diagnostics`, with its file, line, column, the path of its field, a stable code, and what to do. Fix all of them, and send the request again. `invalid_field`: a field of the request has a problem, which `field` names.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "invalid_plan",
                "message": "The plan has 1 problem. Fix each one, and send the request again.\npiston.star:14:9: tasks.test.caches: \"go mod\" isn't snake case; use \"go_mod\" [P0107]",
                "retryable": false,
                "diagnostics": [
                  {
                    "file": "piston.star",
                    "line": 14,
                    "column": 9,
                    "path": "tasks.test.caches",
                    "code": "P0107",
                    "message": "\"go mod\" isn't snake case; use \"go_mod\""
                  }
                ]
              }
            }
          }
        }
      },
      "Unavailable": {
        "description": "Piston can't answer now (`temporarily_unavailable`): wait `retry_after_seconds`, and retry the same request, with the same Idempotency-Key. `unavailable`: this Motor isn't set up to call Piston.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "temporarily_unavailable",
                "message": "Piston can't answer now. Try again in a moment.",
                "retryable": true,
                "retry_after_seconds": 1
              }
            }
          }
        }
      }
    },
    "schemas": {
      "Source": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The source's ID, `src_…`, for `source_id`."
          },
          "sha256": {
            "type": "string",
            "description": "The SHA-256 digest of the source's tar.zst, in lowercase hex."
          },
          "size": {
            "type": "integer",
            "description": "The size of the tar.zst, in bytes."
          },
          "repo": {
            "type": [
              "string",
              "null"
            ],
            "description": "Where the files came from, such as `motor/aragorn`, or null."
          },
          "uploaded": {
            "type": "boolean",
            "description": "Whether Piston has the source's bytes. A run needs an uploaded source."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the team registered the source."
          }
        },
        "required": [
          "id",
          "sha256",
          "size",
          "repo",
          "uploaded",
          "created_at"
        ],
        "additionalProperties": false,
        "title": "Source"
      },
      "SourceUpload": {
        "type": "object",
        "properties": {
          "source": {
            "$ref": "#/components/schemas/Source"
          },
          "upload_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "The URL to `PUT` the bytes of the tar.zst to, with no Motor credential, before `upload_expires_at`. Null when Piston has the source already: go on to the run."
          },
          "upload_expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When `upload_url` stops working. Register the source again for a new URL."
          }
        },
        "required": [
          "source",
          "upload_url",
          "upload_expires_at"
        ],
        "additionalProperties": false
      },
      "Diagnostic": {
        "type": "object",
        "properties": {
          "file": {
            "type": "string",
            "description": "The config's file, such as `piston.star`, when the problem is in a config."
          },
          "line": {
            "type": "integer",
            "description": "The line in `file`, from 1."
          },
          "column": {
            "type": "integer",
            "description": "The column in `line`, from 1."
          },
          "path": {
            "type": "string",
            "description": "The path of the field in the plan, such as `tasks.test.caches`."
          },
          "code": {
            "type": "string",
            "description": "A stable code of the problem, such as `P0107`."
          },
          "message": {
            "type": "string",
            "description": "What is wrong, and how to fix it."
          }
        },
        "required": [
          "code",
          "message"
        ],
        "additionalProperties": false,
        "title": "Diagnostic",
        "description": "A problem of a config or a plan."
      },
      "PlanTask": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "The task's name, unique in the plan, in snake case."
          },
          "platform": {
            "type": "string",
            "enum": [
              "linux/amd64",
              "linux/arm64",
              "windows/amd64"
            ],
            "description": "`linux/amd64` if you don't say."
          },
          "image": {
            "type": "string",
            "description": "An OCI image reference, such as `golang:1.26`. A Linux task needs one. In a run, it has its digest."
          },
          "cpu": {
            "type": "integer",
            "enum": [
              2,
              4,
              8,
              16,
              32,
              48
            ],
            "description": "The number of vCPUs."
          },
          "memory_gb": {
            "type": "integer",
            "description": "2, 4, or 8 GB for each vCPU: 4 for each vCPU if you don't say."
          },
          "workdir": {
            "type": "string",
            "description": "The working directory in the container, where the source is: `/workspace` (`C:\\workspace` on Windows) if you don't say."
          },
          "env": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Environment variables of each step."
          },
          "secrets": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Environment variables whose values are the team's secrets: the variable's name → the secret's name."
          },
          "caches": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Volumes at paths of the container: the path → the volume's key, in snake case."
          },
          "docker": {
            "type": "object",
            "properties": {
              "cache": {
                "type": "string",
                "description": "The key of the volume of the daemon's images and layer cache. A new, empty daemon if you don't say."
              }
            },
            "required": [],
            "description": "A Docker daemon of the task's own, at /var/run/docker.sock."
          },
          "inputs": {
            "type": "object",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "task": {
                  "type": "string"
                },
                "output": {
                  "type": "string"
                }
              },
              "required": [
                "task",
                "output"
              ]
            },
            "description": "Outputs of other tasks, at paths relative to the working directory: the path → the output."
          },
          "needs": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The tasks that must succeed before this task starts, besides the tasks of `inputs`."
          },
          "steps": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string",
                  "description": "The step's name in results and logs. A name from the command if you don't say."
                },
                "run": {
                  "type": "string",
                  "description": "A script for `sh -e` (`pwsh` on Windows). Piston never puts text into it."
                },
                "argv": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "A command and its arguments, without a shell. Send `run` or `argv`."
                },
                "env": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "string"
                  },
                  "description": "Environment variables of this step, added to the task's."
                },
                "report": {
                  "type": "object",
                  "properties": {
                    "format": {
                      "type": "string",
                      "enum": [
                        "junit",
                        "go_test_json"
                      ]
                    },
                    "path": {
                      "type": "string",
                      "description": "The report's file, relative to the working directory. A glob for `junit`."
                    }
                  },
                  "required": [
                    "format",
                    "path"
                  ],
                  "title": "Report",
                  "description": "A test report that the step writes, which Piston parses into the results, even when the step fails."
                },
                "timeout_seconds": {
                  "type": "integer",
                  "description": "The step's time limit, within the task's."
                }
              },
              "required": [],
              "title": "Step"
            },
            "description": "The commands, in order. The task fails at the first step that fails."
          },
          "outputs": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Files or directories that the task makes, relative to its working directory: the output's name → its path."
          },
          "cache": {
            "type": "boolean",
            "description": "Reuse an earlier result of the team for the task's key, and its outputs, instead of running it again."
          },
          "network": {
            "type": "string",
            "enum": [
              "internet",
              "none"
            ],
            "description": "`internet` if you don't say."
          },
          "timeout_seconds": {
            "type": "integer",
            "description": "The task's time limit: 3,600 if you don't say, and 43,200 at most."
          }
        },
        "required": [
          "name",
          "steps"
        ],
        "title": "PlanTask",
        "description": "A task of a plan. It runs its steps in a container of its image, on its own instance."
      },
      "Plan": {
        "type": "object",
        "properties": {
          "tasks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PlanTask"
            }
          }
        },
        "required": [
          "tasks"
        ],
        "title": "Plan",
        "description": "The tasks of a run, as `piston.star` evaluates to them. Values reach a step only as environment variables, files, or argv; Piston never puts text into a command."
      },
      "PlanResult": {
        "type": "object",
        "properties": {
          "plan": {
            "$ref": "#/components/schemas/Plan"
          },
          "diagnostics": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Diagnostic"
            },
            "description": "Warnings, if any. A plan with errors is a 422 `invalid_plan` instead."
          }
        },
        "required": [
          "plan",
          "diagnostics"
        ],
        "additionalProperties": false
      },
      "Step": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "starting",
              "running",
              "succeeded",
              "failed",
              "canceled",
              "timed_out",
              "errored",
              "skipped"
            ],
            "description": "`queued` (waiting for the tasks that it needs, or for an instance), `starting` (an instance starts), `running`, and the final ones: `succeeded`, `failed` (a step failed: read `failure`), `canceled`, `timed_out`, `errored` (Piston failed, not your code: start the run again), and `skipped` (a task that it needs didn't succeed)."
          },
          "exit_code": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The step's exit code, once it ended."
          },
          "started_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the step started."
          },
          "finished_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the step ended."
          }
        },
        "required": [
          "name",
          "status",
          "exit_code",
          "started_at",
          "finished_at"
        ],
        "additionalProperties": false,
        "title": "StepResult"
      },
      "Output": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "The output's name in the plan."
          },
          "sha256": {
            "type": "string",
            "description": "The SHA-256 digest of the output's bytes, in lowercase hex."
          },
          "size": {
            "type": "integer",
            "description": "The size in bytes."
          },
          "directory": {
            "type": "boolean",
            "description": "Whether the output is a directory, which downloads as a tar.zst."
          },
          "download_url": {
            "type": "string",
            "description": "Motor's URL that redirects to a download of the output. Send your credential to it."
          }
        },
        "required": [
          "name",
          "sha256",
          "size",
          "directory",
          "download_url"
        ],
        "additionalProperties": false,
        "title": "Output"
      },
      "TestFailure": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "The test's name, with its suite or package."
          },
          "message": {
            "type": "string",
            "description": "The failure's message and output, at most 8 KiB."
          },
          "file": {
            "type": [
              "string",
              "null"
            ],
            "description": "The test's file, if the report says."
          },
          "line": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The line in `file`, if the report says."
          }
        },
        "required": [
          "name",
          "message",
          "file",
          "line"
        ],
        "additionalProperties": false,
        "title": "TestFailure"
      },
      "TestReport": {
        "type": "object",
        "properties": {
          "step": {
            "type": "string",
            "description": "The step whose report it is."
          },
          "format": {
            "type": "string",
            "enum": [
              "junit",
              "go_test_json"
            ]
          },
          "passed": {
            "type": "integer"
          },
          "failed": {
            "type": "integer"
          },
          "skipped": {
            "type": "integer"
          },
          "failures": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TestFailure"
            },
            "description": "The failed tests, at most 100."
          }
        },
        "required": [
          "step",
          "format",
          "passed",
          "failed",
          "skipped",
          "failures"
        ],
        "additionalProperties": false,
        "title": "TestReport"
      },
      "Failure": {
        "type": "object",
        "properties": {
          "step": {
            "type": [
              "string",
              "null"
            ],
            "description": "The step that failed, or null if the task failed outside its steps."
          },
          "message": {
            "type": "string",
            "description": "Why the task didn't succeed."
          },
          "log_tail": {
            "type": "string",
            "description": "The last lines of the step's log, at most 100 lines and 16 KiB. Read it first; get the full log from the logs endpoint."
          }
        },
        "required": [
          "step",
          "message",
          "log_tail"
        ],
        "additionalProperties": false,
        "title": "Failure"
      },
      "Task": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "starting",
              "running",
              "succeeded",
              "failed",
              "canceled",
              "timed_out",
              "errored",
              "skipped"
            ],
            "description": "`queued` (waiting for the tasks that it needs, or for an instance), `starting` (an instance starts), `running`, and the final ones: `succeeded`, `failed` (a step failed: read `failure`), `canceled`, `timed_out`, `errored` (Piston failed, not your code: start the run again), and `skipped` (a task that it needs didn't succeed)."
          },
          "platform": {
            "type": "string",
            "description": "Such as `linux/amd64`."
          },
          "cpu": {
            "type": "integer",
            "description": "The vCPUs that the task asked for."
          },
          "memory_gb": {
            "type": "integer",
            "description": "The memory that the task asked for, in GB."
          },
          "key": {
            "type": [
              "string",
              "null"
            ],
            "description": "The task's key, in lowercase hex, if the task has `cache`."
          },
          "cached_from_run_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The run whose result this task reused, or null if it ran."
          },
          "started_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the task's first step started."
          },
          "finished_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the task ended."
          },
          "steps": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Step"
            }
          },
          "outputs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Output"
            }
          },
          "reports": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TestReport"
            }
          },
          "failure": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Failure"
              },
              {
                "type": "null"
              }
            ],
            "description": "Why the task didn't succeed, or null."
          }
        },
        "required": [
          "name",
          "status",
          "platform",
          "cpu",
          "memory_gb",
          "key",
          "cached_from_run_id",
          "started_at",
          "finished_at",
          "steps",
          "outputs",
          "reports",
          "failure"
        ],
        "additionalProperties": false,
        "title": "Task"
      },
      "Run": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The run's ID, `run_…`."
          },
          "team_id": {
            "type": "string"
          },
          "agent_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The agent that started the run, or null for a person."
          },
          "user_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The person that started the run, or that the agent works for."
          },
          "source_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "starting",
              "running",
              "succeeded",
              "failed",
              "canceled",
              "timed_out",
              "errored",
              "skipped"
            ],
            "description": "`queued` (waiting for the tasks that it needs, or for an instance), `starting` (an instance starts), `running`, and the final ones: `succeeded`, `failed` (a step failed: read `failure`), `canceled`, `timed_out`, `errored` (Piston failed, not your code: start the run again), and `skipped` (a task that it needs didn't succeed)."
          },
          "reason": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why the run ended when it didn't succeed, such as `over_limit`, or null."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the run started."
          },
          "started_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the first task started."
          },
          "finished_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the last task ended. The run is over once this isn't null."
          },
          "vcpu_milliseconds": {
            "type": "integer",
            "description": "The compute that the run's tasks used, which Piston bills."
          },
          "memory_byte_milliseconds": {
            "type": "string",
            "description": "The memory that the run's tasks used, which Piston bills, as a string, because it can pass 2^53."
          },
          "tasks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Task"
            },
            "description": "The run's tasks and their results. Not in a list of runs."
          },
          "plan": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Plan"
              }
            ],
            "description": "The run's plan, with the digest of each image. Not in a list of runs."
          }
        },
        "required": [
          "id",
          "team_id",
          "agent_id",
          "user_id",
          "source_id",
          "status",
          "reason",
          "created_at",
          "started_at",
          "finished_at",
          "vcpu_milliseconds",
          "memory_byte_milliseconds"
        ],
        "additionalProperties": false,
        "title": "Run"
      },
      "RunPage": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Run"
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "The cursor of the next page, for `cursor`, or null on the last page."
          }
        },
        "required": [
          "data",
          "next_cursor"
        ],
        "additionalProperties": false
      },
      "LogLine": {
        "type": "object",
        "properties": {
          "task": {
            "type": "string"
          },
          "step": {
            "type": "integer",
            "description": "The step's index in the task, from 0."
          },
          "stream": {
            "type": "string",
            "enum": [
              "stdout",
              "stderr",
              "piston",
              "pistond"
            ],
            "description": "`piston` for the messages of Piston's control plane, such as the start of an instance; `pistond` for the messages of the task's instance, such as the pull of the image. Only Piston writes `piston` lines."
          },
          "text": {
            "type": "string"
          },
          "at": {
            "type": "string",
            "format": "date-time",
            "description": "When the line was written."
          },
          "cursor": {
            "type": "string",
            "description": "The line's position, for `after` and `Last-Event-ID`."
          }
        },
        "required": [
          "task",
          "step",
          "stream",
          "text",
          "at",
          "cursor"
        ],
        "additionalProperties": false,
        "title": "LogLine"
      },
      "LogPage": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LogLine"
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "The cursor of the last line of the page, for `after`; the given `after` when the page is empty; null when the run has no lines."
          },
          "has_more": {
            "type": "boolean",
            "description": "Whether more lines follow now. When it's false and the run isn't over, poll again later, or stream instead."
          }
        },
        "required": [
          "data",
          "next_cursor",
          "has_more"
        ],
        "additionalProperties": false
      },
      "OutputDownload": {
        "type": "object",
        "properties": {
          "output": {
            "$ref": "#/components/schemas/Output"
          },
          "url": {
            "type": "string",
            "description": "The URL that downloads the output for 15 minutes, which `Location` has too. Don't send your credential to it."
          }
        },
        "required": [
          "output",
          "url"
        ],
        "additionalProperties": false
      },
      "Volume": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The volume's ID, `vol_…`."
          },
          "key": {
            "type": "string",
            "description": "The volume's key in plans, such as `go_mod`."
          },
          "size": {
            "type": "integer",
            "description": "The size of its head version, in bytes."
          },
          "committed_by_run_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The run that committed its head version."
          },
          "committed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When that run committed it."
          },
          "used_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When a task last used the volume. Piston deletes a volume that no task uses for 7 days."
          }
        },
        "required": [
          "id",
          "key",
          "size",
          "committed_by_run_id",
          "committed_at",
          "used_at"
        ],
        "additionalProperties": false,
        "title": "Volume"
      },
      "VolumePage": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Volume"
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Always null: the list has every volume."
          }
        },
        "required": [
          "data",
          "next_cursor"
        ],
        "additionalProperties": false
      },
      "Secret": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "The secret's name, in snake case, for a task's `secrets`."
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the value was last set."
          }
        },
        "required": [
          "name",
          "updated_at"
        ],
        "additionalProperties": false,
        "title": "Secret",
        "description": "A secret of the team. Its value never comes back."
      },
      "SecretPage": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Secret"
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Always null: the list has every secret."
          }
        },
        "required": [
          "data",
          "next_cursor"
        ],
        "additionalProperties": false
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "description": "What went wrong, for programs, such as `invalid_field`."
              },
              "message": {
                "type": "string",
                "description": "What went wrong, for people."
              },
              "field": {
                "type": "string",
                "description": "The field with the problem, for `invalid_field`."
              },
              "retryable": {
                "type": "boolean",
                "description": "Whether the same request can succeed later."
              },
              "retry_after_seconds": {
                "type": "integer",
                "description": "How long to wait before a retry."
              },
              "diagnostics": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Diagnostic"
                },
                "description": "For `invalid_plan`: each problem of the config or the plan. Fix all of them, and send the request again."
              }
            },
            "required": [
              "code",
              "message",
              "retryable"
            ],
            "additionalProperties": false
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false,
        "title": "Error"
      }
    }
  }
}
