Admin - Machine Collections Beta
Machine collections let an MSP admin create a temporary upload run for collecting machine inventory or diagnostic output from customer endpoints.
The admin creates a run with an MSP admin API key. The create response returns a constrained upload_token and an upload_url_endpoint. RMM scripts should use that constrained token to request one short-lived upload URL per machine. Do not deploy the MSP admin API key to customer machines.
This is a beta API. Your MSP must be enabled for the machine collections beta before these endpoints are available.
Flow
- Create a machine collection run.
- Deploy the returned
upload_tokenthrough your RMM tool. - Each endpoint calls the run-token upload URL endpoint with its machine metadata and intended filename.
- Each endpoint uploads its result file directly to the returned presigned URL.
- The MSP admin lists uploads and generates download URLs for files that are ready.
Supported default file extensions are json, csv, and txt. Upload URLs expire after 15 minutes. Runs expire after 24 hours by default and cannot be extended beyond 30 days.
Authentication
Admin endpoints require an MSP admin API key with the manage_machine_collections permission.
curl 'https://ai.hatz.ai/v1/admin/machine-collection-runs' \
-H 'X-API-Key: $HATZ_API_KEY'
The endpoint used by RMM scripts does not use an MSP admin API key. It is authorized by the constrained run token in the path:
POST /v1/machine-collection-runs/{upload_token}/upload-url
Create a Run
Create a run and receive the one-time upload token.
Endpoint
POST /v1/admin/machine-collection-runs
Request Body
| Field | Required | Description |
|---|---|---|
name |
Yes | Human-readable run name. |
description |
No | Optional run notes. |
target_tenant_id |
No | Tenant to associate with the run. Must belong to the MSP. |
sell_ai_customer_id |
No | Sell-AI customer to associate with the run. Must belong to the MSP. |
allowed_extensions |
No | Allowed file extensions. Defaults to ["json", "csv", "txt"]. |
allowed_mime_types |
No | Optional allowed MIME types. If omitted, platform file policy is used. |
max_file_size_bytes |
No | Per-file max size. Defaults to 50 MB and cannot exceed 50 MB. |
max_file_count |
No | Max files accepted by the run. Defaults to 5000. |
expires_at |
No | ISO timestamp. Defaults to 24 hours from creation; max is 30 days. |
Example Request
curl -X POST 'https://ai.hatz.ai/v1/admin/machine-collection-runs' \
-H 'X-API-Key: $HATZ_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"name": "Acme endpoint inventory",
"description": "Inventory collected through RMM",
"allowed_extensions": ["json"],
"allowed_mime_types": ["application/json"],
"max_file_size_bytes": 1048576,
"max_file_count": 1000
}'
Response
{
"id": "4e3f9f1a-6c6f-4b45-9db6-6b66eac1a7f2",
"name": "Acme endpoint inventory",
"description": "Inventory collected through RMM",
"target_tenant_id": null,
"sell_ai_customer_id": null,
"allowed_extensions": ["json"],
"allowed_mime_types": ["application/json"],
"max_file_size_bytes": 1048576,
"max_file_count": 1000,
"expires_at": "2026-06-09T15:30:00Z",
"disabled_at": null,
"created_at": "2026-06-08T15:30:00Z",
"updated_at": "2026-06-08T15:30:00Z",
"upload_token": "mcr_example_run_token",
"upload_url_endpoint": "https://ai.hatz.ai/v1/machine-collection-runs/mcr_example_run_token/upload-url"
}
Store id for admin retrieval. Pass only upload_token or upload_url_endpoint to endpoint machines.
Request an Upload URL
Each endpoint requests its own upload URL before uploading its result file.
Endpoint
POST /v1/machine-collection-runs/{upload_token}/upload-url
Request Body
| Field | Required | Description |
|---|---|---|
file_name |
Yes | Result filename. The extension must be allowed by the run. |
file_size |
Yes | File size in bytes. Must be within the run's limit. |
mime_type |
Yes | File MIME type. |
source_tool |
No | RMM or script source, such as ninjaone or connectwise-automate. |
script_version |
No | Version of the collection script. |
machine_hostname |
No | Endpoint hostname. |
machine_serial |
No | Endpoint serial number. |
machine_external_id |
No | RMM/device identifier. |
os_name |
No | Operating system name. |
os_version |
No | Operating system version. |
customer_name |
No | Customer name hint. |
customer_domain |
No | Customer domain hint. |
metadata |
No | Additional JSON metadata, max 8 KB. |
Example Request
curl -X POST 'https://ai.hatz.ai/v1/machine-collection-runs/mcr_example_run_token/upload-url' \
-H 'Content-Type: application/json' \
-d '{
"file_name": "acme-laptop-042.json",
"file_size": 2048,
"mime_type": "application/json",
"source_tool": "rmm",
"script_version": "1.0.0",
"machine_hostname": "ACME-LAPTOP-042",
"machine_serial": "ABC123456",
"machine_external_id": "rmm-device-123",
"os_name": "Windows",
"os_version": "11 Pro",
"customer_name": "Acme Inc",
"customer_domain": "acme.example",
"metadata": {
"site": "HQ"
}
}'
Response
{
"upload_id": "49c1df20-cb82-4d35-9cc4-39102afdf724",
"file_id": "69a6b435-6bbd-4bc2-b2aa-d4c6006c67a3",
"presigned_url": "https://uploads.hatz.ai/uploads/...",
"expires_in": 900,
"required_headers": {
"Content-Type": "application/json"
}
}
Upload the File
Use the exact headers returned in required_headers when uploading to the presigned URL.
curl -X PUT "$PRESIGNED_URL" \
-H 'Content-Type: application/json' \
--data-binary '@acme-laptop-042.json'
List Runs
List recent runs for the MSP.
Endpoint
GET /v1/admin/machine-collection-runs
Query Parameters
| Parameter | Required | Description |
|---|---|---|
limit |
No | Number of runs to return, 1-100. Defaults to 50. |
offset |
No | Number of runs to skip. Defaults to 0. |
Example Request
curl 'https://ai.hatz.ai/v1/admin/machine-collection-runs?limit=50&offset=0' \
-H 'X-API-Key: $HATZ_API_KEY'
Response
{
"runs": [
{
"id": "4e3f9f1a-6c6f-4b45-9db6-6b66eac1a7f2",
"name": "Acme endpoint inventory",
"description": "Inventory collected through RMM",
"target_tenant_id": null,
"sell_ai_customer_id": null,
"allowed_extensions": ["json"],
"allowed_mime_types": ["application/json"],
"max_file_size_bytes": 1048576,
"max_file_count": 1000,
"expires_at": "2026-06-09T15:30:00Z",
"disabled_at": null,
"created_at": "2026-06-08T15:30:00Z",
"updated_at": "2026-06-08T15:30:00Z"
}
],
"has_more": true,
"next_offset": 50
}
Get a Run
Retrieve a single run by ID.
Endpoint
GET /v1/admin/machine-collection-runs/{run_id}
Example Request
curl 'https://ai.hatz.ai/v1/admin/machine-collection-runs/4e3f9f1a-6c6f-4b45-9db6-6b66eac1a7f2' \
-H 'X-API-Key: $HATZ_API_KEY'
Update or Disable a Run
Patch run settings. To stop accepting uploads, set disabled_at to the current timestamp.
Endpoint
PATCH /v1/admin/machine-collection-runs/{run_id}
Example Request
curl -X PATCH 'https://ai.hatz.ai/v1/admin/machine-collection-runs/4e3f9f1a-6c6f-4b45-9db6-6b66eac1a7f2' \
-H 'X-API-Key: $HATZ_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"disabled_at": "2026-06-08T18:00:00Z"
}'
List Uploads
List files received for a run.
Endpoint
GET /v1/admin/machine-collection-runs/{run_id}/uploads
Query Parameters
| Parameter | Required | Description |
|---|---|---|
limit |
No | Number of uploads to return, 1-500. Defaults to 100. |
offset |
No | Number of uploads to skip. Defaults to 0. |
Example Request
curl 'https://ai.hatz.ai/v1/admin/machine-collection-runs/4e3f9f1a-6c6f-4b45-9db6-6b66eac1a7f2/uploads?limit=100&offset=0' \
-H 'X-API-Key: $HATZ_API_KEY'
Response
{
"uploads": [
{
"id": "49c1df20-cb82-4d35-9cc4-39102afdf724",
"file_uuid": "69a6b435-6bbd-4bc2-b2aa-d4c6006c67a3",
"file_status": "ready",
"processing_error": null,
"filename": "acme-laptop-042.json",
"mime_type": "application/json",
"bytes": 2048,
"machine_hostname": "ACME-LAPTOP-042",
"machine_serial": "ABC123456",
"machine_external_id": "rmm-device-123",
"customer_name": "Acme Inc",
"customer_domain": "acme.example",
"target_tenant_id": null,
"sell_ai_customer_id": null,
"created_at": "2026-06-08T15:35:00Z"
}
],
"has_more": true,
"next_offset": 100
}
When has_more is true, request the next page with offset set to next_offset.
File statuses follow the file upload pipeline: uploading, processing, ready, failed, or deleted.
Generate a Download URL
Generate a short-lived URL for a ready upload. Download URLs expire after 5 minutes.
Endpoint
POST /v1/admin/machine-collection-runs/{run_id}/uploads/{upload_id}/download-url
Example Request
curl -X POST 'https://ai.hatz.ai/v1/admin/machine-collection-runs/4e3f9f1a-6c6f-4b45-9db6-6b66eac1a7f2/uploads/49c1df20-cb82-4d35-9cc4-39102afdf724/download-url' \
-H 'X-API-Key: $HATZ_API_KEY'
Ready Response
{
"url": "https://uploads.hatz.ai/...",
"expires_in": 300,
"status": "ready",
"processing_error": null
}
Non-Ready Response
{
"url": null,
"expires_in": null,
"status": "processing",
"processing_error": null
}
Common Errors
| Status | Cause |
|---|---|
400 |
The run expiry is invalid, too far in the future, or an update body is empty. |
403 |
The admin key does not have manage_machine_collections. |
404 |
The beta is not enabled, or the run, upload, customer, tenant, or upload token was not found. |
409 |
The run reached its configured file count limit. |
410 |
The run-token upload endpoint is disabled or expired. |
413 |
The requested file is larger than the run's max file size. |
422 |
The filename extension, MIME type, or metadata is invalid. |
When the beta is not enabled for your MSP, endpoints return:
{
"detail": {
"code": "machine_collections_beta_not_enabled",
"message": "Machine collection uploads are currently in beta and are not enabled for this tenant."
}
}