Authentication

Cargoholic APIs use OAuth 2.0 Client Credentials Grant for machine-to-machine (M2M) authentication. Every protected request requires a valid Bearer token and your assigned Tenant ID.


Prerequisites

Before making API calls, obtain your credentials from your Cargoholic representative:

ParameterDescription
client_idUnique public identifier for your API client
client_secretConfidential secret key used to authenticate your client
X-Tenant-IDUnique identifier for your organization's tenant workspace
🚧

Keep Your Secrets Safe

Never expose your client_secret in frontend applications, client-side code, or public repositories. Store it in a secure environment variable or secrets manager.


Step 1: Obtain an Access Token

Send a POST request to the token endpoint using application/x-www-form-urlencoded in the request body.

Endpoint

POST https://authentication.cargoholic.com/oauth2/token
Content-Type: application/x-www-form-urlencoded

Request Parameters

ParameterTypeRequiredDescription
grant_typestringYesMust be set to client_credentials
client_idstringYesYour assigned client ID
client_secretstringYesYour assigned client secret

Example Request

curl --request POST \
  --url https://authentication.cargoholic.com/oauth2/token \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --data grant_type=client_credentials \
  --data client_id=YOUR_CLIENT_ID \
  --data client_secret=YOUR_CLIENT_SECRET
const params = new URLSearchParams({
  grant_type: 'client_credentials',
  client_id: process.env.CARGOHOLIC_CLIENT_ID,
  client_secret: process.env.CARGOHOLIC_CLIENT_SECRET
});

const response = await fetch('https://authentication.cargoholic.com/oauth2/token', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded'
  },
  body: params.toString()
});

const data = await response.json();
console.log(data.access_token);
import os
import requests

url = "https://authentication.cargoholic.com/oauth2/token"
payload = {
    "grant_type": "client_credentials",
    "client_id": os.getenv("CARGOHOLIC_CLIENT_ID"),
    "client_secret": os.getenv("CARGOHOLIC_CLIENT_SECRET")
}
headers = {"Content-Type": "application/x-www-form-urlencoded"}

response = requests.post(url, data=payload, headers=headers)
token_data = response.json()
print(token_data["access_token"])
package main

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"strings"
)

type TokenResponse struct {
	AccessToken string `json:"access_token"`
	ExpiresIn   int    `json:"expires_in"`
	TokenType   string `json:"token_type"`
}

func main() {
	formData := url.Values{}
	formData.Set("grant_type", "client_credentials")
	formData.Set("client_id", os.Getenv("CARGOHOLIC_CLIENT_ID"))
	formData.Set("client_secret", os.Getenv("CARGOHOLIC_CLIENT_SECRET"))

	req, _ := http.NewRequest("POST", "https://authentication.cargoholic.com/oauth2/token", strings.NewReader(formData.Encode()))
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	var tokenResp TokenResponse
	_ = json.Unmarshal(body, &tokenResp)

	fmt.Printf("Token: %s (Expires in: %d seconds)\n", tokenResp.AccessToken, tokenResp.ExpiresIn)
}

Response

{
  "access_token": "eyJraWQiOiJ...",
  "expires_in": 3600,
  "token_type": "Bearer"
}
FieldTypeDescription
access_tokenstringThe JWT bearer token used to authenticate subsequent API calls
expires_inintegerToken lifespan in seconds (typically 3600 / 1 hour)
token_typestringType of token issued (Bearer)

Step 2: Use the Token in Protected API Requests

Include the following two HTTP headers on every request to Cargoholic API endpoints:

  1. Authorization: Bearer <access_token>
  2. X-Tenant-ID: <tenant_id>

Example Protected Request

curl --request GET \
  --url https://api-sandbox.cargoholic.com/v1/orders \
  --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
  --header 'X-Tenant-ID: YOUR_TENANT_ID' \
  --header 'Accept: application/json'
const response = await fetch('https://api-sandbox.cargoholic.com/v1/orders', {
  method: 'GET',
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'X-Tenant-ID': process.env.CARGOHOLIC_TENANT_ID,
    'Accept': 'application/json'
  }
});

const orders = await response.json();
import requests

url = "https://api-sandbox.cargoholic.com/v1/orders"
headers = {
    "Authorization": f"Bearer {access_token}",
    "X-Tenant-ID": os.getenv("CARGOHOLIC_TENANT_ID"),
    "Accept": "application/json"
}

response = requests.get(url, headers=headers)
print(response.json())

Token Lifecycle & Best Practices

  • Token Caching: Access tokens are valid for 3,600 seconds (1 hour). Store the token in memory or a cache (e.g., Redis) and reuse it across multiple API requests until it expires. Requesting a new token on every API call degrades performance and may trigger rate limits.
  • Proactive Refresh: Request a fresh token shortly before the expires_in window elapses (e.g., at the 55-minute mark).
  • 401 Unauthorized Handling: If an API endpoint responds with 401 Unauthorized, invalidate your cached token, request a new token from the /oauth2/token endpoint, and retry the failed request once.
  • Tenant Isolation: The X-Tenant-ID header routes your request to your tenant's data partition. Omitting this header or passing an invalid ID will result in a 400 Bad Request .