nyxcore-systems
11 min read

The IPCHA Launch: Navigating Microservices, Middleware, and Modern Web Dev to Deliver a Rentable API

Join us on a deep dive into the recent sprint where we transformed a complex protocol into a production-ready, rentable API service, complete with a FastAPI sidecar, Next.js frontend, and critical lessons learned along the way.

FastAPINext.jsPrismaTypeScriptDockerMicroservicesAPI DevelopmentLessons LearnedDevOpsDatabase

It's 12:15 UTC, March 15th, 2026, and the feeling of a major milestone achieved is palpable. The last two development sessions culminated in the successful deployment of our new IPCHA protocol implementation and its accompanying rentable API service. PR #133 is merged to main, all 14 end-to-end integration tests are passing, and the system is live on production. Time to capture the journey, the triumphs, and especially, the hard-won lessons.

Our core mission was ambitious: take the newly developed IPCHA protocol—a suite of 15 Python modules handling complex arbitration and claims logic—and expose it as a robust, rentable API service. This wasn't just about wrapping Python functions; it involved building a full-fledged ecosystem: a FastAPI sidecar, a comprehensive REST proxy, a secure token service, a user dashboard, and even CLI integration.

From Protocol to Production: The IPCHA API Architecture

At the heart of this deployment lies a microservices approach, leveraging the strengths of Python for computational logic and TypeScript/Next.js for the API gateway, UI, and backend services.

1. The IPCHA Protocol Engine (FastAPI Sidecar)

The IPCHA protocol itself is a beast of Python logic, spread across various modules for arbitration, claims, and scoring. To expose this, we built a FastAPI sidecar (ipcha/api.py). This service:

  • Provides 10 distinct endpoints, covering sync and async operations.
  • Is containerized with its own ipcha/Dockerfile, ready for Docker Compose integration.
  • Handles the heavy lifting of the IPCHA computations, acting as a dedicated, scalable worker.

2. The API Gateway & Backend Services (Next.js/Node.js)

Our main application, built with Next.js, serves as the API gateway and orchestrator. This layer is responsible for authentication, authorization, rate limiting, and proxying requests to the FastAPI sidecar.

  • Prisma Schema & RLS: We extended our database schema with IpchaApiToken, IpchaUsageLog, and IpchaJob models. Crucially, we implemented Row-Level Security (RLS) policies (prisma/rls.sql) to ensure strict data isolation between tenants.
  • Token Service: A dedicated ipcha-token-service.ts generates secure nyx_ip_ API tokens, hashed with SHA-256, for customer access.
  • Sidecar Client: The ipcha-client.ts abstracts communication with the FastAPI sidecar, using callIpcha() to manage requests.
  • REST Middleware: A critical component, ipcha/middleware.ts, sits in front of our API routes, enforcing:
    • Authentication and Authorization
    • Rate Limiting
    • Daily Quota enforcement
    • Usage Metering
  • REST Proxy: The _proxy.ts and its 11 associated route files mirror the FastAPI endpoints, acting as a pass-through layer, ensuring a consistent API surface for our customers.
  • BYOK Integration: A key feature for enterprise clients, the validate/route.ts handles "Bring Your Own Key" (BYOK). It decrypts tenant-provided API keys and securely passes them to downstream services via the X-LLM-Api-Key header.
  • tRPC Router: For internal and dashboard interactions, we created ipcha.ts tRPC procedures for both customer and admin views.
  • ShellAI CLI API: We even integrated the IPCHA functionality into our src/app/api/v1/cli/ endpoints, providing a powerful command-line interface for power users.

3. The User Interface (Next.js Dashboard)

No rentable service is complete without a dashboard. Our src/app/(dashboard)/dashboard/ipcha/ directory now houses 8 tab components, offering customers insights into their usage, job status, and more, leveraging our ProviderModelPicker for a dynamic experience.

