Inspiration

I do a lot of home automation. Over the past couple of years I've built small apps to help me manage my own energy usage: shifting EV charging, battery storage, and heating around the grid rather than just blindly consuming whatever's available. Octopus Energy is one of the few suppliers that has genuinely cracked demand-side flexibility; their Agile tariff is a good example of what's possible when a supplier has both the data pipelines and the will to use them.

Most suppliers don't have either.

They have billing systems, maybe a basic app, and a vague sense that smart grid stuff is coming. Building the infrastructure to run demand response campaigns in-house (triggering consumers to shift load during grid stress, rewarding them for it, tracking the outcome) is genuinely hard, and most don't have the engineering capacity or the appetite to prioritise it. I started wondering whether a drop-in platform could handle it for them automatically. Not a consultancy engagement or a bespoke build. Something a supplier could onboard to, configure their branding, and be running campaigns within an hour.

I chatted with a couple of people from the industry who didn't immediately laugh at me. In fact, they thought it was worth exploring. So here we are: GridShift.


What it does

GridShift is a multi-tenant demand-side flexibility platform. Energy suppliers sign up, configure their branding and tariff structure, and GridShift handles the rest.

On the supplier side: a clean admin portal for creating demand response campaigns, importing tariffs directly from billing APIs, and managing the onboarding setup. Each supplier gets their own fully branded consumer portal (colours, logo, and custom domain), all controlled via API.

On the consumer side: a dashboard showing live national grid demand data for their geography, their opted-in devices, their points balance, and any active events. When the grid is under stress and a campaign fires, consumers get a push notification and their devices are flagged as participating. Points are awarded based on rated device power and event duration. Those points can be redeemed against energy bill credit, smart devices, or other rewards the supplier configures.

The grid demand data is real. GridShift pulls from national operators across five geographies: NESO for GB, SMARD for Germany and broader EU, EIA for the US, Fingrid for the Nordics, and OpenElectricity for Australia. The same chart component renders regardless of which country the tenant operates in; the API normalises the response shape per geography.


How we built it

Unusually for me, the tech came first. The hackathon was a good excuse to properly test PgCache in something resembling a real workload, and I'd been looking for a context where Aurora Serverless v2 actually made sense rather than just being the fashionable choice. The brief became: build something where the architecture fits naturally, not one where the tech is bolted on to tick boxes.

Stack

Layer Technology Why
Database Aurora PostgreSQL 16.4 Serverless v2 Variable load profile; native logical replication
Read layer PgCache on EC2 Hot-read caching via CDC; no full read replica cost
API + Frontend Next.js on Vercel Single deployment for supplier admin and consumer app
Frontend generation v0 (Vercel) Multi-tenant theming from an existing API contract
Auth JWT + Row-Level Security Tenant isolation enforced at the database layer
Push notifications Web Push (VAPID) Native browser push, no third-party dependency
Secrets AWS Secrets Manager + SSM Admin credentials and PgCache connection string
Grid data NESO, SMARD, EIA, Fingrid, OpenElectricity Real national grid operators across five geographies

Why Aurora Serverless v2

Aurora PostgreSQL 16.4 Serverless v2 is the core database, running in eu-west-2. The burst load profile of a demand response platform (quiet most of the time, then everything wakes up at once when a grid event fires) is exactly the workload ACU auto-scaling is built for. It also supports logical replication natively, which made the PgCache integration straightforward to set up:

-- On Aurora writer
CREATE PUBLICATION gridshift_pgcache_pub FOR ALL TABLES;
SELECT pg_create_logical_replication_slot('gridshift_pgcache_slot', 'pgoutput');

That's it. PgCache connects as a read-only subscriber and stays current via CDC. The app falls back to the writer endpoint if PgCache is unavailable.

Why PgCache

PgCache runs on EC2 in the same VPC as Aurora and acts as a wire-compatible read proxy. It subscribes to Aurora via logical replication, caches the hot read set (event status, device opt-in state, points balance), and keeps that cache current via CDC. Reads from the consumer app route through PgCache; writes go direct to Aurora.

