roam-sdk 0.4.0

Roam Research SDK and terminal UI client
Documentation
# Client

`RoamClient` is the async HTTP client for the Roam Research API.

## Creating a client

```rust
use roam_sdk::RoamClient;

let client = RoamClient::new("graph-name", "api-token");
```

The client is `Clone` — you can share it across async tasks.

## Methods

### `pull`

Fetch an entity by ID or lookup reference using a [pull expression](https://www.roamresearch.com/#/app/developer-documentation/page/eb8OVhaFC).

```rust
pub async fn pull(
    &self,
    eid: serde_json::Value,
    selector: &str,
) -> Result<PullResponse>
```

**Parameters:**

- `eid` — entity identifier. Either a direct eid or a lookup ref as an EDN string:
  - `json!("[:block/uid \"02-21-2026\"]")` — lookup by block UID
  - `json!("[:node/title \"My Page\"]")` — lookup by page title
- `selector` — EDN pull expression selecting which attributes to return

**Returns:** `PullResponse { result: serde_json::Value }`

**Example:**

```rust
use serde_json::json;

let eid = json!("[:node/title \"Projects\"]");
let selector = "[:block/uid :node/title :block/string {:block/children ...}]";

let resp = client.pull(eid, selector).await?;
let title = resp.result.get(":node/title").and_then(|v| v.as_str());
```

### `query`

Run a [Datalog query](https://www.roamresearch.com/#/app/developer-documentation/page/eb8OVhaFC) against the graph.

```rust
pub async fn query(
    &self,
    query: String,
    args: Vec<serde_json::Value>,
) -> Result<QueryResponse>
```

**Parameters:**

- `query` — Datalog query string with `:find` and `:where` clauses
- `args` — arguments for `:in` clause bindings (pass `vec![]` if none)

**Returns:** `QueryResponse { result: Vec<Vec<serde_json::Value>> }`

Each inner `Vec` is one result row, with values matching the `:find` variables.

**Example:**

```rust
let query = r#"[:find ?uid ?s
                :where [?b :block/string ?s]
                       [?b :block/uid ?uid]
                       [(clojure.string/includes? ?s "TODO")]]"#;

let resp = client.query(query.into(), vec![]).await?;
for row in &resp.result {
    let uid = row[0].as_str().unwrap_or("");
    let text = row[1].as_str().unwrap_or("");
    println!("{}: {}", uid, text);
}
```

### `write`

Execute a write operation (create, update, delete, or move a block).

```rust
pub async fn write(&self, action: WriteAction) -> Result<()>
```

**Parameters:**

- `action` — a `WriteAction` variant describing the mutation

**Example:**

```rust
use roam_sdk::types::*;

client.write(WriteAction::UpdateBlock {
    block: BlockUpdate {
        uid: "abc123".into(),
        string: "Updated content".into(),
    },
}).await?;
```

See [Types](types.md) for all `WriteAction` variants.

### `write_batch`

Execute multiple write operations in sequence. Each action is sent as an individual API request, stopping on the first error.

```rust
pub async fn write_batch(&self, actions: Vec<WriteAction>) -> Result<()>
```

**Parameters:**

- `actions` — a list of `WriteAction` variants to execute atomically

**Example:**

```rust
use roam_sdk::types::*;

client.write_batch(vec![
    WriteAction::CreatePage {
        page: PageCreate {
            title: "New Page".into(),
            uid: None,
        },
    },
    WriteAction::CreateBlock {
        location: BlockLocation {
            parent_uid: "page-uid".into(),
            order: OrderValue::Position("last".into()),
        },
        block: NewBlock {
            string: "First block".into(),
            uid: None,
            open: None,
        },
    },
]).await?;
```

Sends each action as a separate API request in order. Stops and returns the error if any request fails.

## Authentication

The client sends the API token as a Bearer token in the `X-Authorization` header on every request. All communication goes over HTTPS via rustls (no OpenSSL needed).

## Base URL

Requests go to `https://api.roamresearch.com/api/graph/{graph_name}/`:

| Endpoint | Method |
|---|---|
| `/pull` | `pull()` |
| `/q` | `query()` |
| `/write` | `write()` |