This guide covers the changes between the ArborXR MDM API v2 and v3, helping you update your integrations.
| Version | Base URL |
|---|---|
| v2 | https://api.xrdm.app/api/v2 |
| v3 | https://api.xrdm.app/api/v3 |
Important: API tokens have changed from organization-scoped to user-scoped in v3.
| Aspect | v2 | v3 |
|---|---|---|
| Token scope | Organization-wide | User-specific |
| Token permissions | Based on organization | Based on user's roles and permissions within an organization |
| v2 tokens in v3 | N/A | Not supported |
Action required: You must generate new API tokens for your v3 integrations. Existing v2 tokens will not work with v3 endpoints.
To generate a new token:
The following deprecated v2 endpoints have been removed in v3:
/groups instead)| Removed Endpoint | Replacement |
|---|---|
GET /device-groups |
GET /groups |
GET /device-groups/{groupId} |
GET /groups/{groupId} |
POST /device-groups |
POST /groups |
PUT /device-groups/{groupId} |
PUT /groups/{groupId} |
DELETE /device-groups/{groupId} |
DELETE /groups/{groupId} |
GET /device-groups/{groupId}/release-channels |
GET /groups/{groupId}/release-channels |
POST /device-groups/{groupId}/release-channels |
POST /groups/{groupId}/release-channels |
DELETE /device-groups/{groupId}/release-channels |
DELETE /groups/{groupId}/release-channels/{releaseChannelId} |
| Removed Endpoint | Replacement |
|---|---|
POST /invite-user |
POST /user-invites |
| Removed Endpoint | Replacement |
|---|---|
GET /organization-roles |
GET /roles |
GET /organization-roles/{roleId} |
GET /roles/{roleId} |
GET /group-roles |
GET /roles |
GET /group-roles/{roleId} |
GET /roles/{roleId} |
The method for unsharing a release channel has changed from DELETE to POST with a dedicated unshare endpoint.
v2:
DELETE /apps/{appId}/release-channels/{releaseChannelId}/share
v3:
POST /apps/{appId}/release-channels/{releaseChannelId}/unshare
Release channel removal now uses path-based identification instead of body-based.
v2:
DELETE /groups/{groupId}/release-channels
Body: { "releaseChannelId": "..." }
DELETE /devices/{deviceId}/release-channels
Body: { "releaseChannelId": "..." }
v3:
DELETE /groups/{groupId}/release-channels/{releaseChannelId}
DELETE /devices/{deviceId}/release-channels/{releaseChannelId}
File removal now uses path-based identification instead of body-based.
v2:
DELETE /groups/{groupId}/files
Body: { "fileId": "..." }
DELETE /devices/{deviceId}/files
Body: { "fileId": "..." }
v3:
DELETE /groups/{groupId}/files/{fileId}
DELETE /devices/{deviceId}/files/{fileId}
Video removal now uses path-based identification instead of body-based.
v2:
DELETE /groups/{groupId}/videos
Body: { "videoId": "..." }
DELETE /devices/{deviceId}/videos
Body: { "videoId": "..." }
v3:
DELETE /groups/{groupId}/videos/{videoId}
DELETE /devices/{deviceId}/videos/{videoId}
The authentication mechanism for migrating devices between organizations has changed significantly.
v2:
POST /devices/{deviceId}/migrate/{organizationSlug}
Body: { "targetOrganizationToken": "43|gC1ajKELCmETw...", "groupId": "..." }
Required a targetOrganizationToken - an API token for the target organization with MDM API ability.
v3:
POST /devices/{deviceId}/migrate/{organizationSlug}
Body: { "groupId": "..." }
The targetOrganizationToken field has been removed. Instead, the authenticated user must:
DEVICE__MIGRATE permission in the source organizationENROLLMENT__ENROLL_DEVICE permission in the target organizationGROUP__ADD_REMOVE_DEVICES permission on the target group (if groupId is specified)The GET /device-models endpoint behavior has changed:
v2: Required full=true query parameter to get all supported models plus enrolled unsupported models.
v3: Returns all supported models plus enrolled unsupported models by default (no query parameter needed).
Get information about the currently authenticated user:
GET /current-user
Full CRUD operations for managing users:
GET /users # List users
POST /users # Create user
GET /users/{userId} # Get user
PUT /users/{userId} # Update user
DELETE /users/{userId} # Delete user
Roles are now unified under a single endpoint:
GET /roles # List all roles
GET /roles/{roleId} # Get role details
Delete files directly:
DELETE /files/{fileId}
Delete videos directly:
DELETE /videos/{videoId}
The following fields have been renamed across multiple resources for consistency:
| Resource | v2 Field | v3 Field |
|---|---|---|
| Device | title |
name |
| Group | title |
name |
| App | title |
name |
| Role | title |
name |
| Change | v2 | v3 |
|---|---|---|
| Name field | title |
name |
| Group reference | deviceGroup (string, deprecated) |
Removed (use group object) |
| Model reference | model (string) |
deviceModel (expanded object with id, name, manufacturer, isSupported) |
| Storage free | storageSpaceFreeGb (float, GB) |
storageSpaceFreeBytes (int64, bytes) |
| Storage total | storageSpaceTotalGb (float, GB) |
storageSpaceTotalBytes (int64, bytes) |
| Custom fields | Inline object | $ref to CustomField schema |
| Timestamps | Not always present | createdAt, updatedAt always included |
| Organization | Not included | organization object included |
| Change | v2 | v3 |
|---|---|---|
| Name field | title |
name |
| Parent reference | parentId (UUID) + parent (expanded Group, if set) |
parent only (expanded Group object, nullable) - parentId removed |
| Timestamps | Not included | createdAt, updatedAt always included |
| Change | v2 | v3 |
|---|---|---|
| Organization role | organizationRole object |
Removed |
| Group roles | groupRoles array of {group, role} |
Removed |
| Access controls | Not included | accessControls array (unified structure with role + protectable) |
The accessControls array in v3 contains objects with:
id: Access control IDuserId: User IDrole: Role objectprotectable: Object with id and type (organization, group, or device)createdAt, updatedAt: Timestamps| Change | v2 | v3 |
|---|---|---|
| Name field | title |
name |
| Type enum values | "Organization", "Group" (capitalized) |
"organization", "group" (lowercase) |
| Timestamps | Not included | createdAt, updatedAt always included |
| Change | v2 | v3 |
|---|---|---|
| Human-readable size | size (e.g., "101 MB") |
Removed (use sizeBytes) |
| Filename | name |
filename |
| Checksum | sha512 (string) |
checksum object with algorithm and value |
| Device Status | Not included | deviceStatuses array of objects with deviceId, status, statusTimestamp |
| Device IDs | deviceIds array |
Removed (use deviceStatuses) |
| Timestamp | added |
createdAt, updatedAt |
| Change | v2 | v3 |
|---|---|---|
| Human-readable size | size (e.g., "250 MB") |
Removed (use sizeBytes) |
| Content name | Not included | name |
| Filename | name |
filename |
| Checksum | Not included | checksum object with algorithm and value |
| Device Status | Not included | deviceStatuses array of objects with deviceId, status, statusTimestamp |
| Device IDs | deviceIds array |
Removed (use deviceStatuses) |
| Timestamp | added |
createdAt, updatedAt |
| Change | v2 | v3 |
|---|---|---|
| Name field | title |
name |
| Timestamps | Not included | createdAt, updatedAt always included |
| Change | v2 | v3 |
|---|---|---|
| ID format | String (no format specified) | UUID format |
| App reference | Not included | app object with full App schema |
| Version | Conditionally loaded | version always included with full AppVersion schema |
| Device Status | Not included | deviceStatuses array (see below) |
| Device IDs | deviceIds array |
Removed (use deviceStatuses) |
| Timestamps | Not included | createdAt, updatedAt always included |
The deviceStatuses array contains objects with:
deviceId: Device UUIDtargetVersion: Target version name from the release channelinstalledVersion: Currently installed version name on the devicestatus: Status enum valuestatusTimestamp: ISO8601 timestamp with millisecond precision| Change | v2 | v3 |
|---|---|---|
| Support status | Not included | isSupported boolean |
| Timestamps | Not included | createdAt, updatedAt always included |
| Change | v2 | v3 |
|---|---|---|
| Timestamps | Not included | createdAt, updatedAt always included |
| Change | v2 | v3 |
|---|---|---|
| User info | user object with id, email, firstName, lastName |
Flattened to userId and userEmail (no name fields) |
| Resource fields | resource |
Split into resourceId, resourceType, and resourceName |
| IP Address | ipAddress |
userIpAddress |
| Type field | String | Enum (string): created, read, updated, deleted, remote_action, exported |
| Timestamps | createdAt only |
createdAt, updatedAt always included |
v3 introduces a structured checksum object instead of plain hash strings:
{ "checksum": { "algorithm": "SHA-512", "value": "3a7bd3e2360a3d29eea4a5c13c6b1b9b..." } }
Files and Videos use SHA-512 (or MD5 for legacy content). AppBuilds use SHA-256.
All datetime fields in v3 now include millisecond precision in ISO8601 format:
2024-01-15T14:30:00Z2024-01-15T14:30:00.123Z| Change | v2 | v3 |
|---|---|---|
| Name field | title |
name |
| Group ID field | deviceGroupId |
groupId |
| Change | v2 | v3 |
|---|---|---|
parentId required |
Optional (omit for root-level) | Required (pass null for root-level) |
The user creation request has been significantly restructured:
v2:
{ "firstName": "John", "lastName": "Doe", "email": "john.doe@example.com", "organizationRoleId": "...", // OR groupRoleId + groupIds "groupRoleId": "...", "groupIds": ["..."] }
v3:
{ "firstName": "John", "lastName": "Doe", "email": "john.doe@example.com", "defaultRoleId": "...", // Required - works for both org and group roles "groupRoles": [ // Optional - for additional group roles { "roleId": "...", "groupId": "..." } ] }
Key changes:
organizationRoleId → defaultRoleId (required for ALL users, unified for both organization and group role types)groupRoleId + groupIds → groupRoles array with explicit role-group pairs (optional)v2:
{ "firstName": "John", "lastName": "Doe", "organizationRoleId": "...", "groupRoleId": "...", "groupIds": ["..."] }
v3:
{ "firstName": "John", "lastName": "Doe", "defaultRoleId": "...", "groupRoles": [ { "roleId": "...", "groupId": "...", "action": "attach" // or "detach" } ] }
Key changes:
organizationRoleId → defaultRoleIdgroupRoleId + groupIds → groupRoles array with explicit attach/detach actionsThe groupRoles array requires each entry to have:
roleId: UUID of the role (required)groupId: UUID of the group (required)action: Must be either "attach" or "detach" (required, no default)| Change | v2 | v3 |
|---|---|---|
| Name field | title |
name |
| Change | v2 | v3 |
|---|---|---|
targetOrganizationToken |
Required (API token for target org) | Removed (user must belong to target org) |
groupId |
Optional | Optional (requires group permission if specified) |
V3 API endpoints now consistently return standardized error responses:
400 Bad Request - Invalid request parameters401 Unauthorized - Missing or invalid authentication403 Forbidden - Insufficient permissions404 Not Found - Resource not found422 Unprocessable Entity - Validation errors500 Internal Server Error - Server-side errors/api/v2 to /api/v3/device-groups routes with /groups/invite-user with /user-invites/organization-roles and /group-roles with /rolesDELETE .../share to POST .../unsharefull=true parameter from /device-models calls (if used)targetOrganizationToken and ensure user belongs to target organization with required permissionstitle field to use name (Device, Group, App, Role)storageSpaceFreeBytes/storageSpaceTotalBytes (was GB, now bytes)deviceModel object (was model string)parent object (parentId removed, only parent remains)accessControls array (replaces organizationRole and groupRoles)type values (organization/group)checksum object (replaces sha512)createdAt/updatedAt (replaces added)name field in Video responses (content display name, separate from filename)user object flattened to userId/userEmail, resource split into resourceId/resourceType/resourceName, ipAddress renamed to userIpAddressapp object, version always included, deviceStatuses with extended fields)2024-01-15T14:30:00.123Z)createdAt/updatedAt timestamps on all resourcesname instead of titlegroupId instead of deviceGroupIdname instead of titleparentId (use null for root-level)defaultRoleId is always provided in user creation requests (now required for ALL users)defaultRoleId and groupRoles arraydefaultRoleId and groupRoles with required action field ("attach" or "detach")targetOrganizationToken (authentication now based on user membership)