# TypeScript

Source: https://docs.littlebigbrain.com/sdks/typescript/

`@littlebigbrain/client` is the TypeScript client for little big brain. Use it
in Node.js services, API routes and edge functions. Each method calls the
[HTTP API](https://docs.littlebigbrain.com/sdks/http-api/) and returns typed JSON.

This page uses the example of the [quickstart](https://docs.littlebigbrain.com/start/quickstart/): support
tickets, bugs and releases. The sections from **Create a client** to **Pin a
commit** form one program. Add each block to the end of one file and run it
with `npx tsx`.

The reference sections after them cover the rest of the client.

The package has no dependencies. It calls the global `fetch` of Node 18+,
browsers and edge workers. Source and issues:
[github.com/littlebigbrains/lbb-typescript](https://github.com/littlebigbrains/lbb-typescript)
(Apache-2.0).

## Install

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

This page describes version 0.31.0.

## Create a client

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

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

`baseUrl` is the complete endpoint from the environment's **Connect** view in the
console. `apiKey` is an environment API key. `graph` names the graph that the client
reads and writes. Without it, the server uses `main`.

One client serves one graph. `lbb.graph("name")` returns the same namespaces
for another graph, plus `facts` for writes. `lbb.withScope({ graph: "name" })`
returns a new client with the same options.

> [!CAUTION]
Keep API keys on your server, in API routes or in edge functions with secret
bindings. Never put a key in a browser bundle. See
[Authentication](https://docs.littlebigbrain.com/start/authentication/).

| Option | Default | Meaning |
| --- | --- | --- |
| `baseUrl` | required | The environment endpoint. |
| `apiKey` | none | An environment API key (`lbb_sk_test_…` or `lbb_sk_live_…`) or a single-mode token. It is sent as a bearer token. |
| `graph` | the server's `main` | The graph, sent as `?graph=`. |
| `environment` | none | Environment slug for an account-scoped session token. Uses the compatible `stack` wire parameter. An environment key already fixes its environment. |
| `stack` | none | Deprecated alias for `environment`; conflicting values fail before a request. |
| `integrationsUrl` | `https://api.littlebigbrain.com` | The integrations API that `integrations` calls with the same key. See [Integrations for your customers](https://docs.littlebigbrain.com/guides/integrations-for-your-customers/). |
| `defaultConsistency` | the server's `eventual` | The read consistency when a call names none. See [Read your own writes](#read-your-own-writes). |
| `timeoutMs` | `120000` | The timeout of one attempt, including the response body. `0` disables it. |
| `retryBudgetMs` | `60000` | The total time that retries of one call may take. |
| `maxRetries` | `6` | The most retries of one call. |
| `retryDelayMs` | `100` | The base delay of the backoff. |
| `fetch` | the global `fetch` | Your own `fetch` implementation. |
| `onRequest`, `onResponse`, `onRetry` | none | Callbacks for request events. They never receive bodies or credentials. |
| `apiVersion` | `2026-10-21` | The value of the `lbb-version` header. An earlier date keeps the list shapes of that date; see [Lists](https://docs.littlebigbrain.com/sdks/http-api/#lists). |

## Declare types and links

A graph accepts the record types, links and fields that you declare. A write
that does not fit is refused. `ontology.define` declares types and links and
creates the graph. `ontology.evolve` adds fields.

```ts
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" },
    { op: "add_property", name: "priority", value_type: "keyword" },
  ],
});
```

Both calls are safe to repeat. `define` returns `changed: false` for a
document the graph already has. It applies an additive change, such as a new
type or a wider link.

`define` refuses a change that narrows or removes what the graph declares. The
error codes are `ontology_restrictive_change`,
`ontology_identity_breaking_change` and `ontology_unsupported_change`. Nothing
is written then.

`merge_default: false` leaves out the built-in default types. `source` also
accepts JSON-LD, Turtle, RDF/XML and CSV. `evolve(body, { dryRun: true })`
previews a change. [Build with JSON records](https://docs.littlebigbrain.com/guides/without-rdf/) covers
parent types, inverse links and allowed values.

## Write records

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

```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: { priority: "high", description: "Since 3.1, people are sent back to the login screen right after they type their password." } },
      { ...invoice, properties: { priority: "low", description: "Downloaded invoices open as empty pages." } },
      { ...csvExport, properties: { priority: "low", description: "Exports with more than 10,000 rows stop after 30 seconds." } },
    ],
  },
  { idempotencyKey: "sdk-support-1" },
);
await lbb.waitForPublished(written.commit_seq);
console.log("commit", written.commit_seq);
```

The response reports `commit_seq`, the number of the commit. `waitForPublished`
waits until the latest published data covers that commit. Search and default
reads use published data. This usually takes a second or two.

Each write carries an idempotency key. The client creates one per call, and
its own retries of that call reuse it. Pass your own `idempotencyKey` so that
a rerun of your code writes nothing new.

A second call with the same key returns the first result with
`replayed: true`. This holds even when the payload changed, so use a new key
for new data.

Field writes merge: a commit that sends one field keeps the other fields. An
unknown type, link or field fails the whole commit. So does a value of the
wrong type. A commit body is at most 1 MiB. For more, use
[NDJSON import](#import-ndjson).

## Turn on search

Declare an **embedding** on a type and name the fields to embed. The platform
builds one text per record, embeds it and keeps the vectors current. The first
build runs in the background.

```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));
}
```

`class` is the global name of the type: its name in lowercase after
`https://littlebigbrain.com/class/`. `label` is the record's `name`. The
embedding's name defaults to the last part of the class, here `ticket`.

The namespace also has `list`, `preview`, `setModel`, `refresh` and `delete`.
[Search by meaning](https://docs.littlebigbrain.com/guides/search-by-meaning/) covers each one, with the
errors and the costs.

## Search by meaning

```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.score.toFixed(3), hit.label);
```

Each hit has an `iri`, a `label`, a `score` and a `class`. The `iri` is the
global name of the record. `served_at_seq` is the commit that the search
checked each hit against.

`filter` keeps hits by type and by link. `include: ["text"]` returns the
embedded text of each hit. `explain: true` plans the search and calls no
model.

## Starter workflows

A starter workflow is a template for work that follows a type. A workflow
created from it reads, after each published commit, the records that the
commit added or changed, and does its work. An embedding is a workflow of
the starter workflow `search.embed`; fit from text is `ontology.fit`.

```ts
await lbb.workflows.starters.use({
  starter: "search.embed",
  watch: { class: TICKET, fields: ["label", "description"] },
});
const ticket = await lbb.workflows.named.get("ticket");
console.log(ticket.status.state, ticket.status.lag_commits);
for await (const workflow of lbb.workflows.named.all()) {
  console.log(workflow.name, workflow.starter);
}
```

`workflows.starters.list()` lists the starter workflows the server offers,
and `workflows.starters.use(request)` creates a workflow from one or
changes it. `workflows.triggered.preview` shows what creating one would do
without writing anything. `workflows.named` reads and changes the
workflows of the graph by name: `list({ limit, cursor })` returns one page
in the [list envelope](https://docs.littlebigbrain.com/sdks/http-api/#lists) and `all()` iterates every
workflow, `get` reads one, `run` asks it to run now, and `delete` repeats
the name as `confirm`. `use`, `pause` and a `preview` without `propose`
retry a failed call. [Starter workflows](https://docs.littlebigbrain.com/guides/starter-workflows/)
covers the fields, the status and the errors.

A developer workflow (a workflow of `workflow`) sends the records to a
workflow of your worker. `use` takes its `workflow`, `owns` and `batch`,
and `workflows.named.pause(name, paused)` pauses or resumes it.
`triggeredWorkflow()` defines the workflow that gets the trigger messages,
and `writeOwned()` commits what your code owns with `replace_owned` inside
a step, under the step's effect id, so a turn that runs again does not
write twice.
`workflows.named.put` creates or changes a workflow of your worker, with
`shards`, `route_by` and `window`, and a `watch` or a `content` trigger.
`queue(name, { cursor, limit })` lists what the trigger has not sent yet,
`runs(name, { cursor, limit, outcome })` and `allRuns(name)` its runs,
`runResults(name, { run, outcome, iri, cursor, limit })` and
`allRunResults(name)` what its runs did with each input, and
`documents(name, { q, sort, cursor, limit })` and `allDocuments(name)` the
documents of a document workflow. A `WorkflowWorker` records those results
for you: `writeOwned`, `review` of an item with a `subject`, and `requeue`
report each input with the turn's completion
([Run results](https://docs.littlebigbrain.com/guides/workflows/#run-results)). `workflows.derivations`
checks and records what one version of a workflow made of one file
(`check`, `record`); `derive(key, work)` does the check, the work and the
record.
[Developer workflows](https://docs.littlebigbrain.com/guides/starter-workflows/#developer-workflows)
covers the message and the rules, with a worked example; the repository's
`database/examples/starter-workflows/word-count.mjs` is a runnable one.

`commit` and `graph(name).facts.create` send `replace_owned` and return
`owned`; `ontology.suggestions.create` sends `facts`, and an accepted
suggestion has `facts_commit`.

A message names instances by IRI. `entityRef(iri, type, name?)` turns one
into the commit reference of that record (its `key` is the id sentinel and
the 32-hex id), so `writeOwned` reaches a keyed record too:

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

const subjects = message.instances.map((iri) => entityRef(iri, "Note"));
```

In `replace_owned` only the id counts. A link endpoint or a field row writes
its name as the record's display name, so pass the record's name as the
third argument there. `entityIdFromIri(iri)` returns the id alone.

## Files

The document workflows read the files of a graph. `files` uploads them and
reads what the parse made of them:

```ts
import { readFile } from "node:fs/promises";

const { file } = await lbb.files.upload(await readFile("manual.pdf"), {
  name: "manual.pdf",
  contentType: "application/pdf",
});
const { files } = await lbb.files.list();
const pages = await lbb.files.pages(file.sha256, { from: 0, to: 2 });
for (const line of pages.pages[0]?.lines ?? []) console.log(line.id, line.box, line.text);
```

`upload` takes a `Blob`, an `ArrayBuffer` or a `Uint8Array` of at most 1 GiB
and sends it as the raw body; the same bytes under the same name again change
nothing, so a failed call is retried. `language` (the natural language of the
content, a BCP 47 tag such as `en` or `de-CH`, at most 64 characters) and
`schema` (a name or a URL, at most 256) describe the content; the file's
`Document` stores each one given. `list()` returns the newest 1,000 files;
`list({ limit, after })` reads one page (`limit` 1 to 1,000), and the
answer's `next` is the `after` of the next page, absent at the end.
`get(sha256)` reads one file's record, and `counts()` counts the files by
parse status, as of the commit the published generation holds.
`content(sha256)` returns the bytes as a `Uint8Array`, and `delete(sha256)`
repeats the SHA-256 as `confirm`.

`content` holds the whole answer. Read a large file in chunks as they arrive
with `contentStream`, ask for one byte range with `range`, and read a file's
size, type and SHA-256 without its bytes with `head`:

```ts
import { createWriteStream } from "node:fs";

const out = createWriteStream("manual.pdf");
for await (const chunk of lbb.files.contentStream(file.sha256)) out.write(chunk);
out.end();
const lastKiB = await lbb.files.content(file.sha256, { range: { suffix: 1024 } });
const firstKiB = await lbb.files.content(file.sha256, { range: { start: 0, end: 1023 } });
const { bytes, contentType, sha256 } = await lbb.files.head(file.sha256);
```

A range with no byte of the file throws `LbbError` 416
`range_not_satisfiable`. A page
range is 0-based with `to` excluded. Also on `graph(name).files`.
`putPages(sha256, parser, { uploaded_at, status: "parsed", pages })` stores
the pages a parser of your own made (`PUT /v1/files/pages`). `uploaded_at`
is the record's (`get(sha256)`): a file deleted and uploaded again since
answers `409 file_upload_changed`. The same pages again store nothing, so a
retry is safe.
[From documents to a graph](https://docs.littlebigbrain.com/guides/documents-to-graph/) creates the
document workflows and reads the links they write.

## Follow links with SPARQL

Search finds a starting point. A graph query follows the stored links from it
to the answer. The query language is SPARQL 1.1, the W3C standard for graph
queries. [Core concepts](https://docs.littlebigbrain.com/start/concepts/#graph-queries-with-sparql) explains
the syntax.

```ts
const ticket = found.hits[0].iri;
const readAt = found.served_at_seq!;
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 } = await lbb.sparqlRows({ query, as_of_commit_seq: readAt });
console.log(rows, "read at commit", readAt);
```

With a real embedding model, the login ticket comes first. On a new graph,
the program then prints:

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

`as_of_commit_seq` runs the query at the commit the search read. Search and
query then agree on one version of the data.

## Pin a commit

Store `readAt` with the answer. Later writes do not change that commit. The
same query with the same `as_of_commit_seq` returns the same rows while the
commit is retained.

```ts
const r321 = { type: "Release", key: "3.2.1", name: "Release 3.2.1" };
const hotfix = await lbb.graph("support").facts.create(
  { triplets: [{ source: auth, relation: "FIXED_IN", target: r321, evidence: "Release notes 3.2.1" }] },
  { idempotencyKey: "sdk-support-2" },
);
await lbb.waitForPublished(hotfix.commit_seq);

const latest = await lbb.sparqlRows({ query });
const replay = await lbb.sparqlRows({ query, as_of_commit_seq: readAt });
console.log(latest.rows.length, replay.rows.length);
```

With the login ticket as the first hit, this prints `2 1`. The latest data
names two releases. The replay reads commit 1, which names one.

A pin past the newest commit returns 400 `invalid_request`. A commit that is
no longer retained returns 400 `historical_generation_unavailable`. The server
never answers from a nearby commit instead. [Replay an answer](https://docs.littlebigbrain.com/guides/time-travel-audit/)
covers retention.

## SPARQL

The client runs SPARQL 1.1 SELECT and ASK queries. The examples in this and
the next sections use the `support` graph from the program above.

| Method | Returns |
| --- | --- |
| `sparqlRows(body, opts?)` | Parsed results: `{ vars, boolean, bindings, rows, snapshot }`, with `rowPage`, `nextCursor`, `traceId`, `search` and `profile` when the server sends them. Retries a retryable `429`. |
| `sparqlText(body, opts?)` | The unparsed response. `results` holds SPARQL Results JSON as a string, next to `row_page`, `snapshot` and `next_cursor`. Retries a retryable `429`. |
| `parseSparqlResults(response)` | The parsed results of a `sparqlText` response. |
| `query.sparql(body, opts?)` | Parsed results, like `sparqlRows`. Retries a retryable `429`. |
| `query.sparqlRaw(body, opts?)` | The unparsed response, like `sparqlText`. Retries a retryable `429`. |

`rows` flattens each binding to `{ variable: value }`. `bindings` keeps the
typed terms, with `type`, `value`, `datatype` and `xml:lang`. `boolean` is the
answer of an ASK query, and `null` for SELECT. `snapshot.served_at_seq` is the
commit that an eventual or pinned read used. A plain strong read returns
`snapshot: null`.

```ts
const ask = await lbb.sparqlRows({
  query: "ASK { ?t <https://littlebigbrain.com/r/reported_as> ?b }",
});
const raw = await lbb.sparqlText({ query: "SELECT (COUNT(*) AS ?n) WHERE { ?s ?p ?o }" });
console.log(ask.boolean, parseSparqlResults(raw).rows[0].n, raw.row_page.total);
```

The body takes these fields:

| Field | Meaning |
| --- | --- |
| `query` | The SPARQL text, SELECT or ASK. |
| `as_of_commit_seq` | Run the query at this retained commit. |
| `entailment` | `none` (default), `subclass`, `rdfs` or `owl`. The others add facts that follow from the schema. See [Reasoning](https://docs.littlebigbrain.com/guides/reasoning/). |
| `limit`, `offset` | Return a window of the result rows. The response's `row_page` reports `returned`, `total` and `has_more`. |
| `cursor` | Pages that stay on one commit. `""` starts, and `next_cursor` from the response continues. The query needs one indexed, ordered `LIMIT` of 1 to 10,000, no `OFFSET`, and no `limit` or `offset` field. Other queries return 400 `invalid_pagination_cursor`; page them with `limit` and `offset`. |
| `request` | The user's words behind the query. The server then records an eval trace, and the parsed results name it in `traceId`. |
| `reason` | `true` returns a typed error. Stored rules already run when data is published. |

CONSTRUCT and DESCRIBE return RDF documents, so these methods refuse them with
400. Run them on the native `/sparql` endpoint. See
[Native SPARQL protocol](#native-sparql-protocol).

### Search inside a query

A query can find records by meaning. The pattern `?x search:similarTo "text"`
binds the records nearest to the text. The rest of the query then filters,
joins and orders them like other rows. `search:score` binds the score. The
prefix `search:` is `https://littlebigbrain.com/search#`, and the graph needs
an [embedding](#turn-on-search).

```ts
const near = await lbb.sparqlRows({
  query: `
    PREFIX search: <https://littlebigbrain.com/search#>
    PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
    SELECT ?ticket ?score WHERE {
      ?t search:similarTo "sign-in fails" ;
         search:score ?score ;
         rdfs:label ?ticket .
    } ORDER BY DESC(?score) LIMIT 3`,
});
console.log(near.rows, near.search?.plan, near.search?.complete);
```

`search` reports how the search ran: the `plan` (`nearest`, `filter_first` or
`search_first`), the hits asked for (`top`) and bound (`hits`), `complete`, and
`lag_commits`, how far the vectors trail the commit the query read.
[Search inside a SPARQL query](https://docs.littlebigbrain.com/guides/search-by-meaning/#search-inside-a-sparql-query)
lists the other patterns and explains the plans. A search query with `cursor`
returns 400. Page it with `search:top` and `LIMIT`.

> [!TIP] Global names are lowercase
The types, links and fields you declare have global names such as
`<https://littlebigbrain.com/class/ticket>`, `<https://littlebigbrain.com/r/reported_as>`
and `<https://littlebigbrain.com/p/description>`. The last part is always
lowercase. An uppercase name is a different IRI and matches nothing. IRIs that
you load with `importRdf` keep their original form.

## Questions in plain words

`query.ask` answers a question in plain words. The server describes the
graph. A decision model picks the kind of question. A reasoning model runs
queries in a short loop, reads their rows, and answers.

```ts
const result = await lbb.query.ask("Which release fixed the login bug?");
if (!result.answer) console.log("the loop stopped:", result.error);
console.log(result.answer, result.citations, result.rows);
```

| Method | Returns |
| --- | --- |
| `query.ask(question, opts?)` | Sends the question to `POST /v1/query/ask`. Returns `answer` (the text), `citations`, `chart` (how to draw the rows, or `null`), `steps`, `route`, `query`, `rationale`, the parsed `rows` and `vars` of the query that holds the answer, `error`, `traceId`, `linked` (the names the server found), `anchors` and `history`. `response` holds the whole response. `opts` takes `context`, `route`, `limit`, `asOfCommitSeq`, `today`, `includeGrounding`, `anchor` (the IRIs of records the user picked) and `timeline` (the commit of each date). `opts.mode: "route"` returns the route alone. |
| `query.askStream(question, opts?)` | Takes the arguments of `query.ask` and yields one event per step: `grounding`, `route`, `step` per tool call, `answer`, and last `done` with the response. An `error` event throws `LbbError`. Abort `opts.signal` to stop the server's work. |
| `query.names({ text, limit? })` | The records a question or a list of names names: `candidates` with `text`, `iri`, `label`, `class`, `score` and `by`, the one to prefer first. No model call. |
| `query.describe({ question?, classes?, properties? })` | The classes and properties a question needs, with how many sampled instances hold each property and the values of small classes, as JSON and as `text`. No model call. |
| `query.commitAt({ date } \| { moment })` | The commit of a date or a moment: `as_of_commit_seq`, `committed_at` and `resolved_by`, or a `note`. |
| `query.compare({ query, before, after?, key?, max_rows?, limit?, cursor? })` | Runs one `SELECT` at two points and returns `added`, `removed` and `changed` with `totals`, `truncated` and `next_cursor`. |

`traceId` names the eval trace of the query that holds the answer. The
question is its request.
[Answer questions in plain words](https://docs.littlebigbrain.com/guides/answer-questions/) covers the
routes, the options and the limits. It also lists the data that goes to the
model providers.

## Model checks and search tuning

A judge model checks a sample of the model calls LBB makes for a graph: the
search rerank, the query router and rewriter, the ontology fit and the eval
labels. `checks` reads the checks. A person can agree with a check or correct
it, and the review becomes the call's ground truth.
[Check model quality](https://docs.littlebigbrain.com/guides/check-model-quality/) explains the jobs and the
scores.

```ts
const month = await lbb.checks.summary();
console.log(month.judge?.agreement, month.jobs);

const { checks } = await lbb.checks.list({ verdict: "wrong", reviewed: false });
for (const check of checks) console.log(check.call, check.summary, check.judge.reason);

await lbb.checks.review(checks[0].call, {
  agree: false,
  verdict: "right",
  note: "The first hit answers the question.",
});
```

| Method | Returns |
| --- | --- |
| `checks.summary({ month? })` | One month per job and model: the checks, the score, right, partly and wrong, and the reviews. Also the judge's agreement with people and today's budget. |
| `checks.list(opts?)` | The month's checks, newest first. `job`, `month`, `verdict`, `reviewed`, `after` and `limit` filter and page. |
| `checks.review(callId, body)` | `{ agree: true }` keeps the judge's verdict. `{ agree: false, verdict, score?, reference?, note? }` corrects it. Returns the check. |
| `checks.export({ job?, month? })` | The month's checks as parsed JSON lines, each `{ call, check }`. |
| `checks.calls(opts?)` | The call log, newest first. `job`, `day`, `checked`, `after` and `limit` filter and page. |
| `checks.call(callId)` | One call with its input, its output and its check. |
| `checks.checkCall(callId)` | Asks the judge to check one call now. Returns `{ queued: true }`. |

A search tuning session tests other search settings on the graph's own
searches. The judge grades the hits, and the session proposes the settings
that rank best. The settings change only when you apply the proposal.

```ts
let session = await lbb.embeddings.searchTuning.start();
while (session.status === "queued" || session.status === "running") {
  await new Promise((resolve) => setTimeout(resolve, 10_000));
  session = await lbb.embeddings.searchTuning.get(session.id);
}
if (session.proposal) await lbb.embeddings.searchTuning.apply(session.id);
```

`searchTuning.list({ limit? })` lists the sessions, newest first.
`embeddings.setSearchSettings` changes the settings by hand. It takes
`rerank`, `rerank_depth`, `blend` and `probe_factor`. A field you leave out
keeps its value, and `null` sets it back to its default:

```ts
await lbb.embeddings.setSearchSettings({ rerank_depth: 60, blend: null });
```

`checks.checkCall` and `searchTuning.start` spend the platform's judge budget.
The client sends them once unless you pass `{ retry: true }`.

## Model choice: test and switch models

Each use of a model on a graph (question answers `ask`, query routing
`route`, search rerank `rerank`, ontology fit `fit`, eval labels `label`) runs
one model. A trial tests another model on the use's checked calls and scores
both models against the same ground truth. When the candidate meets the bar,
switch the use to it, and revert in one step.
[Check model quality](https://docs.littlebigbrain.com/guides/check-model-quality/) explains the scores and
the bar.

```ts
// What each use runs now, and the models you can test.
const { jobs } = await lbb.models.trials.options();

// Test Claude Haiku 5.5 at low effort on question answers.
const { trials: [trial] } = await lbb.models.trials.create({
  candidate: { provider: "anthropic", model: "claude-haiku-5-5", effort: "low" },
  jobs: ["ask"],
});

// The trial runs on the server. Wait until it compared the checked calls.
const result = await lbb.models.trials.wait(trial.id, {
  onUpdate: (t) => console.log(`${t.report.calls} calls compared`),
});
const { report } = result;
console.log(report.current.score, report.candidate.score, report.ci_low, report.ci_high);

if (report.qualifies) {
  await lbb.models.switches.create({ trial: result.id });
}

// Back to LBB's model.
await lbb.models.switches.revert("ask");
```

| Method | Returns |
| --- | --- |
| `models.trials.options()` | Per use: the model it runs now, LBB's model, a switch, its checked calls, and the models you can test with their efforts and prices. |
| `models.trials.create({ candidate, jobs?, target?, days? })` | `{ trials, skipped }`: one trial per use. An open trial of the same use and candidate comes back as it is. |
| `models.trials.wait(id, opts?)` | The trial once it compared what it can (`until: "compared"`, the default) or ended (`until: "ended"`). `onUpdate`, `pollIntervalMs`, `timeoutMs` and `signal` control the wait. |
| `models.trials.list({ job?, limit? })` | The trials, newest first, with their reports. |
| `models.trials.get(id)` | One trial with its report and its compared calls. |
| `models.trials.call(id, callId)` | One compared call: both answers and the ground truth. |
| `models.trials.stop(id)` | Stops a trial. It keeps what it compared. |
| `models.switches.create({ trial })` | Switches the trial's use to its candidate. |
| `models.switches.list()` | The switched uses. |
| `models.switches.revert(job)` | Puts the use back on LBB's model. |
| `models.choices.list({ workflow?, use?, limit?, cursor? })` | The model of each workflow step (`extract`, `decide`, `embed`): the environment's rows, and with `workflow` the row in force for each step of that workflow, with the models each can run (`options`). `all` pages through it. |
| `models.choices.put({ use, model, workflow? })` | Chooses the model of a step for the environment or one workflow. The next calls run it. The client sends an idempotency key. |
| `models.choices.delete({ use, workflow? })` | Removes a choice: the step runs the environment's model, else LBB's. Returns `{ id, object, deleted: true }`. |

A report gives both models' right share, mean score (0 to 1), cost per call
and median time; the difference with its 95% interval (`delta`, `ci_low`,
`ci_high`); and `qualifies`: at least 20 calls, at most 5 points worse at the
low end, no more failures, and cheaper or better. A switch refuses a trial
that does not qualify (`409 trial_not_qualified`) or that compared with
another model than the one the use runs now (`409 trial_outdated`): start a
new trial. Catch them as `LbbError` and read `error.code`.

## Read your own writes

Reads take a `consistency` option:

- `eventual` is the default. It reads the latest published commit, and the
  response's `snapshot.served_at_seq` names it.
- `strong` also applies the newest writes. Use it to read your own write at
  once. Its response has no `snapshot` block.

`minIndexedSeq` sets a floor: the read must cover that commit or a later one.
A floor without a `consistency` reads strong.

```ts
const note = await lbb.graph("support").facts.create({
  entity_properties: [{ ...invoice, properties: { priority: "high" } }],
});
const now = await lbb.sparqlRows(
  { query: "SELECT ?p WHERE { ?t <https://littlebigbrain.com/p/priority> ?p }" },
  { consistency: "strong", minIndexedSeq: note.commit_seq },
);
console.log(now.rows.length);
```

Publication may not cover the floor yet. Then an eventual read returns 429
`read_your_writes_pending` with a `Retry-After`. `sparqlRows`, `sparqlText`,
`query.sparql` and `query.sparqlRaw` retry it within the retry budget. You can
also call `waitForPublished` first.

Use `strong` when you need the answer now. Use `eventual` with a floor when
you must record which commit you read.

`consistency` and `minIndexedSeq` work on `sparqlRows`, `sparqlText`,
`sparql`, `summary`, `query.sparql`, `query.sparqlRaw` and `query.structured`.
`ontology.conformance` and `entities.detail` take `consistency` only.

## Load RDF

If your data is RDF already, load it as it is. `facts.importRdf` sends one
document in N-Triples, Turtle, N-Quads or TriG. [Load RDF data](https://docs.littlebigbrain.com/guides/load-rdf/)
walks through a full import.

```ts
const turtle = `
  @prefix ex: <https://example.org/> .
  @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
  ex:auth-service rdfs:label "auth-service" ; ex:writesTo ex:user-db .
  ex:user-db rdfs:label "user-db" .
`;
const imported = await lbb.graph("catalog").facts.importRdf(turtle, {
  format: "turtle",
  baseIri: "https://example.org/",
  idempotencyKey: "catalog-2026-09-25",
});
console.log(imported.triples_read, imported.committed_commit_seq);
```

The first import creates the graph with a fixed generic schema, so you declare
nothing first. Source IRIs keep their original form in queries. The response
reports `triples_read`, `committed_commit_seq`, `error_count` and the first
`errors`.

| Option | Meaning |
| --- | --- |
| `format` | `ntriples` (default), `turtle`, `nquads` or `trig`. |
| `baseIri` | The base for relative IRIs. |
| `blankNodeScope` | Scopes blank-node labels to one source document. An ASCII token of letters, digits and `-_.:/`. |
| `batch` | Statements per internal commit. Default 100,000, maximum 1,000,000. |
| `strict` | `true` fails the request at the first bad statement. By default the importer skips it and reports it in `errors`. |
| `observedAt` | An RFC 3339 time to record on every statement. |
| `build` | `false` holds back publication for this request. |
| `idempotencyKey` | The key for this request. The client creates one if you omit it. |
| `graphUri` | Refused with 400. Each graph serves the default RDF graph only. |

Keep each request under 64 MiB. N-Triples and N-Quads have one statement per
line. Split a large file on line boundaries. Turtle and TriG requests must
each be a complete document.

Omit `blankNodeScope` to keep blank-node labels the same across chunks and
retries. Reuse one scope for every chunk of one source document. Documents
that reuse the same labels then stay apart.

`facts.importRdfMany([{ rdf }, …], opts)` imports several documents in order.
Only the last request triggers publication. Pass its `finalSequence` to
`waitForPublished`. With an `idempotencyKey`, document `n` uses the key
suffixed with `:n`.

`query.update(text, opts?)` runs SPARQL Update on the `/update` endpoint. The
server accepts `INSERT DATA`, and one request is one commit. Other update
forms return 400. The client sends an idempotency key, so a retry replays the
write. The answer has no body, so read the new facts with a `strong` read.

```ts
await lbb.graph("catalog").query.update(
  'INSERT DATA { <https://example.org/user-db> <https://example.org/tier> "gold" }',
  { idempotencyKey: "catalog-tier-1" },
);
```

A graph whose first write is RDF only takes RDF writes. On it,
`facts.create`, `commitDryRun`, `facts.import`, `retract` and `reload` return
400 `rdf_native_graph`. A durable import job fails. Write JSON records first
when you need both.

## Import NDJSON

For many records, send newline-delimited JSON. Each line is a triplet, or a
record with `type`, `name`, an optional `key` and `properties`.

`facts.import(lines)` imports in one request of at most 64 MiB. The server
splits the lines into bounded commits and returns `committed_commit_seq`, the
last one.

```ts
const tickets = [
  { type: "Ticket", key: "4824", name: "search returns stale results", properties: { priority: "high" } },
  { source: { type: "Ticket", key: "4824", name: "search returns stale results" },
    relation: "REPORTED_AS", target: { type: "Bug", key: "SRCH-9", name: "cache not invalidated" } },
];
const sync = await lbb.graph("support").facts.import(tickets, { idempotencyKey: "tickets-4824" });
console.log(sync.committed_commit_seq);
```

For a large dataset, use `submitImport`. It streams an array, an iterable or
an async iterable into a durable import job. It never holds the whole dataset
in memory. The job runs on the server and survives a lost connection.

```ts
async function* records() {
  yield { type: "Ticket", key: "4825", name: "PDF export fails", properties: { priority: "low" } };
}

const accepted = await lbb.submitImport(records(), { idempotencyKey: "tickets-2026-09-25" });
const status = await lbb.waitForImportJob(accepted.job_id);
if (status.state !== "succeeded") throw new Error(status.failure?.message);
console.log(status.committed_commit_seq);
```

- `idempotencyKey` is required. It binds the key to the uploaded content.
- The client uploads once and does not retry the stream. To retry, call again
  with a fresh iterable and the same key.
- An empty source fails in the client before any request.
- `getImportJob(id)` reads the status, and `cancelImportJob(id)` cancels the
  job.
- `waitForImportJob` polls every second and takes `pollIntervalMs`,
  `timeoutMs` and `signal`.

The durable route needs the server capability `durable_import_jobs_v1`. The
client checks `GET /version` first and throws `LbbCapabilityError` if it is
missing. A request is at most 32 GiB.

Both methods take `batch`, the records per internal commit, up to 10,000. The
default is 1,000 for `facts.import` and 10,000 for a job. `strict: true` fails
at the first malformed line, which is skipped by default. `observedAt` records
an RFC 3339 time on every record.

The [HTTP API](https://docs.littlebigbrain.com/sdks/http-api/) documents the job states and failure fields.

## Structured queries

`sparql(body)` is a SELECT or ASK query written as JSON. It takes link
patterns, typed filters, `group_by`, aggregates, `having` and `order_by`. Use
it when you build a query from user input and want no query text.

```ts
const pairs = await lbb.sparql({
  patterns: [{ subject: { var: "ticket" }, predicate: "REPORTED_AS", object: { var: "bug" } }],
  select: ["ticket", "bug"],
  limit: 50,
});
console.log(pairs.solutions.length);
```

`entities.filterByAttributes` builds that body for you. The `patterns` bind
variables through links. `where` compares declared fields on those variables.
`op` is `eq` (default), `ne`, `lt`, `le`, `gt` or `ge`.

```ts
const urgent = await lbb.entities.filterByAttributes({
  patterns: [{ subject: { var: "ticket" }, predicate: "REPORTED_AS", object: { var: "bug" } }],
  where: [{ var: "ticket", field: "priority", value: "high" }],
  select: ["ticket", "bug"],
});
console.log(urgent.solutions.length);
```

A value can also be `{ dateTime: "…" }` or `{ entity: selector }`. `group_keys`
groups by a field value or by a calendar bucket of a date field. See
[Query and validate](https://docs.littlebigbrain.com/guides/sparql-and-shacl/) for the full request.

## Schema and SHACL

`ontology.view()` returns the declared types, links and fields of the graph.
With `{ counts: true }`, each link also reports how many current links it has.

For rules across fields and links, publish SHACL shapes, the W3C standard for
validation rules. `schema.publish` stores the shapes and a mode. `schema.view()`
returns the active shapes and mode.

```ts
const shapes = `
  @prefix sh: <http://www.w3.org/ns/shacl#> .
  @prefix dclass: <https://littlebigbrain.com/class/> .
  @prefix dr: <https://littlebigbrain.com/r/> .
  <https://example.org/shapes/Ticket> a sh:NodeShape ;
    sh:targetClass dclass:ticket ;
    sh:property [ sh:path dr:reported_as ; sh:maxCount 1 ;
                  sh:message "A ticket is reported as one bug at most." ] .
`;

await lbb.schema.publish({ desired_mode: "warn", shapes: { source: shapes, format: "turtle" } });
const report = await lbb.ontology.conformance({ consistency: "strong", maxRetries: 60 });
console.log(report.conforms, report.result_count, report.validated_at_seq);
if (report.conforms) {
  await lbb.schema.publish({ desired_mode: "reject", shapes: { source: shapes, format: "turtle" } });
}
```

| Mode | Behavior |
| --- | --- |
| `off` | No check at write time. |
| `warn` | Writes succeed. The conformance report lists violations. Use it while you add or repair a shape. |
| `reject` | A write that breaks a shape returns 400, and nothing is stored. Use it after the report is clean. |

After a publication, the platform validates the graph in the background.
`ontology.conformance()` reads the stored report for the active shapes and
takes no shapes of its own.

A strong read returns a retryable 429 `strong_read_pending` until the report
for new shapes exists. The client retries it after each `Retry-After`. The
first report can take several seconds, so the example raises `maxRetries`
from 6.

A change of mode publishes the same shapes as a new version.
`schema.publish(body, { dryRun: true })` previews a publication.
[Choose an enforcement mode](https://docs.littlebigbrain.com/guides/sparql-and-shacl/#choose-an-enforcement-mode)
explains when to switch.

`commitDryRun(body)` runs the same checks as a commit and writes nothing. Its
`schema_validation` lists each violation with `component`, `focus_node`,
`result_path` and `message`. An agent can read that list, fix its write and
retry.

```ts
const preview = await lbb.commitDryRun({
  triplets: [{ source: login, relation: "REPORTED_AS", target: pdf }],
});
console.log(preview.schema_validation?.conforms);
```

Ontology drafts keep a proposed schema apart from the graph.
`ontology.drafts.create(body)` takes 1 to 100 sample records and stores the
proposed operations, an analysis per competency question, coverage, pitfalls
and confidence at one commit. It writes no facts. Continue with
`ontology.drafts.validate(id)`, then `promote(id)` or `reject(id, reason)`.
`promote` sends an idempotency key and is safe to retry.

## Change and delete data

`graph(name).retract` removes current links as a new commit. `edges` names
single links. `entities` removes every link of a record.
`purge_entity_properties: true` also clears the record's fields, so it leaves
search and SPARQL. Past commits keep the retracted data.

```ts
const retracted = await lbb.graph("support").retract({
  edges: [{ source: pdf, relation: "FIXED_IN", target: r311 }],
});
console.log(retracted.commit_seq);
```

| Method | What it does |
| --- | --- |
| `graph(name).retract(body)` | Removes links, and optionally fields, as a new commit. |
| `reload(lines, { confirm, dryRun? })` | Makes the graph match the NDJSON lines in one commit. Records missing from the lines leave current state. `confirm` is the graph name. `dryRun: true` previews the change and writes nothing. |
| `forkGraph({ src, dst })` | Copies a graph into a new graph as a background job. The destination must be a new graph id. The id of a deleted graph is refused. Safe to retry. |
| `createGraph()` | Creates the client's graph with an empty schema. `ontology.define` also creates it. |
| `listGraphs()` | Lists the graphs of the environment. |
| `graph(name).delete({ confirm })` or `deleteGraph({ confirm })` | Deletes the graph, its search feedback and its pending jobs. `confirm` is the graph name. Safe to retry. |

A `reload` response carries `prior_commit_seq`. Read the graph at that commit
with `as_of_commit_seq` to see the state before the reload.

```ts
const planned = await lbb.reload(tickets, { confirm: "support", dryRun: true });
console.log(planned.prior_commit_seq);
```

## Publication status

After a commit, the platform publishes it for search and eventual reads.

| Method | Returns |
| --- | --- |
| `waitForPublished(commitSeq, opts?)` | Resolves when the latest published commit covers `commitSeq`. Throws after `timeoutMs` (default 30,000) or when publication is `blocked`. Polls every `pollIntervalMs` (default 250), or later when the server sends a longer retry delay. |
| `publicationStatus()` | `state` (`current`, `queued`, `compacting`, `building`, `publishing` or `blocked`), `head_seq`, `published_seq`, `lag_commits` and `retry`. |
| `readSnapshot()` | `head_seq`, `snapshot.served_at_seq`, `query_lag_commits`, `base_current` and `conformance_lag_commits`. |
| `summary(opts?)` | Record, link and type counts, with the commit they describe. |
| `schemaSummary()` | The types and fields found in the published data, with counts. |
| `plannerStats({ cursor?, limit? })` | The statistics the query planner uses: triple counts per link and field, and the indexes present. See [See how a query ran](https://docs.littlebigbrain.com/guides/query-profiles/). |
| `metadata()` | The ontology version and storage size of the graph. |

`base_current` is `true` when the published data reflects every commit and
schema change.

```ts
const published = await lbb.readSnapshot();
console.log(published.snapshot.served_at_seq, published.query_lag_commits, published.base_current);
```

## Errors and retries

Each method returns parsed JSON. On a non-2xx response, it throws `LbbError`.

```ts
try {
  await lbb.graph("support").facts.create({
    triplets: [{ source: login, relation: "BLOCKS", target: auth }],
  });
} catch (err) {
  if (!(err instanceof LbbError)) throw err;
  console.error(err.status, err.code, err.message);
}
```

| Field | Meaning |
| --- | --- |
| `status` | The HTTP status. |
| `code`, `type`, `message`, `param` | The error from the response body. |
| `retryable`, `retryAfterSeconds` | Whether a retry can succeed, and when. |
| `requestId`, `docUrl` | The request id to quote in support, and a link to the docs. |
| `endpointHint` | For `stack_endpoint_required` and `stack_endpoint_mismatch`: what to fix. |
| `details` | The reasons for each item of a refusal. An example is the `conflicts` of `409 starter_conflict`. |
| `body` | The raw response body. |

The client retries these requests:

- GET, HEAD and OPTIONS requests.
- Writes that carry an idempotency key: commits, imports, retracts, reloads,
  SPARQL updates, schema publications and draft promotions. The client adds
  the key when you omit it.
- The read-only POST methods `query.sparql`, `query.sparqlRaw`,
  `query.structured`, `embeddings.search`, `ontology.search`,
  `ontology.resolve` and `ontology.drafts.validate`.
- `embeddings.searchTuning.apply`, which sets the same settings again.
- Graph deletes and forks.
- A retryable `429` on `sparqlRows` and `sparqlText`. A `5xx` or a network
  failure is not retried, because the query could run twice.

Other POST and PUT calls are sent once. Examples are `ontology.define`,
`embeddings.declare` and `sparql`. Pass `{ retry: true }` to a namespace method
to override this. `retry` is `true`, `false` or `"rate_limited"`, which retries
a retryable `429` only.

A retry follows a `429`, a `5xx` or a network failure. The delay is the
`Retry-After` header, the body's `retry_after_seconds`, or a full-jitter
exponential backoff. An error that the body marks `retryable: false` is thrown
at once.

`retryBudgetMs` bounds the total retry time, and `maxRetries` caps the count.
Namespace methods take these options as their last argument: `timeoutMs`,
`maxRetries`, `retryBudgetMs`, `retry`, `signal` and `headers`. Writes also
take `idempotencyKey`.

`rawRequest(method, path, opts)` returns the response with metadata:
`requestId`, `version`, `headers`, `attempts`, `retryCount` and `elapsedMs`.
`onRetry` receives each retry with its status, error code and delay.

```ts
const response = await lbb.rawRequest("GET", "/v1/graph/summary");
console.log(response.status, response.requestId, response.attempts);
```

## Types

The package exports the types of every request and response.

| Export | What it is |
| --- | --- |
| `Schemas["Name"]` | Any shape of the API contract, such as `Schemas["SearchResponse"]`. |
| `Entity`, `EntitySelector`, `CommitRequest`, `CommitResponse`, `GraphSummary`, `GraphMetadata`, `SchemaView`, `Snapshot` | Short names for common shapes. |
| `SparqlResults`, `SparqlTerm`, `SparqlResultsJson` | Parsed SPARQL results, one typed term, and the raw results document. |
| `LbbClientOptions`, `CallOptions`, `RequestOptions`, `ReadConsistencyOptions` | The option types of the client and its methods. |
| `components`, `paths`, `operations` | The raw OpenAPI types of the contract. |

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

type Hit = Schemas["SearchHit"];
const best: Hit | undefined = found.hits[0];
```

## Method map

| Area | Methods |
| --- | --- |
| Schema | `ontology.define`, `ontology.evolve`, `ontology.view`, `ontology.conformance`, `ontology.search`, `ontology.resolve`, `schema.view`, `schema.publish` |
| Drafts | `ontology.drafts.create`, `ontology.drafts.get`, `ontology.drafts.validate`, `ontology.drafts.promote`, `ontology.drafts.reject` |
| Ontology starters | `ontology.starters.list`, `ontology.starters.get`, `ontology.starters.apply`, `ontology.starters.update`; the `starters` export: `starters.crm`, `starters.documents`, `starters.work` |
| Starter workflows | `workflows.starters.list`, `workflows.starters.use`, `workflows.triggered.preview`, `workflows.named.list`, `workflows.named.all`, `workflows.named.get`, `workflows.named.put`, `workflows.named.run`, `workflows.named.pause`, `workflows.named.queue`, `workflows.named.runs`, `workflows.named.allRuns`, `workflows.named.getRun`, `workflows.named.runStats`, `workflows.named.runResults`, `workflows.named.allRunResults`, `workflows.named.documents`, `workflows.named.allDocuments`, `workflows.named.delete`, `workflows.derivations.check`, `workflows.derivations.record`, `workflows.derivations.derive`; `graph(name).workflows` scopes each one to a graph; `triggeredWorkflow`, `writeOwned`, `parseTriggerMessage`, `entityRef` and `entityIdFromIri` for developer workflows |
| Files | `files.upload`, `files.list`, `files.get`, `files.counts`, `files.content`, `files.contentStream`, `files.head`, `files.pages`, `files.delete` |
| Suggestions | `ontology.suggestions.list`, `ontology.suggestions.get`, `ontology.suggestions.create`, `ontology.suggestions.validate`, `ontology.suggestions.accept`, `ontology.suggestions.dismiss`, `ontology.suggestions.supersede`, `ontology.suggestions.comment` |
| Write | `graph(name).facts.create`, `commit`, `commitDryRun`, `graph(name).retract`, `reload` |
| Import | `graph(name).facts.importRdf`, `importRdfMany`, `graph(name).facts.import`, `submitImport`, `getImportJob`, `waitForImportJob`, `cancelImportJob` |
| Search | `embeddings.declare`, `embeddings.get`, `embeddings.list`, `embeddings.preview`, `embeddings.search`, `embeddings.setModel`, `embeddings.refresh`, `embeddings.delete`, `embeddings.searchSettings`, `embeddings.setSearchSettings`, `managedModels`, `modelActivity` |
| Search tuning | `embeddings.searchTuning.start`, `embeddings.searchTuning.list`, `embeddings.searchTuning.get`, `embeddings.searchTuning.apply` |
| Model checks | `checks.summary`, `checks.list`, `checks.review`, `checks.export`, `checks.calls`, `checks.call`, `checks.checkCall` |
| Model choice | `models.trials.options`, `models.trials.create`, `models.trials.wait`, `models.trials.list`, `models.trials.get`, `models.trials.call`, `models.trials.stop`, `models.switches.create`, `models.switches.list`, `models.switches.revert`, `models.choices.list`, `models.choices.all`, `models.choices.put`, `models.choices.delete` |
| Query | `sparqlRows`, `sparqlText`, `sparql`, `query.sparql`, `query.sparqlRaw`, `query.structured`, `query.update`, `query.ask`, `query.askStream`, `entities.filterByAttributes` |
| Records | `entities.detail` |
| Graphs | `createGraph`, `listGraphs`, `forkGraph`, `deleteGraph`, `graph(name).delete` |
| Integrations | `integrations.connectors`, `integrations.providerApps`, `integrations.createConnectSession`, `integrations.getConnectSession`, `integrations.create`, `integrations.list`, `integrations.get`, `integrations.setCredentials`, `integrations.setSettings`, `integrations.sync`, `integrations.pause`, `integrations.resume`, `integrations.delete`, `integrations.erase`, `integrations.suggestions`, `integrations.accept`, `integrations.dismiss`; `googleDrive.authorizeUrl` and `googleDrive.exchangeCode` (imported from the package) |
| Publication | `waitForPublished`, `publicationStatus`, `readSnapshot`, `summary`, `schemaSummary`, `metadata`, `status` |
| Transport | `rawRequest`, `request`, `integrationsRequest`, `idempotencyKey`, `withScope`, `listAll` |

`commit(body)` is the client-level form of `facts.create`. It also returns
the number as `commitSeq`. The client has further methods for managed evals and
model training. This page does not cover them.

## Native SPARQL protocol

Each environment also serves the SPARQL 1.1 Protocol at `/sparql`. Standard tools
such as YASGUI and Protégé connect there. It takes `GET ?query=`, a form POST,
or an `application/sparql-query` body.

The `Accept` header selects the format. SELECT and ASK return JSON, XML, CSV
or TSV. CONSTRUCT and DESCRIBE return Turtle or N-Triples.

`/sparql` accepts `consistency`, `min_indexed_seq`, `as_of_commit_seq` and
`entailment` as query parameters. To export the graph as RDF, run a CONSTRUCT
query there:

```ts
const exported = await lbb.rawRequest<string>("GET", "/sparql", {
  query: { query: "CONSTRUCT { ?s ?p ?o } WHERE { ?s ?p ?o }" },
  headers: { accept: "application/n-triples" },
});
console.log(exported.data.split("\n").length);
```

The [HTTP API](https://docs.littlebigbrain.com/sdks/http-api/#native-sparql-11-protocol) covers the protocol
in full.

## Optional application helpers

Start with the client above. The optional `catalog` and `access` helpers add
namespace membership, exact-epoch adoption, bounded catalog pages and delegated
read tokens. They require separately enabled server support. Shared-reference
queries also require explicit access to every selected source. See
[How to model your app](https://docs.littlebigbrain.com/start/model-your-app/) for boundaries and an example.
