# notra
> **Unofficial** Rust SDK for the [Notra](https://usenotra.com) API.
[](https://crates.io/crates/notra)
[](LICENSE)
## Installation
Add to your `Cargo.toml`:
```toml
[dependencies]
notra = "0.1"
tokio = { version = "1", features = ["full"] }
```
## Quick Start
```rust
use notra::Notra;
use notra::models::{ContentType, LookbackWindow, PostStatus};
#[tokio::main]
async fn main() -> Result<(), notra::NotraError> {
let notra = Notra::builder()
.bearer_auth("your-api-key")
.build()?;
// List all published posts
let response = notra.content()
.list_posts()
.status(PostStatus::Published)
.limit(10)
.send()
.await?;
for post in &response.posts {
println!("{} ({})", post.title, post.id);
}
Ok(())
}
```
## Usage
### Client Configuration
```rust
use std::time::Duration;
use notra::Notra;
let notra = Notra::builder()
.bearer_auth("your-api-key")
.server_url("https://api.usenotra.com") // optional, this is the default
.timeout(Duration::from_secs(60)) // optional, defaults to 30s
.build()?;
```
### Posts
```rust
use notra::models::*;
// List posts with filters
let posts = notra.content()
.list_posts()
.status(PostStatus::Published)
.content_type(ContentType::Changelog)
.sort(Sort::Desc)
.limit(20)
.page(1)
.send()
.await?;
// Get a single post
let post = notra.content()
.get_post("post-id")
.send()
.await?;
// Update a post
let updated = notra.content()
.update_post("post-id")
.title("Updated Title")
.markdown("# Hello\n\nNew content here.")
.status(PostStatus::Published)
.slug("updated-title")
.send()
.await?;
// Delete a post
notra.content()
.delete_post("post-id")
.send()
.await?;
```
### Post Generation
Generate posts from your connected GitHub repositories and Linear integrations:
```rust
use notra::models::*;
// Start a generation job
let job = notra.content()
.create_post_generation(ContentType::Changelog)
.lookback_window(LookbackWindow::Last7Days)
.brand_identity_id("brand-id")
.github_integration_ids(vec!["integration-1".into()])
.repositories(vec![
Repository { owner: "my-org".into(), repo: "my-repo".into() },
])
.data_points(DataPoints {
commits: true,
pull_requests: true,
releases: true,
linear_data: false,
})
.send()
.await?;
println!("Job started: {}", job.job.id);
// Poll for completion
let status = notra.content()
.get_post_generation(&job.job.id)
.send()
.await?;
println!("Status: {:?}", status.job.status);
```
### Brand Identities
```rust
use notra::models::*;
// List brand identities
let identities = notra.content()
.list_brand_identities()
.send()
.await?;
// Generate a brand identity from a website
let job = notra.content()
.create_brand_identity("https://example.com")
.name("My Brand")
.send()
.await?;
// Check generation progress
let status = notra.content()
.get_brand_identity_generation(&job.job.id)
.send()
.await?;
// Get a brand identity
let identity = notra.content()
.get_brand_identity("identity-id")
.send()
.await?;
// Update a brand identity
let updated = notra.content()
.update_brand_identity("identity-id")
.name("New Name")
.tone_profile(ToneProfile::Professional)
.language(Language::English)
.audience("Developers")
.is_default(true)
.send()
.await?;
// Delete a brand identity
notra.content()
.delete_brand_identity("identity-id")
.send()
.await?;
```
### Integrations
```rust
// List all integrations
let integrations = notra.content()
.list_integrations()
.send()
.await?;
for gh in &integrations.github {
println!("{}/{} ({})", gh.owner, gh.repo, gh.default_branch);
}
// Connect a GitHub repository
let gh = notra.content()
.create_github_integration("my-org", "my-repo")
.branch("main")
.send()
.await?;
// Disconnect an integration
notra.content()
.delete_integration("integration-id")
.send()
.await?;
```
## Error Handling
All methods return `Result<T, NotraError>`. The error type covers HTTP errors, API errors, and client misconfiguration:
```rust
use notra::NotraError;
match notra.content().get_post("bad-id").send().await {
Ok(response) => println!("Found: {}", response.post.unwrap().title),
Err(NotraError::Api { status: 404, .. }) => println!("Post not found"),
Err(NotraError::Api { status, message }) => {
println!("API error ({}): {}", status, message);
}
Err(e) => println!("Other error: {e}"),
}
```
## API Reference
All operations are accessible through `notra.content()`:
| `list_posts()` | `GET /v1/posts` | List posts with optional filters |
| `get_post(id)` | `GET /v1/posts/{id}` | Get a single post |
| `update_post(id)` | `PATCH /v1/posts/{id}` | Update a post |
| `delete_post(id)` | `DELETE /v1/posts/{id}` | Delete a post |
| `create_post_generation(type)` | `POST /v1/posts/generate` | Start AI post generation |
| `get_post_generation(job_id)` | `GET /v1/posts/generate/{id}` | Check generation status |
| `list_brand_identities()` | `GET /v1/brand-identities` | List brand identities |
| `get_brand_identity(id)` | `GET /v1/brand-identities/{id}` | Get a brand identity |
| `create_brand_identity(url)` | `POST /v1/brand-identities/generate` | Generate brand identity from website |
| `get_brand_identity_generation(job_id)` | `GET /v1/brand-identities/generate/{id}` | Check generation status |
| `update_brand_identity(id)` | `PATCH /v1/brand-identities/{id}` | Update a brand identity |
| `delete_brand_identity(id)` | `DELETE /v1/brand-identities/{id}` | Delete a brand identity |
| `list_integrations()` | `GET /v1/integrations` | List connected integrations |
| `create_github_integration(owner, repo)` | `POST /v1/integrations/github` | Connect a GitHub repo |
| `delete_integration(id)` | `DELETE /v1/integrations/{id}` | Disconnect an integration |
## Disclaimer
This is an **unofficial**, community-maintained SDK and is not affiliated with or endorsed by Notra. Use at your own discretion.
## License
MIT