OAuth 2.0

Recommended authentication mechanism for Marketplace Applications

OAuth 2.0 is the recommended authentication method for all Marketplace apps. It provides a secure and user-friendly way to access Samsara's API by allowing your users to grant permissions through a simple approval flow, eliminating the need to share API tokens manually.

For direct integrations, consider using API tokens created from your Samsara dashboard. Use OAuth if enhanced security with token refresh is needed, but note it requires more development effort. Learn more about using API tokens for authentication.

Review end-to-end examples on GitHub.

Setting Up Your OAuth 2.0 App

From the Samsara dashboard select Settings > OAuth 2.0 Apps

OAuth Apps Settings

To create a new OAuth 2.0 App, you'll need the following:

FieldDescriptionRequired
App nameYour app's name, which appears on the Samsara dashboard.Yes
Redirect URLThe URL where users are redirected after granting access.Yes
ScopesThe permissions your App requires. Each API endpoint requires specific scopes, which you can find in the API Reference.Yes
App LogoA JPEG or PNG. Suggested dimensions are 75x75 (max size 100KB).Yes
Direct Install URLA URL in your app where users can initiate the OAuth 2.0 flow. Enables installation from the Samsara Dashboard.No

OAuth App Configuration

❗️

Warning: Scope changes affect ALL app installs

Scope changes apply to all future app installs or updates. Users must re-authorize or update the app for updated scopes.

Record your App ID and App Secret credentials, as they are used to authenticate calls related to the OAuth process:

  • App ID (also known as client_id)
  • App Secret (also known as client_secret)

App ID and Secret

🔑

Store your App Secret securely

Your App Secret is a form of credential like a password. To keep your integration secure, never store it in your source code or commit it to version control. Instead, read the App Secret from an environment variable or secret manager.

As an example, your .env file might look like this:

# .env

SAMSARA_CLIENT_ID=<your_client_id>
SAMSARA_CLIENT_SECRET=<your_client_secret>

Implementation

Samsara follows the OAuth 2.0 authorization code grant flow.

Overview

  • Step 1: Redirect users to /oauth2/authorize to authorize access
  • Step 2: Users are redirected to your redirect_uri with a code
  • Step 3: Make an API call to /oauth2/token to exchange the code for a set of credentials including access_token and refresh_token
  • Step 4: Use the user's access_token as a Bearer token to make API calls on their behalf
  • Step 5: If the credentials are expired when attempting to make an API call, refresh tokens first using the /oauth2/token endpoint, then make the API call

OAuth 2.0 Flow

Step 1: Authorization Request

Start the OAuth 2.0 flow by redirecting users to the authorize endpoint /oauth2/authorize with these query parameters:

ParameterDescriptionRequired
client_idApp ID provided when you created your OAuth 2.0 App in the Samsara Dashboard.Yes
stateA unique code to prevent CSRF attacks. Verify this code in Step 2 when the user is redirected. Must be more than 8 characters.Yes
response_typeMust be code as only authorization code grant flow is currently supported.Yes
redirect_uriThe URI of your app that Samsara will redirect users back to after they grant access. Only required if you support multiple redirect URIs. Must use SSL protocol (https).Optional

Example

Here's an example link with the client_id, state, and response_type parameters:

<a href="https://api.samsara.com/oauth2/authorize?client_id=IEC65XwwV9&state=z3qAr0h5Ud&response_type=code">Connect to Samsara</a>

When a user clicks the authorize link, they'll be redirected to the Samsara authorization page where they can grant access to your app.

Authorize UI

Step 2: Handle Authorization Response

When the user clicks "Allow", Samsara redirects them to your redirect_uri with these query parameters:

ParameterDescription
codeAuthorization code to exchange for an access token. Expires after 10 minutes.
stateSame as the state parameter from Step 1. If they differ, abort the flow as this may indicate a CSRF attack.
scopeThe access level granted to your app. admin:read for read-only access or admin:write for full read/write access.

Example

When a user grants access, Samsara redirects them to your app with a URL like this:
https://my-app.com?code=hgg2tziN39&state=z3qAr0h5Ud&scope=admin%3Aread

If a user clicks "Cancel" or if an error occurs, they are redirected to the redirect_uri with the following query parameters:

ParameterDescription
errorThe error code. Common values include scope_not_granted and invalid_request.
error_descriptionA human-readable description of the error.
error_hintA human-readable hint that may assist the client in resolving the error.
stateThe state parameter from Step 1.

Example

When a user denies access, the redirect URL includes error details like this:

