---
title: "REST API"
description: "HTTP endpoint reference for Xio.Lucent.Api: ingestion, querying, document management, backup, and admin operations."
published: 2026-05-14T12:11:16.55869+00:00
updated: 2026-05-14T12:11:16.55869+00:00
tags: ["api", "lucent", "reference", "rest"]
url: https://xiobjects.com/docs/xio/lucent/api/rest
source: XI Objects
---

<!-- xion:doctype xion+markdown -->
<!-- xion:metadata
{
  "version": "1.0",
  "content_type": "application/xion\u002Bmarkdown",
  "source_type": "xi-content/doc",
  "generator": "xio-content-publisher/1.0.0",
  "generated": "2026-05-14T12:10:18.3630309\u002B00:00",
  "encoding": "utf-8",
  "render_intent": "markdown",
  "title": "REST API",
  "slug": "xio/lucent/api/rest",
  "copyright": "\u00A9 2026 XI Objects Inc"
}
-->

# REST API

`Xio.Lucent.Api` mounts Lucent's HTTP surface into any ASP.NET Core host. `Xio.Lucent.Api.Host` is a ready-to-run Kestrel server if you don't need a custom host.

## Setup

```csharp
// Register engine + API controllers
builder.Services.AddLucent(builder.Configuration.GetSection("Lucent"));
builder.Services.AddLucentApi();

// Auth: choose JWT bearer or API key (or both)
builder.Services.AddLucentJwtBearer(builder.Configuration);
// or:
builder.Services.AddLucentApiKey(builder.Configuration);

builder.Services.AddLucentAuthorizationPolicies(builder.Configuration);

// Mount
app.UseMiddleware<CorrelationIdMiddleware>();
app.MapControllers();
```

## Endpoints

### Documents

| Method | Route | Scope | Description |
|--------|-------|-------|-------------|
| `POST` | `/v1/collections/{collectionId}/documents` | `lucent:write` | Ingest a document |
| `GET` | `/v1/collections/{collectionId}/documents` | `lucent:read` | List documents |
| `DELETE` | `/v1/collections/{collectionId}/documents/{documentId}` | `lucent:write` | Delete a document |
| `GET` | `/v1/collections/{collectionId}/documents/{documentId}/signed-source` | `lucent:read` | Stream preserved XION bytes |

### Search

| Method | Route | Scope | Description |
|--------|-------|-------|-------------|
| `POST` | `/v1/collections/{collectionId}/query` | `lucent:read` | Hybrid vector + BM25 search |

### Backup

| Method | Route | Scope | Description |
|--------|-------|-------|-------------|
| `GET` | `/v1/collections/{collectionId}/export` | `lucent:admin` | Stream collection backup |
| `POST` | `/v1/collections/{collectionId}/import` | `lucent:admin` | Restore from backup stream |

### Admin

| Method | Route | Scope | Description |
|--------|-------|-------|-------------|
| `POST` | `/v1/admin/provision` | `lucent:admin` | Download and verify ONNX model + sqlite-vec |
| `POST` | `/v1/admin/warmup` | `lucent:admin` | Warm up the embedder |

---

## POST /v1/collections/{collectionId}/documents

Ingests a document. Accepts `multipart/form-data`.

**Form fields:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `file` | File | Yes | Document content (binary or text) |
| `documentId` | string | No | Explicit document ID; generated from content hash if omitted |
| `contentTypeHint` | string | No | Overrides content type detection (e.g. `application/pdf`) |
| `metadataJson` | string | No | JSON object of key-value metadata attached to every chunk |
| `isXionContent` | bool | No | Set to `true` to trigger XION verification before ingestion |

**Response: 200 OK**

```json
{
  "documentId": "abc123",
  "chunkCount": 42,
  "contentType": "application/pdf",
  "contentTypeSource": "Detected",
  "modelId": "nomic-embed-text-v1.5",
  "chunkingDurationMs": 210,
  "embeddingDurationMs": 890,
  "storageDurationMs": 45,
  "routingDurationMs": 3
}
```

**Error responses:**

| Status | Meaning |
|--------|---------|
| 400 | Malformed request (missing file, invalid metadata JSON) |
| 422 | XION verification failed |
| 500 | Pipeline error |

---

## POST /v1/collections/{collectionId}/query

Runs a hybrid search.

**Request body:**

```json
{
  "text": "what is the cancellation policy",
  "topK": 10,
  "scoreThreshold": 0.35,
  "filter": {
    "type": "And",
    "operands": [
      { "type": "Eq", "key": "category", "value": "legal" },
      { "type": "Eq", "key": "status", "value": "approved" }
    ]
  }
}
```

