# Add an organization to your application

Source: https://docs.littlebigbrain.com/guides/add-an-organization/

Your application serves many organizations. LBB has no customer object. The
organization is your word for a **namespace**. The unit of work you give it, such
as a project, a product line or an engagement, is a **graph**. See
[how to model your app](https://docs.littlebigbrain.com/start/model-your-app/) for the three levels.

Adding an organization is a list of calls. You run them from your backend when the
organization signs up. No call changes the server. Every step except step 2 is
safe to send again.

| Step | Call | What it makes |
| --- | --- | --- |
| 1 | `catalog.createNamespace` | The namespace of the organization. |
| 2 | `createGraph` | One graph, with an empty ontology. |
| 3 | `catalog.adoptGraph` | The graph becomes a member of the namespace. |
| 4 | `ontology.starters.apply` | A base ontology on the graph. |
| 5 | `files.upload` or `integrations.create` | The first data. |
| 6 | `workflows.starters.use` | One workflow for each starter workflow you run. |
| 7 | `access.createReadToken` | A short-lived token for the organization's users. |

## Before you start

- **Use an environment API key.** Every call below needs it. Keep it on your
  server. It reads and writes every graph of the environment.
- **Check the catalog.** Steps 1 and 3 need the application catalog. `GET /version`
  lists `application_catalog_v1` when the server supports it. The catalog must also
  be enabled on the deployment.
- **Check read tokens.** Step 7 needs `scoped_graph_reads_v1` and the access
  feature, which is a separate gate. Skip step 7 until your operator enables it.
- **Check workflows.** Step 6 needs the workflow engine. On a local server, run it
  with `--workflows`.

Graph ids have 1 to 32 lowercase letters, digits, `-` or `_`. Derive one from your
own ids, so that names and addresses stay out of storage keys and logs.

```ts
import { createHash } from "node:crypto";
import { readFile } from "node:fs/promises";
import { LbbClient } from "@littlebigbrain/client";

const admin = new LbbClient({
  baseUrl: process.env.LBB_URL!,
  apiKey: process.env.LBB_API_KEY!,
});

const organization = { id: "42", name: "acme" };
const unit = { id: "7" };

const graphId = `u-${createHash("sha256")
  .update(`${organization.id}/${unit.id}`)
  .digest("hex")
  .slice(0, 24)}`;
```

```python
import hashlib
import os

import httpx
from lbb import LbbClient

url = os.environ["LBB_URL"]
api_key = os.environ["LBB_API_KEY"]
admin = LbbClient(url, api_key=api_key)

organization = {"id": "42", "name": "acme"}
unit = {"id": "7"}

graph_id = "u-" + hashlib.sha256(
    f"{organization['id']}/{unit['id']}".encode()
).hexdigest()[:24]
```

## 1. Create the namespace

A namespace name has 1 to 64 lowercase letters, digits, `-` or `_`. `external_key`
is your own id for the organization. It is unique in the environment, so you can
find the namespace by it later. The idempotency key makes a retry safe after a lost
reply. Send the same key with the same request.

```ts
const namespace = await admin.catalog.createNamespace(
  { name: organization.name, external_key: `organization:${organization.id}` },
  { idempotencyKey: `namespace-${organization.id}` },
);
const namespaceId = namespace.identity.id;
```

```python
namespace = admin.catalog.create_namespace(
    {"name": organization["name"], "external_key": f"organization:{organization['id']}"},
    idempotency_key=f"namespace-{organization['id']}",
)
namespace_id = namespace["identity"]["id"]
```

A namespace can hold other namespaces, up to 16 levels with the root. Pass
`parent_id` to create a level below one, for example `projects` below `acme`.
`catalog.resolve("acme/projects")` finds a namespace by its path.

## 2. Create the graph

```ts
const project = admin.withScope({ graph: graphId });
await project.createGraph();
```

```python
project = LbbClient(url, api_key=api_key, graph=graph_id)
project.create_graph()
```

The graph starts with an empty ontology. Run this step once for each unit of work.
A unit that starts from an earlier one can use `POST /v1/graph/fork` instead, which
copies a graph in the same environment.

## 3. Adopt the graph

Adoption names the namespace as the owner of the graph. It needs the exact epoch of
the graph, as a decimal string. A stale epoch fails, so a recreated graph is never
adopted by mistake. The graph id and its history do not change.

```ts
const scope = await project.request<{ epoch: string }>("GET", "/v1/workflows/scope");
await project.catalog.adoptGraph(
  {
    owner_id: namespaceId,
    expected_graph_epoch: scope.epoch,
    external_key: `unit:${unit.id}`,
  },
  { idempotencyKey: `adopt-${graphId}` },
);
```

```python
scope = httpx.get(
    f"{url}/v1/workflows/scope",
    params={"graph": graph_id},
    headers={"Authorization": f"Bearer {api_key}"},
).json()
project.catalog.adopt_graph(
    {
        "owner_id": namespace_id,
        "expected_graph_epoch": scope["epoch"],
        "external_key": f"unit:{unit['id']}",
    },
    idempotency_key=f"adopt-{graph_id}",
)
```

Use a client without the client-wide `expectedGraphEpoch` option for the catalog
calls. Adoption is permanent in this release. A namespace and a binding cannot be
moved or removed.

## 4. Apply a base ontology

An [ontology starter](https://docs.littlebigbrain.com/guides/ontology-starters/) is a versioned base for CRM,
documents or work management. Preview first. The preview writes nothing.

```ts
const starters = admin.graph(graphId).ontology.starters;
const preview = await starters.apply("documents", { dryRun: true });
console.log(preview.applied_ops.length, preview.ontology_version);
const applied = await starters.apply("documents");
console.log(applied.status.state); // "applied"
```

```python
starters = admin.graph(graph_id).ontology.starters
preview = starters.apply("documents", dry_run=True)
applied = starters.apply("documents")
print(applied.status.state.value)  # "applied"
```

Applying again changes nothing. A term the graph holds differently makes the call
refuse with `409 starter_conflict`, and nothing is written. The organization's own
classes and fields land as extensions beside the base.

## 5. Add the first data

Choose the one that fits the unit of work.

**Upload a file.** The server stores the bytes once under their SHA-256 and adds a
`Document` record. The same bytes under the same name change nothing.

```ts
const bytes = await readFile("manual.pdf");
const { file } = await project.files.upload(bytes, {
  name: "manual.pdf",
  contentType: "application/pdf",
});
console.log(file.sha256);
```

```python
answer = project.files.upload("manual.pdf")
print(answer["file"]["sha256"])
```

**Connect a source.** `integrations.create` seals the credential, creates the graph
when it does not exist, applies the connector's starter, checks the fit and starts
the first sync. For a source, send it in place of steps 2 and 4, then adopt the graph
as in step 3. It needs developer access on the environment. See
[integrations for your customers](https://docs.littlebigbrain.com/guides/integrations-for-your-customers/).

```ts
await admin.integrations.create({
  graph: graphId,
  id: "hubspot",
  kind: "hubspot",
  credentials: { HUBSPOT_TOKEN: process.env.HUBSPOT_TOKEN! },
});
```

```python
admin.integrations.create(
    graph=graph_id,
    id="hubspot",
    kind="hubspot",
    credentials={"HUBSPOT_TOKEN": os.environ["HUBSPOT_TOKEN"]},
)
```

## 6. Create the workflows

A workflow belongs to one graph. Create each one from its
[starter workflow](https://docs.littlebigbrain.com/guides/starter-workflows/). Keep the list in your code, and run
the same list for every new graph.

```ts
const workflows = admin.graph(graphId).workflows;
await workflows.starters.use({ starter: "documents.parse" });
await workflows.starters.use({ starter: "documents.link", gate: { below: 0.8 } });
```

```python
project.use_workflow_starter("documents.parse")
project.use_workflow_starter("documents.link", gate=0.8)
```

The same request again changes nothing, so a retry is safe. A workflow reads every
existing instance first, then follows each published commit. See
[from documents to a graph](https://docs.littlebigbrain.com/guides/documents-to-graph/) for the whole chain and
[workflows](https://docs.littlebigbrain.com/guides/workflows/) for workflows that run in your worker.

## 7. Issue a read token

Your backend issues the token after it has checked the signed-in user. The token
grants SPARQL reads on the namespace and on every graph adopted below it later. It
lives for 1 to 900 seconds. It cannot write, read files or start workflows.

```ts
const credential = await admin.access.createReadToken({
  grants: [{ kind: "namespace", namespace_id: namespaceId }],
  ttl_seconds: 300,
});
console.log(credential.expires_at_ms);
```

```python
credential = admin.access.create_read_token(
    {
        "grants": [{"kind": "namespace", "namespace_id": namespace_id}],
        "ttl_seconds": 300,
    }
)
```

Give `credential.token` to the user's session. It is a bearer credential on the
standard SPARQL route:

```sh
curl -X POST "$LBB_URL/sparql?graph=$GRAPH_ID" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/sparql-query' \
  -H 'Accept: application/sparql-results+json' \
  --data 'SELECT ?name WHERE { ?d <https://littlebigbrain.com/p/name> ?name } LIMIT 10'
```

Revoke a token early with `access.revokeToken(credential.credential_id)`. See
[application read tokens](https://docs.littlebigbrain.com/start/model-your-app/#optional-catalog-and-delegated-reads)
for the routes a token can use.

## Check the result

```sh
curl "$LBB_URL/v1/catalog/graphs?owner_id=$NAMESPACE_ID" \
  -H "Authorization: Bearer $LBB_API_KEY"
curl "$LBB_URL/v1/ontology/starters?graph=$GRAPH_ID" \
  -H "Authorization: Bearer $LBB_API_KEY"
curl "$LBB_URL/v1/files/counts?graph=$GRAPH_ID" \
  -H "Authorization: Bearer $LBB_API_KEY"
curl "$LBB_URL/v1/workflows/triggered?graph=$GRAPH_ID" \
  -H "Authorization: Bearer $LBB_API_KEY"
```

The first call lists the graphs of the namespace. The others show the starter status,
the files by parse status, and the workflows with their state.

An interrupted adoption finishes when you send the same idempotency key with the
same request again. `catalog.pendingOperations()` lists the ones that are still open.

## What this recipe does not cover

- **Removing an organization.** A namespace cannot be deleted, renamed or moved in
  this release. Deleting a graph (`POST /v1/graph/delete?graph=<id>&confirm=<id>`) stops reads
  through its binding. The namespace stays.
- **A closed graph.** No call closes a graph for writes while it stays readable.
- **One call for all steps.** Each step above is its own call.

Read the graphs of the organization together with
[a namespace query](https://docs.littlebigbrain.com/guides/query-a-customer-namespace/).
