> Source: https://www.gitdash.info/docs/api-reference · GitDash v4.7.1

# API Reference

REST

Every API route needs a signed-in session (unauthenticated requests get `401`) except `/api/health` (probes), `/api/webhooks/github` (HMAC signature) and `/api/cron/*` (`CRON_SECRET` bearer token). Parameters are query-string unless marked (body).

## Organization-mode responses

| Status | code | Meaning |
| --- | --- | --- |
| 403 | `no_groups` | Signed in, but in no group yet (the page equivalent is /pending). |
| 403 | `forbidden` | The route's feature is not granted to any of your groups; `flag` names it. |
| 403 | `unregistered` | The API route is not in the access registry, so it is denied by default. |
| 403 | `org_not_allowed` | The account is not an active member of `GITDASH_ALLOWED_ORGS`; the session is cleared. |
| 503 | `authz_unavailable` | GitHub or the database could not be reached to check access. Retry shortly. |

GET `/api/github/repos`

Personal repositories the signed-in token can see. Organization repositories come from /api/github/org-repos.

GET `/api/github/repo-overview`

Per-workflow summaries for a repository — status, health, run history, trend, duration points.

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `owner` | string | Required | Repository owner. |
| `repo` | string | Required | Repository name. |

GET `/api/github/repo-dora`

Repository-level DORA 4 Keys computed from merged PRs and releases. Includes cycle breakdown, PR scatter, and throughput by week.

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `owner` | string | Required | Repository owner. |
| `repo` | string | Required | Repository name. |

GET `/api/github/repo-contributors`

Per-contributor delivery stats for a repository: PRs merged, reviews given, avg lead time, avg PR size, review turnaround, first-pass approval rate, self-merges. Also returns reviewer load matrix and bus factor. Without days: the last 60 closed pull requests (repo Team tab). With days=30|90: every pull request merged in the window, read through GraphQL search and shared by everyone who can see the repository, plus window_days, median_hours_to_merge (the true median), prs_merged_total, prs_opened_in_window, prs_human_reviewed, prs_no_human_review, prs_self_merged, bot_reviews, review_pairs (pull requests, not review events), coverage, and is_bot / linked_logins / median_hours_to_merge / reviewed_prs on each row. A human review is a submitted APPROVED, CHANGES_REQUESTED or COMMENTED review by someone who is neither a bot nor the author. Account links are applied in both modes.

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `owner` | string | Required | Repository owner. |
| `repo` | string | Required | Repository name. |
| `days` | number | Optional | 30 or 90: the Team insights window (see description). |

GET `/api/github/contributor-profile`

Full contributor profile: KPI cards, 52-week activity calendar, weekly commits, PR funnel, commit hour distribution, languages, recent PRs, and a period_comparison field (recent vs. prior 45 days) that powers the 1:1 Prep Sheet.

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `owner` | string | Required | Org or user context for PR search. |
| `login` | string | Required | GitHub username. |

GET `/api/github/workflows`

List all GitHub Actions workflows for a repository.

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `owner` | string | Required | Repository owner. |
| `repo` | string | Required | Repository name. |

GET `/api/github/runs`

Fetch workflow runs for a specific workflow (last 50 by default).

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `owner` | string | Required | Repository owner. |
| `repo` | string | Required | Repository name. |
| `workflow_id` | number | Required | Workflow ID. |
| `per_page` | number | Optional | Results per page (default 50, max 100). |

GET `/api/github/job-stats`

Per-job and per-step timing stats for a workflow: avg, p50, p95, max durations, success/failure counts, waterfall data.

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `per_page` | number | Optional | Runs to sample (default 30). |
| `owner` | string | Required | Repository owner. |
| `repo` | string | Required | Repository name. |
| `workflow_id` | number | Required | Workflow ID. |

GET `/api/github/team-stats`

CI-level team stats: per-actor run count, success rate, avg duration, activity by day/hour.

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `owner` | string | Required | Repository owner. |
| `repo` | string | Required | Repository name. |
| `per_page` | number | Optional | Runs to analyse (default 100). |

GET `/api/github/bus-factor`

Bus factor analysis over the last 90 days (up to 300 commits): per module, the smallest number of authors covering 80% of its commits. 1 is critical, 2 a warning.

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `owner` | string | Required | Repository owner. |
| `repo` | string | Required | Repository name. |

GET `/api/github/org-health-scorecard`