A read replica would duplicate the entire database: all storage, all compute, running continuously. For GridShift, the majority of reads hit a small hot set. Replicating the full database to serve those reads is wasteful. PgCache caches what actually gets read, keeps it fresh automatically, and costs a fraction of a read replica. No code changes, no Redis layer, no manual cache invalidation logic. Just a different connection string.

PgCache delivers 80%+ read latency improvement and is billed hourly on AWS Marketplace with 1M cache hits free per month.

Row-Level Security

Row-Level Security handles multi-tenant data isolation at the database layer. Every table has a tenant_id column and a corresponding policy:

ALTER TABLE campaigns ENABLE ROW LEVEL SECURITY;

CREATE POLICY tenant_isolation ON campaigns
  USING (tenant_id = current_setting('app.tenant_id')::uuid);

The application sets app.tenant_id at connection time. After that, the database enforces the rest regardless of what the query says. Even a buggy query cannot cross tenant boundaries.

Migration tooling uses a separate admin user (gridshift_admin, credentials in Secrets Manager) that bypasses RLS. The application user (gridshift_app) does not. Keeping those credential paths separate is the correct pattern and easy to get wrong under time pressure.

The database schema

Eight tables, each with tenant_id: tenants, suppliers, consumers, campaigns, devices, points_transactions, tariffs, and push_subscriptions.

points_transactions is append-only. Every award is a new row; the balance is always a live SUM(). No in-place updates, no balance drift, clean audit trail as a default property of the schema rather than an application-level concern.

The frontend: v0

The frontend was built with v0, and honestly that part surprised me. I'd assumed the Vercel requirement was mainly about hosting (I have plenty of Vercel experience) and had already built a basic frontend before I properly sat down with v0. When I did, I gave it my existing design and asked for two things: make it nicer, and make the whole thing re-skinnable on the fly from a stylesheet each supplier provides during onboarding. It worked first try. Each tenant now gets a fully branded dashboard pulled from their branding config in Aurora. The bit I'd been quietly dreading took one iteration.

Grid data integration

The /api/v1/grid/today endpoint dispatches to the relevant national grid operator based on the tenant's geography and returns a normalised response regardless of source:

  • GB: NESO Day Ahead Demand Forecast
  • DE / EU proxy: SMARD (Bundesnetzagentur)
  • US: EIA API (PJM region)
  • Nordics: Fingrid 15-minute consumption
  • Australia: OpenElectricity NEM power

The simulation endpoint

Rather than build a real-time event scheduler for a hackathon, I built a /api/v1/demo/simulate endpoint that fires the full demand event lifecycle in a single API call: campaign creation, device opt-in, points award, push notification, completion. The full stack gets exercised, the demo is compelling, and I didn't sink two days into a cron scheduler that adds nothing to the proof of concept.


Design

The frontend and backend are not separate concerns here. The database schema directly enables the UI's most distinctive feature.

The suppliers table stores primary_colour, secondary_colour, logo_url, and custom_domain. The branding API serves those values at runtime. v0 generates a component tree that consumes them. Every supplier gets a fully white-labelled consumer portal controlled from their admin dashboard, with no redeploy required.

The consumer app is intentionally simple. The dashboard shows grid demand today, points balance, device count, and event status. Every data point maps directly to an Aurora table.


Challenges we ran into

Multi-tenancy was the hardest call.

Creating a separate database per tenant isn't scalable, and individual schemas add operational overhead fast. I went with a shared schema and RLS, which was the right decision, but it created a complication with PgCache.

Approach Isolation Cost Complexity
Separate database per tenant Strong High High
Separate schema per tenant Medium Medium Medium
Shared schema + RLS Pragmatic Low Low–Medium

PgCache operates using a dedicated read-only database user, and that user bypasses RLS. That meant I couldn't rely on the policy to scope cached reads; I had to handle tenant scoping at the query level for the PgCache read path, while RLS continued to protect writes on the Aurora writer. It took a while to get that boundary right.

In a full production setup, a multi-database architecture per tenant would give cleaner isolation. The costs and complexity are too high for a hackathon, and honestly for an early-stage product too. But it's a decision worth making consciously rather than discovering mid-build.

Knowing where to stop.

