{
  "openapi": "3.1.0",
  "info": {
    "title": "Amedeus Apps API",
    "version": "1",
    "description": "Lets an app act in the workspace that owns it. Authenticate with the app's bot token: `Authorization: Bearer amd_bot_…`. Each endpoint needs one permission (scope), given to the app in the Manage console, and works only in channels the app belongs to. Failures answer `{ \"ok\": false, \"error\": \"<code>\", \"message\": \"…\" }` with the real HTTP status; compare the code, not the message. Posting is limited to 60 messages a minute per channel, and every endpoint to 300 requests a minute per address."
  },
  "servers": [
    {
      "url": "https://amedeus.app/api/public/apps/v1"
    }
  ],
  "security": [
    {
      "botToken": []
    }
  ],
  "paths": {
    "/messages": {
      "post": {
        "operationId": "postMessage",
        "summary": "Post a message",
        "description": "Posts as the app, in a channel it belongs to, and returns the message. Send an `id` to make retries safe: the same id never posts twice.",
        "x-required-scope": "chat:write",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NewMessage"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Posted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Posted"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/reactions": {
      "post": {
        "operationId": "addReaction",
        "summary": "Add a reaction",
        "description": "Adds the app's own reaction. Adding the same one twice changes nothing.",
        "x-required-scope": "reactions:write",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Reaction"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "delete": {
        "operationId": "removeReaction",
        "summary": "Remove a reaction",
        "x-required-scope": "reactions:write",
        "parameters": [
          {
            "name": "message_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "emoji",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 34
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Removed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/channels": {
      "get": {
        "operationId": "listChannels",
        "summary": "List channels",
        "description": "Every public channel of the workspace, each flagged `is_member`, so an integration can offer them in a picker and let people choose where the app goes. With `groups:read` it also lists the private channels the app already belongs to; a private channel the app was not added to is never listed.",
        "x-required-scope": "channels:read",
        "x-also-scopes": [
          "groups:read"
        ],
        "responses": {
          "200": {
            "description": "The channels.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChannelList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/channels/{channelId}/join": {
      "post": {
        "operationId": "joinChannel",
        "summary": "Join a public channel",
        "description": "The app adds itself to a public channel. Joining twice changes nothing. A private channel can only be joined when a person adds the app to it.",
        "x-required-scope": "channels:join",
        "parameters": [
          {
            "name": "channelId",
            "in": "path",
            "required": true,
            "description": "A public channel of the workspace.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Joined.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/channels/{channelId}/messages": {
      "get": {
        "operationId": "listMessages",
        "summary": "Read a channel's messages",
        "description": "Newest first. When `next_cursor` is not null, pass it as `cursor` to read the next, older page.",
        "x-required-scope": "channels:history",
        "parameters": [
          {
            "name": "channelId",
            "in": "path",
            "required": true,
            "description": "A channel the app belongs to.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "The `next_cursor` of the previous page.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of messages.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/users": {
      "get": {
        "operationId": "listUsers",
        "summary": "List the people of the workspace",
        "description": "Names and ids only. Pass `next_cursor` as `cursor` to read the next page.",
        "x-required-scope": "users:read",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "The `next_cursor` of the previous page.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of people.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/hooks/{token}": {
      "servers": [
        {
          "url": "https://amedeus.app/api/public/apps"
        }
      ],
      "post": {
        "operationId": "postToWebhook",
        "summary": "Post through an incoming webhook",
        "description": "Posts `text` to the channel the webhook was made for, as the app. The body matches Slack's, so existing tools work: without `text`, Slack's `attachments` (`pretext`, `title`, `text`, `fields`) become the text.",
        "x-required-scope": "incoming-webhook",
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "text": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 8000
                  },
                  "attachments": {
                    "type": "array",
                    "maxItems": 20,
                    "description": "Used only when `text` is missing.",
                    "items": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Posted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HookPosted"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "description": "The URL is wrong or was replaced.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": []
      }
    },
    "/responses/{token}": {
      "servers": [
        {
          "url": "https://amedeus.app/api/public/apps"
        }
      ],
      "post": {
        "operationId": "respondToCommand",
        "summary": "Answer a slash command",
        "description": "Posts the app's answer to the person who ran the command: privately by default, or in the channel as the app with `response_type: in_channel`. The URL comes in the command's `response_url`, works for 30 minutes and up to 5 times, and needs no token.",
        "x-required-scope": "commands",
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "text"
                ],
                "properties": {
                  "text": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 8000,
                    "description": "Markdown."
                  },
                  "response_type": {
                    "enum": [
                      "ephemeral",
                      "in_channel"
                    ],
                    "default": "ephemeral"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "The URL is wrong or has expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": []
      }
    }
  },
  "components": {
    "securitySchemes": {
      "botToken": {
        "type": "http",
        "scheme": "bearer",
        "description": "The app's bot token, shown once when the app is created or its token replaced.",
        "x-scopes": {
          "incoming-webhook": "Post to a channel through secret URLs, with no token. Free, with a limit.",
          "chat:write": "Post messages and thread replies in the channels the app belongs to.",
          "reactions:write": "Add and remove its own reactions on messages in its channels.",
          "channels:read": "List the workspace's public channels, so an integration can let people pick where the app goes.",
          "groups:read": "Also list the private channels the app already belongs to. A person has to add it to a private channel.",
          "channels:join": "Add itself to public channels.",
          "channels:history": "Read the messages of its channels, and receive message events.",
          "users:read": "List the people of the workspace: names only, never emails.",
          "app_mentions:read": "Receive the messages that mention the app.",
          "commands": "Own slash commands that people can run."
        }
      }
    },
    "schemas": {
      "Ok": {
        "type": "object",
        "required": [
          "ok"
        ],
        "properties": {
          "ok": {
            "const": true
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "ok",
          "error",
          "message"
        ],
        "properties": {
          "ok": {
            "const": false
          },
          "error": {
            "enum": [
              "invalid_auth",
              "missing_scope",
              "app_disabled",
              "forbidden",
              "not_in_channel",
              "channel_not_found",
              "channel_not_public",
              "message_not_found",
              "thread_not_found",
              "webhook_not_found",
              "invalid_arguments",
              "invalid_cursor",
              "invalid_json",
              "invalid_content_type",
              "request_too_large",
              "message_id_in_use",
              "response_url_invalid",
              "response_url_exhausted",
              "ratelimited",
              "internal_error"
            ],
            "description": "A stable code to compare. The message can change; the code does not."
          },
          "message": {
            "type": "string",
            "description": "For people. Don't parse it."
          },
          "needed": {
            "type": "string",
            "description": "With `missing_scope`: the permission the call needs."
          },
          "details": {
            "type": "array",
            "description": "With `invalid_arguments`: what was wrong, field by field.",
            "items": {
              "type": "object",
              "properties": {
                "field": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "Message": {
        "type": "object",
        "required": [
          "id",
          "channel",
          "user",
          "text",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "channel": {
            "type": "string",
            "format": "uuid"
          },
          "user": {
            "type": "string",
            "format": "uuid",
            "description": "Who wrote it."
          },
          "text": {
            "type": "string",
            "description": "Markdown, as it was written."
          },
          "parent_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The thread it replies to."
          },
          "mentions": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "reply_count": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "edited_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "NewMessage": {
        "type": "object",
        "required": [
          "channel",
          "text"
        ],
        "properties": {
          "channel": {
            "type": "string",
            "format": "uuid"
          },
          "text": {
            "type": "string",
            "minLength": 1,
            "maxLength": 8000
          },
          "parent_id": {
            "type": "string",
            "format": "uuid",
            "description": "Reply in this message's thread."
          },
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Makes the request idempotent."
          },
          "mentions": {
            "type": "array",
            "maxItems": 50,
            "items": {
              "type": "string",
              "format": "uuid"
            }
          }
        }
      },
      "Posted": {
        "type": "object",
        "required": [
          "ok",
          "message"
        ],
        "properties": {
          "ok": {
            "const": true
          },
          "message": {
            "$ref": "#/components/schemas/Message"
          }
        }
      },
      "HookPosted": {
        "type": "object",
        "required": [
          "ok",
          "id"
        ],
        "properties": {
          "ok": {
            "const": true
          },
          "id": {
            "type": "string",
            "format": "uuid"
          }
        }
      },
      "Reaction": {
        "type": "object",
        "required": [
          "message_id",
          "emoji"
        ],
        "properties": {
          "message_id": {
            "type": "string",
            "format": "uuid"
          },
          "emoji": {
            "type": "string",
            "minLength": 1,
            "maxLength": 34
          }
        }
      },
      "ChannelList": {
        "type": "object",
        "required": [
          "ok",
          "channels"
        ],
        "properties": {
          "ok": {
            "const": true
          },
          "channels": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "name": {
                  "type": "string"
                },
                "topic": {
                  "type": "string"
                },
                "kind": {
                  "enum": [
                    "public",
                    "private"
                  ]
                },
                "is_member": {
                  "type": "boolean",
                  "description": "Whether the app is in the channel."
                },
                "created_at": {
                  "type": "string",
                  "format": "date-time"
                }
              }
            }
          }
        }
      },
      "MessageList": {
        "type": "object",
        "required": [
          "ok",
          "messages",
          "next_cursor"
        ],
        "properties": {
          "ok": {
            "const": true
          },
          "messages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Message"
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          }
        }
      },
      "UserList": {
        "type": "object",
        "required": [
          "ok",
          "users",
          "next_cursor"
        ],
        "properties": {
          "ok": {
            "const": true
          },
          "users": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "display_name": {
                  "type": "string"
                },
                "is_bot": {
                  "type": "boolean"
                }
              }
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "The request isn't valid.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "The token is missing, wrong or revoked.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "A missing scope (the message names it), or a channel the app isn't in.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "The message or channel doesn't exist.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Slow down; `Retry-After` says for how many seconds.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  }
}
