Inspiration
Most renters who get rejected aren't risky. They're invisible.
A thin credit file, a gap between jobs, a sublet that left no paper trail. These situations don't mean someone is unreliable. They just mean the standard screening system has nothing to work with. We wanted to build something that separates "no evidence" from "bad evidence," and gives renters a way to show what they actually have.
That's Credora: a structured reliability profile built from real, consented evidence, not a single opaque score.
What it does
Credora lets renters fill out one structured intake form covering identity, income, housing history, financial context, and supporting documents. Each category is scored independently on a 0 - 100 scale using a deterministic rubric, so the result is explainable by design.
The applicant sees their results first. They decide when (and whether) to publish. When they do, Credora creates an immutable public snapshot with a SHA-256 payload hash and version chaining, so reviewers can always trust what they're looking at, and applicants can't quietly revise history.
Missing evidence is flagged separately from actual risk. "No bank statements provided" is not the same as "three missed payments." The output reflects that distinction.
How we built it
Frontend and routing: Next.js 16 App Router with a mix of server and client components. Server components fetch and render profile data directly; client components handle interactive form state and transitions.
Scoring engine: A fully deterministic rubric across six weighted categories: identity confidence (15%), housing history (25%), income stability (20%), payment consistency (20%), financial stability (10%), and completeness/recency (10%). The evaluator is behind a clean interface that supports three modes: a local mock for development, a rubric-only deterministic mode, and a remote HUD.ai inference endpoint via the OpenAI SDK for richer signal extraction.
Authentication: NextAuth v5 (beta) with a Credentials provider. The middleware runs on the Edge using a split config that keeps bcryptjs out of the Edge runtime. Role is embedded in the JWT so the UI adapts: applicants see no reviewer surfaces, reviewers see no applicant-only links.
Storage: File-based JSON at .data/credora-db.json for the hackathon. The store layer is abstracted behind typed functions so swapping in Postgres later is a single-file change.
Blockchain readiness: Every published snapshot gets a SHA-256 payload hash. Snapshots chain to their predecessor via a previousSnapshotHash field. No raw applicant data goes on-chain. The anchoring hook lives in src/lib/attestations.ts, ready to plug into a real signing layer.
Challenges we ran into
Edge runtime and bcryptjs: NextAuth's middleware needs to run on the Edge, but bcryptjs uses Node.js crypto APIs that aren't available there. The fix was splitting auth into two files: auth.config.ts (edge-safe, no providers, used by middleware) and auth.ts (full config with bcryptjs, used everywhere else). It wasn't obvious from the docs, and it ate more time than expected.
Evaluator boundary design: We wanted the scoring engine to be swappable without touching call sites. Getting the abstraction right, especially around partial failures where HUD.ai is unavailable and the rubric needs to take over silently, required more iteration than anticipated.
Immutability semantics: Deciding what "immutable" actually means in a JSON file store (not a database with write-once constraints) meant adding explicit guards at the API layer to prevent post-publish mutations. Conceptually simple, but the edge cases around versioning and linked snapshots required careful thought.
Accomplishments we're proud of
The scoring output genuinely distinguishes missing evidence from actual risk signals. That separation is rare in screening tools and we think it matters. The publish flow (private results first, one-way public commit, version chaining) works end-to-end, including the hash chain. The evaluator abstraction is clean enough that switching from the local mock to a real inference call requires zero changes outside src/lib/evaluator.ts.
What we learned
Role-based UI is harder than role-based access. Locking a route is one line of middleware. Making sure every nav link, every back button, and every CTA reflects the current user's role without prop-drilling session state everywhere is an architectural decision that compounds across the whole app.
We also learned that "deterministic" is a design constraint worth imposing early. Once we committed to it (every score traceable to a rubric rule, no hidden weights) the output became much easier to explain and much easier to test.
What's next for Credora
- Real storage: Postgres with row-level security so applicant data is properly isolated.
- Document parsing: Structured extraction from uploaded pay stubs and bank statements instead of filename-only placeholders.
- Real identity verification: Integration with a provider like Stripe Identity instead of self-assertion.
- On-chain anchoring: Publishing snapshot hashes to a public ledger so the immutability guarantee is external, not just enforced by our API.
- Applicant-controlled sharing: Time-limited, revocable access links so applicants can share their profile with specific landlords without making it fully public.
Built With
- hud.ai
- next.js
- openai-sdk
- plaid
- postgresql
- prisma
- react
- typescript
Log in or sign up for Devpost to join the conversation.