Leadership rollup across every repo in an org: a composite health score (60% DORA tier + 40% bus-factor risk), risk band, and throughput trend, sorted worst-first.

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `org` | string | Required | Organization slug. |
| `limit` | number | Optional | Repos to analyse (default 10, max 20 — this is an expensive fan-out). |

GET `/api/github/team-workload-risk`

Team-wide people-risk signals for a repo: after-hours/weekend commit patterns, activity cliffs, and concurrent open-PR overload, per contributor. A conversation-starter signal, not a verdict. Hours and weekdays are read in the org workday (Settings → Team insights; default Asia/Saigon 08:00–19:00). Without days: the last 42 days. With days=30|90: that window, plus partial (the commit page cap was reached; no activity cliff then), thresholds, workday, and is_bot / unlinked_name / linked_logins on each row. Account links are applied in both modes.

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `owner` | string | Required | Repository owner. |
| `repo` | string | Required | Repository name. |
| `days` | number | Optional | 30 or 90: the Team insights window. |

GET `/api/github/security-scan`

Static analysis of workflow YAML files for security anti-patterns. Returns findings grouped by severity.

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `owner` | string | Required | Repository owner. |
| `repo` | string | Required | Repository name. |

GET `/api/github/audit-log`

Commit history for all .github/workflows/*.yml files, sorted by date.

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `limit` | number | Optional | Commits to return (default 30). |
| `owner` | string | Required | Repository owner. |
| `repo` | string | Required | Repository name. |

GET `/api/github/repo-summary`

Lightweight per-repo summary — default branch, latest run status, workflow count, and health signal. Called lazily as repository rows enter the viewport.

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `owner` | string | Required | Repository owner. |
| `repo` | string | Required | Repository name. |

GET `/api/github/rate-limit`

Current GitHub API rate-limit status for the authenticated user. Calls GET /rate_limit, which GitHub excludes from rate-limit accounting — checking it is always free.

GET `/api/github/runner-stats`

Per-runner job counts, durations, and failure rates aggregated across a repo's recent completed workflow runs.

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `owner` | string | Required | Repository owner. |
| `repo` | string | Required | Repository name. |
| `per_page` | number | Optional | Runs to analyse (default 30, max 50). |

GET `/api/cron/sync`

Scheduled background sync, triggered daily by Vercel Cron. Re-syncs every repo tracked in sync_cursors and sends pending digest-channel alert emails. Requires Authorization: Bearer $CRON_SECRET — not callable from the browser.

GET `/api/github/run-details`

Job and step breakdown for a single workflow run: per-job status, timing, and step-level detail.

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `owner` | string | Required | Repository owner. |
| `repo` | string | Required | Repository name. |
| `run_id` | number | Required | Workflow run ID. |

GET `/api/github/open-pr-health`

Open pull-request health for a repository: age, review rounds, draft state, and awaiting-review flags per PR.

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `owner` | string | Required | Repository owner. |
| `repo` | string | Required | Repository name. |

GET `/api/github/org-overview`

Aggregated org-level metrics across all active repositories — totals, active-repo count, and per-repo summaries. Expensive multi-request call (cached 15 min).

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `org` | string | Required | Organization slug. |
| `limit` | number | Optional | Max repositories to analyse. |

GET `/api/github/org-repos`

List all repositories in an organization.

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `org` | string | Required | Organization slug. |

GET `/api/github/orgs`

List organizations the authenticated user belongs to.

GET `/api/github/billing`

GitHub Actions billing for the authenticated user or an org: minutes used, paid minutes, included minutes, and per-OS breakdown.

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `org` | string | Optional | Organization slug; omit for the authenticated user. |

GET `/api/github/billing/cost-analysis`

Detailed GitHub Actions cost breakdown via the Enhanced Billing API — per-product/SKU usage and spend for a given month. Some org/account combinations require fine-grained PAT permissions.

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `org` | string | Required | Organization slug. |
| `year` | number | Optional | Billing year (defaults to current). |
| `month` | number | Optional | Billing month 1–12 (defaults to current). |

POST `/api/db/sync`

Trigger incremental sync of GitHub workflow runs to the database. Checks alert rules after sync completes.

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `org` | string | Optional | Limit sync to a specific org. |

GET `/api/db/runs`

Historical workflow runs from the Neon database (not the GitHub API). Requires DATABASE_URL; returns 0 results if the DB has no data for the repo yet.

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `owner` | string | Required | Repository owner. |
| `repo` | string | Required | Repository name. |
| `limit` | number | Optional | Max rows to return. |
| `offset` | number | Optional | Pagination offset. |
| `conclusion` | string | Optional | Filter by run conclusion (e.g. failure). |

GET `/api/db/trends`

Aggregated historical trend data from the Neon database — daily rollups for charts, quarterly summaries for year-over-year, or org-wide daily trends.

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `owner` | string | Required | Repository owner (or org login for org trends). |
| `repo` | string | Optional | Repository name (omit for org trends). |
| `type` | string | Optional | daily (default), quarterly, or org. |
| `days` | number | Optional | Window for daily rollups. |
| `quarters` | number | Optional | Number of quarters for quarterly summaries. |

GET `/api/db/working-habits`

Working habits from the database: per-person commit and pull-request size in merged pull requests, oversized commits and pull requests, thresholds, and sync coverage. Needs the workingHabits feature to see anyone; without it, a signed-in user may read only their own login. Returns available:false without DATABASE_URL and untrackedRepo:true for a repository GitDash does not sync. With the grant, account links merge a person's logins (linkedLogins); your own view without the grant is always your own login only.

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `owner` | string | Required | Repository owner. |
| `repo` | string | Optional | Repository name (omit for every tracked repository of the owner). |
| `login` | string | Optional | Narrow to one person. Required without the workingHabits grant, and must be your own login. |
| `days` | number | Optional | 30 (default) or 90, by merge date. |

POST `/api/github/create-issue`

File a GitHub issue with the signed-in user's token (used by 'File anomaly as GitHub issue'). Needs the githubIssueFromAnomaly feature; limited to 5 per hour per IP.

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `owner` | string | Required | (body) Repository owner. |
| `repo` | string | Required | (body) Repository name. |
| `title` | string | Required | (body) Issue title, up to 256 characters. |
| `body` | string | Optional | (body) Issue body, up to 10,000 characters. |

GET `/api/admin/users`

Organization mode, admin only. Users who have signed in, with their groups and last-seen time.

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `q` | string | Optional | Filter by login. |
| `group` | string | Optional | Only users in this group. |
| `limit` | number | Optional | Page size (default 100). |
| `offset` | number | Optional | Page offset. |

PUT `/api/admin/users/[githubId]/groups`

Organization mode, admin only. Replace a user's groups (audited). Refused with 409 if it would leave no admin.

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `groups` | string[] | Required | (body) Any of admin, devops, security, dev, pm. |

GET `/api/admin/permissions`

Organization mode, admin only. The group × feature matrix and whether enforcement is on.

PUT `/api/admin/permissions`

Organization mode, admin only. Grant or revoke one feature for one group (audited). The admin group has every feature and is not editable.

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `group` | string | Required | (body) devops, security, dev or pm. |
| `flag` | string | Required | (body) Feature key, e.g. dora. |
| `granted` | boolean | Required | (body) true to grant, false to revoke. |

GET `/api/admin/audit`

Organization mode, admin only. Access changes, newest first.

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `limit` | number | Optional | Entries to return (default 50). |
| `before` | number | Optional | Return entries older than this id. |

GET `/api/cron/sync-pr-facts`

Nightly pull-request sync for the people-metric alert rules. Authenticated by the CRON_SECRET bearer token.

GET `/api/cron/sync-commit-facts`

Nightly working-habits sync at 04:47 UTC: stores the commits of merged pull requests from the last 90 days with their size, then evaluates oversized_commit_pct alert rules. Stops starting new work after 240 s; the rest continues the next night. Authenticated by the CRON_SECRET bearer token.

GET `/api/settings/working-habits`

Organization mode, admin only. Working-habits thresholds in effect (files and lines per commit, commits per pull request). PUT saves them.

GET `/api/settings/team`

Organization mode, admin only. The org workday in effect — time zone (IANA name) and hours — that decides after-hours and weekend commits. PUT { timezone, start, end } saves it; standalone deployments use the default, Asia/Saigon 08:00–19:00.

GET `/api/admin/identity-links`

Organization mode, admin only. Account links (alias → main login) and pairs marked as different people. POST { alias, primary } links (a main login that is itself an alias resolves to its main login; aliases of the alias move along); DELETE ?alias= unlinks. Every change is in the audit log, one row per affected login. Links change numbers only, never access; there is no non-admin endpoint.

POST `/api/admin/identity-links/distinct`

Organization mode, admin only. Body { a, b }: these two logins are different people, so Team insights never suggests linking them. DELETE ?a=&b= undoes it.

GET `/api/alerts`

List alert rules, optionally filtered by scope. Set events=1 to also return the 50 most recent alert events. In organization mode non-admins only see rules and events for repositories and orgs their own token can see, with delivery destinations hidden.

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `scope` | string | Optional | Filter to a scope, e.g. repo:owner/name. |
| `events` | string | Optional | Set to 1 to include recent alert events. |

POST `/api/alerts`

Create an alert rule. metric="leadership_digest" creates a Weekly Leadership Digest instead of a threshold rule — scope must be org:X, and threshold/window_hours are ignored (sent every Monday, no cadence to configure).

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `scope` | string | Required | (body) Rule scope, e.g. repo:owner/name or org:myorg. |
| `metric` | string | Required | (body) Metric to watch. |
| `threshold` | number | Required | (body) Threshold that triggers the alert. |
| `window_hours` | number | Optional | (body) Evaluation window (default 24). |
| `channel` | string | Optional | (body) Delivery channel (default browser). |
| `destination` | string | Optional | (body) Email address for email delivery. |

PATCH `/api/alerts`

Update an existing alert rule — enable/disable or change threshold, window, or destination. Admin only in organization mode (as are POST and DELETE).

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | number | Required | (body) Alert rule ID. |

DELETE `/api/alerts`

Delete an alert rule.

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | number | Required | Alert rule ID. |

POST `/api/alerts/test`

Send a test alert for a rule without a real threshold breach (uses a synthetic value of 1).

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `rule_id` | number | Required | (body) Alert rule ID to test. |

POST `/api/webhooks/github`

Receive GitHub workflow_run webhook events and upsert runs into the database. Authenticated by HMAC-SHA256 signature against GITHUB_WEBHOOK_SECRET — no session required. Rejects all requests if the secret is unset (fail-safe).

GET `/api/ai/status`

Capability probe for the AI layer. Returns { enabled, providers } — which providers have a key configured, never the key material. Not rate-limited (no LLM call).

GET `/api/ai/insights`

LLM synthesis of a repository's or org's metrics. Returns { ok, provider, model, generated_at, cached, partial, content: { summary, bullets, actions } }. 503 when no provider key is configured or the provider is unavailable; 429 when rate-limited (20/min per token) or the daily token budget is spent. Prompts are built from an allowlisted, metrics-only snapshot — never code, logs, or commit messages.

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `owner` | string | Optional | Repository owner (repo surface — pair with repo) |
| `repo` | string | Optional | Repository name (repo surface) |
| `org` | string | Optional | Organisation login (org surface — takes precedence over owner/repo) |
| `refresh` | 1 | Optional | Bypass the cached generation. Still rate-limited. |

GET `/api/ai/anomaly-explanation`

Explain a workflow metric's statistical outliers from surrounding metadata (baseline stats, workflow-file change dates, trigger mix). Returns { ok, provider, model, outlier_count, content: { explanation, check } }. 404 when the metric has no outliers to explain; 503 when unconfigured or unavailable; 429 when rate-limited (20/min per token). Never reads run logs.

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `owner` | string | Required | Repository owner |
| `repo` | string | Required | Repository name |
| `workflow_id` | number | Required | Workflow ID |
| `metric` | duration \| queue_wait | Required | Which metric's outliers to explain. Validated against a literal allowlist — arbitrary values are rejected, never forwarded to a prompt. |

GET `/api/ai/root-cause`

Ranked hypotheses for why a workflow is failing, inferred from failed job/step names, timing shifts, trigger and branch clustering, and workflow-file change dates. Returns { ok, provider, model, failure_count, partial, content: { hypotheses[] } } where each hypothesis carries rank, evidence, confidence (high|medium|low) and next_step. Returns content: null below 3 recent failures — enforced server-side, no provider call. Rate-limited to 10/min per token, half the other AI surfaces. Never reads run logs.

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `owner` | string | Required | Repository owner |
| `repo` | string | Required | Repository name |
| `workflow_id` | number | Required | Workflow ID |

GET `/api/github/issues`

Issue and triage health. Returns { open_count, opened_in_period, closed_in_period, backlog_delta, median_days_to_close, p90_days_to_close, stale_count, unlabelled_count, unanswered_count, age_buckets[], top_labels[], assignee_load[], neglected[], oldest_open, total_analysed, partial }. Pull requests are excluded — GitHub's issues endpoint returns both, and counting PRs would report delivery throughput as triage throughput.

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `owner` | string | Required | Repository owner |
| `repo` | string | Required | Repository name |
| `days` | number | Optional | Window in days, 7-90. Defaults to 30. |

GET `/api/github/deployments`

Measured delivery metrics from GitHub's Deployments API. Returns { source, production_environment, deploys_per_day, change_failure_rate_pct, mttr_hours, mttr_samples, by_environment[], recent[], partial, all_time_count, newest_deployment_at }. source is "deployments" when the window has data, "stale" when the repo has deployment history but none inside the window, and "none" when it has never recorded one — the caller keeps its release/PR estimates for the latter two rather than being handed zeros. all_time_count and newest_deployment_at describe history at any age, so a stale window can state how much exists and when it stopped. Rates exclude pending and in-progress deploys, and return null rather than 0% when nothing is conclusive.

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `owner` | string | Required | Repository owner |
| `repo` | string | Required | Repository name |

GET `/api/github/security-alerts`

GitHub's own security findings: Dependabot, code scanning and secret scanning alerts. Returns { sources, alerts[], counts, total_open, oldest_open_days, partial, needs_scope } where each source carries its own status (ok | forbidden | not_enabled | error) plus 90-day fix count and mean time to remediate. A classic PAT with repo reads all three; no security_events scope is required. A 403 is classified by its response body — a disabled feature becomes not_enabled, a genuine permission failure becomes forbidden and sets needs_scope — so an unreadable source is never reported as a clean one, and a switched-off one never prompts a pointless token change.

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `owner` | string | Required | Repository owner |
| `repo` | string | Required | Repository name |

GET `/api/settings/ai`

Current AI provider override. Returns { configurable, mode, enabled, provider, model, base_url, api_key_hint, has_key, updated_by, updated_at, effective_source, env_providers, db_available }. The API key is never returned — only a masked hint. configurable is false in standalone mode, where the section is hidden and environment defaults apply.

PUT `/api/settings/ai`

Save an AI provider override. Body: { enabled, provider ("bailian"|"gemini"|"qwen"), model, base_url, api_key? }. Omitting api_key preserves the stored one; the key is encrypted before storage. base_url must be https. 403 in standalone mode, 503 when no database is configured. A configured override is used exclusively — server keys are never a fallback behind it.

GET `/api/settings/email`

Current email delivery configuration. Returns { enabled, provider, from_address, api_key_hint, has_key, updated_by, updated_at, effective_source, db_available }. The API key itself is never returned — only a masked hint. effective_source reports whether Settings, environment variables, or nothing is actually in effect.

PUT `/api/settings/email`

Save email delivery configuration. Body: { enabled, provider ("resend"|"sendgrid"), from_address, api_key? }. Omitting api_key preserves the stored one. The key is encrypted with AES-256-GCM before storage. Rejects enabling without a key or from address. 503 when no database is configured.

POST `/api/settings/email/test`

Send a test email through the currently-resolved provider to verify configuration. Body: { to }. Returns the provider's own error verbatim on failure (bad key, unverified sender) since it is actionable and contains no secret. Rate-limited to 3/min per token.

GET `/api/health`

Liveness/readiness probe. Returns {"status":"ok"} with no authentication. Used by Kubernetes and load balancers.

GET `/api/demo`

Sanitized fixture data for demo mode. No authentication required — the payload contains no real credentials.

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `resource` | string | Required | repos, runs, or summary. |
| `repo` | string | Optional | Repository name for run/summary fixtures. |
| `count` | number | Optional | Number of synthetic runs to generate. |

POST `/api/auth/setup`

Standalone mode: validate the submitted PAT and store it in an encrypted session cookie. Rate-limited.

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `pat` | string | Required | (body) GitHub Personal Access Token. |

DELETE `/api/auth/setup`

Standalone mode: clear the stored PAT (sign out).

GET `/api/auth/login`

Organization mode: begin the GitHub OAuth flow (redirects to GitHub). Rate-limited.

GET `/api/auth/callback`

Organization mode: OAuth redirect target — verifies state, exchanges the code, and creates the session.

Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `code` | string | Required | OAuth authorization code (set by GitHub). |
| `state` | string | Required | CSRF state token (set by GitHub). |

POST `/api/auth/logout`

Destroy the session cookie and sign the user out.

GET `/api/auth/me`

Return the current session identity (login, mode), or 401 if unauthenticated.
