← Back to wiki

TurboClientSystems — Federal Award Intelligence

What changed, and when.

The product name in this repo (GovDEETS) is a working codename, not a final brand — it's still in flux. This document deliberately talks about "the application" rather than a name, so it doesn't need a rewrite the next time the name changes.

This is the root of the repository — the front door for anyone landing here from GitHub, not a marketing page. It's written for the audience that actually reads a repo instead of a pitch deck: what the application does, why it's built the way it is, who it's for, how it might make money, and where it honestly stands. Everything below is either derivable from the code or stated plainly as a decision that hasn't been made yet.

Contents

What this is

The application aggregates federal contract and grant data from the public government APIs that publish it, normalizes every source into one schema, and keeps a full field-level history of everything that changes over time. You can filter and sort the combined list, flag specific records to watch, read a record's complete change timeline, and export any filtered view to CSV with column names a CRM will accept.

Concretely, it answers three questions the source systems can't answer cleanly today: what's out there across all of these sources at once? (the normalized list), what changed, and when? (the change history), and which of those changes do I care about? (the watchlist). See web/src/lib/pipeline/sync.ts for the diffing logic that makes the second one possible, and web/prisma/core/schema.prisma for the data model behind all three — or docs/schema/core.md for the same thing as a diagram.

The problem it exists to solve

Federal award data is public, free, and genuinely well-published. That's not the problem. The problem is that it's spread across four systems that don't agree on what a record looks like, and every one of them is a snapshot of right now.

  • USAspending has contract awards, financial assistance, and sub-awards as JSON, no key required.
  • SAM.gov has entity registrations, contract opportunities, and — since February 2026 — the contract award data that used to live in FPDS, behind a free API key.
  • Grants.gov has federal grant opportunities, on its own public API.
  • SBIR.gov has SBIR/STTR awards and open solicitations, free and without a key.

Several sites, several schemas, several sets of field names for the same underlying concepts. Answering "what's happening in my space" means checking all of them and reconciling them by hand. And none of them will tell you what a record looked like last week — if the ceiling on a contract moved, or a solicitation's close date slipped, or a recipient name changed, that history exists nowhere unless you happened to save yesterday's copy yourself.

The landscape also moves underneath you, which is its own argument for this application existing. FPDS — for two decades the federal contract award database — had its public site decommissioned on February 24, 2026, with its ATOM feed retiring later that fiscal year in favor of the SAM.gov Contract Awards API. Anyone who had built a pipeline against the old feed found out by having it break.

Four groups run into this from different directions, and the application is built for all of them:

  • Small and mid-size federal contractors chasing awards. Business development and capture teams who know the work but can't justify a five-figure annual seat on a commercial market-intelligence platform. They're doing this in spreadsheets, or not doing it.
  • Subcontractors and teaming partners. The job isn't the award — it's finding the company that won the award, early enough to get on their team before the slots fill.
  • Grant seekers. Nonprofits and research organizations tracking Grants.gov opportunities and federal financial assistance, who have the same fragmentation problem with a different half of the data.
  • Us. TurboClientSystems is a small federal contractor with exactly this problem, which is why the application exists at all. See Where it stands — dogfooding isn't a phase we're passing through politely, it's the current phase.

Why now

Two things make this buildable at a small scale that wouldn't have been a few years ago. The federal government's own open-data mandates have pushed most of this data onto real, documented, free APIs — USAspending in particular is a genuinely good public API rather than a scraped website. At the same time, the commercial tools in this space have consolidated and priced themselves at enterprise procurement teams, leaving the small contractors who are the actual majority of the market with a gap between "free raw data you have to reconcile yourself" and "more than a small company's entire BD budget."

How we're different

There are established tools in this space — GovWin IQ, Bloomberg Government — and the honest position is that we compete with them directly, not that we've found some smaller niche where we don't have to. The goal is to be better than they are and cost a fraction of what they charge: something like a tenth of an enterprise seat, as a ceiling to aim under rather than a floor. The Bloomberg Terminal is the right reference point on purpose, and not despite its reputation for a dense, unglamorous interface — because of it. Professionals pay real money for something that's fast and complete, not something that's pretty. That's the bet DESIGN.md makes about this product's own interface too.