One of my biggest problems is knowing when to stop! A hackathon forces the issue. Rather than build a real-time event scheduling engine, I built a simulation endpoint that fires the full demand event lifecycle in a single API call: campaign creation, device opt-in, points award, push notification, completion. The full stack gets exercised, the demo is compelling, and I didn't sink two days into a cron scheduler that adds nothing to the proof of concept. Job done.


Accomplishments that we're proud of

The multi-tenant theming landed really well. The fact that the same codebase serves Acme Energy (orange), GreenVolt (green), and OctoFlex (hot pink), all pulling their brand config from Aurora at runtime, is the thing that makes it feel like a real product rather than a demo with a tenant query parameter bolted on.

The grid data integration works across five geographies with a normalised response shape. Real data, not synthetic, from actual national grid operators.

And the RLS architecture is solid. Even a buggy query cannot cross tenant boundaries. That's the kind of thing that's invisible when it works and catastrophic when it doesn't. And it works.


What we learned

PgCache is worth serious consideration, but know your architecture first. In a workload without RLS complexity it would be a very clean win: wire-compatible, CDC-based invalidation, flat hourly pricing with no per-query charges. The RLS incompatibility for multi-tenant SaaS is a real constraint that needs to be designed around from the start rather than retrofitted. I'd use it again, with that decision made upfront.

Aurora Serverless v2 is the right call for event-driven workloads. The combination of ACU auto-scaling and native logical replication support made it a genuinely comfortable fit here rather than a forced one.

v0 is worth your time if you come in with a defined data model and API contract. It's not magic without those inputs. With them, the speed is real. The multi-tenant reskinning that I'd budgeted a day for took one session.

Multi-tenancy is a first-principles decision. The isolation model you pick shapes your credentials strategy, your caching layer, your migration tooling, and your connection pooling. Changing it later is expensive. Pick it first.


What's next for GridShift

A few things are obvious next steps. The PgCache instance is currently a single point of failure; a second instance behind a load balancer with Aurora failback is straightforward to add. The points balance query does a full scan per consumer, which is fine now and won't be at scale; a trigger-maintained running total is the production fix.

Beyond infrastructure: a real event scheduling engine that monitors grid demand data and fires campaigns automatically when thresholds are crossed. That's the bit that closes the loop from "platform that can do demand response" to "platform that does it". The grid data integrations are already there; it's a matter of wiring a scheduler to the thresholds.

Longer term, if the conversations with people in the industry go anywhere, the multi-database tenancy model is worth revisiting properly. RLS is the right pragmatic choice for now. It's not the right permanent choice if isolation requirements tighten.

Monetisation

GridShift has a few natural revenue models, and the right answer is probably a combination depending on which customer segment lands first.

SaaS subscription (supplier-side). The most straightforward path: charge suppliers a monthly fee, tiered by the number of active consumers enrolled or campaigns running. A small regional supplier pays differently to a national one with 500k customers.

Revenue share on grid services. Demand response has real monetary value in balancing mechanism markets. GridShift could take a percentage of the payments suppliers receive for successfully shifting load, which aligns the platform's incentives directly with actual grid outcomes.

Per-event pricing. Charge suppliers per demand response event fired rather than a flat subscription. Easy to justify when each event has a measurable financial outcome for the supplier, and lower friction for smaller suppliers who aren't running events frequently enough to justify a subscription tier.

White-label licensing. Suppliers who want GridShift fully rebranded as their own product pay a licence fee. The branding infrastructure is already there; a premium tier could add custom domain, custom CSS, and dedicated infrastructure for larger players who need stronger data isolation.

Rewards marketplace placement. Smart device manufacturers, retailers, and energy service companies pay for placement in the redemption catalogue. Consumers are already motivated to redeem; the catalogue is a natural commercial opportunity.

Data and analytics. Aggregated, anonymised grid demand and consumer behaviour data has value to energy consultancies, grid operators, and researchers. A reporting API or analytics dashboard tier could sit above the standard subscription for customers who want insight beyond their own tenant data.

Built With

  • aurora-postgresql
  • aws-secrets-manager
  • eia
  • fingrid
  • logical-replication
  • neso
  • next.js
  • openelectricity
  • pgcache
  • row-level-security
  • smard
  • v0
  • vercel
  • web-push
Share this project:

Updates