https://my-app.com?error=scope_not_granted&error_description=The+token+was+not+granted+the+requested+scope&error_hint=The+resource+owner+did+not+grant+the+requested+scope.&state=PDoPp3AE-4AqDHP4dXnbeA

Step 3: Token Exchange

Exchange the authorization code for API credentials with a POST request to the /oauth2/token endpoint.

Store the access_token and refresh_token in your database, associating them with the relevant user, organization, or account.

Calculate and save an expires_at timestamp using the expires_in value from the response. Before making an API call, check if the token has expired; if so, refresh it just-in-time.

Note: You don't need to refresh credentials before expiration if it is not being used to make an API call.

Authorization

To authenticate using HTTP Basic Auth using your OAuth 2.0 client ID and client secret:

  1. Combine the client_id and client_secret with a colon (e.g. "client_id:client_secret")
  2. Base64 encode that string
  3. Add "Basic " followed by the encoded string to the Authorization header

For example, if your credentials are:

  • client_id: my_id
  • client_secret: my_secret

The Authorization header would be: Authorization: Basic bXlfaWQ6bXlfc2VjcmV0

const clientId = process.env.SAMSARA_OAUTH_CLIENT_ID;
const clientSecret = process.env.SAMSARA_OAUTH_CLIENT_SECRET;

// Base64 encoded authorization string
const auth = Buffer.from(`${clientId}:${clientSecret}`).toString("base64");

// Make the request with header `Authorization: Basic ${auth}`
client_id = os.getenv("SAMSARA_OAUTH_CLIENT_ID")
client_secret = os.getenv("SAMSARA_OAUTH_CLIENT_SECRET")

# Base64 encoded authorization string
auth = base64.b64encode(f"{client_id}:{client_secret}".encode()).decode()

# Make the request with header: `Authorization: Basic ${auth}`

# Note: The `requests` library supports Basic Auth by passing a tuple of (client_id, client_secret) to the `auth` parameter
# E.g.  
#   requests.post(
#     'https://api.samsara.com/oauth2/token', 
#     auth=(SAMSARA_CLIENT_ID, SAMSARA_CLIENT_SECRET), 
#     data={'grant_type': 'refresh_token', 'refresh_token': token })

client_id = ENV['SAMSARA_CLIENT_ID']
client_secret = ENV['SAMSARA_CLIENT_SECRET']
auth = Base64.strict_encode64("#{client_id}:#{client_secret}")
var clientId = Environment.GetEnvironmentVariable("SAMSARA_CLIENT_ID");
var clientSecret = Environment.GetEnvironmentVariable("SAMSARA_CLIENT_SECRET");

var auth = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes($"{clientId}:{clientSecret}"));

// using var client = new HttpClient();
// client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", auth);
// Add basic auth header
clientId := os.Getenv("SAMSARA_CLIENT_ID")
clientSecret := os.Getenv("SAMSARA_CLIENT_SECRET")
combined := clientId + ":" + clientSecret
auth := base64.StdEncoding.EncodeToString([]byte(combined))

// req.Header.Add("Authorization", "Basic "+auth)
String clientId = System.getenv("SAMSARA_CLIENT_ID");
String clientSecret = System.getenv("SAMSARA_CLIENT_SECRET");

String combinedSecrets = clientId + ":" + clientSecret;
String auth = Base64.getEncoder().encodeToString(combinedSecrets.getBytes());

// HttpRequest request =
//     HttpRequest.newBuilder()
//        .header("Authorization", "Basic " + auth)
//        .build();
// Create authorization header
$combined = $_ENV['SAMSARA_CLIENT_ID'] . ':' . $_ENV['SAMSARA_CLIENT_SECRET'];
$auth = base64_encode($combined);

// $ch = curl_init();
// curl_setopt($ch, CURLOPT_HTTPHEADER, [
//     'Authorization: Basic ' . $encoded_auth,
// ]);

Body

  • Content-Type: application/x-www-form-urlencoded
ParameterDescriptionRequired
codeThe verification code provided to you in Step 2.Yes
grant_typeMust be authorization_code.Yes

Response

The API returns a JSON response containing the following:

{
  "access_token": "hXdwQUq3GBKZTvSLyURh",
  "expires_in": 3599,
  "scope": "admin:read",
  "token_type": "bearer",
  "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA"
}
FieldDescription
access_tokenThe token to authenticate with the Samsara API on behalf of the user.
expires_inThe number of seconds until the access token expires. Access tokens expire after 1 hour. After the access token expires, you must request a new one using the refresh token. See Refresh Tokens.
scopeadmin:read for read-only apps or admin:write for full-access read/write apps.
token_typebearer indicating the access token is to be used with the Authorization: Bearer HTTP header for calls to the Samsara API.
refresh_tokenThe token used to request a new access token when the access token expires. See Refresh Tokens.