That's a claim we haven't earned yet, and a multi-year one if we do. What we can say honestly about where we actually are: the government sites show you today, and the enterprise platforms charge $15,000–$50,000+ a year for a nicer interface on the same today. Neither gives a small contractor the one thing that matters most and is cheapest for us to build first — memory, a record of what's changed and when. We built that first on purpose, to prove the core idea before spending scarce time on breadth we haven't earned the right to build yet.

Today's actual incumbent — the tool every audience below is really switching from — is a spreadsheet, and nobody abandons a free spreadsheet for something merely more ambitious; they abandon it when something is faster at the same job. See DESIGN.md for what that commits us to. But the spreadsheet is the floor this product has to clear, not the ceiling on what it's trying to become.

Change-tracking is the whole product

  • The diff is the data. Every poll hashes each normalized record against what's stored. New records are logged as created; changed records are updated in place and logged as updated with the exact fields that moved and their before/after values; unchanged records just bump a timestamp and generate no noise. That last case matters as much as the first two — an alerting product that cries wolf gets muted.
  • History is append-only. Records update in place, but ChangeEvent rows are never modified or deleted. A record's current state and its full history are separate things, so "what does this say now" and "what did this say in March" are both answerable.
  • Every change traces to a poll. Change events are tied to the PollRun that produced them, so you can ask "what changed in Tuesday's poll" as easily as "what's this record's history." That also means a failed poll is visible as a failed poll rather than as a suspiciously quiet day — though surfacing that in the UI is still a planned step, not a built one.
  • One shape, many sources. Each connector normalizes its source into the same flat NormalizedRecord, which is what lets contract awards, grant opportunities, and entity registrations live in a single filterable list. Source-specific fields that don't map to a column survive in an extra bag rather than being dropped on the way in.
  • Being able to prove we were watching. A product that promises to tell you what changed has an obligation the source sites don't: showing that it was actually looking. That's why per-request instrumentation and a public status page are roadmap steps rather than ops afterthoughts — a silently broken connector is indistinguishable from a quiet week in this domain, and "no changes" is a claim we have to be able to back up.

Business model

Undecided on the exact number, decided on the target: whatever we charge, it should be a small fraction of what the enterprise incumbents do — call it a tenth of a GovWin or Bloomberg Government seat as the ceiling, not a floor — while we build toward matching or beating them on substance. It's too early to pretend the number itself is settled; the project is days old and hasn't proven itself against our own pipeline yet, let alone anyone else's. The intent is a standalone commercial product rather than an internal tool, which is the only part that's fully settled.

What's already clear is the shape of the constraint. The underlying data is public and free, and we're not going to build a business on pretending otherwise — anyone can go get it from the source. What isn't free is the memory: the normalized history, the watchlists, the alerting, and eventually the pipeline into a CRM — and, as the product grows, whatever else it takes to actually be the better tool, not just the cheaper one. Whether pricing lands per-seat, flat per-company, or a free tier with paid alerting is a real question with real tradeoffs, and picking now would just mean picking blind. The Open Beta phase is deliberately "free or steeply discounted" for that reason: we'd rather find out what it's worth to someone who isn't us before naming a number. But naming it isn't the same as backing off the target — whatever it is, it's not going to look like enterprise pricing, and it's not going to be a rounding error either. This is meant to be a real business, not a $2-a-month hobby project.

Who's building this

The application is built by TurboClientSystems, Inc., founded in 2024 by a father-son team, alongside two other projects: WinDEETS and RunDEETS — the latter a governance, risk, and compliance platform, and the closest sibling to this one in both stack and conventions. The shared DEETS naming is lineage rather than a formally productized suite; see the caveat at the top of this document about how firmly to hold any of these names.

This project started in August 2026 and is the newest of the three. It exists because the same team doing federal contract work needed to answer "what's changing in our space" for itself and found that answering it properly meant building this.

For the fuller version of the team's story, see the RunDEETS README.

Where it stands

Closed Alpha, and early in it. Nobody outside TurboClientSystems uses this. One connector is live — USAspending — and it's enough to have populated the database with real award data and to prove the change-tracking pipeline works end to end. The Grants.gov connector is written but disabled pending response-shape verification. SAM.gov and per-agency feeds are registered as connectors that throw if you enable them, which is deliberate — the slot exists so the pipeline shape is settled, and the implementations land as they're scoped. The FPDS slot is retired rather than pending, for the reason described above.

