# SaaS Seat Usage Evidence Review (`h_murdock/saas-seat-usage-evidence`) Actor

Review supplied SaaS license exports for offboarded accounts, conflicting assignments and inactivity supported by explicit observation coverage. App-scoped opaque IDs, source-linked JSON/CSV/HTML. No account access or deprovisioning.

- **URL**: https://apify.com/h_murdock/saas-seat-usage-evidence.md
- **Developed by:** [Gilad Ronen](https://apify.com/h_murdock) (community)
- **Categories:** Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.25 / completed report

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## SaaS Seat Usage Evidence Review

Turn normalized SaaS license exports into a review queue with source references. Compare assigned seats with app-scoped account roster, observed usage and an explicit observation window. Download a complete JSON report, a flat seat evidence CSV, a findings CSV and a standalone HTML report.

Use this for monthly license reviews, offboarding reconciliation and renewal preparation across several applications. You prepare exports you are allowed to process; the Actor requires no Microsoft, Adobe, Zendesk or other account connection. It does not connect to your apps, change access, estimate savings or make employment decisions.

### What the report distinguishes

- **Offboarded seat:** the supplied roster says an account is offboarded while an exported seat remains assigned. Verify both snapshots before making an access change.
- **Conflicting or duplicate evidence:** repeated app/seat assignments and conflicting roster, usage or coverage declarations remain visible. The Actor does not choose a convenient winner or merge conflicting accounts.
- **Candidate inactivity:** a known active account has an old enough assignment, full declared observation coverage and either sufficiently old observed activity or an explicit `none-observed` declaration. This is a candidate for human review.
- **Unknown usage:** a missing record or `unknown` state stays unknown. A blank last-activity cell alone never means unused.
- **Recent assignment or incomplete coverage:** delayed reports, narrow windows and newly assigned seats cannot establish a full inactivity period.

Counts refer to supplied assignment records, not unique people. Multiple seats for one app/account are flagged for review; they may be legitimate licenses. No license compliance or optimization certificate is issued.

### Prepare input

Set `reviewId`, `asOf` and `inactivityDays` explicitly. All dates use `YYYY-MM-DD` UTC calendar dates from 1970 onward. Activity age is the difference in UTC calendar days from `asOf`; equality with the inactivity threshold qualifies for review when every other condition is supported.

Supply arrays with these exact fields:

| Table | Fields |
| --- | --- |
| `assignments` | `appId`, `seatId`, `accountId`, `assignedAt` |
| `roster` | `appId`, `accountId`, `status` (`active` or `offboarded`), `offboardedAt` (date or null) |
| `activity` | `appId`, `accountId`, `state` (`observed`, `none-observed`, `unknown`), `lastActivityAt` (date or null) |
| `coverage` | `appId`, `from`, `through`, `complete` (boolean) |

Every assignment represents a currently assigned seat in your supplied snapshot. `assignments` or `assignmentsCsv` is required; an empty array explicitly declares no assigned seats. Other missing tables or unmatched exact keys leave that evidence unknown.

Use opaque identifiers only. Employee names, emails and extra object fields are rejected. IDs accept 1–96 ASCII letters, digits, dot, underscore, colon, slash and hyphen, starting with a letter or digit. Leading zeros and case are preserved. `appId/accountId` is an exact pair: an account in one application is not joined to the same-looking account in another. Map identities yourself before uploading; no aliases or case folding are inferred.

`observed` requires a last-activity date. `unknown` and `none-observed` require null. Select `none-observed` only when your actual export explicitly establishes no recorded activity in that covered period. `complete: true` asserts that observations include every relevant assigned account and expected activity type for the app during that window. To support a candidate, the window must start no later than the inactivity cutoff and end on `asOf`. Report delays must be reflected in `through`; do not mark delayed observations complete through a later date. Assignment must also be no later than the cutoff. Future dates, reversed windows and incompatible state/date combinations are rejected.

### Mapped CSV alternative

Each table supports an alternative object named `assignmentsCsv`, `rosterCsv`, `activityCsv` or `coverageCsv`:

```json
{
  "csv": "Application,Seat,Account,Assigned\napp-1,001,acct-1,2026-08-01\n",
  "mapping": {
    "appId": "Application",
    "seatId": "Seat",
    "accountId": "Account",
    "assignedAt": "Assigned"
  }
}
```

Remove that table's prefilled array before supplying CSV. Do not supply both representations, even an empty array. Map every canonical field, remove other columns and retain exact headers. BOM, quoted cells and CRLF are accepted. Duplicate headers, duplicate mappings and irregular rows fail. Optional dates use blank CSV cells; coverage booleans use literal `true` or `false`. JSON array dates use null, never an empty string.

### Results and repeat runs

The default dataset contains one complete report with summary, scope, rules, evidence rows and findings. Source references point to original array indices or mapped CSV data rows, with header counted as row 1. Open the original input to inspect conflicting values. CSV downloads neutralize formula-like cells; HTML escapes supplied content and uses no external scripts or resources. Download HTML and open locally.

The prefilled example includes supported candidates, an offboarded seat, unknown activity, a recent assignment, a conflicting seat and separate app-scoped usage. Replace all example records with your normalized batch before using the results operationally.

Price is **$0.25 per completed report**, with platform usage included. There is no startup or per-row fee. A new run is billable again. Same-run recovery reuses the saved report after verifying input and integrity, restores exports and avoids a second dataset item or report event. Interrupted export writes can leave partial downloads until recovery; they are not billed as a completed report. Apify input and output storage contain your supplied data; use only authorized opaque exports and retain/delete run storage according to your policy.

Input is limited to 4 MB JSON and 5,000 combined records across all tables. Full JSON must stay below 8 MB; every export must stay below 9 MB. Exception-heavy batches can reach output limits before the row limit; split them. Limits are checked before a report event is charged. Default execution is 1024 MB and 180 seconds. No external models or network data fetches are used.

# Actor input Schema

## `reviewId` (type: `string`):

Literal opaque batch ID, 1–96 ASCII letters, digits, dot, underscore, colon, slash or hyphen. IDs are never trimmed or case folded. No employee names or emails.

## `asOf` (type: `string`):

Explicit reference date YYYY-MM-DD. All dates use UTC calendar days; future dates and impossible windows are rejected.

## `inactivityDays` (type: `integer`):

Explicit review period measured back from asOf. A candidate needs complete observations through asOf, an assignment no later than the cutoff, and explicit old activity or none-observed evidence.

## `assignments` (type: `array`):

Assigned seat rows: appId, seatId, accountId, assignedAt (YYYY-MM-DD). Required at runtime as this array or assignmentsCsv; \[] means no assigned seats. All rows represent currently assigned seats in the supplied snapshot. Use only an array or its mapped CSV counterpart. Opaque exact IDs only, no aliases. <=5,000 combined rows and <=4 MB total input.

## `assignmentsCsv` (type: `object`):

Alternative to array. Object {csv: "header...", mapping: {canonicalField: "source header", ...}}. Mapping must name every canonical field; headers must exactly match mapped columns. BOM/quotes accepted; duplicate headers, irregular rows or extra columns rejected. Empty optional dates use blank CSV cell. complete uses literal true/false. Remove the corresponding prefilled array before using CSV. Assigned seat rows: appId, seatId, accountId, assignedAt (YYYY-MM-DD). Required at runtime as this array or assignmentsCsv; \[] means no assigned seats. All rows represent currently assigned seats in the supplied snapshot.

## `roster` (type: `array`):

App-scoped rows: appId, accountId, status active/offboarded, offboardedAt date or null. Active requires null; offboarded may have null if date unknown. Missing table or missing exact app/account key means unknown roster. Use only an array or its mapped CSV counterpart. Opaque exact IDs only, no aliases. <=5,000 combined rows and <=4 MB total input.

## `rosterCsv` (type: `object`):

Alternative to array. Object {csv: "header...", mapping: {canonicalField: "source header", ...}}. Mapping must name every canonical field; headers must exactly match mapped columns. BOM/quotes accepted; duplicate headers, irregular rows or extra columns rejected. Empty optional dates use blank CSV cell. complete uses literal true/false. Remove the corresponding prefilled array before using CSV. App-scoped rows: appId, accountId, status active/offboarded, offboardedAt date or null. Active requires null; offboarded may have null if date unknown. Missing table or missing exact app/account key means unknown roster.

## `activity` (type: `array`):

App-scoped rows: appId, accountId, state observed/none-observed/unknown, lastActivityAt date or null. observed requires a date; other states require null. A blank or missing activity date alone is never evidence of unused. none-observed is an explicit declaration of no activity in the supplied covered period, not a default. Use only an array or its mapped CSV counterpart. Opaque exact IDs only, no aliases. <=5,000 combined rows and <=4 MB total input.

## `activityCsv` (type: `object`):

Alternative to array. Object {csv: "header...", mapping: {canonicalField: "source header", ...}}. Mapping must name every canonical field; headers must exactly match mapped columns. BOM/quotes accepted; duplicate headers, irregular rows or extra columns rejected. Empty optional dates use blank CSV cell. complete uses literal true/false. Remove the corresponding prefilled array before using CSV. App-scoped rows: appId, accountId, state observed/none-observed/unknown, lastActivityAt date or null. observed requires a date; other states require null. A blank or missing activity date alone is never evidence of unused. none-observed is an explicit declaration of no activity in the supplied covered period, not a default.

## `coverage` (type: `array`):

App observation declarations: appId, from, through (YYYY-MM-DD), complete boolean. complete=true asserts observations cover all relevant assigned accounts and expected activity types in this app for the declared window. Missing, delayed, incomplete or conflicting coverage cannot support inactivity. from <= through <= asOf. Use only an array or its mapped CSV counterpart. Opaque exact IDs only, no aliases. <=5,000 combined rows and <=4 MB total input.

## `coverageCsv` (type: `object`):

Alternative to array. Object {csv: "header...", mapping: {canonicalField: "source header", ...}}. Mapping must name every canonical field; headers must exactly match mapped columns. BOM/quotes accepted; duplicate headers, irregular rows or extra columns rejected. Empty optional dates use blank CSV cell. complete uses literal true/false. Remove the corresponding prefilled array before using CSV. App observation declarations: appId, from, through (YYYY-MM-DD), complete boolean. complete=true asserts observations cover all relevant assigned accounts and expected activity types in this app for the declared window. Missing, delayed, incomplete or conflicting coverage cannot support inactivity. from <= through <= asOf.

## Actor input object example

```json
{
  "reviewId": "renewal-2026-10",
  "asOf": "2026-10-02",
  "inactivityDays": 30,
  "assignments": [
    {
      "appId": "app-alpha",
      "seatId": "001",
      "accountId": "acct-001",
      "assignedAt": "2026-07-01"
    },
    {
      "appId": "app-alpha",
      "seatId": "002",
      "accountId": "acct-002",
      "assignedAt": "2026-07-01"
    },
    {
      "appId": "app-alpha",
      "seatId": "003",
      "accountId": "acct-003",
      "assignedAt": "2026-07-01"
    },
    {
      "appId": "app-alpha",
      "seatId": "004",
      "accountId": "acct-004",
      "assignedAt": "2026-09-25"
    },
    {
      "appId": "app-alpha",
      "seatId": "005",
      "accountId": "acct-005",
      "assignedAt": "2026-07-01"
    },
    {
      "appId": "app-alpha",
      "seatId": "006",
      "accountId": "acct-006",
      "assignedAt": "2026-07-01"
    },
    {
      "appId": "app-alpha",
      "seatId": "006",
      "accountId": "acct-007",
      "assignedAt": "2026-07-01"
    },
    {
      "appId": "app-beta",
      "seatId": "001",
      "accountId": "acct-001",
      "assignedAt": "2026-07-01"
    }
  ],
  "roster": [
    {
      "appId": "app-alpha",
      "accountId": "acct-001",
      "status": "active",
      "offboardedAt": null
    },
    {
      "appId": "app-alpha",
      "accountId": "acct-002",
      "status": "offboarded",
      "offboardedAt": "2026-09-15"
    },
    {
      "appId": "app-alpha",
      "accountId": "acct-003",
      "status": "active",
      "offboardedAt": null
    },
    {
      "appId": "app-alpha",
      "accountId": "acct-004",
      "status": "active",
      "offboardedAt": null
    },
    {
      "appId": "app-alpha",
      "accountId": "acct-005",
      "status": "active",
      "offboardedAt": null
    },
    {
      "appId": "app-alpha",
      "accountId": "acct-006",
      "status": "active",
      "offboardedAt": null
    },
    {
      "appId": "app-alpha",
      "accountId": "acct-007",
      "status": "active",
      "offboardedAt": null
    },
    {
      "appId": "app-beta",
      "accountId": "acct-001",
      "status": "active",
      "offboardedAt": null
    }
  ],
  "activity": [
    {
      "appId": "app-alpha",
      "accountId": "acct-001",
      "state": "observed",
      "lastActivityAt": "2026-08-10"
    },
    {
      "appId": "app-alpha",
      "accountId": "acct-002",
      "state": "observed",
      "lastActivityAt": "2026-09-10"
    },
    {
      "appId": "app-alpha",
      "accountId": "acct-003",
      "state": "unknown",
      "lastActivityAt": null
    },
    {
      "appId": "app-alpha",
      "accountId": "acct-004",
      "state": "none-observed",
      "lastActivityAt": null
    },
    {
      "appId": "app-alpha",
      "accountId": "acct-005",
      "state": "none-observed",
      "lastActivityAt": null
    },
    {
      "appId": "app-alpha",
      "accountId": "acct-006",
      "state": "observed",
      "lastActivityAt": "2026-08-15"
    },
    {
      "appId": "app-alpha",
      "accountId": "acct-007",
      "state": "observed",
      "lastActivityAt": "2026-08-15"
    },
    {
      "appId": "app-beta",
      "accountId": "acct-001",
      "state": "observed",
      "lastActivityAt": "2026-10-01"
    }
  ],
  "coverage": [
    {
      "appId": "app-alpha",
      "from": "2026-08-01",
      "through": "2026-10-02",
      "complete": true
    },
    {
      "appId": "app-beta",
      "from": "2026-09-01",
      "through": "2026-10-01",
      "complete": false
    }
  ]
}
```

# Actor output Schema

## `dataset` (type: `string`):

One complete report item with summary, source-linked rows, rules and findings.

## `json` (type: `string`):

No description

## `evidence` (type: `string`):

No description

## `findings` (type: `string`):

No description

## `html` (type: `string`):

Download and open locally. Escaped, no scripts or external resources.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "reviewId": "renewal-2026-10",
    "asOf": "2026-10-02",
    "inactivityDays": 30,
    "assignments": [
        {
            "appId": "app-alpha",
            "seatId": "001",
            "accountId": "acct-001",
            "assignedAt": "2026-07-01"
        },
        {
            "appId": "app-alpha",
            "seatId": "002",
            "accountId": "acct-002",
            "assignedAt": "2026-07-01"
        },
        {
            "appId": "app-alpha",
            "seatId": "003",
            "accountId": "acct-003",
            "assignedAt": "2026-07-01"
        },
        {
            "appId": "app-alpha",
            "seatId": "004",
            "accountId": "acct-004",
            "assignedAt": "2026-09-25"
        },
        {
            "appId": "app-alpha",
            "seatId": "005",
            "accountId": "acct-005",
            "assignedAt": "2026-07-01"
        },
        {
            "appId": "app-alpha",
            "seatId": "006",
            "accountId": "acct-006",
            "assignedAt": "2026-07-01"
        },
        {
            "appId": "app-alpha",
            "seatId": "006",
            "accountId": "acct-007",
            "assignedAt": "2026-07-01"
        },
        {
            "appId": "app-beta",
            "seatId": "001",
            "accountId": "acct-001",
            "assignedAt": "2026-07-01"
        }
    ],
    "roster": [
        {
            "appId": "app-alpha",
            "accountId": "acct-001",
            "status": "active",
            "offboardedAt": null
        },
        {
            "appId": "app-alpha",
            "accountId": "acct-002",
            "status": "offboarded",
            "offboardedAt": "2026-09-15"
        },
        {
            "appId": "app-alpha",
            "accountId": "acct-003",
            "status": "active",
            "offboardedAt": null
        },
        {
            "appId": "app-alpha",
            "accountId": "acct-004",
            "status": "active",
            "offboardedAt": null
        },
        {
            "appId": "app-alpha",
            "accountId": "acct-005",
            "status": "active",
            "offboardedAt": null
        },
        {
            "appId": "app-alpha",
            "accountId": "acct-006",
            "status": "active",
            "offboardedAt": null
        },
        {
            "appId": "app-alpha",
            "accountId": "acct-007",
            "status": "active",
            "offboardedAt": null
        },
        {
            "appId": "app-beta",
            "accountId": "acct-001",
            "status": "active",
            "offboardedAt": null
        }
    ],
    "activity": [
        {
            "appId": "app-alpha",
            "accountId": "acct-001",
            "state": "observed",
            "lastActivityAt": "2026-08-10"
        },
        {
            "appId": "app-alpha",
            "accountId": "acct-002",
            "state": "observed",
            "lastActivityAt": "2026-09-10"
        },
        {
            "appId": "app-alpha",
            "accountId": "acct-003",
            "state": "unknown",
            "lastActivityAt": null
        },
        {
            "appId": "app-alpha",
            "accountId": "acct-004",
            "state": "none-observed",
            "lastActivityAt": null
        },
        {
            "appId": "app-alpha",
            "accountId": "acct-005",
            "state": "none-observed",
            "lastActivityAt": null
        },
        {
            "appId": "app-alpha",
            "accountId": "acct-006",
            "state": "observed",
            "lastActivityAt": "2026-08-15"
        },
        {
            "appId": "app-alpha",
            "accountId": "acct-007",
            "state": "observed",
            "lastActivityAt": "2026-08-15"
        },
        {
            "appId": "app-beta",
            "accountId": "acct-001",
            "state": "observed",
            "lastActivityAt": "2026-10-01"
        }
    ],
    "coverage": [
        {
            "appId": "app-alpha",
            "from": "2026-08-01",
            "through": "2026-10-02",
            "complete": true
        },
        {
            "appId": "app-beta",
            "from": "2026-09-01",
            "through": "2026-10-01",
            "complete": false
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("h_murdock/saas-seat-usage-evidence").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "reviewId": "renewal-2026-10",
    "asOf": "2026-10-02",
    "inactivityDays": 30,
    "assignments": [
        {
            "appId": "app-alpha",
            "seatId": "001",
            "accountId": "acct-001",
            "assignedAt": "2026-07-01",
        },
        {
            "appId": "app-alpha",
            "seatId": "002",
            "accountId": "acct-002",
            "assignedAt": "2026-07-01",
        },
        {
            "appId": "app-alpha",
            "seatId": "003",
            "accountId": "acct-003",
            "assignedAt": "2026-07-01",
        },
        {
            "appId": "app-alpha",
            "seatId": "004",
            "accountId": "acct-004",
            "assignedAt": "2026-09-25",
        },
        {
            "appId": "app-alpha",
            "seatId": "005",
            "accountId": "acct-005",
            "assignedAt": "2026-07-01",
        },
        {
            "appId": "app-alpha",
            "seatId": "006",
            "accountId": "acct-006",
            "assignedAt": "2026-07-01",
        },
        {
            "appId": "app-alpha",
            "seatId": "006",
            "accountId": "acct-007",
            "assignedAt": "2026-07-01",
        },
        {
            "appId": "app-beta",
            "seatId": "001",
            "accountId": "acct-001",
            "assignedAt": "2026-07-01",
        },
    ],
    "roster": [
        {
            "appId": "app-alpha",
            "accountId": "acct-001",
            "status": "active",
            "offboardedAt": None,
        },
        {
            "appId": "app-alpha",
            "accountId": "acct-002",
            "status": "offboarded",
            "offboardedAt": "2026-09-15",
        },
        {
            "appId": "app-alpha",
            "accountId": "acct-003",
            "status": "active",
            "offboardedAt": None,
        },
        {
            "appId": "app-alpha",
            "accountId": "acct-004",
            "status": "active",
            "offboardedAt": None,
        },
        {
            "appId": "app-alpha",
            "accountId": "acct-005",
            "status": "active",
            "offboardedAt": None,
        },
        {
            "appId": "app-alpha",
            "accountId": "acct-006",
            "status": "active",
            "offboardedAt": None,
        },
        {
            "appId": "app-alpha",
            "accountId": "acct-007",
            "status": "active",
            "offboardedAt": None,
        },
        {
            "appId": "app-beta",
            "accountId": "acct-001",
            "status": "active",
            "offboardedAt": None,
        },
    ],
    "activity": [
        {
            "appId": "app-alpha",
            "accountId": "acct-001",
            "state": "observed",
            "lastActivityAt": "2026-08-10",
        },
        {
            "appId": "app-alpha",
            "accountId": "acct-002",
            "state": "observed",
            "lastActivityAt": "2026-09-10",
        },
        {
            "appId": "app-alpha",
            "accountId": "acct-003",
            "state": "unknown",
            "lastActivityAt": None,
        },
        {
            "appId": "app-alpha",
            "accountId": "acct-004",
            "state": "none-observed",
            "lastActivityAt": None,
        },
        {
            "appId": "app-alpha",
            "accountId": "acct-005",
            "state": "none-observed",
            "lastActivityAt": None,
        },
        {
            "appId": "app-alpha",
            "accountId": "acct-006",
            "state": "observed",
            "lastActivityAt": "2026-08-15",
        },
        {
            "appId": "app-alpha",
            "accountId": "acct-007",
            "state": "observed",
            "lastActivityAt": "2026-08-15",
        },
        {
            "appId": "app-beta",
            "accountId": "acct-001",
            "state": "observed",
            "lastActivityAt": "2026-10-01",
        },
    ],
    "coverage": [
        {
            "appId": "app-alpha",
            "from": "2026-08-01",
            "through": "2026-10-02",
            "complete": True,
        },
        {
            "appId": "app-beta",
            "from": "2026-09-01",
            "through": "2026-10-01",
            "complete": False,
        },
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("h_murdock/saas-seat-usage-evidence").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "reviewId": "renewal-2026-10",
  "asOf": "2026-10-02",
  "inactivityDays": 30,
  "assignments": [
    {
      "appId": "app-alpha",
      "seatId": "001",
      "accountId": "acct-001",
      "assignedAt": "2026-07-01"
    },
    {
      "appId": "app-alpha",
      "seatId": "002",
      "accountId": "acct-002",
      "assignedAt": "2026-07-01"
    },
    {
      "appId": "app-alpha",
      "seatId": "003",
      "accountId": "acct-003",
      "assignedAt": "2026-07-01"
    },
    {
      "appId": "app-alpha",
      "seatId": "004",
      "accountId": "acct-004",
      "assignedAt": "2026-09-25"
    },
    {
      "appId": "app-alpha",
      "seatId": "005",
      "accountId": "acct-005",
      "assignedAt": "2026-07-01"
    },
    {
      "appId": "app-alpha",
      "seatId": "006",
      "accountId": "acct-006",
      "assignedAt": "2026-07-01"
    },
    {
      "appId": "app-alpha",
      "seatId": "006",
      "accountId": "acct-007",
      "assignedAt": "2026-07-01"
    },
    {
      "appId": "app-beta",
      "seatId": "001",
      "accountId": "acct-001",
      "assignedAt": "2026-07-01"
    }
  ],
  "roster": [
    {
      "appId": "app-alpha",
      "accountId": "acct-001",
      "status": "active",
      "offboardedAt": null
    },
    {
      "appId": "app-alpha",
      "accountId": "acct-002",
      "status": "offboarded",
      "offboardedAt": "2026-09-15"
    },
    {
      "appId": "app-alpha",
      "accountId": "acct-003",
      "status": "active",
      "offboardedAt": null
    },
    {
      "appId": "app-alpha",
      "accountId": "acct-004",
      "status": "active",
      "offboardedAt": null
    },
    {
      "appId": "app-alpha",
      "accountId": "acct-005",
      "status": "active",
      "offboardedAt": null
    },
    {
      "appId": "app-alpha",
      "accountId": "acct-006",
      "status": "active",
      "offboardedAt": null
    },
    {
      "appId": "app-alpha",
      "accountId": "acct-007",
      "status": "active",
      "offboardedAt": null
    },
    {
      "appId": "app-beta",
      "accountId": "acct-001",
      "status": "active",
      "offboardedAt": null
    }
  ],
  "activity": [
    {
      "appId": "app-alpha",
      "accountId": "acct-001",
      "state": "observed",
      "lastActivityAt": "2026-08-10"
    },
    {
      "appId": "app-alpha",
      "accountId": "acct-002",
      "state": "observed",
      "lastActivityAt": "2026-09-10"
    },
    {
      "appId": "app-alpha",
      "accountId": "acct-003",
      "state": "unknown",
      "lastActivityAt": null
    },
    {
      "appId": "app-alpha",
      "accountId": "acct-004",
      "state": "none-observed",
      "lastActivityAt": null
    },
    {
      "appId": "app-alpha",
      "accountId": "acct-005",
      "state": "none-observed",
      "lastActivityAt": null
    },
    {
      "appId": "app-alpha",
      "accountId": "acct-006",
      "state": "observed",
      "lastActivityAt": "2026-08-15"
    },
    {
      "appId": "app-alpha",
      "accountId": "acct-007",
      "state": "observed",
      "lastActivityAt": "2026-08-15"
    },
    {
      "appId": "app-beta",
      "accountId": "acct-001",
      "state": "observed",
      "lastActivityAt": "2026-10-01"
    }
  ],
  "coverage": [
    {
      "appId": "app-alpha",
      "from": "2026-08-01",
      "through": "2026-10-02",
      "complete": true
    },
    {
      "appId": "app-beta",
      "from": "2026-09-01",
      "through": "2026-10-01",
      "complete": false
    }
  ]
}' |
apify call h_murdock/saas-seat-usage-evidence --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,h_murdock/saas-seat-usage-evidence"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/V8zzXYr4af07gkkLY/builds/o099bwTx7RlJkjNr0/openapi.json
