- The web app serves the dashboard, REST API, Model Context Protocol (MCP), and live notifications.
- The Temporal worker runs scheduled rank checks and maintenance jobs.
Runtime components
The MCP endpoint uses the same router as REST. Manual and scheduled checks also
use the same rank-check runner. These shared paths keep behavior consistent.
Request and data flow
In words: the web process authenticates browser, REST, SDK, CLI, and MCP traffic, then reads or writes durable project state in PostgreSQL. Redis-backed services enforce cross-process rate limits and idempotency and fan out live notifications. A manual check can call the shared rank-check runner from the web process. Scheduled work starts through Temporal, whose task queues are polled by the bisibility worker. Worker activities call the configured provider, persist results and audit rows in PostgreSQL, and publish resulting notifications through Redis. Temporal keeps workflow state; it is not the source of truth for projects, keywords, or rank history.The worker is not the Temporal server
Temporal is split into two independent pieces, and only one of them ships in the bisibility images. The Temporal server stores workflow state, history, schedules, and task queues. It runs no bisibility code and knows nothing about rank checks. It is infrastructure that the stack connects to. The Temporal worker is a bisibility process. It hosts the workflow and activity code, polls the server for due work, and executes it. This is thebisibility-worker image.
Two consequences follow.
Starting the worker on its own does nothing. It needs a reachable Temporal
server, which is why the Temporal topology needs TEMPORAL_ADDRESS, a dedicated
TEMPORAL_NAMESPACE, and both task queues. The web app needs the same values,
not to manage schedules, but to start workflows. SCHEDULER_DRIVER=none keeps
manual checks inline and disables scheduled execution in the core topology.
The Temporal server is replaceable. Docker can add the bundled Temporal overlay,
but the same worker image runs unchanged against a self-managed cluster or
Temporal Cloud. See
Temporal Server or Temporal Cloud.
Data model
AProject owns its keywords, provider connections, alert rules,
notifications, and audit entries. Each keyword is unique by text, location, and
device.
Scheduling is stored as intent, not as jobs: ProjectDefaults holds the
project-wide schedule and market, and an optional per-keyword KeywordSchedule
overrides it. Each executed check is a RankCheck row recording status,
position, ranking URL, provider, and cost. Alerts connect AlertRule to
TriggeredAlert and DeliveryAttempt rows.
Rank data retention by deployment
Each completed check can store a normalized SERP snapshot inrank_checks.raw.
The adapters reduce provider responses to normalized organic results and SERP
feature labels before storage. That snapshot is separate from the rank-history
fields that remain on the row.
The dashboard reads the snapshot on the keyword detail page and in the checks
table, and it is also available through direct PostgreSQL access to
rank_checks.raw; the REST API rank-check resource does not expose this field.
Once a configured window expires, an older check can therefore have a null
raw value while its row, position, and organicRanks remain available. See
Database growth for worker configuration and
purge behavior.
Rank-check lifecycle
lib/rank-check/)
as scheduled checks. When Temporal is unreachable, interactive manual checks
fall back to inline execution in the app process; the REST API can also start
a check asynchronously and return 202.
Extensibility: providers
Providers are the main extension point, underlib/providers/.
Kinds. Every provider has a kind. serp providers answer “where does this
keyword rank” and are tried in a user-configured fallback chain; analytics
providers add owned-data context such as traffic and clicks, and never set
positions. A new category of data source - social media, for example - would be
a new kind following the same pattern.
Registry. PROVIDER_CATALOG in lib/providers/registry.ts is the single
source of truth for which providers exist, their kind, and the credentials
they require. The user-facing roster and setup details live in
Integrations.
Adapters. Each provider is one adapter module implementing a small
interface: identify itself, test a connection, fetch a rank. Adding a search
engine such as Bing means writing one new serp adapter and registering it in
the catalog - the scheduling, fallback, cost, and alerting machinery is
provider-agnostic and does not change.
Connected credentials are stored encrypted per project. Optional environment
fallbacks for local development are documented in
Integrations.