Accounts, organizations, roles and billing now exist, which moves the second phase — Closed Beta — from planned to underway. Sign-in is passwordless: an identity provider or a passkey, with no password model in the schema at all. Auth, billing and application data live in three separate Postgres databases whose credentials cannot reach each other, so a leak of any one of them exposes only what that database holds. Following a record has become a watch that fires on conditions rather than on any change — "only if the money goes down" is a different watch from "tell me everything". And there's a gated public API with per-organization keys, rate limits and usage metering, including the version-history endpoint that is the thing the source systems cannot do at any price.

What's still missing is more interesting than what's there. Nothing leaves the browser yet: watch hits and notifications are recorded correctly and then nothing drains the queue, because there is no worker and no mail transport. Of the five integration channels, only webhook and Slack actually deliver — the UI marks the other three "not sending yet" rather than letting someone wire up an alert that silently goes nowhere. Payment is a mock processor; the Stripe path returns 501 rather than pretending. And production still has no real database, so it serves a static snapshot.

web/src/lib/governance/roadmap.ts is the source of truth for what's completed, in progress, and planned, phase by phase; it's kept current, not aspirational, and it renders at /app/roadmap in the running app. Phases are ordered stages, not dates — see PLANNING.md for why, and for how much detail future work earns based on how far out it is.

Running it locally

The application lives in web/ and runs in Docker alongside every other project in the fleet, following the same pattern they do. From this directory:

docker compose up -d

That starts the app at http://localhost:3117 and Postgres alongside it. It can also be started from the git root's aggregator compose file together with the other projects:

docker compose up -d govdeets

Ports are ours and don't collide with anything else in the fleet: 3117 for the app, matching the convention that a project's port is the same inside and outside its container, and 5417 on the host for Postgres, which stays on the standard 5432 inside the container. Nothing in the compose network uses the host database port — the app reaches Postgres at db:5432 by service name — so 5417 exists purely so host tooling doesn't collide with a local Postgres.

Apply the schema to all three databases, and run a poll:

docker compose exec govdeets npm run db:migrate
docker compose exec govdeets npm run db:seed

The first fans out across the core, auth and billing datastores — see AGENTS.md for why there are three and what that means when you're writing code. The second loads the plan and entitlement matrix. Then sign in at /signin: with no OAuth credentials configured you'll be offered six mock providers, each of which signs you in as a different stock account — a different role, a different plan, a different amount of data — so you can see the product from an owner's seat and an auditor's without seeding anything by hand.

curl -X POST http://localhost:3117/api/poll

Or click "Poll now" in the UI. To run the pipeline without a server up, which is what a cron job does: docker compose exec govdeets npm run poll.

To work on the host instead of in the container — cd web && npm install && npm run dev — copy web/.env.example to web/.env first. It points at localhost:5417, so it works against the Dockerized Postgres with the app service stopped. The container sets its own DATABASE_URL pointing at db:5432, and Prisma won't override an already-set variable, so the two don't fight.

Environment variables

Variable Purpose
DATABASE_URL The core datastore: records, organizations, teams, watches, integrations. Set by docker-compose.yml to db:5432 for the containerized app; web/.env points at localhost:5417 for host-run tooling; a hosted Postgres in production.
AUTH_DATABASE_URL The auth datastore: users, identities, sessions, passkeys. A different database with a different role that cannot connect to the other two. Unset means accounts are unavailable and the app says so, rather than half-working.
BILLING_DATABASE_URL The billing datastore: plans, entitlements, subscriptions, invoices, usage. Unset means entitlements fall back to the free plan defined in code.
APP_URL The origin the app is served from. Used as the WebAuthn relying-party origin and to build OAuth redirect URIs, so it has to be exact — redirects are built from this rather than from the request, because a dev server bound to 0.0.0.0 otherwise sets the session cookie on a host the browser never returns to.
AUTH_ENABLE_MOCK_PROVIDERS Turns on the local mock identity providers. Refused outright when NODE_ENV=production regardless of this value.
GOOGLE_CLIENT_ID / _SECRET, AZURE_*, GITHUB_* Real OAuth providers. Each is optional; the sign-in page only offers the ones that are configured, and a configured real provider replaces the mock of the same name.
BILLING_PROVIDER mock (default) runs the whole billing flow in-process with no third party, which is what keeps every entitlement gate reachable without a Stripe key. stripe is not implemented yet and says so.
POLL_TRIGGER_SECRET Optional in development. Required in production — POST /api/poll refuses to run without it once NODE_ENV=production. When set, requests must carry it as an x-poll-secret header, so the endpoint can't be triggered by anyone who finds the URL.
SAM_GOV_API_KEY Enables the SAM.gov connector once it's implemented. Free from api.data.gov.
USASPENDING_PAGE_LIMIT / USASPENDING_MAX_PAGES How much of USAspending's award search a single poll pulls. The bound is why removal detection isn't implemented yet — absence from a windowed poll doesn't mean absence from the source.

