# Overview
Source: https://docs.manus.ai/enterprise/v2/asset-governance-overview
Enumerate and govern shareable team assets for DSPM workflows
Questions or issues? Contact us at [api-support@manus.ai](mailto:api-support@manus.ai).
# Shared Team Asset Governance
Lets enterprise tenant administrators enumerate the team's sharable surface (sessions, files, web-publish targets, projects) and override individual share scopes from a Data Security Posture Management (DSPM) workflow. The API is split across two services so the read and write halves can be authenticated with separate, independently-rotated keys.
## Endpoints
Enumerate every share the team has issued for one asset type, with filters for owner, time range, keyword, and raw permission
Admin-override a share's permission and write a `note` into the audit trail
## API keys
Both endpoints require an enterprise API key issued from Compliance settings. The mgmt key carries write access and implicitly includes read access, so a single mgmt key can drive the full read-then-override workflow; the audit key is read-only.
| Endpoint | Accepted key types |
| ------------------------------------------------------------------ | --------------------------------------------------------- |
| [team.asset.list](/enterprise/v2/team.asset.list) | `KEY_TYPE_TEAM_ASSET_AUDIT` or `KEY_TYPE_TEAM_ASSET_MGMT` |
| [team.asset.update\_scope](/enterprise/v2/team.asset.update_scope) | `KEY_TYPE_TEAM_ASSET_MGMT` only |
The audit row records the **actual** calling key type, so list calls made with a mgmt key are still distinguishable from list calls made with an audit key. See [Authentication](/enterprise/v2/authentication) for how to issue a key.
## Asset types
| `asset_type` | Underlying resource |
| ---------------------------------- | ------------------------------------ |
| `ASSET_TYPE_SESSION_COLLABORATION` | Session-level collaboration controls |
| `ASSET_TYPE_SESSION_SHARE` | Session share links |
| `ASSET_TYPE_FILE_SHARE` | File share links |
| `ASSET_TYPE_WEBSITE_PUBLISH` | Web-publish targets |
| `ASSET_TYPE_PROJECT_SHARE` | Manus project shares |
`asset_type` is required in [team.asset.list](/enterprise/v2/team.asset.list); a single call enumerates one type. To scan everything, call the endpoint once per value.
## Share scope
The v2 API exposes a **3-value `ShareScope`** enum on every `permission` and `max_permission` field:
| `ShareScope` | Meaning |
| ----------------------- | -------------------------------------------------------------- |
| `SHARE_SCOPE_OWNER` | Owner-only (effectively un-shared) |
| `SHARE_SCOPE_TEAM_ONLY` | Restricted to team members |
| `SHARE_SCOPE_PUBLIC` | Public (semantics depends on `asset_type` — see mapping below) |
### Mapping to v1 `SharePermission`
Internally the platform stores share rows with the v1 `SharePermission` enum (`OWNER`, `TEAM_ONLY`, `PUBLIC`, `EXTERNAL`). The v2 surface collapses `PUBLIC` and `EXTERNAL` into a single `SHARE_SCOPE_PUBLIC` so callers don't have to reason about which underlying resource has a native public-link surface and which only supports invite-based external access:
| v2 `ShareScope` | v1 `SharePermission` | Notes |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `SHARE_SCOPE_OWNER` | `SHARE_PERMISSION_OWNER` | 1:1 |
| `SHARE_SCOPE_TEAM_ONLY` | `SHARE_PERMISSION_TEAM_ONLY` | 1:1 |
| `SHARE_SCOPE_PUBLIC` (write) | `SHARE_PERMISSION_PUBLIC` for `SESSION_SHARE` / `FILE_SHARE` / `WEBSITE_PUBLISH`; `SHARE_PERMISSION_EXTERNAL` for `SESSION_COLLABORATION` / `PROJECT_SHARE` | The asset\_type decides which v1 value gets persisted. Asset types with a native public link/domain land on v1.PUBLIC; invite-only types land on v1.EXTERNAL. |
| `SHARE_SCOPE_PUBLIC` (read) | both `SHARE_PERMISSION_PUBLIC` and `SHARE_PERMISSION_EXTERNAL` | The response folds both v1 values back to a single `SHARE_SCOPE_PUBLIC`. |
When you filter [team.asset.list](/enterprise/v2/team.asset.list) with `permission=SHARE_SCOPE_PUBLIC`, only the v1 value the asset\_type writes today is matched — legacy rows that were stored with the other v1 value (e.g. an old `EXTERNAL` row on `FILE_SHARE`) will not be returned for that filter.
### Effective permission and admin override
Each share row carries two values: the share owner's chosen `permission` and an optional admin-set `max_permission` cap. The effective `permission` returned by [team.asset.list](/enterprise/v2/team.asset.list) is computed as:
* **Admin override always wins.** If an admin has explicitly set `max_permission` on the share row (i.e. it was set via the admin panel or [team.asset.update\_scope](/enterprise/v2/team.asset.update_scope)), that value is returned in `max_permission` as-is and the team's global asset controls are not applied on top.
* **Otherwise the team's global asset controls cap it.** When the share row has no admin-set `max_permission`, the team-level global control for that `asset_type` fills in `max_permission`, and the effective `permission` is the **stricter** of (raw share-row `permission`, global control).
So `max_permission` always reflects whatever ceiling currently applies — admin override if one exists, global control otherwise — and `permission` is the effective scope after that ceiling has been enforced.
## Recommended workflow: default-deny + approved exceptions
A common enterprise pattern is to keep the team's default share scope tight and use this API to mint individual exceptions after they have cleared an internal approval flow. End users never get to publish a resource publicly on their own — every approved exception lands in the audit log alongside the approval ticket.
From the team admin console, set the team's global asset controls so that newly created shares default to `TEAM_ONLY`. From now on, anything users try to share is restricted to the team — even if a user marked their share row as public, [team.asset.list](/enterprise/v2/team.asset.list) will report the effective `permission` as `SHARE_SCOPE_TEAM_ONLY` until an admin lifts the cap on that specific row.
When a team member needs a resource to be public (a published web app, a public file link, …), they file a request in your internal approval tool (ServiceNow, Jira, custom workflow). The ticket captures the business justification, the data-classification review, and the approver's sign-off — exactly the artifacts compliance will ask for later.
Once approved, an automation (or the approver themselves) calls [team.asset.list](/enterprise/v2/team.asset.list) with the right `asset_type` and `owner_id` filter to find the `uid` of the share row.
Use the `KEY_TYPE_TEAM_ASSET_MGMT` key to call [team.asset.update\_scope](/enterprise/v2/team.asset.update_scope) with `permission: SHARE_SCOPE_PUBLIC` and a `note` that includes the approval ticket ID. This sets the share row's admin override, which wins over the team's global `TEAM_ONLY` default. The audit row will carry both your `note` and the calling key's `api_key_name`, so a future audit can trace any public asset back to a specific approval.
Run a scheduled job with the `KEY_TYPE_TEAM_ASSET_AUDIT` key to enumerate every effective-`SHARE_SCOPE_PUBLIC` row, cross-reference against your approval system's still-valid tickets, and call [team.asset.update\_scope](/enterprise/v2/team.asset.update_scope) to revert anything whose approval has expired or been revoked.
## Operational notes
* **Audit logging.** Every call (success or failure, including auth failures) writes one row to the enterprise key audit log and one row to the API query log. The `note` you pass to `team.asset.update_scope` lands in `team_asset_audit_logs.remark`. Operators reviewing the team asset audit log can tell API-driven overrides apart from manual admin-panel actions via the `api_key_name` field on each entry: **unset** means a real human acting from the admin UI, **set to the calling key's name** means an API call, and **set but empty** means an API call where the underlying key has since been deleted.
* **Tightening vs. loosening.** When the new permission is stricter than the current one (for example `SHARE_SCOPE_PUBLIC → SHARE_SCOPE_OWNER`), the underlying resource is tightened first, then the database row is updated. If the second step fails, the system is over-restrictive but never under-restrictive. The reverse direction (loosening) updates the database first; same fail-safe property applies.
* **Stale shares.** If the underlying session / file / web-publish was deleted but the share row remains, [team.asset.list](/enterprise/v2/team.asset.list) returns it with `asset_exists: false` and [team.asset.update\_scope](/enterprise/v2/team.asset.update_scope) returns `internal` because there is no underlying resource to propagate to.
* **Pagination.** Offset-based: `limit` defaults to 100, max 1000; `offset` defaults to 0.
# Authentication
Source: https://docs.manus.ai/enterprise/v2/authentication
Use an enterprise API key to authenticate with the team v2 APIs
Questions or issues? Contact us at [api-support@manus.ai](mailto:api-support@manus.ai).
All team v2 APIs use an **enterprise API key** issued from Compliance settings, sent in the `X-API-Key` header. Only Tenant Owners can issue these keys.
## Get an enterprise API key
Sign in as a Tenant Owner and go to [Compliance settings](https://manus.im/app#settings/compliance/api-key).
Choose the key type that matches the product you want to use. Each key type is scoped to a specific surface and cannot be used outside it.
| Key type | Purpose | Expiry |
| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- |
| **e-Discovery & legal hold** | [Enterprise Data Export](/enterprise/v2/data-export-overview) | optional |
| **SIEM** | [Enterprise SIEM Integrations](/enterprise/v2/siem-integrations-overview) — export audit events (Tier 1 metadata; Tier 2 payloads) | optional |
| **Team User Management** | [Team User Management](/enterprise/v2/user-management-overview) (provision / update / offboard members from your IDP) | optional, max 365 days |
| **Team Asset Audit** | [Shared Team Asset Governance](/enterprise/v2/asset-governance-overview) — read-only DSPM enumeration ([team.asset.list](/enterprise/v2/team.asset.list)) | optional |
| **Team Asset Management** | [Shared Team Asset Governance](/enterprise/v2/asset-governance-overview) — admin override of share scope ([team.asset.update\_scope](/enterprise/v2/team.asset.update_scope)); also accepted on [team.asset.list](/enterprise/v2/team.asset.list) since write access implicitly includes read | optional |
Generate a new key with a descriptive label (e.g. "sailpoint-prod-2026-q2"). Copy it immediately — it is shown only once. Store it in a secrets manager.
Enterprise API keys grant access to **all users' data** within your enterprise (and, for Team User Management keys, the ability to add / remove members). Treat them as highly sensitive credentials. Rotate keys periodically and revoke any key that may have been exposed.
## Use the API key
Include the key in the `X-API-Key` header on every request:
```bash theme={null}
curl --location --request POST 'https://api.manus.im/v2/enterprise.export.create' \
--header 'X-API-Key: ' \
--header 'Content-Type: application/json' \
--data-raw '{
"user_id": "114504",
"scope": "ENTERPRISE_EXPORT_SCOPE_FULL",
"include_files": true,
"reason": "compliance audit"
}'
```
```python theme={null}
import os
import requests
response = requests.post(
"https://api.manus.im/v2/enterprise.export.create",
headers={
"X-API-Key": os.environ["MANUS_ENTERPRISE_API_KEY"],
"Content-Type": "application/json",
},
json={
"user_id": "114504",
"scope": "ENTERPRISE_EXPORT_SCOPE_FULL",
"include_files": True,
"reason": "compliance audit",
},
)
print(response.json())
```
```typescript theme={null}
const response = await fetch("https://api.manus.im/v2/enterprise.export.create", {
method: "POST",
headers: {
"X-API-Key": process.env.MANUS_ENTERPRISE_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
user_id: "114504",
scope: "ENTERPRISE_EXPORT_SCOPE_FULL",
include_files: true,
reason: "compliance audit",
}),
});
const data = await response.json();
console.log(data);
```
## Authentication errors
If the key is missing, invalid, or its key type does not match the endpoint being called, the API returns:
```json theme={null}
{
"ok": false,
"request_id": "req_abc123",
"error": {
"code": "unauthenticated",
"message": "Invalid or missing enterprise API key"
}
}
```
# Overview
Source: https://docs.manus.ai/enterprise/v2/data-export-overview
Export user data from Manus for compliance, legal hold, and internal audits
Questions or issues? Contact us at [api-support@manus.ai](mailto:api-support@manus.ai).
# Enterprise Data Export
Lets enterprise administrators export user data from Manus for **compliance**, **legal hold**, and **internal audit** workflows. Exports run asynchronously and produce a ZIP archive of structured JSON plus optional file attachments.
## Endpoints
Kick off a new compliance export with configurable scope and time range
Poll an export task until it reaches `EXPORT_STATUS_COMPLETED`
Retrieve export history for a user with pagination
Mint a 10-minute pre-signed URL for the completed archive
List a user's tasks before deciding what to export
## Export lifecycle
```
PENDING → PROCESSING → COMPLETED
↘
FAILED / CANCELLED
```
1. [enterprise.export.create](/enterprise/v2/enterprise.export.create) returns immediately with `EXPORT_STATUS_PENDING`.
2. [enterprise.export.detail](/enterprise/v2/enterprise.export.detail) reports `PROCESSING`, then `COMPLETED` (or `FAILED` / `CANCELLED`). Completed records carry `file_url` and `url_expires_at`.
3. [enterprise.export.downloadUrl](/enterprise/v2/enterprise.export.downloadUrl) mints a fresh 10-minute pre-signed URL when the embedded one has expired.
## Operational notes
* **User scope:** You can export data for any user who is currently or was previously a member of your enterprise, including deleted accounts.
* **File retention:** Export archives are retained for **30 days** after completion.
* **Download URL validity:** Pre-signed URLs expire after **10 minutes**; mint a new one if needed.
* **Concurrency:** Recommended to keep at most one in-flight export per user.
* **Audit logging:** All export operations are logged with the supplied `reason` for compliance review.
# enterprise.compliance.export.create
Source: https://docs.manus.ai/enterprise/v2/enterprise.compliance.export.create
POST /v2/enterprise.compliance.export.create
Creates an audit-event export task. Runs asynchronously — the call returns immediately with status `COMPLIANCE_EXPORT_STATUS_PENDING`. Poll [enterprise.compliance.export.detail](/enterprise/v2/enterprise.compliance.export.detail) until status is `COMPLIANCE_EXPORT_STATUS_COMPLETED`, then call [enterprise.compliance.export.downloadUrl](/enterprise/v2/enterprise.compliance.export.downloadUrl) to fetch the archive. All filters are optional; omit them to export every event your enterprise's teams have captured.
Questions or issues? Contact us at [api-support@manus.ai](mailto:api-support@manus.ai).
**Auth:** Requires a **SIEM** enterprise API key (`X-API-Key`).
**Async:** Returns immediately with `COMPLIANCE_EXPORT_STATUS_PENDING`. Poll [enterprise.compliance.export.detail](/enterprise/v2/enterprise.compliance.export.detail) until status is `COMPLIANCE_EXPORT_STATUS_COMPLETED`, then call [enterprise.compliance.export.downloadUrl](/enterprise/v2/enterprise.compliance.export.downloadUrl).
**Filters:** `user` (user ID), `session_uid`, `start_time` (inclusive), `end_time` (exclusive), and `event_names` are all optional. Omit them to export every captured event across your enterprise's teams.
**Payloads:** Set `include_payload=true` to embed chat text, tool arguments, and tool results. Payloads exist only for Tier 2 data (requires a signed SIEM DPA); for Tier 1 (metadata-only) capture this flag has no effect.
**Concurrency:** One export at a time per enterprise. Creating a new export while another is `PENDING` or `PROCESSING` returns `failed_precondition`.
**Audit:** `reason` is logged for compliance review — supply a meaningful value.
**Output:** The archive is a ZIP containing a single `events.ndjson` file. See [Record format](/enterprise/v2/siem-record-format).
**Protocols:** Also callable via Connect RPC. See [Protocols](/enterprise/v2/protocols).
# enterprise.compliance.export.detail
Source: https://docs.manus.ai/enterprise/v2/enterprise.compliance.export.detail
GET /v2/enterprise.compliance.export.detail
Retrieves the status and metadata of an export task. Poll this endpoint after [enterprise.compliance.export.create](/enterprise/v2/enterprise.compliance.export.create) until `record.status` is `COMPLIANCE_EXPORT_STATUS_COMPLETED` (or `COMPLIANCE_EXPORT_STATUS_FAILED`).
Questions or issues? Contact us at [api-support@manus.ai](mailto:api-support@manus.ai).
**Auth:** Requires a **SIEM** enterprise API key (`X-API-Key`).
**Polling:** Call this every few seconds after [enterprise.compliance.export.create](/enterprise/v2/enterprise.compliance.export.create) until `record.status` is `COMPLIANCE_EXPORT_STATUS_COMPLETED` or `COMPLIANCE_EXPORT_STATUS_FAILED`.
**Completed:** `record.event_count` and `record.file_size` report the archive contents. Fetch the archive with [enterprise.compliance.export.downloadUrl](/enterprise/v2/enterprise.compliance.export.downloadUrl).
**Failures:** When status is `COMPLIANCE_EXPORT_STATUS_FAILED`, `record.error_message` describes the cause.
**Protocols:** Also callable via Connect RPC. See [Protocols](/enterprise/v2/protocols).
# enterprise.compliance.export.downloadUrl
Source: https://docs.manus.ai/enterprise/v2/enterprise.compliance.export.downloadUrl
GET /v2/enterprise.compliance.export.downloadUrl
Returns a temporary pre-signed download URL for a completed export. The URL expires after 15 minutes — call this endpoint again to mint a new one. Only callable when the export's status is `COMPLIANCE_EXPORT_STATUS_COMPLETED`.
Questions or issues? Contact us at [api-support@manus.ai](mailto:api-support@manus.ai).
**Auth:** Requires a **SIEM** enterprise API key (`X-API-Key`).
**URL validity:** The returned `download_url` expires after **15 minutes**. Call this endpoint again to mint a fresh URL — it does not consume any quota.
**Status requirement:** The export must be `COMPLIANCE_EXPORT_STATUS_COMPLETED`. Other statuses return `failed_precondition`.
**Archive:** A ZIP containing a single `events.ndjson` file (one JSON event per line). See [Record format](/enterprise/v2/siem-record-format).
**Protocols:** Also callable via Connect RPC. See [Protocols](/enterprise/v2/protocols).
# enterprise.export.create
Source: https://docs.manus.ai/enterprise/v2/enterprise.export.create
POST /v2/enterprise.export.create
Creates an enterprise compliance export task for a user. Runs asynchronously — the call returns immediately with status `EXPORT_STATUS_PENDING`. Poll [enterprise.export.detail](/enterprise/v2/enterprise.export.detail) until status is `EXPORT_STATUS_COMPLETED`, then call [enterprise.export.downloadUrl](/enterprise/v2/enterprise.export.downloadUrl) to fetch the archive.
Questions or issues? Contact us at [api-support@manus.ai](mailto:api-support@manus.ai).
**Required combos:** Provide either `user_id` or `email`. When `scope` is `ENTERPRISE_EXPORT_SCOPE_CUSTOM_SESSION_IDS`, `session_ids` is required. When `scope` is `ENTERPRISE_EXPORT_SCOPE_WEBDEV_PROJECT`, optionally narrow with `webdev_project_uids`. When `scope` is `ENTERPRISE_EXPORT_SCOPE_PROJECT`, optionally narrow with `project_uids`.
**Async:** Returns immediately with `EXPORT_STATUS_PENDING`. Poll [enterprise.export.detail](/enterprise/v2/enterprise.export.detail) until status is `EXPORT_STATUS_COMPLETED`, then call [enterprise.export.downloadUrl](/enterprise/v2/enterprise.export.downloadUrl).
**Concurrency:** Recommended to keep at most one in-flight export per user.
**Audit:** `reason` is logged for compliance review — supply a meaningful value.
**Protocols:** Also callable via Connect RPC. See [Protocols](/enterprise/v2/protocols).
# enterprise.export.detail
Source: https://docs.manus.ai/enterprise/v2/enterprise.export.detail
GET /v2/enterprise.export.detail
Retrieves the status and metadata of an export task. Poll this endpoint after [enterprise.export.create](/enterprise/v2/enterprise.export.create) until `record.status` is `EXPORT_STATUS_COMPLETED`.
Questions or issues? Contact us at [api-support@manus.ai](mailto:api-support@manus.ai).
**Polling:** Call this every few seconds after [enterprise.export.create](/enterprise/v2/enterprise.export.create) until `record.status` is `EXPORT_STATUS_COMPLETED` or `EXPORT_STATUS_FAILED`.
**Failures:** When status is `EXPORT_STATUS_FAILED`, `record.error_message` describes the cause. `EXPORT_STATUS_CANCELLED` indicates the export was cancelled before completion.
**Completed records carry the URL:** When `record.status` is `EXPORT_STATUS_COMPLETED`, `record.file_url` already contains a pre-signed download URL with `record.url_expires_at`. Call [enterprise.export.downloadUrl](/enterprise/v2/enterprise.export.downloadUrl) only when that URL has expired.
**File retention:** Completed exports are retained for 30 days; after that the underlying file is purged and `enterprise.export.downloadUrl` will fail.
**Protocols:** Also callable via Connect RPC. See [Protocols](/enterprise/v2/protocols).
# enterprise.export.downloadUrl
Source: https://docs.manus.ai/enterprise/v2/enterprise.export.downloadUrl
GET /v2/enterprise.export.downloadUrl
Returns a temporary pre-signed download URL for a completed export. The URL expires after 10 minutes — call this endpoint again to mint a new one if needed. Only callable when the export's status is `EXPORT_STATUS_COMPLETED`.
Questions or issues? Contact us at [api-support@manus.ai](mailto:api-support@manus.ai).
**URL validity:** The returned `download_url` expires after **10 minutes**. Call this endpoint again to mint a fresh URL — it does not consume any quota.
**Status requirement:** The export must be `EXPORT_STATUS_COMPLETED`. Other statuses return `failed_precondition`.
**File retention:** Files are purged 30 days after the export completes. After that, this endpoint returns `not_found`.
**Protocols:** Also callable via Connect RPC. See [Protocols](/enterprise/v2/protocols).
# enterprise.export.list
Source: https://docs.manus.ai/enterprise/v2/enterprise.export.list
GET /v2/enterprise.export.list
Lists export task history for a user with page-based pagination.
Questions or issues? Contact us at [api-support@manus.ai](mailto:api-support@manus.ai).
**User identifier:** `user` accepts either a user ID or an email address.
**Pagination:** Page-based — `page` starts at 1, default `page_size` is 10. Use `total` from the response to compute the page count.
**Protocols:** Also callable via Connect RPC. See [Protocols](/enterprise/v2/protocols).
# enterprise.task.list
Source: https://docs.manus.ai/enterprise/v2/enterprise.task.list
GET /v2/enterprise.task.list
Lists tasks belonging to a specific user within the enterprise. Useful for previewing what data will be included in an export.
Questions or issues? Contact us at [api-support@manus.ai](mailto:api-support@manus.ai).
**Use case:** Preview what tasks belong to a user before triggering an export — useful when scoping `enterprise.export.create` with `start_time` / `end_time`.
**User identifier:** `user` accepts either a user ID or an email address.
**Soft-deleted tasks:** Set `include_deleted=true` to include tasks the user has deleted.
**Protocols:** Also callable via Connect RPC. See [Protocols](/enterprise/v2/protocols).
# Introduction
Source: https://docs.manus.ai/enterprise/v2/introduction
Manus Team APIs for enterprise administrators
Questions or issues? Contact us at [api-support@manus.ai](mailto:api-support@manus.ai).
# Manus Team API
The Manus Team API is a collection of admin-facing APIs that enterprise tenant owners use to manage and operate their Manus deployment programmatically. The current surface focuses on compliance workflows; additional product areas (user management, project administration, …) will be added over time.
## Available APIs
Export user data for compliance, legal hold, and internal audits.
Stream audit events to your SIEM, or export them on demand, for compliance and security monitoring.
Provision, update, and offboard team members from your IDP (SailPoint, Okta, custom).
Enumerate the team's shareable surface and override individual share scopes from a DSPM workflow.
## Get started
Before calling any endpoint:
* [Authenticate](/enterprise/v2/authentication) — issue an enterprise API key from Compliance settings.
* [Choose a protocol](/enterprise/v2/protocols) — every operation is callable via REST or Connect RPC.
## Base URL
All API requests are made to:
```
https://api.manus.im
```
## Response format
Every response is wrapped in a consistent envelope by `V2ResponseMiddleware`:
**Success:**
```json theme={null}
{
"ok": true,
"request_id": "req_abc123",
...
}
```
**Error:**
```json theme={null}
{
"ok": false,
"request_id": "req_abc123",
"error": {
"code": "invalid_argument",
"message": "..."
}
}
```
| Error code | Description |
| --------------------- | ----------------------------------------------------------------------------- |
| `invalid_argument` | Missing or invalid request parameters |
| `unauthenticated` | The API key is missing, invalid, or not valid for this endpoint's key type |
| `not_found` | The requested resource does not exist |
| `permission_denied` | Authenticated, but not permitted to perform this operation |
| `failed_precondition` | The resource is not in a valid state for the requested operation |
| `internal` | Internal server error — retry with backoff and contact support if it persists |
# Protocols: REST and Connect RPC
Source: https://docs.manus.ai/enterprise/v2/protocols
Every team v2 operation is callable via REST or Connect RPC
Questions or issues? Contact us at [api-support@manus.ai](mailto:api-support@manus.ai).
Every team v2 service is generated from a protobuf definition and exposed over **two interchangeable protocols**:
* **REST** — `POST/GET /v2/.` with JSON bodies / query strings. This is what the per-endpoint reference pages document.
* **Connect RPC** — `POST /./` with the same JSON body shape (or `application/connect+proto` for typed Connect clients).
Both share the same authentication header (`X-API-Key`), the same request/response payload, and the same `{ok, request_id, ...}` response wrapper.
## URL mapping
### Enterprise Data Export (`dataexport.v2.EnterpriseDataExportV2Service`)
| Operation | REST | Connect RPC |
| -------------- | ----------------------------------------------- | ------------------------------------------------------------------ |
| CreateExport | `POST /v2/enterprise.export.create` | `POST /dataexport.v2.EnterpriseDataExportV2Service/CreateExport` |
| GetExport | `GET /v2/enterprise.export.detail?uid=...` | `POST /dataexport.v2.EnterpriseDataExportV2Service/GetExport` |
| ListExports | `GET /v2/enterprise.export.list?user=...` | `POST /dataexport.v2.EnterpriseDataExportV2Service/ListExports` |
| GetDownloadURL | `GET /v2/enterprise.export.downloadUrl?uid=...` | `POST /dataexport.v2.EnterpriseDataExportV2Service/GetDownloadURL` |
| ListTasks | `GET /v2/enterprise.task.list?user=...` | `POST /dataexport.v2.EnterpriseDataExportV2Service/ListTasks` |
### Enterprise SIEM Integrations — export API (`observability.v2.EnterpriseComplianceV2Service`)
This service is authenticated with a `KEY_TYPE_SIEM` enterprise API key; other key types are rejected. The streaming (OTLP push) side is configured in the Manus console and is not part of this REST/RPC surface.
| Operation | REST | Connect RPC |
| -------------- | ---------------------------------------------------------- | --------------------------------------------------------------------- |
| CreateExport | `POST /v2/enterprise.compliance.export.create` | `POST /observability.v2.EnterpriseComplianceV2Service/CreateExport` |
| GetExport | `GET /v2/enterprise.compliance.export.detail?uid=...` | `POST /observability.v2.EnterpriseComplianceV2Service/GetExport` |
| GetDownloadURL | `GET /v2/enterprise.compliance.export.downloadUrl?uid=...` | `POST /observability.v2.EnterpriseComplianceV2Service/GetDownloadURL` |
### Team User Management (`team.v2.TeamUserManagementApiV2Service`)
| Operation | REST | Connect RPC |
| --------------- | ------------------------------------ | -------------------------------------------------------------- |
| ListUsers | `GET /v2/team.user.list` | `POST /team.v2.TeamUserManagementApiV2Service/ListUsers` |
| GetUser | `GET /v2/team.user.detail?email=...` | `POST /team.v2.TeamUserManagementApiV2Service/GetUser` |
| CreateUser | `POST /v2/team.user.create` | `POST /team.v2.TeamUserManagementApiV2Service/CreateUser` |
| UpdateUser | `POST /v2/team.user.update` | `POST /team.v2.TeamUserManagementApiV2Service/UpdateUser` |
| DelegateProfile | `POST /v2/team.user.delegate` | `POST /team.v2.TeamUserManagementApiV2Service/DelegateProfile` |
| ReclaimProfile | `POST /v2/team.user.reclaim` | `POST /team.v2.TeamUserManagementApiV2Service/ReclaimProfile` |
| RenameProfile | `POST /v2/team.user.rename` | `POST /team.v2.TeamUserManagementApiV2Service/RenameProfile` |
| RemoveMember | `POST /v2/team.user.remove` | `POST /team.v2.TeamUserManagementApiV2Service/RemoveMember` |
### Shared Team Asset Governance (`team.v2.TeamAssetAuditApiV2Service` and `team.v2.TeamAssetManageApiV2Service`)
The audit and management surfaces live on two separate services so they can be authenticated with independently-rotated API keys (`KEY_TYPE_TEAM_ASSET_AUDIT` vs. `KEY_TYPE_TEAM_ASSET_MGMT`).
| Operation | REST | Connect RPC |
| --------------------- | ---------------------------------- | ----------------------------------------------------------------- |
| ListShareableAssets | `GET /v2/team.asset.list` | `POST /team.v2.TeamAssetAuditApiV2Service/ListShareableAssets` |
| UpdateAssetShareScope | `POST /v2/team.asset.update_scope` | `POST /team.v2.TeamAssetManageApiV2Service/UpdateAssetShareScope` |
## When to choose which
* **Use REST** for ad-hoc curl, third-party HTTP clients, no-code platforms, and the auto-rendered "Try it" panel on each endpoint page.
* **Use Connect RPC** for typed clients (Go, TypeScript, Python with generated stubs) where you want compile-time guarantees on request and response shapes. Connect supports the same wire format over HTTP+JSON, so any plain HTTP client also works.
The body shape is identical either way. GET REST endpoints use query parameters; the equivalent Connect call moves those same fields into a JSON body.
## Connect RPC examples
Same operation (`GetExport`) called three ways:
```bash buf curl theme={null}
buf curl \
--schema buf.build/manus/dataexport \
--header 'X-API-Key: ' \
--data '{"uid":"5YX76pz7Dga3yztNVw97Dh"}' \
https://api.manus.im/dataexport.v2.EnterpriseDataExportV2Service/GetExport
```
```typescript Connect (TypeScript) theme={null}
import { createPromiseClient } from "@connectrpc/connect";
import { createConnectTransport } from "@connectrpc/connect-web";
import { EnterpriseDataExportV2Service } from "./gen/dataexport/v2/export_enterprise_v2_connect";
const transport = createConnectTransport({
baseUrl: "https://api.manus.im",
interceptors: [
(next) => async (req) => {
req.header.set("X-API-Key", process.env.MANUS_ENTERPRISE_API_KEY!);
return next(req);
},
],
});
const client = createPromiseClient(EnterpriseDataExportV2Service, transport);
const res = await client.getExport({ uid: "5YX76pz7Dga3yztNVw97Dh" });
console.log(res.record);
```
```go Connect (Go) theme={null}
package main
import (
"context"
"log"
"net/http"
"connectrpc.com/connect"
dataexportv2 "example.com/gen/dataexport/v2"
"example.com/gen/dataexport/v2/dataexportv2connect"
)
func main() {
client := dataexportv2connect.NewEnterpriseDataExportV2ServiceClient(
http.DefaultClient,
"https://api.manus.im",
)
req := connect.NewRequest(&dataexportv2.GetExportRequest{
Uid: "5YX76pz7Dga3yztNVw97Dh",
})
req.Header().Set("X-API-Key", "")
res, err := client.GetExport(context.Background(), req)
if err != nil {
log.Fatal(err)
}
log.Println(res.Msg.Record)
}
```
The auto-rendered "Try it" panel on each endpoint page only exercises the REST surface. For Connect-specific client generation, see the [Connect RPC docs](https://connectrpc.com/docs).
# Overview
Source: https://docs.manus.ai/enterprise/v2/siem-integrations-overview
Stream Manus audit events to your SIEM, or export them on demand, for compliance and security monitoring
Questions or issues? Contact us at [api-support@manus.ai](mailto:api-support@manus.ai).
# Enterprise SIEM Integrations
Export Manus agent activity as an audit feed for **compliance**, **security monitoring**, and **incident investigation**. Every step of an agent session is captured as one of four event types:
| Event | What it records |
| ------------- | --------------------------------------------------------- |
| `USER_CHAT` | A message the user sent to the agent |
| `AGENT_REPLY` | A reply the agent produced (a notification or a question) |
| `TOOL_CALL` | A tool invocation, with its arguments |
| `TOOL_RESULT` | The result returned by a tool |
## Two tiers of data
* **Tier 1 — metadata.** Identity, timing, event type, outcome, and tool/connector attributes. No message content.
* **Tier 2 — metadata + payload.** Everything in Tier 1 plus the actual content: chat text, tool-call arguments, and tool results. Requires a signed SIEM Data Processing Agreement (DPA).
## Two ways to receive it
Manus pushes OpenTelemetry records to your OTLP endpoints in real time. Tier 1 destinations receive metadata; Tier 2 destinations receive metadata + payload.
Run an asynchronous export over HTTP with a **SIEM** [enterprise API key](/enterprise/v2/authentication) and download an archive of the matching events.
Both carry the **same record schema** — see [Record format](/enterprise/v2/siem-record-format).
## Streaming (push)
Manus delivers audit events to OTLP endpoints you control. Destinations are configured in the Manus console; streaming setup uses your console sign-in, not an enterprise API key.
* **Protocols:** OTLP/HTTP and OTLP/gRPC.
* **Destination tiers:** a Tier 1 destination receives metadata-only records; a Tier 2 destination receives metadata + payload (selectable only once your enterprise has signed the SIEM DPA).
* **Endpoint auth:** attach an auth header (e.g. `Authorization: Bearer …`); Manus stores it encrypted and sends it on every push.
* **Health & control:** destinations can be paused and resumed, expose last-success / last-failure timestamps and a consecutive-failure count, and support a connection test that sends an empty OTLP request to verify reachability and auth.
* **Large payloads:** payloads above the inline limit are omitted from the Tier 2 stream; retrieve them with an export (`include_payload=true`).
## Export (on demand)
An asynchronous API authenticated with a **SIEM** enterprise API key (`X-API-Key`). You submit an export, poll until it completes, then download a ZIP archive of newline-delimited JSON (NDJSON) — one event per line.
Submit an export with optional user, session, time-range, and event-type filters
Poll the task until it reaches `COMPLIANCE_EXPORT_STATUS_COMPLETED`
Mint a 15-minute pre-signed URL for the completed archive
### Export lifecycle
```
PENDING → PROCESSING → COMPLETED
↘
FAILED
```
1. [enterprise.compliance.export.create](/enterprise/v2/enterprise.compliance.export.create) returns immediately with `COMPLIANCE_EXPORT_STATUS_PENDING`. Set `include_payload=true` to embed event payloads (Tier 2 only).
2. [enterprise.compliance.export.detail](/enterprise/v2/enterprise.compliance.export.detail) reports `PROCESSING`, then `COMPLETED` (or `FAILED`). Completed records carry `event_count` and `file_size`.
3. [enterprise.compliance.export.downloadUrl](/enterprise/v2/enterprise.compliance.export.downloadUrl) mints a 15-minute pre-signed URL for the archive.
Only one export runs at a time per enterprise; creating another while one is in progress returns `failed_precondition`.
## Operational notes
* **Prerequisite — enable SIEM capture:** Audit events become exportable once your team has at least one active SIEM destination. A Tier 1 destination captures metadata; adding a Tier 2 destination with a signed SIEM DPA also captures payloads. With no active destination, nothing is captured and exports come back empty.
* **Tenancy:** A SIEM key is scoped to your enterprise; exports only ever include events from teams your enterprise owns. Streamed records carry `tenant.team_uid` so you can route per team.
* **Retention:** Manus keeps audit events for **1 year** from each event's `occurred_at`, after which they are permanently deleted. Records already streamed to your SIEM are governed by your own retention policy.
* **Ordering:** Events in the archive are ordered newest first (`occurred_at` descending).
* **Secret redaction:** Inside tool arguments and results, values under secret-like keys (`token`, `password`, `api_key`, …) are replaced with `[REDACTED]` before any event is stored, streamed, or exported.
* **Authentication:** The export API requires a **SIEM** enterprise API key (see [Authentication](/enterprise/v2/authentication)). Streaming destinations are managed in the Manus console.
# Record format
Source: https://docs.manus.ai/enterprise/v2/siem-record-format
The OpenTelemetry schema of Manus audit records — fields, attribute keys, and payload structure
Questions or issues? Contact us at [api-support@manus.ai](mailto:api-support@manus.ai).
# Record format
Every audit event is one record with a single schema, delivered two ways:
* **Streamed (push):** an OpenTelemetry [LogRecord](https://opentelemetry.io/docs/specs/otel/logs/data-model/) sent over OTLP. Attribute keys follow the OpenTelemetry [GenAI semantic conventions](https://opentelemetry.io/docs/specs/semconv/gen-ai/), with a few Manus extensions.
* **Exported (API):** one JSON object per line (NDJSON) inside the export archive's `events.ndjson`.
The values are identical; only the field names differ.
## Export line (NDJSON)
Each line in `events.ndjson` is one event:
```json theme={null}
{
"event_id": "01JABCDE…",
"team_uid": "team_abc",
"user_id": "114504",
"session_uid": "5YX76pz7Dga3yztNVw97Dh",
"event_name": "TOOL_CALL",
"outcome": "SUCCESS",
"occurred_at": "2026-06-09T12:00:00Z",
"metadata": { "eventId": "01JABCDE…", "eventName": "EVENT_NAME_TOOL_CALL", "genAiToolName": "shell_exec" },
"payload": { "gen_ai_tool_call_arguments_json": { "command": "ls" } }
}
```
* **Top-level fields** are convenience columns in `snake_case`.
* **`metadata`** is the full record (keys in `camelCase`; see [Metadata fields](#metadata-fields)). Present on every line.
* **`payload`** is present only when the export was created with `include_payload=true` (keys in `snake_case`; see [Payload](#payload-tier-2)).
## OTLP envelope (streamed records)
A streamed record is an OTel LogRecord with:
| Part | Value |
| -------------------------- | -------------------------------------------------------------------------- |
| Resource `service.name` | `manus-agent` |
| Resource `tenant.team_uid` | Team the event belongs to |
| Resource `tenant.region` | Tenant data-residency region |
| Scope | `manus.audit.tier1` (metadata) or `manus.audit.tier2` (metadata + payload) |
| Time | Event time (`occurred_at`) |
| Observed time | Ingestion time (`ingested_at`) |
| Severity | `INFO` (9), `WARN` (13), or `ERROR` (17) |
| Body | Empty for Tier 1; the payload for Tier 2 |
## Metadata fields
These are the keys inside the export line's `metadata` object, and the matching attribute on a streamed OTLP record:
| Meaning | OTLP attribute | `metadata` key |
| ------------------ | ---------------------------- | ------------------------ |
| Event ID (ULID) | `event.id` | `eventId` |
| Schema version | `schema.version` | `schemaVersion` |
| Event type | `event.name` | `eventName` |
| Outcome | `outcome` | `outcome` |
| User ID | `user.id` | `userId` |
| Session (task) UID | `session.id` | `sessionUid` |
| Request / turn ID | `request.id` | `requestId` |
| Source channel | `source_channel` | `sourceChannel` |
| Team UID | *resource* `tenant.team_uid` | `teamUid` |
| Tenant namespace | — | `tenantNamespace` |
| Tenant region | *resource* `tenant.region` | `tenantRegion` |
| Event time | *record time* | `occurredAt` |
| Ingestion time | *observed time* | `ingestedAt` |
| Severity | *severity number / text* | `severity` |
| Tool name | `gen_ai.tool.name` | `genAiToolName` |
| Tool call ID | `gen_ai.tool.call.id` | `genAiToolCallId` |
| Tool subtype | `gen_ai.tool.subtype` | `genAiToolSubtype` |
| Connector name | `gen_ai.tool.connector.name` | `genAiToolConnectorName` |
| Connector ID | `gen_ai.tool.connector.id` | `genAiToolConnectorId` |
| Connector type | `gen_ai.tool.connector.type` | `genAiToolConnectorType` |
| Agent reply kind | `agent.reply.kind` | `agentReplyKind` |
| Client address | `client.address` | `clientAddress` |
| User agent | `user_agent.original` | `userAgent` |
| Geo country | `geo.country_iso_code` | `geoCountry` |
| Input bytes | `input.bytes` | `inputBytes` |
| Output bytes | `output.bytes` | `outputBytes` |
| Message count | `message.count` | `messageCount` |
Notes on specific fields:
* **`event.name` / `eventName`** — one of `USER_CHAT`, `AGENT_REPLY`, `TOOL_CALL`, `TOOL_RESULT`. Streamed records and the export's top-level `event_name` use the short form (`TOOL_CALL`); the `metadata` object uses the full proto enum (`EVENT_NAME_TOOL_CALL`). The same short-vs-full difference applies to `outcome` (`SUCCESS` vs `OUTCOME_SUCCESS`) and `agentReplyKind` (`notify` / `ask` vs `AGENT_REPLY_KIND_NOTIFY`).
* **`request.id`** — shared by every event in one agent turn; use it to group a user message with the replies and tool calls it triggered.
* **`source_channel`** — how the interaction reached Manus: `in_app` (the Manus web app) or `instant_messaging` (an IM integration such as Slack).
* **Tool attributes** — present on `TOOL_CALL` / `TOOL_RESULT`. `gen_ai.tool.name` is the high-level tool (e.g. `shell_exec`); `gen_ai.tool.subtype` is the model-facing name (e.g. `shell`). The `gen_ai.tool.connector.*` keys are Manus extensions, present only when the tool is a connector (e.g. Slack) — its name, UUID, and type (`mcp`, `native`, …).
* **`agent.reply.kind`** — on `AGENT_REPLY` only: `notify` (a notification) or `ask` (a question to the user).
* **Sizes** — `inputBytes` / `outputBytes` count the content bytes; `messageCount` is the number of messages. In `metadata`, 64-bit integers (`inputBytes`, `outputBytes`) are serialized as strings per protobuf JSON convention; `messageCount` is a number.
* **Reserved** — LLM request/response fields (`gen_ai.request.model`, `gen_ai.usage.*`, …) are reserved for a future version and are not emitted today.
## Payload (Tier 2)
The payload — the `payload` object in an export (when `include_payload=true`), or the body of a streamed Tier 2 record — is a JSON object whose fields depend on the event type. Keys are `snake_case`:
| Field | Event types | Description |
| --------------------------------- | -------------------------- | ------------------------------------------------------------------------------ |
| `chat_text` | `USER_CHAT`, `AGENT_REPLY` | Message content (plain text or a rich-content array, passed through unchanged) |
| `attachments` | `USER_CHAT`, `AGENT_REPLY` | Attached files, if any |
| `agent_reply_kind` | `AGENT_REPLY` | `notify` or `ask` |
| `gen_ai_tool_call_arguments_json` | `TOOL_CALL` | The tool's input arguments |
| `gen_ai_tool_call_result_json` | `TOOL_RESULT` | The tool's output |
| `gen_ai_tool_call_status` | `TOOL_RESULT` | Execution status of the tool call |
Only the fields relevant to an event's type are present.
Inside tool arguments and results, values under secret-like keys (containing `token`, `password`, `secret`, `api_key`, `credential`, …) are replaced with `[REDACTED]` before the payload is stored, streamed, or exported.
# team.asset.list
Source: https://docs.manus.ai/enterprise/v2/team.asset.list
GET /v2/team.asset.list
Lists shareable team assets filtered by type, owner, time range, and (raw) permission. Use this to enumerate the team's sharing surface for DSPM scans. Accepts either `KEY_TYPE_TEAM_ASSET_AUDIT` or `KEY_TYPE_TEAM_ASSET_MGMT` (write keys implicitly include read access).
Questions or issues? Contact us at [api-support@manus.ai](mailto:api-support@manus.ai).
**Auth:** Accepts either `KEY_TYPE_TEAM_ASSET_AUDIT` or `KEY_TYPE_TEAM_ASSET_MGMT` (write keys implicitly include read access). The audit row records which key type actually made the call.
**Asset type is required.** A single call enumerates one type. To scan the full surface, call this endpoint once per [`AssetType`](/enterprise/v2/asset-governance-overview#asset-types).
**Effective vs. raw scope.** The `permission` query filter matches the **raw** `ShareScope` stored on the share row (before admin override and global controls are applied). The `permission` returned in each result is the **effective** scope after `max_permission` has capped it. **Admin override always wins**: if a share row has an admin-set `max_permission`, that override is what `max_permission` reflects (the team's global control is bypassed); otherwise the team's global asset control fills in `max_permission` and tightens `permission` accordingly. See the [overview](/enterprise/v2/asset-governance-overview#effective-permission-and-admin-override) for the full rule.
**`SHARE_SCOPE_PUBLIC` filter.** Only matches the v1 value the asset\_type writes today — see the [v1↔v2 mapping](/enterprise/v2/asset-governance-overview#mapping-to-v1-sharepermission). Legacy rows stored with the other v1 value will not be returned for that filter.
**Stale shares.** Rows with `asset_exists: false` mean the underlying session / file / web-publish was deleted but the share record remains. They cannot be re-scoped via [team.asset.update\_scope](/enterprise/v2/team.asset.update_scope).
**Pagination:** Offset-based — `limit` defaults to 100, max 1000; `offset` defaults to 0. Use `total` to compute the page count.
**Protocols:** Also callable via Connect RPC. See [Protocols](/enterprise/v2/protocols).
# team.asset.update_scope
Source: https://docs.manus.ai/enterprise/v2/team.asset.update_scope
POST /v2/team.asset.update_scope
Overrides the permission scope of a single share record as the team admin and writes a `note` into the audit log. Requires an API key of type `KEY_TYPE_TEAM_ASSET_MGMT`.
Questions or issues? Contact us at [api-support@manus.ai](mailto:api-support@manus.ai).
**Admin override.** This endpoint changes the share scope of a resource that does not belong to the caller. Use it from controlled DSPM workflows only and always supply a meaningful `note` — it is written to the audit log and is the only record of why the override happened.
**Auth:** Requires an `KEY_TYPE_TEAM_ASSET_MGMT` enterprise API key. Audit-class keys are rejected.
**Lookup `share_uid` first.** Call [team.asset.list](/enterprise/v2/team.asset.list) to discover the `uid` of the share row you want to override. The `share_uid` must belong to the same team as the API key.
**Note is mandatory in spirit.** Although `note` is technically optional, it is written verbatim to `team_asset_audit_logs.remark` and is the only place the override's intent is recorded — supply something meaningful. The audit row also records the calling key's name in `api_key_name`, which is what operators reviewing the team asset audit log use to tell API-driven overrides apart from manual admin actions — see the [overview](/enterprise/v2/asset-governance-overview#operational-notes) for the full semantics.
**Protocols:** Also callable via Connect RPC. See [Protocols](/enterprise/v2/protocols).
# team.user.create
Source: https://docs.manus.ai/enterprise/v2/team.user.create
POST /v2/team.user.create
Adds a new member to the team. If the email does not yet have a Manus account, a personal account is created automatically and immediately deactivated outside the team scope. `role` must be `TEAM_MEMBER_ROLE_SUPER_ADMIN`, `TEAM_MEMBER_ROLE_ADMIN`, `TEAM_MEMBER_ROLE_MEMBER`, or `TEAM_MEMBER_ROLE_GUEST` — `TEAM_MEMBER_ROLE_OWNER` and `TEAM_MEMBER_ROLE_UNSPECIFIED` are rejected.
Questions or issues? Contact us at [api-support@manus.ai](mailto:api-support@manus.ai).
**Auto-provisioning:** If the email does not yet have a Manus account, a personal account is created automatically (deactivated outside the team). The team membership is then attached to that account.
**Role:** Required. Accepts `TEAM_MEMBER_ROLE_SUPER_ADMIN`, `TEAM_MEMBER_ROLE_ADMIN`, `TEAM_MEMBER_ROLE_MEMBER`, or `TEAM_MEMBER_ROLE_GUEST`. `TEAM_MEMBER_ROLE_OWNER` and `TEAM_MEMBER_ROLE_UNSPECIFIED` return `invalid_argument` — transfer ownership in the UI instead. Guests do not consume a paid seat.
**Display name:** Optional. If `first_name` and/or `last_name` are provided they are joined with a space; otherwise `user_name` is used as-is.
**Idempotency:** The endpoint is **not** idempotent — calling it twice with the same email returns `already_exists` on the second call. Read the existing record with [team.user.detail](/enterprise/v2/team.user.detail) first if you may be retrying.
**Protocols:** Also callable via Connect RPC. See [Protocols](/enterprise/v2/protocols).
# team.user.delegate
Source: https://docs.manus.ai/enterprise/v2/team.user.delegate
POST /v2/team.user.delegate
Delegates a deactivated profile (`team_user_id`) to an active member (`target_team_user_id`). The delegated profile's `email` is rewritten to `delegate-xxx@manus.im` and the original email is preserved in `original_email`. Pre-checks: the source must be currently `INACTIVE`, the target must be an active non-delegated member, and the source cannot be the team owner.
Questions or issues? Contact us at [api-support@manus.ai](mailto:api-support@manus.ai).
**Source must be deactivated:** `team_user_id` (the source profile) must currently have `status = USER_STATUS_INACTIVE`. Active members and already-removed users are rejected with `failed_precondition` — call [team.user.update](/enterprise/v2/team.user.update) with `status: USER_STATUS_INACTIVE` first if needed.
**Target must be active and never delegated:** `target_team_user_id` must be an active team member that is not currently a delegated profile. This prevents cycles and ownership-laundering through chained delegations.
**Owner cannot be delegated:** The team owner's profile is rejected with `failed_precondition`.
**Email rewrite:** On success, the source's `email` becomes `delegate-@manus.im` and the original is preserved in `original_email`. Subsequent operations on this profile must use `team_user_id`.
**Role (required):** uses the `MigratedProfileRole` enum, distinct from the `TeamMemberRole` used elsewhere. Send `MIGRATED_PROFILE_ROLE_MEMBER` to activate as a regular member, `MIGRATED_PROFILE_ROLE_FREE_GUEST` to activate as a free (non-seat-consuming) guest, or `MIGRATED_PROFILE_ROLE_DEACTIVATED` to keep the profile in the deactivated pool under the new assignee. `MIGRATED_PROFILE_ROLE_UNSPECIFIED` is rejected — the field must carry an explicit value.
**Protocols:** Also callable via Connect RPC. See [Protocols](/enterprise/v2/protocols).
# team.user.detail
Source: https://docs.manus.ai/enterprise/v2/team.user.detail
GET /v2/team.user.detail
Looks up a single team member by `email` or `team_user_id`. When both are supplied, `team_user_id` wins. The response always expands `delegated_profiles[]` for users that have profiles delegated to them.
Questions or issues? Contact us at [api-support@manus.ai](mailto:api-support@manus.ai).
**Identifier:** Provide either `email` or `team_user_id`. When both are supplied, `team_user_id` wins.
**Use `team_user_id` for delegated profiles:** After delegation a profile's email is rewritten to `delegate-@manus.im` — the original is preserved in `original_email` but the `email` field no longer matches what your IDP knows about. Look up by `team_user_id` to avoid surprises.
**Delegation expansion:** Unlike [team.user.list](/enterprise/v2/team.user.list), this endpoint always populates `delegated_profiles[]` for users that have profiles delegated to them.
**Protocols:** Also callable via Connect RPC. See [Protocols](/enterprise/v2/protocols).
# team.user.list
Source: https://docs.manus.ai/enterprise/v2/team.user.list
GET /v2/team.user.list
Lists team members with optional status / delegation filters. Default page size is 100, max 1000.
Questions or issues? Contact us at [api-support@manus.ai](mailto:api-support@manus.ai).
**Pagination:** Offset-based — `limit` defaults to 100, max 1000; `offset` defaults to 0. Use `total` from the response to compute the page count.
**Filtering:** `status_filter` and `delegation_state` can be combined. Omit either to disable that filter.
**Delegation expansion:** `delegated_profiles[]` is **omitted** in list responses to avoid N+1 fan-out. Call [team.user.detail](/enterprise/v2/team.user.detail) to see what is delegated to a specific user.
**Protocols:** Also callable via Connect RPC. See [Protocols](/enterprise/v2/protocols).
# team.user.reclaim
Source: https://docs.manus.ai/enterprise/v2/team.user.reclaim
POST /v2/team.user.reclaim
Reclaims a delegated profile and returns it to the deactivated pool. The previous assignee is reported in `previous_delegate`. Cannot be called on a profile that is not currently delegated.
Questions or issues? Contact us at [api-support@manus.ai](mailto:api-support@manus.ai).
**Profile must be currently delegated:** Returns `failed_precondition` if the `team_user_id` has no current assignee. Inspect `delegated_to` on [team.user.detail](/enterprise/v2/team.user.detail) before calling.
**Reversible:** The reclaimed profile returns to the deactivated pool unchanged — it can be re-delegated later via [team.user.delegate](/enterprise/v2/team.user.delegate).
**`previous_delegate`:** The response always reports the assignee that the profile was reclaimed from, so you can audit the change against your IDP records.
**Bulk reclaim is automatic:** Deactivating or removing the assignee reclaims everything they hold in one go — see [team.user.update](/enterprise/v2/team.user.update) and [team.user.remove](/enterprise/v2/team.user.remove) — so this endpoint is only needed for one-off reassignments.
**Protocols:** Also callable via Connect RPC. See [Protocols](/enterprise/v2/protocols).
# team.user.remove
Source: https://docs.manus.ai/enterprise/v2/team.user.remove
POST /v2/team.user.remove
Permanently removes a team member. The action is irreversible — the user record is hard-deleted and any delegated profiles assigned to them are reclaimed. The team owner cannot be removed via API.
Questions or issues? Contact us at [api-support@manus.ai](mailto:api-support@manus.ai).
**Irreversible:** Hard-deletes the team member's user record. There is no undo — confirm with your offboarding workflow before calling.
**Cascade:** Any profiles delegated to this member are returned to the deactivated pool and listed in `cascade_affected[]`. Inspect [team.user.detail](/enterprise/v2/team.user.detail) first if you need to know which profiles will be reclaimed.
**Owner cannot be removed:** Returns `failed_precondition` for the team owner.
**Equivalent to status = REMOVED:** [team.user.update](/enterprise/v2/team.user.update) with `status: USER_STATUS_REMOVED` performs the same operation by `email`; this endpoint is the `team_user_id`-keyed variant and is preferred for IDP integrations that cache the stable id.
**Protocols:** Also callable via Connect RPC. See [Protocols](/enterprise/v2/protocols).
# team.user.rename
Source: https://docs.manus.ai/enterprise/v2/team.user.rename
POST /v2/team.user.rename
Updates the display name (`team_user_relation.Name`) for a profile. Useful for labelling delegated profiles. The team owner cannot be renamed via this endpoint.
Questions or issues? Contact us at [api-support@manus.ai](mailto:api-support@manus.ai).
**Use case:** Primarily for labelling **delegated profiles**, which by default surface as `delegate-@manus.im`. Setting a clear display name (e.g. "Alice (delegated from [bob@example.com](mailto:bob@example.com))") makes audit logs and the UI easier to read.
**Scope:** Only updates `team_user_relation.Name` (the team-scoped display name). The underlying personal user's `first_name` / `last_name` are not touched.
**Length:** 1–255 characters.
**Owner is API-immutable:** Returns `failed_precondition` for the team owner.
**Protocols:** Also callable via Connect RPC. See [Protocols](/enterprise/v2/protocols).
# team.user.update
Source: https://docs.manus.ai/enterprise/v2/team.user.update
POST /v2/team.user.update
Updates a team member's status and/or role. The target is identified by either `email` or `team_user_id` — when both are provided, `team_user_id` wins. At least one of `status` / `role` should be provided. When `status` becomes `INACTIVE` or `REMOVED` and the user has profiles delegated to them, those profiles are reclaimed and listed in `cascade_affected[]`. `status=REMOVED` cannot be combined with a `role` update. Promoting a `TEAM_MEMBER_ROLE_GUEST` member into a paid role triggers a Stripe seat-quantity bump; a downstream Stripe failure surfaces as `internal`.
Questions or issues? Contact us at [api-support@manus.ai](mailto:api-support@manus.ai).
**Identifier:** Provide either `email` or `team_user_id`. When both are supplied, `team_user_id` wins. **Use `team_user_id` for delegated profiles** — their `email` is rewritten to a synthetic `delegate-@...` address that no longer matches what your IDP knows.
**Status transitions:**
* `USER_STATUS_ACTIVE` ↔ `USER_STATUS_INACTIVE` — reversible.
* `USER_STATUS_REMOVED` — irreversible. Equivalent to calling [team.user.remove](/enterprise/v2/team.user.remove) on the underlying `team_user_id`. Cannot be combined with a `role` update.
**Cascade:** When `status` becomes `INACTIVE` or `REMOVED` and the user has profiles delegated to them, those profiles are returned to the deactivated pool and listed in `cascade_affected[]`.
**Role values:** `TEAM_MEMBER_ROLE_SUPER_ADMIN`, `TEAM_MEMBER_ROLE_ADMIN`, `TEAM_MEMBER_ROLE_MEMBER`, or `TEAM_MEMBER_ROLE_GUEST`. Delegated (and formerly-delegated) profiles can be promoted freely — `TEAM_MEMBER_ROLE_OWNER` is the only role API cannot set; transfer ownership in the UI.
**Seat sync side effect:** Promoting `TEAM_MEMBER_ROLE_GUEST → MEMBER/ADMIN/SUPER_ADMIN` triggers a Stripe seat-quantity bump so the subscription covers the new paid occupant. The reverse direction does not call Stripe — the seat is implicitly released the next time the team's count is computed. If Stripe rejects the upgrade, the API returns `internal` and the role change is rolled back to keep DB and billing in sync.
**Owner is API-immutable:** Returns `failed_precondition` for the team owner.
**Protocols:** Also callable via Connect RPC. See [Protocols](/enterprise/v2/protocols).
# Migrating from v1
Source: https://docs.manus.ai/enterprise/v2/user-management-migration
Move existing SailPoint, Okta, or custom IDP integrations from the v1 user management API to v2
Questions or issues? Contact us at [api-support@manus.ai](mailto:api-support@manus.ai).
v2 supersedes the v1 [Team User Management](/enterprise/v1/user-management-overview) surface. v1 is **not removed** — existing integrations keep working — but new integrations should target v2 directly, and existing ones benefit from a smaller blast radius for credential management, a unified response envelope, the profile-delegation workflow, and stronger field validation.
## What's new in v2
* **One-step auth.** `X-API-Key` issued from Compliance settings — no DevTools-extracted session token, no separate OAuth token endpoint, no 1-hour token rotation.
* **Single REST surface.** Every operation is `GET/POST /v2/team.user.*` with a unified `{ok, request_id, ...}` envelope. Connect RPC is also available at `/team.v2.TeamUserManagementApiV2Service/`.
* **Profile delegation.** [delegate](/enterprise/v2/team.user.delegate) / [reclaim](/enterprise/v2/team.user.reclaim) / [rename](/enterprise/v2/team.user.rename) / [remove](/enterprise/v2/team.user.remove) lets an active member take over a deactivated colleague's data.
* **Stable identifier.** Every team member has a `team_user_id` you can cache; safer than `email` for delegated profiles whose email is rewritten to a synthetic `delegate-@...` address.
* **Filters.** `status_filter` and `delegation_state` on [team.user.list](/enterprise/v2/team.user.list).
* **Server-side validation.** Email format, length limits, and enum membership are enforced at the wire level via protovalidate; bad inputs fail fast with `invalid_argument`.
## Authentication
v1 used a two-step credential dance (session token → CreateApiCredential → `client_id` + `client_secret` → OAuth token endpoint → 1-hour Bearer access token). v2 collapses this to a single header.
| Step | v1 | v2 |
| ---- | -------------------------------------------------------------------- | ---------------------------------------------------------------- |
| 1 | DevTools → copy session token | (none) |
| 2 | RPC `CreateApiCredential` → `clientId` + `clientSecret` (shown once) | Compliance settings → create key (type **Team User Management**) |
| 3 | `POST /api/user/manage/v1/oauth/token` → `access_token` (1 h) | (none) |
| 4 | `Authorization: Bearer ` | `X-API-Key: ` |
See [v2 Authentication](/enterprise/v2/authentication) for the full flow.
## Endpoint mapping
| v1 endpoint | v2 endpoint | Notes |
| ------------------------------------------------------------------------ | --------------------------------------- | ------------------------------------------------------- |
| `GET /api/user/manage/v1/users?limit=&offset=` | `GET /v2/team.user.list?limit=&offset=` | Plus optional `status_filter` / `delegation_state` |
| `GET /api/user/manage/v1/users/{email}` | `GET /v2/team.user.detail?email=...` | Or `?team_user_id=...` |
| `POST /api/user/manage/v1/users` | `POST /v2/team.user.create` | Body shape similar (snake\_case) |
| `PATCH /api/user/manage/v1/users/{email}` | `POST /v2/team.user.update` | Identifier moves into the body; method becomes POST |
| (none) | `POST /v2/team.user.delegate` | New: hand a deactivated profile to an active member |
| (none) | `POST /v2/team.user.reclaim` | New: return a delegated profile to the deactivated pool |
| (none) | `POST /v2/team.user.rename` | New: change a profile's display name |
| (none) | `POST /v2/team.user.remove` | New: hard-delete a member (cascades reclaim) |
| RPC `CreateApiCredential` / `ListApiCredentials` / `DeleteApiCredential` | Compliance settings UI | Credential CRUD moves out of the API surface |
## Field naming and enums
**Wire format.** v1 uses **camelCase** (`userName`, `firstName`, `lastName`); v2 uses **snake\_case** (`user_name`, `first_name`, `last_name`).
**Role.**
| v1 string | v2 enum |
| -------------------- | -------------------------------------------- |
| `"super_admin"` | `TEAM_MEMBER_ROLE_SUPER_ADMIN` |
| `"admin"` | `TEAM_MEMBER_ROLE_ADMIN` |
| `"member"` | `TEAM_MEMBER_ROLE_MEMBER` |
| `"free_tier_member"` | `TEAM_MEMBER_ROLE_GUEST` |
| `"owner"` | `TEAM_MEMBER_ROLE_OWNER` (read-only in both) |
`free_tier_member` was renamed to **GUEST** in v2 to drop billing-internal jargon. Same semantics — does not consume a paid Stripe seat. Promoting `GUEST → MEMBER/ADMIN/SUPER_ADMIN` triggers a Stripe seat-quantity bump in both v1 and v2.
**Status.**
| v1 string | v2 enum |
| --------------- | --------------------------------------------- |
| `"active"` | `USER_STATUS_ACTIVE` |
| `"inactive"` | `USER_STATUS_INACTIVE` |
| (UI-only in v1) | `USER_STATUS_REMOVED` ← **new, irreversible** |
v2 adds `USER_STATUS_REMOVED` which behaves like calling [team.user.remove](/enterprise/v2/team.user.remove) — hard-deletes the user and cascades reclaim of any profiles delegated to them.
**User response shape.**
| Field | v1 | v2 |
| ------------------------ | ------ | ---------------------------------------------------------- |
| `email` / `email` | ✓ | ✓ |
| `userName` / `user_name` | ✓ | ✓ |
| `firstName` / `lastName` | ✓ | not returned (use `user_name`) |
| `status` | string | `UserStatus` enum |
| `role` | string | `TeamMemberRole` enum |
| `team_user_id` | — | ✓ stable handle |
| `delegated_to` | — | ✓ |
| `delegated_profiles[]` | — | ✓ (single-record responses only — list omits to avoid N+1) |
| `original_email` | — | ✓ (pre-delegation email) |
## Response envelope
v1 returns the payload directly. v2 wraps every response in `{ok, request_id, ...}`.
```json v1 success (HTTP 200) theme={null}
{
"users": [...],
"total": 50,
"limit": 100,
"offset": 0
}
```
```json v2 success (HTTP 200) theme={null}
{
"ok": true,
"request_id": "req_abc123",
"users": [...],
"total": 50,
"limit": 100,
"offset": 0
}
```
```json v2 error (HTTP 4xx) theme={null}
{
"ok": false,
"request_id": "req_abc123",
"error": {
"code": "not_found",
"message": "user not found"
}
}
```
## Error code mapping
| v1 (UPPER\_SNAKE) | v2 (lower\_snake) |
| --------------------------------- | -------------------------------------------------------------------- |
| `USER_NOT_FOUND` | `not_found` |
| `USER_ALREADY_EXISTS` | `already_exists` |
| `INVALID_REQUEST` | `invalid_argument` |
| `UNAUTHORIZED` / `INVALID_CLIENT` | `permission_denied` |
| `INTERNAL_ERROR` | `internal` |
| (none) | `failed_precondition` (e.g. owner-immutable, source-not-deactivated) |
## Migration checklist
Create a **Team User Management** key in [Compliance settings](https://manus.im/app#settings/compliance/api-key). Copy it immediately — it is shown only once. See [v2 Authentication](/enterprise/v2/authentication).
v1 and v2 hit the same backing tables, so you can exercise [team.user.list](/enterprise/v2/team.user.list) against your live team without affecting v1 traffic. Confirm field names, role/status enum values, and pagination behavior.
Convert camelCase → snake\_case (`userName` → `user_name`, etc.). Replace role/status string literals with the new enum constants. Move identifiers from path to body for `team.user.update`.
Read `ok` / `error` instead of relying on HTTP status codes alone. Persist `request_id` in your logs to make support requests actionable.
Whenever you create a member or list members, store the returned `team_user_id`. Use it for subsequent updates — it survives renames and delegation, unlike `email`.
Once your integration is fully on v2, delete the old `client_id` / `client_secret` via [team.credential.delete](/enterprise/v1/team.credential.delete) so the v1 OAuth surface can no longer be used by stale callers.
## What stays the same
* Pagination defaults (`limit` 100, max 1000; `offset` 0-based)
* Owner is read-only via API
* Auto-provisioning of personal accounts during create
* All operations are audit-logged
* The Stripe seat-sync behavior on guest↔paid promotions
## SailPoint / Okta connector notes
If your IDP connector is wired against v1, the migration is mostly:
1. Swap the **Token URL** (`/api/user/manage/v1/oauth/token`) for an `X-API-Key` static header — no token endpoint to call.
2. Update endpoint paths and method for update (PATCH → POST).
3. Remap attribute paths under `users[]` (camelCase → snake\_case) and any role/status string lookups to the new enum constants.
4. Optionally map `team_user_id` as a secondary identifier alongside `email` so account aggregation continues to work for delegated profiles.
# Overview
Source: https://docs.manus.ai/enterprise/v2/user-management-overview
Manage Enterprise Team members programmatically from your IDP (SailPoint, Okta, custom integrations)
Questions or issues? Contact us at [api-support@manus.ai](mailto:api-support@manus.ai).
# Team User Management
Lets enterprise tenant administrators **provision, modify, and offboard team members** from external identity systems. Built for SCIM-style automation: account aggregation, role updates, lifecycle (active / inactive / removed), and the **Profile Migration** workflow for taking ownership of a deactivated colleague's data.
## Endpoints
Page through team members with status / delegation filters
Look up a single member by email or `team_user_id`
Create a new team member, auto-provisioning the underlying personal account if needed
Activate, deactivate, or change role; cascading reclaim of delegated profiles when deactivating
Hand a deactivated profile to an active member so they can take over its data
Return a delegated profile to the deactivated pool
Update a profile's display name (useful for delegated profiles)
Permanently remove a team member; cascading reclaim of delegated profiles
## User lifecycle
```
ACTIVE ⇄ INACTIVE → REMOVED (terminal, hard delete)
↑
(delegated profiles live here until reclaimed or re-delegated)
```
* **ACTIVE** — full access. Can be promoted/demoted via [team.user.update](/enterprise/v2/team.user.update) (except `OWNER`, which is API-immutable).
* **INACTIVE** — disabled. Once inactive, a profile becomes eligible for delegation via [team.user.delegate](/enterprise/v2/team.user.delegate).
* **REMOVED** — terminal. Hard-deletes the user; cascades to reclaim profiles delegated to them.
## Profile delegation
When an employee leaves, their account is deactivated and may then be **delegated** to an active teammate so the teammate can keep working on the data:
1. [team.user.update](/enterprise/v2/team.user.update) sets `status: USER_STATUS_INACTIVE` on the departing employee.
2. [team.user.delegate](/enterprise/v2/team.user.delegate) hands `team_user_id` to `target_team_user_id`. The delegated profile's email is rewritten to `delegate-@manus.im`; the original is preserved in `original_email`.
3. [team.user.rename](/enterprise/v2/team.user.rename) (optional) gives the delegated profile a human-readable label.
4. [team.user.reclaim](/enterprise/v2/team.user.reclaim) returns the profile to the deactivated pool — no data loss; can be re-delegated later.
Removing or deactivating an assignee automatically reclaims everything delegated to them; the affected profiles are returned in `cascade_affected[]`.
## Operational notes
* **Authentication:** `X-API-Key` with key type **Team User Management**. See [Authentication](/enterprise/v2/authentication).
* **Owner is read-only:** The team owner cannot be modified, deactivated, removed, or downgraded via the API. Transfer ownership in the UI first.
* **`team_user_id` is the stable handle:** Once a profile is delegated, its email becomes `delegate-@manus.im`. Cache the `team_user_id` from your initial `team.user.create` / `team.user.list` response and use it for every subsequent profile operation.
* **Guest role does not consume a seat:** Promoting `GUEST → MEMBER/ADMIN/SUPER_ADMIN` via [team.user.update](/enterprise/v2/team.user.update) calls Stripe to bump seat quantity; the reverse direction releases the seat implicitly. A Stripe failure during a paid promotion surfaces as `internal` and the role change is rolled back.
* **Audit logging:** Every call writes to the shared enterprise audit log, correlated by `request_id` from the response envelope.