All fields except `text` are optional.

**Response: 200 OK**

```json
{
  "chunks": [
    {
      "score": 0.812,
      "vectorScore": 0.76,
      "textScore": 0.61,
      "source": "hybrid",
      "chunk": {
        "id": "abc123:0000000000",
        "documentId": "abc123",
        "content": "Cancellations must be submitted…",
        "startOffset": 0,
        "endOffset": 312,
        "metadata": {
          "heading": "Cancellation Policy",
          "sectionPath": "Terms > Cancellation Policy",
          "pageNumber": 4,
          "chunkIndex": 0,
          "totalChunks": 42,
          "custom": { "category": "legal" }
        }
      }
    }
  ],
  "embeddingDurationMs": 14,
  "retrievalDurationMs": 8,
  "scoringDurationMs": 0,
  "lucentInstanceId": null
}
```

---

## GET /v1/collections/{collectionId}/documents/{documentId}/signed-source

Streams the preserved XION bytes for a document ingested with `isXionContent = true`.

Returns `application/octet-stream`. Returns `404` for documents that weren't ingested as XION content.

---

## Authentication

Two schemes are supported and can be used simultaneously.

### JWT Bearer

```jsonc
{
  "Lucent": {
    "Auth": {
      "Jwt": {
        "Authority": "https://login.example.com/",
        "Audience": "lucent-api",
        "ValidIssuer": "https://login.example.com/"
      }
    }
  }
}
```

Scopes are checked as JWT claims. The scope claim name is configurable; defaults to `scope`.

### API Key

```jsonc
{
  "Lucent": {
    "Auth": {
      "ApiKey": {
        "HeaderName": "X-Api-Key",
        "Keys": {
          "sk_live_abc123": ["lucent:read", "lucent:write"],
          "sk_admin_xyz789": ["lucent:read", "lucent:write", "lucent:admin"]
        }
      }
    }
  }
}
```

Comparison is constant-time to prevent timing attacks.

---

## Full Configuration Reference

```jsonc
{
  "Lucent": {
    "StorageRoot": "/var/data/lucent",
    "Auth": {
      "Jwt": { "Authority": "…", "Audience": "…" },
      "ApiKey": { "HeaderName": "X-Api-Key", "Keys": { "key": ["scope"] } }
    },
    "Cors": {
      "AllowedOrigins": ["https://app.example.com"],
      "AllowCredentials": true
    },
    "RateLimit": {
      "Global": { "PermitLimit": 100, "WindowSeconds": 60 },
      "PerUser": { "PermitLimit": 20, "WindowSeconds": 60 }
    },
    "OpenTelemetry": {
      "OtlpEndpoint": "http://localhost:4317"
    }
  }
}
```

---

## Typed .NET Client

`Xio.Lucent.Api.Client` provides a typed client for the HTTP surface:

```csharp
services.AddLucentApiClient(new Uri("https://lucent.internal.example.com"));

// Inject and use
public class DocumentIngester(ILucentApiClient lucent)
{
    public Task Ingest(Stream file, string fileName) =>
        lucent.AddDocumentAsync("my-collection", file, fileName);

    public Task<QueryResult> Ask(string question) =>
        lucent.QueryAsync("my-collection", question, topK: 5);
}
```