API surface

Internal, session-authenticated. GET /api/records accepts q, sourceSystem, recordType, agencyName, watchedOnly, minAmount, maxAmount, changedSince, sortBy, sortDir, page, and pageSize. GET /api/export accepts the same filter and sort parameters and streams CSV with CRM-friendly column names instead of raw API field names. GET /api/poll reports which connectors are enabled and what each disabled one needs; POST /api/poll runs them. /api/watches, /api/members, /api/keys, /api/integrations and /api/billing back the account surfaces.

Public, key-authenticated, plan-gated. Everything under /api/v1 takes an API key as Authorization: Bearer gdk_… or X-API-Key, is cursor-paginated, and is metered:

Endpoint What it answers Plan
GET /api/v1/records Current state of the corpus Team and up
GET /api/v1/changes The change feed, resumable from a cursor Team and up
GET /api/v1/records/:id/history Every version this record has had, with diffs Business and up
GET /api/v1/aggregate Counts and sums by agency, recipient, NAICS, PSC… Business and up

Cursors are ids rather than timestamps and offsets: an integrator walking a million rows with ?page= gets duplicates and gaps as records change underneath them, and resuming from a timestamp either re-delivers a whole millisecond or silently skips half of it. Over the rate limit is 429 with Retry-After; outside the plan is 402 naming the plan that includes it.

How it's built

Next.js 16 (App Router) / TypeScript / Tailwind, with Prisma over Postgres, in web/ — matching the layout every other project in the fleet uses. Deployed on Vercel, where serverless functions have no persistent filesystem, which is why the database is Postgres rather than the SQLite the scaffold started with. Note that Vercel's project root directory must be set to web.

.github/workflows/ci.yml gates merges on lint, typecheck, tests, and a real build. .github/workflows/daily-poll.yml calls the poll endpoint on a schedule and needs two repo secrets: GOVDEETS_URL and POLL_TRIGGER_SECRET, the latter matching the server's.

AGENTS.md covers the Next.js version's breaking changes and should be read before making non-trivial changes — this is not the Next.js most training data describes.

In this folder

  • web/src/lib/connectors/ — one module per data source, each implementing the shared Connector interface and normalizing its source into NormalizedRecord.
  • web/src/lib/pipeline/ — the sync and diffing logic that turns a poll into change events.
  • web/src/lib/governance/roadmap.ts — the roadmap content, and the source of truth for phase and status.
  • web/src/app/ — the Next.js application: the record list, record detail, roadmap page, and API routes.
  • web/prisma/ — three schemas and three migration histories: core/, auth/, billing/.
  • web/src/lib/authz/ — the permission matrix, entitlement resolution, and the gate that combines them.
  • web/src/lib/watches/ — trigger evaluation. triggers.ts is pure and exhaustively tested, because a trigger that fails to fire produces silence and silence looks exactly like nothing having happened.
  • docs/schema/ — generated ERDs, one per datastore, rendered in the app at /app/schema.
  • db-init/ — the SQL that carves one Postgres into three isolated databases with three roles.
  • web/scripts/poll.ts — runs the pipeline without a server, for cron.
  • docker-compose.yml — the app and its database, on ports 3117 and 5417. Included from the git root's aggregator so docker compose up -d govdeets works from there too.
  • DESIGN.md — what the application should look and feel like, and what it should refuse to become.
  • PLANNING.md — how future work gets scoped and described.
  • PROJECT_MANAGEMENT.md — how issues get written, labeled, sized, and assigned.
  • AGENTS.md — engineering notes, including this Next.js version's breaking changes.
  • docker-compose.yml — local Postgres for development. Not used in production.