📘

Once an authorization code is exchanged for a token, the code will no longer be valid.

Example

How to exchange an authorization code for a API credentials:

POST /oauth2/token HTTP/1.1
Host: api.samsara.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic bXlfaWQ6bXlfc2VjcmV0

code=hgg2tziN39&grant_type=authorization_code
curl -X POST \
  https://api.samsara.com/oauth2/token \
  -H 'Authorization: Basic bXlfaWQ6bXlfc2VjcmV0' \
  -d 'code=hgg2tziN39&grant_type=authorization_code'
// Using node-fetch
const fetch = (...args) =>
  import("node-fetch").then(({ default: fetch }) => fetch(...args));

const clientId = process.env.SAMSARA_CLIENT_ID;
const clientSecret = process.env.SAMSARA_CLIENT_SECRET;

// Base64 encoded authorization string
const auth = Buffer.from(`${clientId}:${clientSecret}`).toString("base64");

const response = await fetch("https://api.samsara.com/oauth2/token", {
  method: "POST",
  headers: {
    "Content-Type": "application/x-www-form-urlencoded",
    Authorization: `Basic ${auth}`,
  },
  body: new URLSearchParams({
    grant_type: "authorization_code",
    code: code, // from query string
  }),
});

const {access_token, refresh_token, expires_in} = await response.json();

// Calculate expire timestamp
const expiresAt = Math.floor(Date.now() / 1000) + expires_in;

// Store in session
const credentials = {
  access_token: access_token,
  refresh_token: refresh_token,
  expires_at: expiresAt,
};
SAMSARA_CLIENT_ID = os.getenv('SAMSARA_CLIENT_ID')
SAMSARA_CLIENT_SECRET = os.getenv('SAMSARA_CLIENT_SECRET')

# Within your redirect handler:
response = requests.post(
    "https://api.samsara.com/oauth2/token",
    data={
        'code': code, # from query string
        'grant_type': 'authorization_code',
    },
    auth=(SAMSARA_CLIENT_ID, SAMSARA_CLIENT_SECRET)
)

token_data = response.json()
access_token = token_data.get('access_token')
refresh_token = token_data.get('refresh_token')
expires_at = time.time() + token_data.get('expires_in')

# Store the tokens in the session
credentials = {
    'access_token': access_token,
    'refresh_token': refresh_token,
    'expires_at': expires_at
}
print(credentials)
auth = Base64.strict_encode64("#{ENV['SAMSARA_CLIENT_ID']}:#{ENV['SAMSARA_CLIENT_SECRET']}")

uri = URI('https://api.samsara.com/oauth2/token')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true

request = Net::HTTP::Post.new(uri)
request['Content-Type'] = 'application/x-www-form-urlencoded'
request['Authorization'] = "Basic #{auth}"
request.body = URI.encode_www_form({
  code: params[:code], # from query string
  grant_type: 'authorization_code',
})

response = http.request(request)

if response.code == '200'
  token_data = JSON.parse(response.body)
  access_token = token_data['access_token']
  refresh_token = token_data['refresh_token']
  expires_in = token_data['expires_in']

  # Calculate expires_at timestamp
  expires_at = Time.now.to_i + expires_in

  credentials = {
    'access_token' => access_token,
    'refresh_token' => refresh_token,
    'expires_at' => expires_at
  }
end
using var client = new HttpClient();
var tokenRequest = new FormUrlEncodedContent(new Dictionary<string, string>
{
    ["grant_type"] = "authorization_code",
    ["code"] = code // from query string
});

// Add Basic Auth header
var clientId = Environment.GetEnvironmentVariable("SAMSARA_CLIENT_ID");
var clientSecret = Environment.GetEnvironmentVariable("SAMSARA_CLIENT_SECRET");
var auth = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes($"{clientId}:{clientSecret}"));
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", auth);

var response = await client.PostAsync("https://api.samsara.com/oauth2/token", tokenRequest);
var result = await response.Content.ReadAsStringAsync();

if (!response.IsSuccessStatusCode)
{
    context.Response.StatusCode = 400;
    await context.Response.WriteAsync($"Failed to exchange code for tokens: {result}");
    return;
}

// Parse token response using JsonElement to handle numeric values correctly
var tokenResponse = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(result);
var accessToken = tokenResponse["access_token"].GetString();
var refreshToken = tokenResponse["refresh_token"].GetString();
var expiresIn = tokenResponse["expires_in"].GetInt32();

// Calculate when the token will expire
var expiresAt = DateTimeOffset.UtcNow.AddSeconds(expiresIn).ToUnixTimeSeconds();