The client uses `IHttpClientFactory`. Bearer token or API key injection is handled via delegating handlers; wire them in when calling `AddLucentApiClient`.
<!-- xion:trust
{
  "v": 1,
  "canon_v": 1,
  "ctx": "xiobjects.com/content",
  "hash_blake3_hex": "ecce312ef2ddc44f15a0b438054a0b1529911f2c0a43ed11254e841facefa8bc",
  "hash_sha256_hex": null,
  "sig_alg": "ed25519",
  "sig_b64": "t4xBfPNfpeGUBBN08VPzqUYjN9Iiq_f7aD1VgBf2MQs3ffxR4J9_98jCqAw549ot2CclqWl-s4SbfX6PO7YpCg",
  "pubkey_b64": "h-awvV8Rn-juph_c2Y7UH5A6e7NaFia3zBiMrJUOMOo",
  "x509_chain_pem": [
    "-----BEGIN CERTIFICATE-----\r\nMIIB9DCCAaagAwIBAgIQBrrNsmRlBvKQdA4idEliJjAFBgMrZXAwLjEsMCoGA1UE\r\nAwwjWEkgT2JqZWN0cyBJbmMgQ29udHJvbCBJbnRlcm1lZGlhdGUwHhcNMjYwNTEz\r\nMjI0NjA1WhcNMjYwNjEyMjI0NjA1WjBLMR4wHAYDVQQDDBV4aW8tY29udGVudC1w\r\ndWJsaXNoZXIxFzAVBgNVBAoMDlhJIE9iamVjdHMgSW5jMRAwDgYDVQQLDAdDb250\r\nZW50MCowBQYDK2VwAyEAh\u002BawvV8Rn\u002Bjuph/c2Y7UH5A6e7NaFia3zBiMrJUOMOqj\r\ngbwwgbkwDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYI\r\nKwYBBQUHAyQwZQYDVR0jBF4wXIAUOym3mFmw/qs1fgKrujCkxhrTk7KhLqQsMCox\r\nKDAmBgNVBAMMH0luc3RpdHV0ZSBvZiBQcm92ZW5hbmNlIFJvb3QgQ0GCFFJgN/ix\r\nQn72H6h3T5lEr9f8lJQFMB0GA1UdDgQWBBS1LSJi5\u002BeqBq8h974Ht9HTgIcdgTAF\r\nBgMrZXADQQCKjXbPwnk/DZHmLQstUWRzU6GSf\u002BSHTXTTZCtRLbmJKxT17Qlbpexc\r\nsRgdSpxNWpJPe9Fr4vwhRkESMqMIpgQO\r\n-----END CERTIFICATE-----\r\n",
    "-----BEGIN CERTIFICATE-----\r\nMIIByDCCAXqgAwIBAgIUUmA3\u002BLFCfvYfqHdPmUSv1/yUlAUwBQYDK2VwMCoxKDAm\r\nBgNVBAMMH0luc3RpdHV0ZSBvZiBQcm92ZW5hbmNlIFJvb3QgQ0EwHhcNMjUxMTAy\r\nMDMxNzEyWhcNMzAxMTAxMDMxNzEyWjAuMSwwKgYDVQQDDCNYSSBPYmplY3RzIElu\r\nYyBDb250cm9sIEludGVybWVkaWF0ZTAqMAUGAytlcAMhAFSS/pggSRmTcAMko7uc\r\nATH8OHgxVymd5mBFlPXbJkgio4GtMIGqMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYD\r\nVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBQ7KbeYWbD\u002BqzV\u002BAqu6MKTGGtOTsjBlBgNV\r\nHSMEXjBcgBQAZRTDswSVORu\u002BkUOKX6WvrOvmQKEupCwwKjEoMCYGA1UEAwwfSW5z\r\ndGl0dXRlIG9mIFByb3ZlbmFuY2UgUm9vdCBDQYIUJqoJlpiSFg\u002B7W5IJLMrLttgR\r\nQp4wBQYDK2VwA0EA5FOht7YOsVRPp/FOKMQ\u002B3Mo9JxrvGR3ylKWAWNm6OUV7N3DB\r\nI9cD62wU5I0d0EKDBy0CX9DnoqUyxv5yguraAA==\r\n-----END CERTIFICATE-----\r\n",
    "-----BEGIN CERTIFICATE-----\r\nMIIBaTCCARugAwIBAgIUJqoJlpiSFg\u002B7W5IJLMrLttgRQp4wBQYDK2VwMCoxKDAm\r\nBgNVBAMMH0luc3RpdHV0ZSBvZiBQcm92ZW5hbmNlIFJvb3QgQ0EwHhcNMjUxMTAy\r\nMDMwNTEyWhcNMzUxMDMxMDMwNTEyWjAqMSgwJgYDVQQDDB9JbnN0aXR1dGUgb2Yg\r\nUHJvdmVuYW5jZSBSb290IENBMCowBQYDK2VwAyEAEWNZl\u002Br3IC7\u002BgBh90Yo1kWk1\r\npZCVzVuFdFT7qBBU8W2jUzBRMB0GA1UdDgQWBBQAZRTDswSVORu\u002BkUOKX6WvrOvm\r\nQDAfBgNVHSMEGDAWgBQAZRTDswSVORu\u002BkUOKX6WvrOvmQDAPBgNVHRMBAf8EBTAD\r\nAQH/MAUGAytlcANBAO6QeydOFNrN75qNyftggYudsxMyl4w9qWkSdZ6hlhrRcbSr\r\niG9Si0kbrIJOwYB/LTBU0RM4Rl\u002Bo9PM3Qp0mPwo=\r\n-----END CERTIFICATE-----\r\n"
  ],
  "key_id": "SDyVO7FvlAM-6CvQ62VZYOBO7JADFqLquUunUABRgKg",
  "created_at": "2026-05-14T12:10:18Z"
}
-->