{
  "openapi": "3.1.0",
  "info": {
    "title": "Groovy restricted external harness API",
    "version": "1.0.0",
    "description": "Verified external conversation routes only. This is not an API for account creation, worker administration, Groovy Memory setup, connector control, or Datagran source grants. A profile owner must first configure an external, restricted, profile-memory Mind and issue a scoped key in the signed-in Groovy app."
  },
  "servers": [{ "url": "/", "description": "Same Groovy deployment serving this specification" }],
  "components": {
    "securitySchemes": {
      "harnessKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "Groovy harness key scoped to this Mind: ghk_secret_* for server calls or ghk_pub_* for allowed-origin browser calls. Never publish a secret key."
      }
    },
    "responses": {
      "AuthError": {
        "description": "Error response. Consult the returned code. After an uncertain write, inspect thread history before retrying.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } }
      }
    },
    "parameters": {
      "slug": { "name": "slug", "in": "path", "required": true, "schema": { "type": "string" }, "description": "Slug of the external, restricted Mind bound to the key." },
      "threadId": { "name": "threadId", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } },
      "harnessOrigin": { "name": "X-Harness-Origin", "in": "header", "required": false, "schema": { "type": "string", "format": "uri" }, "description": "Hosted same-origin widget's CSP-bound parent origin. Cross-origin browser requests must have a matching Origin header. Publishable keys require an allowed origin." },
      "threadToken": { "name": "X-Harness-Thread-Token", "in": "header", "required": false, "schema": { "type": "string" }, "description": "Required on thread message reads and writes when using a publishable key. Returned by thread creation; bound to key, thread, and origin." }
    },
    "schemas": {
      "ApiError": { "type": "object", "required": ["error"], "properties": { "error": { "type": "object", "required": ["code", "message"], "properties": { "code": { "type": "string" }, "message": { "type": "string" } } } } },
      "Participant": { "type": "object", "required": ["id", "externalId", "displayName"], "properties": { "id": { "type": "string", "format": "uuid" }, "externalId": { "type": "string" }, "displayName": { "type": ["string", "null"] } } },
      "Message": { "type": "object", "required": ["id", "role", "content", "traceId", "createdAt"], "properties": { "id": { "type": "string", "format": "uuid" }, "role": { "type": "string", "enum": ["user", "assistant"] }, "content": { "type": "string" }, "traceId": { "type": ["string", "null"] }, "createdAt": { "type": "string", "format": "date-time" } } }
    }
  },
  "paths": {
    "/api/v1/harnesses/{slug}/config": {
      "get": {
        "operationId": "getExternalHarnessConfig",
        "summary": "Read safe widget appearance fields",
        "security": [{ "harnessKey": [] }],
        "parameters": [{ "$ref": "#/components/parameters/slug" }, { "$ref": "#/components/parameters/harnessOrigin" }],
        "description": "Requires threads:read. The result is widget presentation data, not runtime or provider readiness.",
        "responses": {
          "200": { "description": "Presentation fields", "content": { "application/json": { "schema": { "type": "object", "required": ["name", "greeting", "primaryColor", "avatar"], "properties": { "name": { "type": "string" }, "greeting": { "type": "string" }, "primaryColor": { "type": "string" }, "avatar": { "type": ["string", "null"], "format": "uri" } } } } } },
          "401": { "$ref": "#/components/responses/AuthError" },
          "403": { "$ref": "#/components/responses/AuthError" },
          "404": { "$ref": "#/components/responses/AuthError" },
          "429": { "$ref": "#/components/responses/AuthError" },
          "500": { "$ref": "#/components/responses/AuthError" },
          "503": { "$ref": "#/components/responses/AuthError" }
        }
      }
    },
    "/api/v1/harnesses/{slug}/threads": {
      "post": {
        "operationId": "createExternalHarnessThread",
        "summary": "Create a participant thread",
        "security": [{ "harnessKey": [] }],
        "parameters": [{ "$ref": "#/components/parameters/slug" }, { "$ref": "#/components/parameters/harnessOrigin" }],
        "description": "Requires threads:write. A missing participant.externalId creates a new anonymous identity. A new thread is created on every successful call; there is no idempotency key.",
        "requestBody": { "required": false, "content": { "application/json": { "schema": { "type": "object", "properties": { "title": { "type": "string", "maxLength": 200 }, "participant": { "type": "object", "properties": { "externalId": { "type": "string", "maxLength": 300 }, "displayName": { "type": "string", "maxLength": 200 }, "metadata": { "type": "object", "description": "JSON object with a maximum serialized size of 16 KB." } } } } } } } },
        "responses": {
          "201": { "description": "New thread and publishable-key thread token", "content": { "application/json": { "schema": { "type": "object", "required": ["id", "harness", "participant", "threadToken", "createdAt"], "properties": { "id": { "type": "string", "format": "uuid" }, "harness": { "type": "string" }, "participant": { "$ref": "#/components/schemas/Participant" }, "threadToken": { "type": "string", "description": "Treat as sensitive; required for publishable-key message calls." }, "createdAt": { "type": "string", "format": "date-time" } } } } } },
          "400": { "$ref": "#/components/responses/AuthError" },
          "401": { "$ref": "#/components/responses/AuthError" },
          "403": { "$ref": "#/components/responses/AuthError" },
          "404": { "$ref": "#/components/responses/AuthError" },
          "429": { "$ref": "#/components/responses/AuthError" },
          "500": { "$ref": "#/components/responses/AuthError" },
          "503": { "$ref": "#/components/responses/AuthError" }
        }
      }
    },
    "/api/v1/harnesses/{slug}/threads/{threadId}/messages": {
      "get": {
        "operationId": "listExternalHarnessMessages",
        "summary": "Read a thread's latest messages",
        "security": [{ "harnessKey": [] }],
        "parameters": [{ "$ref": "#/components/parameters/slug" }, { "$ref": "#/components/parameters/threadId" }, { "$ref": "#/components/parameters/harnessOrigin" }, { "$ref": "#/components/parameters/threadToken" }],
        "description": "Requires threads:read. Returns up to 500 messages from the key-bound thread. Use this read to reconcile an uncertain send before retrying.",
        "responses": {
          "200": { "description": "Thread messages", "content": { "application/json": { "schema": { "type": "object", "required": ["data"], "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Message" } } } } } } },
          "401": { "$ref": "#/components/responses/AuthError" },
          "403": { "$ref": "#/components/responses/AuthError" },
          "404": { "$ref": "#/components/responses/AuthError" },
          "429": { "$ref": "#/components/responses/AuthError" },
          "500": { "$ref": "#/components/responses/AuthError" },
          "503": { "$ref": "#/components/responses/AuthError" }
        }
      },
      "post": {
        "operationId": "sendExternalHarnessMessage",
        "summary": "Send one participant message and run the restricted Mind",
        "security": [{ "harnessKey": [] }],
        "parameters": [{ "$ref": "#/components/parameters/slug" }, { "$ref": "#/components/parameters/threadId" }, { "$ref": "#/components/parameters/harnessOrigin" }, { "$ref": "#/components/parameters/threadToken" }],
        "description": "Requires threads:write. The write has no idempotency key. On an uncertain result, read message history before another send. A concurrent turn may return thread_busy (409). A traceId is a correlation handle, not log access.",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["content"], "properties": { "content": { "type": "string", "minLength": 1, "maxLength": 20000 } } } } } },
        "responses": {
          "201": { "description": "Assistant response as JSON", "content": { "application/json": { "schema": { "type": "object", "required": ["data"], "properties": { "data": { "$ref": "#/components/schemas/Message" } } } } } },
          "200": { "description": "When Accept includes text/event-stream, status, message, done, or error events are streamed.", "content": { "text/event-stream": { "schema": { "type": "string" } } } },
          "400": { "$ref": "#/components/responses/AuthError" },
          "401": { "$ref": "#/components/responses/AuthError" },
          "403": { "$ref": "#/components/responses/AuthError" },
          "404": { "$ref": "#/components/responses/AuthError" },
          "409": { "$ref": "#/components/responses/AuthError" },
          "429": { "$ref": "#/components/responses/AuthError" },
          "500": { "$ref": "#/components/responses/AuthError" },
          "503": { "$ref": "#/components/responses/AuthError" }
        }
      }
    }
  }
}
