Use the TypeScript SDK
Install @doxbrix/sdk, create a DoxbrixClient, call its projects, pages, git, keys, AI and import namespaces, and handle errors, timeouts and retries.
The @doxbrix/sdk package is a typed TypeScript client for the Doxbrix REST API. Use it to script documentation work from Node.js or another JavaScript runtime: list projects, create and update pages, search, ask the assistant, manage Git sync, and convert existing files. This page covers installation, client options, every method in each namespace, and the error and retry behavior your code needs to handle.
The dxb CLI and the Doxbrix MCP server are built on this client, so they behave the same way.
Before you begin
- A runtime with a global
fetch, such as a current Node.js LTS release, or your ownfetchimplementation passed in the options. - A personal access token (
dxb_…) with the scopes your calls need. See Create and revoke access tokens and the scope table in the REST API reference.
Install
npm install @doxbrix/sdkThe package is an ES module. Import it with import, not require.
Create a client
import { DoxbrixClient } from '@doxbrix/sdk'
const client = new DoxbrixClient({
apiUrl: 'https://app.doxbrix.com',
token: process.env.DOXBRIX_TOKEN,
})
const me = await client.me()
console.log(`Signed in as ${me.email}`)Client options
| Option | Type | Default | Description |
|---|---|---|---|
apiUrl | string | required | API origin, such as https://app.doxbrix.com. |
token | string | none | Personal access token sent as Authorization: Bearer. |
userAgent | string | doxbrix-sdk | Value of the User-Agent header. |
timeoutMs | number | 30000 | Timeout for each request attempt, in milliseconds. |
maxRetries | number | 2 | Extra attempts for requests that are safe to retry. |
fetch | function | global fetch | Custom fetch implementation. |
apiUrl must be an HTTPS origin with no path, query, fragment, or credentials. Plain HTTP is accepted only for loopback hosts such as http://localhost:3000. An invalid value throws an Error from the constructor, for example Doxbrix API URL must use HTTPS (plain HTTP is allowed only for localhost).
client.hasToken returns true when a token is configured. It does not check that the token is valid; call client.me() for that.
Common tasks
List pages and read one
const projects = await client.projects.list()
const project = projects.find((p) => p.slug === 'pocketbase-docs')!
const { data: drafts, total } = await client.pages.list(project.id, { status: 'draft', limit: 20 })
console.log(`${total} drafts`)
const page = await client.pages.get(drafts[0].id, { content: true })Create, validate, and publish a page
const markdown = '## Before you begin\n\nStop the PocketBase server before you copy `pb_data`.'
const check = await client.validate({ markdown, title: 'Back up your data' })
if (check.unknownComponents.length > 0) {
throw new Error(`Unknown components: ${check.unknownComponents.join(', ')}`)
}
const [space] = await client.projects.spaces('pocketbase-docs')
const created = await client.pages.create('pocketbase-docs', {
spaceId: space.id,
title: 'Back up your data',
content: markdown,
})
const result = await client.pages.publish(created.pageId)
console.log(result.message)pages.publish follows the project's approval rules. result.status is published when no approval is needed, or in_review when the page was submitted for review.
Search and ask the assistant
const { hits } = await client.search('pocketbase-docs', 'backup', { limit: 5 })
const answer = await client.ai.ask('pocketbase-docs', { message: 'How do I back up PocketBase?' })
console.log(answer.answer, answer.sources.map((s) => s.href))Methods
Methods that take a project accept its UUID or slug.
Top-level methods
| Method | Returns | Description |
|---|---|---|
me() | CurrentUser | The authenticated user. |
components() | ComponentCatalog | The live block catalog. |
validate({ markdown, title? }) | ValidateResult | Checks a draft without saving: ok, blockCount, blockTypes, unknownComponents, quality, metrics, and findings. |
search(project, query, { limit?, locale? }) | SearchResponse | Searches a project. query needs at least 2 characters. |
request(method, path, options?) | T | Calls any /api/v1 path directly with the client's authentication, timeout, and retry rules. |
auth
| Method | Description |
|---|---|
listTokens() | Lists your non-revoked tokens. |
createToken({ name, scopes?, expiresInDays? }) | Creates a token. The returned value is shown only once. |
revokeToken(tokenId) | Revokes a token. |
deviceStart({ name?, scopes? }), devicePoll(deviceCode) | The device sign-in flow that dxb login uses. |
projects
| Method | Description |
|---|---|
list() | Projects you can access. |
get(project) | One project. |
create({ name, slug?, description?, visibility?, editorMode?, seedTemplate? }) | Creates a project. |
getSettings(project) | Reads project basics. |
updateSettings(project, patch) | Updates only the basics you pass. Needs project:admin. |
quality(project) | Quality scores for each saved page. |
spaces(project) | Lists spaces. |
createSpace(project, { name, locale? }) | Creates a space. |
structure(project) | The navigation tree with IDs. |
structureOp(project, op) | Renames or moves one item or space. op is rename_item, move_item, rename_space, or move_space, with the fields described in the REST API reference. |
export(project) | Exports the project's files. |
pages
| Method | Description |
|---|---|
list(project, { space?, status?, limit? }) | Returns { data, total }. |
get(pageId, { content? }) | One page, with content when content is true. |
create(project, { spaceId, title, parentId?, content? }) | Creates a draft. Returns pageId, navItemId, slug, and title. |
update(pageId, { markdown }) | Replaces the page body. |
publish(pageId) | Publishes, or submits for review. |
ai, keys, git, and imports
| Method | Description |
|---|---|
ai.ask(project, { message, pageId?, locale? }) | Returns answer, sources, followUps, and resolved. Needs ai:use. |
keys.list(project) | Lists assistant keys. |
keys.create(project, { label, allowedOrigins?, allowPrivate? }) | Creates an assistant key. |
keys.revoke(project, keyId) | Revokes an assistant key. |
git.status(project) | The connection, or null when none exists. |
git.connect(project, input) | Connects a repository. Takes the fields listed under Git sync. |
git.preview(project, input) | Checks a connection without saving it. |
git.sync(project) | Pulls the latest commits now. |
git.pause(project, paused) | Pauses or resumes sync. |
git.disconnect(project) | Disconnects the repository. |
git.conflicts(project) | Lists unresolved conflicts. |
git.resolveConflict(project, pageId, keep) | Keeps the 'editor' or 'git' version. |
imports.convert(files) | Converts { path, content }[] source files into Doxbrix pages and docs.json. |
imports.file(filename, base64) | Converts one document to Markdown. |
The client also has deployments and projects.pushBundle methods. The dxb CLI uses them for pushes; use dxb push rather than calling them directly.
Errors
The client throws two error classes. Both are exported from @doxbrix/sdk.
DoxbrixApiError
Thrown for any non-2xx response.
| Property | Description |
|---|---|
message | The server's error message, or <METHOD> <path> failed with <status>. |
status | HTTP status code. |
code | Stable machine code, such as insufficient_scope. |
requestId | Value of the x-request-id header, when present. |
retryAfterSeconds | Value of the Retry-After header, when present. |
body | The parsed response body. |
Use the getters to branch on the kind of failure:
| Getter | True when status is |
|---|---|
isAuth | 401 |
isEntitlement | 402 or 403 |
isNotFound | 404 |
isConflict | 409 or 412 |
isRateLimited | 429 |
A 403 with the code insufficient_scope also sets isEntitlement, so check code first when you need to tell a missing scope from a plan limit.
DoxbrixNetworkError
Thrown for transport failures: DNS errors, refused connections, timeouts, and aborted requests. The original error is available as cause.
import { DoxbrixApiError, DoxbrixNetworkError } from '@doxbrix/sdk'
try {
await client.pages.update(pageId, { markdown })
} catch (err) {
if (err instanceof DoxbrixApiError) {
if (err.code === 'insufficient_scope') console.error('Token needs docs:write')
else if (err.isAuth) console.error('Token is invalid or expired')
else if (err.isNotFound) console.error('Page not found')
else console.error(`${err.status} ${err.code}: ${err.message} (request ${err.requestId})`)
} else if (err instanceof DoxbrixNetworkError) {
console.error('Network problem:', err.cause)
} else {
throw err
}
}Retries, timeouts, and cancellation
- What is retried. Only idempotent requests are retried:
GETandHEADby default, or any request made throughrequest()withidempotent: true. Other writes run once, so a failed write is not repeated automatically. The exception isauth.revokeToken, which is marked idempotent because revoking a token twice is safe. - When. Idempotent requests are retried after network errors and after
429,500,502,503, and504responses, up tomaxRetriesextra attempts. - Backoff. The wait before each retry starts at 250 ms and doubles, up to 2 seconds, plus a small random delay.
- Timeouts. Each attempt is cancelled after
timeoutMs. Override it for one call withrequest(method, path, { timeoutMs }). - Cancellation. Pass an
AbortSignalassignalinrequest()options. - Redirects. The client refuses redirects, so your token is never sent to another host.
const controller = new AbortController()
setTimeout(() => controller.abort(), 5_000)
const quality = await client.request('GET', '/api/v1/projects/pocketbase-docs/quality', {
signal: controller.signal,
timeoutMs: 10_000,
})