# Quickstart: find it by meaning, prove it by path

Source: https://docs.littlebigbrain.com/start/quickstart/

This quickstart answers one question: **which release fixed the login bug?**

You write a few support tickets, bugs and releases as JSON. Search by meaning
finds the right ticket, although the question uses other words.

A graph query then follows the ticket's links to the release. Both reads name
the same commit, so you can check the answer later.

```text
"users cannot sign in after the upgrade"
   │  search by meaning
   ▼
ticket 4821: login fails after 3.1
   └─ REPORTED_AS → bug AUTH-112: session token expiry
                       └─ FIXED_IN → Release 3.2
```

It takes about ten minutes. You need no RDF or graph database experience.

To start from your own data, see [Load your data](https://docs.littlebigbrain.com/start/load-data/). It loads a
database and documents, with examples for five use cases.

## 1. Get your credentials

Open the [console](https://cloud.littlebigbrain.com). If you are signed out,
it opens the WorkOS sign-in page automatically. Choose **Sign up** there if
you need an account.

Once signed in, create a stack and open **Connect**. Copy the **complete
endpoint** and a stack API key. Then set them in your terminal:

```sh
export LBB_URL="https://<your-complete-stack-host>"
export LBB_API_KEY="<your-stack-api-key>"
```

Use the exact endpoint from Connect. It looks like
`https://<tenant-short-id>--<stack-slug>.db.eu.littlebigbrain.com`. Keep the key
on your server or your machine. Never put it in a browser bundle.

Install a client, or use cURL. The cURL steps also use
[jq](https://jqlang.org) to read fields from JSON responses.

### TypeScript

```sh
npm install @littlebigbrain/client
```

Create `quickstart.mts`. Add the snippet of each step to the end of the file.
Run it with `npx tsx quickstart.mts` after any step. Each call is safe to
repeat.

### Python

```sh
pip install littlebigbrain
```

Create `quickstart.py`. Add the snippet of each step to the end of the file.
Run it with `python3 quickstart.py` after any step. Each call is safe to
repeat.

### cURL

Run each block in the terminal where you set your credentials. Each call is
safe to repeat.

## 2. Describe your data

A graph accepts the record types, links and fields you declare. This works
like a table schema. A write that does not fit is refused, so bad data does not
get in.

| Name | Kind | Meaning |
| --- | --- | --- |
| `Ticket`, `Bug`, `Release` | Record types | The three kinds of records. |
| `REPORTED_AS` | Link | A ticket is reported as a bug. |
| `FIXED_IN` | Link | A bug is fixed in a release. |
| `description` | Text field | Free text on a record. |

The graph `support` is created by the first call. A graph is one dataset in
your stack, like a database or an index. [Load your data](https://docs.littlebigbrain.com/start/load-data/)
explains the schema and the other ways to load data.

### TypeScript

```ts
import { LbbClient } from "@littlebigbrain/client";

const lbb = new LbbClient({
  baseUrl: process.env.LBB_URL!,
  apiKey: process.env.LBB_API_KEY!,
  graph: "support",
});

await lbb.ontology.define({
  format: "spec",
  merge_default: false,
  source: JSON.stringify({
    entity_types: ["Ticket", "Bug", "Release"],
    relation_types: [
      { name: "REPORTED_AS", source: "Ticket", target: "Bug" },
      { name: "FIXED_IN", source: "Bug", target: "Release" },
    ],
  }),
});
await lbb.ontology.evolve({
  ops: [{ op: "add_property", name: "description", value_type: "text" }],
});
```

### Python

```python
import json
import os
import time

from lbb import LbbClient

lbb = LbbClient(os.environ["LBB_URL"], api_key=os.environ["LBB_API_KEY"], graph="support")

lbb.ontology.define({
    "format": "spec",
    "merge_default": False,
    "source": json.dumps({
        "entity_types": ["Ticket", "Bug", "Release"],
        "relation_types": [
            {"name": "REPORTED_AS", "source": "Ticket", "target": "Bug"},
            {"name": "FIXED_IN", "source": "Bug", "target": "Release"},
        ],
    }),
})
lbb.ontology.evolve({
    "ops": [{"op": "add_property", "name": "description", "value_type": "text"}],
})
```

### cURL

```sh
curl --fail-with-body -sS -X POST "$LBB_URL/v1/ontology/define?graph=support" \
  -H "Authorization: Bearer $LBB_API_KEY" -H "Content-Type: application/json" \
  -d '{"format": "spec", "merge_default": false,
       "source": "{\"entity_types\": [\"Ticket\", \"Bug\", \"Release\"], \"relation_types\": [{\"name\": \"REPORTED_AS\", \"source\": \"Ticket\", \"target\": \"Bug\"}, {\"name\": \"FIXED_IN\", \"source\": \"Bug\", \"target\": \"Release\"}]}"}'

curl --fail-with-body -sS -X POST "$LBB_URL/v1/ontology/evolve?graph=support" \
  -H "Authorization: Bearer $LBB_API_KEY" -H "Content-Type: application/json" \
  -d '{"ops": [{"op": "add_property", "name": "description", "value_type": "text"}]}'
```

## 3. Write records

A write is a **commit**. It holds links between records (`triplets`) and field
values (`entity_properties`). A record is its type plus a `key`, and it has a
`name`. Each link can carry `evidence`, the reason it was recorded.

After the write, wait until it is published. Search reads published data. This
usually takes a second or two.

### TypeScript

```ts
const login = { type: "Ticket", key: "4821", name: "login fails after 3.1" };
const invoice = { type: "Ticket", key: "4822", name: "invoice PDF is blank" };
const csvExport = { type: "Ticket", key: "4823", name: "CSV export times out" };
const auth = { type: "Bug", key: "AUTH-112", name: "session token expiry" };
const pdf = { type: "Bug", key: "BILL-7", name: "PDF fonts missing" };
const slowExport = { type: "Bug", key: "DATA-58", name: "export runs on one thread" };
const r32 = { type: "Release", key: "3.2", name: "Release 3.2" };
const r311 = { type: "Release", key: "3.1.1", name: "Release 3.1.1" };

const written = await lbb.graph("support").facts.create(
  {
    triplets: [
      { source: login, relation: "REPORTED_AS", target: auth, evidence: "Support triage, 2 Sep" },
      { source: invoice, relation: "REPORTED_AS", target: pdf, evidence: "Support triage, 3 Sep" },
      { source: csvExport, relation: "REPORTED_AS", target: slowExport, evidence: "Support triage, 4 Sep" },
      { source: auth, relation: "FIXED_IN", target: r32, evidence: "Release notes 3.2" },
      { source: pdf, relation: "FIXED_IN", target: r311, evidence: "Release notes 3.1.1" },
    ],
    entity_properties: [
      { ...login, properties: { description: "Since 3.1, people are sent back to the login screen right after they type their password." } },
      { ...invoice, properties: { description: "Downloaded invoices open as empty pages." } },
      { ...csvExport, properties: { description: "Exports with more than 10,000 rows stop after 30 seconds." } },
    ],
  },
  { idempotencyKey: "quickstart-support-1" },
);
await lbb.waitForPublished(written.commit_seq);
console.log("commit", written.commit_seq);
```

### Python

```python
login = {"type": "Ticket", "key": "4821", "name": "login fails after 3.1"}
invoice = {"type": "Ticket", "key": "4822", "name": "invoice PDF is blank"}
csv_export = {"type": "Ticket", "key": "4823", "name": "CSV export times out"}
auth = {"type": "Bug", "key": "AUTH-112", "name": "session token expiry"}
pdf = {"type": "Bug", "key": "BILL-7", "name": "PDF fonts missing"}
slow_export = {"type": "Bug", "key": "DATA-58", "name": "export runs on one thread"}
r32 = {"type": "Release", "key": "3.2", "name": "Release 3.2"}
r311 = {"type": "Release", "key": "3.1.1", "name": "Release 3.1.1"}

written = lbb.graph("support").facts.create({
    "triplets": [
        {"source": login, "relation": "REPORTED_AS", "target": auth, "evidence": "Support triage, 2 Sep"},
        {"source": invoice, "relation": "REPORTED_AS", "target": pdf, "evidence": "Support triage, 3 Sep"},
        {"source": csv_export, "relation": "REPORTED_AS", "target": slow_export, "evidence": "Support triage, 4 Sep"},
        {"source": auth, "relation": "FIXED_IN", "target": r32, "evidence": "Release notes 3.2"},
        {"source": pdf, "relation": "FIXED_IN", "target": r311, "evidence": "Release notes 3.1.1"},
    ],
    "entity_properties": [
        {**login, "properties": {"description": "Since 3.1, people are sent back to the login screen right after they type their password."}},
        {**invoice, "properties": {"description": "Downloaded invoices open as empty pages."}},
        {**csv_export, "properties": {"description": "Exports with more than 10,000 rows stop after 30 seconds."}},
    ],
}, idempotency_key="quickstart-support-1")
lbb.wait_for_published(written["commit_seq"])
print("commit", written["commit_seq"])
```

### cURL

Save the records as `records.json`:

```json
{
  "triplets": [
    {"source": {"type": "Ticket", "key": "4821", "name": "login fails after 3.1"},
     "relation": "REPORTED_AS",
     "target": {"type": "Bug", "key": "AUTH-112", "name": "session token expiry"},
     "evidence": "Support triage, 2 Sep"},
    {"source": {"type": "Ticket", "key": "4822", "name": "invoice PDF is blank"},
     "relation": "REPORTED_AS",
     "target": {"type": "Bug", "key": "BILL-7", "name": "PDF fonts missing"},
     "evidence": "Support triage, 3 Sep"},
    {"source": {"type": "Ticket", "key": "4823", "name": "CSV export times out"},
     "relation": "REPORTED_AS",
     "target": {"type": "Bug", "key": "DATA-58", "name": "export runs on one thread"},
     "evidence": "Support triage, 4 Sep"},
    {"source": {"type": "Bug", "key": "AUTH-112", "name": "session token expiry"},
     "relation": "FIXED_IN",
     "target": {"type": "Release", "key": "3.2", "name": "Release 3.2"},
     "evidence": "Release notes 3.2"},
    {"source": {"type": "Bug", "key": "BILL-7", "name": "PDF fonts missing"},
     "relation": "FIXED_IN",
     "target": {"type": "Release", "key": "3.1.1", "name": "Release 3.1.1"},
     "evidence": "Release notes 3.1.1"}
  ],
  "entity_properties": [
    {"type": "Ticket", "key": "4821", "name": "login fails after 3.1",
     "properties": {"description": "Since 3.1, people are sent back to the login screen right after they type their password."}},
    {"type": "Ticket", "key": "4822", "name": "invoice PDF is blank",
     "properties": {"description": "Downloaded invoices open as empty pages."}},
    {"type": "Ticket", "key": "4823", "name": "CSV export times out",
     "properties": {"description": "Exports with more than 10,000 rows stop after 30 seconds."}}
  ]
}
```

Commit it, then wait until the graph reports `"state": "current"`:

```sh
curl --fail-with-body -sS -X POST "$LBB_URL/v1/graph/commit?graph=support" \
  -H "Authorization: Bearer $LBB_API_KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: quickstart-support-1" \
  --data-binary @records.json

until curl -sS "$LBB_URL/v1/graph/publication-status?graph=support" \
  -H "Authorization: Bearer $LBB_API_KEY" | grep -q '"state": *"current"'; do sleep 1; done
```

The response reports `commit_seq`, the number of this commit. On a new graph
it is `1`.

If you change the records, change the idempotency key too. A reused key
returns the first write with `replayed: true` and stores nothing new.

## 4. Turn on search for tickets

Declare an **embedding** on the `Ticket` type and name the fields to embed. The
platform builds one text per ticket, embeds it and stores the vectors. When a
ticket changes, its vector is updated. You run no embedding model and no vector
store.

`https://littlebigbrain.com/class/ticket` is the global name of the `Ticket`
type. Each type you declare gets one: its name in lowercase after
`https://littlebigbrain.com/class/`. `label` is the record's `name`.

The first build runs in the background. Wait until the embedding reports
`serving`.

### TypeScript

```ts
const TICKET = "https://littlebigbrain.com/class/ticket";

await lbb.embeddings.declare({ class: TICKET, from: ["label", "description"] });
while (!(await lbb.embeddings.get("ticket")).serving) {
  await new Promise((resolve) => setTimeout(resolve, 2_000));
}
```

### Python

```python
TICKET = "https://littlebigbrain.com/class/ticket"

lbb.embeddings.declare(TICKET, from_=["label", "description"])
while not lbb.embeddings.get("ticket").get("serving"):
    time.sleep(2)
```

### cURL

```sh
curl --fail-with-body -sS -X PUT "$LBB_URL/v1/embeddings?graph=support" \
  -H "Authorization: Bearer $LBB_API_KEY" -H "Content-Type: application/json" \
  -d '{"class": "https://littlebigbrain.com/class/ticket", "from": ["label", "description"]}'

until curl -sS "$LBB_URL/v1/embeddings?graph=support&name=ticket" \
  -H "Authorization: Bearer $LBB_API_KEY" | grep -q '"serving": *{'; do sleep 2; done
```

## 5. Search by meaning

Ask the question in your own words. The question says "sign in" and
"upgrade". The ticket says "login" and "3.1".

### TypeScript

```ts
const found = await lbb.embeddings.search({
  text: "users cannot sign in after the upgrade",
  top_k: 3,
});
for (const hit of found.hits) console.log(hit.label);
```

### Python

```python
found = lbb.embeddings.search("users cannot sign in after the upgrade", top_k=3)
for hit in found["hits"]:
    print(hit["label"])
```

### cURL

```sh
curl --fail-with-body -sS -X POST "$LBB_URL/v1/search?graph=support" \
  -H "Authorization: Bearer $LBB_API_KEY" -H "Content-Type: application/json" \
  -d '{"text": "users cannot sign in after the upgrade", "top_k": 3}' \
  -o found.json

jq -r '.hits[].label' found.json
```

The login ticket comes first:

```text
login fails after 3.1
```

The other two tickets follow, in an order that depends on the model. Each hit
also has a `score` and an `iri`, the global name of that record. The response
field `served_at_seq` is the commit the search read.

## 6. Follow the path to the answer

Similarity found a starting point. It does not prove which release fixed the
bug. The stored links do. This query starts at the ticket the search found and
follows two links.

The query language is SPARQL, the standard for graph queries. Each line in the
braces is a pattern: a record, a link, then another record.

Names that start with `?` are variables. The query returns the values that fit
all patterns at once. In Cypher, the same path reads
`(t)-[:REPORTED_AS]->(b)-[:FIXED_IN]->(r)`.

- `dr:reported_as` is your `REPORTED_AS` link. `dr:` is short for
  `https://littlebigbrain.com/r/`, where the links you declare live.
- `rdfs:label` is the `name` of a record.
- `as_of_commit_seq` runs the query at the commit the search read. Search and
  path then agree on one version of the data.

### TypeScript

```ts
const ticket = found.hits[0].iri;
const query = `
  PREFIX dr: <https://littlebigbrain.com/r/>
  PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
  SELECT ?bug ?release WHERE {
    <${ticket}> dr:reported_as ?b .
    ?b rdfs:label ?bug ; dr:fixed_in ?r .
    ?r rdfs:label ?release .
  }`;

const { rows, snapshot } = await lbb.sparqlRows({ query, as_of_commit_seq: found.served_at_seq });
console.log(rows[0], "read at commit", snapshot?.served_at_seq);
```

### Python

```python
ticket = found["hits"][0]["iri"]
query = f"""
PREFIX dr: <https://littlebigbrain.com/r/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?bug ?release WHERE {{
  <{ticket}> dr:reported_as ?b .
  ?b rdfs:label ?bug ; dr:fixed_in ?r .
  ?r rdfs:label ?release .
}}"""

path = lbb.sparql(query, as_of_commit_seq=found["served_at_seq"])
print(path.rows()[0], "read at commit", path.snapshot["served_at_seq"])
```

### cURL

```sh
TICKET=$(jq -r '.hits[0].iri' found.json)
SEQ=$(jq -r '.served_at_seq' found.json)

curl --fail-with-body -sS -G "$LBB_URL/sparql?graph=support&as_of_commit_seq=$SEQ" \
  -H "Authorization: Bearer $LBB_API_KEY" \
  -H "Accept: application/sparql-results+json" \
  --data-urlencode "query=
    PREFIX dr: <https://littlebigbrain.com/r/>
    PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
    SELECT ?bug ?release WHERE {
      <$TICKET> dr:reported_as ?b .
      ?b rdfs:label ?bug ; dr:fixed_in ?r .
      ?r rdfs:label ?release .
    }"
```

The TypeScript example prints this on a new graph:

```text
{ bug: 'session token expiry', release: 'Release 3.2' } read at commit 1
```

Python prints the same row. cURL returns it in SPARQL Results JSON, under
`results.bindings`.

## 7. Keep the evidence

Your agent can now answer "Release 3.2 fixed it" and show why. Store these
with the answer:

- the question and the search hits,
- the query and the rows it returned,
- the commit number, `served_at_seq`.

Later writes do not change that commit. Run the same query with the same
`as_of_commit_seq` later. It returns the same rows while the commit is retained.
[Replay an answer](https://docs.littlebigbrain.com/guides/time-travel-audit/) covers retention and history.

## If something goes wrong

| What you see | What to do |
| --- | --- |
| `401` | The API key is missing, wrong or revoked. Export a current key in the same terminal. |
| `stack_endpoint_mismatch` or `stack_endpoint_required` | Copy the complete endpoint from the same stack's Connect view. |
| `400` with `no predicate named description` when you declare the embedding | The write is not published yet. Wait for it, then declare again. |
| `409 embedding_not_ready` | The first build is still running. Wait until the embedding reports `serving`. |
| Edited records do not appear, and the commit reports `replayed: true` | The idempotency key was reused. Use a new key for the changed records. |
| A retryable `429` | Wait for the time in `Retry-After`, then retry. |

## Next steps

- [Core concepts](https://docs.littlebigbrain.com/start/concepts/): the model, mapped from vector stores and graph databases.
- [Search by meaning](https://docs.littlebigbrain.com/guides/search-by-meaning/): filter hits by type and by link.
- [Build with JSON records](https://docs.littlebigbrain.com/guides/without-rdf/): validate writes and follow a chain of any length.
- [Connect an agent](https://docs.littlebigbrain.com/sdks/mcp/): give an agent the same tools through MCP.
- [Build a workflow](https://docs.littlebigbrain.com/start/first-workflow/): test a workflow on two records, then let it fill values from their text.
