> For the complete documentation index, see [llms.txt](https://docs.gitloom.cloud/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.gitloom.cloud/documentation/content/quickstart.md).

# Quickstart

GitLoom stores what your agent learns about a user as markdown in a git repository, and gives it back when a later question needs it. This page takes you from nothing to a retrieved memory.

The API is HTTPS and JSON, so every example below is plain `curl` or the `fetch` built into Node 20+. There is also an SDK, if you would rather not write the requests yourself:

```bash
npm install @gitloomhq/sdk
```

### 0. Set the base URL

```bash
export GITLOOM_API="https://api.gitloom.cloud"
```

:::note\[The old hostname still works] GitLoom runs in `ap-south-1`, and before the alias existed it answered at its API Gateway hostname. That one keeps working — the alias was added alongside it, not instead of it — so anything already pointed there needs no change. :::

Check it without any credential:

```bash
curl -s "$GITLOOM_API/health"
```

```json
{"status":"ok"}
```

`/health` is the only unauthenticated route. Everything else returns `401` without a credential.

### 1. Get an API key

Sign in to the dashboard at `https://app.gitloom.cloud` and create a key. The key is shown **once**, at creation; only its hash is stored, so it cannot be recovered or re-displayed. If you lose it, revoke it and mint another.

Keys look like this:

```
gl_live_3f9a2b7c1d4e6f80_kQ8vN2xR7mZpLd3Wc0Ty5Ub9Aj1Hs4Ge
└┬─┘└─┬┘ └──────┬───────┘ └──────────────┬──────────────┘
 │    │         │                        └─ secret, never stored
 │    │         └─ key id: appears in logs, used to revoke
 │    └─ environment: live | test
 └─ prefix, so secret scanners can match it
```

Export it:

```bash
export GITLOOM_KEY="gl_live_3f9a2b7c1d4e6f80_kQ8vN2xR7mZpLd3Wc0Ty5Ub9Aj1Hs4Ge"
```

Confirm the key resolves to your account:

```bash
curl -s "$GITLOOM_API/v1/whoami" \
  -H "Authorization: Bearer $GITLOOM_KEY"
```

```json
{"account":"acme","auth":"api_key","env":"live"}
```

:::caution\[Keys belong on a server] An API key carries full read and write access to every namespace in your account. Keep it server-side. The API's CORS policy allows only `GET` and `OPTIONS` from a browser origin, so a browser cannot write memories even if you tried — but a leaked key can still be read from anywhere.

Keys also cannot manage keys: `POST`, `GET` and `DELETE` on `/v1/keys` return `403 dashboard_only` when called with an API key. Key management requires a signed-in dashboard session, so a leaked key cannot mint its own replacement and survive revocation. :::

### 2. Create a namespace

A namespace is one memory — its own git repository, its own index, isolated from every other namespace in your account. The usual pattern is **one namespace per end user**.

Namespaces are created explicitly. Writing to one that does not exist is a `404`, not an implicit create — a typo in a user id must not quietly mint an empty memory and report success.

```bash
curl -s -X POST "$GITLOOM_API/v1/namespaces" \
  -H "Authorization: Bearer $GITLOOM_KEY" \
  -H "content-type: application/json" \
  -d '{"namespace":"user-8213"}'
```

```json
{"namespace":"user-8213","created":true}
```

Calling it again is success, not a conflict:

```json
{"namespace":"user-8213","created":false}
```

`201` means it was created, `200` means it already existed. Because it is idempotent you can call it on every application startup without branching.

A namespace name may contain lowercase letters, digits and `-`, up to 64 characters. Anything else is `400 invalid_namespace`. Omitting `namespace` gives you `default`, which still has to be created before it can be used.

List them:

```bash
curl -s "$GITLOOM_API/v1/namespaces" \
  -H "Authorization: Bearer $GITLOOM_KEY"
```

```json
{"namespaces":["default","user-8213"]}
```

### 3. Write a memory

You do not write memories. You send a **conversation**, and GitLoom extracts the memories from it.

```bash
curl -s -X POST "$GITLOOM_API/v1/memories" \
  -H "Authorization: Bearer $GITLOOM_KEY" \
  -H "content-type: application/json" \
  -d '{
    "namespace": "user-8213",
    "session_id": "chat-2026-07-31-a",
    "date": "2026-07-31",
    "messages": [
      {"role": "user",      "content": "I finally bought the Sony A7III today — 142k at Fotocentre in Bengaluru. Went with it over the A7IV purely on price."},
      {"role": "assistant", "content": "Nice pick. With the 28-70 kit lens you already have, that pairs well for travel."},
      {"role": "user",      "content": "Yeah, I want it ready before the Japan trip in October."}
    ]
  }'
```

```json
{"id":"3f2b9e01-7c44-4d1a-9f6e-0a1b2c3d4e5f","namespace":"user-8213","status":"accepted"}
```

**`202`, not `200`.** The memory does not exist yet. Extraction is several model calls, so the write is queued and a worker performs it. See [Limits and behaviour](https://github.com/MelloB1989/gitloom/tree/main/limits/README.md) for what that means in practice — in short, expect seconds, not milliseconds, and there is no status endpoint to poll. The `id` is the queue message id, useful in a support conversation and nothing else.

Fields:

| field        | required | notes                                                                                                                                                                                                       |
| ------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `messages`   | yes      | at least one turn; `role` is `user` or `assistant`                                                                                                                                                          |
| `namespace`  | no       | defaults to `default`, which must still exist                                                                                                                                                               |
| `session_id` | no       | your own id for the conversation; carried into ingestion                                                                                                                                                    |
| `date`       | no       | `YYYY-MM-DD` or RFC 3339. **The date the conversation happened**, not now — a backfill of last year's transcripts must not claim everything happened today. Omitted or unparseable falls back to now (UTC). |

The `404` you will hit first:

```json
{"error":{"code":"namespace_not_found","message":"create namespace \"user-8213\" before writing to it"}}
```

That check runs at the front door, while you are still listening, rather than in the worker — by the time the worker sees a job you already have a `202` in hand.

### 4. Retrieve

Give it the question, verbatim.

```bash
curl -s -G "$GITLOOM_API/v1/retrieve" \
  -H "Authorization: Bearer $GITLOOM_KEY" \
  --data-urlencode "q=what camera did I buy" \
  --data-urlencode "namespace=user-8213"
```

```json
{
  "tenant": "acme",
  "namespace": "user-8213",
  "query": "what camera did I buy",
  "hits": [
    {
      "path": "facts/camera-gear/bought-a-sony-a7iii-1f4b9c2a.md",
      "score": 0.0328,
      "snippet": "Bought a Sony A7III on 2026-07-31 from Fotocentre in Bengaluru for ₹142,000, chosen over the A7IV on price."
    },
    {
      "path": "facts/japan-trip/trip-planned-for-october-7d2e05b1.md",
      "score": 0.0061,
      "snippet": "Planning a trip to Japan in October 2026; wants the new camera ready before it."
    }
  ],
  "millis": 118,
  "lexical_ms": 6,
  "vector_ms": 103,
  "graph_ms": 4,
  "cold_start": false
}
```

Parameters:

| param       | default   | notes                    |
| ----------- | --------- | ------------------------ |
| `q`         | —         | required; `400` if empty |
| `namespace` | `default` | must exist, or `404`     |
| `limit`     | `24`      | number of hits returned  |

Notes on the response:

* **`score` is a fusion rank, not a similarity.** Retrieval runs three arms — lexical BM25, semantic over cue vectors, and a one-hop walk of the relationship graph — concurrently, and fuses them by reciprocal rank (k=60). Values are small: a hit ranked first by one arm scores ≈0.016, first by two arms ≈0.033, and a pure graph neighbour ≈0.006. Compare scores **within one response only**; there is no absolute threshold that means "relevant".
* **`snippet` is the opening of the memory**, up to 240 characters. It is not a query-focused highlight. Fetch nothing else — for most questions the snippets are the answer material.
* **The `*_ms` fields tell you where time went.** `vector_ms` dominating is normal and expected: it is the query-embedding round trip to Bedrock, not search.
* `hits` is **absent, not `[]`**, when nothing matched. Treat a missing key as empty.

### 5. The same thing in TypeScript

Node 20+, no dependencies. This is the shape the forthcoming TypeScript SDK wraps — it is not on npm yet, so the runnable version today is `fetch`.

```ts
const API = process.env.GITLOOM_API!;
const KEY = process.env.GITLOOM_KEY!;

async function gl<T>(
  path: string,
  init: RequestInit & { query?: Record<string, string> } = {},
): Promise<T> {
  const url = new URL(path, API);
  for (const [k, v] of Object.entries(init.query ?? {})) url.searchParams.set(k, v);

  const res = await fetch(url, {
    ...init,
    headers: {
      authorization: `Bearer ${KEY}`,
      ...(init.body ? { "content-type": "application/json" } : {}),
      ...init.headers,
    },
  });

  const body = await res.json().catch(() => ({}));
  if (!res.ok) {
    // Two error shapes exist today: /v1/retrieve returns {error: string},
    // everything else returns {error: {code, message}}.
    const e = (body as any).error;
    const code = typeof e === "object" ? e.code : e;
    const message = typeof e === "object" ? e.message : e;
    throw new Error(`gitloom ${res.status} ${code ?? "error"}: ${message ?? ""}`);
  }
  return body as T;
}

// 2. create the namespace — idempotent, safe on every startup
await gl<{ namespace: string; created: boolean }>("/v1/namespaces", {
  method: "POST",
  body: JSON.stringify({ namespace: "user-8213" }),
});

// 3. hand over a conversation; returns 202, the memory does not exist yet
const accepted = await gl<{ id: string; namespace: string; status: string }>(
  "/v1/memories",
  {
    method: "POST",
    body: JSON.stringify({
      namespace: "user-8213",
      session_id: "chat-2026-07-31-a",
      date: "2026-07-31",
      messages: [
        {
          role: "user",
          content:
            "I finally bought the Sony A7III today — 142k at Fotocentre in Bengaluru.",
        },
        { role: "assistant", content: "Nice pick, that pairs well with your 28-70." },
      ],
    }),
  },
);
console.log(accepted.status); // "accepted"

// 4. retrieve — after ingestion has run; see Limits and behaviour
type Hit = { path: string; score: number; snippet: string };
type Retrieved = {
  tenant: string;
  namespace: string;
  query: string;
  hits?: Hit[];
  millis: number;
  lexical_ms: number;
  vector_ms: number;
  graph_ms: number;
  cold_start: boolean;
};

const r = await gl<Retrieved>("/v1/retrieve", {
  query: { q: "what camera did I buy", namespace: "user-8213", limit: "8" },
});

for (const hit of r.hits ?? []) {
  console.log(hit.score.toFixed(4), hit.path);
  console.log("  ", hit.snippet);
}
```

### Where this goes next

The retrieved snippets are context, not an answer. The intended shape is: retrieve (milliseconds, no model call), then hand the hits to whatever model is already answering your user. GitLoom deliberately does not make that call for you on the read path — that is what keeps retrieval fast and its cost predictable.

* [Core concepts](https://github.com/MelloB1989/gitloom/tree/main/concepts/README.md) — accounts, namespaces, memories, cues, and why git
* [Limits and behaviour](https://github.com/MelloB1989/gitloom/tree/main/limits/README.md) — asynchrony, latency numbers, and what is not enforced yet
* [API reference](https://github.com/MelloB1989/gitloom/tree/main/reference/README.md) — generated from the OpenAPI description


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.gitloom.cloud/documentation/content/quickstart.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
