{
  "openapi": "3.1.0",
  "info": {
    "title": "jurniti Customer API",
    "version": "1.0.0",
    "summary": "Provision and drive Firecracker-isolated agent microVMs.",
    "description": "Tenant-scoped REST for the jurniti managed agent platform (one Firecracker microVM per agent). Authenticate with a customer API key (`jrn_live_…`) from `jurniti login` or `jurniti keys issue`. The CLI verb, the REST noun, and the MCP tool are the same operation. No free trial — money verbs (`POST /v1/api/vms`, `POST /v1/api/templates/{id}/fork`) charge the card on file. Docs: https://www.jurniti.com/docs · MCP: `claude mcp add jurniti -- jurniti mcp` · CLI: `curl -fsSL https://jurniti.com/install.sh | sh`.",
    "contact": {
      "name": "jurniti",
      "email": "sales@jurniti.com",
      "url": "https://www.jurniti.com/contact"
    },
    "license": {
      "name": "Proprietary"
    }
  },
  "servers": [
    {
      "url": "https://api.jurniti.com",
      "description": "Production control plane"
    }
  ],
  "tags": [
    {
      "name": "Account",
      "description": "Who the key is."
    },
    {
      "name": "VMs",
      "description": "Provision and lifecycle of agent microVMs."
    },
    {
      "name": "Templates",
      "description": "Snapshot and fork configured persist disks."
    },
    {
      "name": "Harnesses",
      "description": "Available agent runtimes (no harness literals in clients)."
    },
    {
      "name": "Billing",
      "description": "Card on file, usage, credits."
    },
    {
      "name": "Keys",
      "description": "Issue and revoke customer API keys."
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "jrn_live_",
        "description": "Customer API key. Header `Authorization: Bearer jrn_live_…`. Mint with `jurniti keys issue` after `jurniti login`. Read-only keys cannot call money verbs."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error",
          "message"
        ],
        "additionalProperties": false,
        "properties": {
          "error": {
            "type": "string",
            "description": "Stable machine code (unauthorized, not_found, card_required, read_only_key, method_not_allowed, …)."
          },
          "message": {
            "type": "string",
            "description": "Human-safe detail. Empty when the underlying error is internal; never a raw Go error string."
          },
          "hint": {
            "type": "string",
            "description": "Optional recovery hint (where to look, which verb to run)."
          }
        }
      },
      "Whoami": {
        "type": "object",
        "required": [
          "tenant_id"
        ],
        "properties": {
          "tenant_id": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "scope": {
            "type": "string",
            "enum": [
              "read",
              "readwrite"
            ]
          },
          "key_label": {
            "type": "string"
          }
        }
      },
      "CreateVMRequest": {
        "type": "object",
        "properties": {
          "plan": {
            "type": "string",
            "description": "Flat plan id (starter, pro, max). Omit when using a usage tier."
          },
          "harness": {
            "type": "string",
            "description": "Agent runtime id from GET /v1/api/harnesses. Omit to use the platform default."
          },
          "cycle": {
            "type": "string",
            "enum": [
              "monthly",
              "annual"
            ],
            "description": "Billing cycle for a flat plan. Default monthly."
          },
          "tier": {
            "type": "string",
            "enum": [
              "ondemand",
              "spot"
            ],
            "description": "Prepaid usage path instead of a flat plan. Spot is required for swarm --count."
          }
        }
      },
      "CreateVMResponse": {
        "type": "object",
        "required": [
          "vm_id",
          "status"
        ],
        "properties": {
          "vm_id": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "tier": {
            "type": "string"
          }
        }
      },
      "ForkRequest": {
        "type": "object",
        "properties": {
          "plan": {
            "type": "string",
            "description": "Plan for the new VM. Default matches the template."
          }
        }
      }
    }
  },
  "paths": {
    "/v1/api/whoami": {
      "get": {
        "operationId": "whoami",
        "tags": [
          "Account"
        ],
        "summary": "Identity the API key resolves to",
        "description": "Returns tenant id, account email, and key scope. Use this to confirm which account a CI key is acting as.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The calling key's identity.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Whoami"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key. Run `jurniti login` or pass Authorization: Bearer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/api/harnesses": {
      "get": {
        "operationId": "listHarnesses",
        "tags": [
          "Harnesses"
        ],
        "summary": "List available agent runtimes",
        "description": "Every registered harness plus its auth requirement (BYOK vs one-time login). The `--harness` discovery surface — do not hardcode names.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Harness catalog.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/api/harnesses/{name}": {
      "get": {
        "operationId": "getHarness",
        "tags": [
          "Harnesses"
        ],
        "summary": "One harness by id",
        "description": "Auth requirement and display metadata for a single runtime.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Harness id as returned by listHarnesses."
          }
        ],
        "responses": {
          "200": {
            "description": "Harness metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown harness id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/api/vms": {
      "get": {
        "operationId": "listVMs",
        "tags": [
          "VMs"
        ],
        "summary": "List this tenant's VMs",
        "description": "Every microVM owned by the calling key's tenant.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "VM list.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createVM",
        "tags": [
          "VMs"
        ],
        "summary": "Provision a paid agent VM",
        "description": "THE money verb. Card-gated; charges the card on file (flat plan) or draws prepaid credits (tier). Returns 201 {vm_id, status}. Read-only keys get 403 read_only_key. No card → 402 card_required. Equivalent CLI: `jurniti up`.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateVMRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "VM accepted for provision.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateVMResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "card_required or insufficient prepaid credits. `jurniti billing add-card` or `jurniti credits buy`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "read_only_key — issue a read-write key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/api/vms/{id}": {
      "get": {
        "operationId": "getVM",
        "tags": [
          "VMs"
        ],
        "summary": "Get one VM",
        "description": "Status, plan, harness_display, IPv4. Unknown/unowned ids return 404 not_found (existence-as-secret).",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "VM id. Unknown or unowned ids collapse to not_found (existence-as-secret).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "VM.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/api/vms/{id}/stop": {
      "post": {
        "operationId": "stopVM",
        "tags": [
          "VMs"
        ],
        "summary": "Stop a VM (persist kept)",
        "description": "Pause compute; persist disk is preserved. CLI: `jurniti stop`.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "VM id. Unknown or unowned ids collapse to not_found (existence-as-secret).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Stopped (or already stopped).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/api/vms/{id}/start": {
      "post": {
        "operationId": "startVM",
        "tags": [
          "VMs"
        ],
        "summary": "Start a stopped VM",
        "description": "CLI: `jurniti start`.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "VM id. Unknown or unowned ids collapse to not_found (existence-as-secret).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Running (or already running).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/api/vms/{id}/restart": {
      "post": {
        "operationId": "restartVM",
        "tags": [
          "VMs"
        ],
        "summary": "Restart a VM",
        "description": "Reset rootfs, keep persist. CLI: `jurniti restart`.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "VM id. Unknown or unowned ids collapse to not_found (existence-as-secret).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Restarted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/api/vms/{id}/reprovision": {
      "post": {
        "operationId": "reprovisionVM",
        "tags": [
          "VMs"
        ],
        "summary": "Rebuild the guest, keep persist",
        "description": "CLI: `jurniti reprovision`.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "VM id. Unknown or unowned ids collapse to not_found (existence-as-secret).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Reprovisioned.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/api/vms/{id}/run": {
      "post": {
        "operationId": "runVMTask",
        "tags": [
          "VMs"
        ],
        "summary": "Dispatch one headless task",
        "description": "Runs ONE task inside the caller's own VM. The server builds the in-guest command from the harness — the client never supplies a shell script. CLI: `jurniti run`.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "VM id. Unknown or unowned ids collapse to not_found (existence-as-secret).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "task"
                ],
                "properties": {
                  "task": {
                    "type": "string",
                    "description": "The task to run."
                  },
                  "model": {
                    "type": "string"
                  },
                  "provider": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Task result.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/api/templates": {
      "get": {
        "operationId": "listTemplates",
        "tags": [
          "Templates"
        ],
        "summary": "List templates visible to this key",
        "description": "The caller's templates merged with the public gallery. Page with limit/offset; total is the whole matching set so agents can walk the catalog. CLI: `jurniti templates ls`.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Search."
          },
          {
            "name": "harness",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filter by harness id from listHarnesses."
          },
          {
            "name": "sort",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "trending | new | forks | stars"
          },
          {
            "name": "tag",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{data, total, has_more, next_offset}.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/api/templates/{id}": {
      "get": {
        "operationId": "getTemplate",
        "tags": [
          "Templates"
        ],
        "summary": "Get one template",
        "description": "Public or own-private. CLI: `jurniti templates get`.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Template id (`tpl_…`) or public/owned slug.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Template.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/api/templates/{id}/fork": {
      "post": {
        "operationId": "forkTemplate",
        "tags": [
          "Templates"
        ],
        "summary": "Fork a template into a new paid VM",
        "description": "Charges the card on file. Never a free fork. CLI: `jurniti fork`. Equivalent to the Fork action on a public template page.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Template id (`tpl_…`) or public/owned slug.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ForkRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "New VM seeded from the template.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "card_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/api/vms/{id}/capture": {
      "post": {
        "operationId": "captureTemplate",
        "tags": [
          "Templates"
        ],
        "summary": "Snapshot a VM into a template",
        "description": "Captures persist only. Public publish scrubs secrets. CLI: `jurniti capture`.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "VM id. Unknown or unowned ids collapse to not_found (existence-as-secret).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "visibility": {
                    "type": "string",
                    "enum": [
                      "public",
                      "private"
                    ]
                  },
                  "name": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Template created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/api/billing": {
      "get": {
        "operationId": "getBilling",
        "tags": [
          "Billing"
        ],
        "summary": "Card-on-file status",
        "description": "Drives `jurniti billing status`. Money verbs fail with 402 until a card is on file.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{card_on_file, brand, last4}.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/api/usage": {
      "get": {
        "operationId": "getUsage",
        "tags": [
          "Billing"
        ],
        "summary": "Live VM counts and MRR summary",
        "description": "CLI: `jurniti usage`. Distinct from token-usage (BYOK meter).",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Usage summary.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/api/keys": {
      "get": {
        "operationId": "listKeys",
        "tags": [
          "Keys"
        ],
        "summary": "List this tenant's API keys",
        "description": "Never echoes hashes. CLI: `jurniti keys`.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Key list (metadata only).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "issueKey",
        "tags": [
          "Keys"
        ],
        "summary": "Issue a new API key",
        "description": "Plaintext returned exactly ONCE. Read-write gated so a read-only key cannot self-escalate. CLI: `jurniti keys issue`.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "201": {
            "description": "New key (plaintext once).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "read_only_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  }
}
