openrouter-rs 0.11.1

A type-safe OpenRouter Rust SDK
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
# Migration Guide

This document keeps historical migration guides intact because the repo still smoke-tests older domain/naming transitions.

> Latest breaking release target: `0.9.x -> 0.10.0`.
> `0.10.0` makes high-churn public SDK model types non-exhaustive so additive upstream OpenRouter fields and taxonomy values do not keep causing accidental source breaks.

## Latest: 0.9.x -> 0.10.0

This release intentionally future-proofs public SDK model types that mirror upstream request, response, metadata, usage, pricing, discovery, streaming, and taxonomy shapes. The runtime JSON behavior is unchanged, but Rust source that constructed affected public structs with literals or matched affected public enums exhaustively may need small edits.

### Quick Checklist For 0.10.0

- Replace direct struct literals for affected SDK model types with builders, constructors, helpers, or serde deserialization.
- Add a wildcard arm (`_ => ...`) when matching affected public enums such as stream events, choices, provider taxonomies, finish reasons, and embedding vector variants outside the crate.
- Prefer request builders for caller-built request types: `ChatCompletionRequest::builder()`, `ResponsesRequest::builder()`, `AnthropicMessagesRequest::builder()`, `EmbeddingRequest::builder()`, and similar endpoint builders.
- Use new constructors where direct literals were mainly test/helper code, including `ResponseUsage::new(...)`, `ToolCall::new(...)`, `FunctionCall::new(...)`, `JsonSchemaConfig::new(...)`, `EmbeddingContentPart::text(...)`, `EmbeddingContentPart::image_url(...)`, and provider-options `new(...)` helpers.
- Keep this migration with the `0.10.0` release boundary; it is broader than the 2026-04-29 `ResponseUsage` cost-field drift.

### Breaking-Change Mapping For 0.10.0

| Area | Old Usage (`0.9.x`) | New Usage (`0.10.0`) |
| --- | --- | --- |
| Response usage test data | `ResponseUsage { prompt_tokens, completion_tokens, total_tokens, ... }` | `ResponseUsage::new(prompt_tokens, completion_tokens, total_tokens)` |
| Tool-call test data | `ToolCall { id, type_, function, index }` | `ToolCall::new(id, name, arguments).with_index(index)` |
| Request construction | `EmbeddingRequest { model, input, ... }` | `EmbeddingRequest::builder().model(model).input(input).build()?` |
| Provider preferences | `ProviderPreferences { sort, order, ... }` | `let mut prefs = ProviderPreferences::default(); prefs.sort = Some(...);` |
| Public enum matching | `match event { StreamEvent::Done { .. } => ..., StreamEvent::Error(e) => ... }` | Add `_ => {}` or another fallback arm |
| Response fixtures | Public response struct literals | Deserialize JSON fixtures with `serde_json::from_value(...)` or use documented constructors |

### Example: response fixture construction

Before:

```rust
use openrouter_rs::types::completion::ResponseUsage;

let usage = ResponseUsage {
    prompt_tokens: 5,
    completion_tokens: 7,
    total_tokens: 12,
    cost: None,
    cost_details: None,
    is_byok: None,
};
```

After:

```rust
use openrouter_rs::types::completion::ResponseUsage;

let usage = ResponseUsage::new(5, 7, 12);
```

### Example: non-exhaustive enum matching

Before:

```rust
use openrouter_rs::types::stream::StreamEvent;

fn handle(event: StreamEvent) {
    match event {
        StreamEvent::ContentDelta(text) => print!("{text}"),
        StreamEvent::Done { .. } => println!("done"),
        StreamEvent::Error(error) => eprintln!("{error}"),
    }
}
```

After:

```rust
use openrouter_rs::types::stream::StreamEvent;

fn handle(event: StreamEvent) {
    match event {
        StreamEvent::ContentDelta(text) => print!("{text}"),
        StreamEvent::Done { .. } => println!("done"),
        StreamEvent::Error(error) => eprintln!("{error}"),
        _ => {}
    }
}
```

## Previous: 0.8.x -> 0.9.0

If you use the new workspace, generation metadata, video generation, or audio speech surfaces, prefer request builders and the canonical domain clients. Compatibility aliases remain for the old `tts` names, but new code should use `audio`.

### Quick Checklist For 0.9.0

