> ## Documentation Index
> Fetch the complete documentation index at: https://docs.niadra.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart

> From an API key to the first delivered context, in Python, TypeScript or cURL.

This quickstart takes one agent from zero to a delivered context: a key, the SDK, a first conversation recorded, the context read back and a search in the history. It takes about ten minutes in a sandbox space, which never mixes with production data.

## 1. Get a key

Every agent, vendor or system that talks to Niadra is a **source**, with its own keys, scopes and purpose. Create a source for your agent in the Console, or through the [control API](/en/api/control/sources-create), and create a key with the `track`, `context` and `search` scopes.

A key looks like this:

```text theme={null}
nia_sk_test_us-east-1_acme-sandbox_k7Q2mX9a_<secret>
```

`test` keys reach sandbox spaces and `live` keys reach production spaces. The region and the space in the key tell the SDK where to go: `https://acme-sandbox.us-east-1.api.niadra.com`. The secret is shown once; keep it in your secret manager and expose it to the agent as an environment variable.

```sh theme={null}
export NIADRA_API_KEY="nia_sk_test_us-east-1_acme-sandbox_k7Q2mX9a_..."
```

## 2. Install the SDK

<CodeGroup>
  ```sh Python theme={null}
  pip install niadra
  ```

  ```sh TypeScript theme={null}
  npm install @niadra/sdk
  ```
</CodeGroup>

Both SDKs are open source under Apache 2.0. For plain HTTP there is nothing to install: every call below also has a cURL version.

## 3. Record a conversation

A conversation session pins the context, captures the turns and sends `conversation.ended` when it closes. Writes go to a local queue and leave in batches, so they never slow the agent down.

<CodeGroup>
  ```python Python theme={null}
  from niadra import Niadra, phone

  niadra = Niadra(channel="whatsapp")
  marina = phone("+14155550123")

  with niadra.conversation("wa-8812", subject=marina) as conv:
      conv.customer("The technician never showed up. I am calling you.")
      conv.agent("I am sorry, Marina. I am checking the visit now.")
  # leaving the block sends conversation.ended

  niadra.flush()
  ```

  ```typescript TypeScript theme={null}
  import { Niadra, handles } from "@niadra/sdk";

  const niadra = new Niadra();
  const marina = handles.phone("+14155550123");

  const conv = niadra.conversation({ subject: marina, channel: "whatsapp", conversation_id: "wa-8812" });
  conv.customer("The technician never showed up. I am calling you.");
  conv.agent("I am sorry, Marina. I am checking the visit now.");
  await conv.end(); // sends conversation.ended

  await niadra.flush();
  ```

  ```bash cURL theme={null}
  curl -X POST "https://acme-sandbox.us-east-1.api.niadra.com/v1/batch" \
    -H "Authorization: Bearer $NIADRA_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
    "items": [
      {"type": "event", "idempotency_key": "wamid.001", "channel": "whatsapp", "conversation_id": "wa-8812",
       "handles": [{"type": "phone_e164", "value": "+14155550123"}], "speaker": {"role": "customer"},
       "content": {"text": "The technician never showed up. I am calling you."}, "occurred_at": "2026-09-22T17:02:11Z"},
      {"type": "event", "idempotency_key": "wamid.002", "channel": "whatsapp", "conversation_id": "wa-8812",
       "handles": [{"type": "phone_e164", "value": "+14155550123"}], "speaker": {"role": "ai_agent"},
       "content": {"text": "I am sorry, Marina. I am checking the visit now."}, "occurred_at": "2026-09-22T17:02:19Z"},
      {"type": "conversation.ended", "idempotency_key": "wa-8812-end-1", "conversation_id": "wa-8812",
       "occurred_at": "2026-09-22T17:03:00Z"}
    ]
  }'
  ```
</CodeGroup>

