context69-sdk 0.5.0

Async Rust SDK for the Context69 HTTP API.
Documentation
# context69-sdk

Async Rust SDK for the Context69 HTTP API.

`context69-sdk` is a PAT-only client. Initialize it with a personal access token that starts with `ctx_pat_`, then navigate the resource tree to call an API.

## Initialization

```rust
use context69_sdk::Context69Client;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Context69Client::builder()
        .base_url("http://127.0.0.1:8096")?
        .with_personal_access_token("ctx_pat_example_token")?
        .build()?;

    let me = client.me().await?;
    println!("hello {}", me.user.login_name);
    Ok(())
}
```

The builder rejects empty tokens and non-PAT tokens. Protected APIs return `Error::AuthenticationRequired` if no PAT is configured.

## Groups and nested resources

```rust
use context69_sdk::{
    Context69Client,
    contracts::{CreateGroupRequest, GroupKind, Visibility},
};

# async fn example(client: Context69Client) -> Result<(), context69_sdk::Error> {
client.groups().create(&CreateGroupRequest {
    parent_group_path: None,
    group_key: "ops".to_string(),
    name: "Operations".to_string(),
    visibility: Visibility::Private,
    kind: Some(GroupKind::Shared),
}).await?;

let group = client.group("ops").get().await?;
let members = client.group("ops").members().list().await?;
let tree = client.group("ops").library().tree().await?;
# Ok(())
# }
```

Group handles own the group path, so nested calls do not repeat it:

```rust
# use context69_sdk::Context69Client;
# async fn example(client: Context69Client, folder_id: uuid::Uuid, file_id: uuid::Uuid) -> Result<(), context69_sdk::Error> {
let group = client.group("ops/runbooks");
group.source_folder(folder_id).sync().await?;
let file = group.library().file(file_id).get().await?;
# Ok(())
# }
```

## Sources

```rust
# use context69_sdk::{Context69Client, contracts::SourceConfigInput};
# async fn example(client: Context69Client, request: SourceConfigInput) -> Result<(), context69_sdk::Error> {
client.sources().create(&request).await?;
client.source(&request.source_key).sync().await?;

let connections = client.source_connections().list().await?;
client.source_connection("warehouse").delete().await?;
# Ok(())
# }
```

## Library

```rust
use context69_sdk::{
    Context69Client,
    contracts::{CreateTextRequest, LibraryTextContentFormat},
};

# async fn example(client: Context69Client) -> Result<(), context69_sdk::Error> {
let upload = client.library().texts().create(&CreateTextRequest {
    folder_id: None,
    title: "Runbook".to_string(),
    content: "# Step 1".to_string(),
    content_format: LibraryTextContentFormat::Markdown,
    source_uri: None,
    summary: None,
}).await?;

let tree = client.group("ops/runbooks").library().tree().await?;
# Ok(())
# }
```

Multipart uploads use `reqwest::multipart::Part`:

```rust
# use context69_sdk::Context69Client;
use reqwest::multipart::Part;

# async fn example(client: Context69Client) -> Result<(), context69_sdk::Error> {
client.library().files().upload(
    None,
    vec![Part::text("# Notes").file_name("notes.md")],
).await?;
# Ok(())
# }
```

## Settings and search

```rust
# use context69_sdk::{Context69Client, contracts::SearchRequest};
# async fn example(client: Context69Client, request: SearchRequest) -> Result<(), context69_sdk::Error> {
let runtime = client.settings().runtime().get().await?;
let docling = client.settings().docling().get().await?;
let result = client.search().execute(&request).await?;
let document = client.document(result.hits[0].document_id).get().await?;
# Ok(())
# }
```

## Resource tree

- `client.user_directory()`: user search
- `client.groups()` / `client.group(path)`: groups, children, members
- `client.sources()` / `client.source(key)`: source collection and item operations
- `client.source_connections()` / `client.source_connection(name)`: connection operations
- `client.library()`: personal library folders, texts, files, and jobs
- `client.group(path).library()`: group-scoped library resources
- `client.group(path).source_folders()` / `source_folder(id)`: group source folders
- `client.settings()`: runtime, provider accounts, Docling, and search settings
- `client.search()` / `client.document(id)`: search and document lookup
- root client: `me()` and `healthz()`

## PAT scopes

- `workspace`: user directory, groups, memberships
- `sources`: source connections, sources, group source folders, sync
- `library`: personal and group library resources
- `settings`: runtime, provider accounts, Docling, search settings
- `search`: search and document APIs

## Migrating from 0.4

Version 0.5 removes the old grouped service methods. Common replacements:

```text
client.workspace().list_groups()                         -> client.groups().list()
client.workspace().get_group(path)                      -> client.group(path).get()
client.sources().sync_source(key)                       -> client.source(key).sync()
client.sources().sync_group_source_folder(path, id)     -> client.group(path).source_folder(id).sync()
client.library().get_library_tree()                     -> client.library().tree()
client.library().get_group_library_file(path, id)       -> client.group(path).library().file(id).get()
client.settings().get_docling_settings()                -> client.settings().docling().get()
client.search().search(request)                         -> client.search().execute(&request)
client.search().get_document(id)                        -> client.document(id).get()
```

Login, refresh, logout, PAT management, and admin-user APIs remain intentionally excluded. HTTP `401 Unauthorized` responses are returned directly; the SDK does not retry with refresh-cookie logic.