# How to model your app

Source: https://docs.littlebigbrain.com/start/model-your-app/

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.

## Choose what belongs together

| 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](https://docs.littlebigbrain.com/start/quickstart/)
shows one graph; [customer integrations](https://docs.littlebigbrain.com/guides/integrations-for-your-customers/)
shows a backend selecting a graph for the signed-in customer.

## Connect your app

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](https://docs.littlebigbrain.com/start/authentication/).

```ts
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.

## Optional catalog and delegated reads

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.

## Query shared reference data explicitly

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

```json
{
  "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.

## Save a reference selection for reuse

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:

```ts
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](https://docs.littlebigbrain.com/sdks/http-api/#reference-datasets-and-saved-views) for the routes.

## Optional owner usage reports

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.

```ts
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.

## Plan capacity separately

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.