- Replace `client.tts().create(...)` with `client.audio().speech().create(...)`.
- Replace `api::tts::TtsRequest` with `api::audio::SpeechRequest`.
- Replace `api::tts::TtsResponseFormat` with `api::audio::SpeechResponseFormat`.
- Replace direct struct literals for newly added/high-churn request types with their builders, especially workspace and video-generation requests.
- For CLI workspace I/O logging controls, use `workspaces create|update --io-logging-api-key-id`, `--io-logging-sampling-rate`, and `workspaces update --clear-io-logging-api-key-ids`.

### Breaking-Change Mapping For 0.9.0

| Area | Old Usage (`0.8.x`) | New Usage (`0.9.0`) |
| --- | --- | --- |
| Audio speech domain | `client.tts().create(&request)` | `client.audio().speech().create(&request)` |
| Audio speech imports | `api::tts::{TtsRequest, TtsResponseFormat}` | `api::audio::{SpeechRequest, SpeechResponseFormat}` |
| Audio speech flat helper | `api::tts::create_tts(...)` | `api::audio::create_speech(...)` |
| High-churn request construction | `UpdateWorkspaceRequest { ... }` | `UpdateWorkspaceRequest::builder().name(...).build()?` |
| Workspace CLI key filters | Not exposed | `workspaces create|update --io-logging-api-key-id 101` |
| Workspace CLI sampling rate | Not exposed | `workspaces create|update --io-logging-sampling-rate 0.25` |
| Workspace CLI clear key filters | SDK-only wrapper | `workspaces update ws_123 --clear-io-logging-api-key-ids` |

### Example: audio speech rename

Before:

```rust
use openrouter_rs::{
    api::tts::{TtsRequest, TtsResponseFormat},
    OpenRouterClient,
};

# async fn example() -> Result<(), Box<dyn std::error::Error>> {
let client = OpenRouterClient::builder().api_key("sk-or-v1-...").build()?;
let request = TtsRequest::builder()
    .model("elevenlabs/eleven-turbo-v2")
    .input("Hello")
    .voice("alloy")
    .response_format(TtsResponseFormat::Mp3)
    .build()?;
let audio = client.tts().create(&request).await?;
# let _ = audio;
# Ok(())
# }
```

After:

```rust
use openrouter_rs::{
    api::audio::{SpeechRequest, SpeechResponseFormat},
    OpenRouterClient,
};

# async fn example() -> Result<(), Box<dyn std::error::Error>> {
let client = OpenRouterClient::builder().api_key("sk-or-v1-...").build()?;
let request = SpeechRequest::builder()
    .model("elevenlabs/eleven-turbo-v2")
    .input("Hello")
    .voice("alloy")
    .response_format(SpeechResponseFormat::Mp3)
    .build()?;
let audio = client.audio().speech().create(&request).await?;
# let _ = audio;
# Ok(())
# }
```

### Example: builder-first workspace updates

Before:

```rust
use openrouter_rs::api::workspaces::UpdateWorkspaceRequest;

let request = UpdateWorkspaceRequest {
    name: Some("Platform".to_string()),
    slug: None,
    description: None,
    default_text_model: None,
    default_image_model: None,
    default_provider_sort: None,
    io_logging_api_key_ids: Some(vec![101]),
    io_logging_sampling_rate: Some(0.25),
    is_data_discount_logging_enabled: None,
    is_observability_broadcast_enabled: None,
    is_observability_io_logging_enabled: None,
};
```

After:

```rust
use openrouter_rs::api::workspaces::UpdateWorkspaceRequest;

# fn example() -> Result<(), Box<dyn std::error::Error>> {
let request = UpdateWorkspaceRequest::builder()
    .name("Platform")
    .io_logging_api_key_ids(vec![101])
    .io_logging_sampling_rate(0.25)
    .build()?;
# let _ = request;
# Ok(())
# }
```

## Earlier: 0.7.x -> 0.8.0

If you already use the canonical domain clients (`chat()`, `responses()`, `messages()`, `models()`, `management()`, and opt-in `legacy()`), this upgrade is usually just a version bump plus a recompile. The breaking changes mainly affect callers that coupled themselves to the old transport-facing error and utility surface.

### Quick Checklist For 0.8.0

