Features

Versioned documentation for product teams — and the agents they work with

Docxoft treats a document the way your build treats a package: versioned, immutable, and depended on by name. Here is everything it does, grouped by the question it answers.

Docs as contracts

Versioned contracts, not pages that quietly change

A wiki page has one state. A document other teams build against has as many states as there are things built against it, so every version here is numbered, immutable and explained.

Semver on every document

Markdown, OpenAPI, JSON or plain text — each is versioned x.y.z. “Which version did we build against?” is a question with a permanent answer, and ?v=2.1.3 keeps returning 2.1.3.

Immutable versions

Publishing writes a new object under a key built from ids, conditionally, so a retry can never overwrite a body that already exists. Old versions stay byte-for-byte retrievable.

A changelog per version, required

Not optional and not generated from commit messages afterwards. A consumer asking “2.3.0 to 2.5.1, is this safe?” gets prose written by whoever made the change.

Structural OpenAPI diffs

Both bodies are parsed and compared by operation, parameter, request body and response — never line by line. Reordering paths is nothing; removing an endpoint is a major.

A bump you can lose an argument about

Ask for a patch when the diff implies a major and the server answers 409 bump-too-low, naming the change. force: true overrides it and is audited as an override.

Stored exactly as authored

The bytes you wrote are the bytes an agent reads, rendered for people at read time. No lossy import step, no proprietary block format to escape from later.

Private by default

Scope and access, decided in the query rather than the route

Nothing in Docxoft is public. Every read is authenticated and filtered by a predicate that lives in the query layer, so a handler cannot forget it.

  • Workspace → project → document. Roles at every level: owner, admin and member on a workspace; owner, admin, editor and viewer on a project. A workspace owner or admin reaches every project in it; a member reaches only the projects they were assigned to.
  • 404, not 403, out of scope. A document you may not see is indistinguishable from one that does not exist — in a listing, a search hit, a manifest, a count or an error message. In scope but lacking the permission is a 403, so the two answers never blur.
  • Restricted documents. Set a document to restricted and it is readable only by the project’s owners and the people you name. A workspace admin is not automatically one of them.
  • Service tokens with a ceiling. A cds_live_… token carries read, write or publish, is shown once at creation, and is stored hashed with a secret pepper mixed in. It never exceeds what its owner can do right now, because memberships are re-resolved on every request.
  • Audited synchronously. Every mutation writes its audit row in the same database batch as the change itself. If the audit cannot be written, the change does not happen.

A token from another workspace asks for a document

GET /v1/workspaces/archivizz/projects/prd/docs/spec
Authorization: Bearer cds_live_…

HTTP/1.1 404 Not Found
Content-Type: application/problem+json

{
  "type": "/problems/not-found",
  "status": 404,
  "title": "Not found"
}

Not a 403. Existence is the thing that leaks.

Documentation your coding agents can read

An MCP server, so agents read what you ship instead of what you pasted

Pointing Claude Code, Codex or Gemini CLI at Docxoft is one file in the consumer repository. From there they have tools rather than a stale copy.

.mcp.json

{
  "mcpServers": {
    "docxoft": {
      "type": "http",
      "url": "https://docxoft.playxoft.com/mcp",
      "headers": {
        "Authorization": "Bearer ${DOCXOFT_TOKEN}"
      }
    }
  }
}
  • Streamable HTTP. No gateway to run, no local process to keep alive — an authenticated URL.
  • Reads by section. search_docs returns the matching heading or the matching OpenAPI operation with an anchor, so an agent reads one endpoint rather than compressing a 200 KB spec.
  • check_updates. One call at the start of a task answers “has anything I depend on moved, and does it break me” — with the declared range and the current version named.
  • Drafts from agents. write_doc creates a draft nobody else is served. Making it current needs publish_doc and a token minted with publish.
  • The same REST API for CI. Everything the tools do is also a /v1 endpoint, described by an OpenAPI 3.1 document generated from the schemas the server validates with — so it cannot drift from what the server accepts.

Dependency graph for API consumers

Know who breaks before you publish

Consumers declare what they read and at what range. The graph is reconciled from CI on merge, so it describes what is actually deployed rather than what somebody filled in during a migration.

.docsrc.json, committed in the consumer repository

{
  "project": "spendchat/mobile",
  "consumes": [
    { "doc": "spendchat/api/openapi", "range": "^1.0.0" },
    { "doc": "platform/standards/flutter-style",
      "range": "^2.1.0", "pin": "2.1.3" }
  ]
}
  • Reconciled from CI. A GitHub Action sends the file on every merge to the default branch, and refuses to run anywhere else — a reconcile from a pull-request branch would record a declaration nobody merged.
  • Removals actually remove. A reconcile replaces the project’s declarations wholesale, in one batch, so a dependency dropped from the file disappears from the graph instead of lingering.
  • Breaking changes fail the build. The same job reads the consumer’s updates afterwards and exits non-zero on a breaking dependency, naming the provider, the declared range and the current version.
  • Impact preview before you publish. Ask what a proposed version would do to its consumers, computed by the same arithmetic the publish itself uses — so the preview cannot disagree with the outcome.
  • Told when it moves — by email or by signed webhook. Say who should hear when a project publishes: a person by email address, a project by webhook endpoint, watching one document or all of them, on major, minor or any publish. A webhook’s signing secret is generated and shown exactly once, and every delivery carries Standard Webhooks headers you verify before you parse. Deliveries are sent as part of the publish and are recorded — the version, the status code, the attempts — so “did my endpoint get it” is a table rather than a guess.

Self-hosted documentation on Cloudflare

Run the same code on your own infrastructure

One deploy target, two stores, no service of ours in the loop. Every plan limit is unbounded when you run it yourself.

Cloudflare Workers

One Worker serves the app, the REST API and the MCP server, globally. Deployed with OpenNext; the bundle is measured against a gate on every change.

Neon Postgres and R2

Metadata, the version ledger and the search index in Postgres; bodies in object storage over the S3 API. No cache tier and no search cluster to operate.

Your own auth project

Sign-in runs through your own WorkOS AuthKit project. ID tokens are verified against public keys — there is no server SDK and no service account to hold.

Billing optional

No payments configuration means billing is simply off, with a no-op provider. A self-hosted instance is never nagged about a plan it does not have.

Apache-2.0

Patent grant included. Fork it, run it, keep it — the hosted deployment and the source are the same code.

Nothing phones home

Secrets are plain worker bindings and the deployment talks to your Neon project and your bucket. There is no telemetry endpoint pointed at us.

Stop copying specs between repositories

Create a workspace in under a minute, or read how the pieces fit together first.

Start freeRead the blog