{
  "openapi": "3.0.0",
  "info": {
    "title": "envd",
    "version": "0.1.3",
    "description": "API for managing files' content and controlling envd"
  },
  "tags": [
    {
      "name": "environment"
    },
    {
      "name": "files"
    }
  ],
  "paths": {
    "/health": {
      "get": {
        "summary": "Check the health of the service",
        "responses": {
          "204": {
            "description": "The service is healthy"
          }
        },
        "operationId": "getEnvironmentHealth",
        "tags": [
          "environment"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/AgentboxSandboxId"
          },
          {
            "$ref": "#/components/parameters/AgentboxSandboxPort"
          }
        ],
        "security": []
      }
    },
    "/metrics": {
      "get": {
        "summary": "Service stats",
        "responses": {
          "200": {
            "description": "The resource usage metrics of the service",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Metrics"
                }
              }
            }
          }
        },
        "operationId": "getEnvironmentMetrics",
        "tags": [
          "environment"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/AgentboxSandboxId"
          },
          {
            "$ref": "#/components/parameters/AgentboxSandboxPort"
          }
        ],
        "security": [
          {},
          {
            "AccessTokenAuth": []
          }
        ]
      }
    },
    "/envs": {
      "get": {
        "summary": "Environment variables",
        "responses": {
          "200": {
            "description": "Environment variables",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EnvVars"
                }
              }
            }
          }
        },
        "operationId": "getEnvironmentVariables",
        "tags": [
          "environment"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/AgentboxSandboxId"
          },
          {
            "$ref": "#/components/parameters/AgentboxSandboxPort"
          }
        ],
        "security": [
          {},
          {
            "AccessTokenAuth": []
          }
        ]
      }
    },
    "/files": {
      "get": {
        "summary": "Download a file",
        "tags": [
          "files"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/AgentboxSandboxId"
          },
          {
            "$ref": "#/components/parameters/AgentboxSandboxPort"
          },
          {
            "$ref": "#/components/parameters/FilePath"
          },
          {
            "$ref": "#/components/parameters/User"
          },
          {
            "$ref": "#/components/parameters/Signature"
          },
          {
            "$ref": "#/components/parameters/SignatureExpiration"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/DownloadSuccess"
          },
          "400": {
            "$ref": "#/components/responses/InvalidPath"
          },
          "401": {
            "$ref": "#/components/responses/InvalidUser"
          },
          "404": {
            "$ref": "#/components/responses/FileNotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "operationId": "downloadFile",
        "security": [
          {},
          {
            "AccessTokenAuth": []
          }
        ]
      },
      "post": {
        "summary": "Upload a file and ensure the parent directories exist. If the file exists, it will be overwritten.",
        "description": "Any request header of the form `X-Metadata-<key>: <value>` is persisted\nas a user-defined extended attribute on the uploaded file. The\n`X-Metadata-` prefix is stripped and the remaining header name is\nlowercased to form the metadata key; the resulting map is returned on\n`EntryInfo` lookups (e.g. `Stat`, `ListDir`).\n\nEach upload replaces the file's metadata with the keys provided in\nthat request: keys previously stored but absent from the new request\nare removed, and an upload that sends no `X-Metadata-*` header clears\nall existing metadata.\n\nBoth keys and values must be printable US-ASCII (bytes `0x20`-`0x7E`)\nand are rejected with HTTP 400 otherwise. Each key is capped at 246\nbytes (the Linux VFS xattr-name limit minus the namespace prefix), and\nthe combined size of all metadata on a file (keys plus values, with the\nnamespace prefix counted per key) is capped at 4096 bytes to stay within\nthe filesystem's per-inode xattr budget. Multiple files in a single\nmultipart upload receive the same metadata. If the same\n`X-Metadata-<key>` header is sent more than once, only the first\nvalue is used.\n",
        "tags": [
          "files"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/AgentboxSandboxId"
          },
          {
            "$ref": "#/components/parameters/AgentboxSandboxPort"
          },
          {
            "$ref": "#/components/parameters/FilePath"
          },
          {
            "$ref": "#/components/parameters/User"
          },
          {
            "$ref": "#/components/parameters/Signature"
          },
          {
            "$ref": "#/components/parameters/SignatureExpiration"
          }
        ],
        "requestBody": {
          "$ref": "#/components/requestBodies/File"
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/UploadSuccess"
          },
          "400": {
            "$ref": "#/components/responses/InvalidPath"
          },
          "401": {
            "$ref": "#/components/responses/InvalidUser"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "507": {
            "$ref": "#/components/responses/NotEnoughDiskSpace"
          }
        },
        "operationId": "uploadFile",
        "security": [
          {},
          {
            "AccessTokenAuth": []
          }
        ]
      }
    },
    "/files/compose": {
      "post": {
        "summary": "Compose multiple files into a single file using zero-copy concatenation. Source files are deleted after successful composition.",
        "tags": [
          "files"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ComposeRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Files composed successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EntryInfo"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidPath"
          },
          "401": {
            "$ref": "#/components/responses/InvalidUser"
          },
          "404": {
            "$ref": "#/components/responses/FileNotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "507": {
            "$ref": "#/components/responses/NotEnoughDiskSpace"
          }
        },
        "operationId": "composeFiles",
        "parameters": [
          {
            "$ref": "#/components/parameters/AgentboxSandboxId"
          },
          {
            "$ref": "#/components/parameters/AgentboxSandboxPort"
          }
        ],
        "security": [
          {},
          {
            "AccessTokenAuth": []
          }
        ]
      }
    }
  },
  "components": {
    "parameters": {
      "FilePath": {
        "name": "path",
        "in": "query",
        "required": false,
        "description": "Path to the file, URL encoded. Can be relative to the user's home directory (e.g. \"file.txt\" resolves to ~/file.txt).",
        "schema": {
          "type": "string"
        }
      },
      "User": {
        "name": "username",
        "in": "query",
        "required": false,
        "description": "User for setting file ownership and resolving relative paths. Defaults to the sandbox's default user.",
        "schema": {
          "type": "string"
        }
      },
      "Signature": {
        "name": "signature",
        "in": "query",
        "required": false,
        "description": "Signature used for file access permission verification.",
        "schema": {
          "type": "string"
        }
      },
      "SignatureExpiration": {
        "name": "signature_expiration",
        "in": "query",
        "required": false,
        "description": "Unix timestamp (seconds) after which the signature expires. Only used with the signature parameter.",
        "schema": {
          "type": "integer"
        }
      },
      "AgentboxSandboxId": {
        "name": "Agentbox-Sandbox-Id",
        "in": "header",
        "required": true,
        "description": "Identifier of the sandbox that receives the request.",
        "schema": {
          "type": "string"
        }
      },
      "AgentboxSandboxPort": {
        "name": "Agentbox-Sandbox-Port",
        "in": "header",
        "required": true,
        "description": "Internal envd HTTP port exposed through the sandbox proxy.",
        "schema": {
          "type": "integer",
          "default": 49983
        }
      }
    },
    "requestBodies": {
      "File": {
        "required": true,
        "content": {
          "multipart/form-data": {
            "schema": {
              "type": "object",
              "properties": {
                "file": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "application/octet-stream": {
            "schema": {
              "type": "string",
              "format": "binary",
              "description": "Raw file content. The 'path' query parameter is required when using this content type."
            }
          }
        }
      }
    },
    "responses": {
      "UploadSuccess": {
        "description": "The file was uploaded successfully.",
        "content": {
          "application/json": {
            "schema": {
              "type": "array",
              "items": {
                "$ref": "#/components/schemas/EntryInfo"
              }
            },
            "example": [
              {
                "path": "/home/user/hello.txt",
                "name": "hello.txt",
                "type": "file"
              }
            ]
          }
        }
      },
      "DownloadSuccess": {
        "description": "Entire file downloaded successfully.",
        "content": {
          "application/octet-stream": {
            "schema": {
              "type": "string",
              "format": "binary",
              "description": "The raw file content"
            }
          }
        }
      },
      "NotAcceptable": {
        "description": "Requested encoding is not supported",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "message": "no acceptable encoding found, supported: [identity, gzip]",
              "code": 406
            }
          }
        }
      },
      "InvalidPath": {
        "description": "Invalid path",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "message": "path '/home/user/docs' is a directory",
              "code": 400
            }
          }
        }
      },
      "InternalServerError": {
        "description": "Internal server error",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "message": "error opening file '/home/user/file.txt': permission denied",
              "code": 500
            }
          }
        }
      },
      "FileNotFound": {
        "description": "File not found",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "message": "path '/home/user/missing.txt' does not exist",
              "code": 404
            }
          }
        }
      },
      "InvalidUser": {
        "description": "Invalid user",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "message": "error looking up user 'nonexistent': user: unknown user nonexistent",
              "code": 401
            }
          }
        }
      },
      "NotEnoughDiskSpace": {
        "description": "Not enough disk space",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "message": "not enough disk space available",
              "code": 507
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "required": [
          "message",
          "code"
        ],
        "properties": {
          "message": {
            "type": "string",
            "description": "Error message"
          },
          "code": {
            "type": "integer",
            "description": "Error code"
          }
        }
      },
      "EntryInfo": {
        "required": [
          "path",
          "name",
          "type"
        ],
        "properties": {
          "path": {
            "type": "string",
            "description": "Path to the file"
          },
          "name": {
            "type": "string",
            "description": "Name of the file"
          },
          "type": {
            "type": "string",
            "description": "Type of the file",
            "enum": [
              "file"
            ]
          },
          "metadata": {
            "type": "object",
            "description": "User-defined metadata stored as extended attributes on the file.",
            "additionalProperties": {
              "type": "string"
            }
          }
        }
      },
      "EnvVars": {
        "type": "object",
        "description": "Environment variables to set",
        "additionalProperties": {
          "type": "string"
        }
      },
      "Metrics": {
        "type": "object",
        "description": "Resource usage metrics",
        "properties": {
          "ts": {
            "type": "integer",
            "format": "int64",
            "description": "Unix timestamp in UTC for current sandbox time"
          },
          "cpu_count": {
            "type": "integer",
            "description": "Number of CPU cores"
          },
          "cpu_used_pct": {
            "type": "number",
            "format": "float",
            "description": "CPU usage percentage"
          },
          "mem_total": {
            "type": "integer",
            "description": "Total virtual memory in bytes"
          },
          "mem_used": {
            "type": "integer",
            "description": "Used virtual memory in bytes"
          },
          "mem_cache": {
            "type": "integer",
            "description": "Cached memory (page cache) in bytes"
          },
          "mem_total_mib": {
            "type": "integer",
            "description": "Total virtual memory in MiB"
          },
          "mem_used_mib": {
            "type": "integer",
            "description": "Used virtual memory in MiB"
          },
          "disk_used": {
            "type": "integer",
            "description": "Used disk space in bytes"
          },
          "disk_total": {
            "type": "integer",
            "description": "Total disk space in bytes"
          }
        }
      },
      "ComposeRequest": {
        "type": "object",
        "required": [
          "source_paths",
          "destination"
        ],
        "properties": {
          "source_paths": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Ordered list of source file paths to concatenate"
          },
          "destination": {
            "type": "string",
            "description": "Destination file path for the composed file"
          },
          "username": {
            "type": "string",
            "description": "User for setting ownership and resolving relative paths"
          }
        }
      }
    },
    "securitySchemes": {
      "AccessTokenAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Access-Token"
      }
    }
  },
  "servers": [
    {
      "url": "https://sandbox.agentbox-runtime.ru",
      "description": "AgentBox sandbox proxy. Routing headers are required."
    }
  ]
}
