{
  "openapi": "3.1.0",
  "info": {
    "title": "Stacksync API",
    "version": "v1",
    "description": "Run a Genie of your workspace over HTTP: ask it in the same request, or start the run and poll its conversation until the Genie has answered."
  },
  "servers": [
    {
      "url": "https://genies.stacksync.com"
    }
  ],
  "tags": [
    {
      "name": "Genie runs",
      "description": "Send a message to a Genie and read its reply, in one request or by polling the conversation."
    }
  ],
  "security": [
    {
      "Bearer": []
    }
  ],
  "paths": {
    "/v1/workspaces/{workspace_id}/genies/run": {
      "post": {
        "operationId": "run_genie",
        "summary": "Run a Genie",
        "description": "Starts a Genie run in the background and answers at once with the conversation to poll. Send the Genie's reply back with Get a Genie run, passing the region this answer carries. Omit genie_id to let the workspace's Genie dispatcher pick the Genie that fits the message; pass conversation_id to continue an earlier conversation. A message sent while the conversation is still processing is handed to the running Genie as steering instead of starting a new run.",
        "tags": [
          "Genie runs"
        ],
        "security": [
          {
            "Bearer": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WorkspaceId"
          },
          {
            "$ref": "#/components/parameters/GenieId"
          },
          {
            "$ref": "#/components/parameters/RunRegion"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RunGenieRequest"
              },
              "example": {
                "input": "Who are you?",
                "region": "usnv"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "The run was accepted. Poll Get a Genie run with conversation_id and region until status leaves processing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GenieRunAccepted"
                },
                "example": {
                  "status": "processing",
                  "conversation_id": "14d0a0ba-9352-44d6-a7f7-8fd2c3ce0388",
                  "region": "usnv",
                  "message_id": null
                }
              }
            }
          },
          "204": {
            "$ref": "#/components/responses/ValidateOnlyPassed"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/GenieNotFound"
          },
          "409": {
            "$ref": "#/components/responses/GenieInactive"
          }
        }
      }
    },
    "/v1/workspaces/{workspace_id}/genies/run_wait_result": {
      "post": {
        "operationId": "run_genie_and_wait",
        "summary": "Run a Genie and wait for the result",
        "description": "Sends a message to a Genie and answers once the Genie has finished with it, so the reply is in the same response. A Genie that calls several tools can take minutes; keep the connection open or use Run a Genie and poll instead. Omit genie_id to let the workspace's Genie dispatcher pick the Genie; pass conversation_id to continue an earlier conversation. The answer has the same shape as Get a Genie run.",
        "tags": [
          "Genie runs"
        ],
        "security": [
          {
            "Bearer": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WorkspaceId"
          },
          {
            "$ref": "#/components/parameters/GenieId"
          },
          {
            "$ref": "#/components/parameters/RunRegion"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RunGenieRequest"
              },
              "example": {
                "input": "Repeat the first thing I said to you.",
                "conversation_id": "d10c810f-1187-4a6e-9df9-ac2e87d02f2d",
                "region": "usnv"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The run finished, or could not: status says which. completed carries the reply in result; failed carries the reason in error when the run stopped before the Genie answered; processing means the message steered a run that is still going, so poll Get a Genie run.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GenieRun"
                },
                "example": {
                  "status": "completed",
                  "conversation_id": "d10c810f-1187-4a6e-9df9-ac2e87d02f2d",
                  "region": "usnv",
                  "message_id": "aef1f941-d148-4833-9118-86f59c358d5a",
                  "result": "Hello, world! I am the Hello World API Demo genie.",
                  "attachments": [],
                  "options": {}
                }
              }
            }
          },
          "204": {
            "$ref": "#/components/responses/ValidateOnlyPassed"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/GenieNotFound"
          },
          "409": {
            "$ref": "#/components/responses/GenieInactive"
          }
        }
      }
    },
    "/v1/workspaces/{workspace_id}/genies/run/{conversation_id}": {
      "get": {
        "operationId": "get_genie_run",
        "summary": "Get a Genie run",
        "description": "Returns the state of a run started with Run a Genie and, once the Genie has answered, its latest reply. Pass the region the run answer carried: the conversation is read from that data region. While the Genie works, status is processing and there is no result; completed carries the reply in result and its id in message_id; failed means the run could not finish.",
        "tags": [
          "Genie runs"
        ],
        "security": [
          {
            "Bearer": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WorkspaceId"
          },
          {
            "name": "conversation_id",
            "in": "path",
            "required": true,
            "description": "The conversation_id the run answer carried.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "region",
            "in": "query",
            "required": false,
            "description": "Data region of the resources: the region the run answer carried. Allowed values: besg, usnv. Defaults to the region of the server that answers, so always pass it.",
            "schema": {
              "type": "string",
              "enum": [
                "besg",
                "usnv"
              ]
            },
            "example": "usnv"
          }
        ],
        "responses": {
          "200": {
            "description": "The run's current state. A conversation not yet stored answers processing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GenieRun"
                },
                "example": {
                  "status": "completed",
                  "conversation_id": "14d0a0ba-9352-44d6-a7f7-8fd2c3ce0388",
                  "region": "usnv",
                  "message_id": "346f36a0-1902-4871-87d1-11f2d5433d9b",
                  "result": "Hello, world! I am the Hello World API Demo genie.",
                  "attachments": [],
                  "options": {}
                }
              }
            }
          },
          "400": {
            "description": "The region is not a Stacksync data region. The body is a failed run envelope whose error names the region.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GenieRun"
                },
                "example": {
                  "status": "failed",
                  "conversation_id": "14d0a0ba-9352-44d6-a7f7-8fd2c3ce0388",
                  "region": null,
                  "message_id": null,
                  "error": "unknown region: eu"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "Bearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "`Authorization: Bearer <key>`. A workspace API key (workspace_…, created by a workspace owner under Workspace settings > Stacksync API) acts with editor rights on that workspace only; a user API key (user_…, created under Profile > Stacksync API) acts as that user in every workspace they belong to. POST requests must send Content-Type: application/json. Every authentication failure answers 403. Running a Genie needs the editor role in the workspace."
      }
    },
    "parameters": {
      "WorkspaceId": {
        "name": "workspace_id",
        "in": "path",
        "required": true,
        "description": "The workspace whose Genie runs. A workspace API key works only on its own workspace; a user API key on any workspace the user belongs to.",
        "schema": {
          "type": "integer"
        }
      },
      "GenieId": {
        "name": "genie_id",
        "in": "query",
        "required": false,
        "description": "The Genie to run (its id from the Genies list). Omitted, the workspace's Genie dispatcher selects the Genie that best fits the message; a follow-up without genie_id goes through the dispatcher again and may reach another Genie. A named Genie must exist in the region and be active.",
        "schema": {
          "type": "string"
        }
      },
      "RunRegion": {
        "name": "region",
        "in": "query",
        "required": false,
        "description": "Data region of the resources: the region of the Genie, where the conversation is stored. Allowed values: besg, usnv. Defaults to the region of the server that answers, so always pass the Genie's region. A value in the request body wins over the query parameter.",
        "schema": {
          "type": "string",
          "enum": [
            "besg",
            "usnv"
          ]
        }
      }
    },
    "responses": {
      "ValidateOnlyPassed": {
        "description": "validate_only was true and every check of the run passed (the Genie, the conversation). Nothing was asked and nothing was stored."
      },
      "BadRequest": {
        "description": "The request could not be read: the body is not a JSON object, input is missing, source is a reserved value, validate_only is not true or false, or workspace_id is not a number.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Invalid request payload: input is required"
            }
          }
        }
      },
      "Forbidden": {
        "description": "The caller is not allowed: Authorization Error (header missing or not Bearer), Invalid API Key (unknown key, key of another workspace, or a user who is not a member of the workspace), Content-Type Error (POST without application/json), Insufficient Scope (a read-only sign-in on a request that changes data) or Authentication Error (your role in the workspace is below editor).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Invalid API Key",
              "msg": "The API Key provided in the request is invalid"
            }
          }
        }
      },
      "GenieNotFound": {
        "description": "genie_id names no Genie in this region.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/GenieRunRefusal"
            },
            "example": {
              "error": "genie_not_found",
              "message": "This genie does not exist."
            }
          }
        }
      },
      "GenieInactive": {
        "description": "genie_id names a Genie that is switched off. Switch it on before asking it.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/GenieRunRefusal"
            },
            "example": {
              "error": "genie_inactive",
              "message": "This genie is currently inactive."
            }
          }
        }
      }
    },
    "schemas": {
      "RunGenieRequest": {
        "title": "RunGenieRequest",
        "type": "object",
        "description": "The message for a Genie and how to run it. genie_id and region may also be sent here instead of the query string; a body value wins.",
        "required": [
          "input"
        ],
        "properties": {
          "input": {
            "description": "The message handed to the Genie. A string is used as is; a JSON object is encoded as text so nothing is lost.",
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "object",
                "additionalProperties": true
              }
            ]
          },
          "conversation_id": {
            "type": "string",
            "format": "uuid",
            "description": "The conversation to continue, from an earlier run answer: the earlier messages are the Genie's context, and a failed conversation reopens. Omitted, a new conversation starts."
          },
          "region": {
            "type": "string",
            "enum": [
              "besg",
              "usnv"
            ],
            "description": "Data region of the Genie, where the conversation is stored. Allowed values: besg, usnv. Same meaning as the region query parameter; this value wins."
          },
          "options": {
            "$ref": "#/components/schemas/RunGenieOptions"
          },
          "source": {
            "type": "string",
            "description": "A label of your own for where the message came from, stored on it. Omitted or blank, api. The values slack, email, teams and public_api are reserved and refused."
          },
          "validate_only": {
            "type": "boolean",
            "default": false,
            "description": "true runs only the checks of the run (the Genie exists and is active) and answers 204 without asking the Genie or storing anything."
          }
        }
      },
      "RunGenieOptions": {
        "title": "RunGenieOptions",
        "type": "object",
        "description": "Options of the run.",
        "properties": {
          "dispatch_external_message": {
            "$ref": "#/components/schemas/ApprovalDispatch"
          }
        }
      },
      "ApprovalDispatch": {
        "title": "ApprovalDispatch",
        "type": "object",
        "description": "Where an approval request is delivered when the Genie pauses on an action that needs one. Both interface_type and interface_id are needed; the destination inside that interface (a channel, an address) is taken from the Genie's master prompt. Without it the approval is only returned in options.approval of the reply.",
        "required": [
          "interface_type",
          "interface_id"
        ],
        "properties": {
          "interface_type": {
            "type": "string",
            "enum": [
              "slack",
              "teams",
              "email"
            ],
            "description": "The type of the Genie's configured interface that delivers the approval request. Allowed values: slack, teams, email."
          },
          "interface_id": {
            "type": "string",
            "description": "The id of that configured interface of the Genie."
          },
          "attachement_file_ids": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Ids of stored files to deliver with the approval request. The files must belong to this request or conversation."
          }
        }
      },
      "GenieRunAccepted": {
        "title": "GenieRunAccepted",
        "type": "object",
        "description": "The handle of a run started in the background.",
        "required": [
          "status",
          "conversation_id",
          "region",
          "message_id"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "processing"
            ],
            "description": "Always processing: the Genie has not answered yet."
          },
          "conversation_id": {
            "type": "string",
            "format": "uuid",
            "description": "The conversation of the run: poll it with Get a Genie run, and send it back to continue the conversation."
          },
          "region": {
            "type": "string",
            "enum": [
              "besg",
              "usnv"
            ],
            "description": "Data region the conversation is stored in. Pass it as region when polling."
          },
          "message_id": {
            "type": "null",
            "description": "Always null: no reply yet."
          }
        }
      },
      "GenieRun": {
        "title": "GenieRun",
        "type": "object",
        "description": "The state of a run and, once the Genie answered, its latest reply.",
        "required": [
          "status",
          "conversation_id",
          "region",
          "message_id"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "processing",
              "completed",
              "failed"
            ],
            "description": "processing: the Genie is still working (or the message steered a run in progress). completed: the Genie answered; result holds the reply. failed: the run could not finish."
          },
          "conversation_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The conversation of the run. Send it back to continue the conversation."
          },
          "region": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "besg",
              "usnv",
              null
            ],
            "description": "Data region the conversation is stored in. Null only on a failure answered before the region was resolved."
          },
          "message_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Id of the Genie's latest reply in the conversation; null while there is none."
          },
          "result": {
            "type": "string",
            "description": "The text of the Genie's latest reply. Present once the conversation holds a reply."
          },
          "widget": {
            "type": "object",
            "additionalProperties": true,
            "description": "The structured action the reply proposes, when the Genie attached one to its reply."
          },
          "options": {
            "$ref": "#/components/schemas/GenieReplyOptions"
          },
          "attachments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GenieReplyAttachment"
            },
            "description": "The files the reply produced, as stored file references. Present once the conversation holds a reply; empty when the reply has no file."
          },
          "error": {
            "type": "string",
            "description": "Why the run failed, when it stopped before the Genie answered or the region is unknown."
          }
        }
      },
      "GenieReplyOptions": {
        "title": "GenieReplyOptions",
        "type": "object",
        "description": "Details attached to the reply. Carries approval when the Genie paused on an action that needs an approval; decide it with the approval decision operation using the same API key.",
        "properties": {
          "approval": {
            "$ref": "#/components/schemas/GenieApproval"
          },
          "approval_delivery": {
            "type": "object",
            "additionalProperties": true,
            "description": "Where the approval request was delivered when the run asked for an external delivery."
          }
        },
        "additionalProperties": true
      },
      "GenieApproval": {
        "title": "GenieApproval",
        "type": "object",
        "description": "An action the Genie is waiting to have approved before it runs it.",
        "required": [
          "approval_id",
          "type",
          "region",
          "instructions",
          "status",
          "decision_at"
        ],
        "properties": {
          "approval_id": {
            "type": "string",
            "format": "uuid",
            "description": "Id of the approval, used to decide it."
          },
          "type": {
            "type": "string",
            "enum": [
              "execute_module"
            ],
            "description": "What kind of action waits: execute_module, a connector action."
          },
          "region": {
            "type": "string",
            "enum": [
              "besg",
              "usnv"
            ],
            "description": "Data region the approval is stored in. Allowed values: besg, usnv."
          },
          "instructions": {
            "type": "string",
            "description": "What the Genie intends to do with the action."
          },
          "status": {
            "type": "string",
            "enum": [
              "awaiting_approval",
              "rejected",
              "approved_once",
              "approved_always",
              "unknown_outcome"
            ],
            "description": "State of the approval. awaiting_approval until it is decided."
          },
          "decision_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the approval was decided, ISO 8601 UTC without offset; null while it waits."
          },
          "module_id": {
            "type": "string",
            "description": "Id of the connector action waiting for approval."
          },
          "module_name": {
            "type": "string",
            "description": "Name of the connector action waiting for approval."
          },
          "connection_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Id of the connection the action would use."
          },
          "connection_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Name of the connection the action would use."
          }
        }
      },
      "GenieReplyAttachment": {
        "title": "GenieReplyAttachment",
        "type": "object",
        "description": "A file the reply produced. Fetch it by file_id with the files operations; replies carry no download URL.",
        "required": [
          "file_id",
          "filename",
          "content_type"
        ],
        "properties": {
          "file_id": {
            "type": "string",
            "description": "Id of the stored file."
          },
          "filename": {
            "type": "string",
            "description": "Name of the file."
          },
          "content_type": {
            "type": "string",
            "description": "Media type of the file, such as application/pdf."
          }
        }
      },
      "GenieRunRefusal": {
        "title": "GenieRunRefusal",
        "type": "object",
        "description": "Why a run was refused before it started.",
        "required": [
          "error",
          "message"
        ],
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "genie_not_found",
              "genie_inactive"
            ],
            "description": "The refusal code."
          },
          "message": {
            "type": "string",
            "description": "The refusal in plain words."
          }
        }
      },
      "Error": {
        "title": "Error",
        "type": "object",
        "description": "An error answer.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "What went wrong, as a short name or sentence."
          },
          "msg": {
            "type": "string",
            "description": "The error in plain words, when there is more to say."
          },
          "field": {
            "type": "string",
            "description": "The request field at fault, when one is."
          }
        }
      }
    }
  }
}