var credentials = new Dictionary<string, string>
{
    ["access_token"] = accessToken,
    ["refresh_token"] = refreshToken,
    ["expires_at"] = expiresAt.ToString()
};
client := &http.Client{}

tokenURL := "https://api.samsara.com/oauth2/token"
data := url.Values{}
data.Set("code", code)
data.Set("grant_type", "authorization_code")

req, err := http.NewRequest("POST", tokenURL, strings.NewReader(data.Encode()))
if err != nil {
  http.Error(w, "Error creating token request", http.StatusInternalServerError)
  return
}

// Add headers
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")

// Add basic auth header
auth := os.Getenv("SAMSARA_CLIENT_ID") + ":" + os.Getenv("SAMSARA_CLIENT_SECRET")
basicAuth := base64.StdEncoding.EncodeToString([]byte(auth))
req.Header.Add("Authorization", "Basic "+basicAuth)

// Make request
resp, err := client.Do(req)
if err != nil {
  http.Error(w, "Error exchanging code for token", http.StatusInternalServerError)
  return
}
defer resp.Body.Close()

// Parse response
var result struct {
  AccessToken  string `json:"access_token"`
  RefreshToken string `json:"refresh_token"`
  ExpiresIn    int    `json:"expires_in"`
  TokenType    string `json:"token_type"`
  Scope        string `json:"scope"`
}

if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
  http.Error(w, "Error parsing token response", http.StatusInternalServerError)
  return
}

// Store tokens in session
credentials := map[string]interface{}{
  "access_token":  result.AccessToken,
  "refresh_token": result.RefreshToken,
  "expires_at":    time.Now().Unix(), // + int64(result.ExpiresIn),
}
HttpClient client = HttpClient.newHttpClient();
String code = "<code from query string>";
String requestBody =
    String.format("grant_type=authorization_code&code=%s", code);

String clientId = System.getenv("SAMSARA_CLIENT_ID");
String clientSecret = System.getenv("SAMSARA_CLIENT_SECRET");
String auth = Base64.getEncoder().encodeToString(
    (clientId + ":" + clientSecret).getBytes());

HttpRequest request =
    HttpRequest.newBuilder()
        .uri(URI.create("https://api.samsara.com/oauth2/token"))
        .header("Content-Type", "application/x-www-form-urlencoded")
        .header("Authorization", "Basic " + auth)
        .POST(HttpRequest.BodyPublishers.ofString(requestBody))
        .build();

HttpResponse<String> response =
    client.send(request, HttpResponse.BodyHandlers.ofString());

if (response.statusCode() != 200) {
  res.status(400);
  return "Failed to exchange code for tokens";
}

JsonObject tokens =
    JsonParser.parseString(response.body()).getAsJsonObject();
String accessToken = tokens.get("access_token").getAsString();
String refreshToken = tokens.get("refresh_token").getAsString();
long expiresIn = tokens.get("expires_in").getAsLong();
long expiresAt = System.currentTimeMillis() / 1000 + expiresIn;
<?php

// Extract code and state from query string
$code = $_GET['code'] ?? null;

// Create authorization header
$auth = $_ENV['SAMSARA_CLIENT_ID'] . ':' . $_ENV['SAMSARA_CLIENT_SECRET'];
$encoded_auth = base64_encode($auth);

// Initialize cURL session
$ch = curl_init();

// Set cURL options for token exchange
curl_setopt($ch, CURLOPT_URL, 'https://api.samsara.com/oauth2/token');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Basic ' . $encoded_auth,
    'Content-Type: application/x-www-form-urlencoded'
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
    'grant_type' => 'authorization_code',
    'code' => $code,
]));

// Execute request
$response = curl_exec($ch);

// Check for cURL errors
if (curl_errno($ch)) {
    die('Error exchanging code for tokens: ' . curl_error($ch));
}

// Close cURL session
curl_close($ch);

// Parse response
$token_data = json_decode($response, true);

// Calculate expires_at timestamp
$expires_at = time() + $token_data['expires_in'];

// Store credentials in session
$credentials = [
    'access_token' => $token_data['access_token'],
    'refresh_token' => $token_data['refresh_token'],
    'expires_at' => $expires_at
];

Step 4: Using the Access Token

Authenticate to the API by providing the access token in the Authorization: Bearer HTTP header.

Authorization: Bearer <access_token>

Access tokens last for 1 hour (3600 seconds), matching the expires_in value you receive. To request a new one see Refresh an Expired Access Token.

As an example, if the token is hXdwQUq3GBKZTvSLyURh, this is how to make a request to the /fleet/vehicles endpoint:

