# Data Ingestion Guide

> Worked examples for pushing content into Reaktly from TypeScript, Python, and cURL.

URL: https://docs.reaktly.com/docs/integrations/ingestion-api

This guide gets data into a knowledge base. For the exact field list and every endpoint, see the [Ingestion API reference](/docs/api-reference/ingestion).

## What you need

| Value | Where to find it |
|---|---|
| API key with `iq:import` | Dashboard → **Settings → API keys** ([Authentication](/docs/api-reference/authentication)) |
| `integrationId` | Dashboard → the integration (connector) the data belongs to |
| `knowledgeBaseId` | Dashboard → Knowledge base. Optional — omit to use the tenant's default |

## The flow

1. **Extract** — read items from your source system
2. **Transform** — map to the ingestion shape: `sourceType`, `externalId`, `data.title`, `data.content`
3. **Load** — `POST /ingest` for one item, `POST /ingest/bulk` for batches
4. **Clean up** — after a full sync, `POST /ingest/cleanup` removes items deleted at the source

## First request (cURL)

```bash
curl -X POST https://api.reaktly.com/ingest \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '{
    "integrationId": "your-integration-id",
    "knowledgeBaseId": "your-kb-id",
    "sourceType": "ARTICLE",
    "externalId": "my-first-article",
    "data": {
      "title": "Getting Started Guide",
      "content": "The text the AI will answer from…",
      "url": "https://your-site.com/getting-started"
    }
  }'
```

A `200` with `{ "status": "queued", "jobId": "…" }` means the item is accepted. Processing (chunking, embedding, indexing) happens in the background — allow a short delay before the assistant can answer from it.

## TypeScript

```ts
const response = await fetch('https://api.reaktly.com/ingest', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-api-key': process.env.REAKTLY_API_KEY!,
  },
  body: JSON.stringify({
    integrationId: process.env.REAKTLY_INTEGRATION_ID,
    sourceType: 'ARTICLE',
    externalId: `post-${post.id}`,
    data: {
      title: post.title,
      content: post.body,
      url: `https://your-site.com/blog/${post.slug}`,
      hash: contentHash(post),
    },
    options: { priority: 'high' },
  }),
});

if (!response.ok) throw new Error(`Ingestion failed: ${response.status}`);
const result = await response.json();
console.log('Queued as', result.jobId);
```

## Python

```python
import os
import requests

response = requests.post(
    "https://api.reaktly.com/ingest",
    json={
        "integrationId": os.environ["REAKTLY_INTEGRATION_ID"],
        "sourceType": "ARTICLE",
        "externalId": f"post-{post_id}",
        "data": {
            "title": title,
            "content": body,
            "url": url,
        },
    },
    headers={"x-api-key": os.environ["REAKTLY_API_KEY"]},
    timeout=30,
)
response.raise_for_status()
print(response.json()["jobId"])
```

## Batching an initial load

Send 50–100 items per request with `/ingest/bulk`. Keep `externalId`s stable across syncs — they are the deduplication key, and re-sending the same item updates it rather than creating a duplicate.

```ts
const chunk = items.slice(0, 100).map(toIngestionItem);
await fetch('https://api.reaktly.com/ingest/bulk', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-api-key': process.env.REAKTLY_API_KEY!,
  },
  body: JSON.stringify({ items: chunk }),
});
```

## Keeping in sync

| Goal | Approach |
|---|---|
| Skip unchanged content | Send `data.hash`; unchanged items are skipped ([Change detection](/docs/integrations/change-detection)) |
| Update prices or URLs only | `options.metadataOnly: true` updates metadata without re-embedding |
| Remove deleted items | `POST /ingest/cleanup` with the full list of surviving `externalId`s |
| Prioritise user-triggered imports | `options.priority: "high"` |

## Next steps

- [Ingestion API reference](/docs/api-reference/ingestion) — endpoints, fields, responses
- [Integration patterns](/docs/integrations/integration-patterns) — webhook vs. scheduled syncs
- [Error handling](/docs/integrations/error-handling) — retries and backoff
- [Build a custom connector](/docs/integrations/custom-connector) — a full walkthrough