Skip to content

Authenticate API calls⚓︎

All INVERS REST APIs use the OAuth 2.0 protocol for authentication and authorization. This tutorial shows how to obtain an access token, which is required to make calls to the INVERS APIs.

Note: While this guide provides examples of obtaining and using an access token in multiple languages and tools (such as cURL, C#, JavaScript, and Python), in a production environment it is recommended to use established OAuth libraries tailored to your programming language of choice. These libraries are designed to handle the complexities of the OAuth 2.0 protocol, such as token refresh and security best practices. There is usually no need to implement the OAuth flow from scratch, unless you have a specific requirement that cannot be met by existing libraries.

For developers integrating with INVERS REST APIs using OpenID Connect, our OpenID configuration contains all the necessary endpoints and metadata for discovering our OAuth 2.0 and OpenID Connect capabilities. This metadata is useful for configuring OAuth 2.0 clients and can simplify the integration process. You can access our OpenID Connect configuration here: INVERS OpenID Configuration.

The examples in this tutorial demonstrate how to obtain an access token and then make authorized API calls with it, using different tools and programming languages.

Create a client⚓︎

In order to access the INVERS API from your application, you need a client for your application. If you do not have one, learn how to create a client. This only has to be done once for your application.

Authenticate⚓︎

Obtain an access token⚓︎

Let’s assume the following values for your OAuth 2.0 client credentials. Be sure to replace them with your values:

  • client_id: EXFL29#cl1
  • client_secret: eescrt8md3ntefkd…8m

Get an access token with your client’s credentials

Note

Insert your combination of client_id and client_secret, separated by a : character.

curl -X POST \
  'https://api.invers.com/auth/oauth/token?grant_type=client_credentials' \
  -u 'EXFL29#cl1:eescrt8md3ntefkd…8m' \
  -H 'Content-Type: application/json'
using var client = new HttpClient();

var clientId = "EXFL29#cl1";
var clientSecret = "eescrt8md3ntefkd…8m";
var request = new HttpRequestMessage(
    HttpMethod.Post,
    "https://api.invers.com/auth/oauth/token?grant_type=client_credentials"
);
var byteArray = Encoding.ASCII.GetBytes($"{clientId}:{clientSecret}");
request.Headers.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

var response = await client.SendAsync(request);
var content = await response.Content.ReadAsStringAsync();

using var doc = JsonDocument.Parse(content);
var token = doc.RootElement.GetProperty("access_token").GetString();
const client_id = "EXFL29#cl1";
const client_secret = "eescrt8md3ntefkd…8m";
const auth = btoa(client_id + ":" + client_secret);

fetch("https://api.invers.com/auth/oauth/token?grant_type=client_credentials", {
  method: "POST",
  headers: {
    "Authorization": `Basic ${auth}`,
    "Content-Type": "application/json"
  }
})
  .then(res => res.json())
  .then(data => {
    const accessToken = data.access_token;
  });
import base64
import requests

client_id = "EXFL29#cl1"
client_secret = "eescrt8md3ntefkd…8m"

auth = base64.b64encode((client_id + ":" + client_secret).encode('utf-8')).decode("ascii")
headers = {
    "Authorization": f"Basic {auth}",
    "Content-Type": "application/json",
}

response = requests.post(
    "https://api.invers.com/auth/oauth/token?grant_type=client_credentials",
    headers=headers,
)

access_token = response.json().get("access_token")

The response from the authorization endpoint contains the access token.

{
  "access_token" : "eyJraWQiO…(truncated)…M2VhNtYWE3",
  "token_type" : "bearer",
  "expires_in" : 3600
}

The access token can be used for subsequent calls to the REST APIs.

It expires after 60 minutes as indicated by the number of seconds in the expires_in property.

Use access token in API calls⚓︎

Now it is time for the first real API call: Let’s get a list of vehicles in your fleet from the API. Be sure to replace access_token with the value in access_token from the previous step.

Example (API call)

Note

This example simply returns a list of your fleet’s vehicles. Passing the access_token works the same for all resources of the INVERS API.

curl -X GET 'https://api.invers.com/vehicles' \
  -H 'Authorization: Bearer access_token' \
  -H 'Content-Type: application/json'
// use the same client or using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var responseVehicles = await client.GetAsync("https://api-test.invers.com/vehicles");
var vehiclesJson = await responseVehicles.Content.ReadAsStringAsync();
Console.WriteLine(vehiclesJson);
fetch("https://api.invers.com/vehicles", {
  headers: {
    "Authorization": `Bearer ${accessToken}`,
    "Content-Type": "application/json"
  }
})
  .then(res => res.json())
  .then(vehicles => {
    // handle vehicles
  });
import requests

headers = {
    "Authorization": f"Bearer {access_token}",
    "Content-Type": "application/json",
}
response = requests.get("https://api.invers.com/vehicles", headers=headers)
vehicles = response.json()

If the request has been successful, a JSON object is returned along with HTTP status code 200. If there are vehicles in your fleet, the response contains a list of these vehicles.

Use the access token multiple times

Once you have the access token, be sure to use it for any subsequent calls in the next minutes. In other words: Do not get a new access token for every single API call.

It is recommended to wrap your REST API calls in a way that a new access token is automatically fetched at regular intervals — for example, every 15 minutes — even if the current token is still valid.

This ensures that, in case a new token cannot be issued, the existing one remains valid long enough to continue making API requests without interruption.

Complete example: token reuse and refresh every 15 minutes

This example shows how to:

  • Request an access token using the OAuth 2.0 client credentials grant.
  • Reuse the same token for up to 15 minutes.
  • Try to refresh the token after 15 minutes.
  • Fall back to the previous token if the refresh attempt fails, as long as the previous token is still valid.
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;

// Simple demo client that:
// - keeps the current access token in static fields
// - refreshes the token when it is older than 15 minutes
// - falls back to the previous token if refresh fails
public static class InversApiClient
{
    private const string ClientId = "EXFL29#cl1";
    private const string ClientSecret = "eescrt8md3ntefkd…8m";
    private const string BaseUri = "https://api.invers.com";

    private static readonly HttpClient HttpClient = new();

    private static string? _accessToken;
    private static DateTime _tokenCreatedAt;

    private const string ClientCredentials = $"{ClientId}:{ClientSecret}";
    private const string TokenUrl = $"{BaseUri}/auth/oauth/token?grant_type=client_credentials";
    private const string VehiclesUrl = $"{BaseUri}/vehicles";
    private static readonly TimeSpan TokenMaxAge = TimeSpan.FromMinutes(15);

    // Example method that calls the /vehicles endpoint.
    // It automatically handles token creation, reuse, and refresh.
    public static async Task Main()
    {
        await EnsureTokenAsync();

        using var request = new HttpRequestMessage(HttpMethod.Get, VehiclesUrl);
        request.Headers.Authorization =
            new AuthenticationHeaderValue("Bearer", _accessToken);
        request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

        using var response = await HttpClient.SendAsync(request);
        response.EnsureSuccessStatusCode();

        var vehicles = await response.Content.ReadAsStringAsync();
        Console.WriteLine(vehicles);
    }

    // Ensures that _accessToken is present and not older than 15 minutes.
    // If the token is too old, it tries to fetch a new one.
    // If fetching a new token fails, it falls back to the old token.
    private static async Task EnsureTokenAsync()
    {
        bool needsRefresh = _accessToken == null ||
                            DateTime.UtcNow - _tokenCreatedAt > TokenMaxAge;

        if (!needsRefresh)
        {
            return;
        }

        var oldToken = _accessToken;
        var oldCreatedAt = _tokenCreatedAt;

        try
        {
            var byteArray = Encoding.ASCII.GetBytes(ClientCredentials);

            using var request = new HttpRequestMessage(HttpMethod.Post, TokenUrl);
            request.Headers.Authorization =
                new AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
            request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

            using var response = await HttpClient.SendAsync(request);
            response.EnsureSuccessStatusCode();

            var json = await response.Content.ReadAsStringAsync();
            using var doc = JsonDocument.Parse(json);

            _accessToken = doc.RootElement.GetProperty("access_token").GetString();
            _tokenCreatedAt = DateTime.UtcNow;
        }
        catch
        {
            // If we cannot obtain a new token, keep using the previous one
            _accessToken = oldToken;
            _tokenCreatedAt = oldCreatedAt;
        }
    }
}
// Simple demo client that:
// - stores the current access token and its creation time in module-level variables
// - refreshes the token when it is older than 15 minutes
// - falls back to the previous token if refresh fails

const BASE_URL = "https://api.invers.com";
const TOKEN_URL = BASE_URL + "/auth/oauth/token?grant_type=client_credentials";
const VEHICLES_URL = BASE_URL + "/vehicles";
const CLIENT_CREDENTIALS = "EXFL29#cl1:eescrt8md3ntefkd…8m";
const TOKEN_MAX_AGE_MS = 15 * 60 * 1000; // 15 minutes

let accessToken = null;
let tokenCreatedAt = 0;

async function fetchNewToken() {
  const auth = btoa(CLIENT_CREDENTIALS);

  const response = await fetch(TOKEN_URL, {
    method: "POST",
    headers: {
      "Authorization": `Basic ${auth}`,
      "Content-Type": "application/json"
    }
  });

  if (!response.ok) {
    throw new Error("Failed to fetch access token");
  }

  const data = await response.json();
  accessToken = data.access_token;
  tokenCreatedAt = Date.now();
}

// Ensures that accessToken exists and is not older than 15 minutes.
// If the token is too old, it tries to refresh it.
// If refreshing fails, the previous token is kept.
async function ensureToken() {
  const hasToken = !!accessToken;
  const isTooOld = !hasToken || (Date.now() - tokenCreatedAt) > TOKEN_MAX_AGE_MS;

  if (!isTooOld) {
    return;
  }

  const oldToken = accessToken;
  const oldCreatedAt = tokenCreatedAt;

  try {
    await fetchNewToken();
  } catch (error) {
    // If refresh fails, continue using the old token (if any)
    accessToken = oldToken;
    tokenCreatedAt = oldCreatedAt;
  }
}

// Example function that calls the /vehicles endpoint.
// It automatically handles token creation, reuse, and refresh.
export async function getVehicles() {
  await ensureToken();

  const response = await fetch(VEHICLES_URL, {
    method: "GET",
    headers: {
      "Authorization": `Bearer ${accessToken}`,
      "Content-Type": "application/json"
    }
  });

  if (!response.ok) {
    throw new Error("Failed to fetch vehicles");
  }

  return await response.json();
}

await getVehicles().then(vehicles => {
  console.log("Vehicles:", vehicles);
});
"""
Simple demo client that:
- keeps the current access token in module-level variables
- refreshes the token when it is older than 15 minutes
- falls back to the previous token if refresh fails
"""

import base64
import time
import requests

BASE_URL = "https://api.invers.com"
TOKEN_URL = BASE_URL + "/auth/oauth/token?grant_type=client_credentials"
VEHICLES_URL = BASE_URL + "/vehicles"
CLIENT_CREDENTIALS = "EXFL29#cl1:eescrt8md3ntefkd…8m"
TOKEN_MAX_AGE_SECONDS = 15 * 60  # 15 minutes

_access_token: str | None = None
_token_created_at: float = 0.0

def _fetch_new_token() -> None:
    """Request a new access token via client_credentials."""
    global _access_token, _token_created_at

    auth = base64.b64encode(CLIENT_CREDENTIALS.encode("ascii")).decode("ascii")
    headers = {
        "Authorization": f"Basic {auth}",
        "Content-Type": "application/json",
    }

    response = requests.post(TOKEN_URL, headers=headers)
    response.raise_for_status()

    data = response.json()
    _access_token = data.get("access_token")
    _token_created_at = time.time()

def _ensure_token() -> None:
    """
    Ensure that _access_token exists and is not older than 15 minutes.

    If the token is too old, try to refresh it.
    If refreshing fails, keep using the previous token.
    """
    global _access_token, _token_created_at

    has_token = _access_token is not None
    is_too_old = (not has_token) or (
        time.time() - _token_created_at > TOKEN_MAX_AGE_SECONDS
    )

    if not is_too_old:
        return

    old_token = _access_token
    old_created_at = _token_created_at

    try:
        _fetch_new_token()
    except Exception:
        # If refresh fails, continue using the old token (if any)
        _access_token = old_token
        _token_created_at = old_created_at

def get_vehicles() -> dict:
    """
    Call the /vehicles endpoint.

    This function automatically handles token creation, reuse,
    and refresh before performing the request.
    """
    _ensure_token()

    headers = {
        "Authorization": f"Bearer {_access_token}",
        "Content-Type": "application/json",
    }

    response = requests.get(VEHICLES_URL, headers=headers)
    response.raise_for_status()

    return response.json()

if __name__ == "__main__":
  vehicles = get_vehicles()
  print("Vehicles:", vehicles)