<!-- Device Inventory Manager — https://wiki.cyberguard.srl/dim/api/authentication/ -->

# Authentication

The API uses **OAuth 2.0 client credentials**, the same standard used by Microsoft Graph for app-only access.

Two steps:

1. Exchange the **Client ID** and **Client secret** for an **access token**.
2. Send the token in every request, in the `Authorization` header.

The token is valid for **60 minutes**. Reuse it until it expires, then request a new one.

---

## Get a token

```text
POST https://inventory-api.cyberguard.srl/connect/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&client_id=<Client ID>&client_secret=<Client secret>
```

Optional: add `scope=devices.read compliance.read` to request only some of the client's scopes.

Response:

```json
{
  "access_token": "eyJhbGciOi...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "devices.read compliance.read",
  "api_base_url": "https://inventory-api.cyberguard.srl"
}
```

The client credentials can also be sent as HTTP Basic authentication (`Authorization: Basic base64(client_id:client_secret)`).

> Use the API address shown in the **API** page of the portal. The example above is the address for customers whose data is hosted in Italy.

---

## Call the API

```text
GET https://inventory-api.cyberguard.srl/v1/devices
Authorization: Bearer <access_token>
```

---

## Examples

### PowerShell

```powershell
$base   = "https://inventory-api.cyberguard.srl"
$token  = Invoke-RestMethod -Method Post -Uri "$base/connect/token" -Body @{
    grant_type    = "client_credentials"
    client_id     = "<Client ID>"
    client_secret = "<Client secret>"
}
$headers = @{ Authorization = "Bearer $($token.access_token)" }

# All devices, page by page
$devices = @()
$cursor  = $null
do {
    $uri  = "$base/v1/devices?pageSize=500" + $(if ($cursor) { "&cursor=$cursor" })
    $page = Invoke-RestMethod -Uri $uri -Headers $headers
    $devices += $page.data
    $cursor   = $page.nextCursor
} while ($cursor)

$devices | Select-Object deviceName, serialNumber, complianceState | Format-Table
```

### Python

```python
import requests

base = "https://inventory-api.cyberguard.srl"
token = requests.post(f"{base}/connect/token", data={
    "grant_type": "client_credentials",
    "client_id": "<Client ID>",
    "client_secret": "<Client secret>",
}).json()["access_token"]
headers = {"Authorization": f"Bearer {token}"}

devices, cursor = [], None
while True:
    params = {"pageSize": 500}
    if cursor:
        params["cursor"] = cursor
    page = requests.get(f"{base}/v1/devices", headers=headers, params=params).json()
    devices.extend(page["data"])
    cursor = page["nextCursor"]
    if not cursor:
        break

print(len(devices), "devices")
```

---

## Token errors

| Status | `error` | Meaning |
|--------|---------|---------|
| `400` | `unsupported_grant_type` | Use `grant_type=client_credentials` |
| `400` | `invalid_scope` | A requested scope is not granted to the client or not included in your license |
| `400` | `unauthorized_client` | The API add-on is not active or has expired |
| `401` | `invalid_client` | Wrong Client ID or secret, or the client or secret has been revoked or has expired |
| `429` | `slow_down` | Too many tokens requested (max 20 per hour per client), or too many failed attempts from the same IP address |

Failed attempts for an existing client are recorded in your **Audit log** with the IP address.

---

## Keep the secret safe

- Store the secret in a secrets vault or as a protected parameter, not in plain text in scripts or shared files.
- Create a separate client for each system. You can revoke one without affecting the others.
- Renew secrets before they expire (12 months) and revoke the previous one when the system uses the new one.
