> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stockshift.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Rust

> A Rust client for the StockShift API. It has a method for every operation in the API reference and a type for every schema, generated from the API's OpenAPI document.

It is async, built on [reqwest](https://docs.rs/reqwest) and [tokio](https://tokio.rs). It needs Rust 1.85 or later.

## Install

```bash theme={null}
cargo add stockshift
cargo add tokio --features macros,rt-multi-thread
```

TLS uses rustls by default. To use the platform's TLS library instead, turn off the default features and turn on `native-tls`:

```toml theme={null}
stockshift = { version = "1", default-features = false, features = ["native-tls"] }
```

## Quickstart

Create an item, then list stock on hand. `try_next` comes from the [futures-util](https://docs.rs/futures-util) crate.

```rust theme={null}
use futures_util::TryStreamExt;
use stockshift::Client;
use stockshift::types::{CreateItemRequest, ItemType, MakeOrBuy};

#[tokio::main]
async fn main() -> Result<(), stockshift::Error> {
    let client = Client::new(std::env::var("STOCKSHIFT_TOKEN").expect("STOCKSHIFT_TOKEN is set"));

    let item = CreateItemRequest::new("BOLT-M4", "M4 hex bolt", ItemType::Stocked, MakeOrBuy::Buy);
    client.catalog().create_item(&item).await?;

    let mut stock = client.inventory().list_stock_on_hand().stream();
    while let Some(row) = stock.try_next().await? {
        println!("{} {} {}", row.sku, row.location_code, row.quantity);
    }
    Ok(())
}
```

## Authentication

Pass an API key or an OAuth access token to `Client::new`. Create an API key in the app under Settings > Access > API keys. The key acts as the member who made it, and a call needs both the member's permission and one of the key's scopes.

For an OAuth token that expires, pass a `Token::provider`. The client calls it before each request:

```rust theme={null}
use stockshift::{Client, Token};

async fn fresh_token() -> Result<String, std::io::Error> {
    // Refresh the OAuth access token here.
    Ok(String::new())
}

fn client() -> stockshift::Result<Client> {
    Client::builder(Token::provider(fresh_token)).build()
}
```

`Client::builder` takes more settings:

| Method | Default | Meaning |
| - | - | - |
| `base_url` | `https://app.stockshift.io/api/v1` | Where the API is. |
| `max_retries` | `2` | Retries for a call that can be retried. |
| `timeout` | 60 seconds | How long to wait for each attempt. |
| `max_retry_after` | 60 seconds | The longest `Retry-After` worth waiting for. |
| `header` | none | A header sent on every call. |
| `http_client` | a new `reqwest::Client` | The client to send calls with, for a proxy or custom TLS. |

A `Client` is cheap to clone. Clones share one connection pool.

## Making calls

Each area of the API has a method, such as `catalog()` or `inventory()`, with a method for each operation on it. Path parameters and the body are arguments. Optional query parameters are methods on the call. Await the call to send it:

```rust theme={null}
use stockshift::types::ItemStatus;

async fn calls(client: &stockshift::Client) -> stockshift::Result<()> {
    let item = client.catalog().get_item("01J8QZ4M...").await?;
    println!("{} {}", item.sku, item.name);

    let page = client.catalog().list_items().search("bolt").status(ItemStatus::Active).await?;
    for item in page.items {
        println!("{}", item.sku);
    }
    Ok(())
}
```

Each method's documentation names its path, such as `GET /catalog/items/{item}`.

A call returns what the response's `data` holds, so `get_item` returns an `ItemResource`. An empty response returns `()`, a CSV export a `String` and a file `Bytes`.

Every schema in the API reference has a type in `stockshift::types`. A request type is built with `new`, which takes its required fields, and a setter for each optional field:

```rust theme={null}
use stockshift::types::{CreateItemRequest, ItemType, MakeOrBuy};

let mut item = CreateItemRequest::new("BOLT-M4", "M4 hex bolt", ItemType::Stocked, MakeOrBuy::Buy)
    .brand("Acme")
    .weight("0.0040");
// A field the API lets you clear is an `Option<Option<T>>`: `None` leaves
// it out and `Some(None)` sends null.
item.description = Some(None);
```

An enum keeps a value it does not know in its `Unknown` variant, so a value the API adds later still decodes.

For an operation this version of the crate does not have, use `Client::request` with the path and any type that implements `Deserialize`.

## Pagination

A list that can grow returns one page at a time. Call `stream()` on the call to get every record. It fetches the next page when the last one runs out:

```rust theme={null}
use futures_util::TryStreamExt;

async fn skus(client: &stockshift::Client) -> stockshift::Result<Vec<String>> {
    client.catalog().list_items().search("bolt").stream().map_ok(|item| item.sku).try_collect().await
}
```

To page by hand, send each page's `next_cursor` back with `after()` until it is `None`.

## Errors

Every call returns `Result<T, stockshift::Error>`. When the API answers with an error, the error is `Error::Api`, which holds the RFC 9457 problem details body. Branch on its code, which is stable. The detail is written for people and may change.

```rust theme={null}
use stockshift::types::CreateItemRequest;

async fn create(client: &stockshift::Client, item: &CreateItemRequest) {
    match client.catalog().create_item(item).await {
        Ok(created) => println!("created {}", created.id),
        Err(error) if error.code() == Some("validation_failed") => {
            let api = error.api().expect("an API error has problem details");
            println!("{:?}", api.errors()); // {"sku": ["Another item already has the SKU BOLT-M4."]}
        }
        Err(error) => eprintln!("{error}"),
    }
}
```

`ApiError` also has `status`, `detail`, `problem` and `headers`. A call that gets no answer, because the network failed or the attempt timed out, fails with `Error::Connection`.

## Retries and idempotency keys

The client retries a `429` after the time in `Retry-After`. It does this for every method, because the API did not run the call. If `Retry-After` is longer than `max_retry_after`, the call fails instead.

A `5xx` or a network failure is retried for `GET`, `PUT` and `DELETE`, with a short backoff. A `POST` or `PATCH` is retried only when you give it an idempotency key, so a retry cannot make the change twice:

```rust theme={null}
async fn push(client: &stockshift::Client, connection: &str, key: &str) -> stockshift::Result<()> {
    client.integrations().push_stock_now(connection).idempotency_key(key).await?;
    Ok(())
}
```

Use a new key, such as a UUID, for each change you mean to make. The endpoints that honor the key say so in the API reference.

## Webhooks

Check the `StockShift-Signature` header before you trust a delivery. `Webhook::verify` needs the raw body, exactly as it arrived:

```rust theme={null}
use stockshift::webhook::{EventData, Webhook};

/// Returns the HTTP status to answer a delivery with.
fn receive(body: &[u8], signature: Option<&str>, secret: &str) -> u16 {
    let Ok(event) = Webhook::new(secret).verify(body, signature) else {
        return 400;
    };
    if let EventData::InventoryStockAdjusted(adjusted) = &event.data {
        println!("{} {}", adjusted.item_id, adjusted.quantity);
    }
    204
}
```

It refuses a delivery more than five minutes old. Call `tolerance` to change that. Retries of an event keep its `id`, so store it and skip events you have handled. An event this version of the crate does not know arrives as `EventData::Unknown` with its data as JSON.

To test your own receiver, `Webhook::sign` signs a payload the same way StockShift does.

## Versions

The major version moves when an API change can break your code, such as a removed field or a new required parameter. New endpoints, fields and enum values come in minor versions. `stockshift::API_VERSION` holds the version of the API reference the crate was built from.

A minor version may raise the minimum supported Rust version.


## Related topics

- [StockShift for developers](https://docs.stockshift.io/index.md)
- [SDKs](https://docs.stockshift.io/sdks/index.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.