Skip to content

How to model your app

View Markdown

Start with a graph and a token. A graph holds related records and their history. Your application decides which people can use them.

Keep data you normally query and update together in one graph when its access and lifecycle requirements match. Split it when permissions, retention or independent operation require a boundary.

Your application A useful starting point
A personal assistant One graph for each person’s private knowledge.
Mail, tickets and CRM for the same team One team graph when the same people can see it all and queries connect the sources. Keep source information on the records.
A team with private notebooks One shared team graph and separate private graphs.
Customers with several projects One graph per independently managed project. Your app records which customer owns each project.
A CRM containing millions of customer records One graph can hold those records when they share access and query requirements. A customer record is not automatically a separate dataset.

A person can use several graphs, and a graph can serve many people. Your end users do not need their own LBB account. The quickstart shows one graph; customer integrations shows a backend selecting a graph for the signed-in customer.

Copy the complete endpoint and an API key from your environment’s Connect page. Use separate environments for production and testing. Your backend keeps the mapping from an authorized user or project to its graph ID. Graph IDs use 1–32 lowercase letters, digits, - or _.

An environment API key can access every graph in that environment. Keep it on your server, and check the user’s permission before selecting a graph. Creating another graph does not restrict that key. See Authentication & endpoints.

import { LbbClient } from "@littlebigbrain/client";
const project = new LbbClient({
baseUrl: process.env.LBB_URL!,
apiKey: process.env.LBB_API_KEY!,
graph: "project",
});

Writes across graphs are not atomic. If records must change in one commit, keep them in the same graph.

The application catalog, scoped read tokens and reference dataset APIs are optional and disabled by default. A deployment must explicitly enable the relevant feature. Its capability list describes support, not activation. The examples here require the SDK version that adds these optional helpers; they do not claim that every hosted deployment has enabled them.

Namespaces group graphs, like folders. Your app can use customer, workspace or project levels in any combination. An administrator adopts an existing graph at an exact observed epoch without changing its ID. Ownership is fixed at adoption in this release: moving or deleting a namespace, moving a graph, and reusing its binding after recreation are not supported.

The TypeScript catalog namespace and Python catalog attribute provide create, resolve, adopt, list and repair methods. Listings return one bounded page. An empty page may still have next_cursor; continue until it is null. Repair retries a recorded catalog operation or index page. It does not create missing graph data or change ownership.

Scoped read tokens authorize one graph or the descendants of one namespace for up to 15 minutes. Your backend issues and can revoke them with the access helpers. They permit the documented SELECT/ASK and graph metadata read routes; they do not permit writes, file downloads, workflows or environment administration. Their scope uses immutable identities and graph epochs. A graph recreated under the same name does not inherit the old token’s access. Token issuance requires an environment administrator; a delegated token cannot mint another token.

A reference query selects one primary graph plus up to four authorized reference graphs in the same environment. A project can join shared standards without copying them. Grouping graphs in a namespace does not make queries read them. Every selected source must have a published RDF generation.

For TypeScript, project.sparqlDataset accepts this body. For Python, use client.sparql_dataset with the equivalent dictionary (and await on an async client):

{
"query": "SELECT ?project ?standard WHERE { ?project <urn:uses> ?standard . GRAPH <urn:standards> { ?standard <urn:status> <urn:approved> } }",
"expected_graph_epoch": "7",
"references": [
{
"name": "urn:standards",
"graph_id": "standards",
"expected_graph_epoch": "3"
}
]
}

Replace the example epochs with the exact epochs observed for your graphs; keep them as decimal strings. The request’s names are query aliases, not access grants. The credential must authorize every source. SELECT and ASK are supported; remote SERVICE, implicit source discovery and writes are not.

The response contains SPARQL Results JSON in results and a sources version vector. Each source is independently pinned; this is not an atomic snapshot across graphs. An optional per-source as_of_commit_seq requires that exact current published commit or fails rather than rounding down. This initial endpoint does not select older historical generations.

Saved views are optional and disabled by default, with their own deployment gate. When enabled, an environment administrator can save one primary graph and up to four reference graphs in the same environment:

const view = await project.saveDatasetView({
primary_graph_id: "project",
expected_graph_epoch: "7",
references: [
{ name: "urn:standards", graph_id: "standards", expected_graph_epoch: "3" },
],
});
const result = await project.sparqlDataset({
view_revision: view.revision,
references: [],
expected_graph_epoch: "7",
query: "SELECT ?standard WHERE { GRAPH <urn:standards> { ?standard <urn:status> <urn:approved> } }",
});

