Skip to content

Admin API reference

Every route of the admin API: docs repositories, builds, organizations, readers, the audit log and docs analytics.

Basics

  • Base path /admin/api, JSON in and out.
  • Every request needs Authorization: Bearer $OPEND_ADMIN_TOKEN. A missing or wrong token gets 401 {"error":"unauthorized"}. The token is compared in constant time.
  • Without OPEND_ADMIN_TOKEN, none of these routes exist.
  • An invalid body gets 400 {"error":"invalid request","issues":[...]}, one issue per problem.
  • Every change is written to the audit log with the actor admin-token.

Staff readers can do most of this in the browser instead, in the admin console at /admin; see Run your site from the admin console. Its changes are audited with the actor staff:email.

Repositories

RouteWhat it does
GET /admin/api/reposLists registered repositories: id, fullName, sourceUrl, installationId, releaseRefs, docsPath, access, publicEntitlements, versionPicker, releaseLines and declared: the site block of the repository's opend.config.yaml as last applied ({settings, ref, sha, release, appliedAt}), or null.
POST /admin/api/reposRegisters a repository, or replaces the settings of the one with the same fullName. An optional releaseLines (major or minor) works as PUT …/release-lines; leaving it out keeps the current setting. 201 with the repository.
PUT /admin/api/repos/:id/accessMakes the repository's site private or public. Body {"access": "public", "publicEntitlements": {"products": [], "features": [], "version": "2.1"}}. Leaving out publicEntitlements keeps the current set; null clears it. Every cached page is dropped at once, on every replica. 200 with the repository; 404 {"error":"no such repo"}. See Make a site public or private.
PUT /admin/api/repos/:id/release-linesGroups the repository's releases into lines by major (the default) or minor version. Body {"releaseLines": "minor"}. Live releases move to their new lines at once; where several land on one line, the newest build stays live. 200 with the repository; 404 {"error":"no such repo"}. See Release lines by major or minor version.
PUT /admin/api/repos/:id/versionsSets the site's version picker. Body {"mode": "off"}, {"mode": "reader"}, {"mode": "fixed", "version": "2.1"} or {"mode": "list", "versions": ["2.1", "2.0"]} (1 to 50 versions such as 1.4). Every cached page is dropped at once, on every replica. 200 with the repository; 404 {"error":"no such repo"}. See Let readers choose the version they read.
GET /admin/api/repos/:id/releasesThe live release for each release line: releaseId, releaseLine, release, sha.
DELETE /admin/api/repos/:id/releases/:lineTakes a release line off the site, for example one left over from a test build. Readers of that line see that nothing is published, and the staff view and the API fall back to the next newest line. The stored release is kept, and the next successful build for the line puts it back.
GET /admin/api/exportEverything about the site that is not rebuilt from Git, as one JSON document for moving it to another deployment: the repository, orgs, entitlements, readers, staff, editor accounts and working branches, settings, sealed secrets, API key hashes and the audit log. Releases, builds, search indexes and analytics are left out. Treat the file as a secret.
POST /admin/api/importLoads an export into a site that has none of that data yet (409 otherwise). Rows keep their ids. Sealed secrets only open if this deployment uses the same OPEND_SECRET_KEY (or SESSION_SECRET) as the one exported from. Then request builds, or let the poller run, to publish releases.
Body fieldDefaultRules
fullNamerequiredowner/name, as GitHub sends it in webhooks. Matched without regard to case.
sourceUrlrequiredThe URL the worker fetches from, such as https://github.com/acme/docs.git. Must be an https:// URL; when NODE_ENV is not production, an absolute local path or a file: URL is also accepted. The installation token is only sent to github.com, or to the host that GITHUB_API_URL names.
installationIdnullThe GitHub App installation id: a positive integer, or null.
releaseRefs["main"]Branch and tag names that publish. Not empty. * matches any run of characters.
docsPath.The folder holding opend.config.yaml, inside the repository: no leading / and no ...

Omitted fields are reset to their defaults when a repository is registered again. The access mode and the version picker are not among them: only PUT /admin/api/repos/:id/access and PUT /admin/api/repos/:id/versions (or a seed file) change them.

Settings the repository declares in the site block of its opend.config.yaml are its own (Set the site up from opend.config.yaml): a request that would change one gets 409 {"error":"set by opend.config.yaml (site: access); change it in the docs repository"}, naming the fields. Sending the value already in place is not a change. Registering the same repository and folder again keeps declared release refs, and a seed file leaves declared fields as they are.

Builds

RouteWhat it does
POST /admin/api/repos/:id/buildsQueues a build of one commit. Body {"ref": "main", "sha": "<40 hex characters>"}. 202 with the job; 404 {"error":"no such repo"}.
GET /admin/api/buildsRecent builds, newest first. Query repoId to filter, and limit (default 50, at most 200).
GET /admin/api/builds/:idOne build, with its diagnostics. 404 {"error":"no such build"}.