curl -X GET \
  https://api.samsara.com/fleet/vehicles \
  -H 'Authorization: Bearer hXdwQUq3GBKZTvSLyURh'
const accessToken = 'hXdwQUq3GBKZTvSLyURh';

// Using node-fetch
const fetch = (...args) =>
  import("node-fetch").then(({ default: fetch }) => fetch(...args));

fetch('https://api.samsara.com/fleet/vehicles', {
  method: 'GET',
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'Content-Type': 'application/json',
  }
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
access_token = 'hXdwQUq3GBKZTvSLyURh'

headers = {
    'Authorization': f'Bearer {access_token}',
    'Content-Type': 'application/json'
}

response = requests.get('https://api.samsara.com/fleet/vehicles', headers=headers)
response.raise_for_status()
vehicles = response.json()
print(vehicles)
access_token = 'hXdwQUq3GBKZTvSLyURh'

uri = URI('https://api.samsara.com/fleet/vehicles')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true

request = Net::HTTP::Get.new(uri)
request['Authorization'] = "Bearer #{access_token}"

response = http.request(request)
p response
var accessToken = "hXdwQUq3GBKZTvSLyURh";

using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
var response = await client.GetAsync("https://api.samsara.com/fleet/vehicles");
var rawResponse = await response.Content.ReadAsStringAsync();
accessToken := "hXdwQUq3GBKZTvSLyURh"

client := &http.Client{}
req, err := http.NewRequest("GET", "https://api.samsara.com/fleet/vehicles", nil)
if err != nil {
  http.Error(w, "Error creating request", http.StatusInternalServerError)
  return
}

// Add headers
req.Header.Add("Authorization", "Bearer "+accessToken)
req.Header.Add("Content-Type", "application/json")

// Make request
resp, err := client.Do(req)
if err != nil {
  http.Error(w, "Error making request", http.StatusInternalServerError)
  return
}
defer resp.Body.Close()
fmt.Println(resp.Body)
String accessToken = "hXdwQUq3GBKZTvSLyURh";

HttpClient client = HttpClient.newHttpClient();
HttpRequest request =
    HttpRequest.newBuilder()
        .uri(URI.create("https://api.samsara.com/fleet/vehicles"))
        .header("Authorization", "Bearer " + accessToken)
        .GET()
        .build();

HttpResponse<String> response =
    client.send(request, HttpResponse.BodyHandlers.ofString());

if (response.statusCode() != 200) {
  return "API call failed with status code: " + response.statusCode();
}

System.out.println(response.body());
<?php
  
$access_token = 'hXdwQUq3GBKZTvSLyURh';

$ch = curl_init();

// Set cURL options
curl_setopt($ch, CURLOPT_URL, 'https://api.samsara.com/fleet/vehicles');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer ' . $access_token,
    'Accept: application/json'
]);

// Execute request
$response = curl_exec($ch);

// Check for errors
if (curl_errno($ch)) {
    die('Error making API request: ' . curl_error($ch));
}

// Get HTTP status code
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

// Close cURL session
curl_close($ch);

// Set JSON content type header
header('Content-Type: application/json');

// Check if request was successful
if ($http_code !== 200) {
    die(json_encode([
        'error' => 'API request failed',
        'status' => $http_code,
        'response' => $response
    ]));
}

// Output response
echo $response;

Step 5: Refresh an Expired Access Token

After an access token expires, you can request a new one using the refresh_token provided in Step 3. Make a POST request to the /oauth2/token endpoint with the following parameters:

ParameterDescriptionRequired
refresh_tokenThe refresh token provided in Step 3.Yes
grant_typeMust be refresh_token.Yes

🚧

Warning against concurrent OAuth refresh requests

Do not make concurrent requests to the /oauth2/token endpoint to refresh the same API token.

You must only make one request at a time with the same refresh token, otherwise your integration may break. Wait until receiving a response before retrying.

  • refresh tokens may only be used once, then they expire
  • we recommend conservative retry logic: you should receive a response from Samsara in milliseconds, however the upper bound (p99) can be up to 20 seconds due to server load and processing times

Note that you may make concurrent requests to the other Samsara endpoints using the same access token (e.g. to /fleet/drivers or /fleet/vehicles, etc.).

Authorization

To authenticate using HTTP Basic Auth using your OAuth 2.0 client ID and client secret:

  1. Combine the client_id and client_secret with a colon (e.g. "client_id:client_secret")
  2. Base64 encode that string
  3. Add "Basic " followed by the encoded string to the Authorization header

For example, if your credentials are:

  • client_id: my_id
  • client_secret: my_secret

