# Google Search Console API BasecampSEO is a Google Search Console analytics product. It stores search performance beyond Google's 16 month retention window, and adds movers, brand and non brand segmentation, page groups, annotations and indexing coverage on top of the raw Search Console data. The API and MCP server give you all of that outside the dashboard. Read-only, available on paid Pro and Agency plans. Base URL: `https://api.basecampseo.com` Auth: `Authorization: Bearer bcs_live_...`. Create a key at https://basecampseo.com/app/settings/api. OpenAPI: https://api.basecampseo.com/v1/openapi.json A key reads exactly the sites its creator can read. Every endpoint is read-only. ## Envelope Success: `{ "success": true, "data": {...}, "meta": {...} }` Error: `{ "success": false, "error": { "code", "message", "request_id", "docs_url" } }` Two meta fields matter. - `source` is "live" (straight from Google, complete) or "stored" (our archive, top 100 queries and pages per day). `stale_reason` is set when a disconnected Google account forced the fallback. - `truncated_by` is "clicks" when there were more rows than we could fetch. `total` is null on these endpoints because Search Console does not report one. `order_by` other than -clicks pulls a wider sample first and then sorts, so it returns the real top rows for that metric. Countries use lowercase ISO 3166-1 alpha-3 codes ("esp", "ury"). Query and page rows never add up to the totals from /summary. Google withholds rare queries for privacy, so the gap is anonymised traffic, not missing data. ## Date ranges `date_range` accepts last_7d, last_28d (default), last_30d, last_90d, last_3m, last_6m, last_12m, last_16m, month_to_date, previous_month, year_to_date, previous_year. Or pass `start_date` and `end_date` as YYYY-MM-DD. Everything is anchored two days ago. An end date later than that is moved back and noted in `meta.notes`. ## Filters `filters` is URL encoded JSON, for example `[{"dimension":"device","operator":"equals","value":"mobile"}]`. Dimensions: query, page, country, device, plus brand (values "brand" or "non-brand") and page_group (a group name). Operators: contains, equals, regex, not_contains, not_equals, not_regex, is, is_not. A malformed filter returns 400 rather than being ignored. ## Endpoints | Method | Path | Returns | | --- | --- | --- | | GET | /v1/me | Key identity, plan, and all four usage ceilings | | GET | /v1/properties | Every site the key can read | | GET | /v1/properties/{id} | Data window and connection health | | GET | /v1/properties/{id}/summary | Totals, with compare=previous_period | | GET | /v1/properties/{id}/timeseries | Daily rows | | GET | /v1/properties/{id}/queries | Top queries | | GET | /v1/properties/{id}/pages | Top pages | | GET | /v1/properties/{id}/countries | By country | | GET | /v1/properties/{id}/devices | By device | | GET | /v1/properties/{id}/movers/queries | Gainers and losers | | GET | /v1/properties/{id}/movers/pages | Gainers and losers | | GET | /v1/properties/{id}/indexing | Coverage summary | | GET | /v1/properties/{id}/indexing/urls | URLs by index status | | GET | /v1/properties/{id}/brand-terms | Brand terms | | GET | /v1/properties/{id}/page-groups | Page groups | | GET | /v1/properties/{id}/annotations | Dated notes | `{id}` accepts a property id, a bare domain, a full Search Console property string, or a slug. Movers reject `filters` with 400. The underlying comparison returns nothing when filtered, which is indistinguishable from "nothing moved". ## Example ``` curl -G "https://api.basecampseo.com/v1/properties/example.com/queries" \ -H "Authorization: Bearer bcs_live_YOUR_KEY" \ --data-urlencode 'date_range=previous_month' \ --data-urlencode 'limit=10' ``` ```json { "success": true, "data": { "dimension": "query", "rows": [ { "key": "seo dashboard", "clicks": 1204, "impressions": 22410, "ctr": 0.0537, "position": 6.2 } ] }, "meta": { "row_count": 1, "truncated_by": "clicks", "source": "live", "cache": "miss" } } ``` ## Limits and errors One daily allowance shared by every key on the account, resetting at midnight UTC. Pro gets 1,000 requests a day, Agency 5,000. Repeat calls are served from cache. Headers on every response: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-Cache. | Status | Codes | | --- | --- | | 400 | VALIDATION_ERROR, DATE_RANGE_TOO_OLD, UNSUPPORTED_PARAMETER, PROPERTY_AMBIGUOUS | | 401 | INVALID_API_KEY, API_KEY_REVOKED, API_KEY_EXPIRED | | 403 | API_ACCESS_REQUIRED, INSUFFICIENT_SCOPE | | 404 | NOT_FOUND, also for properties the key cannot reach | | 429 | RATE_LIMITED, QUOTA_EXCEEDED, UPSTREAM_QUOTA_EXCEEDED | | 503 | FILTERED_DATA_UNAVAILABLE | Versioning lives in the path. New fields land inside /v1; breaking changes get a new version. Ignore unknown fields.