Apps Built on Data You Already Connected
The returns desk at a mid-size distributor runs on three things: a Postgres database behind the warehouse system, a Shopify store, and a spreadsheet called returns_FINAL_v4 that exactly one person fully understands. When that person went on leave, the operations lead asked an AI builder for "a returns app." Forty seconds later there was a working web app with a login screen, a returns table, a status dropdown, and its own brand new database with zero rows in it.
That app was not wrong. It was just a fourth place where returns now live. The decision that actually mattered was never which builder to use. It was whether to build apps on connected data that already exists in your systems, or to stand up a new store of record that someone has to fill, sync and defend forever.
This is an architectural fork, and almost nobody makes it deliberately. People discover which side they landed on three weeks later, when two numbers disagree.
The same sentence hides two completely different projects
"Build me an internal tool for returns" can mean either of these:
A view and a control surface. The returns already exist as rows in Postgres and as refunds in Stripe and as tickets in Zendesk. The app looks at them, arranges them so a human can decide, and gives that human a button that does something in the system that owns the record.
A new system of record. The returns do not exist anywhere structured. They live in email threads and a spreadsheet. The app has to capture them, store them, and become the truth.
There is a one-question test that sorts almost every request:
If this app and its database vanished tonight, what would be permanently lost?
If the answer is "nothing, everything is still in Postgres and Stripe," you are building a view. If the answer is "the last three weeks of QA sign-offs," you are building a system of record, and you have signed up for backups, migrations, access control, retention, and an on-call story.
Both are legitimate. They cost wildly different amounts to keep alive. The failure mode is asking for the first and quietly receiving the second.
What it really means to build apps on connected data
An app over connected data has no database of its own. Its state is a set of queries and a set of actions.
On load it runs a read: a SQL statement against a replica, a call to the HubSpot API, a Stripe list, a Snowflake query. It renders what came back. When someone clicks a button, it calls the API of whatever system owns that record: refund in Stripe, transition the issue in Jira, post the note in Salesforce, update the row through the same service the warehouse team already uses.
The consequences are concrete:
- Day one has real data. No seeding, no import script, no "we will backfill later" that never happens. The app is useful the hour it exists because the data was already there.
- There is no sync problem, because there is nothing to sync. The classic internal tool disaster is a nightly job that copies orders into a local table, then drifts. Nobody notices until finance asks why the dashboard says 412 orders and NetSuite says 419.
- Schema changes come to you. Add a column upstream and the query can pick it up. Rename a column upstream and the app breaks loudly, which is much better than a copy that keeps serving stale values.
- Permissions can be inherited rather than reinvented. A read-only database role, a scoped OAuth connection, and an approval on the action buttons is a smaller surface than a homegrown users table.
And the constraints move too. You stop worrying about migrations and backups. You start worrying about query latency, API rate limits, and the fact that four systems disagree about what a "customer" is.
That trade is usually worth taking, but it is a trade, not a free lunch.
The bill on a brand new database arrives later
The generated app with its own database feels faster because the first ten minutes are faster. Here is what shows up afterward, in roughly this order.
Double entry. Someone updates the status in the new app. Someone else updates it in the source system. Now both are authoritative and neither is right. This is the single most common way an internal tool gets abandoned: not a crash, just people losing trust in it.
Backfill. The app is useless until history is in it. Somebody writes a one-off import, gets 80 percent of rows in, and the remaining 20 percent become permanent folklore.
Auth built from scratch. Sessions, password reset, roles, invites, offboarding. It is tedious enough that it gets skipped or half done. Security researchers who scan applications produced by AI app builders have published findings that a meaningful share ship with database row-level security switched off, meaning one tenant's rows are readable by another. That is not a knock on AI code specifically. It is what happens when a tool hands you a database and the security step is optional. If you are going that route, read up on the security failures specific to AI-generated apps before you put customer data in one.
Operations. Backups you have never restored. A migration nobody dares run on a Friday. A dependency upgrade that breaks the build eighteen months after the person who wrote it left. Getting from a working demo to something the company can lean on is its own project, and the gap is bigger than it looks from the prototype-to-production side.
None of this is an argument that new databases are bad. It is an argument that you should only pay for one when the data genuinely has nowhere else to live.
The two architectures, compared honestly
| Question | App over connected data | App with its own new database |
|---|---|---|
| Data on day one | Real, complete, immediately useful | Empty until someone imports or types it |
| Who owns the truth | The existing system (Postgres, Stripe, HubSpot) | The app, which now competes with existing systems |
| Typical failure | Slow query, API rate limit, mismatched IDs across tools | Drift, double entry, stale copies nobody trusts |
| Access control | Inherit: read-only DB role, scoped OAuth, action approvals | Build: users, roles, sessions, row-level security |
| Cost of an upstream schema change | Query breaks visibly, you fix the query | Sync silently serves wrong data until someone notices |
| Can it capture data that exists nowhere | No. It has nothing to write into | Yes. This is the entire reason to choose it |
| Offboarding a builder | Delete the app, lose nothing | Delete the app, lose records unless migrated first |
| Sensible lifespan | Indefinite, it is a lens | As long as someone maintains a real application |
| Best fit | Consoles, review queues, ops dashboards, admin views | Intake forms, trackers, anything that is the source |
The row that decides most projects is the second to last one. A view is disposable in the good sense. You can throw away five of them in a month and lose nothing but the time. A system of record is a commitment.
Reading is harder than rendering
The part people underestimate is not drawing charts. It is that no two systems agree on identity.
Stripe knows a customer by cus_.... HubSpot knows a contact by a numeric record ID. Your Postgres knows a user by UUID. There are no foreign keys across a SaaS boundary. Anyone who has joined billing data to CRM data has spent an afternoon on email normalization and discovered that 6 percent of rows match nothing.
The approach that survives contact with reality:
- Pick a spine. One system is the anchor, usually the one with the strongest internal IDs. Everything else is a lookup hanging off it.
- Store the mapping upstream, not in the app. If Stripe customer IDs matter to you, put them on the customer record in your own database or as a CRM property. Then the join is a column, not a heuristic.
- Show the misses. An app that silently drops unmatched rows will be believed and will be wrong. An app with a line reading "1,204 orders matched, 71 unmatched by email" gets trusted and gets fixed.
- Read a replica, not production. A dashboard with a careless
ORDER BYon a 40 million row table will find your slowest query for you, at the worst time.
There is a second, subtler reading problem: layout decisions depend on data you have not seen yet. A column of statuses with 4 distinct values wants a chart. The same column with 900 distinct values wants a search box. A description field averaging 30 characters fits a table cell. The same field averaging 800 characters destroys the row height and pushes every other column off screen.
This is why AI-designed dashboards so often look plausible and read badly: the layout was chosen from column names alone. It is the root cause behind most of the complaints in why AI dashboards look wrong. When Skopx builds an app it runs the query first and profiles what actually came back, column types, value ranges, cardinality, text lengths, before it picks components, so the design fits the real shape of the data rather than an imagined one.
Whatever tool you use, insist on that order. Query, then profile, then design. Anything that designs first is guessing.
Writing back belongs to the system that owns the record
Actions are where connected-data apps earn their keep, and where they are most dangerous.
The rule is simple: the app does not change state itself. It asks the owning system to change state, through the same API that system exposes to everyone else. A refund happens in Stripe. A ticket closes in Zendesk. A deal stage moves in HubSpot. The audit trail lands where auditors already look, and the business rules that live in that system still apply.
The habits worth enforcing on every action button:
- Explicit click, explicit confirmation. No action fires as a side effect of loading a page or changing a filter. Every consequential click states what it will do, to which record, and asks once.
- Idempotency. Double-clicking a refund button should not issue two refunds. If the upstream API supports an idempotency key, use it. If not, disable the button while the call is in flight and re-read state after.
- No unbounded bulk operations. "Apply to all filtered rows" is how someone emails 4,000 customers at 2am. Cap it, show the exact count, and make the number impossible to miss.
- Separate who can look from who can press. Half your team needs the view. Far fewer need the buttons. Splitting those two permissions is the cheapest risk reduction available, and it is the core of sane internal tools permissions.
Skopx apps follow this shape: every action button is an explicit click with a confirmation, and the write goes out through the connected tool rather than into any storage the app controls.
Where connected-data apps hit a wall
Here is the honest boundary, and it is the reason this article exists.
An app that reads from systems and acts through them cannot be the place a record is born. There is no form that creates a row it owns, because it does not own rows. That rules out a specific and large family of tools:
- An invoicing system where the invoice first exists when someone fills in the form.
- A delivery system that creates deliveries.
- An applicant tracker where the application itself is stored in the app.
- A field inspection tool where a technician types findings that exist nowhere else.
Skopx is explicit about this: its apps read from connected systems and take actions through connected tools, and they do not yet store their own records. There is no form component that creates new data. So what you get are consoles, dashboards, review queues and admin views over data that already lives somewhere. If your requirement is "this app must be the system of record," that is not what it does today, and no amount of clever prompting changes it.
The practical move when you hit that wall is to separate the storage question from the interface question. Let the record live in something built to hold records, a Postgres table, an Airtable base, a Jira project, a Notion database, and then build the console on top. You still get the review queue, the ops dashboard and the action buttons. You just stopped asking one tool to be both the warehouse and the window. If instead you need the whole application generated as code you own and host, that is a different architecture with different tradeoffs, laid out in declarative apps versus generated code.
A checklist before you build apps on connected data
Run this before anyone opens a builder. It takes twenty minutes and saves weeks.
- Name the source of every field. Write the app's fields in one column and the system each comes from in the next. Any field with an empty second cell is a capture requirement, which means you need storage, which means this is not purely a view.
- Decide the spine and the join keys. Which system anchors the list, and what column links the others. If you cannot answer, the app will be a pile of unrelated widgets.
- Run the query by hand first. Look at the result. Count rows. Check nulls. Look at the longest text value. This is the profile step, and doing it manually once teaches you what to demand from any tool that claims to do it for you.
- List the actions and their blast radius. For each button: which API, which record, reversible or not, who is allowed to press it.
- Set the refresh expectation. Live on every load, cached for a minute, or a snapshot at 6am. Users will assume live. If it is not, say so on screen.
- Decide who sees it. Private to you, shared with a team, or open to the org. Do this before you build, not after someone forwards a link.
- Write the deletion plan. If this app is unused in three months, what breaks when it is removed? A good view answers "nothing."
Steps 1 and 7 do most of the work. Step 1 catches the system-of-record request early. Step 7 tells you how much rigor the thing deserves.
When you should not build apps on connected data
Being straight about the cases where this architecture is the wrong pick:
The data does not exist yet. Capture-first workflows need storage. Use a database, a forms tool, or a proper application. A view over nothing is nothing.
The source API is too slow or too limited. Some SaaS APIs are strictly rate limited, and a dashboard that fifteen people refresh all morning will find that ceiling. If your read path cannot be cached and cannot be batched, a warehouse plus a scheduled load is the correct answer, even though it reintroduces sync.
You need offline or field use. A technician in a basement with no signal needs local storage and conflict resolution. That is a real mobile application, not a console.
You need heavy computation over huge scans. Scanning hundreds of millions of rows for every page view is not a live query pattern. Pre-aggregate upstream, then view the aggregates.
Regulatory requirements demand your own immutable log. If you must retain a record of exactly what was displayed and approved, independent of vendor retention policies, you need storage you control, plus a governance model for who can create and change these tools.
If your situation is one of these, an honest tool will tell you so. If a vendor tells you their thing handles all five, they are selling, not engineering. The same skepticism applies to the broader question of replacing purchased SaaS with internal tools: sometimes the license is cheaper than the maintenance.
FAQ
Is an app over connected data just a dashboard with extra steps?
No, and the difference is the action layer. A dashboard tells you that 23 orders are stuck in fulfillment. A connected-data app shows you the same 23 rows and gives you a button that reprocesses each one through the warehouse API, with a confirmation and an audit trail in the system that owns the order. The read is the boring half. The write, done carefully through the owning system, is what turns a report into a tool people open every morning.
How fresh is the data if there is no database?
As fresh as the query. Each load hits the source, so what you see is the source at that moment, which is the main advantage over a synced copy. The trade is latency and rate limits: a heavy join across a large table will take seconds, and some SaaS APIs will throttle you if a team refreshes constantly. Where that becomes a problem, cache deliberately and label the staleness on screen. Silent caching is what teaches people to distrust a tool.
What if I need users to submit something?
Then you need a place to put it. Keep the record in a system built for storage, a database table, an Airtable base, a ticketing project, and treat the app as the console over it. In tools that only read and act, including Skopx, there is no form component that creates new data, so an app cannot be the intake point on its own. That constraint is annoying once and clarifying forever: it forces you to name where the record actually lives.
Do these apps work over a data warehouse as well as an operational database?
Yes, and often better. Warehouses like Snowflake, ClickHouse or a Postgres replica are built for exactly this read pattern and keep analytical load off production. The catch is freshness: warehouse tables are usually as current as the last load, so an app reading from one is showing you last night's world. That is fine for an executive view and wrong for a fulfillment queue. Match the source to how quickly the decision has to be made, which is the same reasoning behind a well-built inventory tracking view.
How many of these should a team have?
More than you expect, and each one narrower than you expect. Because a view carries no data, the cost of a bad one is only the time to build it. Teams that get value from this pattern tend to end up with a dozen small, specific apps: one queue per workflow, one console per team, rather than a single giant portal that tries to serve everyone and pleases no one.
What does this cost to run?
That depends entirely on the tooling. The economics worth checking are per seat cost, whether AI usage is marked up, and whether you are also paying for hosting and a database you did not need. For the platform described here, the per-seat plans are listed on the pricing page, including a team tier at 16 dollars per seat per month with AI tokens included and no markup on AI usage.
Start with the app nobody will argue about
Do not begin with the ambitious one. Begin with the query someone already runs by hand every Monday, the one that ends in a screenshot pasted into Slack. Turn that into a view over the live source. Add one action button for the follow-up that person always does next. Ship it to three people.
You will learn more from that in an afternoon than from a month of planning, because it forces you to answer the only questions that matter: where does this data really live, who is allowed to change it, and what happens when the numbers disagree. Get those right, and the app is almost an afterthought. Get them wrong, and no builder on the market will save you.
Skopx Team
The Skopx engineering and product team