The API answers `200` when every item went in, or `207` when some were rejected, always with `accepted`, `duplicates` and one error per rejected item: a bad item never fails the batch. Sending the same `idempotency_key` again is safe; it is counted as a duplicate and stored once.

## 4. Read the context

Now a voice agent picks up a call from the same number. It asks for the context before it says hello:

<CodeGroup>
  ```python Python theme={null}
  ctx = niadra.context(subject=marina, view="voice", conversation_id="call-4471")

  print(ctx.text)           # the pack, ready for the system prompt
  print(ctx.withheld)       # items held back at this verification level
  print(ctx.origin)         # network, cache, stale, last_good or empty
  ```

  ```typescript TypeScript theme={null}
  const ctx = await niadra.context({ subject: marina, view: "voice", conversation_id: "call-4471" });

  console.log(ctx.text);    // the pack, ready for the system prompt
  console.log(ctx.suffix);  // live turns and deltas, for the end of the prompt
  console.log(ctx.source);  // network, cache, stale, fallback or none
  ```

  ```bash cURL theme={null}
  curl -X POST "https://acme-sandbox.us-east-1.api.niadra.com/v1/context" \
    -H "Authorization: Bearer $NIADRA_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"subject": {"type": "phone_e164", "value": "+14155550123"}, "view": "voice", "conversation_id": "call-4471"}'
  ```
</CodeGroup>

Put `text` in the system prompt, after your own instructions. The WhatsApp message from a moment ago is already there: turns are readable by every agent in under a second, and the derived memory (open items, facts, the episode) follows within a minute after the session ends.

<Tip>
  Call `context()` while the phone is still ringing, or as soon as the message arrives. The SDK keeps its own time budget (150 ms for `voice`, 300 ms for other views), so a slow answer never delays your agent: it gets an empty context and carries on.
</Tip>

## 5. Search the history

When the customer says "last time you gave me a credit", the agent looks it up:

<CodeGroup>
  ```python Python theme={null}
  found = niadra.search(marina, "credit for missed technician visit", conversation_id="call-4471", voice=True)

  for item in found.items:
      print(item.at, item.channel, item.text)
  if found.recurrence:
      print(found.recurrence.occurrences, "times in", found.recurrence.window_days, "days")
  ```

  ```typescript TypeScript theme={null}
  const { data } = await niadra.search({
    subject: marina,
    query: "credit for missed technician visit",
    conversation_id: "call-4471",
    max_tokens: 300,
  });

  for (const item of data?.items ?? []) console.log(item.at, item.channel, item.text);
  ```

  ```bash cURL theme={null}
  curl -X POST "https://acme-sandbox.us-east-1.api.niadra.com/v1/history/search" \
    -H "Authorization: Bearer $NIADRA_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"subject": {"type": "phone_e164", "value": "+14155550123"}, "query": "credit for missed technician visit", "max_tokens": 300}'
  ```
</CodeGroup>

To let your LLM decide when to search, hand it the tool kit instead: `niadra.tools(marina, conversation_id="call-4471")` returns function definitions bound to Marina, and routes the model's calls back to Niadra. See [History navigation](/en/concepts/history).

## 6. Check what happened

Every read left a receipt: which source read, which items it received and which were withheld, under which policy. Open the Console to see the reads of this conversation, or list them with [`GET /v1/receipts`](/en/api/receipts) as a person with the `security` role or with a key that has the `admin` scope.

## Next steps

<CardGroup cols={2}>
  <Card title="Spaces and keys" href="/en/concepts/spaces-and-keys">
    Sources, scopes, environments and the stable address.
  </Card>

  <Card title="Events and the batch" href="/en/concepts/events">
    Messages, system events and actions in one format.
  </Card>

  <Card title="Voice agents" href="/en/guides/voice-agents">
    Context before hello, network attestation and handoff.
  </Card>

  <Card title="Internal agents" href="/en/guides/internal-agents">
    Tasks, objects and actions that close open items.
  </Card>
</CardGroup>
