> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://developer-dev.shipbob.dev/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://developer-dev.shipbob.dev/_mcp/server.

# Authentication

> Learn how to authenticate with ShipBob's API using Personal Access Tokens or OAuth 2.0

ShipBob's API supports two authentication methods: **Personal Access Token (PAT)** for single-user integrations and **OAuth 2.0** for multi-user applications.

## Prerequisites

Before you begin, you'll need a ShipBob account. Choose your environment:

Sign up for production access

Sign up for sandbox access

Our sandbox accounts are separate environments and data cannot be transferred over to production.

## Choose Your Authentication Method

* Best for single-user custom integrations
* Quick setup, no expiration, and full account access

- Best if you want to create a multi-user app
- Required for apps listed on the ShipBob App Store

All API authentication operates against the **root user** of the account. When requesting a PAT token or installing an OAuth app, ensure you do so with the root user credentials.

***

## Personal Access Token (PAT) Flow

This is best for single-user custom integrations and quick setup, no expiration and full account access.

Navigate to the ShipBob dashboard and create your PAT:

Go to [Integrations > API Tokens](https://web.shipbob.com/app/merchant/#/Integrations/token-management) > **Generate New Token**

Go to [Integrations > API Tokens](https://webstage.shipbob.dev/app/merchant/#/Integrations/token-management) > **Generate New Token**

When you request your first PAT, ShipBob automatically creates a Single-Merchant Application (SMA) and [channel](https://developer.shipbob.com/api/channels/get-channels) to manage your tokens. You can generate multiple tokens and revoke them anytime.

PAT tokens **do not expire**. Store them securely and never commit them to version control.

For security best practices, we recommend rotating your Personal Access Token (PAT) periodically. Regular rotation helps reduce the risk of unauthorized access.

Include your PAT in the `Authorization` header of all API requests:

```bash
curl -X GET "https://api.shipbob.com/2026-01/channel" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
```

Before making POST requests, get your channel ID using the [GET Channel](https://developer.shipbob.com/api/channels/get-channels) endpoint:

```bash
curl -X GET "https://api.shipbob.com/2026-01/channel" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
```

**Response:**

```json
{
  "items": [
    {
      "id": 56232,
      "name": "PAT Channel",
      "application_name": "SMA",
      "scopes": [
        "billing_read",
        "channels_read",
        "orders_read",
        "fulfillments_read",
        "fulfillments_write",
        "inventory_read",
        "inventory_write",
        "locations_read",
        "orders_write",
        "pricing_read",
        "products_read",
        "products_write",
        "receiving_read",
        "receiving_write",
        "returns_read",
        "returns_write",
        "webhooks_read",
        "webhooks_write"
      ]
    }
  ]
}
```

Use the channel ID with `_write` scopes when making POST requests. Include it in the `shipbob_channel_id` header.

***

## OAuth Flow

OAuth is **required** for multi-user integrations and applications listed on the ShipBob App Store. It provides secure, granular access control with token expiration.

To authenticate via OAuth, follow these steps:

### Step 1: Create App

Generate credentials from the ShipBob dashboard by going to **Integrations** > **OAuth Apps** > **Create App**. See more [here](https://support.shipbob.com/s/article/OAuth-Apps).

If you want to test on a sandbox account here is the link to [create one](https://webstage.shipbob.dev/app/merchant/#/SignUp?utm_source=app_partner).

**To strengthen security, PKCE (Proof Key for Code Exchange) is part of ShipBob's OAuth flow.** The steps below include generating a `code_challenge` for the authorize request and a matching `code_verifier` for the token exchange. Already have an OAuth app on the older flow? Follow the [Migrate to PKCE](/migration/pkce) guide.

### Generate a PKCE Code Verifier and Challenge

Generate a [PKCE](https://datatracker.ietf.org/doc/html/rfc7636) (Proof Key for Code Exchange) pair before redirecting the user to the authorize endpoint:

* **`code_verifier`** – a cryptographically random string, 43-128 characters, using only the URL-safe characters `A-Z`, `a-z`, `0-9`, `-`, `.`, `_`, `~`. Keep it secret and store it for the token exchange (Step 4).
* **`code_challenge`** – the Base64-URL-encoded (no padding) SHA-256 hash of the `code_verifier`.

Always use the `S256` challenge method. Never use the `plain` method in production.

```javascript
import crypto from "crypto";

function base64UrlEncode(buffer) {
  return buffer
    .toString("base64")
    .replace(/\+/g, "-")
    .replace(/\//g, "_")
    .replace(/=+$/, "");
}

// 32 random bytes -> 43-character verifier
const codeVerifier = base64UrlEncode(crypto.randomBytes(32));

const codeChallenge = base64UrlEncode(
  crypto.createHash("sha256").update(codeVerifier).digest()
);

console.log({ codeVerifier, codeChallenge });
```

```python
import base64
import hashlib
import os

def base64_url_encode(data: bytes) -> str:
    return base64.urlsafe_b64encode(data).rstrip(b"=").decode("ascii")

# 32 random bytes -> 43-character verifier
code_verifier = base64_url_encode(os.urandom(32))

code_challenge = base64_url_encode(
    hashlib.sha256(code_verifier.encode("ascii")).digest()
)

print(code_verifier, code_challenge)
```

```powershell
$bytes = New-Object byte[] 32
[System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($bytes)
$codeVerifier = [Convert]::ToBase64String($bytes).TrimEnd('=').Replace('+','-').Replace('/','_')

$sha256 = [System.Security.Cryptography.SHA256]::Create()
$hashBytes = $sha256.ComputeHash([System.Text.Encoding]::ASCII.GetBytes($codeVerifier))
$codeChallenge = [Convert]::ToBase64String($hashBytes).TrimEnd('=').Replace('+','-').Replace('/','_')

"$codeVerifier`n$codeChallenge"
```

### Step 2: Request Permission to Access User Data

Once you have your client ID and secret, you can use them to get a user’s permission to access their account data. You start by making a request to the following endpoint:

```bash
GET https://auth.shipbob.com/connect/authorize
```

Use `https://authstage.shipbob.com/connect/authorize` if on sandbox.

The following query parameters are allowed for this endpoint:

| Parameter               | Values                                                                                                                                                                                                                           | Required?   |
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
| `client_id`             | Client id provided by step 1.                                                                                                                                                                                                    | required    |
| `response_type`         | Must be `code` to use the authorization code flow.                                                                                                                                                                               | required    |
| `scope`                 | One or more scopes granted by step 1, space-separated. **NOTE:** if you want to take advantage of refresh tokens (aka offline access mode) you must additionally request the `offline_access` scope.                             | required    |
| `redirect_uri`          | The callback URI ShipBob will call after the user responds to the request for consent. Must match one of the provided values from step 1.                                                                                        | required    |
| `code_challenge`        | The PKCE code challenge generated above (Base64-URL-encoded SHA-256 of your `code_verifier`).                                                                                                                                    | required    |
| `code_challenge_method` | The PKCE challenge method. Must be `S256`.                                                                                                                                                                                       | required    |
| `response_mode`         | If you include this query parameter with value `form_post` then we will make a POST request to your callback URL, instead of including the data as a fragment.                                                                   | optional    |
| `integration_name`      | Name of the integration for this particular user. We recommend that you know the user’s store name on your platform. If not provided, the user will be prompted to provide their name or choose one from a drop-down of options. | recommended |
| `state`                 | Application-provided string to help prevent replay attacks. Echoed back to the application in the callback for validation.                                                                                                       | recommended |
| `nonce`                 | A random string you can send and we will send it back within the token, to prevent replay attacks, code substitutions, etc.                                                                                                      | recommended |

These parameters must be URL encoded, particularly `redirect_uri`.

A very basic example call to our integrate endpoint will look like this:

```bash
GET https://auth.shipbob.com/connect/authorize?
   client_id=ExternalApplication_123
   &response_type=code
   &scope=openid billing_read channels_read fulfillments_read fulfillments_write inventory_read inventory_write locations_read orders_read orders_write pricing_read products_read products_write receiving_read receiving_write returns_read returns_write webhooks_read webhooks_write offline_access
   &redirect_uri=https%3A%2F%2Fwww.myapp.com%2Fintegrate%2Fshipbob%2Fcallback
   &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
   &code_challenge_method=S256
   &response_mode=form_post
   &integration_name=MyCoolApp
```

### Authorization URL Builder

Fill in your details and the URL will be generated live:

### Step 3: Implement Your Callback URI

When the user grants access, ShipBob redirects to your callback URI with the following parameters:

```bash
POST https://www.myapp.com/integrate/shipbob/callback
```

**Response**

```json highlight={3}
{
  "id_token": "some_open_id_token",
  "code": "some_access_code",
  "scope": "billing_read channels_read fulfillments_read fulfillments_write inventory_read inventory_write locations_read orders_read orders_write pricing_read products_read products_write receiving_read receiving_write returns_read returns_write webhooks_read webhooks_write offline_access openid",
  "session_state": "some_session_state",
}
```

### Step 4: Obtain an Access Token

Exchange the authorization `code` for an access token by making a `POST` request with `application/x-www-form-urlencoded` body. Include the `code_verifier` you generated before Step 2:

```bash
curl -X POST "https://auth.shipbob.com/connect/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "redirect_uri=https://www.myapp.com/integrate/shipbob/callback" \
  --data-urlencode "client_id=ExternalApplication_123" \
  --data-urlencode "client_secret=someSecret" \
  --data-urlencode "code=code_from_step_3" \
  --data-urlencode "code_verifier=code_verifier_from_pkce_step" \
  --data-urlencode "grant_type=authorization_code"
```

The `code_verifier` must match the `code_challenge` you sent on the authorize request. PKCE is an additional layer of protection - the `client_secret` is still required, not replaced.

**Response**

```json
{
  "id_token": "some_open_id_token",
  "access_token": "your_api_token",
  "expires_in": 3600,
  "token_type": "bearer",
  "refresh_token": "ABCDEFGHIJKLMNOPQRSTUVWABCDEFGHIJKLMNOPQRSTUVWXYZ123456789012345-1",
  "scope": "billing_read channels_read fulfillments_read fulfillments_write inventory_read inventory_write locations_read orders_read orders_write pricing_read products_read products_write receiving_read receiving_write returns_read returns_write webhooks_read webhooks_write offline_access openid"
}
```

Use `https://authstage.shipbob.com/connect/token` if using sandbox.

### Step 5: Refresh Token

If using `offline_access`, a `refresh_token` will also be provided.

Use it to obtain new tokens without requiring user login:

```bash
curl -X POST "https://auth.shipbob.com/connect/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "client_id=ExternalApplication_123" \
  --data-urlencode "client_secret=someSecret" \
  --data-urlencode "refresh_token=refresh_token_from_step_4" \
  --data-urlencode "grant_type=refresh_token"
```

Access tokens expire in 1 hour. Refresh tokens are valid for 30 days.

A new `refresh_token` will be generated every time you create a new `access_token`.

PKCE applies only to the initial authorization code exchange. You do **not** need to send a `code_verifier` or `code_challenge` when refreshing tokens.

### Step 6: Retrieve Your Channel ID

Most of the POST requests on the ShipBob API require you to include a `shipbob_channel_id` in the header. Retrieve it via:

```bash
GET https://api.shipbob.com/2026-01/channel
```

Example Response:

```json
{
  "items": [
    {
      "id": 56232,
      "name": "MyCoolApp",
      "application_name": "MyCoolApp",
      "scopes": ["billing_read", "channels_read", "orders_read", "fulfillments_read", "fulfillments_write", "inventory_read", "inventory_write", "locations_read", "orders_read", "orders_write", "pricing_read", "products_read", "products_write", "receiving_read", "receiving_write", "returns_read", "returns_write", "webhooks_read", "webhooks_write"]
    }
  ]
}
```

The channel ID you should use is the channel ID that has `_write` scopes.

If you are using a ShipBob sandbox account, make sure to update your endpoint to `https://sandbox-api.shipbob.com/2026-01/channel`.

You’re now ready to make authenticated API requests using ShipBob!

***

### Step 7: Test Your App (Optional)

* Verify the OAuth flow works end-to-end.
* Ensure scopes and permissions are correctly applied.
* Test with sandbox data before going live.

***

### Step 8: Publish in ShipBob App Store (Optional)

Want to make your app available to all ShipBob merchants? Publishing to the App Store allows any ShipBob user to discover and install your integration.

In the ShipBob dashboard, navigate to **Integrations** > **OAuth Apps**, find your app, and click **Publish** in the Action column.

**Required Information:**

* **App Icon** (500x500px, .png or .jpeg)\
  This image represents your app in the ShipBob App Store. Choose a clear, recognizable logo.

* **Integration Description** (max 500 characters)\
  Highlight key features, benefits, and use cases. Focus on what problems your app solves for merchants.

* **Support Documentation Link**\
  Provide a URL to help articles or guides that explain how to use your integration.

* **Support Email**\
  Enter a support email address where users can reach your team for questions or issues.

* **Install URL**\
  This is where users will be redirected when they click **Get App** in the ShipBob App Store. It should route to your OAuth authorization flow (Step 2).

Click **Submit for Review**.

Your app's status will update to **In Review**.

The ShipBob team will review your app within **1-3 business days**.

They may contact the user listed under the **Created By** column if additional details or changes are needed.

Prepare for potential questions about your app's functionality, security practices, and user experience.

Once approved, your app will be listed in the [ShipBob App Store](https://web.shipbob.com/app/merchant/#/Integrations/app-store).

ShipBob merchants can now discover and install your app.

Monitor your support email for user questions and feedback after launch. A responsive support experience helps build trust with merchants.

* **Clear Icon**: Use a simple, recognizable logo that looks good at small sizes
* **Compelling Description**: Focus on merchant benefits, not just technical features
* **Quality Documentation**: Provide step-by-step setup guides and troubleshooting tips
* **Responsive Support**: Answer merchant questions promptly to build trust
* **Test Thoroughly**: Ensure your OAuth flow works flawlessly before submitting

***

## Quick Reference

### Production

* **Auth URL**: `https://auth.shipbob.com`
* **API Base**: `https://api.shipbob.com`
* **Dashboard**: `https://web.shipbob.com`

### Sandbox

* **Auth URL**: `https://authstage.shipbob.com`
* **API Base**: `https://sandbox-api.shipbob.com`
* **Dashboard**: `https://webstage.shipbob.dev`

```http
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
shipbob_channel_id: YOUR_CHANNEL_ID
```

| Token Type          | Lifetime      | Notes                            |
| ------------------- | ------------- | -------------------------------- |
| PAT                 | Never expires | Revoke manually from dashboard   |
| OAuth Access Token  | 1 hour        | Refresh before expiration        |
| OAuth Refresh Token | 30 days       | New one issued with each refresh |

* ✅ Store tokens securely (e.g., encrypted database, secrets manager)
* ✅ Never commit tokens to version control
* ✅ Use HTTPS for all API requests
* ✅ Implement proper error handling for expired tokens
* ✅ Validate `state` parameter in OAuth callbacks
* ✅ Use PKCE (`S256`) - generate a fresh `code_verifier` per authorization and never reuse it
* ✅ Use `offline_access` scope only when necessary
* ✅ Regularly rotate PAT tokens
* ⛔ Don't expose tokens in client-side code
* ⛔ Don't share tokens between environments