{
  "openapi": "3.1.0",
  "info": {
    "title": "Material Security API",
    "version": "v1",
    "description": "Material Security external API for managing security issues, email messages, accounts, groups, roles, tenants, Trusted Entities, and OAuth apps."
  },
  "servers": [
    {
      "url": "https://{domain}",
      "description": "Material Security instance",
      "variables": {
        "domain": {
          "default": "your-instance.on.material.security",
          "description": "Your Material Security instance domain"
        }
      }
    }
  ],
  "tags": [
    {
      "name": "Accounts",
      "description": "**Overview**\n\nThe Accounts API provides programmatic access to the directory accounts synced from your Google or Microsoft tenant. List and filter accounts, retrieve one by its encoded ID, and update VIP status, licenses, setting overrides, and role assignments. Each account exposes identity and contact fields, [VIP](https://docs.material.security/learn-more/risk-areas/email-threats/detect/vip-impersonation) status, licenses, effective product settings with the [layer they resolve from](https://docs.material.security/learn-more/administration/settings-hierarchy), mailbox status, and provider status.\n\nFour endpoints make up the Accounts API:\n\n* **[List Accounts](https://docs.material.security/reference/api-v1/accounts#get-api-v1-accounts)** (`GET /api/v1/accounts`): filter, sort, and page through accounts\n* **[Get Account](https://docs.material.security/reference/api-v1/accounts#get-api-v1-accounts-id)** (`GET /api/v1/accounts/{id}`): retrieve a single account by its encoded ID\n* **[Update Account](https://docs.material.security/reference/api-v1/accounts#patch-api-v1-accounts-id)** (`PATCH /api/v1/accounts/{id}`): update an account's VIP designation, licenses, setting overrides, and role assignments\n* **[Bulk Accounts](https://docs.material.security/reference/api-v1/accounts#post-api-v1-accounts-bulk)** (`POST /api/v1/accounts/bulk`): apply up to 200 account update operations in one request\n\n**Filtering**\n\nList accepts `tenant`, `search` (a loose match on name or email), `email`, `type` (`google` or `microsoft`), `vip`, `license`, `licensed`, `group`, `mailboxStatus`, `hasAnyRole`, `role`, `providerStatus`, and `includeMaterialSecuritySupportAdmins`. Per-setting filters use dotted params, for example `setting.emailThreatRemediation=on`, and take `on`, `off`, or `overridden` (the account defines its own override). Comma-separated values match any of the listed values within a single filter, and each filter you add narrows the result set further.\n\nMaterial support admins are excluded by default. Pass `includeMaterialSecuritySupportAdmins=true` to list them, except with `tenant`, which always leaves them out.\n\n**Sorting**\n\nSort with `?sort=<field>:asc|desc`. Supported fields: `displayName` (default), `email`, and `mailbox.messageCount`.\n\n**The `include` Parameter**\n\nBy default, account responses return identity, VIP, licenses, roles, settings, mailbox, and provider fields. Pass **`include=groups`** on **[Get Account](https://docs.material.security/reference/api-v1/accounts#get-api-v1-accounts-id)** to also return the account's group memberships. The list endpoint doesn't take `include`; filter by `group` instead, or page a group's members at **[List Group Members](https://docs.material.security/reference/api-v1/groups#get-api-v1-groups-id-members)**.\n\n**Updating an Account**\n\nIdentity, mailbox, and provider fields are synced from your provider and read-only. What you can write is `vip`, `licenses`, `settings`, and `roles`, and only fields present in the body change.\n\nEach setting takes `on`, `off`, or `default`. Setting a value to `default` clears the [account-level override](https://docs.material.security/learn-more/administration/group-and-account-customization) so the account inherits from its group, its tenant, or the product default again.\n\nChanging `roles` additionally requires role-management permission, and you can only grant [roles](https://docs.material.security/learn-more/administration/admin-roles) you're allowed to bind yourself: a tenant-scoped administrator can't grant Super Admin, or grant anything outside their own tenants. Be careful here. Nothing stops you from removing your own access or the last Super Admin, and either is irreversible without Material support.\n\n**Response Structure**\n\nList responses follow the standard two-part structure: a `meta` object (with `totalCount`, `limit`, `hasMore`, and `nextCursor`) and an `items` array of account objects.\n\n**Bulk** responses are shaped differently. They return `results`, one entry per operation in request order, so `results[i]` corresponds to `operations[i]`, plus a `meta` object counting how many succeeded and failed. Operations are applied best-effort: a well-formed envelope always returns 200, and individual failures are reported in `results[]` rather than failing the request.\n\n**Want a walkthrough?**\n\nReview [Hello World](https://docs.material.security/reference/api-v1/hello-world).\n\n**Pagination and Rate Limiting**\n\n* Material APIs use **cursor-based pagination**: pass the `nextCursor` value from one response as the `cursor` parameter in the next request.\n* The `limit` parameter controls page size and is locked into the cursor, so later pages keep the page size of the first request.\n* When `hasMore` is false and `nextCursor` is null, you've reached the last page.\n"
    },
    {
      "name": "Groups",
      "description": "**Overview**\n\nThe Groups API provides programmatic access to the directory groups synced from your Google, Microsoft, or Okta tenant: Google groups, Google org units, Microsoft groups, and Okta groups. List and filter groups, retrieve one by its encoded ID, list a group's member accounts, and update its licenses, per-group setting overrides, and the roles it grants. Each group exposes its type, name, email, member count, licenses, and effective settings with the [layer they resolve from](https://docs.material.security/learn-more/administration/settings-hierarchy).\n\nFive endpoints make up the Groups API:\n\n* **[List Groups](https://docs.material.security/reference/api-v1/groups#get-api-v1-groups)** (`GET /api/v1/groups`): filter, sort, and page through groups\n* **[Get Group](https://docs.material.security/reference/api-v1/groups#get-api-v1-groups-id)** (`GET /api/v1/groups/{id}`): retrieve a single group by its encoded ID\n* **[List Group Members](https://docs.material.security/reference/api-v1/groups#get-api-v1-groups-id-members)** (`GET /api/v1/groups/{id}/members`): retrieve the accounts that belong to a group\n* **[Update Group](https://docs.material.security/reference/api-v1/groups#patch-api-v1-groups-id)** (`PATCH /api/v1/groups/{id}`): update a group's licenses and setting overrides\n* **[Bulk Groups](https://docs.material.security/reference/api-v1/groups#post-api-v1-groups-bulk)** (`POST /api/v1/groups/bulk`): apply up to 200 group update operations in one request\n\n**Filtering**\n\nList accepts `search` (a loose match on name, description, email, or org-unit path), `email`, `type` (`okta`, `google_group`, `google_org_unit`, or `microsoft_group`), `memberOf` (encoded account IDs), `role`, and `hasOverrides`. Per-setting filters use dotted params, for example `setting.emailThreatRemediation=on`, and take `on`, `off`, or `overridden`. These match the group's own override rather than the value it inherits. Comma-separated values match any of the listed values within a single filter, and each filter you add narrows the result set further.\n\n**Sorting**\n\nSort with `?sort=<field>:asc|desc`. Supported fields: `name` (default) and `memberCount`.\n\n**Read-only fields**\n\nName, email, aliases, and membership are synced from your provider and can't be changed through this API. What you can write is `licenses`, `settings`, and `roles`. Setting a value to `default` clears the [group-level override](https://docs.material.security/learn-more/administration/group-and-account-customization) so the group inherits again.\n\nChanging `roles` additionally requires role-management permission. A [role](https://docs.material.security/learn-more/administration/admin-roles) granted here applies to every member of the group, and nothing stops you from removing the last Super Admin, which is irreversible without Material support.\n\n**Response Structure**\n\nList responses follow the standard two-part structure: a `meta` object (with `totalCount`, `limit`, `hasMore`, and `nextCursor`) and an `items` array.\n\nA group's `settings` is an array, not a single object. A group can span several tenants, and its own override is instance-wide, but what that override inherits differs per tenant, so the response carries one entry per tenant you can access, each with its own `settings` and `licenses` and the layer they resolve from. To read what the group itself overrides, which is what `PATCH` writes and what the `setting.<name>` and `hasOverrides` filters match, take the entries of any tenant whose `source` is `group`.\n\n**Bulk** responses return `results`, one entry per operation in request order, so `results[i]` corresponds to `operations[i]`, plus a `meta` object counting how many succeeded and failed. Operations are applied best-effort: a well-formed envelope always returns 200, and individual failures are reported in `results[]`.\n\n**Want a walkthrough?**\n\nReview [Hello World](https://docs.material.security/reference/api-v1/hello-world).\n\n**Pagination and Rate Limiting**\n\n* Material APIs use **cursor-based pagination**: pass the `nextCursor` value from one response as the `cursor` parameter in the next request.\n* The `limit` parameter controls page size and is locked into the cursor, so later pages keep the page size of the first request.\n* When `hasMore` is false and `nextCursor` is null, you've reached the last page.\n"
    },
    {
      "name": "Issues",
      "description": "\nThe Issues API provides programmatic access to security [issues](https://docs.material.security/getting-started/fundamentals/investigate) detected by Material. Query and paginate issues, filter by status, drill down into a specific issue for details including severity and classification, and retrieve the associated email messages along with link and attachment counts.\n\nThese endpoints make up the Issues API:\n\n* **[List Issues](https://docs.material.security/reference/api-v1/issues#get-api-v1-issues)** (`GET /api/v1/issues`): filter and retrieve a paginated list of issues  \n* **[Get Issue](https://docs.material.security/reference/api-v1/issues#get-api-v1-issues-id)** (`GET /api/v1/issues/{id}`): retrieve a single issue by its encoded ID  \n* **[List Issue Messages](https://docs.material.security/reference/api-v1/issues#get-api-v1-issues-id-messages)** (`GET /api/v1/issues/{id}/messages`): retrieve the email messages associated with a specific issue\n* **[Update Issue](https://docs.material.security/reference/api-v1/issues#patch-api-v1-issues-id)** (`PATCH /api/v1/issues/{id}`): update the status, classification, assignee, or remediation actions on an existing issue\n\n**Response Structure**\n\nMost Issues API responses (except **Update Issue**) follow a consistent two-part structure:\n\n* **`meta`**: metadata about the result set, including `totalCount`, `limit`, `hasMore`, and `nextCursor` for pagination.  \n* **`items`**: an array containing the actual results, such as issues or messages.\n\nThis pattern applies to all paginated endpoints, as well as to nested collections within a single issue response, for example, `comments`, `accounts`, `messages`, and `files` each return their own `meta` and `items` when requested via the `include` parameter.\n\n**Suggested Classifications**\n\nSome issues carry a classification Material suggests but hasn't committed. `recommendedClassification` holds the suggested value, and `recommendedClassificationStatus` says whether it's still awaiting a decision (`staged`), became the committed `classification` (`accepted`), or was turned down (`rejected`). Both are read-only, and both are null when there's no suggestion.\n\n**The `include` Parameter**\n\nBy default, issue responses return a core set of fields: ID, name, status, severity, classification and suggested classification, detection rule, primary entity, and key lifecycle timestamps. To keep responses lightweight, related data is excluded unless explicitly requested with the **`include`** parameter. Supported values are `comments`, `accounts`, `messages`, and `files`. Pass multiple values as a comma-separated list (e.g. `include=comments,accounts`).\n\n**Want a walkthrough?**\n\nReview [Hello World](https://docs.material.security/reference/api-v1/hello-world).\n\n**Pagination and Rate Limiting**\n\n* Material APIs use **cursor-based pagination**: you pass the `nextCursor` value from one response as the `cursor` parameter in the next request.  \n* The `limit` parameter controls page size on all paginated endpoints.  \n* Every paginated response includes a `meta` object with `totalCount`, `limit`, `hasMore`, and `nextCursor` to help you know where you are in the result set.  \n* When `hasMore` is false and `nextCursor` is null, you've reached the last page.\n"
    },
    {
      "name": "Messages",
      "description": "**Overview**\n\nThe Messages API provides programmatic access to email messages ingested by Material Security. Retrieve a specific message by ID, run an asynchronous search across messages using MQL (Material Query Language) to find messages matching criteria such as sender, subject, or date range, and move messages back to the inbox when a remediation turns out to be a false positive.\n\nThese endpoints make up the Messages API:\n\n* **[Get Message](https://docs.material.security/reference/api-v1/messages#get-api-v1-messages-id)** (`GET /api/v1/messages/{id}`): retrieves a single message by its encoded ID.  \n* **[Search Messages](https://docs.material.security/reference/api-v1/messages#post-api-v1-messages-search)** (`POST /api/v1/messages/search`): starts an asynchronous search.  \n* **[Get Message Search Results](https://docs.material.security/reference/api-v1/messages#get-api-v1-messages-search-id)** (`GET /api/v1/messages/search/{id}`): retrieves the status and results of a search.  \n* **[Move Messages to Inbox](https://docs.material.security/reference/api-v1/messages#post-api-v1-messages-move-to-inbox)** (`POST /api/v1/messages/move-to-inbox`): queues a move of messages back to the inbox in every mailbox your token can reach.\n\n**Moving Messages Back to the Inbox**\n\nMove to Inbox is the one endpoint in this API that changes mailboxes, and it undoes a placement rather than creating one: it returns messages Material moved to spam, trash, or Surge Protection to the inbox. The work runs in the background across every mailbox your token can reach that holds the message, so a `200` means the request was accepted, not that any message has moved. Copies that are already in the inbox are skipped, so the `mailboxCount` in the response is an upper bound. In the audit log, the `message_moved_to_inbox` entries carrying a `uAcctId` are the authoritative record of what actually moved; a single further entry carrying `numRequested` is written when the request is queued and records the scope of the request rather than any completed move.\n\n**Response Structure**\n\nMessage responses return an ID and a `headers` object containing date, subject, sender, recipients, and authentication results by default. The search endpoints follow the standard paginated two-part structure similar to the Issues endpoints:\n\n* **`meta`**: metadata about the result set, including `totalCount`, `limit`, `hasMore`, and `nextCursor` for pagination, plus `truncated` on search results.  \n* **`items`**: an array of message objects matching the search query.\n\nSearch results can be capped. When `meta.truncated` is true, the query matched more messages than the search returns, and `totalCount` reports that limit rather than the real number of matches. Narrow the query or the date range to see the rest.\n\nNote that search is asynchronous. The **[Search Messages](https://docs.material.security/reference/api-v1/messages#post-api-v1-messages-search)** endpoint returns a search ID (format `job.1.<base64>`) and status immediately; results are only present in the **[Get Message Search Results](https://docs.material.security/reference/api-v1/messages#get-api-v1-messages-search-id)** response once `status` is `\"DONE\"`.\n\n**The `include` Parameter**\n\nBy default, message responses return the message ID and a `headers` object (date, subject, sender, recipients, authentication results). To keep responses lightweight, related data is excluded unless explicitly requested with the **`include`** parameter on the **[Get Message](https://docs.material.security/reference/api-v1/messages#get-api-v1-messages-id)** endpoint. Supported values are `content`, `attachments`, `issue`, `mailboxes`, and `links`. Pass multiple values as a comma-separated list (e.g. `include=content,attachments,links`).\n\n**Want a walkthrough?** \n\nReview [Hello World](https://docs.material.security/reference/api-v1/hello-world).\n\n**Pagination and Rate Limiting**\n\n* Material APIs use **cursor-based pagination**: you pass the `nextCursor` value from one response as the `cursor` parameter in the next request.  \n* The `limit` parameter controls page size on all paginated endpoints.  \n* Every paginated response includes a `meta` object with `totalCount`, `limit`, `hasMore`, and `nextCursor` to help you know where you are in the result set.  \n* When `hasMore` is false and `nextCursor` is null, you've reached the last page.\n* **[Move Messages to Inbox](https://docs.material.security/reference/api-v1/messages#post-api-v1-messages-move-to-inbox)** is rate limited per tenant by the number of messages a request targets, not by the number of requests. Over the limit, the response is a `429` with a `Retry-After` header giving the seconds to wait before retrying.\n* **[Search Messages](https://docs.material.security/reference/api-v1/messages#post-api-v1-messages-search)** can be limited by a daily budget counted in days searched, so each search uses one unit for every day its date range covers. Over the budget, the response is a `429` with a `Retry-After` header giving the seconds to wait before retrying. Check `X-RateLimit-Remaining` on each response to see how much budget is left, and shorten the date range to fit.\n"
    },
    {
      "name": "OAuth Apps",
      "description": "**Overview**\n\nThe OAuth Apps API provides programmatic access to the third-party applications that hold [OAuth token grants](https://docs.material.security/learn-more/risk-areas/malicious-oauth-apps) in your tenant. Google Workspace is the only provider covered today, so every app returned is a Google OAuth client. Each app exposes its Google client ID, the scopes currently granted across your accounts, the restricted-scope families those scopes fall into, brand metadata and verification status, its committed classification, any classification recommended by Material's [OAuth Remediation Agent](https://docs.material.security/learn-more/risk-areas/malicious-oauth-apps), and the standing remediation policy assigned to it.\n\nFive endpoints make up the OAuth Apps API:\n\n* **[List OAuth Apps](https://docs.material.security/reference/api-v1/oauth-apps#get-api-v1-oauth-apps)** (`GET /api/v1/oauth-apps`): filter, sort, and page through OAuth apps\n* **[Get OAuth App](https://docs.material.security/reference/api-v1/oauth-apps#get-api-v1-oauth-apps-id)** (`GET /api/v1/oauth-apps/{id}`): retrieve a single OAuth app by its encoded ID\n* **[List OAuth App Accounts](https://docs.material.security/reference/api-v1/oauth-apps#get-api-v1-oauth-apps-id-accounts)** (`GET /api/v1/oauth-apps/{id}/accounts`): page the accounts that have granted one app\n* **[Revoke OAuth App Account Grant](https://docs.material.security/reference/api-v1/oauth-apps#post-api-v1-oauth-apps-id-accounts-accountid-revoke)** (`POST /api/v1/oauth-apps/{id}/accounts/{accountId}/revoke`): revoke one account's grant of an app\n* **[Update OAuth App](https://docs.material.security/reference/api-v1/oauth-apps#patch-api-v1-oauth-apps-id)** (`PATCH /api/v1/oauth-apps/{id}`): set the classification and the remediation policy\n\n**Filtering**\n\nList accepts `search` (substring match over app name or client ID), `clientId` (exact match, the way to resolve a client ID from a Google Workspace audit log), `classification`, `remediation`, `hasActiveAccounts`, `verified`, `internal`, `loginOnly`, `restrictedScopeType`, and `tenant` (encoded tenant IDs; always intersected with the tenants your token can reach, so an unreachable ID matches nothing). Comma-separated values match any of the listed values within a single filter, and every filter you add narrows the result set further. By default the list returns every OAuth app ever observed, including apps whose grants have all been revoked; pass `hasActiveAccounts=true` to narrow to apps still in use.\n\nThe console labels an app with no classification \"Unknown\", which is `classification: null` here and the `none` filter value. For what each [classification](https://docs.material.security/learn-more/risk-areas/malicious-oauth-apps/oauth-detections-and-classification) means, see OAuth Detections and Classification.\n\nThe `classification` filter matches the **effective** classification, the same value **Explorer** > **Apps** > **OAuth** shows: while the OAuth Remediation Agent has a recommendation staged, that recommendation is matched in place of the committed value. A matching app can therefore come back with `classification: null` (or a different committed value) and the matched value in `recommendedClassification`. There is no committed-only filter mode: an app whose committed value differs from a staged recommendation will not match a `classification` query for that committed value at all. To work with committed values, list without the `classification` filter and compare the `classification` field yourself.\n\n**Sorting**\n\nSort with `?sort=<field>:asc|desc`. Supported fields: `name`, `firstObserved`, `lastNewGrant`, and `activeAccounts`. One field at a time; the default is `firstObserved:desc`. `activeAccounts` orders by a cached count that a tenant-wide revoke does not immediately refresh.\n\n**Accounts**\n\nAn OAuth app is only as dangerous as the accounts that granted it, so each app has an accounts collection: **List OAuth App Accounts** pages it, and `include=accounts` on **Get OAuth App** inlines its first page. Each item is the account (encoded ID, email, display name) plus an `edge` object describing that one account's grant: whether it is still `active`, when it was first and most recently granted, and the scopes in that grant, which can be narrower than the app-level `scopes` union.\n\nBy default every account that has ever granted the app is listed, revoked grants included, ordered by when each account first granted it, newest first. Pass `active=true` for the accounts that currently grant it, or `active=false` for the ones that no longer do; `providerStatus` narrows by the account's own state instead, and `providerStatus=active` is the population the console's Accounts tab shows for the app. `meta.numActive` counts the currently granting accounts across the whole app and does not move when you filter, so it stays comparable against `meta.totalCount`. An `edge.active` of false covers every way a grant ends: the account holder revoked it, Material revoked it, or the account was suspended or deleted.\n\n**The `include` Parameter**\n\nPass **`include=accounts`** on **Get OAuth App** to inline the first page (up to 100) of the accounts collection above. A `meta.hasMore` of true alongside a null `meta.nextCursor` means the app has more accounts than fit inline; page them at **List OAuth App Accounts**.\n\nPass **`include=investigation`** on **Get OAuth App** to also return the most recent run of Material's [OAuth Remediation Agent](https://docs.material.security/learn-more/risk-areas/malicious-oauth-apps): its status, timestamps, overall risk narrative, and per-dimension assessments (vendor trust, scope risk, blast radius, and app behavior). Reading it never starts a new run, and it is null whenever there is no investigation to return. This include requires permission to read investigation data in addition to app access; a token without it gets a 403 rather than a response with the field quietly missing, so a null always means \"there is nothing to show\" and never \"not allowed to see it\". The narrative fields are written by the OAuth Remediation Agent and quote app metadata the app publisher controls, so treat them as untrusted text.\n\nPass **`include=*`** to return both.\n\n**Revoking One Account's Grant**\n\n**[Revoke OAuth App Account Grant](https://docs.material.security/reference/api-v1/oauth-apps#post-api-v1-oauth-apps-id-accounts-accountid-revoke)** revokes a single account's grant of an app at the provider and returns that account's updated entry from the accounts collection.\n\nThis is a one-off action, not a policy. It leaves the app's `remediation` untouched, so if the account grants the app again, Material won't revoke it automatically. Use `remediation` when you want that. The whole grant goes rather than selected scopes, because the provider offers no per-scope handle. Revoking a grant that has already ended succeeds and changes nothing.\n\nA 403 here means [OAuth app remediation](https://docs.material.security/learn-more/risk-areas/malicious-oauth-apps/oauth-remediation-responses) is turned off for that account, which is a per-account setting rather than a property of your token.\n\n**Updating an App**\n\nAn app's identity, scopes, brand, and grant history are observed from Google and read-only. The two fields you can set are `classification` and `remediation`; only fields present in the body change.\n\n`remediation` is a **standing policy, not a one-shot action**. `[\"revoke\"]` or `[\"revoke\", \"notify\"]` tells Material to keep revoking this app's grants, including for accounts that grant it later. The work is asynchronous, so a 200 records the policy rather than confirming a revocation. `[]` removes the policy, which stops future enforcement but does not restore revoked grants. `notify` cannot be sent alone, since it means notifying the accounts whose grants were revoked.\n\nSetting `classification` resolves a staged agent recommendation that carries an `issueId` to `accepted` or `rejected` and mirrors onto the app's open investigation issues. Classification never implies a remediation: set both fields explicitly when you want both. Because remediation is a standing policy, any update to an app that already carries one, including a classification-only update, can enforce it against accounts that have granted the app since the last enforcement pass. There is no way to return an app to unclassified.\n\n**Response Structure**\n\nList responses follow the standard two-part structure: a `meta` object (with `totalCount`, `limit`, `hasMore`, and `nextCursor`) and an `items` array. **List OAuth Apps** items are OAuth app objects; **List OAuth App Accounts** items are accounts carrying the grant under `edge`, and its `meta` adds `numActive`.\n\n**Want a walkthrough?**\n\nReview [Hello World](https://docs.material.security/reference/api-v1/hello-world).\n\n**Pagination and Rate Limiting**\n\n* Material APIs use **cursor-based pagination**: pass the `nextCursor` value from one response as the `cursor` parameter in the next request.\n* The `limit` parameter controls page size and is locked into the cursor, so later pages keep the page size of the first request.\n* When `hasMore` is false and `nextCursor` is null, you've reached the last page.\n* Each page is a point-in-time window over a live inventory. Apps sort in a stable total order, but an app observed while you are paging shifts later rows, so consecutive pages can still repeat or skip one. Reconcile paged results by `clientId` when you need exactness, and re-fetch specific apps with the `clientId` filter.\n"
    },
    {
      "name": "Roles",
      "description": "**Overview**\n\nThe Roles API lists the [roles](https://docs.material.security/learn-more/administration/admin-roles) you can assign to accounts and groups. Role definitions are built into Material and can't be created or changed. This catalog exists so you can discover the assignable role IDs on your instance without hardcoding them.\n\n* **[List Roles](https://docs.material.security/reference/api-v1/roles#get-api-v1-roles)** (`GET /api/v1/roles`): the assignable roles on your instance, with each one's ID, display name, description, and scope\n\nRole assignments are managed through the account and group update endpoints, using the `roles` field of **[Update Account](https://docs.material.security/reference/api-v1/accounts#patch-api-v1-accounts-id)** and **[Update Group](https://docs.material.security/reference/api-v1/groups#patch-api-v1-groups-id)**. For what each role can do, see [Role Descriptions](https://docs.material.security/learn-more/administration/admin-roles/role-descriptions).\n\n**Scope is part of the role ID**\n\nEach ID names a role and the scope it applies at, mirroring the role list under **Edit Roles** (**Explorer** > **Accounts**). The `flavor` field reports which of the three it is:\n\n* **Tenant-scoped** IDs (`flavor: \"tenant\"`), such as `analyst`, `tenant_admin`, and `content_admin`, **require** `tenants` when assigned.\n* **Global** IDs (`flavor: \"global\"`), such as `super_admin` and `global_analyst`, apply to every tenant and **reject** `tenants`.\n* **Individual** IDs (`flavor: \"individual\"`), currently just `tenant_enroller`, grant no tenant access and take no `tenants`.\n\nSo the ID alone tells you how to construct a valid assignment, with no catalog lookup needed. Instances that disable global roles omit the global IDs from this catalog entirely and reject them on assignment. This response contains no tenant IDs, which appear only in assignment payloads. **[List Tenants](https://docs.material.security/reference/api-v1/tenants#get-api-v1-tenants)** returns them.\n\n**Assigning and removing**\n\n`roles` takes `assign` and `remove` blocks, and only the roles you name are touched. It isn't a full-set replacement, so a tenant-scoped token can't accidentally clear assignments it can't see. Assigning a tenant-scoped role the account or group already holds replaces its tenant list, removing a role it doesn't hold does nothing, and naming the same ID in both blocks returns a 400.\n\nRemoval applies at every scope the role is held, so to narrow a tenant-scoped role rather than drop it, re-assign it with a shorter `tenants` list instead of removing it.\n\nRoles read back on the account and group resources carry a `source`: `direct` (assigned on that account), `group` (inherited from the group named in `groupId`, and changeable only on that group), or `tenant` (granted to everyone in the tenant). A role reported with `tenants: \"all\"` was granted across every tenant, which you can read here but can't assign through this API.\n\n**Permissions**\n\nReading this catalog needs the same read permission as accounts and groups. Changing assignments additionally requires role-management permission, and you can only grant roles you're allowed to bind yourself: a tenant administrator can't grant Super Admin, or grant anything outside their own tenants.\n\nBe careful here. Nothing stops you from removing your own access or the last Super Admin, and either is irreversible without Material support.\n\n**Response Structure**\n\nThe catalog follows the standard two-part structure: a `meta` object (with `totalCount`, `limit`, `hasMore`, and `nextCursor`) and an `items` array of role objects. The catalog is small and fixed, so this endpoint takes pagination only and no filters.\n\n**Want a walkthrough?**\n\nReview [Hello World](https://docs.material.security/reference/api-v1/hello-world).\n\n**Pagination and Rate Limiting**\n\n* Material APIs use **cursor-based pagination**: pass the `nextCursor` value from one response as the `cursor` parameter in the next request.\n* The `limit` parameter controls page size and is locked into the cursor, so later pages keep the page size of the first request.\n* When `hasMore` is false and `nextCursor` is null, you've reached the last page.\n"
    },
    {
      "name": "Tenants",
      "description": "**Overview**\n\nThe Tenants API lists the Google Workspace and Microsoft 365 tenants connected to Material that your API token can reach. Use it to find which tenant a tenant ID (`tnt.1.<base64>`) refers to, and to find the ID to pass to a `tenant` filter or a role assignment.\n\nTwo endpoints make up the Tenants API:\n\n* **[List Tenants](https://docs.material.security/reference/api-v1/tenants#get-api-v1-tenants)** (`GET /api/v1/tenants`): page through the tenants your token can reach, sorted by name\n* **[Get Tenant](https://docs.material.security/reference/api-v1/tenants#get-api-v1-tenants-id)** (`GET /api/v1/tenants/{id}`): retrieve a single tenant by its encoded ID\n\nEach tenant reports its display name, its provider (`google` or `microsoft`), the provider's own ID for it (the Google Workspace customer ID or the Microsoft Entra tenant ID), its primary domain, and its domains. Tenants are read-only through the API.\n\n**Permissions**\n\nListing and retrieving tenants requires the same read permission as accounts and groups. Endpoints that take a tenant ID can require more permissions.\n\n**Response Structure**\n\nThe list follows the standard two-part structure: a `meta` object (with `totalCount`, `limit`, `hasMore`, and `nextCursor`) and an `items` array of tenant objects. It doesn't take any filters.\n\n**Want a walkthrough?**\n\nReview [Hello World](https://docs.material.security/reference/api-v1/hello-world).\n\n**Pagination and Rate Limiting**\n\n* Material APIs use **cursor-based pagination**: pass the `nextCursor` value from one response as the `cursor` parameter in the next request.\n* The `limit` parameter controls page size and is locked into the cursor, so later pages keep the page size of the first request.\n* When `hasMore` is false and `nextCursor` is null, you've reached the last page.\n"
    },
    {
      "name": "Trusted Entities",
      "description": "**Overview**\n\nThe Trusted Entities API provides programmatic access to your organization's [Trusted Entities](https://docs.material.security/getting-started/fundamentals/detect/trusted-entities): allowlisted domains, email addresses, and IPs/CIDRs that tune how Material treats email and files from those identities. List and filter entities, retrieve one by ID (including its change timeline), and create, update, or delete entries individually or in bulk. System-managed entities (auto-enrolled domains and Material defaults) can be customized but can't be deleted. Customizing one creates an override of its default settings.\n\nSix endpoints make up the Trusted Entities API:\n\n* **[List Trusted Entities](https://docs.material.security/reference/api-v1/trusted-entities#get-api-v1-trusted-entities)** (`GET /api/v1/trusted-entities`): filter and retrieve a paginated list of Trusted Entities\n* **[Get Trusted Entity](https://docs.material.security/reference/api-v1/trusted-entities#get-api-v1-trusted-entities-id)** (`GET /api/v1/trusted-entities/{id}`): retrieve a single Trusted Entity by its encoded ID\n* **[Create Trusted Entity](https://docs.material.security/reference/api-v1/trusted-entities#post-api-v1-trusted-entities)** (`POST /api/v1/trusted-entities`): create a new Trusted Entity\n* **[Update Trusted Entity](https://docs.material.security/reference/api-v1/trusted-entities#patch-api-v1-trusted-entities-id)** (`PATCH /api/v1/trusted-entities/{id}`): update the editable settings of an existing Trusted Entity\n* **[Delete Trusted Entity](https://docs.material.security/reference/api-v1/trusted-entities#delete-api-v1-trusted-entities-id)** (`DELETE /api/v1/trusted-entities/{id}`): delete a Trusted Entity\n* **[Bulk Trusted Entities](https://docs.material.security/reference/api-v1/trusted-entities#post-api-v1-trusted-entities-bulk)** (`POST /api/v1/trusted-entities/bulk`): apply up to 200 create, update, or delete operations in one request\n\n**Response Structure**\n\nList responses follow the standard two-part structure:\n\n* **`meta`**: pagination metadata, including `limit`, `hasMore`, and `nextCursor`. Unlike other paginated endpoints, `totalCount` isn't provided, because the list merges system-managed entities with the ones you've created. Use `hasMore` and `nextCursor` to page.\n* **`items`**: an array of Trusted Entity objects.\n\nResults return system-managed entities first (auto-enrolled domains and Material defaults), followed by the entities you've created; each block is ordered by entity, then ID.\n\n**The `include` Parameter**\n\nBy default, responses return each entity's settings. Pass **`include=timeline`** on the list or get endpoints to also return the change timeline (creation, comments, and setting updates).\n\n**Want a walkthrough?**\n\nReview [Hello World](https://docs.material.security/reference/api-v1/hello-world).\n\n**Pagination and Rate Limiting**\n\n* Material APIs use **cursor-based pagination**: you pass the `nextCursor` value from one response as the `cursor` parameter in the next request.\n* The `limit` parameter controls page size and is locked into the cursor, so later pages keep the page size of the first request.\n* Every paginated response includes a `meta` object with `limit`, `hasMore`, and `nextCursor` (`totalCount` isn't available for Trusted Entities).\n* When `hasMore` is false and `nextCursor` is null, you've reached the last page.\n"
    }
  ],
  "x-hideTryItPanel": true,
  "paths": {
    "/api/v1/issues": {
      "get": {
        "operationId": "listIssues",
        "summary": "List Issues",
        "description": "Retrieves a paginated list of security issues detected by Material. Filter by status to surface open or in-progress threats, and use the `include` parameter to pull in related data such as comments, accounts, and messages in a single request.",
        "tags": [
          "Issues"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "in": "query",
            "name": "include",
            "schema": {
              "description": "A comma-separated list of additional fields to include in the response. Valid values: `comments`, `accounts`, `messages`, `files`. Use dot notation to select sub-fields (for example, `accounts.email`). Use `*` to include all available fields.",
              "example": "comments,accounts",
              "type": "string"
            },
            "description": "A comma-separated list of additional fields to include in the response. Valid values: `comments`, `accounts`, `messages`, `files`. Use dot notation to select sub-fields (for example, `accounts.email`). Use `*` to include all available fields."
          },
          {
            "in": "query",
            "name": "status",
            "schema": {
              "description": "[Issue statuses](https://docs.material.security/getting-started/fundamentals/investigate#status) to filter the response by, specified as a comma-separated list.",
              "example": "open,in_progress",
              "type": "string"
            },
            "description": "[Issue statuses](https://docs.material.security/getting-started/fundamentals/investigate#status) to filter the response by, specified as a comma-separated list."
          },
          {
            "in": "query",
            "name": "severity",
            "schema": {
              "description": "Comma-separated list of severity levels to filter by.",
              "example": "04_high,05_critical",
              "type": "string"
            },
            "description": "Comma-separated list of severity levels to filter by."
          },
          {
            "in": "query",
            "name": "primaryEntity.type",
            "schema": {
              "description": "Comma-separated list of primary entity types to filter by: `tenant`, `account`, `group`, `file`, `message`, or `oauth_app`. `app` is accepted as a deprecated alias for `oauth_app` and will be removed in a future release.",
              "example": "account,message",
              "type": "string"
            },
            "description": "Comma-separated list of primary entity types to filter by: `tenant`, `account`, `group`, `file`, `message`, or `oauth_app`. `app` is accepted as a deprecated alias for `oauth_app` and will be removed in a future release."
          },
          {
            "in": "query",
            "name": "detection.type",
            "schema": {
              "description": "Comma-separated list of detection types to filter by.",
              "example": "threat,email_threat",
              "type": "string"
            },
            "description": "Comma-separated list of detection types to filter by."
          },
          {
            "in": "query",
            "name": "detection.id",
            "schema": {
              "description": "Comma-separated list of encoded detection IDs (`det.1.<base64>`) to filter by.",
              "example": "det.1.ZXhhbXBsZV9pZF8xMjM",
              "type": "string"
            },
            "description": "Comma-separated list of encoded detection IDs (`det.1.<base64>`) to filter by."
          },
          {
            "in": "query",
            "name": "sort",
            "schema": {
              "description": "Comma-separated list of fields to sort by, applied in the order given, with a stable secondary sort by issue ID. Each field may be suffixed with `:asc` or `:desc` to specify direction; a bare field uses the natural default direction for that field, which is descending for `severity` and any `keyEvents.*` timestamp. Valid fields: `severity`, `keyEvents.created`, `keyEvents.statusChanged`. Default when omitted: `severity,keyEvents.statusChanged` (both descending).",
              "example": "severity,keyEvents.statusChanged",
              "type": "string"
            },
            "description": "Comma-separated list of fields to sort by, applied in the order given, with a stable secondary sort by issue ID. Each field may be suffixed with `:asc` or `:desc` to specify direction; a bare field uses the natural default direction for that field, which is descending for `severity` and any `keyEvents.*` timestamp. Valid fields: `severity`, `keyEvents.created`, `keyEvents.statusChanged`. Default when omitted: `severity,keyEvents.statusChanged` (both descending)."
          },
          {
            "in": "query",
            "name": "cursor",
            "schema": {
              "description": "The pagination cursor returned in a previous response. Pass this value to retrieve the next page of results.",
              "example": "eyJ2IjoxLCJvIjoyNSwibCI6MjV9",
              "type": "string"
            },
            "description": "The pagination cursor returned in a previous response. Pass this value to retrieve the next page of results."
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "description": "The maximum number of items to return per page.",
              "example": 25,
              "type": "integer"
            },
            "description": "The maximum number of items to return per page."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIIssueCollection"
                }
              }
            }
          },
          "400": {
            "description": "Bad request: invalid parameters or request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: the API key doesn't have permission to perform this action",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found: the requested resource doesn't exist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests: you've reached a rate limit. When the response includes a `Retry-After` header, wait that many seconds before you try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/issues/{id}": {
      "get": {
        "operationId": "getIssue",
        "summary": "Get Issue",
        "description": "Retrieves the full details of a single issue by its encoded ID, including severity, classification, primary entity, detection rule, and key lifecycle timestamps.",
        "tags": [
          "Issues"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "description": "The encoded ID of the issue to retrieve. Format: `iss.1.<base64>`.",
              "example": "iss.1.ZXhhbXBsZV9pZF8xMjM",
              "type": "string",
              "pattern": "^[a-z]{1,6}\\.[0-9]{1,2}\\.[A-Za-z0-9_-]+$"
            },
            "required": true,
            "description": "The encoded ID of the issue to retrieve. Format: `iss.1.<base64>`."
          },
          {
            "in": "query",
            "name": "include",
            "schema": {
              "description": "A comma-separated list of additional fields to include in the response. Valid values: `comments`, `accounts`, `messages`, `files`. Use dot notation to select sub-fields (for example, `accounts.email`). Use `*` to include all available fields.",
              "example": "comments,accounts",
              "type": "string"
            },
            "description": "A comma-separated list of additional fields to include in the response. Valid values: `comments`, `accounts`, `messages`, `files`. Use dot notation to select sub-fields (for example, `accounts.email`). Use `*` to include all available fields."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIIssue"
                }
              }
            }
          },
          "400": {
            "description": "Bad request: invalid parameters or request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: the API key doesn't have permission to perform this action",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found: the requested resource doesn't exist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests: you've reached a rate limit. When the response includes a `Retry-After` header, wait that many seconds before you try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "patchIssue",
        "summary": "Update Issue",
        "description": "Updates the status, classification, assignee, or remediation actions on an existing issue. Only fields included in the request body are changed. Supports setting ignore reasons, snooze windows, and email threat remediation actions such as deleting messages, adding warning banners, or redirecting links and attachments.",
        "tags": [
          "Issues"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "description": "The encoded ID of the issue to retrieve. Format: `iss.1.<base64>`.",
              "example": "iss.1.ZXhhbXBsZV9pZF8xMjM",
              "type": "string",
              "pattern": "^[a-z]{1,6}\\.[0-9]{1,2}\\.[A-Za-z0-9_-]+$"
            },
            "required": true,
            "description": "The encoded ID of the issue to retrieve. Format: `iss.1.<base64>`."
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateIssueBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIIssue"
                }
              }
            }
          },
          "400": {
            "description": "Bad request: invalid parameters or request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: the API key doesn't have permission to perform this action",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found: the requested resource doesn't exist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests: you've reached a rate limit. When the response includes a `Retry-After` header, wait that many seconds before you try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/accounts": {
      "get": {
        "operationId": "listAccounts",
        "summary": "List Accounts",
        "description": "Retrieves a paginated list of directory accounts synced from Google or Microsoft. Filter by type, VIP status, licenses, group membership, mailbox and provider status, or per-setting overrides, and sort by name or mailbox size.",
        "tags": [
          "Accounts"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "in": "query",
            "name": "cursor",
            "schema": {
              "description": "The pagination cursor returned in a previous response. Pass this value to retrieve the next page of results.",
              "example": "eyJ2IjoxLCJvIjoyNSwibCI6MjV9",
              "type": "string"
            },
            "description": "The pagination cursor returned in a previous response. Pass this value to retrieve the next page of results."
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "description": "The maximum number of items to return per page.",
              "example": 25,
              "type": "integer"
            },
            "description": "The maximum number of items to return per page."
          },
          {
            "in": "query",
            "name": "tenant",
            "schema": {
              "description": "Filter to accounts in any of these tenants, as comma-separated encoded tenant IDs (format `tnt.1.<base64>`). Matches the `tenantId` field, so it leaves out accounts managed in a tenant they aren't members of, and Material support admins, even with `includeMaterialSecuritySupportAdmins`. Results only include tenants your API token can reach, so an ID you can't access matches nothing instead of returning an error. Call `GET /tenants` for the IDs.",
              "example": "tnt.1.ZXhhbXBsZV9pZF8xMjM",
              "type": "string"
            },
            "description": "Filter to accounts in any of these tenants, as comma-separated encoded tenant IDs (format `tnt.1.<base64>`). Matches the `tenantId` field, so it leaves out accounts managed in a tenant they aren't members of, and Material support admins, even with `includeMaterialSecuritySupportAdmins`. Results only include tenants your API token can reach, so an ID you can't access matches nothing instead of returning an error. Call `GET /tenants` for the IDs."
          },
          {
            "in": "query",
            "name": "search",
            "schema": {
              "description": "Loose match on the account name or email address.",
              "example": "jane",
              "type": "string"
            },
            "description": "Loose match on the account name or email address."
          },
          {
            "in": "query",
            "name": "email",
            "schema": {
              "description": "Filter to these exact email addresses, comma-separated.",
              "example": "jane@example.com",
              "type": "string"
            },
            "description": "Filter to these exact email addresses, comma-separated."
          },
          {
            "in": "query",
            "name": "type",
            "schema": {
              "description": "Filter by the provider the account is synced from: `google` or `microsoft`.",
              "example": "google",
              "type": "string",
              "enum": [
                "google",
                "microsoft"
              ]
            },
            "description": "Filter by the provider the account is synced from: `google` or `microsoft`."
          },
          {
            "in": "query",
            "name": "vip",
            "schema": {
              "description": "Filter to accounts marked as a VIP (true) or not marked (false).",
              "type": "boolean"
            },
            "description": "Filter to accounts marked as a VIP (true) or not marked (false)."
          },
          {
            "in": "query",
            "name": "license",
            "schema": {
              "description": "Filter to accounts holding any of these active licenses, comma-separated.",
              "example": "advanced,ato_resilience",
              "type": "string"
            },
            "description": "Filter to accounts holding any of these active licenses, comma-separated."
          },
          {
            "in": "query",
            "name": "licensed",
            "schema": {
              "description": "Filter to accounts with (true) or without (false) any active license.",
              "type": "boolean"
            },
            "description": "Filter to accounts with (true) or without (false) any active license."
          },
          {
            "in": "query",
            "name": "group",
            "schema": {
              "description": "Filter to members of any of these groups, as comma-separated encoded group IDs (format `grp.1.<base64>`).",
              "example": "grp.1.ZXhhbXBsZV9pZF8xMjM",
              "type": "string"
            },
            "description": "Filter to members of any of these groups, as comma-separated encoded group IDs (format `grp.1.<base64>`)."
          },
          {
            "in": "query",
            "name": "mailboxStatus",
            "schema": {
              "description": "Filter by mailbox status: `enabled` or `disabled`.",
              "example": "enabled",
              "type": "string",
              "enum": [
                "enabled",
                "disabled"
              ]
            },
            "description": "Filter by mailbox status: `enabled` or `disabled`."
          },
          {
            "in": "query",
            "name": "hasAnyRole",
            "schema": {
              "description": "Filter to accounts that hold at least one role (true) or none at all (false).",
              "type": "boolean"
            },
            "description": "Filter to accounts that hold at least one role (true) or none at all (false)."
          },
          {
            "in": "query",
            "name": "providerStatus",
            "schema": {
              "description": "Filter by the account's state at your provider: `active`, `suspended`, `archived`, or `deleted`. One value only.",
              "example": "suspended",
              "type": "string",
              "enum": [
                "active",
                "suspended",
                "archived",
                "deleted"
              ]
            },
            "description": "Filter by the account's state at your provider: `active`, `suspended`, `archived`, or `deleted`. One value only."
          },
          {
            "in": "query",
            "name": "includeMaterialSecuritySupportAdmins",
            "schema": {
              "description": "Include Material support admins, the Material employees with support access to your instance. They are excluded by default. Ignored when `tenant` is set.",
              "type": "boolean"
            },
            "description": "Include Material support admins, the Material employees with support access to your instance. They are excluded by default. Ignored when `tenant` is set."
          },
          {
            "in": "query",
            "name": "role",
            "schema": {
              "description": "Filter to accounts holding any of these roles, comma-separated. Matches roles inherited from a group as well as those assigned directly. Call `GET /roles` for the IDs available on your instance.",
              "example": "tenant_admin,analyst",
              "type": "string"
            },
            "description": "Filter to accounts holding any of these roles, comma-separated. Matches roles inherited from a group as well as those assigned directly. Call `GET /roles` for the IDs available on your instance."
          },
          {
            "in": "query",
            "name": "setting.emailSync",
            "schema": {
              "description": "Filter by the effective `emailSync` setting: `on`, `off`, or `overridden` (the account defines its own override, whatever the value).",
              "type": "string",
              "enum": [
                "on",
                "off",
                "overridden"
              ]
            },
            "description": "Filter by the effective `emailSync` setting: `on`, `off`, or `overridden` (the account defines its own override, whatever the value)."
          },
          {
            "in": "query",
            "name": "setting.fileSync",
            "schema": {
              "description": "Filter by the effective `fileSync` setting: `on`, `off`, or `overridden` (the account defines its own override, whatever the value).",
              "type": "string",
              "enum": [
                "on",
                "off",
                "overridden"
              ]
            },
            "description": "Filter by the effective `fileSync` setting: `on`, `off`, or `overridden` (the account defines its own override, whatever the value)."
          },
          {
            "in": "query",
            "name": "setting.emailThreatRemediation",
            "schema": {
              "description": "Filter by the effective `emailThreatRemediation` setting: `on`, `off`, or `overridden` (the account defines its own override, whatever the value).",
              "type": "string",
              "enum": [
                "on",
                "off",
                "overridden"
              ]
            },
            "description": "Filter by the effective `emailThreatRemediation` setting: `on`, `off`, or `overridden` (the account defines its own override, whatever the value)."
          },
          {
            "in": "query",
            "name": "setting.fileRemediation",
            "schema": {
              "description": "Filter by the effective `fileRemediation` setting: `on`, `off`, or `overridden` (the account defines its own override, whatever the value).",
              "type": "string",
              "enum": [
                "on",
                "off",
                "overridden"
              ]
            },
            "description": "Filter by the effective `fileRemediation` setting: `on`, `off`, or `overridden` (the account defines its own override, whatever the value)."
          },
          {
            "in": "query",
            "name": "setting.sensitiveEmailRedaction",
            "schema": {
              "description": "Filter by the effective `sensitiveEmailRedaction` setting: `on`, `off`, or `overridden` (the account defines its own override, whatever the value).",
              "type": "string",
              "enum": [
                "on",
                "off",
                "overridden"
              ]
            },
            "description": "Filter by the effective `sensitiveEmailRedaction` setting: `on`, `off`, or `overridden` (the account defines its own override, whatever the value)."
          },
          {
            "in": "query",
            "name": "setting.passwordResetProtection",
            "schema": {
              "description": "Filter by the effective `passwordResetProtection` setting: `on`, `off`, or `overridden` (the account defines its own override, whatever the value).",
              "type": "string",
              "enum": [
                "on",
                "off",
                "overridden"
              ]
            },
            "description": "Filter by the effective `passwordResetProtection` setting: `on`, `off`, or `overridden` (the account defines its own override, whatever the value)."
          },
          {
            "in": "query",
            "name": "setting.oauthAppRemediation",
            "schema": {
              "description": "Filter by the effective `oauthAppRemediation` setting: `on`, `off`, or `overridden` (the account defines its own override, whatever the value).",
              "type": "string",
              "enum": [
                "on",
                "off",
                "overridden"
              ]
            },
            "description": "Filter by the effective `oauthAppRemediation` setting: `on`, `off`, or `overridden` (the account defines its own override, whatever the value)."
          },
          {
            "in": "query",
            "name": "setting.emailBombProtection",
            "schema": {
              "description": "Filter by the effective `emailBombProtection` setting: `on`, `off`, or `overridden` (the account defines its own override, whatever the value).",
              "type": "string",
              "enum": [
                "on",
                "off",
                "overridden"
              ]
            },
            "description": "Filter by the effective `emailBombProtection` setting: `on`, `off`, or `overridden` (the account defines its own override, whatever the value)."
          },
          {
            "in": "query",
            "name": "sort",
            "schema": {
              "description": "Sort field, e.g. `displayName:asc` or `mailbox.messageCount:desc`. A single field only.",
              "example": "displayName:asc",
              "type": "string"
            },
            "description": "Sort field, e.g. `displayName:asc` or `mailbox.messageCount:desc`. A single field only."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIAccountCollection"
                }
              }
            }
          },
          "400": {
            "description": "Bad request: invalid parameters or request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: the API key doesn't have permission to perform this action",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found: the requested resource doesn't exist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests: you've reached a rate limit. When the response includes a `Retry-After` header, wait that many seconds before you try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/accounts/{id}": {
      "get": {
        "operationId": "getAccount",
        "summary": "Get Account",
        "description": "Retrieves the full details of a single account by its encoded ID, including identity, VIP, licenses, effective settings with their source, mailbox, and provider status.",
        "tags": [
          "Accounts"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "description": "The encoded ID of the account to retrieve. Format: `acct.1.<base64>`.",
              "example": "acct.1.ZXhhbXBsZV9pZF8xMjM",
              "type": "string",
              "pattern": "^[a-z]{1,6}\\.[0-9]{1,2}\\.[A-Za-z0-9_-]+$"
            },
            "required": true,
            "description": "The encoded ID of the account to retrieve. Format: `acct.1.<base64>`."
          },
          {
            "in": "query",
            "name": "include",
            "schema": {
              "description": "Comma-separated list of extra fields to return. Supported: `groups`, the account's group memberships.",
              "example": "groups",
              "type": "string"
            },
            "description": "Comma-separated list of extra fields to return. Supported: `groups`, the account's group memberships."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIAccount"
                }
              }
            }
          },
          "400": {
            "description": "Bad request: invalid parameters or request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: the API key doesn't have permission to perform this action",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found: the requested resource doesn't exist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests: you've reached a rate limit. When the response includes a `Retry-After` header, wait that many seconds before you try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "patchAccount",
        "summary": "Update Account",
        "description": "Updates an account's [VIP](https://docs.material.security/learn-more/risk-areas/email-threats/detect/vip-impersonation) designation, license allocation, per-account setting overrides, and [role](https://docs.material.security/learn-more/administration/admin-roles) assignments. Identity, mailbox, and provider fields are read-only, since they're synced from your provider. Only provided fields change, and a setting value of `default` clears the account override so it inherits from a higher level. Changing `roles` additionally requires role-management permission, and a tenant-scoped administrator can only grant roles they're allowed to bind themselves, within their own tenants. Be careful: nothing stops you from removing your own access or the last Super Admin, and either is irreversible without Material support.",
        "tags": [
          "Accounts"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "description": "The encoded ID of the account to retrieve. Format: `acct.1.<base64>`.",
              "example": "acct.1.ZXhhbXBsZV9pZF8xMjM",
              "type": "string",
              "pattern": "^[a-z]{1,6}\\.[0-9]{1,2}\\.[A-Za-z0-9_-]+$"
            },
            "required": true,
            "description": "The encoded ID of the account to retrieve. Format: `acct.1.<base64>`."
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateAccountBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIAccount"
                }
              }
            }
          },
          "400": {
            "description": "Bad request: invalid parameters or request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: the API key doesn't have permission to perform this action",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found: the requested resource doesn't exist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests: you've reached a rate limit. When the response includes a `Retry-After` header, wait that many seconds before you try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/accounts/bulk": {
      "post": {
        "operationId": "bulkAccounts",
        "summary": "Bulk Accounts",
        "description": "Applies up to 200 account update operations in one request, best-effort with per-item results in request order (`results[i]` corresponds to `operations[i]`). A well-formed envelope always returns 200; individual failures are reported in `results[]`.",
        "tags": [
          "Accounts"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkAccountsBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BulkAccountsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request: invalid parameters or request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: the API key doesn't have permission to perform this action",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found: the requested resource doesn't exist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests: you've reached a rate limit. When the response includes a `Retry-After` header, wait that many seconds before you try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/groups": {
      "get": {
        "operationId": "listGroups",
        "summary": "List Groups",
        "description": "Retrieves a paginated list of directory groups (Google groups, Google org units, Microsoft groups, Okta groups) synced from a provider. Filter by type, member account, per-setting overrides, and effective settings, and sort by name or member count.",
        "tags": [
          "Groups"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "in": "query",
            "name": "cursor",
            "schema": {
              "description": "The pagination cursor returned in a previous response. Pass this value to retrieve the next page of results.",
              "example": "eyJ2IjoxLCJvIjoyNSwibCI6MjV9",
              "type": "string"
            },
            "description": "The pagination cursor returned in a previous response. Pass this value to retrieve the next page of results."
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "description": "The maximum number of items to return per page.",
              "example": 25,
              "type": "integer"
            },
            "description": "The maximum number of items to return per page."
          },
          {
            "in": "query",
            "name": "search",
            "schema": {
              "description": "Loose match on the group's name, description, email address, or org-unit path.",
              "example": "eng",
              "type": "string"
            },
            "description": "Loose match on the group's name, description, email address, or org-unit path."
          },
          {
            "in": "query",
            "name": "email",
            "schema": {
              "description": "Filter to these exact email addresses, comma-separated.",
              "example": "eng@example.com",
              "type": "string"
            },
            "description": "Filter to these exact email addresses, comma-separated."
          },
          {
            "in": "query",
            "name": "type",
            "schema": {
              "description": "Filter by the kind of directory group: `okta` (Okta), `google_group` (Google Group), `google_org_unit` (Google Org Unit), or `microsoft_group` (Microsoft Group).",
              "example": "google_group",
              "type": "string",
              "enum": [
                "okta",
                "google_group",
                "google_org_unit",
                "microsoft_group"
              ]
            },
            "description": "Filter by the kind of directory group: `okta` (Okta), `google_group` (Google Group), `google_org_unit` (Google Org Unit), or `microsoft_group` (Microsoft Group)."
          },
          {
            "in": "query",
            "name": "memberOf",
            "schema": {
              "description": "Filter to the groups that any of these accounts belong to, as comma-separated encoded account IDs (format `acct.1.<base64>`).",
              "example": "acct.1.ZXhhbXBsZV9pZF8xMjM",
              "type": "string"
            },
            "description": "Filter to the groups that any of these accounts belong to, as comma-separated encoded account IDs (format `acct.1.<base64>`)."
          },
          {
            "in": "query",
            "name": "hasOverrides",
            "schema": {
              "description": "Filter to groups that define at least one setting override of their own (true) or none at all (false).",
              "type": "boolean"
            },
            "description": "Filter to groups that define at least one setting override of their own (true) or none at all (false)."
          },
          {
            "in": "query",
            "name": "role",
            "schema": {
              "description": "Filter to groups that grant any of these roles to their members, comma-separated. Call `GET /roles` for the IDs available on your instance.",
              "example": "analyst",
              "type": "string"
            },
            "description": "Filter to groups that grant any of these roles to their members, comma-separated. Call `GET /roles` for the IDs available on your instance."
          },
          {
            "in": "query",
            "name": "setting.emailSync",
            "schema": {
              "description": "Filter by the group's own `emailSync` override: `on` or `off` (the group's override sets that value), or `overridden` (the group defines an override, whatever the value). Matches the group's own override, not the value it inherits.",
              "type": "string",
              "enum": [
                "on",
                "off",
                "overridden"
              ]
            },
            "description": "Filter by the group's own `emailSync` override: `on` or `off` (the group's override sets that value), or `overridden` (the group defines an override, whatever the value). Matches the group's own override, not the value it inherits."
          },
          {
            "in": "query",
            "name": "setting.fileSync",
            "schema": {
              "description": "Filter by the group's own `fileSync` override: `on` or `off` (the group's override sets that value), or `overridden` (the group defines an override, whatever the value). Matches the group's own override, not the value it inherits.",
              "type": "string",
              "enum": [
                "on",
                "off",
                "overridden"
              ]
            },
            "description": "Filter by the group's own `fileSync` override: `on` or `off` (the group's override sets that value), or `overridden` (the group defines an override, whatever the value). Matches the group's own override, not the value it inherits."
          },
          {
            "in": "query",
            "name": "setting.emailThreatRemediation",
            "schema": {
              "description": "Filter by the group's own `emailThreatRemediation` override: `on` or `off` (the group's override sets that value), or `overridden` (the group defines an override, whatever the value). Matches the group's own override, not the value it inherits.",
              "type": "string",
              "enum": [
                "on",
                "off",
                "overridden"
              ]
            },
            "description": "Filter by the group's own `emailThreatRemediation` override: `on` or `off` (the group's override sets that value), or `overridden` (the group defines an override, whatever the value). Matches the group's own override, not the value it inherits."
          },
          {
            "in": "query",
            "name": "setting.fileRemediation",
            "schema": {
              "description": "Filter by the group's own `fileRemediation` override: `on` or `off` (the group's override sets that value), or `overridden` (the group defines an override, whatever the value). Matches the group's own override, not the value it inherits.",
              "type": "string",
              "enum": [
                "on",
                "off",
                "overridden"
              ]
            },
            "description": "Filter by the group's own `fileRemediation` override: `on` or `off` (the group's override sets that value), or `overridden` (the group defines an override, whatever the value). Matches the group's own override, not the value it inherits."
          },
          {
            "in": "query",
            "name": "setting.sensitiveEmailRedaction",
            "schema": {
              "description": "Filter by the group's own `sensitiveEmailRedaction` override: `on` or `off` (the group's override sets that value), or `overridden` (the group defines an override, whatever the value). Matches the group's own override, not the value it inherits.",
              "type": "string",
              "enum": [
                "on",
                "off",
                "overridden"
              ]
            },
            "description": "Filter by the group's own `sensitiveEmailRedaction` override: `on` or `off` (the group's override sets that value), or `overridden` (the group defines an override, whatever the value). Matches the group's own override, not the value it inherits."
          },
          {
            "in": "query",
            "name": "setting.passwordResetProtection",
            "schema": {
              "description": "Filter by the group's own `passwordResetProtection` override: `on` or `off` (the group's override sets that value), or `overridden` (the group defines an override, whatever the value). Matches the group's own override, not the value it inherits.",
              "type": "string",
              "enum": [
                "on",
                "off",
                "overridden"
              ]
            },
            "description": "Filter by the group's own `passwordResetProtection` override: `on` or `off` (the group's override sets that value), or `overridden` (the group defines an override, whatever the value). Matches the group's own override, not the value it inherits."
          },
          {
            "in": "query",
            "name": "setting.oauthAppRemediation",
            "schema": {
              "description": "Filter by the group's own `oauthAppRemediation` override: `on` or `off` (the group's override sets that value), or `overridden` (the group defines an override, whatever the value). Matches the group's own override, not the value it inherits.",
              "type": "string",
              "enum": [
                "on",
                "off",
                "overridden"
              ]
            },
            "description": "Filter by the group's own `oauthAppRemediation` override: `on` or `off` (the group's override sets that value), or `overridden` (the group defines an override, whatever the value). Matches the group's own override, not the value it inherits."
          },
          {
            "in": "query",
            "name": "setting.emailBombProtection",
            "schema": {
              "description": "Filter by the group's own `emailBombProtection` override: `on` or `off` (the group's override sets that value), or `overridden` (the group defines an override, whatever the value). Matches the group's own override, not the value it inherits.",
              "type": "string",
              "enum": [
                "on",
                "off",
                "overridden"
              ]
            },
            "description": "Filter by the group's own `emailBombProtection` override: `on` or `off` (the group's override sets that value), or `overridden` (the group defines an override, whatever the value). Matches the group's own override, not the value it inherits."
          },
          {
            "in": "query",
            "name": "sort",
            "schema": {
              "description": "Sort field, e.g. `name:asc` or `memberCount:desc`. A single field only.",
              "example": "name:asc",
              "type": "string"
            },
            "description": "Sort field, e.g. `name:asc` or `memberCount:desc`. A single field only."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIGroupCollection"
                }
              }
            }
          },
          "400": {
            "description": "Bad request: invalid parameters or request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: the API key doesn't have permission to perform this action",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found: the requested resource doesn't exist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests: you've reached a rate limit. When the response includes a `Retry-After` header, wait that many seconds before you try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/groups/{id}": {
      "get": {
        "operationId": "getGroup",
        "summary": "Get Group",
        "description": "Retrieves the full details of a single group by its encoded ID, including type, name, email, member count, licenses, and effective settings with their source.",
        "tags": [
          "Groups"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "description": "The encoded ID of the group to retrieve. Format: `grp.1.<base64>`.",
              "example": "grp.1.ZXhhbXBsZV9pZF8xMjM",
              "type": "string",
              "pattern": "^[a-z]{1,6}\\.[0-9]{1,2}\\.[A-Za-z0-9_-]+$"
            },
            "required": true,
            "description": "The encoded ID of the group to retrieve. Format: `grp.1.<base64>`."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIGroup"
                }
              }
            }
          },
          "400": {
            "description": "Bad request: invalid parameters or request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: the API key doesn't have permission to perform this action",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found: the requested resource doesn't exist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests: you've reached a rate limit. When the response includes a `Retry-After` header, wait that many seconds before you try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "patchGroup",
        "summary": "Update Group",
        "description": "Updates a group's license allocation, per-group setting overrides, and the roles it grants its members. Name, email, membership, and provider settings are read-only, since they're synced from your provider. Only provided fields change, and a setting value of `default` clears the group override so it inherits from a higher level. Changing `roles` additionally requires role-management permission, and a tenant-scoped administrator can only grant roles they're allowed to bind themselves, within their own tenants. Be careful: roles granted here apply to every member of the group, and nothing stops you from removing the last Super Admin, which is irreversible without Material support.",
        "tags": [
          "Groups"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "description": "The encoded ID of the group to retrieve. Format: `grp.1.<base64>`.",
              "example": "grp.1.ZXhhbXBsZV9pZF8xMjM",
              "type": "string",
              "pattern": "^[a-z]{1,6}\\.[0-9]{1,2}\\.[A-Za-z0-9_-]+$"
            },
            "required": true,
            "description": "The encoded ID of the group to retrieve. Format: `grp.1.<base64>`."
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateGroupBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIGroup"
                }
              }
            }
          },
          "400": {
            "description": "Bad request: invalid parameters or request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: the API key doesn't have permission to perform this action",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found: the requested resource doesn't exist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests: you've reached a rate limit. When the response includes a `Retry-After` header, wait that many seconds before you try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/groups/{id}/members": {
      "get": {
        "operationId": "listGroupMembers",
        "summary": "List Group Members",
        "description": "Retrieves the paginated list of accounts that belong to a specific group. Each member is returned as a full account object, equivalent to filtering the accounts list by this group.",
        "tags": [
          "Groups"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "description": "The encoded ID of the group to retrieve. Format: `grp.1.<base64>`.",
              "example": "grp.1.ZXhhbXBsZV9pZF8xMjM",
              "type": "string",
              "pattern": "^[a-z]{1,6}\\.[0-9]{1,2}\\.[A-Za-z0-9_-]+$"
            },
            "required": true,
            "description": "The encoded ID of the group to retrieve. Format: `grp.1.<base64>`."
          },
          {
            "in": "query",
            "name": "cursor",
            "schema": {
              "description": "The pagination cursor returned in a previous response. Pass this value to retrieve the next page of results.",
              "example": "eyJ2IjoxLCJvIjoyNSwibCI6MjV9",
              "type": "string"
            },
            "description": "The pagination cursor returned in a previous response. Pass this value to retrieve the next page of results."
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "description": "The maximum number of items to return per page.",
              "example": 25,
              "type": "integer"
            },
            "description": "The maximum number of items to return per page."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIAccountCollection"
                }
              }
            }
          },
          "400": {
            "description": "Bad request: invalid parameters or request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: the API key doesn't have permission to perform this action",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found: the requested resource doesn't exist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests: you've reached a rate limit. When the response includes a `Retry-After` header, wait that many seconds before you try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/groups/bulk": {
      "post": {
        "operationId": "bulkGroups",
        "summary": "Bulk Groups",
        "description": "Applies up to 200 group update operations in one request, best-effort with per-item results in request order (`results[i]` corresponds to `operations[i]`). A well-formed envelope always returns 200; individual failures are reported in `results[]`.",
        "tags": [
          "Groups"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkGroupsBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BulkGroupsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request: invalid parameters or request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: the API key doesn't have permission to perform this action",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found: the requested resource doesn't exist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests: you've reached a rate limit. When the response includes a `Retry-After` header, wait that many seconds before you try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/roles": {
      "get": {
        "operationId": "listRoles",
        "summary": "List Roles",
        "description": "Lists the roles that can be assigned to accounts and groups on this instance. Role definitions are built in, so only assignments are managed through the API, using the `roles` field of the account and group update endpoints. A role ID carries its scope: tenant-scoped IDs require `tenants` when assigned, while IDs that apply everywhere take none. Instances that disable global roles omit the global IDs entirely. The response doesn't contain tenant IDs. To get them, call `GET /tenants`.",
        "tags": [
          "Roles"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "in": "query",
            "name": "cursor",
            "schema": {
              "description": "The pagination cursor returned in a previous response. Pass this value to retrieve the next page of results.",
              "example": "eyJ2IjoxLCJvIjoyNSwibCI6MjV9",
              "type": "string"
            },
            "description": "The pagination cursor returned in a previous response. Pass this value to retrieve the next page of results."
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "description": "The maximum number of items to return per page.",
              "example": 25,
              "type": "integer"
            },
            "description": "The maximum number of items to return per page."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIRoleCollection"
                }
              }
            }
          },
          "400": {
            "description": "Bad request: invalid parameters or request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: the API key doesn't have permission to perform this action",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found: the requested resource doesn't exist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests: you've reached a rate limit. When the response includes a `Retry-After` header, wait that many seconds before you try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tenants": {
      "get": {
        "operationId": "listTenants",
        "summary": "List Tenants",
        "description": "Lists the Google Workspace and Microsoft 365 tenants your API token can reach, sorted by name. Each tenant includes the encoded ID that you pass to `tenant` filters and that `tenantId` fields return. Requires the same read permission as accounts and groups.",
        "tags": [
          "Tenants"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "in": "query",
            "name": "cursor",
            "schema": {
              "description": "The pagination cursor returned in a previous response. Pass this value to retrieve the next page of results.",
              "example": "eyJ2IjoxLCJvIjoyNSwibCI6MjV9",
              "type": "string"
            },
            "description": "The pagination cursor returned in a previous response. Pass this value to retrieve the next page of results."
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "description": "The maximum number of items to return per page.",
              "example": 25,
              "type": "integer"
            },
            "description": "The maximum number of items to return per page."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APITenantCollection"
                }
              }
            }
          },
          "400": {
            "description": "Bad request: invalid parameters or request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: the API key doesn't have permission to perform this action",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found: the requested resource doesn't exist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests: you've reached a rate limit. When the response includes a `Retry-After` header, wait that many seconds before you try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tenants/{id}": {
      "get": {
        "operationId": "getTenant",
        "summary": "Get Tenant",
        "description": "Retrieves a single tenant by its encoded ID, including its name, `csp` (`google` or `microsoft`), provider tenant ID, and domains.",
        "tags": [
          "Tenants"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "description": "The encoded ID of the tenant to retrieve. Format: `tnt.1.<base64>`.",
              "example": "tnt.1.ZXhhbXBsZV9pZF8xMjM",
              "type": "string",
              "pattern": "^[a-z]{1,6}\\.[0-9]{1,2}\\.[A-Za-z0-9_-]+$"
            },
            "required": true,
            "description": "The encoded ID of the tenant to retrieve. Format: `tnt.1.<base64>`."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APITenant"
                }
              }
            }
          },
          "400": {
            "description": "Bad request: invalid parameters or request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: the API key doesn't have permission to perform this action",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found: the requested resource doesn't exist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests: you've reached a rate limit. When the response includes a `Retry-After` header, wait that many seconds before you try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/issues/{id}/messages": {
      "get": {
        "operationId": "listIssueMessages",
        "summary": "List Issue Messages",
        "description": "Retrieves the paginated list of email messages associated with a specific issue. The response includes link and attachment counts across all related messages, useful for quickly assessing the scope of a threat.",
        "tags": [
          "Issues"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "description": "The encoded ID of the issue to retrieve. Format: `iss.1.<base64>`.",
              "example": "iss.1.ZXhhbXBsZV9pZF8xMjM",
              "type": "string",
              "pattern": "^[a-z]{1,6}\\.[0-9]{1,2}\\.[A-Za-z0-9_-]+$"
            },
            "required": true,
            "description": "The encoded ID of the issue to retrieve. Format: `iss.1.<base64>`."
          },
          {
            "in": "query",
            "name": "include",
            "schema": {
              "description": "A comma-separated list of additional fields to include in the response. Valid values: `comments`, `accounts`, `messages`, `files`. Use dot notation to select sub-fields (for example, `accounts.email`). Use `*` to include all available fields.",
              "example": "comments,accounts",
              "type": "string"
            },
            "description": "A comma-separated list of additional fields to include in the response. Valid values: `comments`, `accounts`, `messages`, `files`. Use dot notation to select sub-fields (for example, `accounts.email`). Use `*` to include all available fields."
          },
          {
            "in": "query",
            "name": "cursor",
            "schema": {
              "description": "The pagination cursor returned in a previous response. Pass this value to retrieve the next page of results.",
              "example": "eyJ2IjoxLCJvIjoyNSwibCI6MjV9",
              "type": "string"
            },
            "description": "The pagination cursor returned in a previous response. Pass this value to retrieve the next page of results."
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "description": "The maximum number of items to return per page.",
              "example": 25,
              "type": "integer"
            },
            "description": "The maximum number of items to return per page."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "meta": {
                      "type": "object",
                      "properties": {
                        "totalCount": {
                          "description": "The total number of items matching the query.",
                          "type": "integer",
                          "minimum": -9007199254740991,
                          "maximum": 9007199254740991
                        },
                        "limit": {
                          "description": "The page size used for this response.",
                          "type": "integer",
                          "minimum": -9007199254740991,
                          "maximum": 9007199254740991
                        },
                        "hasMore": {
                          "description": "True if more items exist beyond the current page; false otherwise.",
                          "type": "boolean"
                        },
                        "nextCursor": {
                          "description": "The cursor to fetch the next page of results. Pass this value to the `cursor` query parameter. Null if no more pages exist.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "numLinks": {
                          "description": "Number of links found in related messages",
                          "type": "integer",
                          "minimum": -9007199254740991,
                          "maximum": 9007199254740991
                        },
                        "numAttachments": {
                          "description": "Number of file attachments in related messages",
                          "type": "integer",
                          "minimum": -9007199254740991,
                          "maximum": 9007199254740991
                        }
                      },
                      "required": [
                        "numLinks",
                        "numAttachments"
                      ]
                    },
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/APIMessageItem"
                      }
                    }
                  },
                  "required": [
                    "meta",
                    "items"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request: invalid parameters or request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: the API key doesn't have permission to perform this action",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found: the requested resource doesn't exist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests: you've reached a rate limit. When the response includes a `Retry-After` header, wait that many seconds before you try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/messages/{id}": {
      "get": {
        "operationId": "getMessage",
        "summary": "Get Message",
        "description": "Retrieves the full details of a single email message by its encoded ID, including sender, recipients, subject, date, and attachment metadata.",
        "tags": [
          "Messages"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "description": "The encoded ID of the message to retrieve. Format: `msg.1.<base64>`.",
              "example": "msg.1.ZXhhbXBsZV9pZF8xMjM",
              "type": "string",
              "pattern": "^[a-z]{1,6}\\.[0-9]{1,2}\\.[A-Za-z0-9_-]+$"
            },
            "required": true,
            "description": "The encoded ID of the message to retrieve. Format: `msg.1.<base64>`."
          },
          {
            "in": "query",
            "name": "include",
            "schema": {
              "description": "A comma-separated list of additional fields to include in the response. Valid values: `content`, `attachments`, `issue`, `mailboxes`, `links`.",
              "example": "links",
              "type": "string"
            },
            "description": "A comma-separated list of additional fields to include in the response. Valid values: `content`, `attachments`, `issue`, `mailboxes`, `links`."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIMessage"
                }
              }
            }
          },
          "400": {
            "description": "Bad request: invalid parameters or request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: the API key doesn't have permission to perform this action",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found: the requested resource doesn't exist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests: you've reached a rate limit. When the response includes a `Retry-After` header, wait that many seconds before you try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/messages/search": {
      "post": {
        "operationId": "searchMessages",
        "summary": "Search Messages",
        "description": "Starts an asynchronous search across ingested email messages using an MQL query and a date range. Returns a search ID (format `job.1.<base64>`) to use when polling for results. If your instance has a daily search budget, each search uses one unit of it for every day its date range covers, not one per request. Successful and 429 responses then carry `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` (Unix seconds), counted in days searched. Over the budget, the response is a 429 with a `Retry-After` header. A rejected search uses no budget, so a search with a shorter date range can still go through. The budget resets at midnight UTC.",
        "tags": [
          "Messages"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SearchMessagesBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchMessagesResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request: invalid parameters or request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: the API key doesn't have permission to perform this action",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found: the requested resource doesn't exist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests: you've reached a rate limit. When the response includes a `Retry-After` header, wait that many seconds before you try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/messages/move-to-inbox": {
      "post": {
        "operationId": "moveMessagesToInbox",
        "summary": "Move Messages to Inbox",
        "description": "Moves up to 200 messages back to the inbox in every mailbox your token can reach that holds them, clearing the spam, trash, and Surge Protection placements Material applied. Use this to undo a remediation that turned out to be a false positive. The move runs in the background, so a 200 means the request was accepted and queued, not that any message has moved yet. It is best-effort and safe to repeat: each copy is re-checked against the mail provider at move time, and copies already in the inbox, in mailboxes Material can no longer reach, or on unsupported providers are skipped without failing the request. Because of that, `mailboxCount` is an upper bound rather than a result. The `message_moved_to_inbox` audit log entries carrying a `uAcctId` are the authoritative record of what actually moved; one further entry carrying `numRequested` is written when the request is queued and records its scope, not that anything moved. Message IDs that don't resolve to a message your token can reach come back in `notFound` and aren't queued; if none of them resolve, the response is a 404 and nothing is queued. Requires the Modify Messages permission. An API token carries the permissions of whoever created it, so a token created by someone without that permission receives a 403. Requests are rate limited by the number of messages they target, not by request count, against a single organization-wide budget shared by every token. Successful and 429 responses carry `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` (Unix seconds), counted in messages. A 429 also carries `Retry-After` and spends no budget, so a smaller batch within `X-RateLimit-Remaining` can still go through.",
        "tags": [
          "Messages"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MoveMessagesToInboxBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MoveMessagesToInboxResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request: invalid parameters or request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: the API key doesn't have permission to perform this action",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found: the requested resource doesn't exist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests: you've reached a rate limit. When the response includes a `Retry-After` header, wait that many seconds before you try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/messages/search/{id}": {
      "get": {
        "operationId": "getMessageSearch",
        "summary": "Get Message Search Results",
        "description": "Retrieves the status and results of a message search. Poll this endpoint until `status` is `DONE`, at which point the response includes a paginated list of matching messages.",
        "tags": [
          "Messages"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "description": "The encoded ID of the search job to retrieve. Format: `job.1.<base64>`.",
              "example": "job.1.ZXhhbXBsZV9pZF8xMjM",
              "type": "string",
              "pattern": "^[a-z]{1,6}\\.[0-9]{1,2}\\.[A-Za-z0-9_-]+$"
            },
            "required": true,
            "description": "The encoded ID of the search job to retrieve. Format: `job.1.<base64>`."
          },
          {
            "in": "query",
            "name": "cursor",
            "schema": {
              "description": "The pagination cursor returned in a previous response. Pass this value to retrieve the next page of results.",
              "example": "eyJ2IjoxLCJvIjoyNSwibCI6MjV9",
              "type": "string"
            },
            "description": "The pagination cursor returned in a previous response. Pass this value to retrieve the next page of results."
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "description": "The maximum number of items to return per page.",
              "example": 25,
              "type": "integer"
            },
            "description": "The maximum number of items to return per page."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageSearchResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request: invalid parameters or request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: the API key doesn't have permission to perform this action",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found: the requested resource doesn't exist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests: you've reached a rate limit. When the response includes a `Retry-After` header, wait that many seconds before you try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/trusted-entities": {
      "get": {
        "operationId": "listTrustedEntities",
        "summary": "List Trusted Entities",
        "description": "Retrieves a paginated list of [Trusted Entities](https://docs.material.security/getting-started/fundamentals/detect/trusted-entities): allowlisted domains, email addresses, and IPs or CIDR ranges. Results return system-managed entities first (auto-enrolled domains and Material defaults), then the entities you've created, with each block ordered by entity and then ID. Filter by type, enabled state, and tenant. `totalCount` is not provided for this endpoint, so use `meta.hasMore` and `meta.nextCursor` to page.",
        "tags": [
          "Trusted Entities"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "in": "query",
            "name": "cursor",
            "schema": {
              "description": "The pagination cursor returned in a previous response. Pass this value to retrieve the next page of results.",
              "example": "eyJ2IjoxLCJvIjoyNSwibCI6MjV9",
              "type": "string"
            },
            "description": "The pagination cursor returned in a previous response. Pass this value to retrieve the next page of results."
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "description": "The maximum number of items to return per page.",
              "example": 25,
              "type": "integer"
            },
            "description": "The maximum number of items to return per page."
          },
          {
            "in": "query",
            "name": "type",
            "schema": {
              "description": "Filter by types (comma-separated).",
              "example": "external_domain,internal_domain",
              "type": "string"
            },
            "description": "Filter by types (comma-separated)."
          },
          {
            "in": "query",
            "name": "enabled",
            "schema": {
              "description": "Filter to enabled (true) or disabled (false).",
              "type": "boolean"
            },
            "description": "Filter to enabled (true) or disabled (false)."
          },
          {
            "in": "query",
            "name": "tenant",
            "schema": {
              "description": "Filter to a specific tenant, as an encoded tenant ID (format `tnt.1.<base64>`).",
              "example": "tnt.1.ZXhhbXBsZV9pZF8xMjM",
              "type": "string",
              "pattern": "^[a-z]{1,6}\\.[0-9]{1,2}\\.[A-Za-z0-9_-]+$"
            },
            "description": "Filter to a specific tenant, as an encoded tenant ID (format `tnt.1.<base64>`)."
          },
          {
            "in": "query",
            "name": "include",
            "schema": {
              "description": "Comma-separated includes. Supported: `timeline`.",
              "example": "timeline",
              "type": "string"
            },
            "description": "Comma-separated includes. Supported: `timeline`."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APITrustedEntityCollection"
                }
              }
            }
          },
          "400": {
            "description": "Bad request: invalid parameters or request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: the API key doesn't have permission to perform this action",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found: the requested resource doesn't exist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests: you've reached a rate limit. When the response includes a `Retry-After` header, wait that many seconds before you try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createTrustedEntity",
        "summary": "Create Trusted Entity",
        "description": "Creates a Trusted Entity. `entity` and `type` are required; other settings default to the least-permissive values. Returns 200 with the created entity.",
        "tags": [
          "Trusted Entities"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateTrustedEntityBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APITrustedEntity"
                }
              }
            }
          },
          "400": {
            "description": "Bad request: invalid parameters or request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: the API key doesn't have permission to perform this action",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found: the requested resource doesn't exist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests: you've reached a rate limit. When the response includes a `Retry-After` header, wait that many seconds before you try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/trusted-entities/{id}": {
      "get": {
        "operationId": "getTrustedEntity",
        "summary": "Get Trusted Entity",
        "description": "Retrieves a single Trusted Entity by its encoded ID. Use `include=timeline` to pull the change timeline.",
        "tags": [
          "Trusted Entities"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "description": "Encoded trusted-entity id. Format: `te.1.<base64>`.",
              "example": "te.1.ZXhhbXBsZV9pZF8xMjM",
              "type": "string",
              "pattern": "^[a-z]{1,6}\\.[0-9]{1,2}\\.[A-Za-z0-9_-]+$"
            },
            "required": true,
            "description": "Encoded trusted-entity id. Format: `te.1.<base64>`."
          },
          {
            "in": "query",
            "name": "include",
            "schema": {
              "description": "Comma-separated includes. Supported: `timeline`.",
              "example": "timeline",
              "type": "string"
            },
            "description": "Comma-separated includes. Supported: `timeline`."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APITrustedEntity"
                }
              }
            }
          },
          "400": {
            "description": "Bad request: invalid parameters or request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: the API key doesn't have permission to perform this action",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found: the requested resource doesn't exist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests: you've reached a rate limit. When the response includes a `Retry-After` header, wait that many seconds before you try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "patchTrustedEntity",
        "summary": "Update Trusted Entity",
        "description": "Updates the editable settings of a Trusted Entity. `entity`, `type`, and `tenant` can't be changed after creation. Only provided fields change. Updating a system-managed entity (auto-enrolled or Material) creates a customization that overrides its default settings.",
        "tags": [
          "Trusted Entities"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "description": "Encoded trusted-entity id. Format: `te.1.<base64>`.",
              "example": "te.1.ZXhhbXBsZV9pZF8xMjM",
              "type": "string",
              "pattern": "^[a-z]{1,6}\\.[0-9]{1,2}\\.[A-Za-z0-9_-]+$"
            },
            "required": true,
            "description": "Encoded trusted-entity id. Format: `te.1.<base64>`."
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateTrustedEntityBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APITrustedEntity"
                }
              }
            }
          },
          "400": {
            "description": "Bad request: invalid parameters or request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: the API key doesn't have permission to perform this action",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found: the requested resource doesn't exist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests: you've reached a rate limit. When the response includes a `Retry-After` header, wait that many seconds before you try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteTrustedEntity",
        "summary": "Delete Trusted Entity",
        "description": "Deletes a Trusted Entity by its encoded ID. System-managed entities, whether auto-enrolled or Material defaults, can't be deleted. Returns 204 No Content.",
        "tags": [
          "Trusted Entities"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "description": "Encoded trusted-entity id. Format: `te.1.<base64>`.",
              "example": "te.1.ZXhhbXBsZV9pZF8xMjM",
              "type": "string",
              "pattern": "^[a-z]{1,6}\\.[0-9]{1,2}\\.[A-Za-z0-9_-]+$"
            },
            "required": true,
            "description": "Encoded trusted-entity id. Format: `te.1.<base64>`."
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {}
              }
            }
          }
        },
        "responses": {
          "400": {
            "description": "Bad request: invalid parameters or request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: the API key doesn't have permission to perform this action",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found: the requested resource doesn't exist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests: you've reached a rate limit. When the response includes a `Retry-After` header, wait that many seconds before you try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/trusted-entities/bulk": {
      "post": {
        "operationId": "bulkTrustedEntities",
        "summary": "Bulk Trusted Entities",
        "description": "Applies up to 200 create/update/delete operations in one request, best-effort with per-item results in request order (`results[i]` corresponds to `operations[i]`). A well-formed envelope always returns 200; individual failures are reported in `results[]`.",
        "tags": [
          "Trusted Entities"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkTrustedEntitiesBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BulkTrustedEntitiesResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request: invalid parameters or request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: the API key doesn't have permission to perform this action",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found: the requested resource doesn't exist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests: you've reached a rate limit. When the response includes a `Retry-After` header, wait that many seconds before you try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/oauth-apps": {
      "get": {
        "operationId": "listOauthApps",
        "summary": "List OAuth Apps",
        "description": "Retrieves a paginated list of the third-party OAuth applications observed holding token grants in your Google Workspace tenant (the only provider covered today). By default every app ever observed is returned, including ones whose grants have all been revoked; use `hasActiveAccounts` to narrow to apps in use. Filter by classification, remediation policy, brand verification, restricted scopes, and more, and sort by name, grant times, or account count. The `classification` filter matches the effective classification **Explorer** > **Apps** > **OAuth** shows, so an app with a staged agent recommendation matches that recommendation rather than its committed value.",
        "tags": [
          "OAuth Apps"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "in": "query",
            "name": "cursor",
            "schema": {
              "description": "The pagination cursor returned in a previous response. Pass this value to retrieve the next page of results.",
              "example": "eyJ2IjoxLCJvIjoyNSwibCI6MjV9",
              "type": "string"
            },
            "description": "The pagination cursor returned in a previous response. Pass this value to retrieve the next page of results."
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "description": "The maximum number of items to return per page.",
              "example": 25,
              "type": "integer"
            },
            "description": "The maximum number of items to return per page."
          },
          {
            "in": "query",
            "name": "tenant",
            "schema": {
              "description": "Filter to apps observed in any of these tenants, as comma-separated encoded tenant IDs (format `tnt.1.<base64>`). Matches the `tenantId` field on each app. Results only include tenants your API token can reach, so an ID you can't access matches nothing instead of returning an error. Call `GET /tenants` for the IDs.",
              "example": "tnt.1.ZXhhbXBsZV9pZF8xMjM",
              "type": "string"
            },
            "description": "Filter to apps observed in any of these tenants, as comma-separated encoded tenant IDs (format `tnt.1.<base64>`). Matches the `tenantId` field on each app. Results only include tenants your API token can reach, so an ID you can't access matches nothing instead of returning an error. Call `GET /tenants` for the IDs."
          },
          {
            "in": "query",
            "name": "clientId",
            "schema": {
              "description": "Filter to these Google OAuth client IDs, comma-separated. Exact, case-sensitive match on the `clientId` field, unlike `search`, which is a substring match over the name as well. A client ID is unique within a tenant, so this is the way to resolve a client ID seen in a Google Workspace audit log to an app, and the way to reconcile paged results exactly.",
              "example": "1234567890-abcdefghijklmnop.apps.googleusercontent.com",
              "type": "string"
            },
            "description": "Filter to these Google OAuth client IDs, comma-separated. Exact, case-sensitive match on the `clientId` field, unlike `search`, which is a substring match over the name as well. A client ID is unique within a tenant, so this is the way to resolve a client ID seen in a Google Workspace audit log to an app, and the way to reconcile paged results exactly."
          },
          {
            "in": "query",
            "name": "classification",
            "schema": {
              "description": "Filter by classification, comma-separated. Values: `safe` (Safe), `unnecessary` (Unnecessary), `overprivileged` (Overprivileged), `suspicious` (Suspicious), `malicious` (Malicious), plus `none` for apps with no classification, which **Explorer** > **Apps** > **OAuth** labels Unknown. Matching uses the effective classification: while the OAuth Remediation Agent has a recommendation staged, that recommendation is matched in place of the committed value. A matching app can therefore come back with `classification: null` (or a different committed value) and the matched value in `recommendedClassification`.",
              "example": "suspicious,malicious",
              "type": "string"
            },
            "description": "Filter by classification, comma-separated. Values: `safe` (Safe), `unnecessary` (Unnecessary), `overprivileged` (Overprivileged), `suspicious` (Suspicious), `malicious` (Malicious), plus `none` for apps with no classification, which **Explorer** > **Apps** > **OAuth** labels Unknown. Matching uses the effective classification: while the OAuth Remediation Agent has a recommendation staged, that recommendation is matched in place of the committed value. A matching app can therefore come back with `classification: null` (or a different committed value) and the matched value in `recommendedClassification`."
          },
          {
            "in": "query",
            "name": "remediation",
            "schema": {
              "description": "Filter to apps with a standing remediation policy. `revoke` matches any policy; `notify` matches the subset that also notifies, so `notify` results are always a subset of `revoke` results.",
              "example": "revoke",
              "type": "string",
              "enum": [
                "revoke",
                "notify"
              ]
            },
            "description": "Filter to apps with a standing remediation policy. `revoke` matches any policy; `notify` matches the subset that also notifies, so `notify` results are always a subset of `revoke` results."
          },
          {
            "in": "query",
            "name": "hasActiveAccounts",
            "schema": {
              "description": "Filter to apps that currently have at least one account granting them (`true`) or none (`false`). Omit to list every OAuth app ever observed, including fully revoked ones. Evaluated against a cached count that a tenant-wide revoke does not immediately refresh, so an app can still match `true` for a while after its last grant ends; `meta.numActive` on the accounts collection is the live figure.",
              "type": "boolean"
            },
            "description": "Filter to apps that currently have at least one account granting them (`true`) or none (`false`). Omit to list every OAuth app ever observed, including fully revoked ones. Evaluated against a cached count that a tenant-wide revoke does not immediately refresh, so an app can still match `true` for a while after its last grant ends; `meta.numActive` on the accounts collection is the live figure."
          },
          {
            "in": "query",
            "name": "verified",
            "schema": {
              "description": "Filter by whether Google's brand verification returned a verified brand record.",
              "type": "boolean"
            },
            "description": "Filter by whether Google's brand verification returned a verified brand record."
          },
          {
            "in": "query",
            "name": "internal",
            "schema": {
              "description": "Filter to apps whose brand belongs to one of your own domains. `false` also matches apps where this has not been determined.",
              "type": "boolean"
            },
            "description": "Filter to apps whose brand belongs to one of your own domains. `false` also matches apps where this has not been determined."
          },
          {
            "in": "query",
            "name": "loginOnly",
            "schema": {
              "description": "Filter on `isLoginOnly`. `false` includes apps with no currently granted scopes.",
              "type": "boolean"
            },
            "description": "Filter on `isLoginOnly`. `false` includes apps with no currently granted scopes."
          },
          {
            "in": "query",
            "name": "restrictedScopeType",
            "schema": {
              "description": "Filter to apps granted any of these restricted-scope families, comma-separated. Values: `gmail`, `drive`, `google_fit`, `google_chat`, `data_portability`, `photos_ambient`. Matches on `restrictedScopeTypes`, so it carries that field's staleness.",
              "example": "gmail,drive",
              "type": "string"
            },
            "description": "Filter to apps granted any of these restricted-scope families, comma-separated. Values: `gmail`, `drive`, `google_fit`, `google_chat`, `data_portability`, `photos_ambient`. Matches on `restrictedScopeTypes`, so it carries that field's staleness."
          },
          {
            "in": "query",
            "name": "search",
            "schema": {
              "description": "Case-insensitive substring match on the app name or the Google OAuth client ID.",
              "example": "acme",
              "type": "string"
            },
            "description": "Case-insensitive substring match on the app name or the Google OAuth client ID."
          },
          {
            "in": "query",
            "name": "sort",
            "schema": {
              "description": "Sort field, e.g. `name:asc` or `lastNewGrant:desc`. One field only. Supported: `name`, `firstObserved`, `lastNewGrant`, `activeAccounts`. Defaults to `firstObserved:desc`. `activeAccounts` orders by a cached count that a tenant-wide revoke does not immediately refresh. Apps with equal sort values fall back to a stable internal order, so the sort is deterministic; an app observed while you are paging can still shift later rows, so reconcile by `clientId` when exactness matters.",
              "example": "lastNewGrant:desc",
              "type": "string"
            },
            "description": "Sort field, e.g. `name:asc` or `lastNewGrant:desc`. One field only. Supported: `name`, `firstObserved`, `lastNewGrant`, `activeAccounts`. Defaults to `firstObserved:desc`. `activeAccounts` orders by a cached count that a tenant-wide revoke does not immediately refresh. Apps with equal sort values fall back to a stable internal order, so the sort is deterministic; an app observed while you are paging can still shift later rows, so reconcile by `clientId` when exactness matters."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIOAuthAppCollection"
                }
              }
            }
          },
          "400": {
            "description": "Bad request: invalid parameters or request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: the API key doesn't have permission to perform this action",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found: the requested resource doesn't exist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests: you've reached a rate limit. When the response includes a `Retry-After` header, wait that many seconds before you try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/oauth-apps/{id}": {
      "get": {
        "operationId": "getOauthApp",
        "summary": "Get OAuth App",
        "description": "Retrieves a single OAuth application by its encoded ID, including its Google client ID, currently granted scopes, brand metadata, classification, and standing remediation policy. Use `include=accounts` to also return the first page of the accounts that have granted it, and `include=investigation` for the most recent run of Material's [OAuth Remediation Agent](https://docs.material.security/learn-more/risk-areas/malicious-oauth-apps).",
        "tags": [
          "OAuth Apps"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "description": "The encoded OAuth app ID. Format: `oapp.1.<base64>`.",
              "example": "oapp.1.MDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAw",
              "type": "string",
              "pattern": "^[a-z]{1,6}\\.[0-9]{1,2}\\.[A-Za-z0-9_-]+$"
            },
            "required": true,
            "description": "The encoded OAuth app ID. Format: `oapp.1.<base64>`."
          },
          {
            "in": "query",
            "name": "include",
            "schema": {
              "description": "Comma-separated list of extra fields to return. Supported: `accounts`, the first page of the accounts edge; and `investigation`, which additionally requires permission to read investigation data and returns 403 without it. Use `*` for all of them.",
              "example": "accounts,investigation",
              "type": "string"
            },
            "description": "Comma-separated list of extra fields to return. Supported: `accounts`, the first page of the accounts edge; and `investigation`, which additionally requires permission to read investigation data and returns 403 without it. Use `*` for all of them."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIOAuthApp"
                }
              }
            }
          },
          "400": {
            "description": "Bad request: invalid parameters or request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: the API key doesn't have permission to perform this action",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found: the requested resource doesn't exist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests: you've reached a rate limit. When the response includes a `Retry-After` header, wait that many seconds before you try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "patchOauthApp",
        "summary": "Update OAuth App",
        "description": "Updates an OAuth app's classification and standing remediation policy. All other fields are read-only. Only provided fields change. A remediation containing `revoke` queues revocation of the app's token grants asynchronously, so a 200 records the policy rather than confirming any token is revoked; `[]` removes the policy. Setting a classification also resolves a staged agent recommendation that carries an `issueId`, and mirrors onto the app's open investigation issues.",
        "tags": [
          "OAuth Apps"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "description": "The encoded OAuth app ID. Format: `oapp.1.<base64>`.",
              "example": "oapp.1.MDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAw",
              "type": "string",
              "pattern": "^[a-z]{1,6}\\.[0-9]{1,2}\\.[A-Za-z0-9_-]+$"
            },
            "required": true,
            "description": "The encoded OAuth app ID. Format: `oapp.1.<base64>`."
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateOAuthAppBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIOAuthApp"
                }
              }
            }
          },
          "400": {
            "description": "Bad request: invalid parameters or request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: the API key doesn't have permission to perform this action",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found: the requested resource doesn't exist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests: you've reached a rate limit. When the response includes a `Retry-After` header, wait that many seconds before you try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/oauth-apps/{id}/accounts": {
      "get": {
        "operationId": "listOauthAppAccounts",
        "summary": "List OAuth App Accounts",
        "description": "Retrieves the paginated list of accounts that have granted this OAuth app, ordered by when each account first granted it, newest first. By default every account that ever granted it is listed, including ones whose grants have ended; `active` narrows by grant state and `providerStatus` by account state, and the two are independent. Membership is decided by the grant rather than by the account directory, so this is not a filtered view of `/accounts`. Each item carries the scopes of that account's own grant, which can be narrower than the app-level union, and `meta.numActive` reports how many accounts currently grant the app regardless of the filters.",
        "tags": [
          "OAuth Apps"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "description": "The encoded OAuth app ID. Format: `oapp.1.<base64>`.",
              "example": "oapp.1.MDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAw",
              "type": "string",
              "pattern": "^[a-z]{1,6}\\.[0-9]{1,2}\\.[A-Za-z0-9_-]+$"
            },
            "required": true,
            "description": "The encoded OAuth app ID. Format: `oapp.1.<base64>`."
          },
          {
            "in": "query",
            "name": "cursor",
            "schema": {
              "description": "The pagination cursor returned in a previous response. Pass this value to retrieve the next page of results.",
              "example": "eyJ2IjoxLCJvIjoyNSwibCI6MjV9",
              "type": "string"
            },
            "description": "The pagination cursor returned in a previous response. Pass this value to retrieve the next page of results."
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "description": "The maximum number of items to return per page.",
              "example": 25,
              "type": "integer"
            },
            "description": "The maximum number of items to return per page."
          },
          {
            "in": "query",
            "name": "active",
            "schema": {
              "description": "Filter to accounts that currently grant the app (`true`) or that no longer do (`false`). Omit to list every account that ever granted it.",
              "type": "boolean"
            },
            "description": "Filter to accounts that currently grant the app (`true`) or that no longer do (`false`). Omit to list every account that ever granted it."
          },
          {
            "in": "query",
            "name": "providerStatus",
            "schema": {
              "description": "Filter by the granting account's current state at your provider: `active`, `suspended`, `archived`, or `deleted`. Independent of the grant-level `active` filter: an active account can hold an ended grant, and a suspended one can still hold a live grant. Combine the two to narrow on both.",
              "type": "string",
              "enum": [
                "active",
                "suspended",
                "archived",
                "deleted"
              ]
            },
            "description": "Filter by the granting account's current state at your provider: `active`, `suspended`, `archived`, or `deleted`. Independent of the grant-level `active` filter: an active account can hold an ended grant, and a suspended one can still hold a live grant. Combine the two to narrow on both."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIOAuthAppAccountCollection"
                }
              }
            }
          },
          "400": {
            "description": "Bad request: invalid parameters or request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: the API key doesn't have permission to perform this action",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found: the requested resource doesn't exist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests: you've reached a rate limit. When the response includes a `Retry-After` header, wait that many seconds before you try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/oauth-apps/{id}/accounts/{accountId}/revoke": {
      "post": {
        "operationId": "revokeOauthAppAccount",
        "summary": "Revoke OAuth App Account Grant",
        "description": "Revokes one account's grant of this OAuth app at the provider, and returns the updated entry from the app's accounts collection. This is a one-off action, not a policy: it leaves the app's `remediation` untouched, and if the account grants the app again it will not be revoked automatically. Use `remediation` for that. The whole grant goes, not selected scopes, because the provider offers no per-scope handle. Revoking a grant that has already ended succeeds and changes nothing. Returns 403 if OAuth app remediation is disabled for the account, which is a per-account setting rather than a property of your token.",
        "tags": [
          "OAuth Apps"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "description": "The encoded OAuth app ID. Format: `oapp.1.<base64>`.",
              "example": "oapp.1.MDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAw",
              "type": "string",
              "pattern": "^[a-z]{1,6}\\.[0-9]{1,2}\\.[A-Za-z0-9_-]+$"
            },
            "required": true,
            "description": "The encoded OAuth app ID. Format: `oapp.1.<base64>`."
          },
          {
            "in": "path",
            "name": "accountId",
            "schema": {
              "description": "The encoded ID of the account whose grant of this app is revoked. Format: `acct.1.<base64>`.",
              "example": "acct.1.ZXhhbXBsZV9pZF8xMjM",
              "type": "string",
              "pattern": "^[a-z]{1,6}\\.[0-9]{1,2}\\.[A-Za-z0-9_-]+$"
            },
            "required": true,
            "description": "The encoded ID of the account whose grant of this app is revoked. Format: `acct.1.<base64>`."
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {}
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIOAuthAppAccount"
                }
              }
            }
          },
          "400": {
            "description": "Bad request: invalid parameters or request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: the API key doesn't have permission to perform this action",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found: the requested resource doesn't exist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests: you've reached a rate limit. When the response includes a `Retry-After` header, wait that many seconds before you try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "UpdateIssueBody": {
        "title": "UpdateIssueBody",
        "description": "Fields to update on the issue. Only provided fields are changed.",
        "type": "object",
        "properties": {
          "status": {
            "description": "New [issue status](https://docs.material.security/getting-started/fundamentals/investigate#status): `open`, `in_progress`, `resolved`, `ignored`, or `snoozed`. To ignore or snooze, you must set `ignoreReason` or `snoozedUntil`.",
            "type": "string",
            "enum": [
              "open",
              "in_progress",
              "resolved",
              "ignored",
              "snoozed"
            ]
          },
          "classification": {
            "description": "New issue [classification](https://docs.material.security/getting-started/fundamentals/investigate#status): `safe`, `malicious`, `spam`, or `unknown`. When set without an explicit `remediation`, the default remediation for that classification is applied automatically.",
            "type": "string",
            "enum": [
              "safe",
              "malicious",
              "spam",
              "unknown"
            ]
          },
          "assigneeId": {
            "description": "The encoded account ID of the new assignee. Format: `acct.1.<base64>`. Null to unassign.",
            "example": "acct.1.ZXhhbXBsZV9pZF8xMjM",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "ignoreReason": {
            "description": "Why the status was set to \"ignored\". Setting this will also set the status to \"ignored\".",
            "type": "string",
            "enum": [
              "false_positive",
              "exception"
            ]
          },
          "snoozedUntil": {
            "description": "When the snoozed issue will reopen. Setting this will also set the status to \"snoozed\".",
            "type": "string",
            "format": "date-time"
          },
          "remediation": {
            "description": "The remediation to apply to the issue. The order does not matter. Each must have a `type` from `supportedRemediations`. If conflicting actions are included, only the strictest will be kept. This replaces the list entirely, it is not merged.",
            "type": "array",
            "items": {
              "description": "Remediation actions that can be applied to an issue, when the type is in `issue.supportedRemediations`.",
              "oneOf": [
                {
                  "description": "Remediation actions available for issues with `detection.type=\"email_threat\"`.",
                  "oneOf": [
                    {
                      "description": "Remove the message from the mailbox, and any calendar events it created.",
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "const": "email_threat"
                        },
                        "action": {
                          "type": "string",
                          "const": "delete"
                        }
                      },
                      "required": [
                        "type",
                        "action"
                      ]
                    },
                    {
                      "description": "Remove any calendar events the message created, but leave the message in the mailbox.",
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "const": "email_threat"
                        },
                        "action": {
                          "type": "string",
                          "const": "deleteCalendarEvents"
                        }
                      },
                      "required": [
                        "type",
                        "action"
                      ]
                    },
                    {
                      "description": "Move the message into the spam folder.",
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "const": "email_threat"
                        },
                        "action": {
                          "type": "string",
                          "const": "moveToSpam"
                        }
                      },
                      "required": [
                        "type",
                        "action"
                      ]
                    },
                    {
                      "description": "Add a warning banner to the top of the message.",
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "const": "email_threat"
                        },
                        "action": {
                          "type": "string",
                          "const": "addBanner"
                        },
                        "message": {
                          "description": "The message to put in the banner. If unset, a default is used.",
                          "type": "string"
                        }
                      },
                      "required": [
                        "type",
                        "action"
                      ]
                    },
                    {
                      "description": "Redirect links and attachments to a warning page.",
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "const": "email_threat"
                        },
                        "action": {
                          "type": "string",
                          "const": "redirectLinksAndAttachments"
                        },
                        "mode": {
                          "description": "If `block`, the content is inaccessible. If `speedbump`, the recipient can choose whether to continue to the content after reading the warning.",
                          "type": "string",
                          "enum": [
                            "block",
                            "speedbump"
                          ]
                        }
                      },
                      "required": [
                        "type",
                        "action",
                        "mode"
                      ]
                    },
                    {
                      "description": "Send a response to users that report the message as suspicious.",
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "const": "email_threat"
                        },
                        "action": {
                          "type": "string",
                          "const": "respondToReporters"
                        },
                        "message": {
                          "description": "The message to send to reporting users.",
                          "type": "string"
                        },
                        "sendToPreviousReporters": {
                          "description": "If false / missing, only send to new reporters going forward. If true, also immediately send to all previous reporters.",
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "type",
                        "action"
                      ]
                    }
                  ],
                  "type": "object"
                }
              ],
              "type": "object"
            }
          }
        }
      },
      "UpdateAccountBody": {
        "title": "UpdateAccountBody",
        "type": "object",
        "properties": {
          "vip": {
            "description": "VIP designation and its aliases. Only provided sub-fields change.",
            "$ref": "#/components/schemas/UpdateAccountVip"
          },
          "licenses": {
            "description": "License allocation and the require-mailbox policy.",
            "$ref": "#/components/schemas/UpdateAccountLicenses"
          },
          "settings": {
            "description": "Per-account setting overrides. Only provided settings change.",
            "$ref": "#/components/schemas/UpdateAccountSettings"
          },
          "roles": {
            "description": "[Roles](https://docs.material.security/learn-more/administration/admin-roles) to grant or revoke on this account. Requires role-management permission.",
            "$ref": "#/components/schemas/UpdateRoles"
          }
        }
      },
      "UpdateAccountVip": {
        "title": "UpdateAccountVip",
        "type": "object",
        "properties": {
          "enabled": {
            "description": "Mark (true) or unmark (false) the account as a VIP.",
            "type": "boolean"
          },
          "emailAliases": {
            "description": "Replace the VIP email aliases. An empty array clears them.",
            "example": [],
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "displayNames": {
            "description": "Replace the VIP display names. An empty array clears them.",
            "example": [],
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "UpdateAccountLicenses": {
        "title": "UpdateAccountLicenses",
        "type": "object",
        "properties": {
          "values": {
            "description": "The complete set of licenses the account should end up with, not a list of changes to apply. Include at most one tier, `essentials` (Essentials) or `advanced` (Advanced), and optionally `ato_resilience` (ATO Resilience) alongside it. Send `[]` to allocate none, or `\"default\"` to inherit from a higher level.",
            "example": [
              "advanced",
              "ato_resilience"
            ],
            "anyOf": [
              {
                "type": "array",
                "items": {
                  "type": "string",
                  "enum": [
                    "essentials",
                    "advanced",
                    "ato_resilience"
                  ]
                }
              },
              {
                "type": "string",
                "const": "default"
              }
            ]
          },
          "requireMailbox": {
            "description": "Set `requireMailbox` to `on` or `off`, or to `default` to clear the account override so it inherits from a higher level.",
            "type": "string",
            "enum": [
              "on",
              "off",
              "default"
            ]
          }
        }
      },
      "UpdateAccountSettings": {
        "title": "UpdateAccountSettings",
        "type": "object",
        "properties": {
          "emailSync": {
            "description": "Set `emailSync` to `on` or `off`, or to `default` to clear the account override so it inherits from a higher level.",
            "type": "string",
            "enum": [
              "on",
              "off",
              "default"
            ]
          },
          "fileSync": {
            "description": "Set `fileSync` to `on` or `off`, or to `default` to clear the account override so it inherits from a higher level.",
            "type": "string",
            "enum": [
              "on",
              "off",
              "default"
            ]
          },
          "emailThreatRemediation": {
            "description": "Set `emailThreatRemediation` to `on` or `off`, or to `default` to clear the account override so it inherits from a higher level.",
            "type": "string",
            "enum": [
              "on",
              "off",
              "default"
            ]
          },
          "fileRemediation": {
            "description": "Set `fileRemediation` to `on` or `off`, or to `default` to clear the account override so it inherits from a higher level.",
            "type": "string",
            "enum": [
              "on",
              "off",
              "default"
            ]
          },
          "sensitiveEmailRedaction": {
            "description": "Set `sensitiveEmailRedaction` to `on` or `off`, or to `default` to clear the account override so it inherits from a higher level.",
            "type": "string",
            "enum": [
              "on",
              "off",
              "default"
            ]
          },
          "passwordResetProtection": {
            "description": "Set `passwordResetProtection` to `on` or `off`, or to `default` to clear the account override so it inherits from a higher level.",
            "type": "string",
            "enum": [
              "on",
              "off",
              "default"
            ]
          },
          "oauthAppRemediation": {
            "description": "Set `oauthAppRemediation` to `on` or `off`, or to `default` to clear the account override so it inherits from a higher level.",
            "type": "string",
            "enum": [
              "on",
              "off",
              "default"
            ]
          },
          "emailBombProtection": {
            "description": "Set `emailBombProtection` to `enabled`, `detection_only`, or `disabled`, or to `default` to clear the account override so it inherits from a higher level.",
            "type": "string",
            "enum": [
              "enabled",
              "detection_only",
              "disabled",
              "default"
            ]
          }
        }
      },
      "UpdateRoles": {
        "title": "UpdateRoles",
        "description": "Role assignment changes. Only roles named in `assign` or `remove` are affected, so this is not a full-set replacement. Be careful: nothing stops you from removing your own access or the last Super Admin, and either is irreversible without Material support.",
        "type": "object",
        "properties": {
          "assign": {
            "description": "Roles to grant. Only the listed roles are touched.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/APIRoleAssignment"
            }
          },
          "remove": {
            "description": "Roles to revoke, at every scope the account or group holds them. There is no per-tenant removal, so to narrow a role rather than drop it, re-assign it with a shorter `tenants` list instead. Removing a role that isn't held does nothing, and a role inherited from a group must be removed on that group. Listing an ID in both `assign` and `remove` returns a 400.",
            "example": [
              "analyst"
            ],
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "super_admin",
                "global_analyst",
                "global_issue_responder",
                "global_phishing_simulation_admin",
                "global_analytics_admin",
                "global_settings_admin",
                "tenant_admin",
                "content_admin",
                "drive_content_admin",
                "email_content_admin",
                "analyst",
                "issue_responder",
                "ediscovery_admin",
                "phishing_simulation_admin",
                "analytics_admin",
                "settings_admin",
                "tenant_enroller"
              ]
            }
          }
        }
      },
      "APIRoleAssignment": {
        "title": "APIRoleAssignment",
        "type": "object",
        "properties": {
          "id": {
            "description": "Role ID. The ID carries the role's scope: tenant-scoped IDs (for example `analyst`) require `tenants`, while IDs that apply everywhere (for example `global_analyst`, `super_admin`, `tenant_enroller`) take none. Call `GET /roles` for the IDs available on your instance.",
            "example": "analyst",
            "type": "string",
            "enum": [
              "super_admin",
              "global_analyst",
              "global_issue_responder",
              "global_phishing_simulation_admin",
              "global_analytics_admin",
              "global_settings_admin",
              "tenant_admin",
              "content_admin",
              "drive_content_admin",
              "email_content_admin",
              "analyst",
              "issue_responder",
              "ediscovery_admin",
              "phishing_simulation_admin",
              "analytics_admin",
              "settings_admin",
              "tenant_enroller"
            ]
          },
          "tenants": {
            "description": "Tenants to grant the role in, as encoded tenant IDs (format `tnt.1.<base64>`). Required for tenant-scoped IDs, rejected for the others. Assigning a tenant-scoped role the account or group already holds replaces its tenant list. Call `GET /tenants` for the IDs.",
            "example": [
              "tnt.1.ZXhhbXBsZV9pZF8xMjM"
            ],
            "type": "array",
            "items": {
              "type": "string",
              "pattern": "^[a-z]{1,6}\\.[0-9]{1,2}\\.[A-Za-z0-9_-]+$"
            }
          }
        },
        "required": [
          "id"
        ]
      },
      "BulkAccountsBody": {
        "title": "BulkAccountsBody",
        "type": "object",
        "properties": {
          "operations": {
            "description": "Up to 200 operations, applied best-effort with per-item results.",
            "minItems": 1,
            "maxItems": 200,
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BulkAccountOperation"
            }
          }
        },
        "required": [
          "operations"
        ]
      },
      "BulkAccountOperation": {
        "title": "BulkAccountOperation",
        "type": "object",
        "properties": {
          "method": {
            "description": "The operation to apply. Only `update` is supported.",
            "type": "string",
            "const": "update"
          },
          "id": {
            "description": "The encoded ID of the account to update.",
            "example": "acct.1.ZXhhbXBsZV9pZF8xMjM",
            "type": "string",
            "pattern": "^[a-z]{1,6}\\.[0-9]{1,2}\\.[A-Za-z0-9_-]+$"
          },
          "body": {
            "$ref": "#/components/schemas/UpdateAccountBody"
          }
        },
        "required": [
          "method",
          "id",
          "body"
        ]
      },
      "UpdateGroupBody": {
        "title": "UpdateGroupBody",
        "type": "object",
        "properties": {
          "licenses": {
            "description": "The licenses this group allocates to its members.",
            "$ref": "#/components/schemas/UpdateGroupLicenses"
          },
          "settings": {
            "description": "Per-group setting overrides. Only provided settings change. Name, email, and membership are read-only, since they're synced from your provider.",
            "$ref": "#/components/schemas/UpdateGroupSettings"
          },
          "roles": {
            "description": "[Roles](https://docs.material.security/learn-more/administration/admin-roles) this group grants its members, to add or revoke. Requires role-management permission, and applies to every member of the group.",
            "$ref": "#/components/schemas/UpdateRoles"
          }
        }
      },
      "UpdateGroupLicenses": {
        "title": "UpdateGroupLicenses",
        "type": "object",
        "properties": {
          "values": {
            "description": "The complete set of licenses the group should end up allocating, not a list of changes to apply. Include at most one tier, `essentials` (Essentials) or `advanced` (Advanced), and optionally `ato_resilience` (ATO Resilience) alongside it. Send `[]` to allocate none, or `\"default\"` to inherit from a higher level.",
            "example": [
              "advanced"
            ],
            "anyOf": [
              {
                "type": "array",
                "items": {
                  "type": "string",
                  "enum": [
                    "essentials",
                    "advanced",
                    "ato_resilience"
                  ]
                }
              },
              {
                "type": "string",
                "const": "default"
              }
            ]
          }
        }
      },
      "UpdateGroupSettings": {
        "title": "UpdateGroupSettings",
        "type": "object",
        "properties": {
          "emailSync": {
            "description": "Set `emailSync` to `on` or `off`, or to `default` to clear the group override so it inherits from a higher level.",
            "type": "string",
            "enum": [
              "on",
              "off",
              "default"
            ]
          },
          "fileSync": {
            "description": "Set `fileSync` to `on` or `off`, or to `default` to clear the group override so it inherits from a higher level.",
            "type": "string",
            "enum": [
              "on",
              "off",
              "default"
            ]
          },
          "emailThreatRemediation": {
            "description": "Set `emailThreatRemediation` to `on` or `off`, or to `default` to clear the group override so it inherits from a higher level.",
            "type": "string",
            "enum": [
              "on",
              "off",
              "default"
            ]
          },
          "fileRemediation": {
            "description": "Set `fileRemediation` to `on` or `off`, or to `default` to clear the group override so it inherits from a higher level.",
            "type": "string",
            "enum": [
              "on",
              "off",
              "default"
            ]
          },
          "sensitiveEmailRedaction": {
            "description": "Set `sensitiveEmailRedaction` to `on` or `off`, or to `default` to clear the group override so it inherits from a higher level.",
            "type": "string",
            "enum": [
              "on",
              "off",
              "default"
            ]
          },
          "passwordResetProtection": {
            "description": "Set `passwordResetProtection` to `on` or `off`, or to `default` to clear the group override so it inherits from a higher level.",
            "type": "string",
            "enum": [
              "on",
              "off",
              "default"
            ]
          },
          "oauthAppRemediation": {
            "description": "Set `oauthAppRemediation` to `on` or `off`, or to `default` to clear the group override so it inherits from a higher level.",
            "type": "string",
            "enum": [
              "on",
              "off",
              "default"
            ]
          },
          "emailBombProtection": {
            "description": "Set `emailBombProtection` to `enabled`, `detection_only`, or `disabled`, or to `default` to clear the group override so it inherits from a higher level.",
            "type": "string",
            "enum": [
              "enabled",
              "detection_only",
              "disabled",
              "default"
            ]
          }
        }
      },
      "BulkGroupsBody": {
        "title": "BulkGroupsBody",
        "type": "object",
        "properties": {
          "operations": {
            "description": "Up to 200 operations, applied best-effort with per-item results.",
            "minItems": 1,
            "maxItems": 200,
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BulkGroupOperation"
            }
          }
        },
        "required": [
          "operations"
        ]
      },
      "BulkGroupOperation": {
        "title": "BulkGroupOperation",
        "type": "object",
        "properties": {
          "method": {
            "description": "The operation to apply. Only `update` is supported.",
            "type": "string",
            "const": "update"
          },
          "id": {
            "description": "The encoded ID of the group to update.",
            "example": "grp.1.ZXhhbXBsZV9pZF8xMjM",
            "type": "string",
            "pattern": "^[a-z]{1,6}\\.[0-9]{1,2}\\.[A-Za-z0-9_-]+$"
          },
          "body": {
            "$ref": "#/components/schemas/UpdateGroupBody"
          }
        },
        "required": [
          "method",
          "id",
          "body"
        ]
      },
      "SearchMessagesBody": {
        "title": "SearchMessagesBody",
        "description": "Parameters for starting a message search.",
        "example": {
          "mqlSearchString": "message.from.addresses==\"attacker@example.com\" and message.subject:~\"urgent payment\"",
          "startDate": "2026-01-01T00:00:00.000Z",
          "endDate": "2026-01-02T00:00:00.000Z"
        },
        "type": "object",
        "properties": {
          "mqlSearchString": {
            "description": "The MQL (Material Query Language) query used to filter messages. For example, `message.from.addresses==\"attacker@example.com\" and message.subject:~\"urgent payment\"` finds messages from that sender whose subject contains \"urgent payment.\" The API accepts the same queries as message search in Material, so the easiest way to get valid syntax is to build your query there. Navigate to **Explorer** > **Messages**, then write your query in **Query**, which suggests field names and operators as you type. You can also set up your search in **Quick** or **Prompt**, then switch to **Query** to copy the query it creates. When you send the query in a JSON body, escape its double quotes.",
            "example": "message.from.addresses==\"attacker@example.com\" and message.subject:~\"urgent payment\"",
            "type": "string"
          },
          "startDate": {
            "description": "The start of the search time window in ISO 8601 format.",
            "example": "2026-01-01T00:00:00.000Z",
            "type": "string",
            "format": "date-time"
          },
          "endDate": {
            "description": "The end of the search time window in ISO 8601 format.",
            "example": "2026-01-02T00:00:00.000Z",
            "type": "string",
            "format": "date-time"
          },
          "tenant": {
            "description": "The encoded IDs of the tenants to search (format `tnt.1.<base64>`). If you leave this out, the search covers every tenant your API token can reach. If you include a tenant your token can't reach, the response is a 403. Call `GET /tenants` for the IDs.",
            "example": [
              "tnt.1.ZXhhbXBsZV9pZF8xMjM"
            ],
            "minItems": 1,
            "type": "array",
            "items": {
              "type": "string",
              "pattern": "^[a-z]{1,6}\\.[0-9]{1,2}\\.[A-Za-z0-9_-]+$"
            }
          }
        },
        "required": [
          "mqlSearchString",
          "startDate",
          "endDate"
        ]
      },
      "MoveMessagesToInboxBody": {
        "title": "MoveMessagesToInboxBody",
        "type": "object",
        "properties": {
          "messageIds": {
            "description": "The encoded IDs of the messages to move back to the inbox. Up to 200 per request.",
            "minItems": 1,
            "maxItems": 200,
            "type": "array",
            "items": {
              "description": "An encoded message ID. Format: `msg.1.<base64>`.",
              "example": "msg.1.ZXhhbXBsZV9pZF8xMjM",
              "type": "string",
              "pattern": "^[a-z]{1,6}\\.[0-9]{1,2}\\.[A-Za-z0-9_-]+$"
            }
          }
        },
        "required": [
          "messageIds"
        ]
      },
      "CreateTrustedEntityBody": {
        "title": "CreateTrustedEntityBody",
        "type": "object",
        "properties": {
          "entity": {
            "description": "The domain, email address, or IP/CIDR to allowlist.",
            "example": "evil.com",
            "type": "string"
          },
          "type": {
            "description": "The kind of identity to allowlist: `external_domain`, `internal_domain`, `email_address`, or `ip_address`.",
            "type": "string",
            "enum": [
              "external_domain",
              "internal_domain",
              "email_address",
              "ip_address"
            ]
          },
          "tenant": {
            "description": "The encoded ID of the tenant this entry applies to. Format: `tnt.1.<base64>`. Omit or set to `null` to apply globally across all tenants.",
            "example": "tnt.1.ZXhhbXBsZV9pZF8xMjM",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "enabled": {
            "description": "Whether the entry is active. Defaults to `true`.",
            "default": true,
            "type": "boolean"
          },
          "subdomains": {
            "description": "Whether to also cover subdomains of the domain: `included` or `not_included`. Domain entities only; rejected for email address and IP entities.",
            "type": "string",
            "enum": [
              "included",
              "not_included"
            ]
          },
          "emailThreatDetection": {
            "description": "The [email threat handling](https://docs.material.security/getting-started/fundamentals/detect/trusted-entities#settings-definitions) for messages from this entity: `detect` (detect threats normally), `ignore_mark_safe` (create issues, but mark messages safe), or `ignore_dont_create_issues` (don't create phishing issues). Defaults to `detect`.",
            "default": "detect",
            "type": "string",
            "enum": [
              "detect",
              "ignore_mark_safe",
              "ignore_dont_create_issues"
            ]
          },
          "emailAuthentication": {
            "description": "The [email authentication handling](https://docs.material.security/getting-started/fundamentals/detect/trusted-entities#settings-definitions) for this entity: `enforced` (don't trust messages that fail authentication) or `ignored` (trust messages regardless of authentication). Defaults to `enforced`.",
            "default": "enforced",
            "type": "string",
            "enum": [
              "enforced",
              "ignored"
            ]
          },
          "fileDetection": {
            "description": "The [file detection handling](https://docs.material.security/getting-started/fundamentals/detect/trusted-entities#settings-definitions) for this entity: `treat_as_internal` (treat entity as internal) or `treat_as_external` (treat entity as external). Domain and email address entities only; rejected for IP entities.",
            "type": "string",
            "enum": [
              "treat_as_internal",
              "treat_as_external"
            ]
          }
        },
        "required": [
          "entity",
          "type"
        ]
      },
      "UpdateTrustedEntityBody": {
        "title": "UpdateTrustedEntityBody",
        "type": "object",
        "properties": {
          "enabled": {
            "description": "Whether the entry is active.",
            "type": "boolean"
          },
          "subdomains": {
            "description": "Whether to also cover subdomains of the domain: `included` or `not_included`. Domain entities only.",
            "type": "string",
            "enum": [
              "included",
              "not_included"
            ]
          },
          "emailThreatDetection": {
            "description": "The [email threat handling](https://docs.material.security/getting-started/fundamentals/detect/trusted-entities#settings-definitions) for messages from this entity: `detect` (detect threats normally), `ignore_mark_safe` (create issues, but mark messages safe), or `ignore_dont_create_issues` (don't create phishing issues).",
            "type": "string",
            "enum": [
              "detect",
              "ignore_mark_safe",
              "ignore_dont_create_issues"
            ]
          },
          "emailAuthentication": {
            "description": "The [email authentication handling](https://docs.material.security/getting-started/fundamentals/detect/trusted-entities#settings-definitions) for this entity: `enforced` (don't trust messages that fail authentication) or `ignored` (trust messages regardless of authentication).",
            "type": "string",
            "enum": [
              "enforced",
              "ignored"
            ]
          },
          "fileDetection": {
            "description": "The [file detection handling](https://docs.material.security/getting-started/fundamentals/detect/trusted-entities#settings-definitions) for this entity: `treat_as_internal` (treat entity as internal) or `treat_as_external` (treat entity as external). Domain and email address entities only.",
            "type": "string",
            "enum": [
              "treat_as_internal",
              "treat_as_external"
            ]
          }
        }
      },
      "BulkTrustedEntitiesBody": {
        "title": "BulkTrustedEntitiesBody",
        "type": "object",
        "properties": {
          "operations": {
            "description": "Up to 200 operations, applied best-effort with per-item results.",
            "minItems": 1,
            "maxItems": 200,
            "type": "array",
            "items": {
              "oneOf": [
                {
                  "description": "Create a new Trusted Entity.",
                  "type": "object",
                  "properties": {
                    "method": {
                      "type": "string",
                      "const": "create"
                    },
                    "body": {
                      "$ref": "#/components/schemas/CreateTrustedEntityBody"
                    }
                  },
                  "required": [
                    "method",
                    "body"
                  ]
                },
                {
                  "description": "Update an existing Trusted Entity.",
                  "type": "object",
                  "properties": {
                    "method": {
                      "type": "string",
                      "const": "update"
                    },
                    "id": {
                      "description": "Encoded trusted-entity id. Format: `te.1.<base64>`.",
                      "example": "te.1.ZXhhbXBsZV9pZF8xMjM",
                      "type": "string",
                      "pattern": "^[a-z]{1,6}\\.[0-9]{1,2}\\.[A-Za-z0-9_-]+$"
                    },
                    "body": {
                      "$ref": "#/components/schemas/UpdateTrustedEntityBody"
                    }
                  },
                  "required": [
                    "method",
                    "id",
                    "body"
                  ]
                },
                {
                  "description": "Delete an existing Trusted Entity.",
                  "type": "object",
                  "properties": {
                    "method": {
                      "type": "string",
                      "const": "delete"
                    },
                    "id": {
                      "description": "Encoded trusted-entity id. Format: `te.1.<base64>`.",
                      "example": "te.1.ZXhhbXBsZV9pZF8xMjM",
                      "type": "string",
                      "pattern": "^[a-z]{1,6}\\.[0-9]{1,2}\\.[A-Za-z0-9_-]+$"
                    }
                  },
                  "required": [
                    "method",
                    "id"
                  ]
                }
              ],
              "type": "object"
            }
          }
        },
        "required": [
          "operations"
        ]
      },
      "UpdateOAuthAppBody": {
        "title": "UpdateOAuthAppBody",
        "description": "Only provided fields are changed. An empty body is a no-op.",
        "type": "object",
        "properties": {
          "classification": {
            "description": "Commit a classification. A staged agent recommendation that carries an `issueId` resolves to `accepted` or `rejected`; the value also mirrors onto this app's open investigation issues. There is no way back to unclassified through this API.",
            "example": "suspicious",
            "type": "string",
            "enum": [
              "safe",
              "suspicious",
              "overprivileged",
              "unnecessary",
              "malicious"
            ]
          },
          "remediation": {
            "description": "Replace the standing remediation policy, or send `[]` to remove it. `notify` cannot be sent without `revoke`. Revocation is queued, so a 200 means the policy was recorded, not that any token is revoked yet; removing a policy stops future enforcement but does not restore revoked grants.",
            "example": [
              "revoke"
            ],
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "revoke",
                "notify"
              ]
            }
          }
        }
      },
      "APIIssueCollection": {
        "title": "APIIssueCollection",
        "type": "object",
        "properties": {
          "meta": {
            "$ref": "#/components/schemas/CollectionMeta"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/APIIssue"
            }
          }
        },
        "required": [
          "meta",
          "items"
        ]
      },
      "CollectionMeta": {
        "title": "CollectionMeta",
        "type": "object",
        "properties": {
          "totalCount": {
            "description": "The total number of items matching the query.",
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "limit": {
            "description": "The page size used for this response.",
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "hasMore": {
            "description": "True if more items exist beyond the current page; false otherwise.",
            "type": "boolean"
          },
          "nextCursor": {
            "description": "The cursor to fetch the next page of results. Pass this value to the `cursor` query parameter. Null if no more pages exist.",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "APIIssue": {
        "title": "APIIssue",
        "description": "A security issue representing a detected threat or anomaly. In app, view these under *Issues*.",
        "type": "object",
        "properties": {
          "id": {
            "description": "The encoded issue ID. Format: `iss.1.<base64>`.",
            "example": "iss.1.ZXhhbXBsZV9pZF8xMjM",
            "type": "string"
          },
          "name": {
            "description": "Issue display name derived from the detection rule that triggered it",
            "example": "Credential phishing attempt",
            "type": "string"
          },
          "status": {
            "description": "Current [issue status](https://docs.material.security/getting-started/fundamentals/investigate#status): `open`, `in_progress`, `resolved`, `ignored`, or `snoozed`.",
            "type": "string",
            "enum": [
              "open",
              "in_progress",
              "resolved",
              "ignored",
              "snoozed"
            ]
          },
          "severity": {
            "description": "Issue [severity level](https://docs.material.security/getting-started/fundamentals/detect/detection-severity-framework): `01_info`, `02_low`, `03_medium`, `04_high`, or `05_critical`.",
            "type": "string",
            "enum": [
              "01_info",
              "02_low",
              "03_medium",
              "04_high",
              "05_critical"
            ]
          },
          "classification": {
            "description": "Issue [classification](https://docs.material.security/getting-started/fundamentals/investigate#status): `safe`, `malicious`, `spam`, or `unknown`. Null if the issue isn't classified yet.",
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "safe",
                  "malicious",
                  "spam",
                  "unknown"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "recommendedClassification": {
            "description": "The classification Material suggests for this issue, shown in the console as \"Suggested classification\". This is read-only, and null when there's no suggestion. Check `recommendedClassificationStatus` before you treat a value here as a live suggestion, because on an OAuth app issue a rejected suggestion keeps its value so you can still see the disagreement. Issues use four classification values where an OAuth app uses five, so two different app verdicts can read the same here, and a suggestion that's more severe than the committed `classification` can look like agreement with it. For the exact suggestion, read `recommendedClassification` on the app named by `primaryEntity.oauthApp.id`, which requires OAuth app read access, and confirm its `issueId` matches this issue.",
            "example": "malicious",
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "safe",
                  "malicious",
                  "spam",
                  "unknown"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "recommendedClassificationStatus": {
            "description": "Whether the suggestion in `recommendedClassification` is still live. `staged` means it's awaiting a decision, and is what the console displays in place of `classification`. `accepted` means it became the committed `classification`, whether an admin accepted it or Material committed it automatically. `rejected` means an admin decided otherwise, and only occurs on an OAuth app issue, because an email threat issue withdraws a rejected suggestion instead. Null whenever `recommendedClassification` is null.",
            "example": "staged",
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "staged",
                  "accepted",
                  "rejected"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "analysis": {
            "title": "APIAnalysis",
            "description": "The analysis results for this issue, including insight tags that describe the threat category and techniques.",
            "$ref": "#/components/schemas/APIAnalysis"
          },
          "primaryEntity": {
            "title": "APIPrimaryEntity",
            "description": "The primary entity associated with this issue. Exactly one of the optional fields will be populated based on `type`.",
            "$ref": "#/components/schemas/APIPrimaryEntity"
          },
          "keyEvents": {
            "title": "APIKeyEvents",
            "description": "The key lifecycle timestamps for this issue, including when it was created, opened, and last updated.",
            "$ref": "#/components/schemas/APIKeyEvents"
          },
          "tenantId": {
            "description": "The encoded tenant ID. Format: `tnt.1.<base64>`. Null if no tenant is associated.",
            "example": "tnt.1.ZXhhbXBsZV9pZF8xMjM",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "detection": {
            "title": "APIDetection",
            "description": "The detection rule that triggered this issue.",
            "$ref": "#/components/schemas/APIDetection"
          },
          "assigneeId": {
            "description": "The encoded account ID of the assignee. Format: `acct.1.<base64>`. Null if the issue is unassigned.",
            "example": "acct.1.ZXhhbXBsZV9pZF8xMjM",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "ignoreReason": {
            "description": "Why the status was set to \"ignored\", or null if status is not \"ignored\".",
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "false_positive",
                  "exception",
                  "automatic"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "snoozedUntil": {
            "description": "When the snoozed issue will reopen. Null if the issue is not snoozed.",
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ]
          },
          "supportedRemediations": {
            "description": "The types of remediation action that can be put in the `remediation` list.",
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "email_threat"
              ]
            }
          },
          "remediation": {
            "description": "The remediation to apply to the issue. The order does not matter. Each must have a `type` from `supportedRemediations`.",
            "type": "array",
            "items": {
              "description": "Remediation actions that can be applied to an issue, when the type is in `issue.supportedRemediations`.",
              "oneOf": [
                {
                  "description": "Remediation actions available for issues with `detection.type=\"email_threat\"`.",
                  "oneOf": [
                    {
                      "description": "Remove the message from the mailbox, and any calendar events it created.",
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "const": "email_threat"
                        },
                        "action": {
                          "type": "string",
                          "const": "delete"
                        }
                      },
                      "required": [
                        "type",
                        "action"
                      ]
                    },
                    {
                      "description": "Remove any calendar events the message created, but leave the message in the mailbox.",
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "const": "email_threat"
                        },
                        "action": {
                          "type": "string",
                          "const": "deleteCalendarEvents"
                        }
                      },
                      "required": [
                        "type",
                        "action"
                      ]
                    },
                    {
                      "description": "Move the message into the spam folder.",
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "const": "email_threat"
                        },
                        "action": {
                          "type": "string",
                          "const": "moveToSpam"
                        }
                      },
                      "required": [
                        "type",
                        "action"
                      ]
                    },
                    {
                      "description": "Add a warning banner to the top of the message.",
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "const": "email_threat"
                        },
                        "action": {
                          "type": "string",
                          "const": "addBanner"
                        },
                        "message": {
                          "description": "The message to put in the banner. If unset, a default is used.",
                          "type": "string"
                        }
                      },
                      "required": [
                        "type",
                        "action"
                      ]
                    },
                    {
                      "description": "Redirect links and attachments to a warning page.",
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "const": "email_threat"
                        },
                        "action": {
                          "type": "string",
                          "const": "redirectLinksAndAttachments"
                        },
                        "mode": {
                          "description": "If `block`, the content is inaccessible. If `speedbump`, the recipient can choose whether to continue to the content after reading the warning.",
                          "type": "string",
                          "enum": [
                            "block",
                            "speedbump"
                          ]
                        }
                      },
                      "required": [
                        "type",
                        "action",
                        "mode"
                      ]
                    },
                    {
                      "description": "Send a response to users that report the message as suspicious.",
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "const": "email_threat"
                        },
                        "action": {
                          "type": "string",
                          "const": "respondToReporters"
                        },
                        "message": {
                          "description": "The message to send to reporting users.",
                          "type": "string"
                        },
                        "sendToPreviousReporters": {
                          "description": "If false / missing, only send to new reporters going forward. If true, also immediately send to all previous reporters.",
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "type",
                        "action"
                      ]
                    }
                  ],
                  "type": "object"
                }
              ],
              "type": "object"
            }
          },
          "comments": {
            "description": "Issue comments. Populated when `include=comments`.",
            "type": "object",
            "properties": {
              "meta": {
                "$ref": "#/components/schemas/CollectionMeta"
              },
              "items": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/APIIssueComment"
                }
              }
            },
            "required": [
              "meta",
              "items"
            ]
          },
          "accounts": {
            "description": "The accounts associated with this issue. Populated only when `include=accounts` is specified.",
            "type": "object",
            "properties": {
              "meta": {
                "$ref": "#/components/schemas/CollectionMeta"
              },
              "items": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/APIAccountSummary"
                }
              }
            },
            "required": [
              "meta",
              "items"
            ]
          },
          "messages": {
            "description": "Related messages. Populated when `include=messages`.",
            "type": "object",
            "properties": {
              "meta": {
                "type": "object",
                "properties": {
                  "totalCount": {
                    "description": "The total number of items matching the query.",
                    "type": "integer",
                    "minimum": -9007199254740991,
                    "maximum": 9007199254740991
                  },
                  "limit": {
                    "description": "The page size used for this response.",
                    "type": "integer",
                    "minimum": -9007199254740991,
                    "maximum": 9007199254740991
                  },
                  "hasMore": {
                    "description": "True if more items exist beyond the current page; false otherwise.",
                    "type": "boolean"
                  },
                  "nextCursor": {
                    "description": "The cursor to fetch the next page of results. Pass this value to the `cursor` query parameter. Null if no more pages exist.",
                    "anyOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "numLinks": {
                    "description": "Number of links found in related messages",
                    "type": "integer",
                    "minimum": -9007199254740991,
                    "maximum": 9007199254740991
                  },
                  "numAttachments": {
                    "description": "Number of file attachments in related messages",
                    "type": "integer",
                    "minimum": -9007199254740991,
                    "maximum": 9007199254740991
                  }
                },
                "required": [
                  "numLinks",
                  "numAttachments"
                ]
              },
              "items": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/APIMessageItem"
                }
              }
            },
            "required": [
              "meta",
              "items"
            ]
          },
          "files": {
            "description": "Related files. Populated when `include=files`.",
            "type": "object",
            "properties": {
              "meta": {
                "$ref": "#/components/schemas/CollectionMeta"
              },
              "items": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/APIFileSummary"
                }
              }
            },
            "required": [
              "meta",
              "items"
            ]
          }
        },
        "required": [
          "id",
          "name",
          "status",
          "severity",
          "classification",
          "recommendedClassification",
          "recommendedClassificationStatus",
          "analysis",
          "primaryEntity",
          "keyEvents",
          "tenantId",
          "detection",
          "assigneeId",
          "ignoreReason",
          "snoozedUntil",
          "supportedRemediations",
          "remediation"
        ]
      },
      "APIAnalysis": {
        "title": "APIAnalysis",
        "type": "object",
        "properties": {
          "insights": {
            "description": "Analysis insight tags. Prefixed by type: `category.*`, `subcategory.*`, `ttp.*`. Example: `category.credential_phishing`, `ttp.link_based`.",
            "example": [
              "category.credential_phishing",
              "ttp.link_based"
            ],
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "insights"
        ]
      },
      "APIPrimaryEntity": {
        "title": "APIPrimaryEntity",
        "type": "object",
        "properties": {
          "type": {
            "description": "The entity type. Determines which optional field is populated.",
            "type": "string",
            "enum": [
              "tenant",
              "account",
              "group",
              "file",
              "message",
              "oauth_app"
            ]
          },
          "account": {
            "description": "Present when `type` is `account`",
            "$ref": "#/components/schemas/APIAccountEntity"
          },
          "message": {
            "description": "Present when `type` is `message`",
            "$ref": "#/components/schemas/APIMessageEntity"
          },
          "file": {
            "description": "Present when `type` is `file`",
            "$ref": "#/components/schemas/APIFileEntity"
          },
          "tenant": {
            "description": "Present when `type` is `tenant`",
            "$ref": "#/components/schemas/APITenantEntity"
          },
          "group": {
            "description": "Present when `type` is `group`",
            "$ref": "#/components/schemas/APIGroupEntity"
          },
          "oauthApp": {
            "description": "Present when `type` is `oauth_app`",
            "$ref": "#/components/schemas/APIOAuthAppEntity"
          }
        },
        "required": [
          "type"
        ]
      },
      "APIAccountEntity": {
        "title": "APIAccountEntity",
        "type": "object",
        "properties": {
          "id": {
            "description": "The encoded account ID. Format: `acct.1.<base64>`.",
            "example": "acct.1.ZXhhbXBsZV9pZF8xMjM",
            "type": "string"
          },
          "email": {
            "description": "The Email address of the account. Null if unavailable.",
            "example": "user@example.com",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "displayName": {
            "description": "The display name of the account holder. Null if unavailable.",
            "example": "Jane Doe",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "id",
          "email",
          "displayName"
        ]
      },
      "APIMessageEntity": {
        "title": "APIMessageEntity",
        "type": "object",
        "properties": {
          "id": {
            "description": "The encoded message ID. Format: `msg.1.<base64>`.",
            "example": "msg.1.ZXhhbXBsZV9pZF8xMjM",
            "type": "string"
          },
          "date": {
            "description": "Message date in ISO 8601 format",
            "example": "2024-01-15T12:00:00.000Z",
            "type": "string",
            "format": "date-time"
          },
          "from": {
            "description": "Sender email address",
            "example": "sender@example.com",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "id",
          "date",
          "from"
        ]
      },
      "APIFileEntity": {
        "title": "APIFileEntity",
        "type": "object",
        "properties": {
          "id": {
            "description": "The encoded file ID. Format: `file.1.<base64>`.",
            "example": "file.1.ZXhhbXBsZV9pZF8xMjM",
            "type": "string"
          },
          "name": {
            "description": "File name",
            "example": "invoice.pdf",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "mimeType": {
            "description": "MIME type (e.g. `application/pdf`)",
            "example": "application/pdf",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "id",
          "name",
          "mimeType"
        ]
      },
      "APITenantEntity": {
        "title": "APITenantEntity",
        "type": "object",
        "properties": {
          "id": {
            "description": "The encoded tenant ID, resolvable at `GET /tenants/{id}`. Format: `tnt.1.<base64>`. Looking it up requires the same read permission as accounts and groups.",
            "example": "tnt.1.ZXhhbXBsZV9pZF8xMjM",
            "type": "string"
          },
          "csp": {
            "description": "Cloud service provider (`microsoft` or `google`)",
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "microsoft",
                  "google"
                ]
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "id",
          "csp"
        ]
      },
      "APIGroupEntity": {
        "title": "APIGroupEntity",
        "type": "object",
        "properties": {
          "id": {
            "description": "The encoded group ID. Format: `grp.1.<base64>`.",
            "example": "grp.1.ZXhhbXBsZV9pZF8xMjM",
            "type": "string"
          },
          "name": {
            "description": "The group's display name. Null if Material doesn't have one.",
            "example": "Engineering",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "id",
          "name"
        ]
      },
      "APIOAuthAppEntity": {
        "title": "APIOAuthAppEntity",
        "type": "object",
        "properties": {
          "id": {
            "description": "The encoded OAuth app ID, resolvable at `GET /oauth-apps/{id}`. Format: `oapp.1.<base64>`. Reading it requires OAuth app permissions.",
            "example": "oapp.1.MDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAw",
            "type": "string"
          },
          "name": {
            "description": "The name Google shows on the consent screen. The app publisher chooses it, so it can impersonate a well-known vendor; use the app resource to identify an app. Null when Material has no name for it.",
            "example": "Acme CRM",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "id",
          "name"
        ]
      },
      "APIKeyEvents": {
        "title": "APIKeyEvents",
        "type": "object",
        "properties": {
          "created": {
            "description": "When the issue was first created",
            "type": "object",
            "properties": {
              "at": {
                "description": "ISO 8601 timestamp of the event",
                "type": "string",
                "format": "date-time"
              }
            },
            "required": [
              "at"
            ]
          },
          "opened": {
            "description": "When the issue was last opened",
            "type": "object",
            "properties": {
              "at": {
                "description": "ISO 8601 timestamp of the event",
                "type": "string",
                "format": "date-time"
              }
            },
            "required": [
              "at"
            ]
          },
          "statusChanged": {
            "description": "When the issue status last changed",
            "type": "object",
            "properties": {
              "at": {
                "description": "ISO 8601 timestamp of the event",
                "type": "string",
                "format": "date-time"
              }
            },
            "required": [
              "at"
            ]
          }
        },
        "required": [
          "created",
          "opened",
          "statusChanged"
        ]
      },
      "APIDetection": {
        "title": "APIDetection",
        "type": "object",
        "properties": {
          "id": {
            "description": "The encoded detection ID. Format: `det.1.<base64>`.",
            "example": "det.1.ZXhhbXBsZV9pZF8xMjM",
            "type": "string"
          },
          "name": {
            "description": "The display name of the detection rule. Null if the rule is unnamed.",
            "example": "Credential phishing",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "description": {
            "description": "The description saved on the detection that raised this issue. Null when the detection has no description, or when your token can't read that detection.",
            "example": "Detects messages containing links to credential harvesting pages.",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "type": {
            "description": "Detection type: `vulnerability`, `threat`, or `email_threat`.",
            "type": "string",
            "enum": [
              "vulnerability",
              "threat",
              "email_threat"
            ]
          }
        },
        "required": [
          "id",
          "name",
          "description",
          "type"
        ]
      },
      "APIIssueComment": {
        "title": "APIIssueComment",
        "type": "object",
        "properties": {
          "comment": {
            "description": "Comment text",
            "example": "Confirmed email threat, remediated mailboxes.",
            "type": "string"
          },
          "author": {
            "description": "Author name or email",
            "example": "admin@example.com",
            "type": "string"
          },
          "timestamp": {
            "description": "When the comment was posted",
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "comment",
          "author",
          "timestamp"
        ]
      },
      "APIAccountSummary": {
        "title": "APIAccountSummary",
        "type": "object",
        "properties": {
          "id": {
            "description": "The encoded account ID. Format: `acct.1.<base64>`.",
            "example": "acct.1.ZXhhbXBsZV9pZF8xMjM",
            "type": "string"
          },
          "email": {
            "description": "Email address (included with `include=accounts.email`)",
            "example": "user@example.com",
            "type": "string"
          },
          "displayName": {
            "description": "Display name (included with `include=accounts.displayName`)",
            "example": "Jane Doe",
            "type": "string"
          }
        },
        "required": [
          "id"
        ]
      },
      "APIMessageItem": {
        "title": "APIMessageItem",
        "type": "object",
        "properties": {
          "id": {
            "description": "The encoded message ID. Format: `msg.1.<base64>`.",
            "example": "msg.1.ZXhhbXBsZV9pZF8xMjM",
            "type": "string"
          },
          "headers": {
            "$ref": "#/components/schemas/APIMessageItemHeaders"
          }
        },
        "required": [
          "id"
        ]
      },
      "APIMessageItemHeaders": {
        "title": "APIMessageItemHeaders",
        "type": "object",
        "properties": {
          "date": {
            "description": "The date the message was sent, in ISO 8601 format.",
            "example": "2026-10-07T12:00:00.000Z",
            "type": "string",
            "format": "date-time"
          },
          "from": {
            "description": "The sender address of the message. Null if unavailable.",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "date",
          "from"
        ]
      },
      "APIFileSummary": {
        "title": "APIFileSummary",
        "type": "object",
        "properties": {
          "id": {
            "description": "The encoded file ID. Format: `file.1.<base64>`.",
            "example": "file.1.ZXhhbXBsZV9pZF8xMjM",
            "type": "string"
          },
          "name": {
            "description": "File name (included with `include=files.name`)",
            "example": "invoice.pdf",
            "type": "string"
          },
          "mimeType": {
            "description": "MIME type (included with `include=files.mimeType`)",
            "example": "application/pdf",
            "type": "string"
          }
        },
        "required": [
          "id"
        ]
      },
      "ValidationErrorResponse": {
        "title": "Validation Error Response",
        "type": "object",
        "properties": {
          "error": {
            "description": "A human-readable description of the error.",
            "type": "string"
          },
          "validationFailures": {
            "description": "List of individual validation failures when request parameters fail schema validation.",
            "type": "array",
            "items": {
              "type": "object",
              "properties": {},
              "additionalProperties": {}
            }
          }
        },
        "required": [
          "error"
        ]
      },
      "ErrorResponse": {
        "title": "Error Response",
        "type": "object",
        "properties": {
          "error": {
            "description": "A human-readable description of the error.",
            "type": "string"
          }
        },
        "required": [
          "error"
        ]
      },
      "APIAccountCollection": {
        "title": "APIAccountCollection",
        "type": "object",
        "properties": {
          "meta": {
            "$ref": "#/components/schemas/CollectionMeta"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/APIAccount"
            }
          }
        },
        "required": [
          "meta",
          "items"
        ]
      },
      "APIAccount": {
        "title": "APIAccount",
        "description": "A directory account (person or mailbox) synced from Google or Microsoft.",
        "type": "object",
        "properties": {
          "id": {
            "description": "The encoded account ID. Format: `acct.1.<base64>`.",
            "example": "acct.1.ZXhhbXBsZV9pZF8xMjM",
            "type": "string"
          },
          "type": {
            "description": "The provider the account is synced from: `google` or `microsoft`.",
            "example": "google",
            "type": "string",
            "enum": [
              "google",
              "microsoft"
            ]
          },
          "tenantId": {
            "description": "The encoded ID of the tenant the account belongs to, resolvable at `GET /tenants/{id}`. Format: `tnt.1.<base64>`. Null if the account isn't in the directory of a tenant connected to Material.",
            "example": "tnt.1.ZXhhbXBsZV9pZF8xMjM",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "displayName": {
            "description": "Display name of the account holder.",
            "example": "Jane Doe",
            "type": "string"
          },
          "givenName": {
            "description": "Given (first) name; null if unavailable.",
            "example": "Jane",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "familyName": {
            "description": "Family (last) name; null if unavailable.",
            "example": "Doe",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "email": {
            "description": "Primary email address.",
            "example": "jane@example.com",
            "type": "string"
          },
          "aliases": {
            "description": "Additional email aliases.",
            "example": [
              "jane.doe@example.com"
            ],
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "organization": {
            "description": "The organization on the account's directory record. Read-only, and null if your provider doesn't supply one.",
            "example": "Engineering",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "vip": {
            "$ref": "#/components/schemas/APIVip"
          },
          "licenses": {
            "$ref": "#/components/schemas/APILicenses"
          },
          "roles": {
            "description": "The [roles](https://docs.material.security/learn-more/administration/admin-roles) in effect on this account, both assigned directly and inherited from groups. A role held at a broader scope hides the narrower assignments it supersedes, so this reflects effective access rather than every assignment on record. Empty if the account has no roles. Roles are scoped to this account, so if one person owns several accounts, each reports only its own roles, matching the `role` filter.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/APIAssignedRole"
            }
          },
          "settings": {
            "$ref": "#/components/schemas/APIAccountSettings"
          },
          "mailbox": {
            "$ref": "#/components/schemas/APIAccountMailbox"
          },
          "providerStatus": {
            "description": "The account's state at your provider: `active`, `suspended`, `archived`, or `deleted`.",
            "example": "active",
            "type": "string",
            "enum": [
              "active",
              "suspended",
              "archived",
              "deleted"
            ]
          },
          "lastSyncedAt": {
            "description": "When Material last synced this account, in ISO 8601 format. Null if it has never synced.",
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ]
          },
          "groups": {
            "description": "Group memberships; populated only when `include=groups`.",
            "type": "object",
            "properties": {
              "meta": {
                "$ref": "#/components/schemas/CollectionMeta"
              },
              "items": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/APIGroupRef"
                }
              }
            },
            "required": [
              "meta",
              "items"
            ]
          }
        },
        "required": [
          "id",
          "type",
          "tenantId",
          "displayName",
          "givenName",
          "familyName",
          "email",
          "aliases",
          "organization",
          "vip",
          "licenses",
          "roles",
          "settings",
          "mailbox",
          "providerStatus",
          "lastSyncedAt"
        ]
      },
      "APIVip": {
        "title": "APIVip",
        "type": "object",
        "properties": {
          "enabled": {
            "description": "Whether the account is marked as a [VIP](https://docs.material.security/learn-more/risk-areas/email-threats/detect/vip-impersonation).",
            "type": "boolean"
          },
          "emailAliases": {
            "description": "Additional email aliases treated as this VIP.",
            "example": [],
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "displayNames": {
            "description": "Additional display names treated as this VIP.",
            "example": [],
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "enabled",
          "emailAliases",
          "displayNames"
        ]
      },
      "APILicenses": {
        "title": "APILicenses",
        "type": "object",
        "properties": {
          "values": {
            "description": "The licenses active on this account: `essentials` (Essentials), `advanced` (Advanced), or `ato_resilience` (ATO Resilience). An account carries at most one tier, `essentials` or `advanced`, and can add `ato_resilience` alongside it.",
            "example": [
              "advanced",
              "ato_resilience"
            ],
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "essentials",
                "advanced",
                "ato_resilience"
              ]
            }
          },
          "source": {
            "description": "Which layer the effective value comes from: `account`, `group`, `tenant`, or `default`. See [Settings Hierarchy](https://docs.material.security/learn-more/administration/settings-hierarchy) for how the layers resolve.",
            "example": "group",
            "type": "string",
            "enum": [
              "account",
              "group",
              "tenant",
              "default"
            ]
          },
          "sourceGroups": {
            "description": "The group(s) responsible for the value; present only when `source` is `group`.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/APIGroupRef"
            }
          },
          "sourceTenant": {
            "description": "The tenant responsible for the value, as an encoded tenant ID (format `tnt.1.<base64>`). Present only when `source` is `tenant`.",
            "example": "tnt.1.ZXhhbXBsZV9pZF8xMjM",
            "type": "string"
          },
          "requireMailbox": {
            "title": "APIBooleanSetting",
            "description": "Whether an account needs a provisioned mailbox before Material assigns it a license. Shown as **Require mailbox for license assignment** on an account's **Settings** tab.",
            "$ref": "#/components/schemas/APIBooleanSetting"
          }
        },
        "required": [
          "values",
          "source",
          "requireMailbox"
        ]
      },
      "APIGroupRef": {
        "title": "APIGroupRef",
        "type": "object",
        "properties": {
          "id": {
            "description": "The encoded group ID. Format: `grp.1.<base64>`.",
            "example": "grp.1.ZXhhbXBsZV9pZF8xMjM",
            "type": "string"
          },
          "name": {
            "description": "The group's display name. Null if Material doesn't have one.",
            "example": "Engineering",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "id",
          "name"
        ]
      },
      "APIBooleanSetting": {
        "title": "APIBooleanSetting",
        "type": "object",
        "properties": {
          "enabled": {
            "description": "Whether the setting is enabled for this account.",
            "type": "boolean"
          },
          "source": {
            "description": "Which layer the effective value comes from: `account`, `group`, `tenant`, or `default`. See [Settings Hierarchy](https://docs.material.security/learn-more/administration/settings-hierarchy) for how the layers resolve.",
            "example": "group",
            "type": "string",
            "enum": [
              "account",
              "group",
              "tenant",
              "default"
            ]
          },
          "sourceGroups": {
            "description": "The group(s) responsible for the value; present only when `source` is `group`.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/APIGroupRef"
            }
          },
          "sourceTenant": {
            "description": "The tenant responsible for the value, as an encoded tenant ID (format `tnt.1.<base64>`). Present only when `source` is `tenant`.",
            "example": "tnt.1.ZXhhbXBsZV9pZF8xMjM",
            "type": "string"
          }
        },
        "required": [
          "enabled",
          "source"
        ]
      },
      "APIAssignedRole": {
        "title": "APIAssignedRole",
        "type": "object",
        "properties": {
          "id": {
            "description": "Role ID. The ID carries the role's scope: tenant-scoped IDs (for example `analyst`) require `tenants`, while IDs that apply everywhere (for example `global_analyst`, `super_admin`, `tenant_enroller`) take none. Call `GET /roles` for the IDs available on your instance.",
            "example": "analyst",
            "type": "string",
            "enum": [
              "super_admin",
              "global_analyst",
              "global_issue_responder",
              "global_phishing_simulation_admin",
              "global_analytics_admin",
              "global_settings_admin",
              "tenant_admin",
              "content_admin",
              "drive_content_admin",
              "email_content_admin",
              "analyst",
              "issue_responder",
              "ediscovery_admin",
              "phishing_simulation_admin",
              "analytics_admin",
              "settings_admin",
              "tenant_enroller"
            ]
          },
          "tenants": {
            "description": "Tenants the role applies to, as encoded tenant IDs (format `tnt.1.<base64>`), for tenant-scoped IDs only. `\"all\"` means the role was granted across every tenant, which you can read but not assign through this API.",
            "example": [
              "tnt.1.ZXhhbXBsZV9pZF8xMjM"
            ],
            "anyOf": [
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              {
                "type": "string",
                "const": "all"
              }
            ]
          },
          "source": {
            "description": "Where the assignment comes from: `direct` (on this account), `group` (inherited from `groupId`, and changeable only on that group), or `tenant` (granted to everyone in the tenant).",
            "type": "string",
            "enum": [
              "direct",
              "group",
              "tenant"
            ]
          },
          "groupId": {
            "description": "The granting group; present only when `source` is `group`.",
            "example": "grp.1.ZXhhbXBsZV9pZF8xMjM",
            "type": "string"
          }
        },
        "required": [
          "id",
          "source"
        ]
      },
      "APIAccountSettings": {
        "title": "APIAccountSettings",
        "description": "Each product setting's effective value for this account, with the layer it resolves from. See [Group and Account Customization](https://docs.material.security/learn-more/administration/group-and-account-customization) for how overrides work.",
        "type": "object",
        "properties": {
          "emailSync": {
            "title": "APIBooleanSetting",
            "description": "Whether Material syncs this account's mailbox. Shown as **Mailbox Syncing** on an account's **Settings** tab.",
            "$ref": "#/components/schemas/APIBooleanSetting"
          },
          "fileSync": {
            "title": "APIBooleanSetting",
            "description": "Whether Material syncs this account's Google Drive files. Shown as **My Drive Syncing** on an account's **Settings** tab.",
            "$ref": "#/components/schemas/APIBooleanSetting"
          },
          "emailThreatRemediation": {
            "title": "APIBooleanSetting",
            "description": "Whether Material remediates email threats found in this mailbox. Shown as **Email Threat Remediation** on an account's **Settings** tab.",
            "$ref": "#/components/schemas/APIBooleanSetting"
          },
          "fileRemediation": {
            "title": "APIBooleanSetting",
            "description": "Whether Material remediates risky sharing on this account's files. Shown as **File Remediation** on an account's **Settings** tab.",
            "$ref": "#/components/schemas/APIBooleanSetting"
          },
          "sensitiveEmailRedaction": {
            "title": "APIBooleanSetting",
            "description": "Whether Material redacts sensitive email in this mailbox. Shown as **Email Redaction** on an account's **Settings** tab.",
            "$ref": "#/components/schemas/APIBooleanSetting"
          },
          "passwordResetProtection": {
            "title": "APIBooleanSetting",
            "description": "Whether Material holds password reset and app signup email for this account during a lockdown. Shown as **Email Identity Protection** on an account's **Settings** tab.",
            "$ref": "#/components/schemas/APIBooleanSetting"
          },
          "oauthAppRemediation": {
            "title": "APIBooleanSetting",
            "description": "Whether Material revokes this account's grants when an OAuth app carries a revoke policy. Shown as **OAuth App Remediation** on an account's **Settings** tab.",
            "$ref": "#/components/schemas/APIBooleanSetting"
          },
          "emailBombProtection": {
            "title": "APIModeSetting",
            "description": "How Material handles a sudden surge of email to this account. Shown as **[Email Bomb Protection](https://docs.material.security/learn-more/risk-areas/email-threats/detect/material-email-threat-detections/email-bomb-protection)** on an account's **Settings** tab.",
            "$ref": "#/components/schemas/APIModeSetting"
          }
        },
        "required": [
          "emailSync",
          "fileSync",
          "emailThreatRemediation",
          "fileRemediation",
          "sensitiveEmailRedaction",
          "passwordResetProtection",
          "oauthAppRemediation",
          "emailBombProtection"
        ]
      },
      "APIModeSetting": {
        "title": "APIModeSetting",
        "type": "object",
        "properties": {
          "mode": {
            "description": "How [Email Bomb Protection](https://docs.material.security/learn-more/risk-areas/email-threats/detect/material-email-threat-detections/email-bomb-protection) is applied to this account: `enabled` (detect and remediate), `detection_only` (detect without remediating), or `disabled`.",
            "example": "enabled",
            "type": "string",
            "enum": [
              "enabled",
              "detection_only",
              "disabled"
            ]
          },
          "source": {
            "description": "Which layer the effective value comes from: `account`, `group`, `tenant`, or `default`. See [Settings Hierarchy](https://docs.material.security/learn-more/administration/settings-hierarchy) for how the layers resolve.",
            "example": "group",
            "type": "string",
            "enum": [
              "account",
              "group",
              "tenant",
              "default"
            ]
          },
          "sourceGroups": {
            "description": "The group(s) responsible for the value; present only when `source` is `group`.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/APIGroupRef"
            }
          },
          "sourceTenant": {
            "description": "The tenant responsible for the value, as an encoded tenant ID (format `tnt.1.<base64>`). Present only when `source` is `tenant`.",
            "example": "tnt.1.ZXhhbXBsZV9pZF8xMjM",
            "type": "string"
          }
        },
        "required": [
          "mode",
          "source"
        ]
      },
      "APIAccountMailbox": {
        "title": "APIAccountMailbox",
        "type": "object",
        "properties": {
          "status": {
            "description": "Whether the account's mailbox is `enabled` or `disabled`. Null when Material hasn't synced the mailbox or can't determine its state.",
            "example": "enabled",
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "enabled",
                  "disabled"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "messageCount": {
            "description": "Approximate number of messages in the mailbox. Null if unknown.",
            "example": 3543,
            "anyOf": [
              {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "status",
          "messageCount"
        ]
      },
      "BulkAccountsResponse": {
        "title": "BulkAccountsResponse",
        "type": "object",
        "properties": {
          "results": {
            "description": "Per-operation result, in the same order as the request.",
            "type": "array",
            "items": {
              "oneOf": [
                {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "const": "ok"
                    },
                    "index": {
                      "type": "integer",
                      "minimum": -9007199254740991,
                      "maximum": 9007199254740991
                    },
                    "id": {
                      "type": "string"
                    },
                    "account": {
                      "$ref": "#/components/schemas/APIAccount"
                    }
                  },
                  "required": [
                    "status",
                    "index",
                    "id",
                    "account"
                  ]
                },
                {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "const": "error"
                    },
                    "index": {
                      "type": "integer",
                      "minimum": -9007199254740991,
                      "maximum": 9007199254740991
                    },
                    "id": {
                      "type": "string"
                    },
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "status",
                    "index",
                    "id",
                    "error"
                  ]
                }
              ],
              "type": "object"
            }
          },
          "meta": {
            "description": "Aggregate counts across all operations.",
            "type": "object",
            "properties": {
              "succeeded": {
                "description": "Number of operations that succeeded.",
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              },
              "failed": {
                "description": "Number of operations that failed.",
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              }
            },
            "required": [
              "succeeded",
              "failed"
            ]
          }
        },
        "required": [
          "results",
          "meta"
        ]
      },
      "APIGroupCollection": {
        "title": "APIGroupCollection",
        "type": "object",
        "properties": {
          "meta": {
            "$ref": "#/components/schemas/CollectionMeta"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/APIGroup"
            }
          }
        },
        "required": [
          "meta",
          "items"
        ]
      },
      "APIGroup": {
        "title": "APIGroup",
        "description": "A directory group (Google group, Google org unit, Microsoft group, or Okta group) synced from your provider. A group can span several tenants and its own override is instance-wide, so `settings` reports how that override resolves inside each tenant your token can access.",
        "type": "object",
        "properties": {
          "id": {
            "description": "The encoded group ID. Format: `grp.1.<base64>`.",
            "example": "grp.1.ZXhhbXBsZV9pZF8xMjM",
            "type": "string"
          },
          "type": {
            "description": "The kind of directory group: `okta` (Okta), `google_group` (Google Group), `google_org_unit` (Google Org Unit), or `microsoft_group` (Microsoft Group).",
            "example": "google_group",
            "type": "string",
            "enum": [
              "okta",
              "google_group",
              "google_org_unit",
              "microsoft_group"
            ]
          },
          "name": {
            "description": "The group's display name.",
            "example": "Engineering",
            "type": "string"
          },
          "description": {
            "description": "The group's description. An empty string if it has none.",
            "example": "All engineering staff",
            "type": "string"
          },
          "email": {
            "description": "The group's primary email address. Null for Okta groups and Google org units, which have none.",
            "example": "engineering@example.com",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "aliases": {
            "description": "Additional email addresses that reach this group.",
            "example": [],
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "memberCount": {
            "description": "Estimated number of accounts in the group.",
            "example": 42,
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "roles": {
            "description": "The [roles](https://docs.material.security/learn-more/administration/admin-roles) this group grants to every one of its members. Empty if the group grants none. Members see these on their own account with `source: \"group\"`.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/APIGroupAssignedRole"
            }
          },
          "settings": {
            "description": "Settings and licenses resolved per tenant, with one entry per tenant your token can access, ordered by tenant ID. Empty when your token can access none of them. To read what the group itself overrides, which is what `PATCH` writes and what the `setting.<name>` and `hasOverrides` filters match, take the entries of any tenant whose `source` is `group`.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/APIGroupTenantSettings"
            }
          },
          "lastUpdatedAt": {
            "description": "When Material last synced this group, in ISO 8601 format. Null if it has never synced.",
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "id",
          "type",
          "name",
          "description",
          "email",
          "aliases",
          "memberCount",
          "roles",
          "settings",
          "lastUpdatedAt"
        ]
      },
      "APIGroupAssignedRole": {
        "title": "APIGroupAssignedRole",
        "type": "object",
        "properties": {
          "id": {
            "description": "Role ID. The ID carries the role's scope: tenant-scoped IDs (for example `analyst`) require `tenants`, while IDs that apply everywhere (for example `global_analyst`, `super_admin`, `tenant_enroller`) take none. Call `GET /roles` for the IDs available on your instance.",
            "example": "analyst",
            "type": "string",
            "enum": [
              "super_admin",
              "global_analyst",
              "global_issue_responder",
              "global_phishing_simulation_admin",
              "global_analytics_admin",
              "global_settings_admin",
              "tenant_admin",
              "content_admin",
              "drive_content_admin",
              "email_content_admin",
              "analyst",
              "issue_responder",
              "ediscovery_admin",
              "phishing_simulation_admin",
              "analytics_admin",
              "settings_admin",
              "tenant_enroller"
            ]
          },
          "tenants": {
            "description": "Tenants the role applies to, as encoded tenant IDs (format `tnt.1.<base64>`), for tenant-scoped IDs only. `\"all\"` means the role was granted across every tenant, which you can read but not assign through this API.",
            "example": [
              "tnt.1.ZXhhbXBsZV9pZF8xMjM"
            ],
            "anyOf": [
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              {
                "type": "string",
                "const": "all"
              }
            ]
          }
        },
        "required": [
          "id"
        ]
      },
      "APIGroupTenantSettings": {
        "title": "APIGroupTenantSettings",
        "type": "object",
        "properties": {
          "tenant": {
            "description": "The tenant these values are resolved in, as an encoded tenant ID (format `tnt.1.<base64>`).",
            "example": "tnt.1.ZXhhbXBsZV9pZF8xMjM",
            "type": "string"
          },
          "settings": {
            "$ref": "#/components/schemas/APIGroupSettings"
          },
          "licenses": {
            "$ref": "#/components/schemas/APIGroupLicenses"
          }
        },
        "required": [
          "tenant",
          "settings",
          "licenses"
        ]
      },
      "APIGroupSettings": {
        "title": "APIGroupSettings",
        "description": "Each product setting's effective value for this group in one tenant, with the layer it resolves from. See [Group and Account Customization](https://docs.material.security/learn-more/administration/group-and-account-customization) for how overrides work.",
        "type": "object",
        "properties": {
          "emailSync": {
            "title": "APIGroupBooleanSetting",
            "description": "Whether Material syncs members' mailboxes. Shown as **Mailbox Syncing** on a group's **Settings** tab.",
            "$ref": "#/components/schemas/APIGroupBooleanSetting"
          },
          "fileSync": {
            "title": "APIGroupBooleanSetting",
            "description": "Whether Material syncs members' Google Drive files. Shown as **My Drive Syncing** on a group's **Settings** tab.",
            "$ref": "#/components/schemas/APIGroupBooleanSetting"
          },
          "emailThreatRemediation": {
            "title": "APIGroupBooleanSetting",
            "description": "Whether Material remediates email threats found in members' mailboxes. Shown as **Email Threat Remediation** on a group's **Settings** tab.",
            "$ref": "#/components/schemas/APIGroupBooleanSetting"
          },
          "fileRemediation": {
            "title": "APIGroupBooleanSetting",
            "description": "Whether Material remediates risky sharing on members' files. Shown as **File Remediation** on a group's **Settings** tab.",
            "$ref": "#/components/schemas/APIGroupBooleanSetting"
          },
          "sensitiveEmailRedaction": {
            "title": "APIGroupBooleanSetting",
            "description": "Whether Material redacts sensitive email in members' mailboxes. Shown as **Email Redaction** on a group's **Settings** tab.",
            "$ref": "#/components/schemas/APIGroupBooleanSetting"
          },
          "passwordResetProtection": {
            "title": "APIGroupBooleanSetting",
            "description": "Whether Material holds password reset and app signup email for members during a lockdown. Shown as **Email Identity Protection** on a group's **Settings** tab.",
            "$ref": "#/components/schemas/APIGroupBooleanSetting"
          },
          "oauthAppRemediation": {
            "title": "APIGroupBooleanSetting",
            "description": "Whether Material revokes members' grants when an OAuth app carries a revoke policy. Shown as **OAuth App Remediation** on a group's **Settings** tab.",
            "$ref": "#/components/schemas/APIGroupBooleanSetting"
          },
          "emailBombProtection": {
            "title": "APIGroupModeSetting",
            "description": "How Material handles a sudden surge of email to a member. Shown as **[Email Bomb Protection](https://docs.material.security/learn-more/risk-areas/email-threats/detect/material-email-threat-detections/email-bomb-protection)** on a group's **Settings** tab.",
            "$ref": "#/components/schemas/APIGroupModeSetting"
          }
        },
        "required": [
          "emailSync",
          "fileSync",
          "emailThreatRemediation",
          "fileRemediation",
          "sensitiveEmailRedaction",
          "passwordResetProtection",
          "oauthAppRemediation",
          "emailBombProtection"
        ]
      },
      "APIGroupBooleanSetting": {
        "title": "APIGroupBooleanSetting",
        "type": "object",
        "properties": {
          "enabled": {
            "description": "Whether the setting is enabled for this group in this tenant.",
            "type": "boolean"
          },
          "source": {
            "description": "Which layer the effective value comes from: `group`, `tenant`, or `default`. Groups have no account layer. See [Settings Hierarchy](https://docs.material.security/learn-more/administration/settings-hierarchy) for how the layers resolve.",
            "example": "group",
            "type": "string",
            "enum": [
              "group",
              "tenant",
              "default"
            ]
          }
        },
        "required": [
          "enabled",
          "source"
        ]
      },
      "APIGroupModeSetting": {
        "title": "APIGroupModeSetting",
        "type": "object",
        "properties": {
          "mode": {
            "description": "How [Email Bomb Protection](https://docs.material.security/learn-more/risk-areas/email-threats/detect/material-email-threat-detections/email-bomb-protection) is applied to this group's members in this tenant: `enabled` (detect and remediate), `detection_only` (detect without remediating), or `disabled`.",
            "example": "enabled",
            "type": "string",
            "enum": [
              "enabled",
              "detection_only",
              "disabled"
            ]
          },
          "source": {
            "description": "Which layer the effective value comes from: `group`, `tenant`, or `default`. Groups have no account layer. See [Settings Hierarchy](https://docs.material.security/learn-more/administration/settings-hierarchy) for how the layers resolve.",
            "example": "group",
            "type": "string",
            "enum": [
              "group",
              "tenant",
              "default"
            ]
          }
        },
        "required": [
          "mode",
          "source"
        ]
      },
      "APIGroupLicenses": {
        "title": "APIGroupLicenses",
        "type": "object",
        "properties": {
          "values": {
            "description": "The licenses this group allocates to its members: `essentials` (Essentials), `advanced` (Advanced), or `ato_resilience` (ATO Resilience). A group allocates at most one tier, `essentials` or `advanced`, and can add `ato_resilience` alongside it.",
            "example": [
              "advanced"
            ],
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "essentials",
                "advanced",
                "ato_resilience"
              ]
            }
          },
          "source": {
            "description": "Which layer the effective value comes from: `group`, `tenant`, or `default`. Groups have no account layer. See [Settings Hierarchy](https://docs.material.security/learn-more/administration/settings-hierarchy) for how the layers resolve.",
            "example": "group",
            "type": "string",
            "enum": [
              "group",
              "tenant",
              "default"
            ]
          }
        },
        "required": [
          "values",
          "source"
        ]
      },
      "BulkGroupsResponse": {
        "title": "BulkGroupsResponse",
        "type": "object",
        "properties": {
          "results": {
            "description": "Per-operation result, in the same order as the request.",
            "type": "array",
            "items": {
              "oneOf": [
                {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "const": "ok"
                    },
                    "index": {
                      "type": "integer",
                      "minimum": -9007199254740991,
                      "maximum": 9007199254740991
                    },
                    "id": {
                      "type": "string"
                    },
                    "group": {
                      "$ref": "#/components/schemas/APIGroup"
                    }
                  },
                  "required": [
                    "status",
                    "index",
                    "id",
                    "group"
                  ]
                },
                {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "const": "error"
                    },
                    "index": {
                      "type": "integer",
                      "minimum": -9007199254740991,
                      "maximum": 9007199254740991
                    },
                    "id": {
                      "type": "string"
                    },
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "status",
                    "index",
                    "id",
                    "error"
                  ]
                }
              ],
              "type": "object"
            }
          },
          "meta": {
            "description": "Aggregate counts across all operations.",
            "type": "object",
            "properties": {
              "succeeded": {
                "description": "Number of operations that succeeded.",
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              },
              "failed": {
                "description": "Number of operations that failed.",
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              }
            },
            "required": [
              "succeeded",
              "failed"
            ]
          }
        },
        "required": [
          "results",
          "meta"
        ]
      },
      "APIRoleCollection": {
        "title": "APIRoleCollection",
        "type": "object",
        "properties": {
          "meta": {
            "$ref": "#/components/schemas/CollectionMeta"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/APIRole"
            }
          }
        },
        "required": [
          "meta",
          "items"
        ]
      },
      "APIRole": {
        "title": "APIRole",
        "description": "An assignable [role](https://docs.material.security/learn-more/administration/admin-roles). Role definitions are built in, so only assignments are managed through the API.",
        "type": "object",
        "properties": {
          "id": {
            "description": "Role ID. The ID carries the role's scope: tenant-scoped IDs (for example `analyst`) require `tenants`, while IDs that apply everywhere (for example `global_analyst`, `super_admin`, `tenant_enroller`) take none. Call `GET /roles` for the IDs available on your instance.",
            "example": "analyst",
            "type": "string",
            "enum": [
              "super_admin",
              "global_analyst",
              "global_issue_responder",
              "global_phishing_simulation_admin",
              "global_analytics_admin",
              "global_settings_admin",
              "tenant_admin",
              "content_admin",
              "drive_content_admin",
              "email_content_admin",
              "analyst",
              "issue_responder",
              "ediscovery_admin",
              "phishing_simulation_admin",
              "analytics_admin",
              "settings_admin",
              "tenant_enroller"
            ]
          },
          "name": {
            "description": "Display name, as shown under **Edit Roles**.",
            "example": "Analyst",
            "type": "string"
          },
          "description": {
            "description": "What the role allows, as shown under **Edit Roles**. See [Role Descriptions](https://docs.material.security/learn-more/administration/admin-roles/role-descriptions) for the full breakdown.",
            "example": "Investigate and respond to issues within assigned tenants.",
            "type": "string"
          },
          "flavor": {
            "description": "Scope of the ID: `tenant` requires `tenants` when you assign it, `global` applies to every tenant and takes no `tenants`, and `individual` grants no tenant access at all.",
            "example": "tenant",
            "type": "string",
            "enum": [
              "global",
              "tenant",
              "individual"
            ]
          }
        },
        "required": [
          "id",
          "name",
          "description",
          "flavor"
        ]
      },
      "APITenantCollection": {
        "title": "APITenantCollection",
        "type": "object",
        "properties": {
          "meta": {
            "$ref": "#/components/schemas/CollectionMeta"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/APITenant"
            }
          }
        },
        "required": [
          "meta",
          "items"
        ]
      },
      "APITenant": {
        "title": "APITenant",
        "description": "A Google Workspace or Microsoft 365 tenant connected to Material.",
        "type": "object",
        "properties": {
          "id": {
            "description": "The encoded tenant ID, the value `tenant` filters take and `tenantId` fields return. Format: `tnt.1.<base64>`.",
            "example": "tnt.1.ZXhhbXBsZV9pZF8xMjM",
            "type": "string"
          },
          "name": {
            "description": "The tenant's display name: the custom name set in Material, otherwise the first of `domains`. A tenant with no domains yet falls back to a name inferred from the provider.",
            "example": "example.com",
            "type": "string"
          },
          "csp": {
            "description": "Cloud service provider (`microsoft` or `google`)",
            "example": "google",
            "type": "string",
            "enum": [
              "microsoft",
              "google"
            ]
          },
          "providerTenantId": {
            "description": "The tenant's ID at the provider: the Google Workspace customer ID, or the Microsoft Entra tenant ID.",
            "example": "C0acme123",
            "type": "string"
          },
          "primaryDomain": {
            "description": "The provider's primary domain for the tenant. Null until Material has synced it from the provider.",
            "example": "example.com",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "domains": {
            "description": "The tenant's domains, `primaryDomain` first. Normally the provider's verified and alias domains. Until a provider sync succeeds, Material fills this from the domains of the tenant's account emails instead.",
            "example": [
              "example.com",
              "example.org"
            ],
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "keyEvents": {
            "$ref": "#/components/schemas/APITenantKeyEvents"
          }
        },
        "required": [
          "id",
          "name",
          "csp",
          "providerTenantId",
          "primaryDomain",
          "domains",
          "keyEvents"
        ]
      },
      "APITenantKeyEvents": {
        "title": "APITenantKeyEvents",
        "type": "object",
        "properties": {
          "created": {
            "description": "When the tenant was connected to Material.",
            "type": "object",
            "properties": {
              "at": {
                "description": "ISO 8601 timestamp of the event",
                "type": "string",
                "format": "date-time"
              }
            },
            "required": [
              "at"
            ]
          }
        },
        "required": [
          "created"
        ]
      },
      "APIMessage": {
        "title": "APIMessage",
        "description": "An email message.",
        "type": "object",
        "properties": {
          "id": {
            "description": "The encoded message ID. Format: `msg.1.<base64>`.",
            "example": "msg.1.ZXhhbXBsZV9pZF8xMjM",
            "type": "string"
          },
          "headers": {
            "$ref": "#/components/schemas/APIMessageHeaders"
          },
          "links": {
            "description": "The links found in the message body. Populated only when `include=links` is specified.",
            "type": "object",
            "properties": {
              "meta": {
                "$ref": "#/components/schemas/CollectionMeta"
              },
              "items": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/APILink"
                }
              }
            },
            "required": [
              "meta",
              "items"
            ]
          },
          "issue": {
            "description": "The issue associated with this message. Populated only when `include=issue` is specified. Null if no issue is associated.",
            "anyOf": [
              {
                "$ref": "#/components/schemas/APIIssueRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "content": {
            "description": "The body content of the message. Populated only when `include=content` is specified. Null if unavailable.",
            "anyOf": [
              {
                "$ref": "#/components/schemas/APIMessageContent"
              },
              {
                "type": "null"
              }
            ]
          },
          "attachments": {
            "description": "The attachments for this message. Populated only when `include=attachments` is specified.",
            "type": "object",
            "properties": {
              "meta": {
                "$ref": "#/components/schemas/CollectionMeta"
              },
              "items": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/APIAttachment"
                }
              }
            },
            "required": [
              "meta",
              "items"
            ]
          },
          "mailboxes": {
            "description": "The mailboxes in which this message appears. Populated only when `include=mailboxes` is specified.",
            "type": "object",
            "properties": {
              "meta": {
                "$ref": "#/components/schemas/CollectionMeta"
              },
              "items": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/APIMailbox"
                }
              }
            },
            "required": [
              "meta",
              "items"
            ]
          }
        },
        "required": [
          "id",
          "headers"
        ]
      },
      "APIMessageHeaders": {
        "title": "APIMessageHeaders",
        "description": "Header-derived fields for the message.",
        "type": "object",
        "properties": {
          "messageId": {
            "description": "The RFC 2822 Message-ID header value for this message.",
            "type": "string"
          },
          "date": {
            "description": "The date the message was sent, in ISO 8601 format.",
            "example": "2026-10-07T12:00:00.000Z",
            "type": "string",
            "format": "date-time"
          },
          "subject": {
            "description": "The subject line of the email. Null if unavailable.",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "from": {
            "description": "The sender address of the message. Typically the From header, but may be the original sender for messages forwarded through customer-owned infrastructure. Null if unavailable.",
            "example": "sender@example.com",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "to": {
            "description": "The recipients on the To header. Addresses only; display names are not included.",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "cc": {
            "description": "The recipients on the Cc header. Addresses only; display names are not included.",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "bcc": {
            "description": "The recipients on the Bcc header. Addresses only; display names are not included. May include recipients reconstructed from envelope-level data when the `Bcc:` header was stripped before delivery.",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "authResults": {
            "description": "Resolved SPF, DKIM, and DMARC results. `null` when no domain context is available to resolve your SEG settings against.",
            "anyOf": [
              {
                "$ref": "#/components/schemas/APIAuthResults"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "messageId",
          "date",
          "subject",
          "from",
          "to",
          "cc",
          "bcc",
          "authResults"
        ]
      },
      "APIAuthResults": {
        "title": "APIAuthResults",
        "description": "Resolved [authentication results](https://docs.material.security/learn-more/risk-areas/email-threats/investigate/issue-details) (SPF, DKIM, and DMARC) for the message. When you have a Secure Email Gateway (SEG) configured, values are pulled from the SEG’s preserved authentication-results header rather than the inline auth headers.",
        "type": "object",
        "properties": {
          "spf": {
            "description": "The resolved SPF authentication result.",
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "pass",
                  "none",
                  "neutral",
                  "temperror",
                  "permerror",
                  "softfail",
                  "policy",
                  "fail"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "dkim": {
            "description": "The resolved DKIM authentication result.",
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "pass",
                  "none",
                  "neutral",
                  "temperror",
                  "permerror",
                  "policy",
                  "fail"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "dmarc": {
            "description": "The resolved DMARC authentication result.",
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "pass",
                  "none",
                  "temperror",
                  "permerror",
                  "fail"
                ]
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "spf",
          "dkim",
          "dmarc"
        ]
      },
      "APILink": {
        "title": "APILink",
        "type": "object",
        "properties": {
          "href": {
            "description": "The URL of the link.",
            "example": "https://example.com/login",
            "type": "string"
          },
          "text": {
            "description": "The visible text of the link. Null if unavailable.",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "type": {
            "description": "What kind of content the link comes from. Null if unavailable.",
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "a.href",
                  "img.src",
                  "script.src"
                ]
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "href",
          "text",
          "type"
        ]
      },
      "APIIssueRef": {
        "title": "APIIssueRef",
        "type": "object",
        "properties": {
          "id": {
            "description": "The encoded ID of the associated issue. Format: `iss.1.<base64>`.",
            "example": "iss.1.ZXhhbXBsZV9pZF8xMjM",
            "type": "string"
          }
        },
        "required": [
          "id"
        ]
      },
      "APIMessageContent": {
        "title": "APIMessageContent",
        "type": "object",
        "properties": {
          "parts": {
            "description": "The content parts that make up the message body.",
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "mime": {
                  "description": "The MIME type of this content part (for example, `text/html`).",
                  "example": "text/html",
                  "type": "string"
                },
                "body": {
                  "description": "The body text of this content part.",
                  "type": "string"
                }
              },
              "required": [
                "mime",
                "body"
              ]
            }
          }
        },
        "required": [
          "parts"
        ]
      },
      "APIAttachment": {
        "title": "APIAttachment",
        "type": "object",
        "properties": {
          "id": {
            "description": "The identifier for this attachment.",
            "type": "string"
          },
          "filename": {
            "description": "The original filename of the attachment. Null if unavailable.",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "mime": {
            "description": "The MIME type of the attachment (for example, `application/pdf`).",
            "example": "application/pdf",
            "type": "string"
          },
          "size": {
            "description": "The size of the attachment in bytes.",
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "hashes": {
            "description": "File hashes, if available",
            "type": "object",
            "properties": {
              "sha256": {
                "description": "The SHA-256 hash of the attachment. Null if unavailable.",
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "md5": {
                "description": "The MD5 hash of the attachment. Null if unavailable.",
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            },
            "required": [
              "sha256",
              "md5"
            ]
          }
        },
        "required": [
          "id",
          "filename",
          "mime",
          "size"
        ]
      },
      "APIMailbox": {
        "title": "APIMailbox",
        "type": "object",
        "properties": {
          "accountId": {
            "description": "The encoded ID of the account that owns this mailbox. Format: `acct.1.<base64>`.",
            "example": "acct.1.ZXhhbXBsZV9pZF8xMjM",
            "type": "string"
          },
          "email": {
            "description": "The email address of the mailbox.",
            "example": "user@example.com",
            "type": "string"
          },
          "tags": {
            "description": "The tags associated with this mailbox.",
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "accountId",
          "email",
          "tags"
        ]
      },
      "SearchMessagesResponse": {
        "title": "SearchMessagesResponse",
        "type": "object",
        "properties": {
          "id": {
            "description": "The encoded ID of the search. Format: `job.1.<base64>`.",
            "example": "job.1.ZXhhbXBsZV9pZF8xMjM",
            "type": "string",
            "pattern": "^[a-z]{1,6}\\.[0-9]{1,2}\\.[A-Za-z0-9_-]+$"
          },
          "status": {
            "description": "The current status of the search. Valid values: `CREATED` (queued), `STARTED` (running), `DONE` (results ready).",
            "type": "string",
            "enum": [
              "CREATED",
              "STARTED",
              "DONE"
            ]
          }
        },
        "required": [
          "id",
          "status"
        ]
      },
      "MoveMessagesToInboxResponse": {
        "title": "MoveMessagesToInboxResponse",
        "type": "object",
        "properties": {
          "id": {
            "description": "The encoded ID of the queued move job. Format: `job.1.<base64>`. Quote this when you contact support. You can't check the job's progress through this API.",
            "example": "job.1.ZXhhbXBsZV9pZF8xMjM",
            "type": "string",
            "pattern": "^[a-z]{1,6}\\.[0-9]{1,2}\\.[A-Za-z0-9_-]+$"
          },
          "status": {
            "description": "Always `CREATED`. The move runs in the background and this endpoint never reports it finishing.",
            "type": "string",
            "const": "CREATED"
          },
          "accepted": {
            "description": "How many of the supplied message IDs resolved to a message you can reach, and were queued for the move.",
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "notFound": {
            "description": "The supplied message IDs that didn't resolve to a message you can reach. These weren't queued. An ID appears here whether the message doesn't exist or sits outside the tenants your token covers.",
            "type": "array",
            "items": {
              "description": "An encoded message ID. Format: `msg.1.<base64>`.",
              "example": "msg.1.ZXhhbXBsZV9pZF8xMjM",
              "type": "string",
              "pattern": "^[a-z]{1,6}\\.[0-9]{1,2}\\.[A-Za-z0-9_-]+$"
            }
          },
          "mailboxCount": {
            "description": "This is the highest number of mailbox copies the move could touch, summed across the accepted messages. Copies already in the inbox are skipped when the move runs, so fewer may actually change.",
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          }
        },
        "required": [
          "id",
          "status",
          "accepted",
          "notFound",
          "mailboxCount"
        ]
      },
      "MessageSearchResponse": {
        "title": "MessageSearchResponse",
        "type": "object",
        "properties": {
          "id": {
            "description": "The encoded ID of the search. Format: `job.1.<base64>`.",
            "example": "job.1.ZXhhbXBsZV9pZF8xMjM",
            "type": "string",
            "pattern": "^[a-z]{1,6}\\.[0-9]{1,2}\\.[A-Za-z0-9_-]+$"
          },
          "status": {
            "description": "The current status of the search. Valid values: `CREATED` (queued), `STARTED` (running), `DONE` (results ready).",
            "type": "string",
            "enum": [
              "CREATED",
              "STARTED",
              "DONE"
            ]
          },
          "data": {
            "description": "The search results. Present only when `status` is `\"DONE\"`.",
            "type": "object",
            "properties": {
              "meta": {
                "description": "The pagination metadata for the search results, including `totalCount`, `limit`, `hasMore`, `nextCursor`, and `truncated`.",
                "type": "object",
                "properties": {
                  "totalCount": {
                    "description": "The total number of items matching the query.",
                    "type": "integer",
                    "minimum": -9007199254740991,
                    "maximum": 9007199254740991
                  },
                  "limit": {
                    "description": "The page size used for this response.",
                    "type": "integer",
                    "minimum": -9007199254740991,
                    "maximum": 9007199254740991
                  },
                  "hasMore": {
                    "description": "True if more items exist beyond the current page; false otherwise.",
                    "type": "boolean"
                  },
                  "nextCursor": {
                    "description": "The cursor to fetch the next page of results. Pass this value to the `cursor` query parameter. Null if no more pages exist.",
                    "anyOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "truncated": {
                    "description": "True when the query matched more messages than the search returns, so `items` holds only the first part of the matches and `totalCount` reports that limit rather than the real number of matches. Narrow the query or the date range to see the rest.",
                    "type": "boolean"
                  }
                }
              },
              "items": {
                "description": "The list of messages matching the search query.",
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/APIMessageItem"
                }
              }
            },
            "required": [
              "meta",
              "items"
            ]
          }
        },
        "required": [
          "id",
          "status"
        ]
      },
      "APITrustedEntityCollection": {
        "title": "APITrustedEntityCollection",
        "type": "object",
        "properties": {
          "meta": {
            "$ref": "#/components/schemas/CollectionMeta"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/APITrustedEntity"
            }
          }
        },
        "required": [
          "meta",
          "items"
        ]
      },
      "APITrustedEntity": {
        "title": "APITrustedEntity",
        "description": "An organization-level allowlist entry (a domain, email address, or IP/CIDR) that tunes how Material treats mail and files from that identity.",
        "type": "object",
        "properties": {
          "id": {
            "description": "The encoded trusted-entity ID. Format: `te.1.<base64>`.",
            "example": "te.1.ZXhhbXBsZV9pZF8xMjM",
            "type": "string"
          },
          "entity": {
            "description": "The domain, email address, or IP/CIDR this entry allowlists.",
            "example": "evil.com",
            "type": "string"
          },
          "type": {
            "description": "The kind of identity this entry allowlists: `external_domain`, `internal_domain`, `email_address`, or `ip_address`.",
            "type": "string",
            "enum": [
              "external_domain",
              "internal_domain",
              "email_address",
              "ip_address"
            ]
          },
          "tenant": {
            "description": "The encoded ID of the tenant this entry applies to. Format: `tnt.1.<base64>`. `null` means it applies globally across all tenants.",
            "example": "tnt.1.ZXhhbXBsZV9pZF8xMjM",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "enabled": {
            "description": "Whether this Trusted Entity is active.",
            "type": "boolean"
          },
          "subdomains": {
            "description": "Whether the entry also covers subdomains of the domain: `included` or `not_included`. Applies to domain entities only; `null` for email address and IP entities.",
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "included",
                  "not_included"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "emailThreatDetection": {
            "description": "The [email threat handling](https://docs.material.security/getting-started/fundamentals/detect/trusted-entities#settings-definitions) for messages from this entity: `detect` (detect threats normally), `ignore_mark_safe` (create issues, but mark messages safe), or `ignore_dont_create_issues` (don't create phishing issues).",
            "type": "string",
            "enum": [
              "detect",
              "ignore_mark_safe",
              "ignore_dont_create_issues"
            ]
          },
          "emailAuthentication": {
            "description": "The [email authentication handling](https://docs.material.security/getting-started/fundamentals/detect/trusted-entities#settings-definitions) for this entity: `enforced` (don't trust messages that fail authentication) or `ignored` (trust messages regardless of authentication).",
            "type": "string",
            "enum": [
              "enforced",
              "ignored"
            ]
          },
          "fileDetection": {
            "description": "The [file detection handling](https://docs.material.security/getting-started/fundamentals/detect/trusted-entities#settings-definitions) for this entity: `treat_as_internal` (treat entity as internal) or `treat_as_external` (treat entity as external). Applies to domain and email address entities only; `null` for IP entities.",
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "treat_as_internal",
                  "treat_as_external"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "updatedAt": {
            "description": "When the entry was last modified, in ISO 8601 format. `null` for system-managed entities.",
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ]
          },
          "updatedBy": {
            "description": "The encoded account ID of the last editor. Format: `acct.1.<base64>`. `null` for system-managed entities.",
            "example": "acct.1.ZXhhbXBsZV9pZF8xMjM",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "timeline": {
            "description": "The change timeline for this entry. Populated only when `include=timeline`.",
            "type": "object",
            "properties": {
              "meta": {
                "$ref": "#/components/schemas/CollectionMeta"
              },
              "items": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/APITrustedEntityTimelineEvent"
                }
              }
            },
            "required": [
              "meta",
              "items"
            ]
          }
        },
        "required": [
          "id",
          "entity",
          "type",
          "tenant",
          "enabled",
          "subdomains",
          "emailThreatDetection",
          "emailAuthentication",
          "fileDetection",
          "updatedAt",
          "updatedBy"
        ]
      },
      "APITrustedEntityTimelineEvent": {
        "title": "APITrustedEntityTimelineEvent",
        "type": "object",
        "properties": {
          "type": {
            "description": "The timeline type: `created`, `comment`, or `setting_updated`.",
            "example": "created",
            "type": "string"
          },
          "at": {
            "description": "When the event occurred, in ISO 8601 format. `null` for system events.",
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ]
          },
          "actor": {
            "description": "The encoded account ID of the account that triggered the event. Format: `acct.1.<base64>`. `null` for system events.",
            "example": "acct.1.ZXhhbXBsZV9pZF8xMjM",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "detail": {
            "description": "The comment text or the setting name that changed. `null` when neither applies.",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "type",
          "at",
          "actor",
          "detail"
        ]
      },
      "BulkTrustedEntitiesResponse": {
        "title": "BulkTrustedEntitiesResponse",
        "type": "object",
        "properties": {
          "results": {
            "description": "Per-operation result, in the same order as the request.",
            "type": "array",
            "items": {
              "oneOf": [
                {
                  "type": "object",
                  "properties": {
                    "method": {
                      "type": "string",
                      "enum": [
                        "create",
                        "update",
                        "delete"
                      ]
                    },
                    "status": {
                      "type": "string",
                      "const": "ok"
                    },
                    "index": {
                      "type": "integer",
                      "minimum": -9007199254740991,
                      "maximum": 9007199254740991
                    },
                    "id": {
                      "type": "string"
                    },
                    "entity": {
                      "$ref": "#/components/schemas/APITrustedEntity"
                    }
                  },
                  "required": [
                    "method",
                    "status",
                    "index",
                    "id"
                  ]
                },
                {
                  "type": "object",
                  "properties": {
                    "method": {
                      "type": "string",
                      "enum": [
                        "create",
                        "update",
                        "delete"
                      ]
                    },
                    "status": {
                      "type": "string",
                      "const": "error"
                    },
                    "index": {
                      "type": "integer",
                      "minimum": -9007199254740991,
                      "maximum": 9007199254740991
                    },
                    "error": {
                      "type": "string"
                    },
                    "id": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "method",
                    "status",
                    "index",
                    "error"
                  ]
                }
              ],
              "type": "object"
            }
          },
          "meta": {
            "description": "Aggregate counts across all operations.",
            "type": "object",
            "properties": {
              "succeeded": {
                "description": "Number of operations that succeeded.",
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              },
              "failed": {
                "description": "Number of operations that failed.",
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              }
            },
            "required": [
              "succeeded",
              "failed"
            ]
          }
        },
        "required": [
          "results",
          "meta"
        ]
      },
      "APIOAuthAppCollection": {
        "title": "APIOAuthAppCollection",
        "type": "object",
        "properties": {
          "meta": {
            "$ref": "#/components/schemas/CollectionMeta"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/APIOAuthApp"
            }
          }
        },
        "required": [
          "meta",
          "items"
        ]
      },
      "APIOAuthApp": {
        "title": "APIOAuthApp",
        "description": "A third-party OAuth application observed holding token grants in your Google Workspace tenant. Google Workspace is the only provider covered today.",
        "type": "object",
        "properties": {
          "id": {
            "description": "The encoded OAuth app ID. Format: `oapp.1.<base64>`.",
            "example": "oapp.1.MDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAw",
            "type": "string",
            "pattern": "^[a-z]{1,6}\\.[0-9]{1,2}\\.[A-Za-z0-9_-]+$"
          },
          "tenantId": {
            "description": "The encoded ID of the tenant the app was observed in. Format: `tnt.1.<base64>`.",
            "example": "tnt.1.ZXhhbXBsZV9pZF8xMjM",
            "type": "string"
          },
          "name": {
            "description": "The name Google shows on the consent screen. The app publisher chooses it, so it can impersonate a well-known vendor; use `clientId` to identify an app.",
            "example": "Acme CRM",
            "type": "string"
          },
          "clientId": {
            "description": "The Google OAuth client ID. This is the stable identity of the app and the value that appears in Google Workspace audit logs.",
            "example": "1234567890-abcdefghijklmnop.apps.googleusercontent.com",
            "type": "string"
          },
          "isInternal": {
            "description": "True when the app's registered brand belongs to one of your own domains (for example an Apps Script written in-house). False when unknown.",
            "type": "boolean"
          },
          "isLoginOnly": {
            "description": "True when every scope currently granted is a sign-in scope (`email`, `profile`, or `openid`), so the app can identify users but read nothing. Derived from `scopes`, so it carries the same staleness, and it is false when `scopes` is empty.",
            "type": "boolean"
          },
          "scopes": {
            "description": "The union of scopes across the grants Material last observed as active, sorted. This can lag in both directions: a very recent grant may not appear yet, and after every account is revoked at once it can keep listing scopes no account still holds. Use the accounts collection for live per-account grant state.",
            "example": [
              "https://www.googleapis.com/auth/drive",
              "openid"
            ],
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "restrictedScopeTypes": {
            "description": "Which of Google's restricted-scope families the observed granted scopes fall into, sorted. Derived from `scopes`, so it carries the same staleness in both directions.",
            "example": [
              "gmail",
              "drive"
            ],
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "gmail",
                "drive",
                "google_fit",
                "google_chat",
                "data_portability",
                "photos_ambient"
              ]
            }
          },
          "brand": {
            "description": "Brand metadata, or null when Google returned no brand record.",
            "anyOf": [
              {
                "$ref": "#/components/schemas/APIOAuthAppBrand"
              },
              {
                "type": "null"
              }
            ]
          },
          "classification": {
            "description": "The committed classification of the app, set by an admin or committed automatically by a `safe` investigation verdict. Null means unclassified, which **Explorer** > **Apps** > **OAuth** labels Unknown. The list `classification` filter matches the effective classification instead, so a filtered result can carry a value here that differs from the one you filtered on. The effective value is derivable from this payload: it is `recommendedClassification.value` while that object has `status: 'staged'`, and this field otherwise.",
            "example": "safe",
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "safe",
                  "suspicious",
                  "overprivileged",
                  "unnecessary",
                  "malicious"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "recommendedClassification": {
            "description": "The OAuth Remediation Agent's suggestion and how it was resolved. Null when there is no suggestion.",
            "anyOf": [
              {
                "$ref": "#/components/schemas/APIOAuthAppRecommendedClassification"
              },
              {
                "type": "null"
              }
            ]
          },
          "remediation": {
            "description": "The standing remediation policy for this app, not a record of past actions. `[]` means none. `notify` always accompanies `revoke`, never on its own.",
            "example": [
              "revoke"
            ],
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "revoke",
                "notify"
              ]
            }
          },
          "keyEvents": {
            "$ref": "#/components/schemas/APIOAuthAppKeyEvents"
          },
          "investigation": {
            "description": "Populated only when `include=investigation`. Null when there is no investigation available (never investigated, or a stale pointer whose run has been purged). Requesting the include without permission to read investigation data returns 403, so a null here never means \"not allowed to see it\".",
            "anyOf": [
              {
                "$ref": "#/components/schemas/APIOAuthAppInvestigation"
              },
              {
                "type": "null"
              }
            ]
          },
          "accounts": {
            "description": "Populated only when `include=accounts`, with the first page of the accounts edge. `meta.hasMore: true` with a null `meta.nextCursor` means there are more; page them at `GET /oauth-apps/{id}/accounts`.",
            "$ref": "#/components/schemas/APIOAuthAppAccountCollection"
          }
        },
        "required": [
          "id",
          "tenantId",
          "name",
          "clientId",
          "isInternal",
          "isLoginOnly",
          "scopes",
          "restrictedScopeTypes",
          "brand",
          "classification",
          "recommendedClassification",
          "remediation",
          "keyEvents"
        ]
      },
      "APIOAuthAppBrand": {
        "title": "APIOAuthAppBrand",
        "description": "Brand metadata registered with Google for this OAuth client. Null when Google returned no brand record.",
        "type": "object",
        "properties": {
          "verified": {
            "description": "Whether Google's brand verification returned a verified brand record for this app. Unverified apps can still set any of the fields below, so treat them as attacker-controllable input.",
            "type": "boolean"
          },
          "displayName": {
            "description": "Brand display name; the verified value when present. Null if the brand has no name.",
            "example": "Acme CRM",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "supportEmail": {
            "description": "Brand support email; the verified value when present. Null if unavailable.",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "homePageUrl": {
            "description": "Brand home page URL; the verified value when present. Null if unavailable.",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "iconUrl": {
            "description": "URL of the app's consent-screen icon. Null if unavailable.",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "verified",
          "displayName",
          "supportEmail",
          "homePageUrl",
          "iconUrl"
        ]
      },
      "APIOAuthAppRecommendedClassification": {
        "title": "APIOAuthAppRecommendedClassification",
        "description": "The OAuth Remediation Agent's suggested classification, mirrored from the issue that carries it. Null when no issue currently sources one. The investigation of a newly granted app stages whatever it concludes, so `safe` is reachable here; a later re-investigation that concludes `safe` stages nothing, instead committing `classification` directly when the app is unclassified or was system-classified, and dropping the verdict otherwise.",
        "type": "object",
        "properties": {
          "value": {
            "description": "The classification Material's [OAuth Remediation Agent](https://docs.material.security/learn-more/risk-areas/malicious-oauth-apps) recommends.",
            "example": "overprivileged",
            "type": "string",
            "enum": [
              "safe",
              "suspicious",
              "overprivileged",
              "unnecessary",
              "malicious"
            ]
          },
          "status": {
            "description": "`staged` means the recommendation is awaiting a human decision and is what **Explorer** > **Apps** > **OAuth** displays in place of `classification`. `accepted` and `rejected` record how an admin resolved it; `rejected` leaves `classification` untouched, so the disagreement stays readable.",
            "type": "string",
            "enum": [
              "staged",
              "accepted",
              "rejected"
            ]
          },
          "issueId": {
            "description": "The audit issue that carries this recommendation. Null when no issue exists. Reading it requires issue permissions.",
            "example": "iss.1.ZXhhbXBsZV9pZF8xMjM",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "value",
          "status",
          "issueId"
        ]
      },
      "APIOAuthAppKeyEvents": {
        "title": "APIOAuthAppKeyEvents",
        "type": "object",
        "properties": {
          "created": {
            "description": "When Material created this app record, which is the same sync that first observed a grant, so it normally matches `firstObserved`.",
            "type": "object",
            "properties": {
              "at": {
                "description": "ISO 8601 timestamp of the event",
                "type": "string",
                "format": "date-time"
              }
            },
            "required": [
              "at"
            ]
          },
          "firstObserved": {
            "description": "The earliest grant of this app Material has observed. Google does not report when a grant was actually made, so for apps already present when Material first synced the tenant this is the first-sync time rather than the original grant date. Reflects grants that still exist: if the earliest granting account is deleted, this moves forward.",
            "type": "object",
            "properties": {
              "at": {
                "description": "ISO 8601 timestamp of the event",
                "type": "string",
                "format": "date-time"
              }
            },
            "required": [
              "at"
            ]
          },
          "lastNewGrant": {
            "description": "When this app was most recently granted by an account that did not already grant it. An existing account re-granting after revoking does not advance this; the per-account `lastGrantedAt` on the accounts collection does.",
            "type": "object",
            "properties": {
              "at": {
                "description": "ISO 8601 timestamp of the event",
                "type": "string",
                "format": "date-time"
              }
            },
            "required": [
              "at"
            ]
          },
          "classificationChanged": {
            "description": "When `classification` was last written, including a re-save of the same value. Absent while the app has no classification, which also covers a classification that was later cleared.",
            "type": "object",
            "properties": {
              "at": {
                "description": "ISO 8601 timestamp of the event",
                "type": "string",
                "format": "date-time"
              }
            },
            "required": [
              "at"
            ]
          },
          "remediationChanged": {
            "description": "When the remediation policy was last written, including a write that set it to none. Present alongside an empty `remediation` whenever a classification was saved without one.",
            "type": "object",
            "properties": {
              "at": {
                "description": "ISO 8601 timestamp of the event",
                "type": "string",
                "format": "date-time"
              }
            },
            "required": [
              "at"
            ]
          }
        },
        "required": [
          "created",
          "firstObserved",
          "lastNewGrant"
        ]
      },
      "APIOAuthAppInvestigation": {
        "title": "APIOAuthAppInvestigation",
        "description": "The most recent run of Material's [OAuth Remediation Agent](https://docs.material.security/learn-more/risk-areas/malicious-oauth-apps) for this app. Reading this never starts a new run.",
        "type": "object",
        "properties": {
          "status": {
            "description": "The state of the run: `running`, `completed`, or `failed`. Check this rather than the text fields to tell whether a run has finished.",
            "type": "string",
            "enum": [
              "running",
              "completed",
              "failed"
            ]
          },
          "startedAt": {
            "description": "When the run started, in ISO 8601 format. For runs reconciled from an existing issue this is taken from the issue rather than the run, so it can fall after `completedAt`; treat the pair as bounds rather than a duration.",
            "type": "string",
            "format": "date-time"
          },
          "completedAt": {
            "description": "When the run finished, in ISO 8601 format. Null while running.",
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ]
          },
          "riskAssessment": {
            "description": "The overall risk narrative, written by Material's [OAuth Remediation Agent](https://docs.material.security/learn-more/risk-areas/malicious-oauth-apps). Empty until the run completes. It quotes app metadata the app publisher controls, so treat it as untrusted text and never as markup.",
            "type": "string"
          },
          "assessments": {
            "$ref": "#/components/schemas/APIOAuthAppInvestigationAssessments"
          }
        },
        "required": [
          "status",
          "startedAt",
          "completedAt",
          "riskAssessment",
          "assessments"
        ]
      },
      "APIOAuthAppInvestigationAssessments": {
        "title": "APIOAuthAppInvestigationAssessments",
        "description": "Per-dimension verdicts. Each is null when the run produced no assessment for that dimension.",
        "type": "object",
        "properties": {
          "vendorTrust": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/APIVendorTrustAssessment"
              },
              {
                "type": "null"
              }
            ]
          },
          "scopeRisk": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/APIScopeRiskAssessment"
              },
              {
                "type": "null"
              }
            ]
          },
          "blastRadius": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/APIBlastRadiusAssessment"
              },
              {
                "type": "null"
              }
            ]
          },
          "appBehavior": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/APIAppBehaviorAssessment"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "vendorTrust",
          "scopeRisk",
          "blastRadius",
          "appBehavior"
        ]
      },
      "APIVendorTrustAssessment": {
        "title": "APIVendorTrustAssessment",
        "description": "How well-known and reputable the app publisher is.",
        "type": "object",
        "properties": {
          "level": {
            "description": "How much the app publisher is trusted: `very_high`, `high`, `medium`, `low`, or `distrust`.",
            "type": "string",
            "enum": [
              "very_high",
              "high",
              "medium",
              "low",
              "distrust"
            ]
          },
          "summary": {
            "description": "One-paragraph finding for this dimension, written by Material's [OAuth Remediation Agent](https://docs.material.security/learn-more/risk-areas/malicious-oauth-apps). It quotes app metadata the app publisher controls, so treat it as untrusted text and never as markup.",
            "type": "string"
          }
        },
        "required": [
          "level",
          "summary"
        ]
      },
      "APIScopeRiskAssessment": {
        "title": "APIScopeRiskAssessment",
        "description": "How much damage the scopes this app holds would allow.",
        "type": "object",
        "properties": {
          "level": {
            "description": "How dangerous the granted scopes are: `very_low`, `low`, `medium`, `high`, or `critical`.",
            "type": "string",
            "enum": [
              "very_low",
              "low",
              "medium",
              "high",
              "critical"
            ]
          },
          "summary": {
            "description": "One-paragraph finding for this dimension, written by Material's [OAuth Remediation Agent](https://docs.material.security/learn-more/risk-areas/malicious-oauth-apps). It quotes app metadata the app publisher controls, so treat it as untrusted text and never as markup.",
            "type": "string"
          }
        },
        "required": [
          "level",
          "summary"
        ]
      },
      "APIBlastRadiusAssessment": {
        "title": "APIBlastRadiusAssessment",
        "description": "How much of the tenant is exposed through this app.",
        "type": "object",
        "properties": {
          "level": {
            "description": "How widely the app is granted across your accounts: `small`, `moderate`, or `large`.",
            "type": "string",
            "enum": [
              "small",
              "moderate",
              "large"
            ]
          },
          "summary": {
            "description": "One-paragraph finding for this dimension, written by Material's [OAuth Remediation Agent](https://docs.material.security/learn-more/risk-areas/malicious-oauth-apps). It quotes app metadata the app publisher controls, so treat it as untrusted text and never as markup.",
            "type": "string"
          }
        },
        "required": [
          "level",
          "summary"
        ]
      },
      "APIAppBehaviorAssessment": {
        "title": "APIAppBehaviorAssessment",
        "description": "How the app's observed API activity reads.",
        "type": "object",
        "properties": {
          "level": {
            "description": "What the observed behavior looks like: `benign`, `unclear`, or `suspicious`.",
            "type": "string",
            "enum": [
              "benign",
              "unclear",
              "suspicious"
            ]
          },
          "summary": {
            "description": "One-paragraph finding for this dimension, written by Material's [OAuth Remediation Agent](https://docs.material.security/learn-more/risk-areas/malicious-oauth-apps). It quotes app metadata the app publisher controls, so treat it as untrusted text and never as markup.",
            "type": "string"
          }
        },
        "required": [
          "level",
          "summary"
        ]
      },
      "APIOAuthAppAccountCollection": {
        "title": "APIOAuthAppAccountCollection",
        "type": "object",
        "properties": {
          "meta": {
            "type": "object",
            "properties": {
              "totalCount": {
                "description": "The total number of items matching the query.",
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              },
              "limit": {
                "description": "The page size used for this response.",
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              },
              "hasMore": {
                "description": "True if more items exist beyond the current page; false otherwise.",
                "type": "boolean"
              },
              "nextCursor": {
                "description": "The cursor to fetch the next page of results. Pass this value to the `cursor` query parameter. Null if no more pages exist.",
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "numActive": {
                "description": "How many accounts currently grant this app, counted live for this request. Counted across every account, not just this page, and unaffected by the filters, so it stays comparable as you filter. The `hasActiveAccounts` filter and the `activeAccounts` sort on the app list read a cached count instead, so this is the figure to trust when the two disagree.",
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              }
            },
            "required": [
              "numActive"
            ]
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/APIOAuthAppAccount"
            }
          }
        },
        "required": [
          "meta",
          "items"
        ]
      },
      "APIOAuthAppAccount": {
        "title": "APIOAuthAppAccount",
        "description": "An account that has granted this OAuth app, with the grant itself under `edge`. An account appears here once it has ever granted the app, whether or not it still does. Membership is decided by the grant, not by the account directory, so this is not a filtered view of `/accounts` and does not apply that resource's rules about which accounts are listed. Every account here is a licensed member of the tenant whose grant Material recorded.",
        "type": "object",
        "properties": {
          "id": {
            "description": "The encoded account ID. Format: `acct.1.<base64>`.",
            "example": "acct.1.ZXhhbXBsZV9pZF8xMjM",
            "type": "string"
          },
          "email": {
            "description": "Null when Material holds no email address for the account, or when your token cannot read the account. The latter means the account has since moved to a domain your token cannot reach; the grant it made while in this one still lists, so the app's reach is not understated. Narrow with `providerStatus` to drop those rows.",
            "example": "user@example.com",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "displayName": {
            "description": "Null when Material holds no display name for the account, or when your token cannot read the account, on the same terms as `email`.",
            "example": "Jane Doe",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "edge": {
            "$ref": "#/components/schemas/APIOAuthAppGrant"
          }
        },
        "required": [
          "id",
          "email",
          "displayName",
          "edge"
        ]
      },
      "APIOAuthAppGrant": {
        "title": "APIOAuthAppGrant",
        "description": "This account's grant of this app. Describes the pair, never the account on its own.",
        "type": "object",
        "properties": {
          "active": {
            "description": "Whether this account currently grants the app. False covers every way a grant can end: the account revoked it, Material revoked it, or the account was suspended or deleted. Reflects the last completed sync.",
            "type": "boolean"
          },
          "firstGrantedAt": {
            "description": "When Material first observed this account granting the app, in ISO 8601 format. Google does not report when a grant was actually made, so for a grant that already existed when Material first synced the tenant this is the first-sync time rather than the original grant date. The app-level `keyEvents.firstObserved` is the earliest of these across every account.",
            "type": "string",
            "format": "date-time"
          },
          "lastGrantedAt": {
            "description": "When Material most recently observed this account granting the app, in ISO 8601 format. Equal to `firstGrantedAt` unless the account granted the app again after an earlier grant had ended. Not the per-account form of `keyEvents.lastNewGrant`: that one moves only when an account that did not already grant the app grants it, so a re-grant advances this and leaves it alone.",
            "type": "string",
            "format": "date-time"
          },
          "scopes": {
            "description": "The scopes in this account's own grant, which can be narrower than the app-level `scopes` union.",
            "example": [
              "https://www.googleapis.com/auth/drive.readonly"
            ],
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "active",
          "firstGrantedAt",
          "lastGrantedAt",
          "scopes"
        ]
      }
    },
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "x-material-client-secret",
        "description": "Material Security API key"
      }
    }
  }
}