The Authorization header would be: Authorization: Basic bXlfaWQ6bXlfc2VjcmV0

const clientId = process.env.SAMSARA_OAUTH_CLIENT_ID;
const clientSecret = process.env.SAMSARA_OAUTH_CLIENT_SECRET;

// Base64 encoded authorization string
const auth = Buffer.from(`${clientId}:${clientSecret}`).toString("base64");

// Make the request with header `Authorization: Basic ${auth}`
client_id = os.getenv("SAMSARA_OAUTH_CLIENT_ID")
client_secret = os.getenv("SAMSARA_OAUTH_CLIENT_SECRET")

# Base64 encoded authorization string
auth = base64.b64encode(f"{client_id}:{client_secret}".encode()).decode()

# Make the request with header: `Authorization: Basic ${auth}`

# Note: The `requests` library supports Basic Auth by passing a tuple of (client_id, client_secret) to the `auth` parameter
# E.g.  
#   requests.post(
#     'https://api.samsara.com/oauth2/token', 
#     auth=(SAMSARA_CLIENT_ID, SAMSARA_CLIENT_SECRET), 
#     data={'grant_type': 'refresh_token', 'refresh_token': token })

client_id = ENV['SAMSARA_CLIENT_ID']
client_secret = ENV['SAMSARA_CLIENT_SECRET']
auth = Base64.strict_encode64("#{client_id}:#{client_secret}")
var clientId = Environment.GetEnvironmentVariable("SAMSARA_CLIENT_ID");
var clientSecret = Environment.GetEnvironmentVariable("SAMSARA_CLIENT_SECRET");

var auth = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes($"{clientId}:{clientSecret}"));

// using var client = new HttpClient();
// client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", auth);
// Add basic auth header
clientId := os.Getenv("SAMSARA_CLIENT_ID")
clientSecret := os.Getenv("SAMSARA_CLIENT_SECRET")
combined := clientId + ":" + clientSecret
auth := base64.StdEncoding.EncodeToString([]byte(combined))

// req.Header.Add("Authorization", "Basic "+auth)
String clientId = System.getenv("SAMSARA_CLIENT_ID");
String clientSecret = System.getenv("SAMSARA_CLIENT_SECRET");

String combinedSecrets = clientId + ":" + clientSecret;
String auth = Base64.getEncoder().encodeToString(combinedSecrets.getBytes());

// HttpRequest request =
//     HttpRequest.newBuilder()
//        .header("Authorization", "Basic " + auth)
//        .build();
// Create authorization header
$combined = $_ENV['SAMSARA_CLIENT_ID'] . ':' . $_ENV['SAMSARA_CLIENT_SECRET'];
$auth = base64_encode($combined);

// $ch = curl_init();
// curl_setopt($ch, CURLOPT_HTTPHEADER, [
//     'Authorization: Basic ' . $encoded_auth,
// ]);

Body

  • Content-Type: application/x-www-form-urlencoded
ParameterDescriptionRequired
refresh_tokenThe refresh token provided to you in Step 3.Yes
grant_typeMust be refresh_token.Yes

Response

You'll receive a JSON response containing the following:

{
  "access_token": "2YotnFZFEjr1zCsicMWpAA",
  "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA",
  "expires_in": 3599,
  "scope": "admin:read",
  "token_type": "bearer"
}
FieldDescription
access_tokenThe new access token to authenticate with the Samsara API.
refresh_tokenThe new refresh token for the next time the access token expires.
expires_inThe number of seconds before the new access token expires. After the access token expires, you must request a new one using the new refresh token.
scopeadmin:read for read-only apps or admin:write for full-access read/write apps.
token_typeIndicates that the access token is to be used with the Authorization: Bearer HTTP header in calls to the Samsara API.

🚧

Each refresh token can only be used once. Update both your access token and refresh token after each refresh.

Failed Refresh If the new refreshed credentials are not stored and the refresh token was used you can ask the customer to re-authenticate starting at Step 1.

Additional Features

Direct Installation

Users can start the installation flow in two ways:

  1. From your application
  2. From the Samsara dashboard where they are linked to your application (Direct Install URL).

Set a Direct Install URL during app registration to let users start the OAuth flow directly from the Samsara dashboard. This URL should point to where users begin Step 1 of the OAuth process.

Example

A Direct Install URL that redirects to a landing page on your website: https://my-app.com/oauth/start?source=samsara. You can include additional query parameters as needed for your application.

Revoking Access

Users can revoke authorization from your app through the Samsara Apps page in the Samsara dashboard. This action invalidates all access tokens and refresh tokens for that organization. Requests made with invalid tokens will return responses with 400 status code with the following message: "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client."

