TerminusDB is a database built for collaboration. In this quickstart you will clone a pre-populated dataset and immediately use the git-for-data workflow: branch, edit, diff, and merge. No schema to write, no data to invent.
Set it up with an AI agent
TerminusDB is local-first: the database runs on your own machine, and with Versioned Search (12.1 release candidate) the embeddings can run on your own hardware too, with no API key.
If you use an AI coding agent such as Claude Code, Codex or Cursor, you can paste this prompt and let it do the setup for you. It uses the docker-compose.yml in the vectorsearch folder of the TerminusDB repository. That is the 12.1 release candidate, with built-in vector search and local embeddings (no API key). Read the steps before you run them, as the agent will run commands on your machine.
Set up a local TerminusDB for me and check that it works. Use Docker Compose.
1. Make a new empty folder called terminusdb-local and work in it.
2. Download these three files from the terminusdb/terminusdb repository, main branch, folder vectorsearch:
https://raw.githubusercontent.com/terminusdb/terminusdb/main/vectorsearch/docker-compose.yml
https://raw.githubusercontent.com/terminusdb/terminusdb/main/vectorsearch/.env.example
https://raw.githubusercontent.com/terminusdb/terminusdb/main/vectorsearch/README.md
3. Copy .env.example to .env. Set TERMINUSDB_ADMIN_PASS to a new random password.
Leave OPENAI_KEY empty. Do not print the password in your answer.
4. Run: docker compose up -d
Then run: docker compose ps
5. Wait until http://localhost:6363/api/info answers with HTTP 200 using basic auth
admin:<password>. Try for up to 60 seconds.
Then wait until the embeddings model is ready — docker compose logs embeddings-init
should end with "Model ready." This can take a few minutes on first start.
6. Create a database:
POST /api/db/admin/agent_demo
body: {"label": "Agent demo", "comment": "Made by my AI agent"}
7. Add a tiny schema. The @metadata.embedding block is what makes documents
searchable — the query fetches the fields, the template renders them into text:
POST /api/document/admin/agent_demo?graph_type=schema&author=agent&message=Add+schema
body: {"@type": "Class", "@id": "Person", "name": "xsd:string",
"friends": {"@type": "Set", "@class": "Person"},
"@metadata": {"embedding": {
"query": "query($id: ID){ Person(id: $id) { name } }",
"template": "{{name}}"}}}
8. Insert two documents:
POST /api/document/admin/agent_demo?author=agent&message=Add+people
body: [{"@type": "Person", "@id": "Person/ada", "name": "Ada", "friends": []},
{"@type": "Person", "@id": "Person/alan", "name": "Alan", "friends": ["Person/ada"]}]
9. Run one query with GraphQL:
POST /api/graphql/admin/agent_demo
body: {"query": "{ Person { name friends { name } } }"}
10. Index and run one vector search:
POST /api/index/admin/agent_demo (empty body)
then GET /api/search/admin/agent_demo?q=person+named+Ada&count=2
Check the index status with GET /api/index/admin/agent_demo:
searchable_documents should be 2, not 0.
11. Report back to me: which commands you ran, the HTTP status of each call,
the GraphQL result, the search hits, and how to stop everything (docker compose down).
If a step failed, show the error and stop. Do not guess.
Docs: https://terminusdb.com/docs/get-started/ and https://terminusdb.com/llms.txtThe 12.1 release candidate image reports "version": "12.0.8" in /api/info. That is expected. Step 10 (the manual index call) is optional. It makes the index ready before the first search. Without it, the first search starts the catch-up and may return no results at first.
Automatic embeddings need three things
Automatic embeddings only work when:
- the server runs with
TERMINUSDB_INDEXER_BACKEND=vectorlink(the vectorsearchdocker-compose.ymlsets this for you), - the class has an
@metadata.embeddingblock with aqueryand atemplate, as in step 7, - and the documents were committed after both were in place. Documents that already exist are indexed on the first search. This can take a moment, so retry if the first search is empty. To do it in advance, run
POST /api/index/{org}/{db}once.
See How automatic indexing works for the details and a troubleshooting list.
The rest of this page does the same kind of thing by hand, with a single docker run command.
Step 1 — Start TerminusDB
Pull and run TerminusDB in one command:
docker run --pull always -d -p 127.0.0.1:6363:6363 \
-v terminusdb_storage:/app/terminusdb/storage \
--name terminusdb terminusdb/terminusdb-server:v12Wait a few seconds for the server to start, then confirm it is running:
Loading…You should see a JSON response with "authority": "terminusdb://system/data/User/admin" confirming the server is ready.
Not working?
If the command hangs or returns a connection error: Docker may not be running, port 6363 may already be in use, or a previous container named terminusdb may already exist. See Troubleshooting Connection Failures and Authentication Errors for solutions.
Step 2 — Clone a ready-made dataset
Skip the manual database creation and start with real data immediately by cloning a data product to your local terminusdb running in docker:
Clone Quickstart Database
Get the Star Wars dataset on your local TerminusDB — ready to branch and diff.
You now have a fully populated Star Wars database ready to explore — characters, films, planets, starships, and species. Use it to try the full git-for-data workflow: what would have changed if Anakin Skywalker had turned to the Dark Side?
Step 3 — Create a branch
Create a what-if branch to experiment without affecting the original data:
Loading…Expected output:
{
"@type": "api:BranchResponse",
"api:status": "api:success"
}You now have two branches: main (the cloned data, unchanged) and what-if (a copy, ready for edits).
Step 4 — Edit on the branch
Pose a "what if" — what if Anakin Skywalker fell to the Dark Side? First, fetch his current document:
Loading…Now PUT it back with four fields changed to tell the Dark Side story: eye_color → yellow, mass → 120, quote → "I am your father.", side → Dark Side:
Loading…Expected output:
[ "Person/Anakin%20Skywalker" ]
Why send the full document?
TerminusDB's PUT replaces the entire document. If you omit optional fields, they become null — and will appear in the diff. By sending the full document with only target fields changed, the diff shows exactly what you intended. TerminusDB detects which fields actually changed, regardless of what you sent.
The edit lives only on what-if. On main, Anakin is still himself — blue eyes, Light Side, mass 84, original quote. The branch is your sandbox.
Step 5 — Diff the branches
This is the moment. See exactly what changed between main and what-if:
Loading…Expected output:
[
{
"@id": "Person/Anakin%20Skywalker",
"eye_color": {
"@op": "SwapValue",
"@before": "blue",
"@after": "yellow"
},
"mass": {
"@op": "SwapValue",
"@before": 84,
"@after": 120
},
"quote": {
"@op": "SwapValue",
"@before": "This is where the fun begins.",
"@after": "I am your father."
},
"side": {
"@op": "SwapValue",
"@before": "Light Side",
"@after": "Dark Side"
}
}
]What just happened? TerminusDB computed a structural diff between two branches. This is not a line-by-line text diff — it is a semantic patch that knows exactly which fields changed, what the old values were, and what the new values are. Each change is a SwapValue operation that can be applied, reversed, or composed with other patches.
Four fields changed: eye_color, mass, quote, and side. You sent the full document, but TerminusDB detected only the differences — no manual tracking, no event sourcing, no change-data-capture pipeline. The database does it natively.
Step 6 — Merge the branch
Apply the changes from what-if back to main:
Loading…Expected output:
{
"@type": "api:ApplyResponse",
"api:status": "api:success"
}Done. The main branch now reflects the "what if" scenario — Anakin has turned to the Dark Side.
What you just did
In 6 steps you used the full git-for-data workflow:
| Step | Git equivalent | TerminusDB API |
|---|---|---|
| Clone dataset | git clone | POST /api/clone |
| Create branch | git branch | POST /api/branch |
| Edit on branch | commit on branch | PUT /api/document |
| Diff branches | git diff main..what-if | POST /api/diff/{org}/{db} with before_data_version / after_data_version |
| Merge | git merge --squash | POST /api/apply with before_commit / after_commit |
Step 7 — Clean up
Delete the cloned database when you are done experimenting:
Loading…To stop TerminusDB entirely:
docker stop terminusdb && docker rm terminusdbYour data persists in the terminusdb_storage Docker volume. To remove it entirely: docker volume rm terminusdb_storage.
Next steps
- Build from scratch (15 min) — Create a database, insert data, branch, diff, and merge manually — the full walkthrough
- Explore a Real Dataset — Clone a Star Wars database and query relationships, branch, and diff on rich data
- TypeScript Client Quickstart — Same workflow using the TypeScript SDK
- Python Client Quickstart — Same workflow using the Python SDK
- Your First Schema — Add type safety when you are ready
- JSON Diff and Patch — Deep dive into structural diff operations
- What is TerminusDB? — Understand the architecture behind what you just did
Other ways to run and use TerminusDB
- Install from source code — build the server yourself from GitHub
- Docker container install — more options for the Docker setup you used above
- GraphQL reference — query your data with the GraphQL schema that TerminusDB makes for you
- CLI commands — work with databases from the command line
- WOQL — the datalog query language of TerminusDB
- TerminusDB Cloud — a hosted server with a visual dashboard
Troubleshooting
Error: Connection refused
You see:
curl: (7) Failed to connect to localhost port 6363 after 0 ms: Connection refusedCause: TerminusDB is not running, or another process is using port 6363.
Fix:
# Check if the container is running
docker ps | grep terminusdb
# If not running, start it
docker start terminusdb
# If it doesn't exist, create it
docker run -d --name terminusdb -p 127.0.0.1:6363:6363 \
-v terminusdb_storage:/app/terminusdb/storage \
terminusdb/terminusdb-serverTroubleshooting Connection Failures →
Error: 401 Unauthorized
You see:
{"@type":"api:ErrorResponse","api:error":{"@type":"api:IncorrectAuthenticationError"},"api:status":"api:failure"}Cause: Wrong username or password. The default local credentials are admin / root.
Fix:
# Verify credentials work
curl -u admin:root http://localhost:6363/api/infoIf you set a custom password via TERMINUSDB_ADMIN_PASS when creating the container, use that value instead of root.
Troubleshooting Authentication Errors →
Frequently Asked Questions
Q: Do I need to define a schema before inserting data?
A: No. Using ?raw_json=true on the document endpoint lets you insert any valid JSON without a schema. TerminusDB stores it as-is. When you are ready for type safety, add a schema later — TerminusDB will validate all future inserts against it. See Schema Reference for details.
Q: Can I use TerminusDB without Docker?
A: Yes. You can build from source on Linux or macOS, or run on Kubernetes. Docker is the recommended path because it requires no dependencies and works on all platforms, but it is not the only option.
Q: How do I reset and start fresh?
A: Stop and remove the container (docker stop terminusdb && docker rm terminusdb), then remove the storage volume (docker volume rm terminusdb_storage). Restart with the original docker run command. This gives you a clean instance with no databases.