The culmination of these efforts was the successful merge of feat/cli-api into main (as PR #133) at 2026-03-15 11:50 UTC. All CI checks—unit tests, linting, typechecking, and E2E tests—passed with flying colors. The integration test report, docs/reports/2026-03-15-ipcha-api-integration-test.md, proudly declares 14/14 tests passing across scoring, sanitization, arbitration, routing, evaluation, and access control. Full data flow traces are meticulously documented.

Navigating the Minefield: Lessons Learned from the Trenches

Shipping a system of this complexity is never without its challenges. The "pain log" from the session handoff is a treasure trove of practical lessons. Here are some of the critical ones we encountered and how we overcame them.

Lesson 1: Don't Assume Record<string, unknown> for Prisma Json Fields

The Problem: We needed to store arbitrary JSON data in a Prisma Json field. The natural TypeScript instinct is to type it as Record<string, unknown> or simply any.

The Attempt: Directly assigning an object typed as Record<string, unknown> to a Prisma model's Json field.

The Failure: TypeScript threw TS2322, complaining that Record<string, unknown> was not assignable to Prisma.InputJsonValue. Prisma's generated types for JSON fields are surprisingly strict and specific, designed to ensure valid JSON structure.

The Solution: The most robust workaround we found was to explicitly serialize and deserialize the object:

typescript
// For an incoming body that might be Record<string, unknown>
const bodyToStore = JSON.parse(JSON.stringify(body));
// Now bodyToStore is compatible with Prisma.InputJsonValue
await prisma.ipchaJob.create({
  data: {
    // ... other fields
    payload: bodyToStore,
  },
});

This ensures a deep clone and forces TypeScript to re-evaluate the type as a valid JSON structure, satisfying Prisma's stricter requirements.

Takeaway: Prisma's type safety extends deeply, even to Json fields. When dealing with dynamic JSON, be prepared to explicitly manage its type compatibility, often through serialization, to match Prisma.InputJsonValue.

Lesson 2: Beware of Next.js 14's Aggressive Fetch Caching

The Problem: Our IPCHA playground was showing stale data. After making a request to the FastAPI sidecar, subsequent requests in the playground would return the exact same response, even if the underlying sidecar logic had changed or new parameters were sent.

The Attempt: We were using standard fetch() calls to our internal API routes, which then proxied to the FastAPI sidecar.

The Failure: Next.js 14, by default, aggressively caches fetch() results, especially for data fetching within React Server Components or server-side functions. This caching behavior is powerful for performance but disastrous for highly dynamic, real-time data like an interactive playground.

The Solution: We explicitly disabled caching for these dynamic requests by adding cache: "no-store" to our fetch options in ipcha-client.ts:

typescript
async function callIpcha<T>(endpoint: string, data: any): Promise<T> {
  const response = await fetch(`${process.env.IPCHA_SIDECAR_URL}/${endpoint}`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data),
    cache: "no-store", // <--- THE CRITICAL FIX
  });
  if (!response.ok) {
    throw new Error(`IPCHA API error: ${response.statusText}`);
  }
  return response.json();
}

Takeaway: Always be mindful of caching strategies in modern web frameworks like Next.js. For dynamic, real-time, or frequently changing data, cache: "no-store" is your friend. Assume caching by default and opt-out when necessary.

Lesson 3: Fine-tuning .dockerignore for Specific Inclusions

The Problem: When building our Docker image, essential evaluation test files, crucial for integration and internal validation, were missing.

The Attempt: Our .dockerignore file had a broad rule: tests/, intended to exclude all test-related files from the production image to keep it lean.

The Failure: The tests/ rule was too aggressive, excluding tests/evaluation/ which contained files needed at runtime for specific validations.

The Solution: We added an exception rule to .dockerignore:

# Exclude all tests by default
tests/

# But include specific evaluation tests
!tests/evaluation

This tells Docker to ignore tests/ unless it's tests/evaluation/.

Takeaway: .dockerignore (and similar ignore files) process rules in order. Specific exceptions ! can override broader exclusions. Be precise with your ignore rules to ensure your build context contains exactly what's needed, and nothing more.

Lesson 4: Explicit Type Casting for uuid in Raw SQL Queries with Prisma

The Problem: We were using Prisma's $queryRaw to execute custom SQL, specifically filtering by a tenantId column of type uuid. The query failed with a operator does not exist: uuid = text error.

The Attempt:

typescript
// This was the problematic part
const result = await prisma.$queryRaw`SELECT * FROM public."Workflow" w WHERE w."tenantId" = ${ctx.tenantId}`;

Here, ctx.tenantId is a string in TypeScript, representing a UUID.

The Failure: When Prisma $queryRaw interpolates a TypeScript string into SQL, it treats it as a text type literal by default. The PostgreSQL database then tried to compare a uuid column with a text literal, which is an invalid operation without an explicit cast.

The Solution: We explicitly cast the interpolated string to uuid within the SQL query:

typescript
const result = await prisma.$queryRaw`SELECT * FROM public."Workflow" w WHERE w."tenantId" = ${ctx.tenantId}::uuid`;

Adding ::uuid tells PostgreSQL to treat the string literal as a uuid type for the comparison.

Takeaway: Even with an ORM like Prisma, when dropping down to raw SQL with $queryRaw, you must be acutely aware of database-specific type casting, especially for non-standard types like uuid or jsonb. Don't assume the database will implicitly convert types as intuitively as your programming language might.

Lesson 5: Handle Union Return Types in tRPC Asynchronously

The Problem: In our tRPC systemHealth endpoint, we wanted to fetch metrics from the IPCHA sidecar and provide a default if the call failed. TypeScript complained about accessing fields on the return type.

The Attempt:

typescript
// Simplified example
const metrics = await callIpcha<{ total: number }>('metrics', {})
  .catch(() => ({ total: 0 })); // This creates a union type