When a user wants to revoke access to your app, you can make a POST request to the /oauth2/revoke endpoint to invalidate their tokens:

curl -X POST \
  https://api.samsara.com/oauth2/revoke \
  -H 'Authorization: Basic bXlfaWQ6bXlfc2VjcmV0' \
  -d 'token=tGzv3JOkF0XG5Qx2TlKWIA'
const clientId = process.env.SAMSARA_CLIENT_ID;
const clientSecret = process.env.SAMSARA_CLIENT_SECRET;
const refreshToken = 'tGzv3JOkF0XG5Qx2TlKWIA';

const auth = Buffer.from(`${clientId}:${clientSecret}`).toString("base64");

fetch('https://api.samsara.com/oauth2/revoke', {
  method: 'POST',
  headers: {
    'Authorization': `Basic ${auth}`
  },
  body: `token=${refreshToken}`
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
client_id = os.getenv("SAMSARA_CLIENT_ID")
client_secret = os.getenv("SAMSARA_CLIENT_SECRET")
refresh_token = 'tGzv3JOkF0XG5Qx2TlKWIA'

# Within your revoke handler:
response = requests.post(
    "https://api.samsara.com/oauth2/revoke",
    data={
        'token': refresh_token,
    },
    auth=(client_id, client_secret)
)

print(response.json())
client_id = ENV['SAMSARA_CLIENT_ID']
client_secret = ENV['SAMSARA_CLIENT_SECRET']
auth = Base64.strict_encode64("#{client_id}:#{client_secret}")
refresh_token = 'tGzv3JOkF0XG5Qx2TlKWIA'

uri = URI('https://api.samsara.com/oauth2/revoke')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true

request = Net::HTTP::Post.new(uri)
request['Content-Type'] = 'application/x-www-form-urlencoded'
request['Authorization'] = "Basic #{auth}"
request.body = URI.encode_www_form({
  token: refresh_token
})

response = http.request(request)
var clientId = Environment.GetEnvironmentVariable("SAMSARA_CLIENT_ID");
var clientSecret = Environment.GetEnvironmentVariable("SAMSARA_CLIENT_SECRET");
var refreshToken = "tGzv3JOkF0XG5Qx2TlKWIA";

// Call Samsara revoke endpoint
using var client = new HttpClient();
var revokeEndpoint = "https://api.samsara.com/oauth2/revoke";

// Add basic auth header
var auth = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes($"{clientId}:{clientSecret}"));
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", auth);

var revokeRequest = new Dictionary<string, string>
{
    { "token", refreshToken }
};

var revokeResponse = await client.PostAsync(revokeEndpoint, new FormUrlEncodedContent(revokeRequest));
var revokeResponseContent = await revokeResponse.Content.ReadAsStringAsync();

Console.WriteLine(revokeResponseContent);
clientId := os.Getenv("SAMSARA_CLIENT_ID")
clientSecret := os.Getenv("SAMSARA_CLIENT_SECRET")
refreshToken := "tGzv3JOkF0XG5Qx2TlKWIA"

// Create auth header
auth := clientId + ":" + clientSecret
basicAuth := base64.StdEncoding.EncodeToString([]byte(auth))

// Create request with refresh token in body
client := &http.Client{}
data := url.Values{}
data.Set("token", refreshToken)
req, err := http.NewRequest("POST", "https://api.samsara.com/oauth2/revoke", strings.NewReader(data.Encode()))
if err != nil {
  http.Error(w, "Error creating request", http.StatusInternalServerError)
  return
}

// Add headers
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.Header.Add("Authorization", "Basic "+basicAuth)

// Make request
resp, err := client.Do(req)
if err != nil {
  http.Error(w, "Error making request", http.StatusInternalServerError)
  return
}

defer resp.Body.Close()
String clientId = System.getenv("SAMSARA_CLIENT_ID");
String clientSecret = System.getenv("SAMSARA_CLIENT_SECRET");
String refreshToken = "tGzv3JOkF0XG5Qx2TlKWIA";

HttpClient client = HttpClient.newHttpClient();

String auth = Base64.getEncoder().encodeToString(
    (clientId + ":" + clientSecret).getBytes());

// Build request body
String requestBody = "token=" + refreshToken;

// Create request
HttpRequest request =
    HttpRequest.newBuilder()
        .uri(URI.create("https://api.samsara.com/oauth2/revoke"))
        .header("Content-Type", "application/x-www-form-urlencoded")
        .header("Authorization", "Basic " + auth)
        .POST(HttpRequest.BodyPublishers.ofString(requestBody))
        .build();

HttpResponse<String> response =
    client.send(request, HttpResponse.BodyHandlers.ofString());
<?php

$refresh_token = 'tGzv3JOkF0XG5Qx2TlKWIA';

// Create authorization header
$auth = $_ENV['SAMSARA_CLIENT_ID'] . ':' . $_ENV['SAMSARA_CLIENT_SECRET'];
$encoded_auth = base64_encode($auth);

// Initialize cURL session
$ch = curl_init();

// Set cURL options
curl_setopt($ch, CURLOPT_URL, 'https://api.samsara.com/oauth2/revoke');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Basic ' . $encoded_auth,
    'Content-Type: application/x-www-form-urlencoded'
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
    'token' => $refresh_token
]));

