503 and service-unavailable errors
Why Metric Vault deliberately returns 503 rather than guessing, which gates fail closed, and the internal-secret case that stops AI article generation.
Last updated 2026-08-06
Summary#
A 503 from Metric Vault almost never means the platform is down. It means a safety gate refused to guess. Two designs produce it: a check that could not complete and therefore blocked the action rather than allowing it unmetered, and a dependency that is not configured on the environment being used. Both are deliberate. This page separates the two, lists every 503 the platform can emit, and covers the single configuration mistake that most often produces one: a missing internal secret.
Symptoms#
- A tool refuses with
Usage check temporarily unavailable, please retry.orPlan check temporarily unavailable, please retry. - A feature refuses with a message ending "is not configured", "is not set up yet" or "not configured".
- AI article generation starts, sits at queued, and eventually reports
Job not found or expired. Please re-submit. - Background jobs stop advancing: the Job Console shows jobs permanently
Due nowand last-run timestamps stop moving. - Plans stop reconciling with the payment provider.
- A specific tool refuses for everyone with
This tool is temporarily unavailable. Please try again shortly. - A browser network panel shows HTTP 503 on a request that used to succeed.
Diagnosis#
Step 1. Decide which of the three kinds of 503 you have.
| Kind | Signature | Meaning |
|---|---|---|
| Fail-closed check | The message says "temporarily unavailable, please retry" | A lookup the platform needs before allowing an action did not complete. Rather than let the action through unmetered, the platform blocks it. |
| Not configured | The message names the thing that is missing | A dependency, credential or binding is absent on this environment. |
| Deliberate switch | This tool is temporarily unavailable. Please try again shortly. | An owner turned that individual tool off in the admin console. |
The first is transient and self-clears. The second and third persist until somebody changes configuration.
Step 2. Check whether it affects one account or everyone.
Open Admin console → System Health and set the range to the last 24 hours. Look at the Error rate tile and the Tool health table. A single customer's problem does not move a platform-wide error rate; a configuration problem does, and it will show a tool as down or degraded. Grouped issues underneath gives you the repeated message and a sample.
Step 3. Rule out a deliberate switch before anything else.
Open Admin console → Config. A row whose key begins tool_off: disables that tool for every customer and makes it answer This tool is temporarily unavailable. Please try again shortly. Deleting the row restores it immediately. See Runtime configuration.
Step 4. If jobs or article generation are involved, check the internal secret.
This is the highest-value check on the page and is covered in its own section below.
The internal-secret case#
Some endpoints exist only for the platform to call itself: the job runner, the billing reconciliation, and the second half of the AI article writer. They are protected by a shared secret, MV_INTERNAL_SECRET, and that check fails closed. When the secret is not set, those endpoints answer:
Internal jobs are not configured (MV_INTERNAL_SECRET is not set).with HTTP 503. When the secret is set but does not match, they answer HTTP 401 with Unauthorized internal call.
Why AI article generation is the visible casualty
A full-length article takes far longer than a single request can safely stay open, so the writer splits the work in two. The first call records a queued job and returns a job id immediately. It then starts a second, separate call that does the actual generation, authenticating with the internal secret.
If the secret is missing, that second call is refused with 503. There is no fallback: the platform will not process the work with an unauthenticated call. The consequence for the customer is not an error message but a silence. The job stays at queued, never advances through its phases, and is removed after one hour. Polling it then returns Job not found or expired. Please re-submit.
Why background jobs stop
Scheduled reports, rank alerts, the competitor monitor and AI-visibility tracking are not driven by a clock inside the application. An external scheduler calls a protected endpoint roughly every 15 minutes, and that endpoint uses the same secret. If it is missing or mismatched, every call is rejected and nothing due is ever processed. Symptom: jobs sit at Due now in the Job Console and last-run timestamps freeze. See Background jobs console.
Where the secret must be set
The same value must exist in three places. A mismatch in any one of them produces 401 rather than 503, and the symptoms look identical from the customer's side.
| Location | Used by |
|---|---|
| The Cloudflare Pages project's environment variables | The application itself, so it can verify incoming internal calls and authenticate its own outgoing one |
| The scheduler worker's secret | The primary scheduler that calls the job endpoint every 15 minutes |
| The repository secrets | The hourly backup scheduler |
After setting or changing it, redeploy so the application picks up the new value, then confirm from the Job Console that a job's last-run time advances. See Environment variables and secrets and Maintenance and health.
The fail-closed gates#
These block an action because a prerequisite lookup failed. They are transient. Retrying after a moment is the correct first response.
| Message | Code | Gate | Why it fails closed |
|---|---|---|---|
Usage check temporarily unavailable, please retry. | quota_check_failed | Monthly credit check | Allowing the run would spend an unmeasured amount of the customer's allowance |
Plan check temporarily unavailable, please retry. | plan_check_failed | Plan tier check | Allowing the run would hand out a paid feature without confirming entitlement |
Could not verify your team seat limit, please retry. | seat_check_failed | Team seat check | Allowing the invite could exceed the plan's seat cap |
Internal jobs are not configured (MV_INTERNAL_SECRET is not set). | — | Internal call check | Processing the work unauthenticated would leave a protected endpoint open |
Webhook is not configured (STRIPE_WEBHOOK_SECRET is not set). | webhook_not_configured | Payment webhook signature check | Accepting an unverified webhook would let anyone grant themselves a plan |
Important: A persistent quota_check_failed or plan_check_failed for many customers means the database is unreachable, not that individual accounts are at fault. Check System Health before working through customers one at a time.
The not-configured responses#
These persist until an administrator sets the missing credential or binding. They are honest by design: the platform names what is absent rather than failing vaguely.
| Message | Feature affected |
|---|---|
Billing is not configured yet. | Opening the billing portal |
The Stripe billing portal has no configuration, and it could not be created automatically: <reason> | Opening the billing portal |
Originality checking is not configured (DataForSEO credentials are not set). | Plagiarism checker |
Fact-checking is not configured (the Perplexity key is not set). | Fact checker |
Fact-checking is temporarily unavailable (no AI provider responded). | Fact checker |
Briefing unavailable: OpenAI key not configured | The daily briefing on the Overview screen |
Real benchmarks require DataForSEO credentials. | Benchmarks |
Perplexity API key not configured. | Cited sources |
SEO data source not configured | Browser extension quick view |
Translation not configured. followed by setup instructions | Result translation into a non-English language |
Growth Actions storage is unavailable right now. | Growth Actions |
Job queue unavailable (D1 binding missing) / Job queue unavailable | Long-running article jobs |
Email sending is not configured yet (RESEND_API_KEY is missing). | Blog newsletter sends |
AI translation is not configured (ANTHROPIC_API_KEY is missing). | Blog post translation |
Image storage is not set up yet (the BLOG_MEDIA R2 bucket is not bound). | Blog media uploads |
Google OAuth is not yet configured. followed by setup instructions | Connecting Google Search Console |
<Platform> is not set up yet. followed by setup instructions | Connecting that social channel |
Not available | Opening a saved result when storage is unreachable |
Email is the exception worth remembering: when the email provider is not configured, most sends are skipped silently rather than returning 503. A customer reporting "no email arrived" may therefore see no error at all. Only the blog newsletter refuses loudly. See My scheduled report never arrived.
Resolution#
- Classify the 503 using the table in Step 1. Fail-closed and not-configured have completely different responses.
- For a fail-closed message, retry once. If it repeats across accounts, treat it as a database availability incident and check System Health rather than the individual customer.
- For a not-configured message, identify the missing credential from the message itself and set it in the Cloudflare Pages project's environment variables, then redeploy. Confirm with the affected feature, not with the configuration screen.
- For a deliberate switch, delete the
tool_off:row in Config. The change takes effect within about a minute, because configuration is cached briefly. - For the internal secret, set the same value in all three locations listed above, redeploy, then confirm a job advances in the Job Console and an article generation completes end to end.
- Tell the customer what happened. A fail-closed 503 protected their credits; a not-configured 503 was our gap. Both deserve a plain answer. See Contacting support.
Things a 503 is not
- It is not a quota problem. Running out of credits returns 429 with
quota_exceeded, not 503. See I ran out of credits. - It is not a plan problem. A feature above the customer's tier returns 403 with
upgrade_required. See "This feature requires the ... plan". - It is not a diagnostics problem. The internal diagnostic endpoints answer 403 with
forbiddenwhen called without their key, never 503. - On the blog interface, a 502 from an upstream provider is reported to the browser as 503, because the edge would otherwise replace a 502 body with its own page. A blog 503 can therefore mean "the provider failed", not "we are unconfigured".
Prevention#
- Treat the internal secret as a three-place value. Never rotate it in one place. The failure it produces is silent, and it takes the article writer and every background job with it.
- Verify after every deploy that changes environment variables, by confirming a job's last-run time advances in the Job Console rather than assuming.
- Watch the Job Console rather than waiting for reports. Jobs stuck at
Due nowis the earliest visible signal of an internal-secret problem. - Record every
tool_off:switch you set, including who set it and why. An undocumented switch reads as an outage to the next person on shift. - Keep the customer-visible message in the ticket. The message names the missing dependency, which is most of the diagnosis.
- Monitor error rate, not error count. A configuration failure moves the rate; a single customer's bad input does not.
See also
Was this article helpful?
Thanks — feedback noted for the docs team.