- Replace `OpenRouterError::HttpRequest(surf::Error)` handling with backend-neutral `OpenRouterError::HttpRequest(HttpRequestError)` handling.
- Replace `surf::StatusCode` comparisons with `http::StatusCode`.
- Stop importing `openrouter_rs::utils::{with_bearer_auth, with_request_metadata, with_client_request_headers, handle_error}`.
- Keep any custom auth/header/request glue in your own application code, or prefer the canonical domain clients directly.

### Breaking-Change Mapping For 0.8.0

| Area | Old Usage (`0.7.x` and earlier) | New Usage (`0.8.0`) |
| --- | --- | --- |
| HTTP request errors | `OpenRouterError::HttpRequest(surf::Error)` | `OpenRouterError::HttpRequest(HttpRequestError)` |
| API status comparisons | `api_error.status == surf::StatusCode::BadRequest` | `api_error.status == http::StatusCode::BAD_REQUEST` |
| Public transport helpers | `openrouter_rs::utils::with_bearer_auth(...)`, `with_request_metadata(...)`, `with_client_request_headers(...)`, `handle_error(...)` | Use `OpenRouterClient` domain methods or caller-owned transport/header helpers |
| SDK transport stack | `surf -> isahc -> curl` | `reqwest + rustls` |

### Example: HTTP request error matching

Before:

```rust
use openrouter_rs::error::OpenRouterError;

fn describe(error: OpenRouterError) {
    match error {
        OpenRouterError::HttpRequest(surf_error) => {
            eprintln!("transport failed: {}", surf_error);
        }
        other => eprintln!("{other}"),
    }
}
```

After:

```rust
use openrouter_rs::error::OpenRouterError;

fn describe(error: OpenRouterError) {
    match error {
        OpenRouterError::HttpRequest(request_error) => {
            eprintln!("transport failed: {}", request_error);
        }
        other => eprintln!("{other}"),
    }
}
```

### Example: API status comparisons

Before:

```rust
if api_error.status == surf::StatusCode::BadRequest {
    eprintln!("bad request");
}
```

After:

```rust
if api_error.status == http::StatusCode::BAD_REQUEST {
    eprintln!("bad request");
}
```

### Example: removing `utils` transport imports

Before:

```rust
use openrouter_rs::utils::{
    handle_error,
    with_bearer_auth,
    with_client_request_headers,
    with_request_metadata,
};
```

After:

```rust
use openrouter_rs::OpenRouterClient;

// Prefer the SDK's domain clients, or keep any custom transport glue local
// to your application instead of importing transport-specific SDK utilities.
```

## Historical: 0.5.x -> 0.6.0

This guide is for users upgrading from the `0.5.x` API surface to `0.6.0`.
All "Before" snippets in this document represent compatibility aliases removed in `0.6.0`.

## Quick Checklist

1. Replace `provisioning_key` naming with `management_key` (`OpenRouterClientBuilder::management_key`, `OpenRouterClient::set_management_key`, `OpenRouterClient::clear_management_key`).
2. Move legacy completions usage to `api::legacy::completion` and `client.legacy().completions()`.
3. Move from flat client calls to domain clients (`chat()`, `responses()`, `messages()`, `models()`, `management()`).
4. Replace old pagination shapes with `types::PaginationOptions`.
5. Update renamed domain methods (`count`, `list_for_user`, `exchange_code_for_api_key`).

## Removed in 0.6.0

These compatibility aliases were removed and must be migrated:

| Removed API | Replacement |
| --- | --- |
| `OpenRouterClientBuilder::provisioning_key(...)` | `OpenRouterClientBuilder::management_key(...)` |
| `OpenRouterClient::set_provisioning_key(...)` | `OpenRouterClient::set_management_key(...)` |
| `OpenRouterClient::clear_provisioning_key()` | `OpenRouterClient::clear_management_key()` |
| `OpenRouterClient::list_api_keys(Some(offset_f64), include_disabled)` | `client.management().list_api_keys(Some(PaginationOptions::with_offset(offset_u32)), include_disabled)` |
| `ManagementClient::exchange_code_for_api_key(...)` | `ManagementClient::create_api_key_from_auth_code(...)` |
| `api::completion::*` | `api::legacy::completion::*` |
| `OpenRouterClient::send_completion_request(...)` | `client.legacy().completions().create(...)` |
| `ModelsClient::list_for_user()` | `ModelsClient::list_user_models()` |
| `ModelsClient::count()` | `ModelsClient::get_model_count()` |

## Breaking-Change Mapping