// console.log(metrics.total); // TS error: 'total' does not exist on type '{ total: number; } | { total: 0; }'

The .catch() block returned an object { total: 0 }, which, while structurally similar, created a union type ({ total: number; } | { total: 0; }) with the success branch. TypeScript couldn't guarantee that total would be present or of a specific numeric type across all union members without explicit narrowing.

The Failure: TypeScript correctly identified that metrics could be either the successful response or the fallback, and it couldn't infer that total would always be a number across both.

The Solution: We separated the success and error handling more explicitly, ensuring the final return type was consistent, or explicitly typed the catch return to match the success type. The cleanest approach was to declare the expected type upfront and handle the .then and .catch separately to maintain type consistency:

typescript
const defaultMetrics = { total: 0, /* ...other metrics */ };
const metrics = await callIpcha<typeof defaultMetrics>('metrics', {})
  .then(r => r) // Ensure success path returns the expected type
  .catch(() => defaultMetrics); // Error path returns the same structure
// Now metrics is always of type typeof defaultMetrics
console.log(metrics.total); // No TS error

Takeaway: When dealing with asynchronous operations and error handling that might return different values, be explicit about your return types. Ensure that both success and failure paths resolve to a consistent type to avoid TypeScript's union type inference issues and simplify subsequent property access.

What's Next on the Horizon

While the core IPCHA API is live, our work isn't done. Immediate next steps include:

  1. Docker Compose Environment: Refactor the ipcha service in docker-compose to use env_file: .env.production for cleaner environment variable management.
  2. Idempotent RLS: Make our RLS SQL scripts idempotent using DROP POLICY IF EXISTS ...; CREATE POLICY ... for safer migrations.
  3. API Documentation: Generate and expose Swagger/OpenAPI docs for the FastAPI sidecar (it's auto-generated at /docs).
  4. Workflow Integration: Wire IPCHA scoring directly into our nyxCore workflow engine as a new step type.
  5. Dashboard Enhancements: Add usage dashboard charts (e.g., 30-day trends) to complement the current recent calls view.
  6. External Documentation: Develop comprehensive pricing and API documentation for external customers.

Wrapping Up

The journey from a complex protocol specification to a fully deployed, rentable API service has been challenging but incredibly rewarding. Seeing PR #133 merged and all tests passing is a testament to the team's dedication and the robust architecture we've built. The lessons learned along the way, particularly around Next.js caching, Prisma's type strictness, and raw SQL nuances, are invaluable and will undoubtedly inform our future development.

This session handoff, framed as a "Letter to Myself," proved crucial for capturing the detailed context, the successes, and especially the hard-won insights. It's a practice I highly recommend for any developer tackling complex systems.

json
{
  "thingsDone": [
    "Implemented IPCHA protocol (15 Python modules)",
    "Built FastAPI sidecar with 10 endpoints",
    "Integrated Docker Compose for sidecar",
    "Designed Prisma schema for IpchaApiToken, IpchaUsageLog, IpchaJob with RLS",
    "Developed secure token service (nyx_ip_ tokens, SHA-256)",
    "Created sidecar client with cache: no-store",
    "Implemented REST middleware for auth, rate limiting, quota, metering",
    "Built REST proxy for all IPCHA endpoints",
    "Integrated BYOK (Bring Your Own Key) functionality",
    "Developed tRPC router for customer and admin IPCHA access",
    "Built Next.js dashboard with 8 tab components",
    "Integrated IPCHA into ShellAI CLI API with 8 endpoints",
    "Merged PR #133 to main branch",
    "Achieved 14/14 passing integration tests",
    "Documented full data flow traces in test report"
  ],
  "pains": [
    "Prisma InputJsonValue strictness vs. Record<string, unknown>",
    "Next.js 14 default fetch caching behavior for dynamic data",
    ".dockerignore excluding necessary subdirectories with broad rules",
    "PostgreSQL 'uuid = text' operator error in Prisma $queryRaw",
    "TypeScript union type issues from tRPC async .catch() blocks"
  ],
  "successes": [
    "Full end-to-end deployment of a complex microservice architecture",
    "Robust authentication, authorization, and metering system",
    "Seamless integration between Python (FastAPI) and TypeScript (Next.js/Node.js)",
    "Comprehensive test coverage validating all data flows",
    "Successful resolution of critical technical challenges through targeted workarounds",
    "Delivering a production-ready, rentable API service"
  ],
  "techStack": [
    "Python",
    "FastAPI",
    "Uvicorn",
    "TypeScript",
    "Next.js 14",
    "Prisma",
    "PostgreSQL",
    "Docker",
    "Docker Compose",
    "tRPC",
    "Node.js",
    "SHA-256",
    "REST API"
  ]
}
    The IPCHA Launch: Navigating Microservices, Middleware, and Modern Web Dev to Deliver a Rentable API — nyxcore-systems