Fixing Silent AgentOS API Authentication Failures in v0.1.50
Fixing Silent AgentOS API Authentication Failures in v0.1.50
Release: v0.1.50 · PR: #33 · Severity: Critical
Background
The myProp portal communicates with the AgentOS/LetMC platform through a set of server-side API calls constructed by a shared utility function, buildLetmcUrl(), located in src/lib/letmc/config.ts. This function accepts a path fragment and returns a fully-qualified URL ready for use in fetch requests.
The AgentOS API authenticates all requests via a api_key query parameter appended to the URL — for example:
GET https://live-api.letmc.com/acme/payments/requests/person-123?api_key=MY_KEY
This pattern was established in the original Lambda BFF (Express handler) that the Next.js app replaced.
The Problem
When buildLetmcUrl() was ported to the Next.js app, the api_key query parameter was never included. The function resolved the API key from the environment correctly, but then discarded it — the returned URL contained only the base URL and path:
// Before fix
export function buildLetmcUrl(path: string, config?: LetmcConfig): string {
const resolved = config ?? getLetmcConfig();
const cleanPath = path.startsWith("/") ? path : `/${path}`;
return `${resolved.apiBaseUrl}${cleanPath}`;
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// api_key was never appended
}
Because buildLetmcUrl() is the single entry point for constructing every AgentOS API URL, this omission affected all endpoints simultaneously — not just one route. Every request to accounts, landlords, tenants, contractors, affiliates, and payments was sent without authentication. The AgentOS API silently rejected or returned empty responses, meaning the portal appeared to function but served no real data.
The Fix
The fix appends ?api_key={KEY} (URL-encoded) to every URL produced by buildLetmcUrl(). A ? separator is used when no existing query string is present; & is used if one already exists, preserving correctness for any future paths that include their own query parameters.
// After fix
export function buildLetmcUrl(path: string, config?: LetmcConfig): string {
const resolved = config ?? getLetmcConfig();
const cleanPath = path.startsWith("/") ? path : `/${path}`;
const baseUrl = `${resolved.apiBaseUrl}${cleanPath}`;
const separator = baseUrl.includes("?") ? "&" : "?";
return `${baseUrl}${separator}api_key=${encodeURIComponent(resolved.apiKey)}`;
}
URL encoding
The API key is passed through encodeURIComponent() before being appended. This ensures that any special characters in the key (spaces, &, =, etc.) are safely percent-encoded and cannot corrupt the query string.
Scope of Impact
All AgentOS API endpoints are affected by this fix. Because every request is routed through buildLetmcUrl(), a single-line change restores authentication across the entire integration:
| Endpoint category | Was broken? | Fixed? |
|---|---|---|
| Accounts | ✅ Yes | ✅ Yes |
| Landlords | ✅ Yes | ✅ Yes |
| Tenants | ✅ Yes | ✅ Yes |
| Contractors | ✅ Yes | ✅ Yes |
| Affiliates | ✅ Yes | ✅ Yes |
| Payments / requests | ✅ Yes | ✅ Yes |
Test Coverage
The following test cases were added or updated in this release:
buildLetmcUrlwith leading slash — asserts?api_key=keyis appendedbuildLetmcUrlwithout leading slash — same assertion- Custom base URL — verifies
api_keyis appended regardless of base URL override - Pre-resolved config object — verifies
api_keyfrom config is used, not the environment - URL-encoding of special characters — key
key with spaces&special=charsbecomeskey%20with%20spaces%26special%3Dchars - Payment-requests path — explicit regression test asserting the URL matches the original Lambda BFF format exactly
- All HTTP methods (GET, POST, PUT, PATCH, DELETE) — each client method test updated to assert
?api_key=is present in the outbound URL
Configuration
No configuration changes are required. The API key continues to be sourced from the LETMC_API_KEY environment variable. This fix only changes how that value is transmitted — it is now correctly included in every outbound request URL.
If you are running the portal locally, ensure LETMC_API_KEY is set in your .env.local file. Without it, buildLetmcUrl() will throw at config resolution time before any URL is constructed.