# AIqel Public Integration API

A small, API-key-authenticated REST surface (namespaced under `/v1`) that lets
other systems and AI agents **pull** processed knowledge out of AIqel and
**push** new content in to be followed and processed.

Base URL: `http://localhost:3001` in development (set `PORT` / deploy host in prod).

---

## Authentication

Every `/v1/*` endpoint requires an API key sent as a Bearer token:

```
Authorization: Bearer <your-api-key>
```

Keys are stored only as a `sha256` hash (`ApiKey.hashedKey`); the plaintext is
shown **once** at creation time and cannot be recovered. Revoked keys
(`revokedAt` set) are rejected.

### Minting a key

**Option A — admin HTTP endpoint.** Guarded by the `ADMIN_TOKEN` env var. If
`ADMIN_TOKEN` is unset the endpoint is disabled (returns 404).

```bash
curl -X POST http://localhost:3001/admin/api-keys \
  -H "x-admin-token: $ADMIN_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{ "name": "my-integration" }'
# → { "id": "clx…", "name": "my-integration", "key": "aiqel_XXXXXXXX…" }
```

**Option B — local script** (direct DB access, no server needed):

```bash
DATABASE_URL=postgresql://… pnpm --filter @aiqel/db exec tsx prisma/create-api-key.ts "my-integration"
# prints the plaintext key once
```

Store the returned `key` securely — it is the only time you will see it.

---

## Endpoints

All request/response bodies are JSON. On validation failure endpoints return
`400` with a Zod `error` payload; missing/invalid keys return `401`.

### `POST /v1/search` — unified search (pull)

Searches across item titles, insight blocks, transcripts, translations and tags.

```bash
curl -X POST http://localhost:3001/v1/search \
  -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
  -d '{ "q": "great ideas" }'
```

```jsonc
{
  "q": "great ideas",
  "hits": [
    {
      "kind": "insight",            // insight | transcript | tag | item
      "itemId": "clx…",
      "itemTitle": "Episode 12",
      "title": "On idea formation",
      "snippet": "…great ideas rarely arrive fully formed…",
      "blockId": "clb…",
      "startMs": 0,
      "tags": [{ "name": "creativity", "slug": "creativity", "kind": "topic" }]
    }
  ],
  "counts": { "insight": 1, "transcript": 2 }
}
```

### `POST /v1/items` — add content & start the pipeline (push)

`type: "audio"` enters the pipeline at **ingest**; `type: "text"` skips
transcription and enters at **translate**.

```bash
# Audio
curl -X POST http://localhost:3001/v1/items \
  -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
  -d '{ "type": "audio", "title": "My clip", "audioUrl": "https://example.com/a.mp3", "targetLang": "he" }'

# Text
curl -X POST http://localhost:3001/v1/items \
  -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
  -d '{ "type": "text", "title": "A note", "text": "Some text to translate & analyze." }'
```

```json
{ "id": "clx…", "status": "ingesting" }
```

Fields: `type` (`audio`|`text`, required), `title?`, `audioUrl?` (audio),
`text?` (text), `targetLang?` (2-letter, default `he`).

### `GET /v1/items/:id` — full item (pull)

Returns the item plus its transcript, translations and insight blocks (each with
tags and speaker) — the primary path for pulling processed knowledge.

```bash
curl http://localhost:3001/v1/items/clx… -H "Authorization: Bearer $KEY"
```

```jsonc
{
  "id": "clx…",
  "status": "enriched",
  "type": "audio",
  "title": "My clip",
  "transcript": { "lang": "en", "fullText": "…", "segments": [ … ] },
  "translations": [ { "lang": "he", "fullText": "…", "segments": [ … ] } ],
  "blocks": [
    {
      "id": "clb…",
      "startMs": 0, "endMs": 3000,
      "summary": "…",
      "tags": [ { "tag": { "name": "creativity", "slug": "creativity", "kind": "topic" } } ],
      "speaker": { "name": "Host" }
    }
  ]
}
```

### `GET /v1/items?status=&limit=` — list items (poll)

```bash
curl "http://localhost:3001/v1/items?status=enriched&limit=20" \
  -H "Authorization: Bearer $KEY"
```

Returns an array of items (newest first). `limit` is clamped to 1–200 (default 100).
Omit `status` to list all.

### `POST /v1/sources` — follow a source (push)

```bash
curl -X POST http://localhost:3001/v1/sources \
  -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
  -d '{ "type": "podcast_rss", "title": "My Podcast", "url": "https://feed.example.com/rss" }'
```

`type` is one of `podcast_rss | youtube | website | manual`. Returns the created
`Source` row.

---

## Integration patterns

**Push → poll → pull.**

1. `POST /v1/items` to submit audio or text → get `{ id, status }`.
2. Poll `GET /v1/items/:id` (or `GET /v1/items?status=enriched`) until the item
   reaches a terminal status (`translated` / `enriched`).
3. `GET /v1/items/:id` to pull the transcript, translations and insight blocks.

**Search-first retrieval.** Use `POST /v1/search` to find relevant insights /
transcript spans across everything AIqel has processed, then `GET /v1/items/:id`
for full context on any hit's `itemId`.

**Follow a feed.** `POST /v1/sources` to register a podcast/website so new
content from it can be processed on an ongoing basis.

> For agent-native access to the same operations, see [MCP.md](./MCP.md).
