{
  "openapi": "3.1.0",
  "info": {
    "title": "Smart Business HQ for AI assistants",
    "version": "1.0.0",
    "description": "Design a Smart Business HQ board with the person in your chat, make their account, send the design, read where it stands and send changes or questions. Read getDesignGuide first. Guide: https://smartbusinesshq.com/ai"
  },
  "servers": [
    {
      "url": "https://smartbusinesshq.com"
    }
  ],
  "paths": {
    "/api/ai/v1/guide": {
      "get": {
        "operationId": "getDesignGuide",
        "summary": "Read the design guide",
        "description": "What the service is, how a board is laid out, how to design one with the person, the steps and the design format. Read it first.",
        "security": [],
        "responses": {
          "200": {
            "description": "The design guide.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "500": {
            "description": "Our side failed: try again in a minute.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/ai/v1/accounts": {
      "post": {
        "operationId": "createAccount",
        "summary": "Create the person's account",
        "description": "Makes the account and sends one email to the person. Nothing is built or charged until they press \"Yes, this is me\" in it (works 7 days). It needs four of the person's own details: email, business_name, name and phone. Only with their OK. Returns access_key: keep it private.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateAccountInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The account, waiting for the email press.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateAccountOutput"
                }
              }
            }
          },
          "400": {
            "description": "Bad request: the message says which field.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many calls: wait retryAfterSeconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds until a call is allowed again.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Our side failed: try again in a minute.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Email is off just now: nothing was kept, try again later.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/ai/v1/accounts/resend": {
      "post": {
        "operationId": "resendConfirmation",
        "summary": "Send the confirmation email again",
        "description": "Sends the confirmation email again, for when the first one did not arrive. Only while the sign-up waits for the press.",
        "security": [
          {
            "accessKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sent again, or why not.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResendOutput"
                }
              }
            }
          },
          "400": {
            "description": "Bad request: the message says which field.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The access key is missing or not known.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "The sign-up has expired or was declined.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many calls: wait retryAfterSeconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds until a call is allowed again.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Our side failed: try again in a minute.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Email is off just now: try again later.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/ai/v1/design": {
      "post": {
        "operationId": "sendDesign",
        "summary": "Send the board design",
        "description": "Sends the design the person has seen and agreed. Only after the person pressed \"Yes, this is me\" in our email (getStatus says confirmed); before that it is refused with 409 email_not_confirmed. Sending again makes a new version.",
        "security": [
          {
            "accessKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Design"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The design version we hold.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SendDesignOutput"
                }
              }
            }
          },
          "400": {
            "description": "Bad request: the message says which field.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The access key is missing or not known.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "email_not_confirmed: the person has not pressed \"Yes, this is me\" yet. Check getStatus until it says confirmed, then send the design.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "The sign-up has expired or was declined.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "The design is too large.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many calls: wait retryAfterSeconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds until a call is allowed again.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Our side failed: try again in a minute.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/ai/v1/status": {
      "get": {
        "operationId": "getStatus",
        "summary": "Get where it stands",
        "description": "The email press (account.state confirmed: send the design then), the design we hold, the build stage, what happens next, whether a pay link went to the person, and the newest messages with our answers.",
        "security": [
          {
            "accessKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Where it stands.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StatusOutput"
                }
              }
            }
          },
          "400": {
            "description": "Bad request: the message says which field.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The access key is missing or not known.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "The sign-up has expired or was declined.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many calls: wait retryAfterSeconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds until a call is allowed again.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Our side failed: try again in a minute.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/ai/v1/messages": {
      "post": {
        "operationId": "sendMessage",
        "summary": "Send a change or a question",
        "description": "Sends a change to the board or a question, in the person's words. Held until the email press. Our answers show in getStatus.",
        "security": [
          {
            "accessKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MessageInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Received.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SendMessageOutput"
                }
              }
            }
          },
          "400": {
            "description": "Bad request: the message says which field.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The access key is missing or not known.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "The sign-up has expired or was declined.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many calls: wait retryAfterSeconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds until a call is allowed again.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Our side failed: try again in a minute.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "accessKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "The access_key createAccount returned, as Authorization: Bearer <access_key>."
      }
    },
    "schemas": {
      "Design": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "business_name"
        ],
        "properties": {
          "business_name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "The business (or, for a personal board, the person's) name."
          },
          "kind": {
            "type": "string",
            "enum": [
              "business",
              "personal"
            ],
            "description": "business (default) or personal (one user)."
          },
          "website": {
            "type": "string",
            "maxLength": 200,
            "description": "Their website, https://…"
          },
          "language": {
            "type": "string",
            "enum": [
              "en",
              "es"
            ],
            "description": "The language the board's words are written in (default en)."
          },
          "languages": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "en",
                "es"
              ]
            },
            "maxItems": 2,
            "description": "The languages the board should be in."
          },
          "colour": {
            "type": "string",
            "pattern": "^#[0-9A-Fa-f]{6}$",
            "description": "Main colour, #RRGGBB."
          },
          "look": {
            "type": "string",
            "enum": [
              "light",
              "dark"
            ]
          },
          "summary": {
            "type": "string",
            "maxLength": 2000,
            "description": "What the board is for, in the person's words."
          },
          "tabs": {
            "type": "array",
            "maxItems": 8,
            "description": "The board's own tabs (Approvals, Map, Setup and Knowledge are on every board already).",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "name",
                "panels"
              ],
              "properties": {
                "name": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 40
                },
                "panels": {
                  "type": "array",
                  "maxItems": 40,
                  "items": {
                    "type": "object",
                    "additionalProperties": false,
                    "required": [
                      "title",
                      "kind"
                    ],
                    "properties": {
                      "title": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 80
                      },
                      "shows": {
                        "type": "string",
                        "maxLength": 500,
                        "description": "What it shows or does."
                      },
                      "kind": {
                        "type": "string",
                        "enum": [
                          "read",
                          "automation",
                          "agent"
                        ],
                        "description": "read = shows numbers or lists; automation = does a set job; agent = works on its own (we build and look after it)."
                      },
                      "view": {
                        "type": "string",
                        "enum": [
                          "bignum",
                          "chart",
                          "todo",
                          "table",
                          "cards",
                          "leaderboard",
                          "columns",
                          "calendar",
                          "feed",
                          "progress",
                          "timeline",
                          "map",
                          "custom"
                        ],
                        "description": "How it is drawn; custom (or absent) = we draw it from their words."
                      },
                      "source": {
                        "type": "string",
                        "maxLength": 500,
                        "description": "Where the data comes from today."
                      },
                      "runs": {
                        "type": "string",
                        "maxLength": 500,
                        "description": "automation / agent: when it runs and what it does."
                      },
                      "approval": {
                        "type": "string",
                        "enum": [
                          "needs_approval",
                          "always_allow",
                          "blocked"
                        ],
                        "description": "automation / agent: needs_approval (default), always_allow or blocked."
                      }
                    }
                  }
                }
              }
            }
          },
          "apps": {
            "type": "array",
            "maxItems": 30,
            "items": {
              "type": "string",
              "maxLength": 60
            },
            "description": "The apps and accounts it connects to."
          },
          "notes": {
            "type": "string",
            "maxLength": 4000,
            "description": "Anything else for us."
          },
          "files": {
            "type": "array",
            "maxItems": 5,
            "description": "A design the AI drew: an HTML page, an image (base64 png, jpeg or webp, up to 1 MB) or text.",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "kind",
                "content"
              ],
              "properties": {
                "kind": {
                  "type": "string",
                  "enum": [
                    "html",
                    "image",
                    "text"
                  ]
                },
                "name": {
                  "type": "string",
                  "maxLength": 120
                },
                "content": {
                  "type": "string"
                },
                "content_type": {
                  "type": "string",
                  "enum": [
                    "image/png",
                    "image/jpeg",
                    "image/webp"
                  ]
                }
              }
            }
          }
        }
      },
      "CreateAccountInput": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "email",
          "business_name",
          "name",
          "phone"
        ],
        "properties": {
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 254,
            "description": "The person's own email address, as they gave it."
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "The person's name."
          },
          "phone": {
            "type": "string",
            "minLength": 7,
            "maxLength": 30,
            "pattern": "^\\+?[0-9 ().-]+$",
            "description": "The person's phone number, as they gave it: digits, with an optional + at the start and spaces, dashes, dots or brackets between them; 7 to 15 digits."
          },
          "business_name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "The business (or, for a personal board, the person's) name."
          },
          "kind": {
            "type": "string",
            "enum": [
              "business",
              "personal"
            ],
            "description": "business (default) or personal (one user)."
          },
          "ai_name": {
            "type": "string",
            "maxLength": 60,
            "description": "Which AI you are, for example ChatGPT, Claude, Gemini or Grok."
          },
          "language": {
            "type": "string",
            "enum": [
              "en",
              "es"
            ],
            "description": "The email's language (default en)."
          }
        }
      },
      "MessageInput": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "kind",
          "text"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "change",
              "question"
            ],
            "description": "change = a change to the board; question = a question for us."
          },
          "text": {
            "type": "string",
            "minLength": 1,
            "maxLength": 4000,
            "description": "The change or question, in the person's words."
          }
        }
      },
      "CreateAccountOutput": {
        "type": "object",
        "required": [
          "ok",
          "access_key",
          "state",
          "email_sent",
          "message"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "access_key": {
            "type": "string",
            "description": "Pass it to every other call. Shown once."
          },
          "state": {
            "type": "string",
            "enum": [
              "waiting_for_email_click"
            ]
          },
          "email_sent": {
            "type": "boolean",
            "description": "true when our mail provider accepted the email."
          },
          "message": {
            "type": "string",
            "description": "Plain words to tell the person."
          }
        }
      },
      "SendDesignOutput": {
        "type": "object",
        "required": [
          "ok",
          "version",
          "tabs",
          "panels",
          "files",
          "state",
          "message"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "version": {
            "type": "integer"
          },
          "tabs": {
            "type": "integer"
          },
          "panels": {
            "type": "integer"
          },
          "files": {
            "type": "integer"
          },
          "state": {
            "type": "string",
            "enum": [
              "confirmed"
            ],
            "description": "A design is taken only after the person pressed \"Yes, this is me\"."
          },
          "message": {
            "type": "string"
          }
        }
      },
      "StatusOutput": {
        "type": "object",
        "required": [
          "ok",
          "status"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "status": {
            "type": "object",
            "required": [
              "account",
              "design",
              "build",
              "messages"
            ],
            "properties": {
              "account": {
                "type": "object",
                "required": [
                  "email",
                  "business_name",
                  "state",
                  "created_at"
                ],
                "properties": {
                  "email": {
                    "type": "string"
                  },
                  "business_name": {
                    "type": "string"
                  },
                  "state": {
                    "type": "string",
                    "enum": [
                      "waiting_for_email_click",
                      "confirmed",
                      "expired",
                      "declined"
                    ]
                  },
                  "created_at": {
                    "type": "string"
                  },
                  "confirmed_at": {
                    "anyOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "expires_at": {
                    "anyOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  }
                }
              },
              "design": {
                "type": "object",
                "required": [
                  "sent",
                  "versions",
                  "tabs",
                  "panels",
                  "files"
                ],
                "properties": {
                  "sent": {
                    "type": "boolean"
                  },
                  "versions": {
                    "type": "integer"
                  },
                  "last_sent_at": {
                    "anyOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "tabs": {
                    "type": "integer"
                  },
                  "panels": {
                    "type": "integer"
                  },
                  "files": {
                    "type": "integer"
                  }
                }
              },
              "build": {
                "type": "object",
                "required": [
                  "stage",
                  "where",
                  "next",
                  "pay"
                ],
                "properties": {
                  "stage": {
                    "type": "string"
                  },
                  "where": {
                    "type": "string",
                    "description": "Where it stands, in plain words."
                  },
                  "next": {
                    "type": "string",
                    "description": "What happens next and who does it."
                  },
                  "pay": {
                    "type": "string",
                    "enum": [
                      "not_yet",
                      "pay_link_sent",
                      "paid"
                    ]
                  }
                }
              },
              "messages": {
                "type": "array",
                "description": "The newest 20, oldest first: what you sent and our answers.",
                "items": {
                  "type": "object",
                  "required": [
                    "from",
                    "text",
                    "at"
                  ],
                  "properties": {
                    "from": {
                      "type": "string",
                      "enum": [
                        "you",
                        "smart_business_hq"
                      ]
                    },
                    "kind": {
                      "anyOf": [
                        {
                          "type": "string",
                          "enum": [
                            "change",
                            "question"
                          ]
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "text": {
                      "type": "string"
                    },
                    "at": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "SendMessageOutput": {
        "type": "object",
        "required": [
          "ok",
          "delivered",
          "state",
          "message"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "delivered": {
            "type": "boolean",
            "description": "true once it is with us; false = held until the person presses the email link."
          },
          "state": {
            "type": "string",
            "enum": [
              "waiting_for_email_click",
              "confirmed",
              "expired",
              "declined"
            ]
          },
          "message": {
            "type": "string"
          }
        }
      },
      "ResendOutput": {
        "type": "object",
        "required": [
          "ok",
          "email_sent",
          "message"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "email_sent": {
            "type": "boolean"
          },
          "message": {
            "type": "string"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "ok",
          "error",
          "message"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": false
          },
          "error": {
            "type": "string",
            "description": "A short code, for example bad_design, key_unknown, email_not_confirmed, rate_limited."
          },
          "message": {
            "type": "string",
            "description": "Plain words: what went wrong and what to do."
          },
          "retryAfterSeconds": {
            "type": "integer"
          }
        }
      }
    }
  }
}