> ## 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.

# .NET

> A .NET client for the StockShift API. It has a method for every operation in the API reference and a model for every request and response. All of it is generated from the API's OpenAPI document.

It targets .NET 10 and .NET Standard 2.0, so it also runs on .NET 8 and .NET Framework 4.6.2 or later. It works with Native AOT and trimming on .NET 10.

## Install

```bash theme={null}
dotnet add package StockShift.Sdk
```

## Quickstart

Create an item, then list stock on hand:

```csharp theme={null}
using StockShift;
using StockShift.Models;

var stockshift = new StockShiftClient(Environment.GetEnvironmentVariable("STOCKSHIFT_TOKEN")!);

await stockshift.Catalog.CreateItemAsync(new CreateItemRequest
{
    Sku = "BOLT-M4",
    Name = "M4 hex bolt",
    Type = ItemType.Stocked,
    MakeOrBuy = MakeOrBuy.Buy,
});

await foreach (var row in stockshift.Inventory.ListStockOnHandAsync())
{
    Console.WriteLine($"{row.Sku} {row.LocationCode} {row.AvailableToPromise}");
}
```

Make one client and reuse it. It is thread safe.

## Dependency injection

In an ASP.NET Core app or any app that uses `Microsoft.Extensions.DependencyInjection`, register the client with `AddStockShift()`. It sends its calls through `IHttpClientFactory`:

```csharp theme={null}
builder.Services.AddStockShift(options => options.Token = builder.Configuration["StockShift:Token"]);
```

Then take `StockShiftClient` in a constructor. `AddStockShift()` returns the `IHttpClientBuilder`, so you can add your own handlers, such as logging, to it.

## Authentication

Pass an API key or an OAuth access token as the token. 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, set `TokenProvider` instead. The client calls it before each request:

```csharp theme={null}
var stockshift = new StockShiftClient(new StockShiftOptions
{
    TokenProvider = cancellationToken => tokens.GetCurrentAsync(cancellationToken),
});
```

Other options:

| Option | Default | Meaning |
| - | - | - |
| `BaseAddress` | `https://app.stockshift.io/api/v1` | Where the API is. |
| `MaxRetries` | `2` | Retries for a call that can be retried. |
| `Timeout` | 60 seconds | How long to wait for each attempt. |
| `MaxRetryAfter` | 60 seconds | The longest `Retry-After` worth waiting for. |
| `TimeProvider` | `TimeProvider.System` | The clock for timeouts and waits. Replace it in tests. |

## Making calls

Each area of the API is a property, such as `Catalog` or `Inventory`, with a method for each operation. Path parameters come first, then the body, then the query string. Every method takes a `CancellationToken` last:

```csharp theme={null}
var item = await stockshift.Catalog.GetItemAsync(itemId, cancellationToken: cancellationToken);
Console.WriteLine(item.Sku);

var page = await stockshift.Catalog.ListItemsPageAsync(new ListItemsQuery { Search = "bolt", Status = ItemStatus.Active }, cancellationToken: cancellationToken);
```

Each method's doc comment names its path, such as `GET /catalog/items/{item}`. A call returns the `data` of the response. A call with an empty response returns a `Task`. A CSV export returns its text. A file or a PDF comes back as a `StockShiftContent`, which holds the unread stream. Dispose it when you are done.

A value that is one of a fixed set, such as an item's type, is a `string`, and the set is a class of constants, such as `ItemType`. A value the API adds later still reads.

A request field that may be left out or sent as null is an `Opt<T>`. Leave the property unset to leave the field out, so the API keeps what it has. Set it to `null` to send an explicit null:

```csharp theme={null}
// Takes the device off its location and keeps its offline grace as it is.
await stockshift.Devices.RenameDeviceAsync(deviceId, new UpdateDeviceRequest { LocationId = null }, cancellationToken: cancellationToken);
```

## Pagination

A list that can grow returns one page at a time. Its method returns an `IAsyncEnumerable` that follows the cursors and yields each record:

```csharp theme={null}
await foreach (var found in stockshift.Catalog.ListItemsAsync(new ListItemsQuery { Search = "bolt" }))
{
    Console.WriteLine(found.Sku);
}
```

The method ending in `PageAsync`, such as `ListItemsPageAsync`, gets one page. To page by hand, send each page's `NextCursor` back as `After` in the query until it is null.

## Errors

A failed call throws a `StockShiftApiException` that holds the RFC 9457 problem details body. Branch on `Code`, which is stable. The detail is written for people and may change.

```csharp theme={null}
try
{
    await stockshift.Catalog.CreateItemAsync(request);
}
catch (StockShiftApiException error) when (error.Code == "validation_failed")
{
    foreach (var (field, messages) in error.Errors)
    {
        Console.WriteLine($"{field}: {string.Join(" ", messages)}");
    }
}
```

`StockShiftApiException` also has `StatusCode`, `Problem` and `Headers`. A call that gets no answer, because the network failed or the attempt timed out, throws a `StockShiftConnectionException`. Both extend `StockShiftException`. Cancelling the token throws an `OperationCanceledException`, as usual in .NET.

## 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 `MaxRetryAfter`, it throws 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:

```csharp theme={null}
await stockshift.Integrations.PushStockNowAsync(connectionId, new RequestOptions { IdempotencyKey = Guid.NewGuid().ToString() });
```

Use a new key 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. `StockShiftWebhook.Verify()` needs the raw body, exactly as it arrived:

```csharp theme={null}
app.MapPost("/webhooks/stockshift", async (HttpRequest request) =>
{
    using var reader = new StreamReader(request.Body);
    var payload = await reader.ReadToEndAsync();

    try
    {
        var webhookEvent = StockShiftWebhook.Verify(payload, request.Headers[StockShiftWebhook.SignatureHeader], secret);
        if (webhookEvent is InventoryStockAdjustedEvent adjusted)
        {
            Console.WriteLine($"{adjusted.Data.ItemId} {adjusted.Data.Quantity}");
        }

        return Results.NoContent();
    }
    catch (WebhookVerificationException)
    {
        return Results.BadRequest();
    }
});
```

Each event type has its own class, such as `InventoryStockAdjustedEvent`. A type this package does not know yet arrives as an `UnknownWebhookEvent`.

It refuses a delivery more than five minutes old. Pass `tolerance` to change that. Retries of an event keep its `Id`, so store it and skip events you have handled.

To test your own receiver, `StockShiftWebhook.Sign()` signs a payload the same way StockShift does.

## Versions

The major version moves when the API changes in a way that can break your code, such as a removed field or a new required parameter. New endpoints and fields come in minor versions. `StockShiftClient.ApiVersion` holds the version of the API reference the package was built from.


## Related topics

- [StockShift for developers](https://docs.stockshift.io/index.md)
- [SDKs](https://docs.stockshift.io/sdks/index.md)
- [Look up a product in the library](https://docs.stockshift.io/api-reference/product-library/look-up-a-product-in-the-library.md)


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