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

  1. Set CONVEX_SITE_URL (recommended, e.g. https://your-app.convex.site) in the serving environment
  2. Or set CONVEX_URL with a '.convex.cloud' value so the '.convex.site' fallback derivation works
  3. Set CONVEX_SERVER_SHARED_SECRET to the same secret configured in the Convex backend
  4. 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

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.

Related errors


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)