Venture Platform
Turning a fragmented fundraising process into a controlled MVP.
How I took a loosely defined private-capital coordination problem through product definition, workflow modelling, architecture, quality assurance and cloud deployment to a working staging MVP, and what building it taught me about the real problem.
Private fundraising is often described as a matching problem: put the right business in front of the right investor. Once I looked closely at the work involved, it turned out to be mostly a coordination problem.
A founder has to present ownership, financial position and funding requirements coherently. An investor needs enough reliable information to decide whether an opportunity deserves a closer look. Someone in between has to check who each party is, decide who may see what, track which confidentiality terms apply, and follow up on whatever is still unclear. In practice that work is spread across email threads, spreadsheets, web forms and shared folders.
Spread out like that, simple questions become hard to answer:
- Is this business ready to be shown to investors?
- Has this investor completed the required checks, and does the opportunity fit their mandate?
- Who is allowed to see this opportunity, and which documents have they actually received?
- Which version of the confidentiality terms did they accept?
- What evidence is still outstanding before anything can move forward?
Venture Platform was an independent product exploration focused on Kenyan private-capital workflows. I led it from concept through product definition, architecture, implementation, quality assurance and deployment to a cloud-hosted staging environment.
Three participants, three different jobs
I started by separating the participants. Each one needs something different from the same process.
Founders and issuer representatives need a structured way to present their company and funding requirements, and to understand what reviewers still need from them.
Investors and their representatives need to describe what they invest in, receive opportunities relevant to that mandate, and review approved information without wading through everything else.
Platform operators and reviewers carry the most responsibility and get the least tooling. They assess participants, request clarification, decide which matches are worth making, and control what each person can see.
Framing it this way shifted my attention away from the feature that’s easy to picture, a marketplace of deals. The underlying need was a controlled, traceable path from “we don’t know this party yet” to “this party has seen exactly what it should, under the right terms, and has signalled interest.”
Deciding what the product would not do
The first real product decision was where to draw the boundary.
The MVP supports private introductions and nonbinding expressions of interest. It does not process payments, hold funds or assets, execute securities transactions, or present an expression of interest as a completed investment. The public website explains the service but never lists live opportunities. Opportunities stay private and are shown only by invitation. Anything that happens after an expression of interest moves outside the platform.
I also deferred automated investment decisions, recommendation algorithms, recurring portfolio reporting and integrations with external ERP systems. Each of these was technically feasible. Each would also have pulled the product into legal, regulatory or trust questions that a staging MVP had no business answering, or into features whose value hadn’t been established yet.
I treat these boundaries as product design, not unfinished work. They kept the MVP focused on coordination, review and controlled disclosure. That is where software can clearly help without pretending to automate judgements that belong to people and institutions.
From workflow to working software
The work didn’t follow a strict linear plan, but it moved through a recognisable sequence.
I began by defining each participant’s jobs to be done and mapping the end-to-end journey from first contact to expression of interest. On that map I marked the sensitive transitions: points where approving someone, inviting them or granting access changes who can see confidential information. Those transitions became the backbone of the domain model.
Next I picked the smallest journey that would still be useful: an operator brings in a participant, reviews them, makes a match, and lets an approved investor into a deal room under current confidentiality terms. I translated that journey into domain objects and explicit state transitions. Then I built the issuer, investor and operator experiences in stages and deployed each to a working staging environment rather than holding everything back for one large release.
Building it changed the model. The clearest example was approval. Early on it was natural to think of a participant as simply “approved.” As the workflow took shape, it became obvious that identity verification, issuer assessment and investor-mandate review answer different questions. I split them into separate stages. That change rippled into permissions, tests and data migration, which I cover below. The architecture answered to the product model, and the product model kept being corrected by what implementation revealed.
flowchart TD
INV[Operator invites participant] --> ACT[Participant activates account]
ACT --> STAGES
subgraph STAGES["Separate review stages"]
ID[Identity verification]
ISS[Issuer assessment]
MAN[Investor mandate review]
end
STAGES --> DEC{Operator decision}
DEC -->|Request information| RFI[Participant revises and resubmits]
RFI --> STAGES
DEC -->|Reject| REJ[Rejected]
DEC -->|Approve| MATCH[Operator assesses match]
MATCH --> PINV[Private invitation]
PINV --> TEASER[Anonymous teaser]
TEASER --> NDA[Accept current confidentiality terms]
NDA --> GRANT[Access grant]
GRANT --> ROOM[Deal-room review]
GRANT -.->|Revoked| REV[Access removed]
ROOM --> EOI[Nonbinding expression of interest]
EOI --> OUT[Handoff outside the platform]
Architecture shaped by the workflow
The backend is a modular Django application using Django REST Framework. It owns authentication, authorisation, workflow transitions, business rules and all persistent data. The frontend is a separate Next.js application written in React and TypeScript, serving both the public site and the authenticated issuer, investor and operator experiences.
The frontend has no authority over confidential data. Every decision about who can see what is made in the API, so a bug in the user interface can make a page look wrong, but it can’t expose a document.
flowchart LR
PUB[Public visitor] --> WEB
USR[Issuer, investor, operator] --> WEB
subgraph CLOUD["Google Cloud staging"]
WEB[Next.js web app on Cloud Run]
subgraph APIBOX["Django API on Cloud Run"]
AUTHZ{{Authentication, authorisation, workflow rules}}
MODS[Domain modules]
AUTHZ --> MODS
end
WEB -->|API calls| AUTHZ
MODS --> DB[(PostgreSQL on Cloud SQL)]
MODS -->|Check access, then sign| GCS[(Private object storage)]
SM[Secret Manager] --> APIBOX
APIBOX --> LOG[Cloud Logging]
MIG[Migration job on Cloud Run] --> DB
end
USR -.->|Short-lived signed URL| GCS
GHA[GitHub Actions] -->|Workload Identity Federation| AR[Artifact Registry]
AR --> WEB
AR --> APIBOX
GHA -->|Run before deploy| MIG
PostgreSQL runs on Cloud SQL, and private documents live in Cloud Storage. Containerised API and web services run on Cloud Run, with images stored in Artifact Registry and application secrets in Secret Manager. GitHub Actions runs the checks and handles deployment. It builds the containers, runs database migrations as a Cloud Run job, deploys the services and confirms they pass readiness checks. Workload Identity Federation lets the pipeline authenticate to Google Cloud without a long-lived service-account key. The services emit structured logs and expose health endpoints, so the deployment can be checked without guesswork.
I chose a modular monolith on purpose. The MVP needed one authoritative permission model, transactional integrity across workflow changes, and the ability to reshape the domain quickly as I learned more. Independently deployed services would have spread the rules this product depends on across network boundaries before I even knew where those boundaries should sit. Domain modules gave me separation in the code, while one application and one database kept enforcement consistent.
The difficult decisions
Permissions depend on context, not just role. Being an approved investor shouldn’t mean seeing every deal. Access to a particular opportunity depends on several things at once: whether the investor is approved, whether they’ve been invited, whether they’ve accepted the current confidentiality agreement, whether they hold an active access grant, and how each document is classified. I modelled the investor’s relationship to a specific opportunity explicitly. Invitations, confidentiality acceptances and access grants are persistent domain objects, not conditional checks scattered through the views. The simplified sketch below shows the shape of that rule, not the literal implementation:
def can_view_document(investor, deal, document) -> bool:
return (
investor.identity_approved
and investor.mandate_approved
and Invitation.objects.active(investor, deal).exists()
and ConfidentialityAcceptance.objects.current(investor, deal).exists()
and AccessGrant.objects.active(investor, deal).exists()
and document.is_available_to_investors()
)
Modelling access this way makes the rule something I can explain to an operator, test directly and audit later.
Different approvals represent different facts. Identity verification, issuer assessment and mandate review each get their own states, evidence and decisions. A revised investor mandate can be reviewed again without repeating identity checks. A new version of the confidentiality agreement doesn’t inherit acceptance of the old one. Reviewers can send an application back with specific requests for information, and the applicant can edit and resubmit without losing the review history.
Confidentiality goes beyond files. A deal title, a notification, an API response or a historical record can reveal which business is raising money even when its documents are locked down. I gave anonymous teasers and authenticated responses the same scrutiny as the deal room itself.
Signed URLs are bearer credentials. Once the backend authorises a document request, it issues a short-lived signed storage URL. Anyone holding that URL can use it until it expires, so the short expiry is part of the security boundary.
Audit history should not become a second copy of the secrets. Critical changes record the actor, action, target and time. Audit payloads use identifiers and redacted summaries rather than copying confidential documents or financial submissions. Otherwise the audit log would become the easiest place to leak from.
Changing rules have to be safe for existing records. When I split approval into separate stages, existing approvals could not simply be treated as satisfying the new rules. I treated the schema migration and the business-state migration as separate problems. Adding columns is mechanical. Deciding what an old “approved” actually means under the new model is a product decision.
Software supports review but does not replace it. Issuer submissions are checked for completeness and laid out for structured review, with checklists and analyst notes to record concerns and recommendations. The platform does not claim to verify that financial figures are accurate. Eligibility filters and investor preferences help with matching, but operators make the invitation decisions and record their reasoning.
Quality assurance as product validation
For this product, “the tests pass” was never the bar. Before the staging MVP could be considered credible, I needed evidence of specific behaviours:
- unauthorised users can’t reach private opportunities or documents;
- state transitions follow the staged-review rules;
- earlier approvals don’t carry over into contexts where they shouldn’t apply;
- confidentiality versions and access grants are enforced;
- requests for information can be returned and resubmitted without losing history;
- database migrations run repeatably and safely;
- builds are reproducible, and deployed services actually come up healthy.
Each of those maps to a concrete practice. Pytest runs against a real PostgreSQL database in CI, because permission and workflow behaviour often depends on constraints, transactions and query semantics that a substitute database would hide. The test suite concentrates on permissions and workflow transitions, since those are where a defect would have serious consequences. Ruff handles static analysis on the backend. ESLint and TypeScript checks cover the frontend, and every change produces a production frontend build and container images, so “it builds” refers to the artifact that actually ships.
On the delivery side, migrations run as their own Cloud Run job before new code is deployed, which makes schema changes an explicit, observable step. Readiness checks confirm the services are working after deployment, and structured logs make their behaviour inspectable afterwards.
I see this as validating the product rather than just the code. The tests encode the workflow and privacy boundaries I designed, so a passing pipeline is evidence that the system behaves the way the product model says it should.
Deployment is not operational readiness
A successful deployment to staging shows that the system builds, migrates, starts and serves its workflows. It doesn’t show that backups can be restored, that permissions have been rehearsed with real operators, or that the service is ready to hold real participants’ financial and identity information.
Throughout development I used fictional participants and synthetic documents. Bringing in real participants would require further legal and regulatory review, recovery testing, security assessment, data-governance decisions and operating procedures. I treated those as explicit readiness gates, not afterthoughts.
Working with AI assistance
I used AI tools extensively for implementation, test generation, documentation and interface iteration. They sped up writing code. They didn’t change who was responsible for the result.
I supplied the context and requirements, decided the scope, chose the architecture, reviewed what was generated, weighed the trade-offs, defined the tests and acceptance criteria, and operated the deployed system. If anything, faster generation raised the bar on precise acceptance criteria and meaningful tests, because producing plausible code is cheap and verifying correct behaviour is not.
Outcome
I took a private-capital matching concept through product definition, architecture, implementation, quality assurance and cloud deployment, producing a working staging MVP for controlled evaluation.
The staging MVP includes a public landing page and authenticated workflows for issuers, investors and operators. It supports staged reviews, requests for information and resubmission, operator-curated private invitations, versioned confidentiality acceptance, controlled document access through short-lived signed URLs, nonbinding expressions of interest and an audit history.
It is not a commercial launch, and I make no claims of regulatory approval, customer adoption, completed investments or revenue.
What building it taught me
Workflow modelling is architecture. Much of the complexity lay in deciding what could happen next, who could trigger it, and which earlier decisions still held after something changed.
Permissions belong in the domain model. Explicit invitations, confidentiality acceptances and access grants were clearer, safer and easier to test than broad role checks.
A modular monolith suits product discovery. Strong internal boundaries with one transactional store let me reshape the model repeatedly without paying distributed-systems costs early.
Deployment is narrower than readiness. A green pipeline is necessary but far from sufficient for handling real participant data.
AI assistance raises the value of clear requirements. The more code a tool can produce, the more the work shifts to specifying, reviewing and verifying it.
A complete workflow is not automatically a compelling product. This was the most useful lesson. With the coordination problem handled, the more valuable question became clearer: how can a platform help businesses become more investment-ready, and help investors make better-informed decisions?
That question suggests several hypotheses worth validating before expanding the platform: investment-readiness guidance for founders, concise decision briefs for investors, trustworthy recurring business reporting, and possibly an optional integration with accounting systems such as Odoo as a reporting source. None of these are commitments. Each needs evidence from real participants that it solves a problem worth solving.