---
title: Build an app for other merchants
description: Register an OAuth application, ask merchants for scoped access with the authorization code flow, and keep tokens fresh.
---

Goal: an app that other Bolder merchants connect to their account, such as a fulfillment tool, an ERP sync or a reporting service. Each merchant authorizes your app once; you get a token scoped to what you asked for.

For scripts that act on your own account, a [personal access token](/guides/first-api-request) is simpler. Apps can't be listed in a public app directory yet: you share your app's connect link with merchants yourself.

1. **Register the application**

    Go to [start.onbolder.com/dev/credentials](https://start.onbolder.com/dev/credentials), sign in and create an application. Set its redirect URI (for example `https://yourapp.com/callback`). You get a `CLIENT_ID` and a `CLIENT_SECRET`; keep the secret on your server.

2. **Send the merchant to authorize**

    ```
    GET https://auth.onbolder.com/oauth/authorize
      ?client_id=YOUR_CLIENT_ID
      &redirect_uri=https://yourapp.com/callback
      &response_type=code
      &scope=products.read,orders.read,orders.write
    ```

    Ask only for the scopes you need: each namespace (`products`, `orders`, `customers`, `shops`, `hub`, `store`, `sellers`, `batches`) has a `.read` and a `.write` scope. The table is in the [API reference](/api/v2).

3. **Exchange the code**

    Bolder redirects back to your `redirect_uri` with a `code`. Exchange it on your server:

    ```js
    const res = await fetch('https://auth.onbolder.com/oauth/token', {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: new URLSearchParams({
        grant_type: 'authorization_code',
        client_id: process.env.BOLDER_CLIENT_ID,
        client_secret: process.env.BOLDER_CLIENT_SECRET,
        redirect_uri: 'https://yourapp.com/callback',
        code: req.query.code
      })
    })
    const { access_token, refresh_token, expires_in, scope } = await res.json()
    ```

    Store both tokens with the merchant's account in your app.

4. **Call the API as the merchant**

    ```js
    import { createBolder } from 'bolder-api-client'

    const bolder = await createBolder({ token: access_token })
    const orders = await bolder.orders.list({ status: 'pending' })
    ```

5. **Refresh before it expires**

    Access tokens are temporary (`expires_in` is in seconds). Get a new one with the refresh token:

    ```js
    new URLSearchParams({
      grant_type: 'refresh_token',
      client_id: process.env.BOLDER_CLIENT_ID,
      client_secret: process.env.BOLDER_CLIENT_SECRET,
      refresh_token
    })
    ```

    Post it to the same `/oauth/token` endpoint.

## Ruby apps

[`omniauth-bolder`](https://github.com/onbolder/omniauth-bolder) is the OmniAuth strategy for the authorization step:

```ruby
use OmniAuth::Builder do
  provider :bolder, ENV['BOLDER_KEY'], ENV['BOLDER_SECRET'], scope: 'products.read,orders.read'
end
```

## Server-to-server

A backend that only acts on the account that owns the application can skip the redirect and use the client credentials grant:

```bash
curl -X POST https://auth.onbolder.com/oauth/token \
  -d "grant_type=client_credentials&client_id=CLIENT_ID&client_secret=CLIENT_SECRET&scope=orders.read"
```

The CLI reads the same credentials from `BOLDER_CLIENT_ID` and `BOLDER_CLIENT_SECRET` for use in CI.

## Next

- [Receive order webhooks](/guides/receive-webhooks) to react to each merchant's events.
- [Authentication reference](/api/v2): every grant type and the token response.
