{
  "openapi": "3.1.0",
  "info": {
    "title": "Sonar API",
    "description": "App Store Optimization data for AI agents, MCP tools, and automation. Keyword research, rank tracking, ASO scoring, reviews, and revenue estimates for the iOS App Store and Google Play.",
    "version": "1.0.0",
    "termsOfService": "https://trysonar.app/terms",
    "contact": {
      "url": "https://trysonar.app/support"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://trysonar.app/terms"
    }
  },
  "servers": [
    {
      "url": "https://trysonar.app",
      "description": "Production"
    }
  ],
  "externalDocs": {
    "description": "API reference",
    "url": "https://trysonar.app/docs/api"
  },
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "aso_xxx",
        "description": "API key with the `aso_` prefix. Get one at /developers (50 free credits on signup). A few stateless endpoints also work with NO auth at all (anonymous free tier, per-IP daily limits): /apps/search, /apps/lookup, /apps/aso-score, /apps/extract-keywords, /keywords/suggestions, /charts/top (shared 30 req/day) and /keywords/metrics (5 keywords/day)."
      }
    },
    "parameters": {
      "Store": {
        "name": "store",
        "in": "query",
        "required": true,
        "description": "App store",
        "schema": {
          "type": "string",
          "enum": [
            "ios",
            "android"
          ]
        }
      },
      "Country": {
        "name": "country",
        "in": "query",
        "required": false,
        "description": "ISO 3166-1 alpha-2 country code",
        "schema": {
          "type": "string",
          "default": "us",
          "pattern": "^[a-z]{2}$"
        }
      },
      "Cursor": {
        "name": "cursor",
        "in": "query",
        "required": false,
        "description": "Pagination cursor from a previous response's next_cursor",
        "schema": {
          "type": "string"
        }
      },
      "Limit": {
        "name": "limit",
        "in": "query",
        "required": false,
        "description": "Page size (1–200)",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 200
        }
      },
      "Days": {
        "name": "days",
        "in": "query",
        "required": false,
        "description": "History window in days (1–365)",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 365,
          "default": 30
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      },
      "App": {
        "type": "object",
        "properties": {
          "storeId": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "developer": {
            "type": "string"
          },
          "rating": {
            "type": "number"
          },
          "reviews": {
            "type": "integer"
          },
          "category": {
            "type": "string"
          },
          "iconUrl": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "Keyword": {
        "type": "object",
        "properties": {
          "keyword": {
            "type": "string"
          },
          "popularity": {
            "type": "number",
            "description": "0–100 popularity score"
          },
          "popularity_proxy": {
            "type": "number",
            "nullable": true,
            "description": "Sonar's in-house estimate, present only when Apple censors an iOS keyword's real Search Popularity to its floor (5). Disambiguates keywords inside the censored band (rendered as '5 (58)'). Null otherwise."
          },
          "popularity_source": {
            "type": "string",
            "enum": [
              "apple",
              "proxy"
            ],
            "nullable": true,
            "description": "Provenance of `popularity`: 'apple' = real Apple Search Popularity reported by Apple Search Ads (not an estimate), 'proxy' = Sonar's in-house estimate. Null when unknown or when popularity is null."
          },
          "difficulty": {
            "type": "number",
            "description": "0–100 proprietary difficulty score"
          },
          "est_downloads_at_1": {
            "type": "number",
            "nullable": true,
            "description": "Estimated downloads/day for the app ranking #1 on this keyword (rough order of magnitude from the Apple-calibrated popularity curve, 2 significant figures). iOS only — null on Android or when popularity is unknown."
          },
          "difficulty_breakdown": {
            "type": "object",
            "nullable": true,
            "description": "Explainable difficulty ingredients. `beatable: true` = a top-3 slot looks winnable (an app holds it with ≥10x less strength than the SERP median, or the term is under-targeted in titles with a weak top-3 app). Null for rows cached before the field existed or SERPs with <3 results.",
            "properties": {
              "titleMatches": {
                "type": "integer",
                "description": "Top-10 apps whose title contains the keyword"
              },
              "appsAnalyzed": {
                "type": "integer"
              },
              "top3Strength": {
                "type": "array",
                "items": {
                  "type": "number"
                },
                "description": "Ratings count (iOS) / installs (Android) of the top 3 apps, in rank order"
              },
              "medianStrength": {
                "type": "number"
              },
              "weakSpotRank": {
                "type": "integer",
                "nullable": true
              },
              "beatable": {
                "type": "boolean"
              }
            }
          },
          "resultsCount": {
            "type": "integer"
          }
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing or invalid API key",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "Plan or scope insufficient (workspace endpoints need the Indie plan; writes also need a write-scope key)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found or not tracked by your organization",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Rate limit exceeded (Indie plan: 1000/day)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  },
  "paths": {
    "/api/v1/apps/lookup": {
      "get": {
        "tags": [
          "Apps"
        ],
        "summary": "Look up an app by store ID",
        "parameters": [
          {
            "$ref": "#/components/parameters/Store"
          },
          {
            "name": "id",
            "in": "query",
            "required": true,
            "description": "App store ID (numeric for iOS, package for Android)",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Country"
          }
        ],
        "responses": {
          "200": {
            "description": "App metadata",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/App"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/v1/apps/search": {
      "get": {
        "tags": [
          "Apps"
        ],
        "summary": "Search apps in a store",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "description": "Search query",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Store"
          },
          {
            "$ref": "#/components/parameters/Country"
          },
          {
            "name": "num",
            "in": "query",
            "description": "Max results (1–50)",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 10
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Search results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/App"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/v1/apps/aso-score": {
      "get": {
        "tags": [
          "Apps"
        ],
        "summary": "Calculate an ASO optimization score (0–100)",
        "parameters": [
          {
            "$ref": "#/components/parameters/Store"
          },
          {
            "name": "id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Country"
          }
        ],
        "responses": {
          "200": {
            "description": "ASO score breakdown"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/v1/apps/extract-keywords": {
      "get": {
        "tags": [
          "Apps"
        ],
        "summary": "Extract keywords from an app's metadata",
        "parameters": [
          {
            "$ref": "#/components/parameters/Store"
          },
          {
            "name": "id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Country"
          },
          {
            "name": "max",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Extracted keywords"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/v1/apps/reviews": {
      "get": {
        "tags": [
          "Apps"
        ],
        "summary": "Fetch app reviews",
        "parameters": [
          {
            "$ref": "#/components/parameters/Store"
          },
          {
            "name": "id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Country"
          },
          {
            "name": "sort",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "recent",
                "helpful"
              ],
              "default": "recent"
            }
          },
          {
            "name": "min_rating",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 5
            }
          },
          {
            "name": "max_rating",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 5
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "App reviews"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/v1/apps/revenue": {
      "get": {
        "tags": [
          "Apps"
        ],
        "summary": "Estimate monthly revenue for an app (single or bulk)",
        "description": "Pass `id` for a single app or `ids` (comma-separated, up to 25) for bulk lookup. Bulk counts as one request. Each estimate includes a `confidence` grade (high/medium/low) and `confidence_factors` explaining it.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Store"
          },
          {
            "name": "id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "ids",
            "in": "query",
            "description": "Comma-separated store IDs (max 25)",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Country"
          }
        ],
        "responses": {
          "200": {
            "description": "Monthly revenue estimate(s)"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/v1/keywords/search": {
      "get": {
        "tags": [
          "Keywords"
        ],
        "summary": "Keyword research with difficulty + popularity",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Store"
          },
          {
            "$ref": "#/components/parameters/Country"
          }
        ],
        "responses": {
          "200": {
            "description": "Keyword data",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Keyword"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/v1/keywords/suggestions": {
      "get": {
        "tags": [
          "Keywords"
        ],
        "summary": "Autocomplete-driven keyword suggestions",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Store"
          },
          {
            "$ref": "#/components/parameters/Country"
          }
        ],
        "responses": {
          "200": {
            "description": "Suggested keywords"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/v1/charts/top": {
      "get": {
        "tags": [
          "Charts"
        ],
        "summary": "Top free/paid/grossing chart with day-over-day movement",
        "description": "Market-wide store chart (no tracked apps needed). 1 credit. The first request for a (store, country, chart, category) combo on a given day fetches it live and snapshots it; later requests that day are served from the snapshot. `summary`, `movers` and `droppedApps` always describe the whole top 100 — `limit` truncates `entries` only. A combo requested for the first time has no previous snapshot, so `previousMeasuredAt` is null and every `delta` is null.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Store"
          },
          {
            "$ref": "#/components/parameters/Country"
          },
          {
            "name": "chart",
            "in": "query",
            "required": false,
            "description": "Chart type",
            "schema": {
              "type": "string",
              "enum": [
                "free",
                "paid",
                "grossing"
              ],
              "default": "free"
            }
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "description": "Normalized category key (e.g. HEALTH_AND_FITNESS on iOS, GAME on Android), or `overall` for the store-wide chart",
            "schema": {
              "type": "string",
              "default": "overall"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Chart entries to return",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Chart entries with movement, summary, movers and dropped apps",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "store": {
                          "type": "string"
                        },
                        "country": {
                          "type": "string"
                        },
                        "chart": {
                          "type": "string"
                        },
                        "category": {
                          "type": "string"
                        },
                        "measuredAt": {
                          "type": "string",
                          "format": "date"
                        },
                        "previousMeasuredAt": {
                          "type": "string",
                          "format": "date",
                          "nullable": true,
                          "description": "Date movement is computed against"
                        },
                        "stale": {
                          "type": "boolean",
                          "description": "True when the live fetch failed and an older snapshot was served"
                        },
                        "summary": {
                          "type": "object",
                          "properties": {
                            "total": {
                              "type": "integer"
                            },
                            "newToday": {
                              "type": "integer"
                            },
                            "dropped": {
                              "type": "integer"
                            }
                          }
                        },
                        "entries": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "rank": {
                                "type": "integer"
                              },
                              "storeId": {
                                "type": "string"
                              },
                              "numericId": {
                                "type": "integer",
                                "nullable": true,
                                "description": "iOS only — numeric Apple id (storeId is the bundle id)"
                              },
                              "name": {
                                "type": "string"
                              },
                              "iconUrl": {
                                "type": "string",
                                "nullable": true
                              },
                              "developer": {
                                "type": "string",
                                "nullable": true
                              },
                              "rating": {
                                "type": "number",
                                "nullable": true
                              },
                              "delta": {
                                "type": "integer",
                                "nullable": true,
                                "description": "previousRank - rank (positive = moved up); null when new or no previous snapshot"
                              },
                              "isNew": {
                                "type": "boolean"
                              }
                            }
                          }
                        },
                        "movers": {
                          "type": "array",
                          "description": "Up to 10 biggest movers (|delta| >= 3) across the whole chart",
                          "items": {
                            "type": "object",
                            "properties": {
                              "storeId": {
                                "type": "string"
                              },
                              "name": {
                                "type": "string"
                              },
                              "iconUrl": {
                                "type": "string",
                                "nullable": true
                              },
                              "rank": {
                                "type": "integer"
                              },
                              "delta": {
                                "type": "integer"
                              }
                            }
                          }
                        },
                        "droppedApps": {
                          "type": "array",
                          "description": "Apps in the previous snapshot but not in this one",
                          "items": {
                            "type": "object",
                            "properties": {
                              "storeId": {
                                "type": "string"
                              },
                              "name": {
                                "type": "string"
                              },
                              "iconUrl": {
                                "type": "string",
                                "nullable": true
                              },
                              "previousRank": {
                                "type": "integer"
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "503": {
            "description": "Store fetch failed and no recent snapshot is stored"
          }
        }
      }
    },
    "/api/v1/keywords/metrics": {
      "get": {
        "tags": [
          "Keywords"
        ],
        "summary": "Difficulty + popularity for a specific keyword (or bulk)",
        "description": "Pass `q` for a single keyword or `qs` (comma-separated, max 25) for bulk. 1 credit per keyword. Use this when you already have a keyword list — `/keywords/search` is the heavier endpoint that also fans out into related terms.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Single keyword (use this OR qs)"
          },
          {
            "name": "qs",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated bulk keywords, up to 25"
          },
          {
            "$ref": "#/components/parameters/Store"
          },
          {
            "$ref": "#/components/parameters/Country"
          }
        ],
        "responses": {
          "200": {
            "description": "Single keyword data (when `q`) or array of keyword data (when `qs`)",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/Keyword"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Keyword"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid params"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/v1/apps": {
      "get": {
        "tags": [
          "Workspace"
        ],
        "summary": "List your tracked apps with latest snapshots",
        "description": "Requires the Indie plan. Cursor paginated (default 100).",
        "parameters": [
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Limit"
          }
        ],
        "responses": {
          "200": {
            "description": "Tracked apps (paginated)"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/api/v1/apps/{id}": {
      "get": {
        "tags": [
          "Workspace"
        ],
        "summary": "App metadata + up to 90 days of snapshots",
        "description": "Requires the Indie plan.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Sonar app UUID",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "App detail with snapshot history"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "tags": [
          "Write"
        ],
        "summary": "Untrack an app",
        "description": "Removes the org's tracking link and org-scoped data (tracked keywords, discovered keywords, competitor edges), detaches it from any owned product, and deletes that product if left empty. Shared rows survive for other orgs. Requires the Indie plan and a write-scope API key.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Sonar app UUID",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ id, deleted }"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/apps/{id}/keywords": {
      "get": {
        "tags": [
          "Workspace"
        ],
        "summary": "Keywords tracked for an app",
        "description": "Latest difficulty/popularity per keyword. Requires the Indie plan. Cursor paginated (default 50).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Sonar app UUID",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Limit"
          }
        ],
        "responses": {
          "200": {
            "description": "Tracked keywords (paginated)"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "tags": [
          "Write"
        ],
        "summary": "Track keywords for an app (bulk, idempotent)",
        "description": "Requires the Indie plan and a write-scope API key. Re-posting the same terms reports them as already_tracked.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Sonar app UUID",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "keywords"
                ],
                "properties": {
                  "keywords": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 120
                    },
                    "minItems": 1,
                    "maxItems": 200
                  },
                  "country": {
                    "type": "string",
                    "pattern": "^[a-z]{2}$",
                    "description": "Optional — defaults to the linked product's country"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Per-keyword outcomes: { added, already_tracked, failed, results }"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "tags": [
          "Write"
        ],
        "summary": "Bulk-untrack keywords for an app",
        "description": "Requires an explicit selector (a bare delete is a 400). Pass all=true to untrack every keyword for the app, or ids=<id1,id2,…> (the id field from GET, not keyword_id; max 1000) for a subset. Only per-org tracking rows are removed. Requires the Indie plan and a write-scope API key.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Sonar app UUID",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "all",
            "in": "query",
            "description": "Set to true to untrack every keyword for this app",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "ids",
            "in": "query",
            "description": "Comma-separated tracked-keyword ids to untrack (max 1000). Mutually exclusive with all",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ deleted, requested }"
          },
          "400": {
            "description": "Missing/ambiguous selector or too many ids"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/apps/{id}/rankings": {
      "get": {
        "tags": [
          "Workspace"
        ],
        "summary": "Rank history for an app's tracked keywords",
        "description": "Daily positions per keyword over the window. Requires the Indie plan. Cursor paginated over keywords (default 50).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Sonar app UUID",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Days"
          },
          {
            "name": "keyword_id",
            "in": "query",
            "description": "Restrict to one keyword",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Limit"
          }
        ],
        "responses": {
          "200": {
            "description": "Rank history per keyword (paginated)"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/apps/{id}/changes": {
      "get": {
        "tags": [
          "Workspace"
        ],
        "summary": "Change history for a tracked app",
        "description": "Detected releases, metadata edits, screenshots, price, and category changes, newest first. Requires the Indie plan.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Sonar app UUID",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "type",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "release",
                "metadata",
                "screenshots",
                "price",
                "category"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          }
        ],
        "responses": {
          "200": {
            "description": "Detected changes"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/keywords/{id}/rankings": {
      "get": {
        "tags": [
          "Workspace"
        ],
        "summary": "SERP history for a tracked keyword",
        "description": "Which apps ranked in the top results on each measured day. Requires the Indie plan.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Sonar keyword UUID",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Days"
          }
        ],
        "responses": {
          "200": {
            "description": "SERP entries, newest first"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/competitors/{id}/keywords": {
      "get": {
        "tags": [
          "Workspace"
        ],
        "summary": "Keywords a competitor ranks for (+ gap analysis)",
        "description": "Last 7 days of SERP data. Pass app_id for gap analysis against your own app. Requires the Indie plan. Cursor paginated (default 50).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Competitor's Sonar app UUID",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "app_id",
            "in": "query",
            "description": "Your own app's Sonar UUID for gap analysis",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Limit"
          }
        ],
        "responses": {
          "200": {
            "description": "Competitor keywords (paginated)"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/apps/{id}/competitor-landscape": {
      "get": {
        "tags": [
          "Workspace"
        ],
        "summary": "Competitive landscape for one of your own apps",
        "description": "The live competitive keyword picture vs every tracked competitor: gap/winnable/threat/lead stats, the top gap (25), threat (10), and lead (10) rows with metrics, and the latest AI insight (or null). Empty landscape (zeroed stats, insight null) when no competitors are tracked. Requires the Indie plan.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Your own app's Sonar UUID",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Landscape: { app_id, stats: { competitors, keywords_compared, gaps, winnable, threats, leads }, gaps[], threats[], leads[], competitors[], insight, insight_cooldown: { in_cooldown, next_available_at } }"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "tags": [
          "Write"
        ],
        "summary": "Generate an AI competitive insight (7-day cooldown)",
        "description": "Runs an AI analysis over the landscape: clusters keyword gaps into named opportunity themes with per-keyword metrics, writes threat narratives for climbing competitors, and diffs against the previous analysis. At most one analysis per app per 7 days — 429 with code `cooldown` and the next available time while in cooldown. 422 `no_competitors` / `too_little_data` (fewer than 5 compared keywords), 502 `ai_failed`. Requires the Indie plan and a write-scope API key.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Your own app's Sonar UUID",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {}
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{ app_id, insight } — same insight shape as the GET endpoint"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "no_competitors (track a competitor first) or too_little_data (fewer than 5 compared keywords)"
          },
          "429": {
            "description": "cooldown — an analysis was generated within the last 7 days; the message carries the next available time"
          },
          "502": {
            "description": "ai_failed — the AI analysis failed, retry shortly"
          }
        }
      }
    },
    "/api/v1/products": {
      "get": {
        "tags": [
          "Workspace"
        ],
        "summary": "List products with linked apps + competitor counts",
        "description": "Returns every product the org owns (whole, no pagination). Use it to discover product/app UUIDs. Requires the Indie plan.",
        "responses": {
          "200": {
            "description": "Products, each with apps[] (id, store, store_id, name, icon_url) and competitor_count"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "tags": [
          "Write"
        ],
        "summary": "Create a product and start tracking its app(s)",
        "description": "A product is the cross-store unit (up to one iOS + one Android app). Requires the Indie plan and a write-scope API key.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "apps"
                ],
                "properties": {
                  "apps": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 2,
                    "items": {
                      "type": "object",
                      "required": [
                        "store",
                        "store_id"
                      ],
                      "properties": {
                        "store": {
                          "type": "string",
                          "enum": [
                            "ios",
                            "android"
                          ]
                        },
                        "store_id": {
                          "type": "string"
                        },
                        "country": {
                          "type": "string",
                          "pattern": "^[a-z]{2}$"
                        }
                      }
                    }
                  },
                  "name": {
                    "type": "string",
                    "maxLength": 120
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Product with linked apps — apps[].id are the Sonar app UUIDs"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/api/v1/products/{id}": {
      "delete": {
        "tags": [
          "Write"
        ],
        "summary": "Delete a product and untrack its apps",
        "description": "Deletes the product and untracks every linked app (cascading org-scoped data). Shared rows survive for other orgs. Requires the Indie plan and a write-scope API key.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Product UUID",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ id, deleted, untracked_apps }"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/products/{id}/apps": {
      "post": {
        "tags": [
          "Write"
        ],
        "summary": "Link the second-store version of a product",
        "description": "Requires the Indie plan and a write-scope API key. Each product holds at most one app per store.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Product UUID",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "store",
                  "store_id"
                ],
                "properties": {
                  "store": {
                    "type": "string",
                    "enum": [
                      "ios",
                      "android"
                    ]
                  },
                  "store_id": {
                    "type": "string"
                  },
                  "country": {
                    "type": "string",
                    "pattern": "^[a-z]{2}$"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Linked app"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/products/{id}/competitors": {
      "post": {
        "tags": [
          "Write"
        ],
        "summary": "Track a competitor app under a product",
        "description": "The product must already have its own app in the same store. Requires the Indie plan and a write-scope API key.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Product UUID",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "store",
                  "store_id"
                ],
                "properties": {
                  "store": {
                    "type": "string",
                    "enum": [
                      "ios",
                      "android"
                    ]
                  },
                  "store_id": {
                    "type": "string"
                  },
                  "country": {
                    "type": "string",
                    "pattern": "^[a-z]{2}$"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Linked competitor app"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/products/{id}/competitors/{competitorId}": {
      "delete": {
        "tags": [
          "Write"
        ],
        "summary": "Remove a competitor from a product",
        "description": "competitorId is the competitor's Sonar app UUID. Drops the competitor edge under the product's own app(s); the competitor app is untracked automatically if no other own app references it. Requires the Indie plan and a write-scope API key.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Product UUID",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "competitorId",
            "in": "path",
            "required": true,
            "description": "Competitor's Sonar app UUID",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ product_id, competitor_app_id, deleted, edges_removed }"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/tracked-keywords/{id}": {
      "patch": {
        "tags": [
          "Write"
        ],
        "summary": "Update the note and/or starred flag on a tracked keyword",
        "description": "id is the tracked-keyword UUID (the id field from GET /apps/{id}/keywords, not keyword_id). At least one of note/starred must be present. Requires the Indie plan and a write-scope API key.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Tracked-keyword UUID",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "minProperties": 1,
                "properties": {
                  "note": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 1000,
                    "description": "Note text; null or empty clears it"
                  },
                  "starred": {
                    "type": "boolean",
                    "description": "true marks the keyword as a favorite/target (sets starred_at), false unstars it"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated tracked keyword"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "tags": [
          "Write"
        ],
        "summary": "Untrack a keyword/app pair",
        "description": "Removes a single tracked keyword. id is the tracked-keyword UUID (the id field from GET /apps/{id}/keywords, not keyword_id). Only the per-org tracking row is deleted; the shared keyword and its history remain. Requires the Indie plan and a write-scope API key.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Tracked-keyword UUID",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted tracked keyword"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/competitors/{id}/scan": {
      "post": {
        "tags": [
          "Write"
        ],
        "summary": "Run an AI keyword discovery scan on a competitor",
        "description": "Generates the search terms the competitor's listing is optimized for (brand terms included), queues them for SERP verification, and verifies the first batch inline (~30s); the rest verify in the background, recording both apps' ranks. Requires the Indie plan and a write-scope API key.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Competitor's Sonar app UUID",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "own_app_id"
                ],
                "properties": {
                  "own_app_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Your own app the scan compares against"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Scan counts: { generated, queued, verified_now } — remaining candidates verify in the background; poll the competitor keywords endpoint for results"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/alerts": {
      "get": {
        "tags": [
          "Workspace"
        ],
        "summary": "List alert rules (subscriptions)",
        "description": "Each rule is org-wide (scope_app_id null) or scoped to one app, with the configured and effective threshold. Requires the Indie plan.",
        "responses": {
          "200": {
            "description": "Alert rules: { id, type, scope_app_id, threshold, effective_threshold, enabled, created_at }"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "tags": [
          "Write"
        ],
        "summary": "Create or update an alert rule",
        "description": "Upserts on (type, scope_app_id). Omit threshold for the per-type default; omit scope_app_id for an org-wide rule. Requires the Indie plan and a write-scope API key.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "type"
                ],
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "rank_drop",
                      "rank_gain",
                      "entered_top10",
                      "left_top10",
                      "new_ranking",
                      "rating_drop",
                      "review_spike",
                      "competitor_change"
                    ]
                  },
                  "scope_app_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "App UUID to scope to; null/omitted = all tracked apps"
                  },
                  "threshold": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "description": "null/omitted = per-type default"
                  },
                  "enabled": {
                    "type": "boolean",
                    "default": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created/updated alert rule"
          },
          "400": {
            "description": "Unknown type or scope_app_id not tracked"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/api/v1/alerts/{id}": {
      "delete": {
        "tags": [
          "Write"
        ],
        "summary": "Delete an alert rule (unsubscribe)",
        "description": "id is the rule id from GET /api/v1/alerts. Requires the Indie plan and a write-scope API key.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Alert rule UUID",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ id, deleted }"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    }
  },
  "tags": [
    {
      "name": "Apps",
      "description": "App lookup, search, scoring, and reviews"
    },
    {
      "name": "Keywords",
      "description": "Keyword research, difficulty, and suggestions"
    },
    {
      "name": "Charts",
      "description": "Store top charts with day-over-day movement"
    },
    {
      "name": "Workspace",
      "description": "Your tracked apps, keywords, rankings, and competitors (Indie plan)"
    },
    {
      "name": "Write",
      "description": "Mutate your workspace — create products, track keywords/competitors, notes, scans (Indie plan + write-scope key)"
    }
  ]
}