{
  "openapi": "3.1.0",
  "info": {
    "title": "ChatBakers Public API",
    "version": "1.0.0",
    "description": "Programmatic access to ChatBakers AI visibility monitoring. Authenticate every request with `Authorization: Bearer cbk_live_...`. Agency master keys (`cbk_agency_live_...`) additionally require an `X-Client-Org-Slug` header. See https://chatbakers.com/docs/api/ for tutorials.",
    "contact": {
      "name": "ChatBakers",
      "url": "https://chatbakers.com/docs/api/"
    }
  },
  "servers": [
    {
      "url": "http://app.chatbakers.com"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "ChatBakers key: cbk_live_..., cbk_read_..., or cbk_agency_live_..."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error",
          "code",
          "requestId"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "requestId": {
            "type": "string",
            "format": "uuid"
          },
          "details": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "Website": {
        "type": "object",
        "required": [
          "id",
          "name",
          "domain",
          "country",
          "type",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "domain": {
            "type": "string"
          },
          "country": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "brand",
              "ecommerce",
              "agency"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "initStatus": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "pending",
              "analyzing",
              "topics",
              "prompts",
              "competitors",
              "collecting",
              "ready",
              null
            ]
          }
        }
      },
      "Topic": {
        "type": "object",
        "required": [
          "id",
          "websiteId",
          "name",
          "isActive",
          "source",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "websiteId": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "maxLength": 100
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500
          },
          "isActive": {
            "type": "boolean"
          },
          "source": {
            "type": "string",
            "enum": [
              "ai-generated",
              "user-created",
              "generic-fallback"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Prompt": {
        "type": "object",
        "required": [
          "id",
          "websiteId",
          "text",
          "intent",
          "country",
          "status",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "websiteId": {
            "type": "string",
            "format": "uuid"
          },
          "text": {
            "type": "string",
            "maxLength": 500
          },
          "intent": {
            "type": "string",
            "enum": [
              "informational",
              "commercial",
              "transactional",
              "navigational"
            ]
          },
          "country": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "archived",
              "suggested",
              "collecting"
            ]
          },
          "topicId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "lastQueriedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "Competitor": {
        "type": "object",
        "required": [
          "id",
          "websiteId",
          "name",
          "domain",
          "isTracking",
          "mentionedInPrompts",
          "discoveredAutomatically",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "websiteId": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "domain": {
            "type": "string",
            "description": "Empty string when the competitor has no domain yet."
          },
          "logoUrl": {
            "type": [
              "string",
              "null"
            ]
          },
          "isTracking": {
            "type": "boolean"
          },
          "mentionedInPrompts": {
            "type": "integer"
          },
          "discoveredAutomatically": {
            "type": "boolean"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Citation": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "websiteId": {
            "type": "string",
            "format": "uuid"
          },
          "url": {
            "type": "string"
          },
          "domain": {
            "type": "string"
          },
          "category": {
            "type": "string",
            "enum": [
              "competition",
              "own",
              "neutral"
            ]
          },
          "aiModels": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "firstSeenAt": {
            "type": "string",
            "format": "date-time"
          },
          "lastSeenAt": {
            "type": "string",
            "format": "date-time"
          },
          "usageRate": {
            "type": "number",
            "description": "Share of prompts (0–1) whose answers cited this URL."
          },
          "usageCount": {
            "type": "integer"
          },
          "totalPrompts": {
            "type": "integer"
          },
          "isPinned": {
            "type": "boolean"
          }
        }
      },
      "Scores": {
        "type": "object",
        "properties": {
          "visibilityScore": {
            "type": "integer",
            "description": "0–100."
          },
          "sentimentScore": {
            "type": "number",
            "description": "−1.00 … +1.00."
          },
          "citationScore": {
            "type": "integer",
            "description": "0–100."
          },
          "competitorCount": {
            "type": "integer",
            "description": "Number of tracked competitors."
          }
        }
      },
      "Dashboard": {
        "type": "object",
        "description": "Brand dashboard aggregate — AI Search score, trends, per-model performance, top brands/prompts/domains.",
        "properties": {
          "score": {
            "type": "object",
            "properties": {
              "score": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "label": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Excellent | Good | Needs Improvement | Critical"
              },
              "breakdown": {
                "type": "object",
                "additionalProperties": true,
                "description": "Per-component (visibility / citation / sentiment) value, weight, contribution."
              }
            }
          },
          "scoreTrend": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "date": {
                  "type": "string"
                },
                "value": {
                  "type": [
                    "number",
                    "null"
                  ]
                }
              }
            }
          },
          "visibilityTrend": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "date": {
                  "type": "string"
                },
                "value": {
                  "type": [
                    "number",
                    "null"
                  ]
                }
              }
            }
          },
          "citationTrend": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "date": {
                  "type": "string"
                },
                "value": {
                  "type": [
                    "number",
                    "null"
                  ]
                }
              }
            }
          },
          "sentimentTrend": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "date": {
                  "type": "string"
                },
                "value": {
                  "type": [
                    "number",
                    "null"
                  ]
                }
              }
            }
          },
          "bestPerformingModel": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "model": {
                "type": "string"
              },
              "visibilityScore": {
                "type": "number"
              },
              "citationCount": {
                "type": "integer"
              },
              "resultCount": {
                "type": "integer"
              },
              "tracked": {
                "type": "boolean"
              }
            }
          },
          "bestVisibilityModel": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "model": {
                "type": "string"
              },
              "visibilityScore": {
                "type": "number"
              },
              "citationCount": {
                "type": "integer"
              },
              "resultCount": {
                "type": "integer"
              },
              "tracked": {
                "type": "boolean"
              }
            }
          },
          "mostCitedModel": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "model": {
                "type": "string"
              },
              "visibilityScore": {
                "type": "number"
              },
              "citationCount": {
                "type": "integer"
              },
              "resultCount": {
                "type": "integer"
              },
              "tracked": {
                "type": "boolean"
              }
            }
          },
          "perModel": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "model": {
                  "type": "string"
                },
                "visibilityScore": {
                  "type": "number"
                },
                "citationCount": {
                  "type": "integer"
                },
                "resultCount": {
                  "type": "integer"
                },
                "tracked": {
                  "type": "boolean"
                }
              }
            }
          },
          "topBrands": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "rank": {
                  "type": "integer"
                },
                "name": {
                  "type": "string"
                },
                "domain": {
                  "type": "string"
                },
                "logoUrl": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "visibilityPercent": {
                  "type": "number"
                },
                "avgPosition": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "isOwn": {
                  "type": "boolean"
                },
                "sparkline": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "date": {
                        "type": "string"
                      },
                      "value": {
                        "type": [
                          "number",
                          "null"
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          "averageBrandVisibility": {
            "type": [
              "number",
              "null"
            ]
          },
          "averageBrandPosition": {
            "type": [
              "number",
              "null"
            ]
          },
          "topPrompts": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "promptId": {
                  "type": "string"
                },
                "text": {
                  "type": "string"
                },
                "topicId": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "visibilityScore": {
                  "type": "number"
                },
                "sparkline": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "date": {
                        "type": "string"
                      },
                      "value": {
                        "type": [
                          "number",
                          "null"
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          "topDomains": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "domain": {
                  "type": "string"
                },
                "count": {
                  "type": "integer"
                },
                "category": {
                  "type": "string",
                  "enum": [
                    "own",
                    "competitor",
                    "neutral"
                  ]
                }
              }
            }
          },
          "meta": {
            "type": "object",
            "properties": {
              "totalPrompts": {
                "type": "integer"
              },
              "totalResults": {
                "type": "integer"
              },
              "lastUpdatedAt": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "durationMs": {
                "type": "integer"
              },
              "hasData": {
                "type": "boolean"
              }
            }
          }
        }
      },
      "Pagination": {
        "type": "object",
        "properties": {
          "cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Reserved for cursor pagination; currently always null."
          },
          "hasMore": {
            "type": "boolean"
          },
          "page": {
            "type": "integer",
            "description": "Present on offset-paginated endpoints (e.g. citations)."
          },
          "perPage": {
            "type": "integer",
            "description": "Present on offset-paginated endpoints (e.g. citations)."
          },
          "total": {
            "type": "integer",
            "description": "Present on offset-paginated endpoints (e.g. citations)."
          }
        }
      }
    },
    "parameters": {
      "WebsiteId": {
        "name": "websiteId",
        "in": "query",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "ClientOrgSlug": {
        "name": "X-Client-Org-Slug",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string"
        },
        "description": "Required when using a cbk_agency_live_* master key; identifies which client org the request targets."
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string",
          "maxLength": 100
        },
        "description": "Optional. Cached for 86400 seconds per (key, endpoint)."
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing or invalid Authorization header",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "Insufficient scope (read-only key on a write endpoint)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "PaymentRequired": {
        "description": "Subscription cascade (trial expired / past_due / cancelled)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found (or scoped to a different organization)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Rate limit exceeded",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Limit": {
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Remaining": {
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Reset": {
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  },
  "paths": {
    "/api/v1/websites": {
      "get": {
        "summary": "List websites accessible to this key",
        "tags": [
          "Websites"
        ],
        "responses": {
          "200": {
            "description": "Websites list",
            "headers": {
              "X-Request-Id": {
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Website"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/websites/{id}": {
      "get": {
        "summary": "Get a single website",
        "tags": [
          "Websites"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Website detail",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Website"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/dashboard": {
      "get": {
        "summary": "Dashboard aggregate for a website",
        "tags": [
          "Dashboard"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebsiteId"
          }
        ],
        "responses": {
          "200": {
            "description": "Dashboard aggregate",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Dashboard"
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/topics": {
      "get": {
        "summary": "List topics for a website",
        "tags": [
          "Topics"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebsiteId"
          }
        ],
        "responses": {
          "200": {
            "description": "Topics list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Topic"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "summary": "Create a new topic",
        "tags": [
          "Topics"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "websiteId",
                  "name"
                ],
                "properties": {
                  "websiteId": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string",
                    "maxLength": 100
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 500
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Topic created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Topic"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/topics/{id}": {
      "get": {
        "summary": "Get a single topic",
        "tags": [
          "Topics"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Topic detail",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Topic"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "summary": "Update topic name and/or description (OQ-4 lock)",
        "description": "Topics support rename + description updates. `PATCH /api/v1/prompts/{id}` is intentionally not exposed — prompt text is immutable because historical scrape results are tied to it.",
        "tags": [
          "Topics"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "minProperties": 1,
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 100
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Topic updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Topic"
                }
              }
            }
          },
          "400": {
            "description": "Empty body or invalid fields",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "summary": "Archive a topic (soft delete)",
        "tags": [
          "Topics"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Archived"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/topics/generate": {
      "post": {
        "summary": "Enqueue an async topic generation job",
        "tags": [
          "Topics"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "websiteId"
                ],
                "properties": {
                  "websiteId": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Job enqueued — poll GET /api/v1/jobs/{id}",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "jobId": {
                          "type": "string"
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "queued"
                          ]
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/prompts": {
      "get": {
        "summary": "List prompts for a website",
        "tags": [
          "Prompts"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebsiteId"
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "archived",
                "suggested",
                "collecting"
              ]
            }
          },
          {
            "name": "topicId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Prompts list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Prompt"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "summary": "Create prompts (single or batch)",
        "description": "Batch is limited to 50 prompts per request. Exceeding the org plan cap returns 409 QUOTA_EXCEEDED.",
        "tags": [
          "Prompts"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "websiteId",
                  "prompts"
                ],
                "properties": {
                  "websiteId": {
                    "type": "string"
                  },
                  "prompts": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 50,
                    "items": {
                      "type": "object",
                      "required": [
                        "text",
                        "intent"
                      ],
                      "properties": {
                        "text": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 500
                        },
                        "intent": {
                          "type": "string",
                          "enum": [
                            "informational",
                            "commercial",
                            "transactional",
                            "navigational"
                          ]
                        },
                        "country": {
                          "type": "string"
                        },
                        "topicId": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Prompts created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Prompt"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "QUOTA_EXCEEDED — plan cap reached",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "PRMPT_BATCH_LIMIT — batch exceeds maximum",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/prompts/{id}": {
      "get": {
        "summary": "Get a single prompt",
        "tags": [
          "Prompts"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Prompt detail",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Prompt"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "summary": "Archive a prompt (soft delete)",
        "tags": [
          "Prompts"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Archived"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "summary": "PATCH /prompts/{id} is a permanent ban (OQ-4)",
        "description": "Prompt text is part of the historical scrape identity. Editing would invalidate historical visibility metrics. PATCH /api/v1/topics/{id} is exposed because topic labels do not affect scrapes.",
        "tags": [
          "Prompts"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "405": {
            "description": "Method Not Allowed — permanent ban",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/competitors": {
      "get": {
        "summary": "List competitors",
        "description": "CHAT-290 — pass view=grouped to get one entry per brand group (duplicates merged in the UI count as one competitor, with memberNames). Default view=flat returns raw rows.",
        "tags": [
          "Competitors"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebsiteId"
          },
          {
            "name": "view",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "flat",
                "grouped"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Competitors list. Default view=flat returns Competitor objects. view=grouped returns one entry per brand group instead — { groupId, name, domain, logoUrl, isTracking, totalMentions, memberNames[], memberIds[] } — plus a top-level view:\"grouped\".",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Competitor"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "summary": "Add a competitor",
        "description": "CHAT-291 — domain is optional. Adding a same-name competitor with a domain merge-fills the existing record (200 + merged: true) when it has no domain yet.",
        "tags": [
          "Competitors"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "websiteId",
                  "name"
                ],
                "properties": {
                  "websiteId": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string",
                    "maxLength": 120
                  },
                  "domain": {
                    "type": "string",
                    "maxLength": 255
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Existing same-name competitor was domain-merge-filled (merged: true).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Competitor"
                    },
                    "merged": {
                      "type": "boolean"
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "201": {
            "description": "Competitor added (merged: false).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Competitor"
                    },
                    "merged": {
                      "type": "boolean"
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Duplicate competitor with a conflicting domain",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/competitors/{id}": {
      "patch": {
        "summary": "Update a competitor (name and/or domain)",
        "description": "CHAT-291 — attach a domain to an auto-discovered competitor or rename it. Renames keep the old name as an alias; historical mention data is unaffected. Empty domain string clears the domain.",
        "tags": [
          "Competitors"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 120
                  },
                  "domain": {
                    "type": "string",
                    "maxLength": 255
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Competitor updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Competitor"
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Name or domain conflicts with another competitor",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/citations": {
      "get": {
        "summary": "List citations",
        "tags": [
          "Citations"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebsiteId"
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "competition",
                "own",
                "neutral"
              ]
            }
          },
          {
            "name": "isPinned",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          },
          {
            "name": "perPage",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Citations list (offset-paginated: pagination.page / perPage / total).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Citation"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/scores": {
      "get": {
        "summary": "Aggregate visibility / sentiment / citation scores",
        "tags": [
          "Scores"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebsiteId"
          }
        ],
        "responses": {
          "200": {
            "description": "Scores aggregate",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Scores"
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/site-audit": {
      "get": {
        "summary": "Site Audit findings — Health Score, per-category scores, findings with fix guidance, New/Fixed diff",
        "tags": [
          "Site Audit"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebsiteId"
          },
          {
            "name": "auditId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Specific audit id (from recentAudits); defaults to the latest audit."
          }
        ],
        "responses": {
          "200": {
            "description": "Audit findings (audit: null when the website has no audit yet)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "audit": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "description": "The audit these findings come from. null when the website has never been audited.",
                          "properties": {
                            "id": {
                              "type": "string"
                            },
                            "status": {
                              "type": "string",
                              "enum": [
                                "queued",
                                "running",
                                "completed",
                                "failed"
                              ]
                            },
                            "trigger": {
                              "type": "string",
                              "description": "manual | scheduled"
                            },
                            "crawlMethod": {
                              "type": "string"
                            },
                            "startedAt": {
                              "type": "string",
                              "format": "date-time"
                            },
                            "completedAt": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "format": "date-time"
                            },
                            "pagesCrawled": {
                              "type": "integer"
                            },
                            "pagesAnalyzed": {
                              "type": "integer"
                            }
                          }
                        },
                        "healthScore": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "description": "Share of crawled pages without a critical issue, 0–100."
                        },
                        "healthBand": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "weak | fair | good | excellent"
                        },
                        "severityCounts": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "integer"
                          },
                          "description": "Finding count keyed by severity (critical / moderate / minor)."
                        },
                        "categoryScores": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "number"
                          },
                          "description": "0–100 score per category (Technical SEO, Content Quality, GEO Readiness)."
                        },
                        "findings": {
                          "type": "array",
                          "description": "Every distinct finding, aggregated across affected pages.",
                          "items": {
                            "type": "object",
                            "properties": {
                              "type": {
                                "type": "string",
                                "description": "Stable rule id, e.g. \"title-missing\"."
                              },
                              "severity": {
                                "type": "string",
                                "enum": [
                                  "critical",
                                  "moderate",
                                  "minor"
                                ]
                              },
                              "category": {
                                "type": "string"
                              },
                              "scope": {
                                "type": "string",
                                "description": "page | site"
                              },
                              "occurrences": {
                                "type": "integer"
                              },
                              "pages": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                },
                                "description": "Affected page URLs (capped per finding)."
                              },
                              "pagesTotal": {
                                "type": "integer",
                                "description": "Total affected pages, even when `pages` is capped."
                              },
                              "guidance": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "description": "Why the finding matters for AI visibility and how to fix it.",
                                "properties": {
                                  "whyItMattersForAI": {
                                    "type": "string"
                                  },
                                  "howToFix": {
                                    "type": "string"
                                  }
                                }
                              }
                            }
                          }
                        },
                        "diff": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "description": "Change vs the previous audit. Page-level changes compare only pages crawled in BOTH audits.",
                          "properties": {
                            "newByType": {
                              "type": "object",
                              "additionalProperties": {
                                "type": "integer"
                              }
                            },
                            "fixedByType": {
                              "type": "object",
                              "additionalProperties": {
                                "type": "integer"
                              }
                            },
                            "newTotal": {
                              "type": "integer"
                            },
                            "fixedTotal": {
                              "type": "integer"
                            },
                            "comparablePages": {
                              "type": "integer"
                            },
                            "baselineAuditId": {
                              "type": [
                                "string",
                                "null"
                              ]
                            }
                          }
                        },
                        "trend": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "additionalProperties": true
                          },
                          "description": "Health score over recent audits."
                        },
                        "recentAudits": {
                          "type": "array",
                          "description": "Recent audits — pass an id back as ?auditId to fetch an older one.",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "status": {
                                "type": "string"
                              },
                              "startedAt": {
                                "type": "string",
                                "format": "date-time"
                              },
                              "healthScore": {
                                "type": [
                                  "integer",
                                  "null"
                                ]
                              }
                            }
                          }
                        },
                        "disabledChecks": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "Rule ids the user has turned off for this website."
                        }
                      }
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/jobs/{id}": {
      "get": {
        "summary": "Poll an async job",
        "tags": [
          "Jobs"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Job status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "type": {
                          "type": "string"
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "queued",
                            "running",
                            "completed",
                            "failed"
                          ]
                        },
                        "progress": {
                          "type": [
                            "number",
                            "null"
                          ]
                        },
                        "result": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "additionalProperties": true
                        },
                        "error": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    }
  },
  "x-rate-limits": {
    "perKeyRead": "600/60s",
    "perKeyWrite": "60/60s",
    "perKeyGenerate": "5/60s",
    "perIpRead": "1000/60s"
  }
}