| Area | Old Usage | New Usage (`0.6.0` target) |
| --- | --- | --- |
| Management key naming | `builder().provisioning_key(...)` | `builder().management_key(...)` |
| Runtime management key naming | `set_provisioning_key`, `clear_provisioning_key` | `set_management_key`, `clear_management_key` |
| Legacy completions module | `api::completion::*` | `api::legacy::completion::*` |
| Legacy completion call path | `client.send_completion_request(...)` | `client.legacy().completions().create(...)` |
| Models user list method | `models().list_for_user()` | `models().list_user_models()` |
| Models count method | `models().count()` | `models().get_model_count()` |
| PKCE exchange in management domain | `management().exchange_code_for_api_key(...)` | `management().create_api_key_from_auth_code(...)` |
| API key management pagination | `client.list_api_keys(Some(offset_f64), include_disabled)` | `client.management().list_api_keys(Some(PaginationOptions::with_offset(offset_u32)), include_disabled)` |
| Guardrails access path | `client.list_guardrails(...)` | `client.management().list_guardrails(...)` |
| Flat endpoint calls | `client.send_chat_completion`, `client.stream_response`, `client.create_api_key`, ... | `client.chat().create`, `client.responses().stream`, `client.management().create_api_key`, ... |

## Top 10 Before/After Recipes

### 1) Builder management key rename

Before:

```rust
let client = OpenRouterClient::builder()
    .provisioning_key("or-mgmt-key")
    .build()?;
```

After:

```rust
let client = OpenRouterClient::builder()
    .management_key("or-mgmt-key")
    .build()?;
```

### 2) Runtime management key rename

Before:

```rust
client.set_provisioning_key("or-mgmt-key");
client.clear_provisioning_key();
```

After:

```rust
client.set_management_key("or-mgmt-key");
client.clear_management_key();
```

### 3) Legacy completions import path

Before:

```rust
use openrouter_rs::api::completion::CompletionRequest;
```

After:

```rust
use openrouter_rs::api::legacy::completion::CompletionRequest;
```

### 4) Legacy completions call path

Before:

```rust
let response = client.send_completion_request(&request).await?;
```

After:

```rust
let response = client.legacy().completions().create(&request).await?;
```

### 5) Flat chat call -> domain chat call

Before:

```rust
let response = client.send_chat_completion(&request).await?;
```

After:

```rust
let response = client.chat().create(&request).await?;
```

### 6) Flat response stream -> domain response stream

Before:

```rust
let stream = client.stream_response(&request).await?;
```

After:

```rust
let stream = client.responses().stream(&request).await?;
```

### 7) Models renamed methods

Before:

```rust
let user_models = client.models().list_for_user().await?;
let count = client.models().count().await?;
```

After:

```rust
let user_models = client.models().list_user_models().await?;
let count = client.models().get_model_count().await?;
```

### 8) PKCE exchange renamed in management domain

Before:

```rust
let auth = client
    .management()
    .exchange_code_for_api_key("code", Some("verifier"), None)
    .await?;
```

After:

```rust
let auth = client
    .management()
    .create_api_key_from_auth_code("code", Some("verifier"), None)
    .await?;
```

### 9) API keys pagination migration

Before:

```rust
let keys = client.list_api_keys(Some(0.0), Some(false)).await?;
```

After:

```rust
use openrouter_rs::types::PaginationOptions;

let keys = client
    .management()
    .list_api_keys(Some(PaginationOptions::with_offset(0)), Some(false))
    .await?;
```

### 10) Guardrails flat call -> management domain

Before:

```rust
use openrouter_rs::types::PaginationOptions;

let guardrails = client
    .list_guardrails(Some(PaginationOptions::with_offset_and_limit(0, 25)))
    .await?;
```

After:

```rust
use openrouter_rs::types::PaginationOptions;

let guardrails = client
    .management()
    .list_guardrails(Some(PaginationOptions::with_offset_and_limit(0, 25)))
    .await?;
```

## Feature Notes

- Legacy text completions require the `legacy-completions` feature.
- On the target `0.6.x` line, enable it explicitly if needed:

```toml
[dependencies]
openrouter-rs = { version = "0.8", features = ["legacy-completions"] }
```

## Validation Tips

- Compile your migration branch with strict checks:
  - `cargo check --all-targets --all-features`
  - `cargo clippy --all-targets --all-features -- -D warnings`
- Re-run your integration suite with real keys after API call-path updates.