# wb-api-client
> Auto-generated client libraries for the Wildberries Seller API — Python, TypeScript, Go, Java, PHP, OneScript, C# and Rust. One version across all eight ecosystems, bearer tokens redacted by default.
Every page on the site is also served as Markdown at its own address plus `.md` — `guides/quickstart.md`, for instance. That includes the {apiPages} endpoint reference pages, which are not listed individually below: each module page lists its own operations.
---
title: "SDKs for the Wildberries Seller API"
description: "Auto-generated SDKs for Python, TypeScript, Go, Java, PHP, OneScript (1С), C# and Rust. One version across every ecosystem. Bearer tokens redacted by default."
lang: en-US
source: https://valeryverkhoturov.github.io/wb-api-client-docs/en/
---
# wb-api-client — Wildberries API, in your language.
Auto-generated SDKs for Python, TypeScript, Go, Java, PHP, OneScript (1С), C# and Rust. One version across every ecosystem. Bearer tokens redacted by default.
- **13 API categories, one package** — Every category — items, orders (FBS/DBW/FBW/DBS), promotion, analytics, finances, and the rest — ships as an isolated sub-module inside a single per-language package. No cross-category name clashes.
- **Secret redaction, built in** — Your bearer JWT is wrapped in a per-language SecretString so it stays out of logs, prints, and debuggers unless you explicitly ask for the raw value.
- **Daily upstream sync** — A CI job re-downloads the WB specs every morning. If anything changed, a new release is cut and pushed to PyPI, npm, Go, Maven Central, Packagist, hub.oscript.io, NuGet and crates.io automatically — with a matching git tag.
- **One version, eight ecosystems** — 1.YYYYMMDD.N — a valid stable semver in every language. pip / npm / go get / mvn / composer / opm / dotnet add / cargo add all pick the latest without prerelease dance.
- **Bearer auth, everywhere** — The upstream YAMLs conflate an API-key scheme with a bearer JWT. A post-processing pass strips that duplication so every generated client exposes exactly one auth path.
- **Deterministic regeneration** — Pinned openapi-generator + formatter versions + hidden generation timestamps. PRs that break determinism are caught by a per-PR regen check.
wb-api-client is a set of client libraries for the Wildberries Seller API: 13 categories (items, orders across FBS / DBW / FBW / DBS, promotion, analytics, finances and the rest) in eight languages, sharing one version and rebuilt from the official WB specs every day.
## Install
**Python**
```bash
pip install valeryverkhoturov-wb-api-client
```
**TypeScript**
```bash
npm install @valeryverkhoturov/wb-api-client
```
**Go**
```bash
go get github.com/ValeryVerkhoturov/wb-api-client-go@latest
```
**Java (Maven)**
```xml
io.github.valeryverkhoturov
wb-api-client
LATEST
```
**PHP**
```bash
composer require valeryverkhoturov/wb-api-client
```
**OneScript**
```bash
opm install wb-api-client
```
**C#**
```bash
dotnet add package ValeryVerkhoturov.WbApiClient
```
**Rust**
```bash
cargo add wb-api-client
```
## Call something
**Python**
```python
from wb_api_client.items import Configuration, ApiClient
from wb_api_client.items.api import ItemsApi
cfg = Configuration(access_token="")
api = ItemsApi(ApiClient(cfg))
```
**TypeScript**
```ts
import { Configuration, ItemsApi } from "@valeryverkhoturov/wb-api-client/items";
const cfg = new Configuration({});
cfg.setAccessToken("");
const api = new ItemsApi(cfg);
```
**Go**
```go
import wbitems "github.com/ValeryVerkhoturov/wb-api-client-go/items"
cfg := wbitems.NewConfiguration()
cfg.SetAccessToken("")
client := wbitems.NewAPIClient(cfg)
```
**Java**
```java
import io.github.valeryverkhoturov.wbapi.items.ApiClient;
import io.github.valeryverkhoturov.wbapi.items.api.ItemsApi;
ApiClient client = new ApiClient();
client.setAccessToken("");
ItemsApi api = new ItemsApi(client);
```
**PHP**
```php
use ValeryVerkhoturov\WbApiClient\Items\Configuration;
use ValeryVerkhoturov\WbApiClient\Items\Api\ItemsApi;
use GuzzleHttp\Client;
$config = (new Configuration())
->setAccessToken('');
$api = new ItemsApi(new Client(), $config);
```
**OneScript**
```bsl
#Использовать "wb-api-client"
Настройки = Новый Конфигурация();
Настройки.УстановитьТокен("");
Клиент = Новый ItemsApi(Настройки);
```
**C#**
```csharp
using ValeryVerkhoturov.WbApiClient.Items.Api;
using ValeryVerkhoturov.WbApiClient.Items.Client;
var config = new Configuration();
config.SetAccessToken("");
var api = new ItemsApi(config);
```
**Rust**
```rust
use wb_api_client::items::apis::{configuration::Configuration, items_api};
let mut cfg = Configuration::new();
cfg.set_access_token("");
```
Full tour of every sub-module, per-language: [Python](https://valeryverkhoturov.github.io/wb-api-client-docs/en/languages/python) · [TypeScript](https://valeryverkhoturov.github.io/wb-api-client-docs/en/languages/typescript) · [Go](https://valeryverkhoturov.github.io/wb-api-client-docs/en/languages/go) · [Java](https://valeryverkhoturov.github.io/wb-api-client-docs/en/languages/java) · [PHP](https://valeryverkhoturov.github.io/wb-api-client-docs/en/languages/php) · [OneScript](https://valeryverkhoturov.github.io/wb-api-client-docs/en/languages/onescript) · [C#](https://valeryverkhoturov.github.io/wb-api-client-docs/en/languages/csharp) · [Rust](https://valeryverkhoturov.github.io/wb-api-client-docs/en/languages/rust).
For LLM agents: [llms.txt](https://valeryverkhoturov.github.io/wb-api-client-docs/en/llms.txt) — an index of every page on the site, [llms-full.txt](https://valeryverkhoturov.github.io/wb-api-client-docs/en/llms-full.txt) — the whole documentation in one file.
---
title: "Quickstart"
description: "Five minutes: get a token, install the client for your language, make your first API call."
lang: en-US
source: https://valeryverkhoturov.github.io/wb-api-client-docs/en/guides/quickstart
---
# Quickstart
Five minutes: get a token, install the client for your language, make your first API call.
## 1. Get a WB API token
The client libraries are transports — you still need a Wildberries seller account and a personal API token.
1. Sign in at [seller.wildberries.ru](https://seller.wildberries.ru) and open the [Интеграции по API](https://seller.wildberries.ru/api-integrations) (API integrations) section.
2. Click **+ Создать токен** (+ Create token) and pick the **Для интеграции вручную** (Manual integration) tab.
3. Choose the token type — **персональный** (personal) or **базовый** (base) for normal work, **тестовый** (test) for the sandbox.
4. Fill in the name, pick the method categories and the access level (**Чтение и запись** — read & write, or **Только чтение** — read only). For a first test, **Контент** (Content) is enough — that's what powers the `items` sub-module.
5. For a personal token, tick the **Я понимаю, что не следует передавать токен третьим лицам** ("I understand the token must not be shared with third parties") checkbox and click **Создать** (Create).
6. Click **Скопировать и закрыть** (Copy and close) — the token lands on your clipboard. You won't be able to view it again.
Only pick the categories you plan to work with. If you need to call multiple API categories (`items`, `orders_fbs`, `analytics`, …) create a token with the union of categories — or one token per category to keep blast radius small.
## 2. Install
Every SDK ships one package per language. All 13 API categories are inside as sub-modules.
**Python**
```bash
pip install valeryverkhoturov-wb-api-client
```
**TypeScript**
```bash
npm install @valeryverkhoturov/wb-api-client
```
**Go**
```bash
go get github.com/ValeryVerkhoturov/wb-api-client-go@latest
```
**Java (Maven)**
```xml
io.github.valeryverkhoturov
wb-api-client
LATEST
```
**PHP**
```bash
composer require valeryverkhoturov/wb-api-client
```
**OneScript**
```bash
opm install wb-api-client
```
**C#**
```bash
dotnet add package ValeryVerkhoturov.WbApiClient
```
**Rust**
```bash
cargo add wb-api-client
```
`LATEST` for Java: check the [releases page](https://github.com/ValeryVerkhoturov/wb-api-client/releases) for the current `1.YYYYMMDD.N` version and paste it in.
## 3. First call — list your seller info
The `general` sub-module has a `getV1SellerInfo` endpoint that's cheap to call and confirms both your token and your network path.
**Python**
```python
from wb_api_client.general import Configuration, ApiClient
from wb_api_client.general.api import GeneralApi
cfg = Configuration(access_token="")
api = GeneralApi(ApiClient(cfg))
info = api.get_v1_seller_info()
print(info)
```
**TypeScript**
```ts
import {
Configuration,
GeneralApi,
} from "@valeryverkhoturov/wb-api-client/general";
const cfg = new Configuration({});
cfg.setAccessToken("");
const api = new GeneralApi(cfg);
const info = await api.getV1SellerInfo();
console.log(info.data);
```
**Go**
```go
package main
import (
"context"
"fmt"
wbgeneral "github.com/ValeryVerkhoturov/wb-api-client-go/general"
)
func main() {
cfg := wbgeneral.NewConfiguration()
cfg.SetAccessToken("")
client := wbgeneral.NewAPIClient(cfg)
info, _, err := client.GeneralAPI.GetV1SellerInfo(context.Background()).Execute()
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", info)
}
```
**Java**
```java
import io.github.valeryverkhoturov.wbapi.general.ApiClient;
import io.github.valeryverkhoturov.wbapi.general.api.GeneralApi;
ApiClient client = new ApiClient();
client.setAccessToken("");
GeneralApi api = new GeneralApi(client);
System.out.println(api.getV1SellerInfo());
```
**PHP**
```php
use ValeryVerkhoturov\WbApiClient\General\Configuration;
use ValeryVerkhoturov\WbApiClient\General\Api\GeneralApi;
use GuzzleHttp\Client;
$config = (new Configuration())
->setAccessToken('');
$api = new GeneralApi(new Client(), $config);
print_r($api->getV1SellerInfo());
```
**OneScript**
```bsl
#Использовать "wb-api-client"
Настройки = Новый Конфигурация();
Настройки.УстановитьТокен("");
Клиент = Новый GeneralApi(Настройки);
Сообщить(Клиент.GetV1SellerInfo().Тело);
```
**C#**
```csharp
using ValeryVerkhoturov.WbApiClient.General.Api;
using ValeryVerkhoturov.WbApiClient.General.Client;
var config = new Configuration();
config.SetAccessToken("");
var api = new GeneralApi(config);
Console.WriteLine(api.GetV1SellerInfo());
```
**Rust**
```rust
use wb_api_client::general::apis::{configuration::Configuration, general_api};
#[tokio::main]
async fn main() {
let mut cfg = Configuration::new();
cfg.set_access_token("");
let info = general_api::get_v1_seller_info(&cfg).await;
println!("{:#?}", info);
}
```
If that returns your seller data — you're wired up.
## 4. Where to go next
- **[Authentication](https://valeryverkhoturov.github.io/wb-api-client-docs/en/guides/authentication)** — how the secret-string wrapper works, why you should never print your token, and how to expose the raw value when you actually need to.
- **[Error handling](https://valeryverkhoturov.github.io/wb-api-client-docs/en/guides/error-handling)** — WB's status codes, retry strategy for 429/5xx, and how errors surface in each language.
- **[Language pages](https://valeryverkhoturov.github.io/wb-api-client-docs/en/languages/python)** — every sub-module, install snippet, and the shape of each `Api` class.
## Troubleshooting the first call
| Symptom | Likely cause | Fix |
| ------------------------------------ | --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `401 Unauthorized` | Token wrong / expired / not for this category | Regenerate in Seller portal; ensure the category matches the module (e.g. `Контент` for `items`) |
| `403 Forbidden` | Token valid but category missing | Add the required category to the token, or make a new one |
| `429 Too Many Requests` | You've hit WB's rate limit | Wait `X-Ratelimit-Retry` seconds from the response and back off; the SDK does not auto-retry |
| Connection hangs | Corporate proxy / TLS interception | Set `HTTPS_PROXY` env var; most clients honor it |
| `ImportError` / `Cannot find module` | Installed the wrong package name | Package is `valeryverkhoturov-wb-api-client` / `@valeryverkhoturov/wb-api-client` — the bare `wb-api-client` name is a different project |
---
title: "Authentication"
description: "Every Wildberries Seller API call takes a bearer JWT in the Authorization header. The clients handle the header for you; you just hand them the token."
lang: en-US
source: https://valeryverkhoturov.github.io/wb-api-client-docs/en/guides/authentication
---
# Authentication
Every Wildberries Seller API call takes a **bearer JWT** in the `Authorization` header. The clients handle the header for you; you just hand them the token.
## Where the token comes from
Tokens are minted in the seller portal:
1. [seller.wildberries.ru](https://seller.wildberries.ru) → the [Интеграции по API](https://seller.wildberries.ru/api-integrations) (API integrations) section.
2. **+ Создать токен** (+ Create token) → the **Для интеграции вручную** (Manual integration) tab → choose the token type: **персональный** (personal), **базовый** (base), or **тестовый** (test) for the sandbox.
3. Fill in the name, pick the method categories (Контент, Маркетплейс, Аналитика, …) and the access level — **Чтение и запись** (read & write) or **Только чтение** (read only).
4. **Создать** (Create) → **Скопировать и закрыть** (Copy and close). The token is shown once — save it immediately.
Each category you tick corresponds roughly to one or more sub-modules in the clients. If a call returns `403`, the token is valid but lacks the category — regenerate with it ticked.
Which token type to pick:
- **Personal** — for your own programs and on-premise systems (including local 1C installations). Grants access to sensitive data, so it must not be shared with third parties or used in cloud services.
- **Base** — access to a limited set of data. Good for testing the integration on real data and for cases where a personal token doesn't fit.
- **Test** — works only with the [WB API sandbox](https://dev.wildberries.ru/openapi/sandbox) and generated test data; real seller data is not accessible.
- **Service** — created for a specific cloud service from the [Solutions for Business catalog](https://seller.wildberries.ru/auth-services); categories and access levels are filled in automatically.
Every token lives 180 days from creation, after which you need to mint a new one. You can check a token's validity and categories by decoding the JWT — it's a plain [RFC 7519](https://www.rfc-editor.org/rfc/rfc7519) token. Don't paste it into online decoders: anyone can intercept it that way.
## Secret-string wrappers
Once you have the token, don't pass it around as a plain string. Every client stores the value in a language-native "secret string" type — in Java, PHP, C# and Rust you hand the setter a plain string and it wraps the value internally — so the token redacts under logs, `print`, debuggers, and error dumps, unless you explicitly ask for the raw value.
| Language | Wrapper | Setter | Redacted under | Explicit expose |
| ---------- | -------------------------------------------------------------------------------------------- | --------------------------------------- | --------------------------------------------------------- | --------------------- |
| Python | [`pydantic.SecretStr`](https://docs.pydantic.dev/latest/api/types/#pydantic.types.SecretStr) | `Configuration(access_token=…)` | `print(cfg.access_token)` → `**********` | `.get_secret_value()` |
| TypeScript | inlined `SecretString` class | `Configuration.setAccessToken(…)` | `console.log(new SecretString("…"))` → `` | `.exposeSecret()` |
| Go | [`secrecy.SecretString`](https://github.com/negrel/secrecy) | `Configuration.SetAccessToken(…)` | `fmt.Printf("%v", cfg.AccessToken)` → `` | `.ExposeSecret()` |
| Java | per-sub-module `SecretString` | `ApiClient.setAccessToken(String)` | `System.out.println(s)` → `` | — |
| PHP | per-sub-module `SecretString` | `Configuration::setAccessToken(string)` | `var_dump($config)` → token `''` | `getAccessToken()` |
| C# | per-namespace `SecretString` | `Configuration.SetAccessToken(string)` | `Console.WriteLine(s)` → `` | no getter by design |
| OneScript | `СекретнаяСтрока` class | `Конфигурация.УстановитьТокен(…)` | `Сообщить(Настройки.Токен())` → `СекретнаяСтрока` | `.Раскрыть()` |
| Rust | per-module `SecretString` struct | `Configuration::set_access_token(&str)` | `println!("{s:?}")` / `{}` → `` | `.expose_secret()` |
### Why bother?
Real incidents behind this design:
- Logger middleware that dumps the request config on error.
- `print(config)` sprinkled in during debugging and never removed.
- Exceptions serialized to Sentry / a bug tracker with the full HTTP client context attached.
- IDE debuggers displaying object internals in a shared screen-share.
The wrapper flips all of those from a token leak into a `` marker. The one-line cost is the wrapper type at the boundary.
### Examples of the redaction firing
**Python**
```python
from wb_api_client.items import Configuration
cfg = Configuration(access_token="eyJhbGciOi...")
print(cfg.access_token) # SecretStr('**********')
print(cfg.access_token.get_secret_value()) # eyJhbGciOi... (opt-in)
```
**TypeScript**
```ts
import { Configuration } from "@valeryverkhoturov/wb-api-client/items";
const cfg = new Configuration({});
cfg.setAccessToken("eyJhbGciOi...");
console.log(cfg.accessToken); // SecretString { }
console.log(cfg.accessToken.exposeSecret()); // eyJhbGciOi... (opt-in)
```
**Go**
```go
import wbitems "github.com/ValeryVerkhoturov/wb-api-client-go/items"
cfg := wbitems.NewConfiguration()
cfg.SetAccessToken("eyJhbGciOi...")
fmt.Printf("%v\n", cfg.AccessToken) //
fmt.Println(cfg.AccessToken.ExposeSecret()) // eyJhbGciOi... (opt-in)
```
**Java**
```java
SecretString s = new SecretString("eyJhbGciOi...");
System.out.println(s); //
System.out.println(s.exposeSecret()); // eyJhbGciOi... (opt-in)
```
**PHP**
```php
use ValeryVerkhoturov\WbApiClient\Items\Configuration;
$config = (new Configuration())->setAccessToken('eyJhbGciOi...');
var_dump($config); // accessToken => ''
echo $config->getAccessToken(); // eyJhbGciOi... (opt-in)
```
**OneScript**
```bsl
С = Новый СекретнаяСтрока("eyJhbGciOi...");
Сообщить(С); // СекретнаяСтрока
Сообщить(С.Раскрыть()); // eyJhbGciOi... (opt-in)
```
**C#**
```csharp
var s = new SecretString("eyJhbGciOi...");
Console.WriteLine($"{s}"); //
Console.WriteLine(s.ExposeSecret()); // eyJhbGciOi... (opt-in)
```
**Rust**
```rust
use wb_api_client::items::apis::configuration::SecretString;
let s = SecretString::new("eyJhbGciOi...");
println!("{s:?}"); //
println!("{}", s.expose_secret()); // eyJhbGciOi... (opt-in)
```
## Multiple sub-modules, one token
If you use several sub-modules in the same process, each has its own `Configuration` / `ApiClient` — because each sub-module is a self-contained SDK generated from a different upstream spec. Reuse the token string; construct a config per module.
**Python**
```python
from wb_api_client.items import Configuration as ItemsCfg, ApiClient as ItemsClient
from wb_api_client.orders_fbs import Configuration as OrdCfg, ApiClient as OrdClient
TOKEN = ""
items_api = ItemsClient(ItemsCfg(access_token=TOKEN))
orders_api = OrdClient(OrdCfg(access_token=TOKEN))
```
**TypeScript**
```ts
import { Configuration as ItemsCfg } from "@valeryverkhoturov/wb-api-client/items";
import { Configuration as OrdCfg } from "@valeryverkhoturov/wb-api-client/orders_fbs";
const TOKEN = "";
const itemsCfg = new ItemsCfg({}); itemsCfg.setAccessToken(TOKEN);
const ordCfg = new OrdCfg({}); ordCfg.setAccessToken(TOKEN);
```
## Rotating tokens
WB tokens don't rotate on the wire and live at most 180 days — you have to swap them. Best practice:
1. Store the token in a secret manager (SSM, Vault, GitHub Actions secret) — never in source.
2. Read it into an env var at process start; construct the config once.
3. On rotation: kill the process (or the connection pool) and start fresh with the new token. The clients cache the token per Configuration instance.
## Common auth failures
| Response | Meaning |
| ----------------------------------------- | ------------------------------------------------------------------------------------------ |
| `401 Unauthorized` — `token expired` | Token past its expiry; mint a new one |
| `401 Unauthorized` — `invalid signature` | Wrong Authorization header format, or token from a different environment (sandbox vs prod) |
| `403 Forbidden` — `no access to resource` | Token valid, scope missing — regenerate with the right box ticked |
| `429 Too Many Requests` | Rate-limited; the token itself is fine |
See the [error-handling guide](https://valeryverkhoturov.github.io/wb-api-client-docs/en/guides/error-handling) for retry patterns.
---
title: "Error handling"
description: "WB returns standard HTTP status codes plus a JSON body. The clients surface these as language-native exceptions — you don't get a raw Response unless you…"
lang: en-US
source: https://valeryverkhoturov.github.io/wb-api-client-docs/en/guides/error-handling
---
# Error handling
WB returns standard HTTP status codes plus a JSON body. The clients surface these as language-native exceptions — you don't get a raw `Response` unless you explicitly ask for one.
## Status codes worth handling
| Code | Meaning | Right response |
| ----------- | ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `200`–`204` | Success | Parse the body (or don't, for 204) |
| `400` | Request malformed | Fix the request; not retriable |
| `401` | Missing / bad / expired token | Refresh the token; do NOT retry with the same token |
| `402` | Service balance is out of funds | Only returned to services from the "Solutions for Business" catalog — top up the balance |
| `403` | Token lacks scope for this endpoint | Check: token category matches the method, the method is allowed for the token type, the token wasn't issued by a deleted user. Regenerate if needed |
| `404` | Resource doesn't exist | Check the URL; business-logic decision — not always an error. Useful details in the body's `detail` field |
| `409` | Conflict (e.g. supply already delivered) | Business-logic; specific WB schemas per endpoint |
| `413` | Request body exceeds the limit | Reduce the number of objects in the request |
| `422` | Request parameter processing error | Check the request data — fields must not contradict each other |
| `429` | Rate-limited | Wait `X-Ratelimit-Retry` seconds from the response, then retry |
| `498` | WBAAS anti-bot challenge | Never fires against these clients (correct UA) — if you see it, something is proxying/rewriting |
| `5xx` | Upstream problem | Retry with exponential backoff, cap attempts |
## Retry pattern
The clients don't retry on your behalf — that's a policy decision. A minimal retriable-error handler:
**Python**
```python
import time
from wb_api_client.items.exceptions import ApiException
def with_retry(fn, *, max_attempts=5, base=1.0):
for attempt in range(1, max_attempts + 1):
try:
return fn()
except ApiException as e:
if e.status not in (429, 500, 502, 503, 504) or attempt == max_attempts:
raise
retry_after = float(
e.headers.get("X-Ratelimit-Retry") or e.headers.get("Retry-After") or 0
) or base * 2 ** attempt
time.sleep(retry_after)
```
**TypeScript**
```ts
import { AxiosError } from "axios";
async function withRetry(
fn: () => Promise,
{ maxAttempts = 5, base = 1000 } = {},
): Promise {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await fn();
} catch (err) {
const e = err as AxiosError;
const status = e.response?.status ?? 0;
if (![429, 500, 502, 503, 504].includes(status) || attempt === maxAttempts) throw err;
const retryAfterHeader =
e.response?.headers?.["x-ratelimit-retry"] ??
e.response?.headers?.["retry-after"];
const delay = retryAfterHeader
? Number(retryAfterHeader) * 1000
: base * 2 ** attempt;
await new Promise((r) => setTimeout(r, delay));
}
}
throw new Error("unreachable");
}
```
**Go**
```go
import (
"context"
"errors"
"net/http"
"time"
)
func withRetry[T any](ctx context.Context, fn func() (T, *http.Response, error)) (T, error) {
var zero T
for attempt := 1; attempt <= 5; attempt++ {
v, resp, err := fn()
if err == nil {
return v, nil
}
status := 0
if resp != nil { status = resp.StatusCode }
if status != 429 && (status < 500 || status > 504) || attempt == 5 {
return zero, err
}
delay := time.Duration(1<
---
title: "Custom headers"
description: "Sometimes every request needs a header the WB spec knows nothing about: credentials for a gateway or corporate proxy sitting in front of the API, a trace…"
lang: en-US
source: https://valeryverkhoturov.github.io/wb-api-client-docs/en/guides/custom-headers
---
# Custom headers
Sometimes every request needs a header the WB spec knows nothing about: credentials for a gateway or corporate proxy sitting in front of the API, a trace identifier, an environment tag. Every client can add them — the mechanism differs per language, and all eight are covered below.
## What this is not for
Two things are worth separating up front, or you will end up adding a header the client already sets.
**The WB token.** The client builds the `Authorization` header itself from your token — see [Authentication](https://valeryverkhoturov.github.io/wb-api-client-docs/en/guides/authentication). Do not assemble it by hand through the mechanisms on this page: in some languages (Go) that produces two `Authorization` headers on one request.
**Headers declared in the spec.** If a header is declared on an operation, the generator has already turned it into an ordinary method argument. WB has a couple: `X-Nm-Id` and `X-Photo-Number` on `postV3MediaFile` in the `items` sub-module. Pass those as arguments, not as custom headers.
What is left is everything else — headers the spec does not know about. Those are what we add here.
## Headers for the whole client
The common case: the header belongs on every request. Set it once, when you build the client.
**Python**
```python
from wb_api_client.items import Configuration, ApiClient
from wb_api_client.items.api import ItemsApi
client = ApiClient(Configuration(access_token=""))
client.set_default_header("X-Gateway-Token", "")
client.set_default_header("X-Request-Source", "erp-sync")
api = ItemsApi(client)
```
**TypeScript**
```ts
import {
Configuration,
ItemsApi,
} from "@valeryverkhoturov/wb-api-client/items";
const cfg = new Configuration({
baseOptions: {
headers: {
"X-Gateway-Token": "",
"X-Request-Source": "erp-sync",
},
},
});
cfg.setAccessToken("");
const api = new ItemsApi(cfg);
```
**Go**
```go
import wbitems "github.com/ValeryVerkhoturov/wb-api-client-go/items"
cfg := wbitems.NewConfiguration()
cfg.SetAccessToken("")
cfg.AddDefaultHeader("X-Gateway-Token", "")
cfg.AddDefaultHeader("X-Request-Source", "erp-sync")
client := wbitems.NewAPIClient(cfg)
```
**Java**
```java
import io.github.valeryverkhoturov.wbapi.items.ApiClient;
import io.github.valeryverkhoturov.wbapi.items.api.ItemsApi;
ApiClient client = new ApiClient();
client.setAccessToken("");
client.addDefaultHeader("X-Gateway-Token", "");
client.addDefaultHeader("X-Request-Source", "erp-sync");
ItemsApi api = new ItemsApi(client);
```
**PHP**
```php
use ValeryVerkhoturov\WbApiClient\Items\Configuration;
use ValeryVerkhoturov\WbApiClient\Items\Api\ItemsApi;
use GuzzleHttp\Client;
$config = (new Configuration())
->setAccessToken('');
// PHP's Configuration has no header registry — the Guzzle client holds them.
$http = new Client([
'headers' => [
'X-Gateway-Token' => '',
'X-Request-Source' => 'erp-sync',
],
]);
$api = new ItemsApi($http, $config);
```
**OneScript**
```bsl
#Использовать "wb-api-client"
Настройки = Новый Конфигурация();
Настройки.УстановитьТокен("");
Настройки.ДополнительныеЗаголовки.Вставить("X-Gateway-Token", "");
Настройки.ДополнительныеЗаголовки.Вставить("X-Request-Source", "erp-sync");
Клиент = Новый ItemsApi(Настройки);
```
**C#**
```csharp
using ValeryVerkhoturov.WbApiClient.Items.Api;
using ValeryVerkhoturov.WbApiClient.Items.Client;
var config = new Configuration();
config.SetAccessToken("");
config.DefaultHeaders["X-Gateway-Token"] = "";
config.DefaultHeaders["X-Request-Source"] = "erp-sync";
var api = new ItemsApi(config);
```
**Rust**
```rust
use wb_api_client::items::apis::configuration::Configuration;
// Rust's Configuration has no header registry — the reqwest client holds them.
let mut headers = reqwest::header::HeaderMap::new();
headers.insert("X-Gateway-Token", "".parse().unwrap());
headers.insert("X-Request-Source", "erp-sync".parse().unwrap());
let mut cfg = Configuration::new();
cfg.set_access_token("");
cfg.client = reqwest::Client::builder()
.default_headers(headers)
.build()
.unwrap();
```
Headers live on a `Configuration` / `ApiClient` instance — and each sub-module has its own. If you use both `items` and `orders_fbs`, set the headers on both; see [Multiple sub-modules, one token](https://valeryverkhoturov.github.io/wb-api-client-docs/en/guides/authentication#multiple-sub-modules-one-token).
## Headers for a single request
Not every client can set a header for one call — it depends on what openapi-generator emitted for that language.
| Language | Per-call support |
| ---------- | ---------------------------------------------------- |
| Python | yes — a `_headers=` keyword argument on every method |
| TypeScript | yes — a trailing `RawAxiosRequestConfig` argument |
| Go | no — methods take no headers |
| Java | no |
| PHP | no |
| C# | no |
| OneScript | no — `Заголовки` are assembled inside the method |
| Rust | no — methods take only `&Configuration` |
**Python**
```python
# Careful: default headers override _headers when the names collide.
tags = api.get_v2_tags(_headers={"X-Correlation-Id": "9f1c-…"})
```
**TypeScript**
```ts
// The opposite here: a per-call header beats baseOptions and Authorization.
const tags = await api.getV2Tags({
headers: { "X-Correlation-Id": "9f1c-…" },
});
```
In the other six languages, if a header is only needed on some requests, build a second client instance carrying its own headers and route those calls through it.
## Precedence
Each generator merges headers in its own order, and it is not always the intuitive one. Verified against the generated code:
| Language | Order (rightmost wins) | Note |
| ---------- | ------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| Python | `_headers` → default headers | **Defaults win**: `default_headers` are applied on top of `_headers` |
| TypeScript | auth → `baseOptions.headers` → per-call headers | a config header can override `Authorization` too |
| Go | — | uses `Header.Add`, so it **appends rather than replaces**: a duplicate name sends two headers |
| Java | `defaultHeaderMap` over operation headers | replaces by name |
| PHP | request headers → Guzzle defaults | Guzzle client defaults apply only when the header is not already set |
| C# | `DefaultHeaders` → operation headers | replaces by name |
| OneScript | `Accept`/`User-Agent`/`Authorization` → `ДополнительныеЗаголовки` → operation headers | `Соответствие.Вставить` replaces the value |
| Rust | client `default_headers` → operation headers | a header set by the operation overrides the reqwest client default |
The practical takeaway is the same everywhere: do not use the mechanisms on this page to override a header the client already sets. `User-Agent` has a dedicated setting in every language, and `Authorization` has the token setter.
## Secrets in headers
`SecretString` wraps the WB token **only**. Gateway or partner-service credentials are plain strings, and the client does not redact them: they appear in full in an HTTP-client log or a configuration dump.
So the same rules apply to them as to the WB token:
1. Keep the value in a secret manager (Vault, SSM, a GitHub Actions secret), not in source.
2. Read it into an environment variable at process start and build the client once.
3. Check that your request logger does not dump headers wholesale — or add your header names to its redaction list.
If you log requests through middleware, add these header names to the same filter that already holds `Authorization`.
---
title: "Python"
description: "Python client for the Wildberries Seller API: valeryverkhoturov-wb-api-client on PyPI, 13 API categories, redacted token."
lang: en-US
source: https://valeryverkhoturov.github.io/wb-api-client-docs/en/languages/python
---
# Python
Package: [`valeryverkhoturov-wb-api-client`](https://pypi.org/project/valeryverkhoturov-wb-api-client/) on PyPI.
- **Python:** 3.9+
- **HTTP:** `urllib3`
- **Models:** `pydantic` v2
- **Secret wrapper:** `pydantic.SecretStr`
## Install
```bash
pip install valeryverkhoturov-wb-api-client
```
For projects on Poetry / uv / PDM, add the dependency the usual way:
**pyproject.toml**
```toml
[project]
dependencies = [
"valeryverkhoturov-wb-api-client",
]
```
**uv**
```bash
uv add valeryverkhoturov-wb-api-client
```
**poetry**
```bash
poetry add valeryverkhoturov-wb-api-client
```
## Import shape
The distribution installs one top-level package `wb_api_client` with 13 sub-modules — one per WB API category. Each sub-module is a self-contained SDK with its own `Configuration`, `ApiClient`, and exactly one API class named after the module (`GeneralApi`, `ItemsApi`, `OrdersFbsApi`, and so on).
```python
from wb_api_client. import Configuration, ApiClient
from wb_api_client..api import Api
```
## Sub-modules
| Slug | Category |
| ----------------- | ------------------------------------------ |
| `general` | Общее — ping, seller info, user management |
| `items` | Работа с товарами (Content) |
| `orders_fbs` | Заказы FBS |
| `orders_dbw` | Заказы DBW |
| `dbs` | DBS |
| `in_store_pickup` | Самовывоз |
| `orders_fbw` | Поставки FBW |
| `promotion` | Маркетинг и продвижение |
| `communications` | Общение с покупателями |
| `rates` | Тарифы |
| `analytics` | Аналитика и данные |
| `reports` | Отчёты |
| `finances` | Документы и бухгалтерия |
Each sub-module exposes a single API class named after the module: `general` → `GeneralApi`, `items` → `ItemsApi`, `orders_fbs` → `OrdersFbsApi`, and so on. The full list is in the per-release README: [clients/python/README.md](https://github.com/ValeryVerkhoturov/wb-api-client/blob/main/clients/python/README.md).
## Auth
```python
from wb_api_client.items import Configuration, ApiClient
from wb_api_client.items.api import ItemsApi
cfg = Configuration(access_token="") # wrapped in SecretStr
client = ApiClient(cfg)
api = ItemsApi(client)
```
Passing a bare string is fine — it's auto-wrapped. If you want the raw token back for logging / diagnostics:
```python
cfg.access_token # SecretStr(**********)
cfg.access_token.get_secret_value() # ""
```
## Async / sync
The generated client is **synchronous** (urllib3). If you need async, run calls in a thread pool:
```python
import asyncio
result = await asyncio.to_thread(api.get_v1_seller_info)
```
A native-async variant is not on the roadmap — openapi-generator's async-python template lags the sync one.
## Type hints
Every model is a pydantic v2 model. Editors and type-checkers see full types:
```python
from wb_api_client.items.models import Product # dataclass-like pydantic model
p: Product = Product.model_validate(some_dict)
```
## Testing against the client
The `Configuration` object accepts a `host` override so you can point at a mock server:
```python
cfg = Configuration(
access_token="test-token",
host="http://localhost:8080",
)
```
## See also
- [Authentication guide](https://valeryverkhoturov.github.io/wb-api-client-docs/en/guides/authentication)
- [Error handling](https://valeryverkhoturov.github.io/wb-api-client-docs/en/guides/error-handling)
- [Full per-module reference on GitHub](https://github.com/ValeryVerkhoturov/wb-api-client/blob/main/clients/python/README.md)
---
title: "TypeScript"
description: "TypeScript client for the Wildberries Seller API: @valeryverkhoturov/wb-api-client on npm, first-class .d.ts types, axios, 13 API categories."
lang: en-US
source: https://valeryverkhoturov.github.io/wb-api-client-docs/en/languages/typescript
---
# TypeScript
Package: [`@valeryverkhoturov/wb-api-client`](https://www.npmjs.com/package/@valeryverkhoturov/wb-api-client) on npm.
- **Node:** 18+
- **HTTP:** `axios`
- **Types:** first-class TypeScript, ships `.d.ts`
- **Secret wrapper:** inlined `SecretString` class
## Install
**npm**
```bash
npm install @valeryverkhoturov/wb-api-client
```
**pnpm**
```bash
pnpm add @valeryverkhoturov/wb-api-client
```
**yarn**
```bash
yarn add @valeryverkhoturov/wb-api-client
```
**bun**
```bash
bun add @valeryverkhoturov/wb-api-client
```
## Import shape
Every sub-module is a subpath export. Import from `@valeryverkhoturov/wb-api-client/`:
```ts
import {
Configuration,
ItemsApi,
} from "@valeryverkhoturov/wb-api-client/items";
```
You never import from the root `@valeryverkhoturov/wb-api-client` — there's no barrel to avoid pulling in 13 SDKs when you only need one.
## Sub-modules
Sub-path per category:
`general`, `items`, `orders_fbs`, `orders_dbw`, `dbs`, `in_store_pickup`, `orders_fbw`, `promotion`, `communications`, `rates`, `analytics`, `reports`, `finances`.
Full list of `Api` classes per module lives in the per-release README: [clients/typescript/README.md](https://github.com/ValeryVerkhoturov/wb-api-client/blob/main/clients/typescript/README.md).
## Auth
```ts
import { Configuration, ItemsApi } from "@valeryverkhoturov/wb-api-client/items";
const cfg = new Configuration({});
cfg.setAccessToken("");
const api = new ItemsApi(cfg);
const res = await api.someEndpoint();
console.log(res.data);
```
The `setAccessToken` method wraps the value in a `SecretString` internally. Direct assignment (`cfg.accessToken = "..."`) is not supported — always go through the setter so redaction is guaranteed.
To read the raw value back (e.g. for a diagnostic log):
```ts
cfg.accessToken.exposeSecret();
```
## User-Agent
The client sends `ValeryVerkhoturov/wb-api-client/typescript` on every request — WB uses this as a coarse-grained routing signal. To add your own app identifier without replacing it:
```ts
const cfg = new Configuration({});
cfg.setAccessToken(TOKEN);
cfg.baseOptions = {
...cfg.baseOptions,
headers: {
...cfg.baseOptions.headers,
"User-Agent":
"MyApp/1.2.3 (+https://myapp.example) ValeryVerkhoturov/wb-api-client/typescript",
},
};
```
## ESM / CJS
The package publishes dual entry points — `import` from ESM code, `require` from CommonJS. TypeScript `"module": "NodeNext"` or `"module": "bundler"` in your `tsconfig.json` picks the right one automatically.
## Bundler support
The subpath exports are declared in `package.json` `exports`. Modern bundlers (Vite, Rollup, esbuild, Webpack 5+, Turbopack) respect them out of the box. Webpack 4 does not — upgrade or add a manual alias.
## Testing
Point at a mock server via `basePath`:
```ts
const cfg = new Configuration({ basePath: "http://localhost:8080" });
```
## See also
- [Authentication guide](https://valeryverkhoturov.github.io/wb-api-client-docs/en/guides/authentication)
- [Error handling](https://valeryverkhoturov.github.io/wb-api-client-docs/en/guides/error-handling)
- [Full per-module reference on GitHub](https://github.com/ValeryVerkhoturov/wb-api-client/blob/main/clients/typescript/README.md)
---
title: "Go"
description: "Go client for the Wildberries Seller API: module github.com/ValeryVerkhoturov/wb-api-client-go, 13 API categories, redacted bearer token."
lang: en-US
source: https://valeryverkhoturov.github.io/wb-api-client-docs/en/languages/go
---
# Go
Module: `github.com/ValeryVerkhoturov/wb-api-client-go` — resolved from git tags on its [dedicated repo](https://github.com/ValeryVerkhoturov/wb-api-client-go).
- **Go:** 1.22+
- **HTTP:** `net/http` (standard library)
- **Secret wrapper:** [`github.com/negrel/secrecy`](https://github.com/negrel/secrecy)
## Install
```bash
go get github.com/ValeryVerkhoturov/wb-api-client-go@latest
```
Pin a specific version by tag:
```bash
go get github.com/ValeryVerkhoturov/wb-api-client-go@v1.20260926.0
```
`proxy.golang.org` fetches directly from the pushed tag — no separate registry.
## Why the module lives in its own repo
Go's convention is one module per repo, so the client lives in [wb-api-client-go](https://github.com/ValeryVerkhoturov/wb-api-client-go) with `go.mod` at its root. Release tags (`vX.Y.Z`) are pushed to that repo by the same pipeline that tags the main one — Go picks up whichever tag matches the release.
Because `MAJOR = 1` is fixed (see [versioning](https://valeryverkhoturov.github.io/wb-api-client-docs/en/guides/versioning)), there's no `/v2` suffix now or later.
## Import shape
One Go package per WB API category, each with its own `Configuration`, `APIClient`, and `*API` types.
```go
import wbitems "github.com/ValeryVerkhoturov/wb-api-client-go/items"
```
Alias each sub-module (`wbitems`, `wbfbs`, `wbanalytics`, …) so call sites are readable — they all export the same identifier names.
## Sub-modules
`general`, `items`, `orders_fbs`, `orders_dbw`, `dbs`, `in_store_pickup`, `orders_fbw`, `promotion`, `communications`, `rates`, `analytics`, `reports`, `finances`.
Per-module `*API` structs and endpoint methods are enumerated in the per-release README: [wb-api-client-go/README.md](https://github.com/ValeryVerkhoturov/wb-api-client-go#readme).
## Auth
```go
package main
import (
"context"
"fmt"
wbitems "github.com/ValeryVerkhoturov/wb-api-client-go/items"
)
func main() {
cfg := wbitems.NewConfiguration()
cfg.SetAccessToken("") // wrapped in *secrecy.SecretString
client := wbitems.NewAPIClient(cfg)
v, _, err := client.DefaultAPI.SomeEndpoint(context.Background()).Execute()
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", v)
}
```
`SetAccessToken` is the ONLY path — the openapi-generator-default `ContextAccessToken` context-value channel is stripped so there's no way to accidentally bypass the wrapper.
To read the raw value back:
```go
cfg.AccessToken.ExposeSecret()
```
## Contexts
Every call takes a `context.Context`. Wire your app's deadline/cancellation through — the client honors it:
```go
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
v, _, err := client.DefaultAPI.SomeEndpoint(ctx).Execute()
```
## Custom HTTP client
Wire an `*http.Client` for proxying, custom timeouts, or a transport wrapper:
```go
cfg := wbitems.NewConfiguration()
cfg.HTTPClient = &http.Client{
Timeout: 60 * time.Second,
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
},
}
```
## Testing
Override the host at Configuration construction:
```go
cfg := wbitems.NewConfiguration()
cfg.Host = "localhost:8080"
cfg.Scheme = "http"
```
## See also
- [Authentication guide](https://valeryverkhoturov.github.io/wb-api-client-docs/en/guides/authentication)
- [Error handling](https://valeryverkhoturov.github.io/wb-api-client-docs/en/guides/error-handling)
- [Full per-module reference on GitHub](https://github.com/ValeryVerkhoturov/wb-api-client-go#readme)
---
title: "Java"
description: "Java client for the Wildberries Seller API: io.github.valeryverkhoturov:wb-api-client on Maven Central, 13 API categories, redacted token."
lang: en-US
source: https://valeryverkhoturov.github.io/wb-api-client-docs/en/languages/java
---
# Java
Coordinates: `io.github.valeryverkhoturov:wb-api-client` on [Maven Central](https://central.sonatype.com/artifact/io.github.valeryverkhoturov/wb-api-client).
- **Java:** 8+ (compiled with `-source 1.8 -target 1.8`)
- **HTTP:** OkHttp 4
- **JSON:** Gson + gson-fire
- **Secret wrapper:** per-sub-module `SecretString`
## Install
**Maven**
```xml
io.github.valeryverkhoturov
wb-api-client
LATEST
```
**Gradle (Kotlin)**
```kts
dependencies {
implementation("io.github.valeryverkhoturov:wb-api-client:LATEST")
}
```
**Gradle (Groovy)**
```groovy
dependencies {
implementation 'io.github.valeryverkhoturov:wb-api-client:LATEST'
}
```
**sbt**
```scala
libraryDependencies += "io.github.valeryverkhoturov" % "wb-api-client" % "LATEST"
```
Replace `LATEST` with the current `1.YYYYMMDD.N` version from [releases](https://github.com/ValeryVerkhoturov/wb-api-client/releases).
## Package layout
Base package: `io.github.valeryverkhoturov.wbapi`. Each WB category lives in a sub-package with its own `ApiClient`, models, and `api` classes.
```java
import io.github.valeryverkhoturov.wbapi.items.ApiClient;
import io.github.valeryverkhoturov.wbapi.items.api.ItemsApi;
```
## Sub-packages
`general`, `items`, `orders_fbs`, `orders_dbw`, `dbs`, `in_store_pickup`, `orders_fbw`, `promotion`, `communications`, `rates`, `analytics`, `reports`, `finances`.
Full per-module API class listing: [clients/java/README.md](https://github.com/ValeryVerkhoturov/wb-api-client/blob/main/clients/java/README.md).
## Auth
```java
import io.github.valeryverkhoturov.wbapi.items.ApiClient;
import io.github.valeryverkhoturov.wbapi.items.api.ItemsApi;
ApiClient client = new ApiClient();
client.setAccessToken("");
ItemsApi api = new ItemsApi(client);
```
`setAccessToken` takes a plain `String` and wraps it in the sub-module's `SecretString` internally — the token redacts under logging and debug dumps from that point on.
## One `SecretString` per sub-package — why?
The wrapper is generated into each sub-package rather than shared across the whole library because openapi-generator emits fully isolated SDKs per spec — there is no shared "core" module. Consolidating would require an additional generator pass with cross-module coupling. The cost is a class per sub-package; the benefit is the client remains a pure openapi-generator output with only surgical patches.
Practically you never touch them: `setAccessToken` wraps your plain string in the sub-module's own `SecretString` — `io.github.valeryverkhoturov.wbapi.items.SecretString` vs `.orders_fbs.SecretString` — automatically.
## Custom OkHttp client
Wire your own instance for proxying, interceptors, or custom timeouts:
```java
import okhttp3.OkHttpClient;
import java.time.Duration;
OkHttpClient http = new OkHttpClient.Builder()
.connectTimeout(Duration.ofSeconds(5))
.readTimeout(Duration.ofSeconds(120))
.build();
ApiClient client = new ApiClient(http);
client.setAccessToken(TOKEN);
```
## Java 8 vs newer
The generator emits Java-8-compatible code with `threetenbp` for date/time. If you're on Java 11+, this still works — no need to add a "modern" dependency. If you'd rather use `java.time` directly, the models expose the underlying string values via `set*ToString` / `get*ToString` methods where applicable.
## Testing
Override the base path:
```java
ApiClient client = new ApiClient();
client.setBasePath("http://localhost:8080");
```
## See also
- [Authentication guide](https://valeryverkhoturov.github.io/wb-api-client-docs/en/guides/authentication)
- [Error handling](https://valeryverkhoturov.github.io/wb-api-client-docs/en/guides/error-handling)
- [Full per-module reference on GitHub](https://github.com/ValeryVerkhoturov/wb-api-client/blob/main/clients/java/README.md)
---
title: "PHP"
description: "PHP client for the Wildberries Seller API: valeryverkhoturov/wb-api-client on Packagist, Guzzle, 13 API categories, redacted token."
lang: en-US
source: https://valeryverkhoturov.github.io/wb-api-client-docs/en/languages/php
---
# PHP
Package: [`valeryverkhoturov/wb-api-client`](https://packagist.org/packages/valeryverkhoturov/wb-api-client) on Packagist.
- **PHP:** 8.1+
- **HTTP:** [Guzzle 7](https://docs.guzzlephp.org/en/stable/)
- **Autoload:** PSR-4, root namespace `ValeryVerkhoturov\WbApiClient\`
- **Secret wrapper:** per-sub-namespace `SecretString`
## Install
```bash
composer require valeryverkhoturov/wb-api-client
```
## Where the source lives
PHP is the odd language out here: Packagist requires `composer.json` at the **root** of the crawled repo, so the PHP client lives in its own repository — [`ValeryVerkhoturov/wb-api-client-php`](https://github.com/ValeryVerkhoturov/wb-api-client-php) — instead of under `clients/php` in the main mono-repo.
That sibling repo is mounted as a `clients/php` git submodule in the main repo, so daily-check regenerates all eight languages in one place, then commits + tags both repos in lockstep. From a Composer consumer's perspective this is transparent — you just `composer require` and go.
## Namespace shape
Root namespace: `ValeryVerkhoturov\WbApiClient\`. Every WB category is a PascalCase sub-namespace:
```php
use ValeryVerkhoturov\WbApiClient\Items\Configuration;
use ValeryVerkhoturov\WbApiClient\Items\Api\ItemsApi;
```
## Sub-namespaces
`General`, `Items`, `OrdersFbs`, `OrdersDbw`, `Dbs`, `InStorePickup`, `OrdersFbw`, `Promotion`, `Communications`, `Rates`, `Analytics`, `Reports`, `Finances`.
Full per-sub-namespace API listing: [ValeryVerkhoturov/wb-api-client-php README](https://github.com/ValeryVerkhoturov/wb-api-client-php#readme).
## Auth
```php
setAccessToken('');
$api = new ItemsApi(new Client(), $config);
$result = $api->someEndpoint();
```
`setAccessToken` takes a plain string and wraps it in a `SecretString` internally — the token redacts under `var_dump($config)` and similar dumps.
To read the raw value back:
```php
$config->getAccessToken(); // string|null
```
## User-Agent
Every request sends `ValeryVerkhoturov/wb-api-client/php` (not the openapi-generator default). To prepend your own app identifier:
```php
$config = (new Configuration())
->setAccessToken(TOKEN)
->setUserAgent('MyApp/1.2.3 ValeryVerkhoturov/wb-api-client/php');
```
## Custom Guzzle client
Wire your own for proxying, middleware, custom timeouts:
```php
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
$stack = HandlerStack::create();
// $stack->push(...); // your middleware here
$http = new Client([
'timeout' => 60,
'handler' => $stack,
'proxy' => getenv('HTTPS_PROXY') ?: null,
]);
$api = new ItemsApi($http, $config);
```
## Testing
Override the host on Configuration:
```php
$config = (new Configuration())
->setHost('http://localhost:8080')
->setAccessToken('test-token');
```
## See also
- [Authentication guide](https://valeryverkhoturov.github.io/wb-api-client-docs/en/guides/authentication)
- [Error handling](https://valeryverkhoturov.github.io/wb-api-client-docs/en/guides/error-handling)
- [Sibling repo readme](https://github.com/ValeryVerkhoturov/wb-api-client-php#readme)
---
title: "OneScript (1С)"
description: "OneScript (1С) client for the Wildberries Seller API: wb-api-client on hub.oscript.io, 13 API categories, redacted bearer token."
lang: en-US
source: https://valeryverkhoturov.github.io/wb-api-client-docs/en/languages/onescript
---
# OneScript (1С)
Package: [`wb-api-client`](https://hub.oscript.io/pools/default/packages/wb-api-client) on hub.oscript.io.
- **OneScript:** 2.0.0+
- **HTTP:** [1connector](https://hub.oscript.io/pools/default/packages/1connector) 2.3.3
- **Dependencies:** [1connector](https://hub.oscript.io/pools/default/packages/1connector) 2.3.3 — transport, [jason](https://hub.oscript.io/pools/default/packages/jason) 0.6.0 — model serialization
- **Secret wrapper:** `СекретнаяСтрока` class
## Install
```bash
opm install wb-api-client
```
Or point at a package directory directly — for example by cloning the sibling repo at a tag:
```bash
git clone --branch v1.20260926.0 https://github.com/ValeryVerkhoturov/wb-api-client-1c.git
```
```bsl
#Использовать "path/to/wb-api-client-1c"
```
## Where the source lives
Like PHP, OneScript lives in its own repository — [`ValeryVerkhoturov/wb-api-client-1c`](https://github.com/ValeryVerkhoturov/wb-api-client-1c) — mounted into the mono-repo as a `clients/onescript` git submodule. That lets 1C users clone just the client instead of dragging along the whole pipeline, and install it straight from a tag.
Daily-check regenerates all eight languages in one place, then commits and tags both sibling repos under the same `v` tag.
## How categories are separated
OneScript has no namespaces: every class name is global. Categories are therefore separated by **name**, not by import path:
- **API classes** are named after the category — one class per category: `GeneralApi`, `ItemsApi`, `OrdersFbsApi`, `FinancesApi`. The names are unique across all 13 specs, so they need no prefix.
- **Model classes** carry the category as a prefix: `ItemsResponse4XX`, `OrdersFbsResponse4XX`. Without it they would collide — `Response4XX` alone appears in 12 of the 13 specs.
Files are grouped under `src/Классы//` and `src/Модели//`, but the directories are for humans only: `lib.config` is what resolves a class.
A single `#Использовать` brings in every category at once.
## Authentication
```bsl
#Использовать "wb-api-client"
Настройки = Новый Конфигурация();
Настройки.УстановитьТокен("");
Клиент = Новый GeneralApi(Настройки);
Ответ = Клиент.GetPing();
Сообщить(Ответ.КодСостояния);
Сообщить(Ответ.Данные["Status"]);
```
`УстановитьТокен` accepts either a plain string or a ready `СекретнаяСтрока`.
The token does not leak when printed: the object exposes no property holding the value, and `Строка(Объект)` in OneScript returns only the class name.
```bsl
Сообщить(Настройки.Токен()); // СекретнаяСтрока
Сообщить(Настройки.Токен().Раскрыть()); // eyJhbGciOi... (on request)
```
## Method shape
Required parameters are positional; optional ones arrive as a single collection in the last argument:
```bsl
Клиент = Новый ItemsApi(Настройки);
// Request body only
Ответ = Клиент.PostV2CardsDeleteTrash(Тело);
// Body + optional query parameters
Ответ = Клиент.PostV2CardsErrorList(Тело, Новый Структура("locale", "ru"));
```
Keys in `ДопПараметры` match the names from the spec. When a name is not a legal `Структура` identifier — anything with a dash, such as the `X-Trace` header — pass a `Соответствие` instead:
```bsl
ДопПараметры = Новый Соответствие;
ДопПараметры.Вставить("X-Trace", "trace-1");
```
## The response
Every method returns an `ОтветAPI`:
| Property / method | Type | Description |
| ----------------- | ------------ | ------------------------------ |
| `КодСостояния` | Число | HTTP status code |
| `Данные` | Произвольный | body parsed from JSON |
| `Тело` | Строка | raw response body |
| `Заголовки` | Соответствие | response headers |
| `Успешно()` | Булево | 2xx |
| `ЭтоОшибка()` | Булево | 4xx or 5xx |
| `Заголовок(Имя)` | Строка | case-insensitive header lookup |
When the body is not JSON (`text/plain`, `application/zip`), `Данные` is `Неопределено` and the content stays in `Тело`.
## Error handling
By default a 4xx or 5xx response raises an exception whose text reads `HTTP 404 — GET /api/v3/orders: `.
`ВызватьИсключение` in OneScript does not preserve the object for the handler — `ОписаниеОшибки()` would return only a class name — so there is no dedicated exception class here: the detail is all in the text. To inspect an error field by field, turn the raise off:
```bsl
Настройки.ВыбрасыватьИсключениеПриОшибке = Ложь;
Ответ = Клиент.GetPing();
Если Ответ.ЭтоОшибка() Тогда
Сообщить(Ответ.КодСостояния);
Сообщить(Ответ.Данные["detail"]);
КонецЕсли;
```
## Service address
Each WB category sits behind its own host, and that address is baked into the operation. `БазовыйURL` is empty by default, which means "use the address from the spec". Fill it in to send **every** request elsewhere — a sandbox or a test stand:
```bsl
Настройки.БазовыйURL = "https://marketplace-api-sandbox.wildberries.ru";
```
## User-Agent and headers
Every request sends `ValeryVerkhoturov/wb-api-client/onescript`. To replace or extend it:
```bsl
Настройки.ПользовательскийАгент = "MyApp/1.2.3 ValeryVerkhoturov/wb-api-client/onescript";
Настройки.ДополнительныеЗаголовки.Вставить("X-Request-Id", "…");
```
> **ASCII-only headers**
>
> Header values must be ASCII — a limitation of the .NET HTTP stack OneScript runs on. Cyrillic in query parameters, path segments and request bodies works fine: those are encoded separately.
## Timeout and proxy
```bsl
Настройки.Таймаут = 120; // seconds
Настройки.Прокси = Новый ИнтернетПрокси(Истина);
```
## Models
Models are classes with properties plus a pair of conversion methods. Unset properties are omitted from the request body, so a missing field never goes out as `null`.
```bsl
Товар = Новый ItemsItem();
Товар.nmID = 123;
Товар.price = 1000;
Данные = Товар.Данные(); // Соответствие keyed by the spec's names
// And back, from a response
Восстановленный = Новый ItemsItem(Ответ.Данные);
```
A request body can be passed either as a model or as a plain `Соответствие` / `Структура` — the transport handles both.
## What generates it
openapi-generator ships no OneScript target, so one was written separately: [`ValeryVerkhoturov/onescript-openapi-generator`](https://github.com/ValeryVerkhoturov/onescript-openapi-generator), a plugin extending `DefaultCodegen` and loaded next to `openapi-generator-cli.jar` through `META-INF/services`. The main repo pins it by commit, exactly as it pins the openapi-generator image tag for the other languages.
## See also
- [Authentication guide](https://valeryverkhoturov.github.io/wb-api-client-docs/en/guides/authentication)
- [Error handling](https://valeryverkhoturov.github.io/wb-api-client-docs/en/guides/error-handling)
- [Sibling repo readme](https://github.com/ValeryVerkhoturov/wb-api-client-1c#readme)
---
title: "C#"
description: "C# client for the Wildberries Seller API: ValeryVerkhoturov.WbApiClient on NuGet, 13 API categories, redacted token."
lang: en-US
source: https://valeryverkhoturov.github.io/wb-api-client-docs/en/languages/csharp
---
# C#
Package: [`ValeryVerkhoturov.WbApiClient`](https://www.nuget.org/packages/ValeryVerkhoturov.WbApiClient) on NuGet.
- **Target framework:** .NET 8.0
- **HTTP:** [RestSharp](https://restsharp.dev/)
- **Namespace:** `ValeryVerkhoturov.WbApiClient.`
- **Secret wrapper:** per-namespace `SecretString`
## Install
```bash
dotnet add package ValeryVerkhoturov.WbApiClient
```
Or in your `.csproj`:
```xml
```
## Namespace shape
The root is `ValeryVerkhoturov.WbApiClient`, and each WB category is a PascalCase sub-namespace. Inside one, the generator splits the code three ways:
```csharp
using ValeryVerkhoturov.WbApiClient.Items.Api; // Api classes
using ValeryVerkhoturov.WbApiClient.Items.Client; // Configuration, SecretString, ApiException
using ValeryVerkhoturov.WbApiClient.Items.Model; // request and response models
```
Categories are isolated by namespace, so identically named models from different sections coexist: `Items.Model.Error` and `Finances.Model.Error` are separate types.
## Sub-namespaces
`General`, `Items`, `OrdersFbs`, `OrdersDbw`, `Dbs`, `InStorePickup`, `OrdersFbw`, `Promotion`, `Communications`, `Rates`, `Analytics`, `Reports`, `Finances`.
## Authentication
```csharp
using ValeryVerkhoturov.WbApiClient.General.Api;
using ValeryVerkhoturov.WbApiClient.General.Client;
var config = new Configuration();
config.SetAccessToken("");
var api = new GeneralApi(config);
var ping = api.GetPing();
Console.WriteLine(ping.Status);
```
`SetAccessToken` stores the string in a `SecretString` internally. `Configuration` deliberately has no getter for the token: the generated Api classes read it through `IReadableConfiguration` when they build the `Authorization` header.
The token does not leak when printed: `SecretString.ToString()` always returns ``, and string interpolation, `Console.WriteLine` and most logging frameworks all go through it.
```csharp
var s = new SecretString("eyJhbGciOi...");
Console.WriteLine($"{s}"); //
Console.WriteLine(s.ExposeSecret()); // eyJhbGciOi... (on request)
```
## Async calls
Every method has an `Async` counterpart taking a `CancellationToken`:
```csharp
var ping = await api.GetPingAsync(cancellationToken: token);
```
## Client settings
```csharp
var config = new Configuration
{
Timeout = TimeSpan.FromSeconds(60), // 100 seconds by default
UserAgent = "MyApp/1.2.3 ValeryVerkhoturov/wb-api-client/csharp",
};
config.SetAccessToken(token);
config.DefaultHeaders.Add("X-Request-Id", requestId);
```
Every request sends `ValeryVerkhoturov/wb-api-client/csharp` by default.
### Service address
Each WB category sits behind its own host, already baked into the client from the spec. To send requests elsewhere — a sandbox, say — set `BasePath`:
```csharp
config.BasePath = "https://marketplace-api-sandbox.wildberries.ru";
```
## Error handling
A 4xx or 5xx response raises `ApiException`:
```csharp
using ValeryVerkhoturov.WbApiClient.OrdersFbs.Api;
using ValeryVerkhoturov.WbApiClient.OrdersFbs.Client;
var fbsConfig = new Configuration();
fbsConfig.SetAccessToken("");
var fbs = new OrdersFbsApi(fbsConfig);
try
{
var orders = fbs.GetV3Orders(limit: 10, next: 0);
}
catch (ApiException e)
{
Console.WriteLine(e.ErrorCode); // HTTP status code
Console.WriteLine(e.Message);
Console.WriteLine(e.ErrorContent); // response body
}
```
> **Configuration is per-namespace**
>
> The generator emits its own `Configuration` — and `SecretString`, and `ApiException` — inside every category, so `OrdersFbs.Client.Configuration` is not `General.Client.Configuration`. A single instance cannot be shared across categories: each Api class takes the `Configuration` from its own namespace. The token itself is the same one.
## Dependency injection
Api clients are ordinary classes with a `Configuration` constructor, so they register as-is:
```csharp
services.AddSingleton(_ =>
{
var config = new Configuration();
config.SetAccessToken(builder.Configuration["Wb:Token"]);
return config;
});
services.AddTransient();
```
No DI-specific package is needed: the client is built on RestSharp rather than `IHttpClientFactory`.
## See also
- [Authentication guide](https://valeryverkhoturov.github.io/wb-api-client-docs/en/guides/authentication)
- [Error handling](https://valeryverkhoturov.github.io/wb-api-client-docs/en/guides/error-handling)
- [Package on NuGet](https://www.nuget.org/packages/ValeryVerkhoturov.WbApiClient)
---
title: "Rust"
description: "Rust client for the Wildberries Seller API: the wb-api-client crate on crates.io, 13 API categories, redacted bearer token."
lang: en-US
source: https://valeryverkhoturov.github.io/wb-api-client-docs/en/languages/rust
---
# Rust
Crate: [`wb-api-client`](https://crates.io/crates/wb-api-client) on crates.io — the sources live in a [dedicated repo](https://github.com/ValeryVerkhoturov/wb-api-client-rust), mounted into the main one as the `clients/rust` submodule.
- **Rust:** edition 2021 (the pipeline publishes with toolchain 1.98)
- **HTTP:** `reqwest` with `rustls-tls` — no system OpenSSL needed
- **Secret wrapper:** a house `SecretString` in every module
## Install
```bash
cargo add wb-api-client
```
Pin a specific version by tag with a git dependency:
```toml
wb-api-client = { git = "https://github.com/ValeryVerkhoturov/wb-api-client-rust", tag = "v1.20261022.0" }
```
## Why the crate lives in its own repo
crates.io links the crate to a dedicated repository, and git dependencies (`wb-api-client = { git = "…", tag = "vX.Y.Z" }`) resolve from the repo root — so the client lives in [wb-api-client-rust](https://github.com/ValeryVerkhoturov/wb-api-client-rust) with `Cargo.toml` at its root. Release tags (`vX.Y.Z`) are pushed to that repo by the same pipeline that tags the main one.
## Import shape
One crate, 13 category modules under `wb_api_client::`, each with `apis` and `models` submodules:
```rust
use wb_api_client::items::apis::{configuration::Configuration, items_api};
```
Endpoint methods are free `async fn`s inside `_api` taking `&Configuration`, so you need an async runtime (`tokio` in the examples).
## Sub-modules
`general`, `items`, `orders_fbs`, `orders_dbw`, `dbs`, `in_store_pickup`, `orders_fbw`, `promotion`, `communications`, `rates`, `analytics`, `reports`, `finances`.
Per-module functions and models are enumerated in the per-release README: [wb-api-client-rust/README.md](https://github.com/ValeryVerkhoturov/wb-api-client-rust#readme).
## Auth
```rust
use wb_api_client::items::apis::{configuration::Configuration, items_api};
#[tokio::main]
async fn main() {
let mut cfg = Configuration::new();
cfg.set_access_token(""); // wrapped in SecretString
let result = items_api::some_endpoint(&cfg).await;
println!("{:#?}", result);
}
```
`set_access_token` is the ONLY path: the token is stored in a house `SecretString` whose `Debug` and `Display` both print `` — a `println!("{cfg:?}")` can't leak it. To read the raw value back you have to ask explicitly:
```rust
use wb_api_client::items::apis::configuration::SecretString;
let s = SecretString::new("");
println!("{}", s.expose_secret());
```
## Custom HTTP client
Wire your own `reqwest::Client` for proxying, custom timeouts, or default headers:
```rust
let mut cfg = Configuration::new();
cfg.client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(60))
.build()
.unwrap();
```
## Testing
Override the base URL at Configuration construction:
```rust
let mut cfg = Configuration::new();
cfg.base_path = "http://localhost:8080".to_owned();
```
## See also
- [Authentication guide](https://valeryverkhoturov.github.io/wb-api-client-docs/en/guides/authentication)
- [Error handling](https://valeryverkhoturov.github.io/wb-api-client-docs/en/guides/error-handling)
- [Full per-module reference on GitHub](https://github.com/ValeryVerkhoturov/wb-api-client-rust#readme)
---
title: "Versioning"
description: "All eight language packages share one version string per release: 1.YYYYMMDD.N."
lang: en-US
source: https://valeryverkhoturov.github.io/wb-api-client-docs/en/guides/versioning
---
# Versioning
All eight language packages share one version string per release: **`1.YYYYMMDD.N`**.
## Format
| Field | Value | Why |
| ------- | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `MAJOR` | `1` — fixed | Keeps Go's module path free of the `/vN` suffix that `MAJOR ≥ 2` would require. Any real breaking change to the pipeline surfaces as a major-version bump of the _code repo_, not a new module path. |
| `MINOR` | `YYYYMMDD` (UTC release date) | Monotonic across days — `20260919 < 20260920 < 20261001`. Sorts as an integer under semver rules. |
| `PATCH` | `N` — same-day counter | `0` for the first release of a day, `1`, `2`, … for repeats (rare). |
Example: `1.20260921.0` — first release cut on 21 Sep 2026 UTC.
## Where the number comes from
The daily-check workflow computes it inline:
```bash
today="$(date -u +%Y%m%d)"
n="$(git tag --list "v1.${today}.*" | wc -l | tr -d ' ')"
version="1.${today}.${n}"
```
The resulting string goes into every language's manifest — `pyproject.toml`, `package.json`, `go.mod` tag, `pom.xml`, `composer.json`, `Cargo.toml` — verbatim. No language sees a translated form.
## What "latest" resolves to
Because every release is a **stable** (non-prerelease) version, `@latest` / `LATEST` / `^1.0.0` all pick the highest published:
**Python**
```bash
pip install valeryverkhoturov-wb-api-client
```
**TypeScript**
```bash
npm install @valeryverkhoturov/wb-api-client
```
**Go**
```bash
go get github.com/ValeryVerkhoturov/wb-api-client-go@latest
```
**Java**
```xml
[1.0.0,)
```
**PHP**
```bash
composer require valeryverkhoturov/wb-api-client
```
**OneScript**
```bash
opm install wb-api-client
```
**C#**
```bash
dotnet add package ValeryVerkhoturov.WbApiClient
```
**Rust**
```bash
cargo add wb-api-client
```
## Pinning a version
Pin the exact `1.YYYYMMDD.N` when reproducibility matters (CI, production deploys):
**Python**
```txt
valeryverkhoturov-wb-api-client==1.20260926.0
```
**TypeScript**
```json
"@valeryverkhoturov/wb-api-client": "1.20260926.0"
```
**Go**
```bash
go get github.com/ValeryVerkhoturov/wb-api-client-go@v1.20260926.0
```
**Java**
```xml
1.20260926.0
```
**PHP**
```json
"valeryverkhoturov/wb-api-client": "1.20260926.0"
```
**OneScript**
```bsl
.ЗависитОт("wb-api-client", "1.20260926.0")
```
**C#**
```xml
```
**Rust**
```toml
wb-api-client = "=1.20260926.0"
```
## Release cadence
New versions get cut only when upstream specs actually change. Concretely: `daily-check.yml` runs at 06:15 UTC, checksum-diffs `swaggers/` against the previous run, and only re-generates + tags if anything moved. A quiet week produces no releases; a WB portal deploy typically produces one the next morning.
Manually forcing a release (e.g. to pick up a client-side fix): `Actions → Daily upstream check → Run workflow` with `force: true`.
## Deprecations
WB removes fields on their own schedule. When they do:
1. Their spec drops the field.
2. Next daily-check regenerates without the field.
3. A new version publishes with the field gone.
4. Callers on the previous version keep working (their local models still have it, they just get nulls).
There is no formal deprecation window — the upstream contract is the source of truth. Pin an old version if you need time to migrate.
## Multi-language version drift — impossible by construction
Because the same `1.YYYYMMDD.N` is stamped into every language's manifest in the same generate-clients.py invocation, and every language is regenerated in every release run, you cannot end up with "Python 1.20260921.0 but Go 1.20260919.0" from a single upstream state — they're always in lockstep.
The only way to skew: a language's publish step fails (e.g. Maven Central rejects a signature). The tag exists and the source is on GitHub; a manual re-run of the failed publish job for that specific language fixes it.
---
title: "Architecture"
description: "A one-page tour of what's actually in the main repo, so nothing about the generated clients feels magic."
lang: en-US
source: https://valeryverkhoturov.github.io/wb-api-client-docs/en/guides/architecture
---
# Architecture
A one-page tour of what's actually in the [main repo](https://github.com/ValeryVerkhoturov/wb-api-client), so nothing about the generated clients feels magic.
## The pipeline
```
download-specs.py → swaggers/*.yaml (raw upstream, checksummed)
process-specs.py → swaggers/processed/ (13 passes)
generate-clients.py → clients//… (openapi-generator-cli, 8 langs)
├── inject-secrets.py (SecretString wrapper per lang)
├── {black|prettier|gofmt|spotless|php-cs-fixer|rustfmt} (canonicalize formatting)
└── generate-readmes.py (per-language README.md)
↓ committed at v, single tag covers all 8 languages
publish.yml → PyPI / npm / Go tag / Maven Central / Packagist / hub.oscript.io / NuGet / crates.io
generate-docs.py → wb-api-client-docs (API reference: operation pages,
call snippets and data structures
in 8 languages, RU/EN)
```
Every step is a plain Python file, so nothing is hidden behind opaque tooling.
## Why generate?
WB ships OpenAPI specs. Hand-writing 13 clients × 8 languages would drift within a week. The upside of generating from the spec is that every field, every enum, every response wrapper is exactly what WB documents — and re-syncing takes exactly zero effort once the pipeline exists.
The downside is that generator output is generator output — sometimes ugly, sometimes carrying artifacts from spec quirks. Which is why there's a post-processing layer.
## process-specs.py — 13 passes
1. **`inject_bearer_auth`** — drops WB's `HeaderApiKey` scheme (duplicated with the real bearer JWT semantics) and installs a single `BearerAuth` scheme instead. Every generated client then exposes exactly one auth code path.
2. **`fix_untyped_arrays`** — adds `items: {type: string}` to arrays missing their inner type (Go / TS would otherwise emit `[]any`).
3. **`widen_integer_formats`** — sets `format: int64` on integer fields with no format or with `uint32`/`uint64`/Unix-timestamp formats: WB uses those for IDs and millisecond timestamps that overflow a signed 32-bit int. Explicit `int32` and currency-code formats are left alone.
4. **`inline_top_level_arrays`** — inlines every `$ref` to a `type: array` component and collapses `allOf: [array, description-only]` wrappers (openapi-generator mishandles arrays-at-root). Runs to fixed point because inlined subtrees can contain further refs.
5. **`rename_digit_prefixed_schemas`** — `409SupplyDeliverError` → `Http409SupplyDeliverError` (invalid Go identifier otherwise).
6. **`name_inline_response_schemas`** — hoists anonymous inline 4xx/5xx bodies to `Response` (again, valid Go identifiers).
7. **`drop_response_required`** — WB marks nearly every response field `required`, but the live API omits fields at will, and every generated language then throws on deserialization. The pass deletes `required` from every schema reachable from a response; request bodies keep their `required` lists, so input validation is unaffected.
8. **`sanitize_non_ascii_enums`** — adds `x-enum-varnames` for Russian enum members like `"Склад WB"` (empty JS/Go/Java identifiers otherwise).
9. **`rename_tags_to_module`** — WB tags are Russian (`Финансовые отчёты`), which openapi-generator sanitizes to an empty class-name stem, so every language fell back to `Api`/`DefaultApi` classes. The pass rewrites every operation's tags to the module name (`Finances`, `Reports`, …), giving each module a single well-named API class (`FinancesApi`, …), and stashes the original tags in `x-original-tags` for the docs generator.
10. **`drop_date_formats`** — WB returns `""` for unset date fields (`fixTariffDateFrom`, `utdUcdDate`), which no strict date parser accepts. The pass strips `format: date` / `format: date-time` from string schemas so clients type these as plain strings.
11. **`htmlize_descriptions_to_markdown`** — every `description` field is passed through `markdownify` so HTML in docstrings renders as Markdown in generated docs.
12. **`absolutize_description_links`** — relative `[…](https://valeryverkhoturov.github.io/wb-api-client-docs/openapi/…)` links get `https://dev.wildberries.ru` prepended so links in generated docstrings still work.
13. **`add_docs_links`** — appends a `Library doc: ` line to every operation's `description`, pointing at the operation's page on this site. Generators copy `description` into docstrings/Javadoc, so every client method links to its rendered docs. `generate-docs.py` strips the line back out, so the docs site itself is unaffected.
## generate-clients.py
- Runs `openapi-generator-cli` (pinned Docker image) per spec per language, into scratch directories.
- Splices each per-spec SDK into a unified per-language tree — Python packages nested under `wb_api_client.`, TypeScript subpath exports, Go sub-packages, Java sub-packages, PHP sub-namespaces, for OneScript a class-name prefix (the language has no namespaces), C# namespaces, and Rust modules of a single crate (`wb_api_client::`).
- Runs `inject-secrets.py` to patch every generated `Configuration` / `ApiClient` with the language-native secret-string wrapper.
- Runs the pinned formatter per language: `black`, `prettier`, `gofmt`, spotless (google-java-format), `php-cs-fixer`, `dotnet format`, `cargo fmt` (rustfmt). All inside Docker — no host runtime needed.
- Substitutes `__VERSION__` in top-level manifests (`pyproject.toml`, `package.json`, `go.mod`, `pom.xml`, `composer.json`, `Cargo.toml`) with the passed version.
- Runs `generate-readmes.py` to emit a per-language `README.md`.
The result: `clients/{python,typescript,go,java,php,onescript,csharp,rust}/` — eight ready-to-publish packages.
## Determinism
The daily job would flap constantly if generation weren't deterministic. To keep it stable:
- `openapi-generator-cli` Docker tag is pinned.
- Every language's `openapi-generator` config sets `hideGenerationTimestamp: true`.
- Every formatter is pinned to a specific version.
- The PR-check workflow regenerates from scratch and fails the PR if `git diff swaggers/ clients/` is non-empty. This catches any change that would make subsequent runs produce drift.
## Publishing
`daily-check.yml` computes the version, regenerates, commits, tags. Then it fires 8 `workflow_dispatch` events at `publish.yml` — one per language — with the tag as the `ref` input. Each language branch of `publish.yml`:
- Python — OIDC → PyPI trusted publishing (`pypa/gh-action-pypi-publish`).
- TypeScript — OIDC → npm trusted publishing (`npm publish --provenance`).
- Go — the tag is pushed to [`wb-api-client-go`](https://github.com/ValeryVerkhoturov/wb-api-client-go) (see below); the job itself is a no-op because `proxy.golang.org` fetches directly from the tag.
- Java — GPG-signed deploy to Maven Central via `central-publishing-maven-plugin`. Maven is pinned to 3.9.x (`stCarolas/setup-maven`): the plugin builds its bundle through a Maven-2 stager, and Maven 3.10's Resolver drops a stray `maven-metadata-local.xml` into it, so Central rejects the bundle ("Bundle has content that does NOT have a .pom file").
- PHP — ping the Packagist update-package API.
- OneScript — `opm build` then `opm push` to hub.oscript.io.
- C# — `dotnet pack` then `dotnet nuget push` to NuGet.
- Rust — `cargo publish` to crates.io (GitHub environment `crates-io`, toolchain pinned at 1.98).
Separately, daily-check regenerates the API reference (`generate-docs.py` — operation pages, call snippets, and request/response data structures per language, with links to the WB specification) and pushes it to `wb-api-client-docs`, which deploys to GitHub Pages.
Reusable workflows (`workflow_call`) would be cleaner, but PyPI + npm trusted publishing don't support them — both the OIDC token's `job_workflow_ref` (callee) and the Sigstore attestation cert's `workflow_ref` (caller) have to point at the same file, which is impossible with `workflow_call`. Hence `workflow_dispatch`.
## The sibling repositories
Four languages live in their own repos, mounted here as git submodules:
- [`ValeryVerkhoturov/wb-api-client-php`](https://github.com/ValeryVerkhoturov/wb-api-client-php) → `clients/php`. Reason: Packagist requires `composer.json` at the ROOT of the crawled repo.
- [`ValeryVerkhoturov/wb-api-client-1c`](https://github.com/ValeryVerkhoturov/wb-api-client-1c) → `clients/onescript`. So 1C users can clone just the client and install it from a tag.
- [`ValeryVerkhoturov/wb-api-client-go`](https://github.com/ValeryVerkhoturov/wb-api-client-go) → `clients/go`. Reason: Go versions modules with repository tags, so the module must sit at a repo root (`go get github.com/ValeryVerkhoturov/wb-api-client-go`) — a subdirectory module would need `clients/go/`-prefixed tags and `go get` would fetch the whole monorepo.
- [`ValeryVerkhoturov/wb-api-client-rust`](https://github.com/ValeryVerkhoturov/wb-api-client-rust) → `clients/rust`. Reason: crates.io links the crate to a dedicated repo, and git dependencies (`wb-api-client = { git = "…", tag = "vX.Y.Z" }`) resolve from the repo root.
Every daily run commits and tags all four siblings BEFORE the main repo — otherwise the submodule pointers recorded in the release commit would reference commits their remotes have never seen, and a fresh `git clone --recurse-submodules` would fail.
A fifth sibling is [`ValeryVerkhoturov/wb-api-client-docs`](https://github.com/ValeryVerkhoturov/wb-api-client-docs) — this site. Not a submodule: daily-check clones it, runs `generate-docs.py`, and pushes the result back.
## The OneScript generator
openapi-generator ships no OneScript target, so it lives in a separate repo — [`ValeryVerkhoturov/onescript-openapi-generator`](https://github.com/ValeryVerkhoturov/onescript-openapi-generator). It is an ordinary plugin: the class extends `DefaultCodegen`, registers through `META-INF/services`, and is loaded next to `openapi-generator-cli.jar`, so `-g onescript` resolves like a built-in generator. The main repo pins it by commit — the OneScript equivalent of pinning the Docker image tag for the other languages.
## What's not generated
The hand-written parts of the main repo:
- `scripts/` — the pipeline itself, organized by bounded context under `scripts/pipeline/`: `core/` (shared kernel: repo paths, spec/module/language naming, YAML IO, external-tool runners (subprocess, docker, git, python, macOS codesign)), `specs/` (download, postprocess), `generation/` (generate, secrets), `onescript/` (toolchain, manifests, verify), `docs/` (reference, readmes). The flat `scripts/*.py` files are thin CLI shims over those modules, named verb-object by what they produce: `download-specs`, `process-specs`, `generate-clients`, `generate-readmes`, `generate-docs`, `generate-onescript-manifests`, `inject-secrets`, `verify-onescript`. House rule: no `#` comments and no docstrings under `scripts/` — enforced by `make scripts-check` and the PR check.
- `generator-configs/*.yaml` — one openapi-generator config per language.
- `templates/*` — top-level manifests with `__VERSION__` placeholders.
- `.github/workflows/*` — daily-check, per-language publish, PR drift check.
- `Makefile`, `README.md`, `CLAUDE.md`.
Everything under `clients/` is generated output — never hand-edit it, regenerate.
## Full pipeline docs
The main-repo [`CLAUDE.md`](https://github.com/ValeryVerkhoturov/wb-api-client/blob/main/CLAUDE.md) is a working, machine-readable version of the same content — kept up to date because CI itself reads it.
---
title: "Contributing"
description: "Bugs, spec-drift oddities, generator quirks: open an issue on the main code repo — ValeryVerkhoturov/wb-api-client/issues."
lang: en-US
source: https://valeryverkhoturov.github.io/wb-api-client-docs/en/guides/contributing
---
# Contributing
## Reporting issues
Bugs, spec-drift oddities, generator quirks: open an issue on the main code repo — [`ValeryVerkhoturov/wb-api-client/issues`](https://github.com/ValeryVerkhoturov/wb-api-client/issues).
Include:
- Which language client (Python / TypeScript / Go / Java / PHP / OneScript / C# / Rust)
- Which sub-module (`items`, `orders_fbs`, `analytics`, …)
- Version string from the manifest (`1.YYYYMMDD.N`)
- Minimal reproduction — one API call, one exception, one expected-vs-actual
Docs-site issues (typos, missing content, broken examples) go on the docs repo: [`ValeryVerkhoturov/wb-api-client-docs/issues`](https://github.com/ValeryVerkhoturov/wb-api-client-docs/issues).
## Do NOT hand-edit generated code
Every file under `clients/` in the main repo is generated. Editing there gets overwritten on the next daily-check run — the PR check will also flag it before merge.
If you find something wrong in generated output, the fix goes in one of:
- **`scripts/pipeline/specs/postprocess.py`** — a new pass, if the fix applies to the spec before generation.
- **`scripts/pipeline/generation/secrets.py`** — if the fix is in the generated `Configuration` / `ApiClient` boilerplate.
- **`generator-configs/*.yaml`** — if it's an openapi-generator option toggle.
- **`templates/{lang}/*`** — if it's in the top-level manifest.
The [architecture reference](https://valeryverkhoturov.github.io/wb-api-client-docs/en/guides/architecture) has the full pipeline map.
## Local development
```bash
git clone --recurse-submodules https://github.com/ValeryVerkhoturov/wb-api-client
cd wb-api-client
pip install -r scripts/requirements.txt # ruamel.yaml, markdownify, requests, black, flask
./scripts/download-specs.py # pull upstream (may 498 locally)
python scripts/process-specs.py # -> swaggers/processed/
./scripts/generate-clients.py 1.20260920.0 # -> clients/{python,typescript,go,java,php,onescript,csharp,rust}
make verify # build + format check every language
```
`generate-clients.py` always regenerates all eight languages. For per-language iteration during debugging, comment out the other `queue_generation` calls rather than adding a flag. The `OPENAPI_GENERATOR_THREADS` environment variable controls generation concurrency (default: 2).
Formatting runs inside Docker so contributors don't need language runtimes installed. `make verify-` and `make {black,prettier,gofmt,spotless,php-cs-fixer,dotnet-format}` are individual targets. OneScript has no formatter, so `make verify-onescript` compiles every module with `oscript -check` and loads the package instead. `make help` lists everything.
The pipeline itself lives in `scripts/pipeline/`, organized by bounded context (see [architecture](https://valeryverkhoturov.github.io/wb-api-client-docs/en/guides/architecture)); the flat `scripts/*.py` files are just CLI shims. Changes under `scripts/` are additionally gated by `make scripts-check` (kernel smoke checks, no `#` comments, no docstrings), and black covers `scripts/` too, not just `clients/python`.
## PR flow
1. Fork the main repo.
2. Make your change to `scripts/`, `templates/`, `generator-configs/`, or a workflow.
3. Regenerate locally so `clients/` matches.
4. Commit `swaggers/`, `clients/`, and your source changes together.
5. Open a PR. The PR-check workflow will regenerate from scratch and fail if `git diff` is non-empty against your commit — that catches non-determinism.
## Contributing to the docs site
This site lives in [`ValeryVerkhoturov/wb-api-client-docs`](https://github.com/ValeryVerkhoturov/wb-api-client-docs).
```bash
git clone https://github.com/ValeryVerkhoturov/wb-api-client-docs
cd wb-api-client-docs
npm install
npm run docs:dev
# → http://localhost:5173/wb-api-client-docs/
```
Push to `main`; the deploy workflow builds and publishes to GitHub Pages automatically.
## License
Both repos are Apache 2.0. Contributions are assumed to be under the same license.