koala73/worldmonitor · error · ApiError
Service unavailable
Error message
Service unavailable
What it means
submitContact requires Convex connection configuration before it can forward the contact form. If neither CONVEX_SITE_URL (or a derivable CONVEX_URL) nor CONVEX_SERVER_SHARED_SECRET is configured, it refuses to run and throws this 503.
Solutions
- Set CONVEX_SITE_URL (recommended, e.g. https://your-app.convex.site) in the serving environment
- Or set CONVEX_URL with a '.convex.cloud' value so the '.convex.site' fallback derivation works
- Set CONVEX_SERVER_SHARED_SECRET to the same secret configured in the Convex backend
- Redeploy/restart the API after adding the env vars so they are picked up
Example fix
// before # environment CONVEX_URL= // after # environment CONVEX_SITE_URL=https://your-deployment.convex.site CONVEX_SERVER_SHARED_SECRET=<shared-secret>
Defensive patterns
Strategy: validation
Validate before calling
const required = ['CONVEX_SITE_URL', 'CONVEX_SERVER_SHARED_SECRET'] as const;
for (const k of required) if (!process.env[k]) throw new Error(`Missing env var: ${k}`); Type guard
null
Try / catch
try {
await submitContact(form);
} catch (e) {
if (e instanceof ApiError && e.status === 503) {
alertOps('submit-contact misconfigured: Convex env vars missing');
showBanner('Contact form is temporarily unavailable.');
} else throw e;
} Prevention
- Assert all required env vars at service startup, not per-request
- Keep env var definitions in .env.example and CI config checks
- Prefer CONVEX_SITE_URL explicitly over the .convex.cloud -> .convex.site string derivation
When it happens
Trigger: Calling the contact endpoint on an environment where process.env.CONVEX_SITE_URL and process.env.CONVEX_URL are unset, or where CONVEX_SERVER_SHARED_SECRET is missing.
Common situations: Deployed to a preview/production environment whose env vars were never set; Convex URL lacks the expected '.convex.cloud' suffix so the .convex.site derivation yields an empty/invalid value; secrets not synced to the hosting platform.
Understand the failure class
Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.
- HTTP status errors: handling 4xx and 5xx responses — how to handle 4xx and 5xx responses properly.
Related errors
- INVALID_ORIGIN
- Convex embed key validation unavailable: missing-config
- PRO_REQUIRED
- INCOMPATIBLE_DELIVERY
- COUNTRIES_LIMIT_EXCEEDED
AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15).
Data as JSON: /api/errors/241d03ac986b0d00.
Report an issue: GitHub.
Appendix: source
Thrown at server/worldmonitor/leads/v1/submit-contact.ts:154
}
if (!organization || organization.trim().length === 0) {
throw new ValidationError([{ field: 'organization', description: 'Company is required' }]);
}
if (!phone || !PHONE_RE.test(phone.trim())) {
throw new ValidationError([{ field: 'phone', description: 'Valid phone number is required' }]);
}
const safeName = name.slice(0, MAX_FIELD);
const safeOrg = organization.slice(0, MAX_FIELD);
const safePhone = phone.trim().slice(0, 30);
const safeMsg = message ? message.slice(0, MAX_MESSAGE) : undefined;
const safeSource = source ? source.slice(0, 100) : 'enterprise-contact';
const convexUrl = (process.env.CONVEX_SITE_URL
?? (process.env.CONVEX_URL ?? '').replace('.convex.cloud', '.convex.site')).replace(/\/$/, '');
const secret = process.env.CONVEX_SERVER_SHARED_SECRET;
if (!convexUrl || !secret) {
throw new ApiError(503, 'Service unavailable', '');
}
let response: Response;
try {
response = await fetch(`${convexUrl}/leads/submit-contact`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'User-Agent': 'worldmonitor-leads/1.0',
'x-convex-shared-secret': secret,
},
body: JSON.stringify({
name: safeName,
email: email.trim(),
organization: safeOrg,
phone: safePhone,
message: safeMsg,
source: safeSource,View on GitHub (pinned to 7d06c8633d)