Quickstart: Developers
Evaluate Dots from the terminal: install the CLI, create a Dotabase, write and read rows, and connect them into a graph.
This page goes from nothing to a Dotabase you can query. It uses the dots CLI end
to end, so everything here is scriptable and nothing depends on the web app. It takes
about ten minutes.
It answers three questions an evaluating developer asks: what the data model looks like, how knowledge gets in, and how it comes back out in a form a program can use.
The model is three nouns. A is a typed collection with a schema of its own. A row — a — is one record in it: a title, a Markdown body, and typed properties. An edge is a directed, typed link from one row to another. Every command below reads or writes one of those three, and every one of them is a thin front-end onto an HTTP route you can call yourself.
Get The CLI
The dots command ships inside the Dots desktop app rather than a package registry.
Install the app for your platform, open a new terminal so the updated PATH is
picked up, and confirm the credential the app stored resolves:
dots auth whoami
That prints the identity and company the CLI acts as. If you are signed out,
dots auth login re-establishes the credential. Install the CLI covers the install
in full.
Two root flags decide where a command lands before anything else does. --api-url
overrides the API base URL the stored credential points at, and --company <slug>
picks the active company among your memberships. The company is a selector rather
than a field: it rides as a header, and the server injects the tenant onto every
write from the resolved actor, so no command carries a company id in its body.
dots use persists a default company once you tire of typing the flag.
Create A Dotabase
A Dotabase needs a display name, a slug, and a row-id prefix. The slug is the stable key every later command references; the prefix is what row ids are built from:
dots dotabases create --name "Reading list" --slug reading-list --prefix READ
You can seed a schema at create time with --properties-file, or add properties
later. The same command takes --views-file to seed saved views and
--behaviors-file to bind behaviors, while --no-embedding and --no-diiice switch
off the enrichment behaviors for a collection that does not want them.
A bare create ships a deliberately small schema — a required title and a rich-text
body, nothing else — and a row write naming an undeclared property is refused with
the legal keys named, never silently dropped. This walkthrough records reading status,
so declare that property before writing rows: save the two-option select definition
shown in Your First Dotabase as status-property.json, then append it:
dots dotabases schema add-property reading-list --file status-property.json
Read the schema back whenever you are generating types or validating an import:
dots dotabases describe --slug reading-list --schema-only
--schema-only prints the property roster — key, display name, type, and option ids —
instead of the full record, and --property <key> drills into one property's config.
Write Rows
Each row is a DOT: a title, a body, and typed properties. --set fills a property:
dots rows create reading-list --title "Thinking, Fast and Slow" --set status=read
dots rows create reading-list --title "The Undoing Project" --set status=to-read
As each row is written the Dotabase's default behaviors classify it into a DIIICE type and embed it for search. A fresh Dotabase creates no edges automatically; related-row connections require an explicitly bound , or edges you create yourself.
--set repeats, and the body has inputs of its own: --body for a short string,
--body-file for Markdown you keep on disk, and --json when you would rather hand
the whole record over at once. Tag on the way in with --tag, and load a batch from
one file with dots rows bulk-create --from-file. Edits work the same way:
dots rows update reading-list READ-1 --set status=read --add-tags psychology
Read Them Back
dots rows list reading-list --level scan
dots rows count reading-list
dots rows get reading-list READ-1
Those lines are the reading pattern the CLI is built around: survey the collection
cheaply, then pull the one record you chose in full. --level names how much of each
record the Markdown envelope carries. scan keeps identity — the row id, the title,
the icon. summary adds the bounded fields: the DIIICE classification, the edge
count, and every property whose value space is closed, such as a select or a date.
full is the widest record, and a row's body renders at full alone.
You get the middle tier without asking. Survey reads default to summary and print a
footer naming the tier they used, so a bare rows list is already narrowed; rows get
names one record and defaults to full, which is why the third line above carries no
flag. --level full restores the widest record anywhere the default narrowed it, and
--fields bypasses the ladder entirely for the exact allowlist you name.
rows get prints the row as an agent envelope: its fields as YAML frontmatter, its
body beneath. That is the same block an AI agent receives, so what you read in the
terminal is exactly what a model reads.
rows list filters and pages on the server rather than in your shell. --limit,
--offset, --status, --tag, and --title-contains resolve into one combined
filter group, --archived flips the scope to archived rows, and --no-body drops
the body field when you only want the index:
dots rows list reading-list --limit 10 --title-contains Undoing --no-body
Those flags choose which rows come back; --level chooses how much of each one does.
The two axes are independent and compose, so narrowing a filtered page to identity is
one more flag rather than a different command.
For a long body, read its outline before its text. rows outline prints the H1–H3
outline with per-section word counts, and rows get --sections expands only the
sections you name by their ordinal ref from that outline:
dots rows outline reading-list READ-1
dots rows get reading-list READ-1 --sections 2,4.1
--body-only prints the body with no envelope and no other fields, which is the form
you pipe into another program.
Shape The Output For A Script
Every surface defaults to md — the record envelope — piped or not. When a program is
the reader, ask for a structured format instead:
dots rows list reading-list --format json --no-agent-mode
--format accepts md, json, yaml, table, csv, and plain; json and yaml
are the full-fidelity pair. The JSON envelope is stable — records under data, source
counts and the truncation outcome under meta — so a caller always knows where to
look.
By default the CLI sends an agent marker and responses carry the Dotabase Operating
Manual as a side-channel: a compact --- DBOM --- trailer in md, a dbom key beside
data in json. --no-agent-mode opts out, and without the marker the envelope is
byte-identical to what any non-agent caller receives. Three more root flags trim the
payload beside --level: --fields projects an allowlist of fields onto each record
and switches tiering off in the process, --depth expands one hop of edges and takes
only 0 or 1 — edges list is the path for anything further, and a larger number
is refused as a usage error rather than silently walked — and --token-budget caps
the output at a record boundary rather than mid-record.
Connect And Traverse
Edges are typed and directed. Draw one, then read it back from either endpoint:
dots edges create reading-list READ-2 READ-1 references
dots edges list reading-list READ-1
dots edges census reading-list
edges create takes four positionals in that order — the Dotabase, the source row,
the target row, and the edge type — so the line above records READ-2 references
READ-1. --reasoning persists a free-text rationale on the edge, and
--target-dotabase points the far end at a different Dotabase, which is how the graph
spans collections instead of staying inside one. Reads narrow the same way:
dots edges list reading-list READ-1 --direction out --type references
--direction keeps only in or out edges relative to the row. edges list is a
survey read like the others, so it answers at summary; the edge type, its direction,
and the far endpoint's id and title are identity fields surviving even --level scan,
which is why a traversal never needs to widen the read. The --reasoning text you
persisted above is a full-tier field, so pass --level full to read it back.
edges census counts every active edge incident to the Dotabase, read from the edge
table itself rather than from the denormalized per-row count — the fastest check that
an import built a graph and not just a pile of rows.
Search
Search spans the workspace and can be narrowed to one Dotabase:
dots search "thinking fast" --dotabase reading-list --limit 5
That is Universal Search: a hybrid of keyword and semantic matching over everything the
calling credential can read. --diiice filters by DIIICE type, --tag by tag,
--sort changes the ordering, and --cursor walks to the next page. To search inside
a single Dotabase, dots rows search takes the Dotabase and the query as positionals
and adds its own --mode:
dots rows search reading-list "undoing" --limit 5
Both searches are survey reads, so both answer at summary: a hit carries its id, its
title, and its DIIICE type — enough to decide what is worth opening — and rows get
pulls the row you picked back whole. That is the same scan-then-select shape the
listing section walked, and you get it from the defaults without naming a flag.
Exit Codes And Errors
A script branches on the exit code rather than parsing the message. The CLI's exit codes mirror the API's error families one to one, so scripting the CLI gives you the same outcome class as calling the API directly:
0— success.1— an unexpected error.2— a usage error: an unknown flag, a missing argument, an impossible combination.3— authentication or permission.4— not found.5— a conflict, such as a slug already taken.6— the server was unreachable.7— a configuration problem.8— transient. Retry this class, and only this class.
Data goes to stdout and every notice or error to stderr, so a redirect keeps the two
apart. --verbose appends the error code, HTTP status, details, and stack trace to
stderr; --quiet reduces an error to its bare message.
Call The API Directly
The CLI's API-backed workspace command families have no private channel: each uses a
typed HTTP route under /api/v1, and the same routes are open to any language. The
dots local family is the explicit exception — it controls the machine-local daemon
without resolving the platform API client.
POST /api/v1/dotabases/{dotabaseId}/rows
Authorization: Bearer dots_pat_...
x-dots-company: your-company-slug
Content-Type: application/json
Authentication is a personal access token presented as a bearer credential, and the
active-company selector is the x-dots-company header. A slug naming a company you
are not a member of is denied rather than quietly redirected. Responses take exactly
two shapes: a success is { "data": ... }, and a failure is
{ "error": { "code": ..., "message": ..., "details"?: ... } }.
Three route shapes differ from what their names suggest. Listing rows is a POST
rather than a GET — POST /api/v1/dotabases/{dotabaseId}/rows/query carries the
filters, sort, grouping, and paging in its body. A single row is
GET /api/v1/dotabases/{dotabaseId}/rows/{humanId}. Edges are keyed flat:
POST /api/v1/edges creates one, while a row's own edges are read at
GET /api/v1/dotabases/{dotabaseId}/rows/{humanId}/edges. Universal Search is
POST /api/v1/search.
Paging is deliberately mixed, and the response tells you which kind you are in: keyset
feeds hand back a cursor you follow until it comes back null, while offset feeds page
by limit and offset against a known total.
The public surface is described by an OpenAPI 3.1.0 document served at
GET /api/v1/openapi.json. Point a code generator at that document rather than
transcribing routes by hand — REST API Overview covers the envelopes and the full
group table.
Build On The SDK
In TypeScript the typed client replaces a hand-rolled fetch layer. @dots/sdk
exports createDotsClient, which builds one from a base URL and a token:
import { createDotsClient } from "@dots/sdk";
const client = createDotsClient({
baseUrl: "https://your-instance/api/v1",
apiKey: token,
selectedCompany: "your-company-slug",
});
const row = await client.createRow({
dotabaseId,
title: "Thinking, Fast and Slow",
});
The method surface is flat rather than namespaced: createRow, queryRows,
getRowByHumanId, createEdge, and getRowEdges sit directly on the client, with
cross-Dotabase search at client.universalSearch.search. selectedCompany becomes the
x-dots-company header; a company id is never mapped or sent, because the server
resolves the tenant from the credential plus that selector.
Three more constructor options matter for anything long-running. agentMode sends the
agent marker and routes each response's Operating Manual to an onDbom sink instead of
leaving it in your data. fetch injects your own implementation for tests or a
non-standard runtime. retry sets the retry policy, which is off for mutations by
default and stays off until you opt in with retryMutations.
Failures arrive as typed error classes — not-found, validation, auth,
permission-denied, conflict, approval-required, rate-limit, unavailable — so you branch
on the class instead of matching a message. Paging comes as async iterators:
paginateKeyset and paginateOffset cover the two conventions above, and collectAll
drains either into an array. The package resolves from a local checkout; SDK
covers its five axes and when to reach for each one.
Where To Go Next
Your First Dotabase walks the same ground more slowly and ends at the graph view in
the web app. CLI Reference is the full command surface — 47 command families and 341
commands covering rows, edges, search, , Chat, and the platform plane. Row
Envelope Format specifies the record block byte for byte. If your corpus is a
repository rather than a reading list, Query the Code Graph covers the dots graph
verbs over an ingested codebase. To let an agent do all of this on your behalf, see
Quickstart: Agents.
Quickstart: Developers · This page in the graph
- mentionstoREST API Overview
- mentionstodots mcp, dots repo, and dots graph
- mentionstoSpace
- mentionstoDIIICE
- mentionstoQuery The Code Graph
- mentionstoQuickstart: Agents
- mentionstoView
- mentionstoAgent Envelope
- mentionstoEdge
- mentionstoDOT
- mentionstoDIIICE
- mentionstoDIIICE: Data
- mentionstoDotabase
- mentionstoBehavior
- mentionstoAccess
- mentionstoDots
- mentionstoDOT
- mentionstodots rows, dots edges, and dots pages
- required bytoYour First Dotabase
- referencestoCLI Reference
- referencestoInstall The CLI
- referencesfromCLI Overview
- documented byfromCLI Row and Page Content Management