Authentication
Get an access token with OAuth 2.0 client credentials and send it with every request.
The API uses the OAuth 2.0 client credentials grant. Your integration is a client with an id and a secret. It exchanges them for a short-lived access token and sends that token with every request. There is no user login and no browser redirect.
Get credentials
Ask WinShop support at [email protected] for a client_id and client_secret for the instance you integrate with. Say what your integration needs to read and write, because support assigns the client a role that decides which endpoints it may call.
Keep the secret on your server. Never ship it in a browser or a mobile app.
Request a token
Send a form-encoded POST to /connect/token:
curl -X POST https://<your-instance>/connect/token \
-d grant_type=client_credentials \
-d client_id=svc-your-client \
-d client_secret=your-secretDo not send a scope parameter. The API defines no scopes; what a client may do is decided by its role.
The response:
{
"access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6...",
"token_type": "Bearer",
"expires_in": 3600
}| Field | Meaning |
|---|---|
access_token | The token to send with API requests. Treat it as an opaque string. |
token_type | Always Bearer. |
expires_in | Lifetime of the token in seconds. The default is 3600 (one hour), but each instance can configure its own value. Always use the number you receive. |
Call the API
Send the token in the Authorization header:
curl https://<your-instance>/api/item-lists/warehouses \
-H "Authorization: Bearer $ACCESS_TOKEN"Cache the token
Request one token and reuse it until shortly before it expires. Do not request a new token for every API call.
The token endpoint is rate limited to 300 requests per minute per client. Above that it answers 429 Too Many Requests with a Retry-After header that says how many seconds to wait.
A minimal cache:
using System.Net.Http.Json;
string? cachedToken = null;
DateTime validUntil = DateTime.MinValue;
async Task<string> GetAccessTokenAsync()
{
if (cachedToken is not null && DateTime.UtcNow < validUntil) return cachedToken;
var response = await http.PostAsync("/connect/token", new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "client_credentials",
["client_id"] = "svc-your-client",
["client_secret"] = "your-secret",
}));
response.EnsureSuccessStatusCode();
var token = (await response.Content.ReadFromJsonAsync<TokenResponse>())!;
cachedToken = token.AccessToken;
// renew one minute early
validUntil = DateTime.UtcNow.AddSeconds(token.ExpiresIn - 60);
return cachedToken;
}The samples in the rest of this site assume a helper like this one and call it GetAccessTokenAsync() or getAccessToken().
When a request is refused
| Status | Meaning | What to do |
|---|---|---|
401 Unauthorized | The token is missing, malformed or expired. | Request a new token and repeat the call once. |
403 Forbidden | The token is valid, but the role of your client lacks the permission this endpoint needs. | Ask WinShop support to adjust the role of your client. A new token will not help. |
429 Too Many Requests | Too many token requests. | Wait for the number of seconds in Retry-After, and cache the token. |
Permissions are named Permissions.{Resource}.{Action}, for example Permissions.DeliveryNotes.View. The description of each endpoint in the reference names the permission it requires. Tell support which permissions your client is missing.
Rotating the secret
You cannot change the client secret through the API. To rotate it, or if it may have leaked, contact WinShop support at [email protected].