{"openapi":"3.1.0","info":{"title":"Signal Scanner Connector API","version":"1.0.0","description":"Machine-readable specification for the Signal Scanner Connector API. All data returned is authentic — sourced from real AI engine scan runs stored in the database. No fabricated or mock data is ever returned. Keys are available to Pro plan subscribers.\n\nAuthentication: pass your API key as a Bearer token in the Authorization header, or in the x-api-key header.\n\nEvery successful response includes an X-Signal-Schema-Version response header matching the schema_version field in the body.","contact":{"name":"Signal Scanner","url":"https://signalscanner.io/developers"}},"servers":[{"url":"https://signalscanner.io","description":"Production"}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Pass your Signal Scanner API key (ssk_ or sat_ prefix) as a Bearer token. Example: Authorization: Bearer ssk_xxxxx. Keys are Pro-gated."},"apiKeyHeader":{"type":"apiKey","in":"header","name":"x-api-key","description":"Alternatively pass your API key in the x-api-key header. Example: x-api-key: ssk_xxxxx. Keys are Pro-gated."}},"parameters":{"brandId":{"name":"brandId","in":"path","required":true,"description":"Numeric brand ID. Obtain from GET /api/connector/brands.","schema":{"type":"integer","minimum":1}},"locationIndex":{"name":"locationIndex","in":"query","required":false,"description":"Zero-based index into the brand locations array. Defaults to 0 (the primary location).","schema":{"type":"integer","minimum":0,"default":0}}},"responses":{"Unauthorized":{"description":"No valid API key supplied.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"unauthorized"},"error_description":{"type":"string","example":"Authentication required"}}}}}},"Forbidden":{"description":"The API key does not own the requested brand, or the endpoint requires a Pro plan.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"}}}}}},"BadRequest":{"description":"Invalid path or query parameter.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"}}}}}},"ServerError":{"description":"Internal server error.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"}}}}}}}},"paths":{"/api/connector/brands":{"get":{"operationId":"getBrands","summary":"List brands","description":"Returns every brand the API key's account owns, with the IDs and locations needed for all other endpoints.","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"responses":{"200":{"description":"Success.","headers":{"X-Signal-Schema-Version":{"description":"Schema version. Currently 1.0.","schema":{"type":"string","example":"1.0"}}},"content":{"application/json":{"schema":{"type":"object","required":["schema_version","brands"],"properties":{"schema_version":{"type":"string","example":"1.0"},"brands":{"type":"array","items":{"type":"object","required":["id","name"],"properties":{"id":{"type":"integer","description":"Brand ID. Use as :brandId in all other endpoints."},"name":{"type":"string","description":"Brand display name."},"domain":{"type":["string","null"],"description":"Primary domain. Null when no website is configured."},"locations":{"type":"array","items":{"type":"string"},"description":"All configured locations. The array index is the locationIndex accepted by other endpoints."}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/api/connector/signals/{brandId}":{"get":{"operationId":"getSignals","summary":"Get Signal Score and breakdown","description":"Returns the composite Signal Score and six per-signal scores from the latest stored scan run. Returns hasData: false when no scan has been run — no fabricated data is returned.","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"$ref":"#/components/parameters/brandId"},{"$ref":"#/components/parameters/locationIndex"}],"responses":{"200":{"description":"Success. hasData is false when no scan exists for this brand and location.","headers":{"X-Signal-Schema-Version":{"schema":{"type":"string","example":"1.0"}}},"content":{"application/json":{"schema":{"oneOf":[{"title":"No data yet","type":"object","required":["schema_version","hasData"],"properties":{"schema_version":{"type":"string","example":"1.0"},"hasData":{"type":"boolean","enum":[false]}}},{"title":"Scan data available","type":"object","required":["schema_version","hasData","runId","runCreatedAt","signals"],"properties":{"schema_version":{"type":"string","example":"1.0"},"hasData":{"type":"boolean","enum":[true]},"runId":{"type":"integer","description":"ID of the audit run this data was read from."},"runCreatedAt":{"type":"string","format":"date-time"},"composite":{"type":["number","null"],"description":"Composite Signal Score 0-100."},"band":{"type":["string","null"],"description":"Band label for the composite score."},"signals":{"type":"array","description":"The six measured signals.","items":{"type":"object","properties":{"id":{"type":"string","description":"Stable signal identifier (e.g. reputation, presence, content)."},"name":{"type":"string"},"score":{"type":"number","description":"Signal score 0-1."},"weight":{"type":"number","description":"Weight in the composite score calculation."},"band":{"type":"string"},"status":{"type":"string"},"components":{"type":"array","description":"Sub-component scores within this signal.","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"score":{"type":"number"},"points":{"type":"number"}}}}}}}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/api/connector/citations/{brandId}":{"get":{"operationId":"getCitations","summary":"Get cited sources","description":"Returns every domain that appeared as a cited source across AI engine responses in the latest scan, ranked by citation count. The isOwn flag marks your brand's own domain.","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"$ref":"#/components/parameters/brandId"},{"$ref":"#/components/parameters/locationIndex"}],"responses":{"200":{"description":"Success. hasAudit is false when no scan exists.","headers":{"X-Signal-Schema-Version":{"schema":{"type":"string","example":"1.0"}}},"content":{"application/json":{"schema":{"type":"object","required":["schema_version","hasAudit","sources"],"properties":{"schema_version":{"type":"string","example":"1.0"},"hasAudit":{"type":"boolean","description":"False when no scan run exists for this brand and location."},"runId":{"type":"integer"},"runCreatedAt":{"type":"string","format":"date-time"},"brandDomain":{"type":["string","null"]},"totalProbes":{"type":"integer","description":"Total query probes in the scan run."},"probesWithCitations":{"type":"integer","description":"Probes that returned at least one cited domain."},"sources":{"type":"array","description":"Cited domains ranked by citation count, descending.","items":{"type":"object","properties":{"domain":{"type":"string"},"count":{"type":"integer","description":"Number of probes in which this domain was cited."},"engines":{"type":"array","items":{"type":"string"},"description":"AI engines that cited this domain."},"queryCount":{"type":"integer","description":"Number of distinct queries in which this domain appeared."},"isOwn":{"type":"boolean","description":"True when this domain matches the brand's configured domain."}}}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/api/connector/reputation/{brandId}":{"get":{"operationId":"getReputation","summary":"Get reputation diagnosis","description":"Returns the reputation signal score, raw measurement data (review platforms, citation domains, sentiment snippet count), and a prioritized list of actionable diagnoses sorted by measured recoverable points. Returns hasData: false when no scan exists.","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"$ref":"#/components/parameters/brandId"},{"$ref":"#/components/parameters/locationIndex"}],"responses":{"200":{"description":"Success.","headers":{"X-Signal-Schema-Version":{"schema":{"type":"string","example":"1.0"}}},"content":{"application/json":{"schema":{"oneOf":[{"title":"No data yet","type":"object","required":["schema_version","hasData"],"properties":{"schema_version":{"type":"string"},"hasData":{"type":"boolean","enum":[false]}}},{"title":"Reputation data available","type":"object","required":["schema_version","hasData"],"properties":{"schema_version":{"type":"string","example":"1.0"},"hasData":{"type":"boolean","enum":[true]},"score":{"type":"number","description":"Reputation signal score 0-1."},"band":{"type":["string","null"]},"status":{"type":["string","null"]},"reviewPlatformsFound":{"type":"array","items":{"type":"string"},"description":"Review platforms detected in search results."},"aggregateRating":{"type":["number","null"],"description":"Aggregate review rating if detected."},"citationDomains":{"type":"array","items":{"type":"string"},"description":"Third-party domains that mention the brand."},"accurateMentions":{"type":"integer","description":"Citations that explicitly name the brand in their title or description."},"citationsScanned":{"type":"integer","description":"Total citations examined."},"negativeSnippets":{"type":"integer","description":"Search result snippets containing negative language."},"serpAvailable":{"type":"boolean","description":"Whether SERP data was available for this scan."},"diagnoses":{"type":"array","description":"Prioritized gaps sorted by measured recoverable points, descending.","items":{"type":"object","properties":{"id":{"type":"string","description":"Stable identifier (reviews, citations, ref_accuracy, sentiment_cons)."},"title":{"type":"string"},"points":{"type":"integer","description":"Recoverable points in the current score for this gap."},"finding":{"type":"string","description":"What the scan measured."},"action":{"type":"string","description":"Recommended action based on the measured gap."}}}}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/api/connector/publish-prescription/{brandId}":{"get":{"operationId":"getPublishPrescription","summary":"Get query coverage gaps","description":"Returns a prescription of queries where the brand is missing from one or more AI engines, sorted by severity (total gaps first, then partial gaps by uncited engine count). Includes Search Console content gaps when available.","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"$ref":"#/components/parameters/brandId"},{"$ref":"#/components/parameters/locationIndex"}],"responses":{"200":{"description":"Success. hasData is false when no scan exists.","headers":{"X-Signal-Schema-Version":{"schema":{"type":"string","example":"1.0"}}},"content":{"application/json":{"schema":{"oneOf":[{"title":"No data yet","type":"object","required":["schema_version","hasData"],"properties":{"schema_version":{"type":"string"},"hasData":{"type":"boolean","enum":[false]}}},{"title":"Prescription available","type":"object","required":["schema_version","hasData","totalQueries","coveredQueries","gapQueries","prescription"],"properties":{"schema_version":{"type":"string","example":"1.0"},"hasData":{"type":"boolean","enum":[true]},"totalQueries":{"type":"integer","description":"Total distinct queries probed in the scan."},"coveredQueries":{"type":"integer","description":"Queries where the brand was cited by every engine that probed them."},"gapQueries":{"type":"integer","description":"Queries with at least one engine not citing the brand."},"prescription":{"type":"array","description":"Queries with coverage gaps, sorted by severity.","items":{"type":"object","properties":{"query":{"type":"string"},"enginesCited":{"type":"integer","description":"Number of engines that cited the brand for this query."},"enginesTotal":{"type":"integer","description":"Total engines that probed this query."},"gapType":{"type":"string","enum":["total_gap","partial_gap"],"description":"total_gap: no engine cited the brand. partial_gap: some engines cited, some did not."},"uncitedEngines":{"type":"array","items":{"type":"string"},"description":"Engines that did not cite the brand for this query."}}}},"contentGaps":{"type":"array","description":"Search Console content gaps when available. Empty array when not connected.","items":{}},"contentGapSource":{"type":"string","enum":["real_search_queries","none"],"description":"Indicates whether contentGaps came from real Search Console data."}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/api/connector/fix-file/{brandId}":{"get":{"operationId":"getFixFile","summary":"Get AI-ready fix file (Pro)","description":"Returns the AI-ready fix file from the latest audit run. Requires a Pro plan. Default response is a JSON envelope containing both markdown and structured json fields. Pass ?format=json to receive the raw structured fix file document directly with a Content-Disposition attachment header.","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"$ref":"#/components/parameters/brandId"},{"$ref":"#/components/parameters/locationIndex"},{"name":"format","in":"query","required":false,"description":"Pass format=json to receive the raw structured fix file document directly instead of the envelope.","schema":{"type":"string","enum":["json"]}}],"responses":{"200":{"description":"Success. hasAudit is false when no scan exists.","content":{"application/json":{"schema":{"oneOf":[{"title":"No audit yet","type":"object","required":["hasAudit"],"properties":{"hasAudit":{"type":"boolean","enum":[false]}}},{"title":"Fix file envelope (default)","type":"object","required":["hasAudit","filename","markdown","jsonFilename","json"],"properties":{"hasAudit":{"type":"boolean","enum":[true]},"filename":{"type":"string","description":"Suggested markdown filename."},"markdown":{"type":"string","description":"Full markdown fix file text."},"jsonFilename":{"type":"string","description":"Suggested JSON filename."},"json":{"type":"object","description":"Structured machine-readable fix file document."},"composite":{"type":["number","null"],"description":"Composite Signal Score from the audit run."},"band":{"type":["string","null"]},"runCreatedAt":{"type":"string","format":"date-time"},"generatedAt":{"type":"string","format":"date-time"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/api/connector/rescan/{brandId}":{"post":{"operationId":"triggerRescan","summary":"Trigger a new scan (Pro)","description":"Starts a new audit scan for the brand and location. Requires a Pro plan. Counts against the account's monthly scan quota identically to a manual scan in the dashboard. Also subject to a per-key hourly limit of 5 rescans per hour. The delta in the response is a measured change in composite score since the previous run and does not attribute score changes to any action.","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"$ref":"#/components/parameters/brandId"},{"$ref":"#/components/parameters/locationIndex"}],"responses":{"200":{"description":"Scan completed.","headers":{"X-Signal-Schema-Version":{"schema":{"type":"string","example":"1.0"}}},"content":{"application/json":{"schema":{"type":"object","required":["schema_version","started"],"properties":{"schema_version":{"type":"string","example":"1.0"},"started":{"type":"boolean","enum":[true]},"runId":{"type":["integer","null"]},"composite":{"type":["number","null"],"description":"Composite Signal Score from the completed scan."},"band":{"type":["string","null"]},"delta":{"description":"Measured change since the previous run. Null when there is no previous run to compare against.","oneOf":[{"type":"null"},{"type":"object","properties":{"compositeDelta":{"type":["number","null"],"description":"Arithmetic difference in composite score between this run and the previous run."},"previous":{"type":["object","null"],"properties":{"composite":{"type":["number","null"]}}},"note":{"type":"string","description":"Clarification that this is a measured change, not an attribution."}}}]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Brand not owned by this key, Pro plan required, or monthly scan quota reached.","content":{"application/json":{"schema":{"type":"object","properties":{"started":{"type":"boolean","enum":[false]},"reason":{"type":"string"},"limitReached":{"type":"boolean"},"resetDate":{"type":"string","description":"Human-readable reset date when the monthly quota resets. Present only when limitReached is true."}}}}}},"429":{"description":"Hourly rate limit reached (5 rescans per hour per API key).","content":{"application/json":{"schema":{"type":"object","properties":{"started":{"type":"boolean","enum":[false]},"reason":{"type":"string"},"limitReached":{"type":"boolean","enum":[true]}}}}}},"500":{"$ref":"#/components/responses/ServerError"}}}}}}