> For the complete documentation index, see [llms.txt](https://developer.esw.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developer.esw.com/getting-started/get-started-with-esw/quick-start-guide/authentication.md).

# Authentication

Get an STS token, make a first authenticated call, and cache tokens. About 10 minutes.

Every ESW API request carries an OAuth 2.0 access token issued by the ESW **Security Token Service (STS)**. In this quick start you request a token with the client credentials flow, use it to make a first authenticated call, and add token caching so your integration never requests a token on every call.

**Time:** about 10 minutes

**Environment:** sandbox

### Before you begin

You need these from ESW:

* A sandbox `client_id` and `client_secret`
* Your `tenantCode`, the retailer identifier used in API paths
* The scopes enabled for your client

{% tabs %}
{% tab title="Sandbox" %}
{% @code-walkthrough/get-credentials cfgN="1" cfg0="{"title":"","envs":\["sandbox"],"clientId":"","testHost":"security-sts.test.eshopworld.net","scopes":\[]}" %}
{% endtab %}
{% endtabs %}

{% hint style="warning" icon="key" %}
Treat the client secret like a password. Store it in a secrets manager or an environment variable. Never commit it to source control or share it in tickets or chat.
{% endhint %}

{% stepper %}
{% step %}

#### Choose your scopes

A scope grants access to an API or a group of actions. Request only the scopes your integration needs, as a space-separated list.

| Scope                                            | Grants                                | Used in         |
| ------------------------------------------------ | ------------------------------------- | --------------- |
| `checkout.preorder.api.all`                      | PreOrder requests to the Checkout API | Checkout        |
| `order.transaction.create.api.all`               | Order create actions                  | Order           |
| `order.transaction.get.api.all`                  | Get Order                             | Order           |
| `order.transaction.update.api.all`               | Order update actions                  | Order           |
| `order.transaction.cancel.api.all`               | Order cancel actions                  | Order           |
| `pricing.advisor.api.all`                        | Full access to Pricing Advisor        | Pricing Advisor |
| `logistics.catalog.api.upload`                   | Catalog upload actions                | Customs Catalog |
| `logistics.package.api.all`                      | Full access to the Package API        | Package         |
| `logistics.returns.api.all`                      | Full access to the Returns API        | Returns         |
| `logistics.inventory.cart.availability.api.read` | Inventory availability access.        | Inventory       |

This table covers the scopes the quick starts use. For every scope, including narrower read-only and per-action scopes, see [Access Tokens](broken://pages/QPzbTvC6XsT5gERiU43E).
{% endstep %}

{% step %}

#### Request an access token

Send a `POST` to the STS token endpoint. The body must be **form URL encoded**, not JSON.

```sh
export ESW_CLIENT_ID="<client_id>"
export ESW_CLIENT_SECRET="<client_secret>"

curl --request POST "https://security-sts.sandbox.eshopworld.com/connect/token" \
  --header "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=client_credentials" \
  --data-urlencode "scope=pricing.advisor.api.all" \
  --data-urlencode "client_id=$ESW_CLIENT_ID" \
  --data-urlencode "client_secret=$ESW_CLIENT_SECRET"
```

A successful call returns `200`:

```json
{
  "access_token": "<jwt>",
  "expires_in": 3600,
  "token_type": "Bearer",
  "scope": "pricing.advisor.api.all"
}
```

`expires_in` is the token lifetime in seconds. Keep the `access_token` value for the next step.
{% endstep %}

{% step %}

#### Make your first authenticated call

Send the token in the `Authorization` header. This example calls Pricing Advisor, which is read-only, so it is safe to run as a smoke test. Replace `<tenantCode>` with your tenant code.

```sh
export ESW_TOKEN="<access_token>"

curl "https://pricing-advisor-api.sandbox.eshopworld.com/api/4.0/MultiplierAdvice/<tenantCode>/DE" \
  --header "Authorization: Bearer $ESW_TOKEN" \
  --header "Accept: application/json"
```

A `200` response with pricing data confirms that your credentials, scope, and tenant code are all valid.

{% hint style="info" %}
STS hosts only issue tokens. Send API requests to the service base URLs listed in [API Base URLs](broken://pages/7FvWQMF0kTK7HGhlQfmo#api-base-urls), never to an STS host.
{% endhint %}
{% endstep %}

{% step %}

#### Cache and refresh tokens

A token stays valid until `expires_in` elapses. Request one, reuse it, and refresh it shortly before it expires. If an idempotent request returns `401` for an expired token, refresh once and retry it. Do not automatically replay a mutating request unless its API documentation confirms it is retry-safe.

{% tabs %}
{% tab title="Node.js" %}
{% code expandable="true" %}

```js
// token-client.js (Node 18 or later)
const STS_URL = "https://security-sts.sandbox.eshopworld.com/connect/token";
const cache = new Map(); // scope -> { token, expiresAt }

export async function getToken(scope) {
  const hit = cache.get(scope);
  const now = Date.now();
  if (hit && now < hit.expiresAt - 60_000) return hit.token; // refresh 60s early

  const res = await fetch(STS_URL, {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({
      grant_type: "client_credentials",
      scope,
      client_id: process.env.ESW_CLIENT_ID,
      client_secret: process.env.ESW_CLIENT_SECRET,
    }),
  });
  if (!res.ok) throw new Error(`STS token request failed: ${res.status}`);

  const { access_token, expires_in } = await res.json();
  cache.set(scope, {
    token: access_token,
    expiresAt: Date.now() + expires_in * 1000,
  });
  return access_token;
}
```

{% endcode %}
{% endtab %}

{% tab title="Python" %}
{% code expandable="true" %}

```python
import os
import time
import requests

STS_URL = "https://security-sts.sandbox.eshopworld.com/connect/token"
_cache = {}  # scope -> (token, expires_at)


def get_token(scope):
    token, expires_at = _cache.get(scope, (None, 0))
    if token and time.time() < expires_at - 60:  # refresh 60s early
        return token

    res = requests.post(
        STS_URL,
        data={
            "grant_type": "client_credentials",
            "scope": scope,
            "client_id": os.environ["ESW_CLIENT_ID"],
            "client_secret": os.environ["ESW_CLIENT_SECRET"],
        },
        timeout=10,
    )
    res.raise_for_status()
    body = res.json()
    _cache[scope] = (body["access_token"], time.time() + body["expires_in"])
    return body["access_token"]
```

{% endcode %}
{% endtab %}
{% endtabs %}
{% endstep %}
{% endstepper %}

### Sandbox

| Service                  | Sandbox                                                     |
| ------------------------ | ----------------------------------------------------------- |
| **STS (token endpoint)** | `https://security-sts.sandbox.eshopworld.com/connect/token` |

To move to production, request tokens from the production STS endpoint with the credentials ESW provides for production. Confirm the production API base URL for each API with your ESW representative before go-live.

### Credential lifecycle

Client credentials are valid for **six months**. A **one-month rollover** window lets you rotate secrets without downtime. Put a rotation reminder in your team calendar when you receive new credentials.

### Troubleshooting

| Symptom                                 | Likely cause                                                                                      | Fix                                                                                               |
| --------------------------------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| STS returns `403` with `invalid_client` | `client_id` or `client_secret` is wrong, or you are using credentials for a different environment | Re-check both values and confirm you are calling the STS host that matches your credentials       |
| STS rejects the token request           | Body sent as JSON, or a parameter is missing                                                      | Send a form URL encoded body with `grant_type`, `scope`, `client_id`, and `client_secret`         |
| STS returns `400` with `invalid_scope`  | The requested scope is unknown or not enabled for your client                                     | Check the scope spelling, then ask ESW to enable the required scope                               |
| API returns `401`                       | Token missing, malformed, or expired                                                              | Send `Authorization: Bearer <access_token>` and request a new token when the current one expires  |
| API returns `403`                       | Token does not carry the scope the endpoint needs, or the credentials are insufficient            | Request a token with the scope for that API. Ask ESW to enable it on your client if it is missing |
| API returns `429`                       | Too many requests                                                                                 | Wait for the number of seconds in the `Retry-After` header, then retry                            |
| Request to an STS host returns an error | API request sent to the token endpoint                                                            | Send API requests to the service base URL, not STS                                                |

### Next steps

* Pick a flow on the [Quick Start Guide](/getting-started/get-started-with-esw/quick-start-guide.md) page
* Run the sandbox flow in the [Postman collection](/getting-started/get-started-with-esw/quick-start-guide/postman-collection.md)
* Review [API best practices](/getting-started/get-started-with-esw/quick-start-guide/best-practices/apis.md) before you go live


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://developer.esw.com/getting-started/get-started-with-esw/quick-start-guide/authentication.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
