| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
Part of bisibility - open-source keyword rank tracking you can self-host and automate. This repository contains the Python SDK for the Bisibility REST API.
Docs · API reference · Roadmap
Status: Release candidate: v0.8.0 is prepared; v0.7.0 remains published on PyPI.
Python SDK for the Bisibility REST API.
pip install bisibilityFor local development:
pip install -e .[dev]import os
from bisibility import BisibilityClient
bisibility = BisibilityClient(api_key=os.environ["BISIBILITY_API_KEY"])
projects = bisibility.list_projects()
project_id = projects.data[0].id if projects.data else None
if project_id:
created = bisibility.create_keywords(
project_id,
{
"keywords": [
{
"keyword": "rank tracker api",
"target_url": "https://example.com/rank-tracker",
"tags": ["api"],
}
]
},
)
keyword_id = created.results[0].keyword.id if created.results else None
if keyword_id:
check = bisibility.run_rank_check(keyword_id)
print(check.position, check.ranking_url)import os
from bisibility import BisibilityClient
bisibility = BisibilityClient(
api_key=os.environ["BISIBILITY_API_KEY"],
base_url="https://bisibility.com/api/v1",
project_id=os.environ.get("BISIBILITY_PROJECT_ID"),
timeout=30.0,
)base_url should point at the API v1 root. Protected methods send Authorization: Bearer <api_key>. Write methods accept RequestOptions with an idempotency_key, which maps to the server Idempotency-Key header.
The client accepts project API keys (bsb_key_live_... or bsb_key_test_...) and personal access tokens (bsb_pat_live_...). For a PAT with multiple project memberships, set project_id to a project ID returned by list_projects; the client sends it as X-Bisibility-Project on project-implicit routes. PAT methods include get_me, create_project, personal-token self-management, project API-key minting, and webhook CRUD.
Legacy bsk_... and bsp_... credentials are not accepted by the v3 contract.
AsyncBisibilityClient exposes the same API operations as BisibilityClient through httpx.AsyncClient. Use async with to close an owned HTTP client, or call await client.aclose() explicitly:
import os
from bisibility import AsyncBisibilityClient
async def list_all_keywords(project_id: str) -> None:
async with AsyncBisibilityClient(
api_key=os.environ["BISIBILITY_API_KEY"],
project_id=os.environ.get("BISIBILITY_PROJECT_ID"),
) as bisibility:
async for keyword in bisibility.iter_keywords(project_id, {"limit": 100}):
print(keyword.text)create_async_bisibility_client() is the factory counterpart to create_bisibility_client(). Async requests preserve the same authentication, timeouts, retries, validation, errors, and cursor filters as the synchronous client. Retry delays are cancellable and do not block the event loop.
Every resource identifier at the HTTP boundary is a strict lowercase public ID: <prefix>_[a-z][a-z0-9]{23}. The SDK rejects raw database IDs, legacy IDs, wrong prefixes, uppercase characters, and malformed suffixes before sending a request. The same validation is applied to typed request bodies, project header, and ID-bearing API responses. A malformed success response raises BisibilityResponseError.
The registry is fixed to: al, alr, audit, check, cmp, conn, dwh, ferry, imp, inv, key, kw, mbr, ntf, pat, prj, sid, sig, svkw, tag, usr, viw, and we. Import PUBLIC_ID_PREFIXES or public_id_pattern() when another component needs the same contract.
List operations accept and return opaque v3 cursors. Pass meta.next_cursor back unchanged; the SDK rejects legacy, unversioned, and malformed cursors before a request is sent or a success response is returned.
Cloud transfer uses package version 5 only. Its project, cloud-import, and transfer-token identifiers follow the same public-ID rules.
The examples below reuse project_id and keyword_id values returned by the API, as shown in the quickstart, instead of embedding synthetic resource IDs.
The default timeout is 30 seconds per attempt. Pass timeout=None to the client to opt out globally, or RequestOptions(timeout=None) for one request. The SDK sends User-Agent: bisibility-sdk-python/<version> unless you supplied a user agent, and always sends the matching X-Bisibility-Client header.
Failed requests are retried automatically up to max_retries times (default 2) when the failure is an httpx transport error or a 429/503 response. Only requests that are safe to replay are retried: idempotent GET/HEAD/PUT/DELETE requests are always eligible, while non-idempotent POST/PATCH requests are retried only when an Idempotency-Key header is present (set via RequestOptions(idempotency_key=...)). When the server sends a Retry-After header in seconds or HTTP-date form, the client waits that long (capped at 60 seconds); otherwise it uses capped exponential backoff (0.5s, 1s, 2s, ... up to 8s). Set max_retries=0 to disable retries.
bisibility = BisibilityClient(api_key="bsb_key_live_...", max_retries=3)from bisibility import RequestOptions
bisibility.create_api_key(
{"name": "CI"},
request_options=RequestOptions(idempotency_key="request-123"),
)Cloud import accepts only version 5 packages. Every package must use strict snake_case top-level keys and include all five section arrays, even when they are empty. Package, session, chunk, and response identifiers are validated as typed public IDs before a request is sent.
from bisibility import CloudImportPackage, CloudImportSessionCreate
package = CloudImportPackage(
version=5,
project_id=project_id,
keywords=[],
alert_rules=[],
competitors=[],
notification_preferences=[],
saved_views=[],
)
session = CloudImportSessionCreate(
version=5,
chunk_count=1,
source_project_id=project_id,
)The nested CloudImportKeyword, CloudImportCompetitor, CloudImportAlertRule, CloudImportNotificationPreference, and CloudImportSavedView models mirror the API schema. Alert targets are a discriminated union with type="keyword" or type="tag"; arbitrary fields and legacy aliases are rejected.
List methods return ListResponse[T] with data and meta.next_cursor. Resource methods return pydantic models matching the Bisibility API response shape.
Keyword research uses a single seed and a result limit of 100, 300, or 500. Metrics requests accept at most 700 keywords per call, matching the REST contract. Split larger batches explicitly so each response retains its own cache counts and provider cost.
Cursor-paginated resources also expose lazy iter_* generators. They preserve the supplied filters, request pages only as needed, and stop when meta.next_cursor is None:
from bisibility import ListKeywordsOptions
for keyword in bisibility.iter_keywords(
project_id,
ListKeywordsOptions(tag="Product", limit=100),
):
print(keyword.text)Iterators are available for keywords, rank checks, signals, API keys (including project API keys), webhooks, alert rules, triggered alerts, team members, team invites, providers, saved views, saved keywords, competitors, and migration tokens. The async client exposes the same iter_* names as async iterators.
Domain Overview reads public search-index estimates for a domain or subdomain. A cache miss can spend the project's connected provider account, so obtain the current price before explicitly accepting it:
from math import ceil
from bisibility import AnalyzeDomainOverviewOptions, DomainOverviewEstimate
estimate = bisibility.analyze_domain_overview(
project_id,
AnalyzeDomainOverviewOptions(
target="example.com",
location_code=2840,
language_code="en",
estimate_only=True,
),
)
if not isinstance(estimate.data, DomainOverviewEstimate):
raise RuntimeError("Expected a Domain Overview estimate")
report = bisibility.analyze_domain_overview(
project_id,
AnalyzeDomainOverviewOptions(
target="example.com",
location_code=2840,
language_code="en",
max_cost_cents=ceil(estimate.data.estimated_cost_cents),
),
)History and additional keyword/page loads use their dedicated option models and also require max_cost_cents. Cached-only callers can pass 0; the API returns a problem response instead of silently spending when the requested data is not cached.
To rotate a webhook secret, set hmac_secret on WebhookUpdateInput to a non-empty string. Sending a null, empty, or whitespace-only secret is rejected. Omit hmac_secret entirely to leave the existing secret unchanged; omitted fields are not included in the PATCH request.
All SDK-defined exceptions derive from BisibilityError. BisibilityApiError also exposes is_rate_limit, is_not_found, and retry_after_seconds helpers. Problem responses follow RFC 9457 and preserve extension members; sensitive response headers are removed before an API error is exposed.
There is intentionally no create_project method: the API returns 403 Forbidden for POST /projects because project-scoped API keys cannot create projects. Create projects from the Bisibility app instead.
list_keywords supports country, device, tag, topic, intent, position_gt/position_lt, search and sort filters via ListKeywordsOptions (or a plain dict); topic and intent match the keyword classification fields, case-insensitively.
Ingest deploy/CMS/API events as signals and read them back per project. create_signal requires source ("deploy", "cms", or "api") and type (shaped like category.event, e.g. "deploy.completed"); payload must serialize to 8KB or less. list_project_signals returns signals newest first and accepts source, type and a from/to time range (use the from_ field name, or the "from" key in a dict).
from bisibility import CreateSignalInput, ListSignalsOptions
emitted = bisibility.create_signal(
CreateSignalInput(
source="deploy",
type="deploy.completed",
payload={"version": "1.2.3"},
url="https://example.com/releases/1",
)
)
signals = bisibility.list_project_signals(
project_id,
ListSignalsOptions(source="deploy", from_="2026-07-01T00:00:00Z"),
)get_provider_rates and get_cost_estimate are public discovery endpoints and work without an API key. get_cost_estimate requires keywords and accepts locations, devices (1-2), frequency ("daily", "weekly", "monthly"), provider, and a flat-rate option or plan plan selector. Both return a DataResponse whose data is typed (list[ProviderRate] and CostEstimate).
from bisibility import BisibilityClient, CostEstimateOptions
anonymous = BisibilityClient() # no api_key needed for these calls
rates = anonymous.get_provider_rates()
estimate = anonymous.get_cost_estimate(
CostEstimateOptions(keywords=248, frequency="daily", provider="dataforseo")
)
print(estimate.data.monthly_cost_usd)run_rank_check blocks until the check completes by default. Pass async_mode=True to queue the check instead: the server responds with 202 Accepted and a rank check in status "running"; poll get_rank_check_result until the status becomes "completed" or "failed".
queued = bisibility.run_rank_check(keyword_id, async_mode=True)
assert queued.status == "running"
result = bisibility.get_rank_check_result(queued.id)Plain dictionaries are supported for request bodies, and pydantic input models are available when you want validation and editor completion.
from bisibility import CreateKeywordInput, CreateKeywordsBatch, KeywordScheduleInput
created = bisibility.create_keywords(
project_id,
CreateKeywordsBatch(
keywords=[
CreateKeywordInput(
keyword="rank tracker",
target_url="https://example.com/rank-tracker",
tags=["api"],
schedule=KeywordScheduleInput(
frequency="daily",
cron_expression=None,
),
)
]
),
)New app-surface endpoints also have typed request models. Plain dictionaries remain supported.
from bisibility import (
AlertRuleInput,
ApiKeyCreateInput,
ConnectProviderInput,
ProjectDefaultsPatch,
ProviderCredentialsInput,
UpdateProjectInput,
)
project = bisibility.update_project(
project_id, UpdateProjectInput(name="Marketing site")
)
defaults = bisibility.update_project_defaults(
project_id,
ProjectDefaultsPatch(frequency="daily", location_key="US/Texas/Austin"),
)
api_key = bisibility.create_api_key(ApiKeyCreateInput(name="CI"))
rule = bisibility.create_alert_rule(
project_id,
AlertRuleInput(
channels=["email"],
condition_type="threshold",
name="Ranking drop",
target_type="all",
threshold_position=10,
),
)
provider = bisibility.connect_provider(
project_id,
"serpapi",
ConnectProviderInput(
credentials=ProviderCredentialsInput(api_key="serpapi-secret"),
priority=0,
),
)
# `connect_provider` applies `priority` after connecting. Legacy `primary=True` maps to zero;
# `primary=False` is a no-op. Failures expose `.connection` in `BisibilityProviderPrioritySyncError`.
# Self-hosted analytics providers (e.g. "plausible") accept an optional
# endpoint credential pointing at the instance API.
analytics = bisibility.connect_provider(
project_id,
"plausible",
ConnectProviderInput(
credentials=ProviderCredentialsInput(
api_key="plausible-secret",
endpoint="https://plausible.example.com/api",
),
),
)Non-2xx API responses raise BisibilityApiError. The RFC problem details body is available on error.problem when the server returns one.
from bisibility import BisibilityApiError
try:
bisibility.get_keyword("kw_z9y8x7w6v5u4t3s2r1q0p9n8")
except BisibilityApiError as error:
print(error.status, error.problem.detail if error.problem else error.body)Licensed under the Apache License, Version 2.0. See LICENSE and NOTICE.
| Back | FazBrowse Home | New Git URL |