Authentication
The 3PLGuys API supports two authentication methods. Choose the one that fits your use case.
API Keys
Static bearer tokens for programmatic access. No token exchange or refresh flow required.
- ✓ Scripts, bots, and cron jobs
- ✓ Server-to-server integrations
- ✓ CI/CD pipelines
- ✓ Simple — just set the header and go
OAuth 2.0
Authorization Code flow for apps that act on behalf of other users and organizations.
- ✓ Third-party integrations
- ✓ Multi-tenant apps
- ✓ User-facing applications
- ✓ MCP servers and AI agents
API Keys
API keys are the simplest way to authenticate. Create a key, include it as a Bearer token, and start making requests. Each key is tied to a specific organization and has scoped permissions.
Go to Account Settings → API Keys in your 3PLGuys dashboard. Select the organization, choose the scopes your integration needs, and optionally set an expiration date.
Important
Include the API key as a Bearer token in the Authorization header. That's it — no token exchange, no refresh flow.
curl -X GET https://api.3plguys.com/v0/shipments \-H "Authorization: Bearer 3pl_your_api_key_here"
From the API Keys page you can:
- View all your keys, their scopes, and last used time
- Edit the name, scopes, or expiration of existing keys
- Revoke keys instantly — any integration using that key will stop working immediately
API keys are prefixed with 3pl_ so you can easily identify them in your codebase. Keys that have an expiration date will automatically stop working after that date.
OAuth 2.0
Use OAuth 2.0 when building applications that need to access other users' organizations. The user authorizes your app, you exchange the code for tokens, and use the access token in requests.
Go to Account Settings → OAuth Apps in your 3PLGuys dashboard. Give your app a name, add redirect URIs, and select the scopes it needs. You'll receive a client_id and client_secret.
Important
OAuth 2.0 Authorization Code Flow
Redirect the user's browser to the 3PLGuys authorization page. The user will log in and grant your application access to the requested scopes.
GET https://api.3plguys.com/oauth/authorize?response_type=code&client_id=your_client_id&redirect_uri=https://yourapp.com/callback&scope=inventory shipments&state=random_state_value&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256
| Parameter | Type | Description |
|---|---|---|
response_type* | string | Must be "code" |
client_id* | string | Your OAuth client ID |
redirect_uri* | string | URL to redirect back to after authorization. Must match a registered redirect URI. |
scope | string | Space-separated list of scopes (see below). Optional; omit to request the app's default scopes. |
state | string | Random value to prevent CSRF attacks. Returned unchanged in the callback. |
code_challenge | string | PKCE challenge: base64url(SHA256(code_verifier)). Recommended for all clients, required for public clients. |
code_challenge_method | string | Must be "S256" when using PKCE. |
After the user authorizes, they are redirected back to your redirect_uri with a code query parameter:
https://yourapp.com/callback?code=abc123&state=random_state_value
Make a server-side POST request to exchange the authorization code for an access token and refresh token. This must be done within a few minutes of receiving the code. The token endpoint expects application/x-www-form-urlencoded per the OAuth 2.0 spec.
grant_type=authorization_code&code=abc123&client_id=your_client_id&client_secret=your_client_secret&redirect_uri=https://yourapp.com/callback&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
| Parameter | Type | Description |
|---|---|---|
grant_type* | string | Must be "authorization_code" |
code* | string | The authorization code from step 1 |
client_id* | string | Your OAuth client ID |
client_secret* | string | Your OAuth client secret |
redirect_uri | string | Must match the redirect_uri from step 1. Optional but recommended. |
code_verifier | string | PKCE verifier. Required if code_challenge was sent in step 1. |
resource | string | Resource indicator (RFC 8707). Optional, accepted for MCP/OAuth compatibility. |
Access tokens expire after 1 hour. Use the refresh token to obtain a new access token without requiring the user to re-authorize. The response includes a new refresh token — always store and use the latest one.
grant_type=refresh_token&refresh_token=def456...&client_id=your_client_id&client_secret=your_client_secret
Token Response
Both the authorization code exchange and refresh token requests return the same response format.
{"access_token": "eyJhbGciOiJIUzI1NiIs...","token_type": "Bearer","expires_in": 3600,"refresh_token": "def456...","scope": "inventory shipments"}
| Parameter | Type | Description |
|---|---|---|
access_token* | string | JWT token to include in API requests |
token_type* | string | Always "Bearer" |
expires_in* | number | Token lifetime in seconds (3600 = 1 hour) |
refresh_token* | string | Use to obtain a new access token when the current one expires |
scope* | string | Scopes granted to this token |
Using Your Token
Include the access token in the Authorization header of every API request.
curl -X GET https://api.3plguys.com/v0/shipments \-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
Scopes
Scopes control which parts of the API your application or key can access. Both API keys and OAuth tokens use the same scopes. Request only the scopes you need.
| Scope | Access | Endpoints |
|---|---|---|
inventory | Products, cartons, stock levels | /v0/inventory/* |
shipments | All shipment operations (PnP, SPD, list, cancel) | /v0/shipments/* |
locations | Warehouse locations | /v0/warehouses |
invoices | Invoice history and line items (read-only) | /v0/invoices/* |
notifications | Unified activity feed and action items | /v0/notifications |
recommendations | AI-powered operational suggestions | /v0/recommendations |
user-account | Organization and user account details | /v0/organization |
PKCE (Proof Key for Code Exchange)
PKCE (RFC 7636) prevents authorization code interception attacks. It is required for public clients (SPAs, mobile apps, CLI tools, MCP servers) and recommended for all clients.
import crypto from "crypto";// 1. Generate a random code verifier (43-128 chars)const codeVerifier = crypto.randomBytes(32).toString("base64url");// 2. Hash it with SHA-256 to produce the code challengeconst codeChallenge = crypto.createHash("sha256").update(codeVerifier).digest("base64url");// Send code_challenge + code_challenge_method=S256 in the authorize URL// Send code_verifier in the token exchange POST
How it works
- Your app generates a random
code_verifierstring and computescode_challenge = base64url(SHA256(code_verifier)). - Send
code_challengeandcode_challenge_method=S256in the authorization URL (step 1). - When exchanging the code (step 2), send the original
code_verifier. The server verifiesSHA256(code_verifier) == code_challenge. - If the challenge was sent but the verifier is missing or wrong, the token request is rejected.
Error Responses
401 UnauthorizedMissing, invalid, or expired token / API key. For OAuth, refresh the token and retry. For API keys, verify the key is correct and not expired.
403 ForbiddenThe token or API key does not have the required scope for this endpoint.
400 Bad RequestInvalid grant type, expired authorization code, or mismatched redirect URI.
Security Best Practices
- Never expose client secrets or API keys in frontend code, public repositories, or client-side JavaScript.
- Store credentials securely on the server side. Use environment variables or a secrets manager.
- Always use HTTPS for all API requests.
- Use the minimum scopes your integration actually needs.
- Set expiration dates on API keys where possible.
- Revoke keys and secrets immediately if they are compromised.
- For OAuth, validate the
stateparameter in the callback to prevent CSRF attacks. - For OAuth, always use the latest refresh token from the most recent response.