// Execute request
$response = curl_exec($ch);

// Get HTTP status code
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

// Check for errors
if (curl_errno($ch)) {
    die('Error revoking token: ' . curl_error($ch));
}

// Close cURL session
curl_close($ch);

Authorization

To authenticate using HTTP Basic Auth using your OAuth 2.0 client ID and client secret:

  1. Combine the client_id and client_secret with a colon (e.g. "client_id:client_secret")
  2. Base64 encode that string
  3. Add "Basic " followed by the encoded string to the Authorization header

For example, if your credentials are:

  • client_id: my_id
  • client_secret: my_secret

The Authorization header would be: Authorization: Basic bXlfaWQ6bXlfc2VjcmV0

const clientId = process.env.SAMSARA_OAUTH_CLIENT_ID;
const clientSecret = process.env.SAMSARA_OAUTH_CLIENT_SECRET;

// Base64 encoded authorization string
const auth = Buffer.from(`${clientId}:${clientSecret}`).toString("base64");

// Make the request with header `Authorization: Basic ${auth}`
client_id = os.getenv("SAMSARA_OAUTH_CLIENT_ID")
client_secret = os.getenv("SAMSARA_OAUTH_CLIENT_SECRET")

# Base64 encoded authorization string
auth = base64.b64encode(f"{client_id}:{client_secret}".encode()).decode()

# Make the request with header: `Authorization: Basic ${auth}`

# Note: The `requests` library supports Basic Auth by passing a tuple of (client_id, client_secret) to the `auth` parameter
# E.g.  
#   requests.post(
#     'https://api.samsara.com/oauth2/token', 
#     auth=(SAMSARA_CLIENT_ID, SAMSARA_CLIENT_SECRET), 
#     data={'grant_type': 'refresh_token', 'refresh_token': token })

client_id = ENV['SAMSARA_CLIENT_ID']
client_secret = ENV['SAMSARA_CLIENT_SECRET']
auth = Base64.strict_encode64("#{client_id}:#{client_secret}")
var clientId = Environment.GetEnvironmentVariable("SAMSARA_CLIENT_ID");
var clientSecret = Environment.GetEnvironmentVariable("SAMSARA_CLIENT_SECRET");

var auth = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes($"{clientId}:{clientSecret}"));

// using var client = new HttpClient();
// client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", auth);
// Add basic auth header
clientId := os.Getenv("SAMSARA_CLIENT_ID")
clientSecret := os.Getenv("SAMSARA_CLIENT_SECRET")
combined := clientId + ":" + clientSecret
auth := base64.StdEncoding.EncodeToString([]byte(combined))

// req.Header.Add("Authorization", "Basic "+auth)
String clientId = System.getenv("SAMSARA_CLIENT_ID");
String clientSecret = System.getenv("SAMSARA_CLIENT_SECRET");

String combinedSecrets = clientId + ":" + clientSecret;
String auth = Base64.getEncoder().encodeToString(combinedSecrets.getBytes());

// HttpRequest request =
//     HttpRequest.newBuilder()
//        .header("Authorization", "Basic " + auth)
//        .build();
// Create authorization header
$combined = $_ENV['SAMSARA_CLIENT_ID'] . ':' . $_ENV['SAMSARA_CLIENT_SECRET'];
$auth = base64_encode($combined);

// $ch = curl_init();
// curl_setopt($ch, CURLOPT_HTTPHEADER, [
//     'Authorization: Basic ' . $encoded_auth,
// ]);

Body

  • Content-Type: application/x-www-form-urlencoded
ParameterDescriptionRequired
tokenThe refresh token for the organization requesting revocation of app authorization. The access token will also be revoked.Yes

Response

You'll receive a 200 HTTP status code if the token is successfully revoked.

Publishing to App Marketplace

After testing your integration with multiple customers and ensuring it works reliably, review the requirements to get your app certified for the Samsara App Marketplace.