This is the official .NET SDK for GetStream's Feeds API.
For detailed setup instructions, see Example.
If you are coming from stream-chat-net, we have a detailed migration guide with
side-by-side code examples for every common Chat use case.
See the Migration Guide.
export STREAM_API_KEY=your-api-key-here
export STREAM_API_SECRET=your-api-secret-hereThis repository uses the API key zta48ppyvwet for continuous integration testing.
The project includes a Makefile for common development tasks:
# Run the sample application
make sample
# Run tests
make test# Additional commands available in Makefile
make build # Build the project
make clean # Clean build artifactsThe SDK is organized into several key components:
src/- Core SDK implementationClient.cs- Main HTTP client with authentication and request handlingCommonClient.cs- Shared client functionality and utilitiesCustomCode.cs- Custom implementations and extensionsFeed.cs- Feed entity and core functionality- Several OpenAPI-generated files:
FeedsV3Client.cs- Generated client with all feeds API methodsmodels.cs- Generated response modelsrequests.cs- Generated request models
tests/- Comprehensive test suiteFeedClientTests.cs- Unit tests for feed clientFeedIntegrationTests.cs- Integration testsFeedTests.cs- General feed functionality tests
samples/- Example applications and usage demosConsoleApp/- Console application demonstrating basic usage
The SDK development workflow:
Core Components
- Manual implementation of core client functionality
- Custom extensions and utilities for .NET-specific features
- Comprehensive test coverage for all components
- OpenAPI-generated code for complete API coverage
Testing
- Unit tests for individual components
- Integration tests for end-to-end functionality
- Sample applications for usage demonstration
Build and Run
- Use Makefile commands for common tasks
- Regular testing and validation
- Continuous integration checks
The SDK follows .NET best practices and conventions while providing a clean, maintainable codebase for GetStream's Feeds API integration.
Pass Logger on StreamOptions (or ClientBuilder.Logger(...)) to receive structured events via Microsoft.Extensions.Logging.ILogger. No logger set means no output; the SDK never changes the logger's configured level. Four canonical events, matching the cross-SDK logging spec:
| Event (dotted name, message prefix) | Level | Emitted |
|---|---|---|
client.initialized | INFO | Once, at BaseClient construction |
http.request.sent | DEBUG | Before every request is sent |
http.response.received | DEBUG | After any HTTP response, including 4xx/5xx |
http.request.failed | ERROR or DEBUG | ERROR on a final transport failure (no HTTP response received); DEBUG instead, with a {RetryAttempt} field, on any attempt that Retry will retry. Never emitted for a final/non-retried 429 (already covered by http.response.received). |
.NET's ILogger uses PascalCase message-template placeholders ({Method}, {StatusCode}, ...). Each maps to a canonical snake_case field name used identically across all GetStream SDKs:
| Event | Placeholder | Canonical field |
|---|---|---|
client.initialized | {SdkName} | stream.sdk.name |
{SdkVersion} | stream.sdk.version | |
{MaxConnsPerHost} | stream.client.max_conns_per_host | |
{IdleTimeoutSeconds} | stream.client.idle_timeout_seconds | |
{ConnectTimeoutSeconds} | stream.client.connect_timeout_seconds | |
{RequestTimeoutSeconds} | stream.client.request_timeout_seconds | |
{GzipEnabled} | stream.client.gzip_enabled | |
{UserHttpClient} | stream.client.user_http_client | |
{LogBodies} | stream.client.log_bodies | |
http.request.sent | {Method} | method |
{Path} | path | |
{Query} | query (redacted) | |
{Body} | body (redacted; only present when LogBodies=true) | |
http.response.received | {Method} | method |
{Path} | path | |
{StatusCode} | status_code | |
{BodySize} | body_size (bytes) | |
{DurationMs} | duration_ms | |
{Body} | body (redacted; only present when LogBodies=true) | |
http.request.failed | {Method} | method |
{Path} | path | |
{ErrorType} | error.type (transport failures only; never present on a retried 429 — see below) | |
{DurationMs} | duration_ms (transport failures only) | |
{Message} | error.message (redacted; transport failures only) | |
{RetryAttempt} | retry.attempt (1-indexed; present only when this attempt will be retried) |
error.type is one of connection_reset, timeout, dns_failure, tls_handshake_failed, unknown (see GetStreamTransportException.ErrorType) — a closed transport-only enum. A retried HTTP 429 has no transport error at all, so its http.request.failed DEBUG line carries only {Method}, {Path}, {RetryAttempt}: never {ErrorType}.
Redaction (always on, no opt-out): query values for api_key/api_secret/token (case-insensitive) become <redacted>; top-level JSON body keys api_secret/token/password become <redacted> (shallow, key names are preserved). No header values are ever logged. error.message is additionally scrubbed for any api_key=/api_secret=/token= value appearing anywhere in the free-form transport-exception text.
Bodies are not logged by default. Set StreamOptions.LogBodies = true (or ClientBuilder.LogBodies(true)) to opt in; body content is still key-redacted as above. Enabling it emits exactly one WARN line at construction.
Auto-retry is opt-in and off by default: with no Retry configured, the client performs exactly one attempt and errors surface unchanged.
varclient=newStreamClient(newStreamOptions{ApiKey="...",ApiSecret="...",Retry=newRetryConfig{Enabled=true,MaxAttempts=3,MaxBackoff=TimeSpan.FromSeconds(30)},});When enabled:
- Only
GET/HEADrequests are retried. Writes (POST/PUT/PATCH/DELETE) never are. - Only HTTP 429 (rate limit) and transport-layer failures (connection reset, timeout, DNS, TLS) are retried; any other 4xx/5xx is never retried.
- A 429 marked
unrecoverableby the backend is never retried. MaxAttemptsis the total attempt budget including the initial request (default3: 1 initial + 2 retries).- The delay before each retry honors a
Retry-Afterresponse header when present, clamped toMaxBackoff; otherwise it's full jitter over[0, min(MaxBackoff, 2^attempt seconds)].
Releases use two paths, both handled by .github/workflows/release.yml:
- Default: automatic release when a PR is merged to
master. The PR title drives the semver bump. - Fallback: manual release via the
Releaseworkflow'sworkflow_dispatch(admin use). Select aversion_bump(patch/minor/major).use_current_version=trueskips the bump and publishes whatever is already insrc/stream-feed-net.csproj.
Automatic semver bump rules:
feat:-> minorfix:(orbug:) -> patchfeat!:or<type>(scope)!:(the!marker) -> major
PRs with any other prefix do not trigger a release.
The release pipeline runs dotnet build, make test, and a warnings-as-errors check on the merged commit before publishing to NuGet. Each step is idempotent; a failed run can be re-dispatched from the Actions UI.