> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bahnexpress.fi/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication

> Create a credential and use the OAuth 2.0 client credentials grant.

The Bahn Customer API uses the OAuth 2.0 client credentials grant. It is a server-to-server flow with no user sign-in or redirect.

Authentication has two parts. First, you create and store a credential. Then, your system gets short-lived access tokens with that credential.

<Warning>
  Store the client secret in a secret manager. Do not put it in source code, browser code, logs, or an agent prompt.
</Warning>

## Complete the one-time setup

<Steps>
  <Step title="Create a sandbox credential">
    Open **API integrations** in the Bahn customer portal. Select **Create credential**, then give the credential a name that identifies one system.

    Copy the client ID and client secret before you close the window. The portal shows the client secret one time.
  </Step>

  <Step title="Store the credential">
    Store the client ID and client secret in your server-side secret manager. Do not send the secret to a browser or mobile application.
  </Step>

  <Step title="Add the environment URLs">
    Store the API base URL and token URL with the credential. Do not combine sandbox and production configuration.

    | Environment | API base URL                         | Token URL                                         |
    | ----------- | ------------------------------------ | ------------------------------------------------- |
    | Sandbox     | `https://sandbox.api.bahnexpress.fi` | `https://sandbox.api.bahnexpress.fi/oauth2/token` |
    | Production  | `https://api.bahnexpress.fi`         | `https://api.bahnexpress.fi/oauth2/token`         |
  </Step>

  <Step title="Verify the sandbox connection">
    Get an access token. Then call `GET /v2/orders` with that token.

    Complete your order and webhook tests in the sandbox before you create a production credential.
  </Step>
</Steps>

Use a separate credential for each system and environment. This separation lets you replace one credential while other systems stay active.

## Run the token flow

Your system repeats this flow automatically:

1. Read the client ID and client secret from secure storage.
2. Request an access token with HTTP Basic authentication.
3. Cache the token for its credential and scope set.
4. Send the token in the `Authorization` header for API requests.
5. Request a new token before the current token expires.

Do not request a new token for each API call.

### Get an access token

Use the client ID as the HTTP Basic username. Use the client secret as the password. Send the request body as form data.

```bash theme={null}
curl --request POST "https://sandbox.api.bahnexpress.fi/oauth2/token" \
  --user "$BAHN_CLIENT_ID:$BAHN_CLIENT_SECRET" \
  --header "Content-Type: application/x-www-form-urlencoded" \
  --data "grant_type=client_credentials"
```

The response contains the token, its expiry, and its granted scopes.

```json theme={null}
{
  "access_token": "eyJ...",
  "expires_in": 900,
  "expires_at": 1785417831,
  "token_type": "Bearer",
  "scope": "orders:read orders:write reports:read test:write"
}
```

The token is valid for 15 minutes. Request a new token 60 seconds before it expires. The flow does not use refresh tokens.

### Use the access token

Send the access token as a Bearer token.

```bash theme={null}
curl "https://sandbox.api.bahnexpress.fi/v2/orders" \
  --header "Authorization: Bearer $BAHN_ACCESS_TOKEN"
```

The access token belongs to one environment. A sandbox token cannot access production, and a production token cannot access the sandbox.

## Select scopes

Omit the `scope` form field to request every scope assigned to the credential. Send it when your process needs a smaller set.

| Scope          | Access                                                        |
| -------------- | ------------------------------------------------------------- |
| `orders:read`  | Read orders, files, inspection reports, and tracking data.    |
| `orders:write` | Check prices, create files, create orders, and change orders. |
| `reports:read` | Read customer order report rows.                              |
| `test:write`   | Apply scenarios in the sandbox.                               |

A request for a scope that is not assigned to the credential fails. Most integrations can omit the `scope` field.

## Handle token failures

If an API request returns `401`, get a new token and retry the request once. Stop if the new token also fails.

If the token endpoint returns `invalid_client`, check that the credential belongs to the environment in the token URL. Do not retry the same values in a loop.

If the token endpoint returns `invalid_request`, check the content type, `grant_type`, and optional `scope` field.

## Replace a credential

Create the replacement credential before you revoke the old credential. Update your system, verify a new token, and then revoke the old credential.

Contact `support@bahnexpress.com` if a credential is exposed or you need integration support.

An ordering-channel credential uses the same token flow. It can act for represented businesses through `ordered_for`. Read [Platform ordering](./ordering-channels) for its access rules.