A build has id, repoId, ref, sha, status (queued, running, succeeded or failed), attempts, error, diagnostics, releaseId, createdAt and finishedAt. Each diagnostic has the severity, code, message, file, line and column described in Validation diagnostics.

Organizations and entitlements

RouteWhat it does
GET /admin/api/orgsLists organizations with their products, features and version (null without entitlements).
POST /admin/api/orgsCreates or renames an organization. Body {"slug", "name", "entitlements"?}. 201 {"slug"}.
PUT /admin/api/orgs/:slug/entitlementsReplaces the entitlements. Body {"products": [], "features": [], "version": "0.1"}. 200 with the result; 404 {"error":"no such org"}.
DELETE /admin/api/orgs/:slugDeletes the organization, its entitlements and its readers. 204; 404 if there is no such organization.

A slug is 1 to 63 lowercase letters, digits and dashes, starting with a letter or digit. products and features default to empty lists and hold tokens; version is required and looks like 0.1, with an optional leading v.

Readers

RouteWhat it does
GET /admin/api/readersLists readers: id, orgId, orgSlug, email, name, staff, and the linked oidcIssuer and oidcSubject.
POST /admin/api/readersAdds a reader, or moves an existing email to another organization. Body {"orgSlug", "email", "name"?, "staff"?}; leaving out staff keeps it. 201 with the reader; 404 {"error":"no such org"}.
PUT /admin/api/readers/:id/staffGrants or revokes the staff view. Body {"staff": true}. Takes effect on the reader's next request. 200 with the reader; 404 {"error":"no such reader"}. See Manage organizations, entitlements and readers.
DELETE /admin/api/readers/:idRemoves a reader. 204; 404 {"error":"no such reader"}.

Emails are unique without regard to case.

Audit log

RouteWhat it does
GET /admin/api/auditThe latest 100 entries, newest first: at, actor, action and detail.

Actions are repo.upsert, repo.access, repo.versions, build.request, org.upsert, entitlements.set, org.delete, reader.upsert, reader.staff, reader.delete and, from the seed command, seed.apply. The admin console adds settings.oidc, settings.signin, settings.editor, settings.appearance.theme, settings.appearance.pdf, github.connect, github.install, github.disconnect, editor.user, admin.setup-link and admin.setup. Details never hold a secret.

Admin console settings and setup links

RouteWhat it does
GET /admin/api/settingsThe settings stored by the admin console (values), which stored secrets are set (secrets, as "set"; never their values), any that this key cannot decrypt (unreadable), where each group of settings comes from (sources: env, console, broker or none) and the GitHub mode.
POST /admin/api/setup-linksA one-time link that makes its visitor the first staff reader. Body {"minutes": 30} (5 to 1440, default 30). 201 {"url", "expiresAt"}; 409 once a staff reader exists. See Run your site from the admin console.
GET /admin/api/appearanceThe site theme and PDF template set on the console's Appearance page: {"theme", "pdf", "version"}, each null when not set (the repository's look applies). See Change the site's look from the admin console.
PUT /admin/api/appearanceSets either or both. Body {"theme": {"preset": "ocean", "accent": "#0b6bcb"}, "pdf": {"template": "report", "toc": false}}: a field left out stays as it is, null resets it. theme takes preset, accent, accentDark, font, headingFont, radius, width, colorScheme and customCss; pdf takes the settings in Choose a PDF template plus customCss, and the named template fills in the rest. 200 with the new state; 400 for an unknown value or an accent too faint to read.
GET /admin/api/api-keysLists public API keys: id, name, prefix, scopes, audience, orgSlug, creation, expiry, last use and revocation times. Never the token. See Public API and AI assistants.
POST /admin/api/api-keysCreates a key. Body {"name": "Support bot", "scopes": ["read"], "audience": "org", "org": "acme", "expiresInDays": 90}: scopes from read, write, publish, builds; audience staff, org (with org) or public; expiresInDays 1 to 3650, or null for never (default 90). 201 with the key and its token, shown only here.
DELETE /admin/api/api-keys/:idRevokes a key at once. 204; 404 when there is no such active key.

Analytics and feedback

RouteWhat it does
GET /admin/api/analytics/summaryTotals, views by surface, top pages, top searches, searches with no results, and the share of "helpful" answers per page, least helpful first. enabled says whether recording is on.
GET /admin/api/analytics/feedbackFeedback answers, newest first: at, topicId, helpful, comment, surface, orgSlug and releaseLine.
GET /admin/api/analytics/feedback.csvThe same list as a CSV download, for a spreadsheet.
QueryDefaultRules
days30Look back this many days, 1 to 3650.
limit20; 100 for feedbackRows per list: at most 1000 in the summary, 10000 for feedback.
orgallOnly this organization's readers, by slug.
topicallFeedback only: one topic id, such as topics/install.
comments0Feedback only: 1 lists only answers with a comment.

Comments are reader text: the JSON returns them as written, and the CSV quotes every field and prefixes values that start with =, +, - or @ with an apostrophe, so a spreadsheet does not run them as formulas. Escape comments before showing them in any web page of your own.