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:

SH
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:

SH
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:

SH
dots dotabases schema add-property reading-list --file status-property.json

Read the schema back whenever you are generating types or validating an import:

SH
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:

SH
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:

SH
dots rows update reading-list READ-1 --set status=read --add-tags psychology

Read Them Back

SH
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:

SH
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:

SH
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:

SH
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 1edges 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:

SH
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:

SH
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 spans the workspace and can be narrowed to one Dotabase:

SH
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:

SH
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.

HTTP
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:

TS
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

Connected Records23

Documented by
Mentions
DIIICE
DOT
References
Required by
23 connections.

Where To Go Next

Reading this as an agent?For Agents
Something wrong on this page?Dots On GitHub