Keep the administrator credential on your backend. Save the returned revision; it identifies an immutable definition, with no mutable “latest” selection. TypeScript getDatasetView(revision) retrieves it; Python provides save_dataset_view, get_dataset_view and sparql_dataset (await async clients). Use your graphs’ actual decimal-string epochs in place of the example values.

A query with view_revision supplies references: [] and selects the saved primary graph through its client or ?graph=. It must supply the saved primary epoch. The view grants no access: the query credential must still authorize every source. Optional saved commit pins retain the exact-published-commit rule above; a commit supplied by the query must match the saved definition. Saving a view does not copy data, include a namespace subtree, or create an atomic snapshot. See the HTTP API for the routes.

Owner reporting is an optional, separately activated feature. It preserves the owner identity recorded for an execution; renaming a customer in your application does not rewrite that history. Keep the environment administrator key on your backend. Scoped query tokens cannot read reports.

Use catalog.usage with the exact namespace ID and incarnation from the catalog. Choose one reporting profile and follow next_cursor until it is null, even if a page has no matching executions. Every response contains page totals, not an interval-wide total. Registered-request pages inspect at most four owner-index entries per call and accept limit from 1 to 4 (default 4). Raw pages accept 1 to 32 (default 16). Both may return fewer matches. Cursors are stateless and bound to the original environment, owner, period and profile; pass them back unchanged. They do not create a snapshot of a changing listing, so replaying a cursor must not be used to certify interval totals. Resource quantities are decimal strings; use BigInt in TypeScript or int in Python when adding page totals.

const owner = await admin.catalog.resolve("acme");
const page = await admin.catalog.usage({
ownerId: owner.identity.id,
ownerIncarnation: owner.identity.incarnation,
periodStartMicros: "1800000000000000", // replace with a UTC-hour boundary
profile: "registered-request-resource-v1",
limit: 4,
});
// Request the same owner, period and profile with cursor: page.next_cursor.
const cpuNanosOnThisPage = BigInt(page.page_totals.cpu_nanos);

For Python, call client.catalog.usage(owner_id=..., owner_incarnation=..., period_start_micros=..., profile=..., cursor=...); an async client uses the same helper with await.

  • registered-request-resource-v1 selects one UTC hour by admission time. A complete result proves that this registered request profile’s frozen set of admissions has settled with valid attribution. The selected hour’s complete_through_micros does not certify older hours, raw history, workflows, unregistered requests or every resource family. Missing/crashed outcomes, unattributed work and the first partial activation hour remain gaps. An hour waits for late outcomes for up to 24 hours after its end, then closes as terminal partial if evidence is still missing. Operator repair can record an explicit admission gap; it does not turn a partial hour into complete.
  • raw-resource-receipts-v1 selects one UTC day by execution completion time. It shows receipts already projected from retained raw sources. Its status is always partial or unavailable, and it never has a completeness watermark. In-flight, aborted and conflicting captures remain visible gaps; unsupported attribution is withheld from owner totals.

The two profiles can contain the same execution. Do not add their totals together. Check status and gaps before treating a result as complete. After environment erasure, reports are unavailable; missing evidence is not zero usage. These reports do not change billing rates, pricing formulas or canonical usage events, create extra charges, or replace your billing integration.

Deployment operators must enable attribution/resource metering and explicitly activate the reporting profiles on a compatible serving and maintenance fleet. Policy creation defaults to off. Returning a flag to off does not discard already registered work. Existing workflow/job workers perform projection and period closure. These examples describe implementation support, not activation on every hosted deployment.

An opted-in write can finish its execution but fail to confirm its durable reporting measurement. The server then returns HTTP 503 application_usage_handoff_unconfirmed with retryable: false and no Retry-After. The original measured execution is retained once; this response does not mean that no work ran. The SDK surfaces it without automatic replay. Check operation state before explicitly retrying, because a new request can execute and be measured again. A pre-execution application_usage_unavailable response remains retryable. Once measurement is saved, a later settlement failure preserves the successful write response and is recovered by maintenance. Completed reads keep their original response while reporting finishes in bounded background work; an unconfirmed handoff remains an explicit reporting gap.

Graph boundaries follow data and access needs. Creating more graphs does not add capacity. This release uses the deployment’s existing serving and worker capacity; it makes no automatic scaling or throughput guarantee. Measure your queries and expected concurrency on the capacity you intend to use.