MSP License Tracker API
A read-only REST API for your tenant, license, and alert data — for PSA tools and custom integrations. Available on the Pro plan. All responses are JSON; all amounts are in your account currency.
Base URL: https://www.msplicensetracker.com
Authentication
Generate an API key under Settings → API access. The key (prefixed mlt_live_) is shown once — store it securely. Send it as a bearer token on every request:
curl https://www.msplicensetracker.com/api/v1/tenants \ -H "Authorization: Bearer mlt_live_xxxxxxxxxxxxxxxx"
Requests without a valid, non-revoked key receive 401. Keys can be revoked anytime from the same settings page.
Rate limits
There is no hard rate limit today. Please keep automated polling reasonable — once per minute is ample, since tenant data refreshes on a daily sync cycle. Abusive traffic may be throttled without notice.
Endpoints
/api/v1/tenantsAll connected tenants with their license, cost, and leakage summary.
Response — data array items
| Field | Type | Description |
|---|---|---|
| id | string | Tenant ID |
| displayName | string | Tenant name |
| primaryDomain | string | null | Primary domain |
| provider | string | microsoft |
| connectionStatus | string | connected | error | pending | disconnected |
| totalLicenses | number | Total purchased licenses |
| assignedLicenses | number | Assigned licenses |
| unassignedLicenses | number | Unassigned (unused) licenses |
| totalUsers | number | Total users in the tenant |
| inactiveUserCount | number | Licensed users flagged inactive |
| estimatedMonthlyCost | number | Estimated monthly license cost |
| estimatedMonthlyLeakage | number | Estimated monthly waste |
| lastSyncAt | string | null | ISO timestamp of the last sync |
Example response
{
"data": [
{
"id": "tnt_a1b2c3",
"displayName": "Contoso Ltd",
"primaryDomain": "contoso.com",
"provider": "microsoft",
"connectionStatus": "connected",
"totalLicenses": 320,
"assignedLicenses": 298,
"unassignedLicenses": 22,
"totalUsers": 305,
"inactiveUserCount": 14,
"estimatedMonthlyCost": 8420,
"estimatedMonthlyLeakage": 1180,
"lastSyncAt": "2026-05-22T02:14:00.000Z"
}
]
}/api/v1/summaryAccount-wide totals across every connected tenant.
Response — data object
| Field | Type | Description |
|---|---|---|
| tenants | number | Count of connected tenants |
| totalLicenses | number | Total purchased licenses |
| assignedLicenses | number | Total assigned licenses |
| unassignedLicenses | number | Total unassigned licenses |
| inactiveUsers | number | Total licensed users flagged inactive |
| estimatedMonthlyCost | number | Total estimated monthly cost |
| estimatedMonthlyLeakage | number | Total estimated monthly waste |
Example response
{
"data": {
"tenants": 23,
"totalLicenses": 4180,
"assignedLicenses": 3790,
"unassignedLicenses": 390,
"inactiveUsers": 210,
"estimatedMonthlyCost": 112400,
"estimatedMonthlyLeakage": 14820
}
}/api/v1/alertsThe 100 most recent alerts, newest first.
Response — data array items
| Field | Type | Description |
|---|---|---|
| id | string | Alert ID |
| type | string | seat_increase | seat_decrease | inactive_users | renewal_due | … |
| severity | string | critical | warning | info |
| title | string | Short alert title |
| message | string | Full alert detail |
| status | string | unread | read | dismissed | resolved |
| createdAt | string | ISO timestamp |
| tenant | object | null | { id, displayName } of the related tenant |
Example response
{
"data": [
{
"id": "alt_x1y2z3",
"type": "renewal_due",
"severity": "warning",
"title": "Microsoft 365 E3 renews in 14 days — 18 unused seats",
"message": "Microsoft 365 E3 renews on June 5, 2026. 18 of 50 seats …",
"status": "unread",
"createdAt": "2026-05-22T09:00:00.000Z",
"tenant": { "id": "tnt_a1b2c3", "displayName": "Contoso Ltd" }
}
]
}Errors
Non-200 responses return a JSON body of the form { "error": "…" }.
| Status | Meaning |
|---|---|
| 200 | Success. Payload under the data key. |
| 401 | Missing, malformed, revoked, or unknown API key. |
| 403 | Valid key, but the account's plan does not include API access. |
| 5xx | Unexpected server error — retry with backoff. |
Webhooks
Set a webhook URL under Settings → API access to receive an alert.created event whenever an alert fires. The request body:
{
"event": "alert.created",
"alert": {
"id": "alt_x1y2z3",
"type": "renewal_due",
"severity": "warning",
"title": "Microsoft 365 E3 renews in 14 days — 18 unused seats",
"message": "Microsoft 365 E3 renews on June 5, 2026 …",
"tenant": "Contoso Ltd",
"createdAt": "2026-05-22T09:00:00.000Z"
},
"deliveredAt": "2026-05-22T09:00:01.420Z"
}Verifying the signature
Every request carries an X-MLT-Signature header — sha256=<hmac>. Compute an HMAC-SHA256 of the raw request body using your signing secret and compare before trusting the payload:
import { createHmac, timingSafeEqual } from "crypto";
function verify(rawBody, signatureHeader, signingSecret) {
const expected =
"sha256=" + createHmac("sha256", signingSecret).update(rawBody).digest("hex");
const a = Buffer.from(signatureHeader);
const b = Buffer.from(expected);
return a.length === b.length && timingSafeEqual(a, b);
}Need something not covered here? Contact support.