Skip to main content
An app is one row with a home storefront. Keywords are separate rows scoped by text, store and country, and a join table connects the two. That shape is what lets a single app track keywords in many markets without duplicating the app.

Entities

How do they relate?

A workspace owns apps. Each app captures snapshots of its listing and tracks keywords through a join table, because the same keyword row is shared by every app that tracks it. Traffic and difficulty attach to the keyword, while a position attaches to the pair of an app and a keyword on a given date. Action items belong to the workspace.

Rules that are easy to get wrong

  • An app is one row. Countries live on keyword tracking, not on a second app row. See Countries and markets.
  • Keyword is unique on text, store and country. The same phrase tracked in two markets is two keyword rows checked by two searches.
  • A competitor is an app row too. It carries isCompetitor and points at the primary app it was added to.
  • Traffic and difficulty persist in KeywordMetric. Opportunity does not persist. It is computed per app in the read layer. See Traffic, difficulty and opportunity.
  • installs is a BigInt and serializes as a JSON number.
  • All dates are UTC. Daily granularity uses a PostgreSQL date column, and today means the UTC date.
  • Every tenant owned row carries workspaceId, and PostgreSQL enforces it. Each of those tables has a tenant_isolation row level security policy, so a query with no workspace in scope returns nothing rather than everything. Service code does not filter by workspace in a where clause. Keyword, KeywordMetric and SerpEntry are deliberately global and carry no policy, because one search for a phrase serves every workspace tracking it. What is tenant owned is the tracking relation and the recorded position.

How does the schema change?

Only forward. Every schema change ships as a forward Prisma migration and the API applies pending migrations on boot. There are no hand written SQL migrations and no down migrations, which is why a rollback is a database restore. See Upgrade and roll back.
Last modified on August 24, 2026