{
  "openapi": "3.0.3",
  "info": {
    "title": "TalkToPlanB Developer API",
    "version": "1.0.0",
    "description": "Send and read messages and receive webhooks on TalkToPlanB. Each API key acts on behalf of one TalkToPlanB account (a bot identity). Authenticate every request with the `X-API-Key` header. Rate limit: 60 requests per minute."
  },
  "servers": [
    { "url": "https://talktoplanb.duckdns.org", "description": "Production" }
  ],
  "security": [{ "ApiKeyAuth": [] }],
  "tags": [
    { "name": "Messages", "description": "Send and read messages" },
    { "name": "Rooms", "description": "List conversations" },
    { "name": "Account", "description": "Info about the authenticated bot account" }
  ],
  "paths": {
    "/api/v1/me": {
      "get": {
        "tags": ["Account"],
        "summary": "Get the account this API key acts as",
        "operationId": "getMe",
        "responses": {
          "200": {
            "description": "Account info",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Account" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/v1/rooms": {
      "get": {
        "tags": ["Rooms"],
        "summary": "List rooms (conversations) the account belongs to",
        "operationId": "listRooms",
        "responses": {
          "200": {
            "description": "Array of rooms",
            "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Room" } } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      }
    },
    "/api/v1/messages": {
      "get": {
        "tags": ["Messages"],
        "summary": "Read message history for a room",
        "operationId": "listMessages",
        "parameters": [
          { "name": "roomId", "in": "query", "required": true, "schema": { "type": "string", "format": "uuid" }, "description": "The room to read messages from." },
          { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "default": 50, "maximum": 200 }, "description": "Max messages to return (newest last)." }
        ],
        "responses": {
          "200": {
            "description": "Array of messages, oldest first",
            "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Message" } } } }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      },
      "post": {
        "tags": ["Messages"],
        "summary": "Send a message",
        "description": "Send a message either to an existing room (roomId) or to a user by phone number (toPhone). If toPhone is used and no DM exists yet, one is created automatically.",
        "operationId": "sendMessage",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SendMessageRequest" } } }
        },
        "responses": {
          "201": {
            "description": "Message sent",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SendMessageResponse" } } }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "Your API key, e.g. ttpb_xxxxxxxxxxxx. Generate one at /portal."
      }
    },
    "schemas": {
      "Account": {
        "type": "object",
        "properties": {
          "userId": { "type": "string", "format": "uuid" },
          "username": { "type": "string" },
          "scopes": { "type": "array", "items": { "type": "string" }, "example": ["messages:send", "messages:read"] }
        }
      },
      "Room": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "name": { "type": "string" },
          "is_group": { "type": "boolean" },
          "is_ai_room": { "type": "boolean" }
        }
      },
      "Message": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "content": { "type": "string" },
          "created_at": { "type": "string", "format": "date-time" },
          "sender_id": { "type": "string", "format": "uuid" },
          "sender_name": { "type": "string" },
          "attachment_url": { "type": "string", "nullable": true },
          "attachment_name": { "type": "string", "nullable": true },
          "attachment_type": { "type": "string", "nullable": true }
        }
      },
      "SendMessageRequest": {
        "type": "object",
        "properties": {
          "roomId": { "type": "string", "format": "uuid", "description": "Target room. Provide this OR toPhone." },
          "toPhone": { "type": "string", "description": "Recipient phone in E.164, e.g. +60123456789. Creates a DM if needed.", "example": "+60123456789" },
          "text": { "type": "string", "description": "Message text.", "example": "Hello from my bot!" },
          "attachment": {
            "type": "object",
            "nullable": true,
            "properties": {
              "url": { "type": "string" },
              "name": { "type": "string" },
              "size": { "type": "integer" },
              "type": { "type": "string" }
            }
          }
        },
        "required": ["text"]
      },
      "SendMessageResponse": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "roomId": { "type": "string", "format": "uuid" },
          "createdAt": { "type": "string", "format": "date-time" }
        }
      },
      "WebhookEvent": {
        "type": "object",
        "description": "POSTed to your configured webhook URL when a message arrives for the account.",
        "properties": {
          "event": { "type": "string", "example": "message.received" },
          "data": { "$ref": "#/components/schemas/Message" }
        }
      },
      "Error": {
        "type": "object",
        "properties": { "error": { "type": "string" } }
      }
    },
    "responses": {
      "BadRequest": { "description": "Invalid request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "Unauthorized": { "description": "Missing or invalid API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "Forbidden": { "description": "Key lacks the required scope or no access", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "NotFound": { "description": "Resource not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
    }
  }
}
