{
  "openapi": "3.1.0",
  "info": {
    "title": "ModAssemble public API",
    "version": "2026-10-09",
    "description": "Public plan catalogue only. Organisation records require a signed-in session. Versioned at /api/v1. A retired path will send a Deprecation header before removal."
  },
  "servers": [{ "url": "https://modassemble.com" }],
  "paths": {
    "/api/v1/plans": {
      "get": {
        "operationId": "listPlans",
        "summary": "List public ModAssemble plans",
        "description": "Returns the published plan names, monthly prices, and limits. No organisation data.",
        "parameters": [
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of plans",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/PlanPage" }
              }
            }
          }
        }
      }
    },
    "/api": {
      "get": {
        "operationId": "apiEntry",
        "summary": "API entry that tells an agent how to authenticate",
        "responses": {
          "401": {
            "description": "Sign-in required for workspace data",
            "content": {
              "application/problem+json": {
                "schema": { "$ref": "#/components/schemas/Problem" }
              }
            }
          }
        }
      }
    },
    "/api/v1/sandbox": {
      "get": {
        "operationId": "getSandboxAccess",
        "summary": "Issue the public sandbox key with no account",
        "description": "Zero-auth. Returns a self-serve public token and confirms the Starters free tier. Nothing is stored.",
        "responses": {
          "200": {
            "description": "Public sandbox access",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxAccess" } } }
          }
        }
      }
    },
    "/api/v1/echo": {
      "post": {
        "operationId": "echoSandbox",
        "summary": "Echo a sandbox request",
        "description": "Returns the same acknowledgement for a repeated Idempotency-Key. Nothing is stored in an organisation.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": { "type": "string", "minLength": 8 }
          }
        ],
        "responses": {
          "200": {
            "description": "Sandbox echo",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EchoResult" } } }
          },
          "400": {
            "description": "Missing idempotency key",
            "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
          }
        }
      }
    },
    "/api/v1/batch": {
      "post": {
        "operationId": "batchSandbox",
        "summary": "Run a sandbox batch",
        "description": "Accepts an array of operations and returns one result per item. Nothing is stored in an organisation.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": { "type": "string", "minLength": 8 }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/BatchRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "One result per operation",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BatchResult" } } }
          },
          "400": {
            "description": "Invalid batch",
            "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
          }
        }
      }
    },
    "/api/v1/jobs": {
      "post": {
        "operationId": "createSandboxJob",
        "summary": "Start a sandbox job",
        "description": "Returns 202 Accepted and a location to poll. The job does not touch organisation data.",
        "responses": {
          "202": {
            "description": "Job accepted",
            "headers": {
              "Location": { "schema": { "type": "string" } }
            },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JobAccepted" } } }
          }
        }
      }
    },
    "/api/v1/jobs/{id}": {
      "get": {
        "operationId": "readSandboxJob",
        "summary": "Read a sandbox job",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "Job status",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JobStatus" } } }
          },
          "404": {
            "description": "Job not found",
            "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Plan": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" },
          "priceMonthlyUsd": { "type": "number" }
        },
        "required": ["id", "name", "priceMonthlyUsd"]
      },
      "PlanPage": {
        "type": "object",
        "properties": {
          "data": { "type": "array", "items": { "$ref": "#/components/schemas/Plan" } },
          "next_cursor": { "type": "string", "nullable": true }
        },
        "required": ["data", "next_cursor"]
      },
      "SandboxAccess": {
        "type": "object",
        "properties": {
          "sandbox": { "type": "boolean" },
          "authentication": { "type": "string" },
          "apiKeyRequired": { "type": "boolean" },
          "zeroAuth": { "type": "boolean" },
          "selfServeKey": { "type": "boolean" },
          "freeTier": { "type": "object" },
          "key": { "type": "object" },
          "plans": { "type": "string" }
        },
        "required": ["sandbox", "authentication", "apiKeyRequired", "zeroAuth", "selfServeKey", "key", "plans"]
      },
      "EchoResult": {
        "type": "object",
        "properties": {
          "echoed": { "type": "boolean" },
          "sandbox": { "type": "boolean" },
          "idempotencyKey": { "type": "string" }
        },
        "required": ["echoed", "sandbox", "idempotencyKey"]
      },
      "BatchRequest": {
        "type": "object",
        "properties": {
          "operations": { "type": "array", "items": { "type": "object" } }
        },
        "required": ["operations"]
      },
      "BatchResult": {
        "type": "object",
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "index": { "type": "integer" },
                "ok": { "type": "boolean" },
                "sandbox": { "type": "boolean" }
              },
              "required": ["index", "ok", "sandbox"]
            }
          }
        },
        "required": ["results"]
      },
      "JobAccepted": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "status": { "type": "string" },
          "location": { "type": "string" }
        },
        "required": ["id", "status", "location"]
      },
      "JobStatus": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "status": { "type": "string" },
          "result": { "type": "object" }
        },
        "required": ["id", "status"]
      },
      "Problem": {
        "type": "object",
        "properties": {
          "type": { "type": "string" },
          "title": { "type": "string" },
          "status": { "type": "integer" },
          "code": { "type": "string" },
          "detail": { "type": "string" }
        },
        "required": ["type", "title", "status", "code", "detail"]
      }
    }
  }
}
