All endpoints
The complete Metric Vault route table, every path the Worker answers, with method, authentication mechanism and purpose.
Last updated 2026-08-06
Summary#
This is the complete list of HTTP routes served by the Metric Vault Worker, grouped by feature area. Only POST /api/v1/analyze and the three /api/keys/* management routes are a supported public integration surface; everything else exists to serve the web app, the Chrome extension, the public blog and the operator tooling. They are documented here so a developer or a support engineer can tell exactly what a request did and what guarded it.
Warning: Routes outside /api/v1/ and /api/keys/ are internal. Their request and response shapes change with the product and carry no compatibility promise. Build against POST /api/v1/analyze.
Overview#
How routing works#
Every request to metricvaultai.com reaches one Cloudflare Pages Worker before anything else. The Worker evaluates a flat, ordered chain of path checks and the first match wins; anything unmatched falls through to the static assets. Order is load-bearing in a few places, most notably that the Stripe webhook is matched before any body-reading middleware so the raw bytes stay available for signature verification, and that /api/blog/site and /api/blog/sites are matched before the /api/blog/* catch-all.
Fifteen paths never reach the Worker at all: /free/*, /blog.html, /favicon.ico, /favicon.png, /favicon-16x16.png, /favicon-32x32.png, /apple-touch-icon.png, /og-image.png, /logo-dark.png, /logo-light.png, /icon-192.png, /icon-192.svg, /icon-512.png, /icon-512.svg and /manifest.json. They are served directly as static assets.
Authentication mechanisms#
Seven distinct mechanisms appear in the tables below.
Verified session is the one to read carefully. MV_C1_ENFORCE is not set in production, so a route marked that way rejects a caller who sends no address at all, but still believes the address it is given. Ownership therefore rests on a claim until enforcement is switched on. See Authorization model.
Billing does not wait for the flag. Any route that spends credits requires a proven session whatever MV_C1_ENFORCE says. An unverified caller gets 401 unauthorized; a caller whose session could not be checked because the identity provider was unreachable gets 503 auth_unavailable and should retry rather than sign in again. /api/analyst is the exception and answers anonymous callers, because it also serves the public chat — it bills them to nobody instead. Routes that cost nothing, /api/chatbot among them, stay open and are capped per IP.
| Label | Mechanism | Notes |
|---|---|---|
| Session email | The handler reads user_email from the JSON body, the x-mv-user header, or a query parameter, and uses it as the actor | Not a verified credential. Used by the majority of app endpoints |
| Verified session | mvC1Identity prefers a verified bearer token and falls back to the address in the body. The route refuses a caller it cannot name at all | Under MV_C1_ENFORCE=1 an unverified caller is refused outright; that flag is off in production today |
| Verified session, owner of the row | The same, plus the stored row’s user_email must match the caller | A row with no owner was created by the signed-out free tool and stays reachable by its id, because the id is the only handle its creator ever had |
| Verified token | A Metric Vault session access token is verified against the identity provider, and the address must have a confirmed email | Used by the key-management routes and saved-result deletion |
| Admin token | A session token that also resolves to an active row in the administrator table | All /api/admin/* and the workflow-failure console |
| Internal secret | Header x-mv-internal-secret, or ?key= on a GET, matched against MV_INTERNAL_SECRET. Fails closed when unset | Operator and machine routes. See Internal endpoints |
| API key | Authorization: Bearer mv_live_... | /api/v1/analyze only |
| Public | No credential | Genuinely open, or protected by something else such as a signature |
Where the truth lives. _worker.js is the only authority; this table is a hand-maintained copy of it and has drifted before. tests/api-auth-doc.mjs compares the two on every deploy and fails if a row claims a gate the handler does not implement.
It is not GENERATED from the source, and that is deliberate rather than unfinished. Three things defeat generation today. A classifier has to know every identity helper by name — there are ten, and one it does not know turns a correctly documented route into a wrong one, which is how an earlier version of the checker reported /api/blog/sites and /api/library/saved/delete as unprotected when both are gated. Several routes are guarded in the router rather than the handler — /api/dbgdfs compares ?key= against MV_INTERNAL_SECRET before any handler is reached — and a check that reads handlers cannot see them. And a prefix route is not one gate: /api/blog/<rest> is a sub-router whose branches differ from each other, so no single generated label is true of it. The checker is where the derivation belongs; the table stays readable prose that the checker holds to account.
CORS#
Successful JSON responses carry Access-Control-Allow-Origin: *. The OPTIONS preflight answers for every path with:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type, AuthorizationNote: The preflight advertises only GET, POST, OPTIONS and only the Content-Type and Authorization headers. The blog sub-router uses PATCH, PUT and DELETE and the custom headers x-mv-user and x-mv-site, so cross-origin blog writes fail preflight. Same-origin calls from the dashboard are unaffected.
The Chrome-extension endpoint uses a wider set that also allows the X-Extension-Version header.
Billing and account#
| Path | Method | Auth | Purpose |
|---|---|---|---|
/api/create-checkout | POST | Session email | Creates a Stripe Checkout session for a plan and billing period and returns its URL |
/api/stripe/webhook | POST | Stripe signature | Receives subscription lifecycle events and grants or revokes plans |
/api/billing/sync | POST | Internal secret | Reconciles stored plans against live Stripe subscriptions |
/api/manage-subscription | POST | Verified session | Returns a Stripe Customer Portal session URL |
/api/usage | POST | Verified session | Plan, month-to-date credits, quota, reset date and per-tool breakdown |
Library, saved results and history#
| Path | Method | Auth | Purpose |
|---|---|---|---|
/api/library/saved | GET | Verified session | Lists saved tool results, newest first |
/api/library/saved/check | POST | Verified session | Reports whether a saved result already matches these tool inputs |
/api/library/saved/open | GET | Verified session | Returns one saved result, ownership enforced in the query |
/api/library/saved/html | POST | Verified session, owner of the row | Stores the rendered HTML of a saved result |
/api/library/saved/delete | POST | Verified token | Deletes saved results. Deliberately ignores any body email |
/api/library/save-run | POST | Verified session | Saves a completed run submitted by the client |
/api/history | POST | Verified session | Older analysis history read from the identity provider's database |
/api/save-analysis | POST | Verified session | Writes an analysis row to that same database |
Tools and AI#
| Path | Method | Auth | Purpose |
|---|---|---|---|
/api/tools | POST | Session email, optional | Runs one of the 18 direct-fetch technical tools. Anonymous callers are rate-limited by IP |
/api/premium-ai | POST | Session email, optional | The main analysis endpoint behind most dashboard tools. Applies the shared data cache, credit metering and result saving |
/api/analyst | POST | Verified session | Conversational analyst behind the in-app assistant and the marketing widget; metering and charges land only on a verified caller, never on a claimed address |
/api/brief | POST | Session email, optional | The daily briefing shown on dashboard load. A signed-in caller gets a brief built from their own monitored competitors, cached one per account per UTC day; an anonymous caller is answered a generic brief and rate-limited by IP. Not charged |
/api/opportunities | POST | Verified session | The Opportunities card shown on dashboard load. Returns five prioritized opportunities for a target. Answers 401 auth_required to a caller it cannot name; not charged, cached one per account per target per ISO week |
/api/originality | POST | Verified session | Duplicate-content and AI-likelihood check on pasted text |
/api/factcheck | POST | Verified session | Extracts claims from pasted text and verifies them with citations |
/api/chatbot | POST | Public, IP rate-limited | The support assistant behind the chat bubble on the public site. Answers signed-out visitors, capped per IP address; a signed-in caller is metered on the fair-use bucket instead. Forwards up to ten truncated user/assistant turns to gpt-4o. Its plan, price and billing facts are built from MV_PUBLIC_PLANS and the plan tables, and never charged |
/api/translate | POST | Public, IP rate-limited | Translates dynamic result strings, cached permanently per string |
/api/cited-sources | POST | Verified session | Aggregates the sources cited about a brand across research queries |
/api/benchmarks | POST | Verified session | Industry benchmark comparison for a domain, cached 30 days |
/api/extension/quickview | POST | Public, IP rate-limited | The Chrome extension's domain snapshot, cached 24 hours. Answers an anonymous caller and charges nothing, because the extension has no sign-in. A verified bearer token is metered at 1 credit and skips the IP cap; an address sent in the body without one is ignored rather than believed |
The 18 tool types accepted by /api/tools are onpage_seo, pagespeed, robots_audit, schema_check, http_headers, sitemap_validator, redirect_chain, mixed_content, hreflang_checker, tech_stack, dup_content, internal_linking, ai_visibility, geo_audit, ai_overview, ai_citation, seo_compare and llms_txt. Anything else returns Invalid tool type: <type>.
Article writer#
The Article Writer runs through POST /api/premium-ai like every other dashboard tool, with the tool type in the request body.
There used to be a separate three-route job queue here — /api/article-start, /api/article-status and /api/article-process. It was removed on 2026-09-01: nothing in the product called it, and /api/article-start was public. See Internal endpoints for why an internal handoff cannot carry a caller's identity.
Tier-2 AI visibility#
| Path | Method | Auth | Purpose |
|---|---|---|---|
/api/tier2/prompts/list | POST | Session email | Lists tracked prompts for a brand |
/api/tier2/prompts/add | POST | Session email | Adds a tracked prompt |
/api/tier2/prompts/delete | POST | Session email | Removes a tracked prompt |
/api/tier2/alerts/list | POST | Verified session | Lists visibility-drop alerts |
/api/tier2/alerts/add | POST | Verified session | Creates a drop alert, threshold 1 to 99 percent |
/api/tier2/alerts/delete | POST | Verified session, owner of the row | Deletes a drop alert |
/api/tier2/trends/export | POST | Verified session | Exports a brand's visibility trend as JSON or CSV, 1 to 365 days |
/api/tier2/compare | POST | Verified session | Compares up to 5 brands over 1 to 180 days |
Competitor Monitor#
All ten are POST, authenticated by session email, and cost no credits.
| Path | Purpose |
|---|---|
/api/monitor/list | Lists monitored URLs |
/api/monitor/add | Adds a URL to monitor and runs the first check immediately |
/api/monitor/delete | Removes a monitored URL |
/api/monitor/update | Updates label, interval, sensitivity, severity gate or alert channels |
/api/monitor/changes | Lists detected changes, 1 to 500 per page |
/api/monitor/snapshot | Returns stored snapshots for one monitored URL |
/api/monitor/check-now | Forces an immediate check |
/api/monitor/mark-read | Marks changes read or dismissed. id may be all |
/api/monitor/stats | Aggregate counts, timeline and most volatile domains |
/api/monitor/test-alert | Sends a synthetic High-severity alert to the supplied channels |
Responsive Website Analyzer#
| Path | Method | Auth | Purpose |
|---|---|---|---|
/api/responsive/analyze | POST | Session email, optional | Fetches a page and returns a responsiveness analysis |
/api/responsive/chat | POST | Verified session, owner of the row | Follow-up questions about a stored analysis |
/api/responsive/history | POST | Verified session | Previous analyses |
/api/responsive/get | POST | Verified session, owner of the row | Returns one stored analysis |
/api/responsive/delete | POST | Verified session, owner of the row | Deletes one stored analysis |
/api/responsive/device | POST | Verified session, owner of the row | Returns the analysis for one device profile |
/api/responsive/proxy | GET | Public | Renders a target page inside the preview frame. Returns a styled HTML card on failure, not JSON |
/api/responsive/asset | GET | Public | Proxies a sub-resource for the preview frame and rewrites CSS URLs to absolute |
Google and social integrations#
| Path | Method | Auth | Purpose |
|---|---|---|---|
/auth/google/start | GET | Public, ?email= | Begins the Google OAuth flow with a server-stored state token |
/auth/google/callback | GET | Server-stored state | Completes Google OAuth and stores the tokens |
/api/google/status | POST | Session email | Whether Google is connected and for which address |
/api/google/disconnect | POST | Session email | Removes the stored Google tokens |
/api/gsc/sites | POST | Verified session | Lists Search Console properties available to the account |
/api/gsc/performance | POST | Session email | Search Console performance rows, cached 1 hour |
/api/social/connect-url | POST | Verified session | Mints a single-use ticket for a social OAuth flow, tied to the signed-in account |
/api/social/connect | GET | Session email (one-time ?t= ticket) | Begins a social OAuth flow from a ticket minted by /api/social/connect-url. Errors render as HTML, not JSON |
/api/social/callback/<platform> | GET | Server-stored state | Completes a social OAuth flow. State is single-use and expires after 15 minutes |
/api/social/connections | GET, POST | Verified session | Lists connected social accounts |
/api/social/disconnect | POST | Verified session | Removes one social connection |
/api/social/publish | POST | Verified session | Publishes text to the selected channels |
/api/social/schedule | POST | Verified session | Queues a post for a future time |
/api/social/scheduled | GET, POST | Verified session | Lists queued posts, pending first |
/api/social/scheduled/cancel | POST | Verified session | Cancels a pending queued post |
The six supported platforms are LinkedIn, X, Facebook, Instagram, TikTok and Threads. Instagram and TikTok return a "needs media" notice on text publishing.
Team and workspaces#
| Path | Method | Auth | Purpose |
|---|---|---|---|
/api/team/list | POST | Session email | Members of your workspace and workspaces you have joined |
/api/team/invite | POST | Session email | Invites a teammate. Enforces the plan's seat cap |
/api/team/accept | POST | Invite token plus matching email | Accepts an invitation |
/api/team/remove | POST | Session email | Removes a member from your workspace |
/team/accept/<token> | GET | Public | Self-contained HTML page that posts to /api/team/accept |
Notifications, preferences and branding#
| Path | Method | Auth | Purpose |
|---|---|---|---|
/api/notifications | POST | Verified session | Aggregated feed of competitor changes, rank drops, scheduled runs and share views |
/api/notifications/mark-read | POST | Verified session | Marks competitor-change items read |
/api/prefs/notifications/get | POST | Verified session | Reads the four notification preferences |
/api/prefs/notifications/save | POST | Verified session | Saves the four notification preferences |
/api/branding/get | POST | Verified session | Reads white-label branding |
/api/branding/save | POST | Verified session plus Pro plan | Saves logo, colors, company name and footer |
/api/branding/reset | POST | Verified session | Restores the default branding |
Alerts, schedules, editorial, publishing, sharing#
| Path | Method | Auth | Purpose |
|---|---|---|---|
/api/rank-alerts/create | POST | Verified session | Creates a keyword rank alert and takes an immediate baseline |
/api/rank-alerts/list | POST | Verified session | Lists rank alerts |
/api/rank-alerts/delete | POST | Verified session | Deletes an alert and its history |
/api/rank-alerts/check-now | POST | Verified session, owner of the row | Forces an immediate rank check |
/api/schedules/create | POST | Verified session plus Starter plan | Creates a scheduled report |
/api/schedules/list | POST | Verified session | Lists scheduled reports |
/api/schedules/delete | POST | Verified session | Deletes a schedule and its run history |
/api/editorial/list | POST | Verified session | Lists editorial calendar items |
/api/editorial/create | POST | Verified session | Creates a calendar item |
/api/editorial/update | POST | Verified session, owner of the row | Updates title, date, status, owner, notes or order |
/api/editorial/delete | POST | Verified session | Deletes a calendar item |
/api/publish/status | GET, POST | Verified session | Reports which CMS platforms this account has connected, and which can publish live at all |
/api/publish/push | POST | Verified session | Publishes a post. WordPress publishes for real; the other five refuse and say why. Draft unless publish_live: true |
/api/publish/connect | POST | Verified session | Verifies and stores a WordPress site URL, username and Application Password |
/api/share/create | POST | Verified session | Creates a public share link for a rendered report |
/api/share/list | POST | Verified session | Lists your share links |
/api/share/delete | POST | Verified session, owner of the row | Deletes a share link |
/api/workflow/failure | POST | Session email | Client-reported workflow step failure, for triage |
/api/workflow/failures | POST | Admin token | Lists reported failures with aggregate statistics |
/api/workflow/failure/resolve | POST | Admin token | Marks a reported failure resolved |
Blog#
| Path | Method | Auth | Purpose |
|---|---|---|---|
/api/blog/site | GET, POST | Blog identity | Current site payload: ids, public URLs, feed URL and embed snippet |
/api/blog/sites | GET, POST, PATCH | Blog identity, writes owner-only | Lists, creates, selects and renames blog sites |
/api/blog/role | POST | Session email | Sets a team member's blog role |
/api/blog/higgsfield/signin | GET | Internal secret | Operator sign-in for the image provider |
/api/blog/higgsfield/callback | GET | Server-stored PKCE state | Completes that operator sign-in |
/api/blog/media/file/<siteId>/<file> | GET | Public | Streams a stored media object, cached immutably for a year |
/api/blog/public/<siteId>/<rest> | GET, POST | Public | The public blog API: posts, posts/<slug>, feed.rss, subscribe, unsubscribe/<token> |
/api/blog/<rest> | GET, POST, PATCH, PUT, DELETE | Blog identity; role capability on some branches only | The authenticated blog sub-router |
The blog sub-router's capability checks are uneven. Identity is required for every branch, but mvbCan is not. Thirteen branches carry no capability check at all, including POST posts (create), POST media (upload), POST media/import (a server-side fetch of a caller-supplied URL) and both metered generators, POST ai/blog and POST ai/image — every cheaper ai/* branch beside them opens with a postWriteOwn check. Two further points matter when reading the role model: postWriteOwn and media are true for all three roles, so the guards that use them refuse nobody, and mvbNormalizeRole coerces an unrecognised role to author. Only postWrite, postDelete and taxonomy can actually refuse a caller, and only the author role. See Authorization model.
The authenticated sub-router covers posts, categories, tags, authors, media, stock search, subscribers, newsletter templates and sending, AI generation and AI translation. Blog roles are owner, editor and author; anything else is coerced to author. See Roles and what each can do.
API keys and the public API#
| Path | Method | Auth | Purpose |
|---|---|---|---|
/api/keys/create | POST | Verified token | Creates a key and returns it once. Maximum 5 active |
/api/keys/list | POST | Verified token | Lists your keys in masked form |
/api/keys/revoke | POST | Verified token | Revokes one key by id |
/api/v1/analyze | POST | API key | The public analysis endpoint. 6 credits per call |
Administration#
All 22 routes are POST and take a session token in the body as token. A caller that is not an active administrator receives HTTP 403 with {"error":"Unauthorized"}.
| Path | Owner only | Purpose |
|---|---|---|
/api/admin/me | No | Returns the caller's admin email and role |
/api/admin/data | No | Subscribers, usage and headline statistics for the month |
/api/admin/metrics | No | Hourly metrics, window capped at 168 hours |
/api/admin/errors | No | Grouped error issues |
/api/admin/errors/resolve | No | Resolves or reopens an error group |
/api/admin/users | No | Lists administrators |
/api/admin/users/mutate | Yes | Adds, updates or removes an administrator |
/api/admin/audit | No | Reads the admin audit log |
/api/admin/runs | No | Reads the activity log across accounts |
/api/admin/blog/posts | No | Lists blog posts across accounts |
/api/admin/blog/posts/status | Yes | Moderates a post to draft, published or trash |
/api/admin/config | No | Lists platform configuration keys |
/api/admin/config/set | Yes | Sets or deletes a configuration key |
/api/admin/tools | No | Per-tool credit costs, sections and cache lifetimes |
/api/admin/jobs | No | Background job status and next-due times |
/api/admin/jobs/run | Yes | Triggers one job: monitor, schedules, rank_alerts or tier2 |
/api/admin/cache/clear | Yes | Clears the shared data cache, optionally for one tool |
/api/admin/cache/setall | Yes | Sets the global cache lifetime, 1 to 365 days |
/api/admin/customer | No | One customer's plan, usage, connections and team |
/api/admin/customer/mutate | Yes | Sets plan, resets usage, refunds credits, suspends, reactivates, removes a seat |
/api/admin/customer/billing | No | One customer's plan source and live Stripe subscription |
/api/admin/social/disconnect | Yes | Removes a customer's social connection |
Internal and diagnostic routes#
| Path | Method | Auth | Purpose |
|---|---|---|---|
/api/cron/run | POST, GET | Internal secret | Runs whichever background jobs are due |
/api/billing/sync | POST | Internal secret | Reconciles plans against Stripe |
/api/dbgdfs | GET | Internal secret via ?key= | Raw data-provider probe |
/api/diag/psi | Any | Internal secret via ?key= | Reports which PageSpeed keys are loaded |
/api/diag/websearch | Any | Internal secret via ?key= | Runs one real web-search call |
/api/diag/email | Any | Internal secret via ?key= | Sends one real test email |
/api/diag/aiwrite | Any | Internal secret via ?key= | Runs one real article-writer model call |
/api/diag/stock | Any | Internal secret via ?key= | Runs a real stock-photo search |
/api/diag/blog-image | Any | Internal secret via ?key= | Reports image-generation readiness |
/api/diag/subs | Any | Internal secret via ?key= | Per-site newsletter subscriber counts |
/api/diag/keywordgap | Any | Internal secret via ?key= | Raw keyword-gap probe. Makes billable provider calls |
/api/diag/social | Any | Internal secret via ?key= | Per-platform OAuth configuration and redirect URIs |
Full detail is in Internal endpoints and Diagnostic endpoints.
Non-API routes served by the Worker#
| Path | Method | Behavior |
|---|---|---|
/share/<slug> | GET | Public shared report. 404 page when unknown, 410 when expired. Increments the view count |
/team/accept/<token> | GET | Invitation acceptance page |
/login, /login.html | GET | Returns an inlined copy of the sign-in page with no-store headers. Never touches the static assets |
/blog, /blog.html | GET | Serves the blog index, edge-cached 300 seconds |
/blog/<slug> | GET | Serves the blog post shell |
/legal, /legal.html | GET | Serves the legal page, edge-cached 300 seconds |
/compare, /compare.html | Any | 301 to /pricing |
/sitemap.xml | GET | Inline sitemap, cached 1 hour |
/robots.txt | GET | Inline robots file, cached 1 hour |
/logo-dark.png, /logo-light.png | GET | Logo images, edge-cached 1 hour |
/ext-preview.html | GET | Extension preview page, never cached |
/free, /free/… | GET | Not handled. _routes.json excludes /free/* from the Worker, so these paths are served as static assets and return 404. /free-tools/ is the only URL for the hub |
/privacy, /terms, /legal/cookies and variants (7 paths) | GET | 301 to the canonical legal pages, cached 1 day |
/free-tools, /free-tools/schema-validator, /free-tools/robots-checker, /free-tools/pagespeed-audit and variants (9 paths) | GET | Serves the free-tool page, edge-cached 300 seconds |
Anything else falls through to the static assets. Clean URLs are remapped (/ to /index.html, /dashboard to /dashboard.html, and the same for /login, /admin, /privacy and /terms). A 404 on a path with no file extension is upgraded to the branded 404 page; a 404 on a path that has a file extension keeps its bare 404, so a missing script never receives an HTML body.
Paths that are not routes#
| Path | Reality |
|---|---|
/api/integration-waitlist | Called by the dashboard's integration waitlist form, but no handler exists. The request falls through and returns 404, and the client swallows it |
/api/broadcast | Belongs to the vendored identity-provider library, not to Metric Vault |
/blog.html, /logo-dark.png, /logo-light.png | Handlers exist in the Worker but these paths are excluded from Worker routing, so the static asset answers first |
Request logging#
Every request passes through an activity wrapper before and after routing. It records a row in the activity log for recognized routes, using the actor resolved from the body, the x-mv-user header, or a query parameter. Read-only polling routes such as /api/library, /api/usage and /api/notifications are excluded. Status 429 is recorded as warning, any other status of 400 or above as failed, and everything else as success. Logging happens after the response is produced and never blocks or fails a request.
See also
Was this article helpful?
Thanks — feedback noted for the docs team.