Skip to content

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 → the Интеграции по API (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 and generated test data; real seller data is not accessible.
  • Service — created for a specific cloud service from the Solutions for Business catalog; 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 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.

LanguageWrapperSetterRedacted underExplicit expose
Pythonpydantic.SecretStrConfiguration(access_token=…)print(cfg.access_token) → **********.get_secret_value()
TypeScriptinlined SecretString classConfiguration.setAccessToken(…)console.log(new SecretString("…")) → <REDACTED>.exposeSecret()
Gosecrecy.SecretStringConfiguration.SetAccessToken(…)fmt.Printf("%v", cfg.AccessToken) → <!SECRET_LEAKED!>.ExposeSecret()
Javaper-sub-module SecretStringApiClient.setAccessToken(String)System.out.println(s) → <REDACTED>—
PHPper-sub-module SecretStringConfiguration::setAccessToken(string)var_dump($config) → token '<REDACTED>'getAccessToken()
C#per-namespace SecretStringConfiguration.SetAccessToken(string)Console.WriteLine(s) → <REDACTED>no getter by design
OneScriptСекретнаяСтрока classКонфигурация.УстановитьТокен(…)Сообщить(Настройки.Токен()) → СекретнаяСтрока.Раскрыть()
Rustper-module SecretString structConfiguration::set_access_token(&str)println!("{s:?}") / {} → <REDACTED>.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 <REDACTED> marker. The one-line cost is the wrapper type at the boundary.

Examples of the redaction firing ​

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)
ts
import { Configuration } from "@valeryverkhoturov/wb-api-client/items";
const cfg = new Configuration({});
cfg.setAccessToken("eyJhbGciOi...");
console.log(cfg.accessToken);      // SecretString { <REDACTED> }
console.log(cfg.accessToken.exposeSecret()); // eyJhbGciOi...  (opt-in)
go
import wbitems "github.com/ValeryVerkhoturov/wb-api-client-go/items"
cfg := wbitems.NewConfiguration()
cfg.SetAccessToken("eyJhbGciOi...")
fmt.Printf("%v\n", cfg.AccessToken)         // <!SECRET_LEAKED!>
fmt.Println(cfg.AccessToken.ExposeSecret()) // eyJhbGciOi...  (opt-in)
java
SecretString s = new SecretString("eyJhbGciOi...");
System.out.println(s);            // <REDACTED>
System.out.println(s.exposeSecret()); // eyJhbGciOi...  (opt-in)
php
use ValeryVerkhoturov\WbApiClient\Items\Configuration;
$config = (new Configuration())->setAccessToken('eyJhbGciOi...');
var_dump($config);                // accessToken => '<REDACTED>'
echo $config->getAccessToken();   // eyJhbGciOi...  (opt-in)
bsl
С = Новый СекретнаяСтрока("eyJhbGciOi...");
Сообщить(С);                      // СекретнаяСтрока
Сообщить(С.Раскрыть());           // eyJhbGciOi...  (opt-in)
csharp
var s = new SecretString("eyJhbGciOi...");
Console.WriteLine($"{s}");        // <REDACTED>
Console.WriteLine(s.ExposeSecret()); // eyJhbGciOi...  (opt-in)
rust
use wb_api_client::items::apis::configuration::SecretString;
let s = SecretString::new("eyJhbGciOi...");
println!("{s:?}");                // <REDACTED>
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
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 = "<your WB JWT>"
items_api = ItemsClient(ItemsCfg(access_token=TOKEN))
orders_api = OrdClient(OrdCfg(access_token=TOKEN))
ts
import { Configuration as ItemsCfg } from "@valeryverkhoturov/wb-api-client/items";
import { Configuration as OrdCfg } from "@valeryverkhoturov/wb-api-client/orders_fbs";

const TOKEN = "<your WB JWT>";
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 ​

ResponseMeaning
401 Unauthorized — token expiredToken past its expiry; mint a new one
401 Unauthorized — invalid signatureWrong Authorization header format, or token from a different environment (sandbox vs prod)
403 Forbidden — no access to resourceToken valid, scope missing — regenerate with the right box ticked
429 Too Many RequestsRate-limited; the token itself is fine

See the error-handling guide for retry patterns.

wb-api-client mascot