dimer47 / simplemdm-php-sdk
PHP SDK for the SimpleMDM API - Manage Apple devices programmatically
Requires
- php: ^8.1
- guzzlehttp/guzzle: ^7.0
Requires (Dev)
- laravel/pint: ^1.27
- pestphp/pest: *
- phpstan/phpstan: ^1.0
This package is auto-updated.
Last update: 2026-03-15 11:08:04 UTC
README
โ ๏ธ This SDK is in beta. It has not been extensively tested in production environments. Unit and integration tests are included in the source code and can be run with your own API key and SimpleMDM environment. Use in production at your own risk.
A PHP SDK for the SimpleMDM API to programmatically manage Apple devices. Compatible with Laravel and any PHP 8.1+ project. ๐
๐ Features
- ๐ฑ Full device management โ CRUD, lock, wipe, restart, bluetooth, lost mode, etc.
- ๐ฆ 20 API resources covering the entire SimpleMDM v1 API
- ๐ Automatic rate limiting โ smart retry on 429 errors
- ๐งฉ Laravel ready โ Service provider, facade and dependency injection
- โก Async-friendly โ Guzzle HTTP under the hood
- ๐งช 200 tests (unit + integration) with VCR cassettes
๐ Installation
composer require dimer47/simplemdm-php-sdk
๐ Quick Start
use SimpleMDM\SimpleMDM; $mdm = new SimpleMDM('your-api-key'); // List all devices $devices = $mdm->devices->list(); // Get a specific device $device = $mdm->devices->get(123); // Lock a device $mdm->devices->lock(123, [ 'message' => 'This device has been locked.', 'phone_number' => '555-0123', ]);
๐ง Laravel Integration
โ๏ธ Configuration
The package auto-discovers the service provider and facade. Publish the config file:
php artisan vendor:publish --tag=simplemdm-config
Add your API key to .env:
SIMPLEMDM_API_KEY=your-api-key
๐ท๏ธ Usage with Facade
use SimpleMDM\Laravel\Facades\SimpleMDM; $devices = SimpleMDM::devices->list(); $account = SimpleMDM::account->get();
๐ Usage with Dependency Injection
use SimpleMDM\SimpleMDM; class DeviceController extends Controller { public function index(SimpleMDM $mdm) { return $mdm->devices->list(); } }
๐ Available Resources
๐ข Account
$mdm->account->get(); $mdm->account->update(['name' => 'My Company']);
๐ฑ Devices
$mdm->devices->list(); $mdm->devices->get($id); $mdm->devices->create('Device Name', $groupId); $mdm->devices->update($id, ['name' => 'New Name']); $mdm->devices->delete($id); // Actions $mdm->devices->lock($id, ['message' => '...', 'phone_number' => '...']); $mdm->devices->wipe($id); $mdm->devices->restart($id); $mdm->devices->shutdown($id); $mdm->devices->refresh($id); $mdm->devices->pushApps($id); $mdm->devices->clearPasscode($id); $mdm->devices->clearFirmwarePassword($id); $mdm->devices->clearRestrictionsPassword($id); $mdm->devices->updateOS($id); // Firmware & Recovery passwords $mdm->devices->rotateFirmwarePassword($id); $mdm->devices->clearRecoveryLockPassword($id); $mdm->devices->rotateRecoveryLockPassword($id); $mdm->devices->rotateFileVaultKey($id); // Admin password $mdm->devices->setAdminPassword($id, ['password' => '...']); $mdm->devices->rotateAdminPassword($id); // Bluetooth & Remote Desktop $mdm->devices->enableBluetooth($id); $mdm->devices->disableBluetooth($id); $mdm->devices->enableRemoteDesktop($id); $mdm->devices->disableRemoteDesktop($id); // Related data $mdm->devices->listInstalledApps($id); $mdm->devices->listProfiles($id); $mdm->devices->listUsers($id); $mdm->devices->deleteUser($deviceId, $userId); // Custom attributes $mdm->devices->listCustomAttributes($id); $mdm->devices->setCustomAttribute($id, 'attribute_name', 'value'); $mdm->devices->setCustomAttributes($id, ['attr1' => 'val1', 'attr2' => 'val2']); // bulk // Other $mdm->devices->setTimeZone($id, 'Europe/Paris'); $mdm->devices->unenroll($id);
๐ฆ Apps
$mdm->apps->list(); $mdm->apps->get($id); $mdm->apps->create('/path/to/app.ipa', 'App Name'); // Upload binary $mdm->apps->createFromAppStore('899247664', 'App Name'); // From App Store ID $mdm->apps->createFromBundleId('com.example.app', 'App Name'); // From Bundle ID $mdm->apps->update($id, ['name' => 'Updated Name']); $mdm->apps->delete($id); // Munki $mdm->apps->uploadMunkiPkgInfo($id, '/path/to/pkginfo.plist'); $mdm->apps->deleteMunkiPkgInfo($id);
๐ Assignment Groups
$mdm->assignmentGroups->list(); $mdm->assignmentGroups->get($id); $mdm->assignmentGroups->create('Group Name'); $mdm->assignmentGroups->update($id, ['name' => 'New Name']); $mdm->assignmentGroups->delete($id); // Apps $mdm->assignmentGroups->assignApp($groupId, $appId); $mdm->assignmentGroups->unassignApp($groupId, $appId); $mdm->assignmentGroups->pushApps($id); $mdm->assignmentGroups->updateApps($id); // Devices $mdm->assignmentGroups->assignDevice($groupId, $deviceId); $mdm->assignmentGroups->unassignDevice($groupId, $deviceId); // Device Groups $mdm->assignmentGroups->assignDeviceGroup($groupId, $deviceGroupId); $mdm->assignmentGroups->unassignDeviceGroup($groupId, $deviceGroupId); // Profiles $mdm->assignmentGroups->assignProfile($groupId, $profileId); $mdm->assignmentGroups->unassignProfile($groupId, $profileId); $mdm->assignmentGroups->syncProfiles($id); // Other $mdm->assignmentGroups->clone($id); $mdm->assignmentGroups->getCustomAttributes($id); $mdm->assignmentGroups->setCustomAttribute($id, 'attr_name', 'value');
๐ท๏ธ Custom Attributes
$mdm->customAttributes->list(); $mdm->customAttributes->get($id); $mdm->customAttributes->create('Attribute Name', 'default_value'); $mdm->customAttributes->update($id, ['name' => 'New Name']); $mdm->customAttributes->delete($id);
๐ Custom Configuration Profiles
$mdm->customConfigurationProfiles->list(); $mdm->customConfigurationProfiles->get($id); $mdm->customConfigurationProfiles->create('/path/to/profile.mobileconfig'); $mdm->customConfigurationProfiles->update($id, ['name' => 'New Name']); $mdm->customConfigurationProfiles->delete($id); $mdm->customConfigurationProfiles->download($id); // Per-device assignment $mdm->customConfigurationProfiles->pushToDevice($profileId, $deviceId); $mdm->customConfigurationProfiles->removeFromDevice($profileId, $deviceId); // Device group assignment $mdm->customConfigurationProfiles->assignToDeviceGroup($profileId, $deviceGroupId); $mdm->customConfigurationProfiles->unassignFromDeviceGroup($profileId, $deviceGroupId);
๐ Custom Declarations (DDM)
$mdm->customDeclarations->list(); $mdm->customDeclarations->get($id); $mdm->customDeclarations->create('com.apple.configuration.management.test', '/path/to/payload.json', 'Declaration Name'); $mdm->customDeclarations->update($id, ['name' => 'New Name']); $mdm->customDeclarations->delete($id); $mdm->customDeclarations->download($id); // Per-device $mdm->customDeclarations->pushToDevice($declarationId, $deviceId); $mdm->customDeclarations->removeFromDevice($declarationId, $deviceId);
๐ DEP Servers
$mdm->depServers->list(); $mdm->depServers->get($id); $mdm->depServers->listDevices($id); $mdm->depServers->getDevice($serverId, $depDeviceId); $mdm->depServers->sync($id);
๐ง Enrollments
$mdm->enrollments->list(); $mdm->enrollments->get($id); $mdm->enrollments->delete($id); $mdm->enrollments->sendInvitation($id, 'user@example.com');
๐ฒ Installed Apps
$mdm->installedApps->get($id); $mdm->installedApps->update($id); $mdm->installedApps->delete($id);
๐ Logs
$mdm->logs->list(['namespace' => 'device']); $mdm->logs->get($id);
๐ Lost Mode
$mdm->lostMode->enable($deviceId, [ 'message' => 'Please return this device.', 'phone_number' => '555-0123', ]); $mdm->lostMode->disable($deviceId); $mdm->lostMode->playSound($deviceId); $mdm->lostMode->updateLocation($deviceId);
โ๏ธ Managed App Configs
$mdm->managedAppConfigs->list($appId); $mdm->managedAppConfigs->create($appId, 'config_key', 'config_value', 'string'); $mdm->managedAppConfigs->push($appId); $mdm->managedAppConfigs->delete($appId, $configId); $mdm->managedAppConfigs->deleteAll($appId);
๐ก๏ธ Profiles
$mdm->profiles->list(); $mdm->profiles->get($id); $mdm->profiles->assignToDevice($profileId, $deviceId); $mdm->profiles->unassignFromDevice($profileId, $deviceId); $mdm->profiles->assignToDeviceGroup($profileId, $deviceGroupId); $mdm->profiles->unassignFromDeviceGroup($profileId, $deviceGroupId);
๐ Push Certificate
$mdm->pushCertificate->get(); $mdm->pushCertificate->update('/path/to/cert.pem', 'apple-id@example.com'); $mdm->pushCertificate->getSignedCsr();
๐ Scripts
$mdm->scripts->list(); $mdm->scripts->get($id); $mdm->scripts->create('Script Name', '/path/to/script.sh', variableSupport: true); $mdm->scripts->update($id, ['name' => 'New Name']); $mdm->scripts->delete($id);
โถ๏ธ Script Jobs
$mdm->scriptJobs->list(); $mdm->scriptJobs->get($id); $mdm->scriptJobs->create($scriptId, [ 'device_ids' => [1, 2, 3], 'assignment_group_ids' => [10], ]); $mdm->scriptJobs->cancel($id);
โ Error Handling
use SimpleMDM\Exceptions\ApiException; use SimpleMDM\Exceptions\AuthenticationException; use SimpleMDM\Exceptions\RateLimitException; try { $devices = $mdm->devices->list(); } catch (AuthenticationException $e) { // Invalid API key (401) } catch (RateLimitException $e) { // Too many requests (429) $retryAfter = $e->getRetryAfter(); // seconds until rate limit resets } catch (ApiException $e) { // Other API error $statusCode = $e->getStatusCode(); $responseBody = $e->getResponseBody(); }
๐งช Testing
# Unit tests only vendor/bin/pest tests/Unit/ # Full suite (unit + integration) vendor/bin/pest # With Docker docker compose run --rm php vendor/bin/pest
Integration tests use a VCR cassette system that records real API responses and replays them. To record new cassettes, set SIMPLEMDM_API_KEY in .env and delete the corresponding cassette in tests/Fixtures/cassettes/.
๐ API Coverage
๐งช 200 tests | โ 404 assertions | โญ๏ธ 17 skipped
Tests were run against the real SimpleMDM API with a supervised iPad (iOS 12.5).
| Resource | Method | Endpoint | Unit | Integration |
|---|---|---|---|---|
| ๐ข Account | get() |
GET /account |
โ | โ |
update() |
PATCH /account |
โ | โ | |
| ๐ฆ Apps | list() |
GET /apps |
โ | โ |
get() |
GET /apps/{id} |
โ | โ | |
create() |
POST /apps (binary) |
โ | ๐ถ | |
createFromAppStore() |
POST /apps (App Store) |
โ | โ | |
createFromBundleId() |
POST /apps (Bundle ID) |
โ | ๐ถ | |
update() |
PATCH /apps/{id} |
โ | ๐ถ | |
delete() |
DELETE /apps/{id} |
โ | โ | |
listInstalls() |
GET /apps/{id}/installs |
โ | โ | |
uploadMunkiPkgInfo() |
POST .../munki_pkginfo |
โ | ๐ถ | |
deleteMunkiPkgInfo() |
DELETE .../munki_pkginfo |
โ | ๐ถ | |
| ๐ Assignment Groups | list() |
GET /assignment_groups |
โ | โ |
get() |
GET /assignment_groups/{id} |
โ | โ | |
create() |
POST /assignment_groups |
โ | โ | |
update() |
PATCH /assignment_groups/{id} |
โ | โ | |
delete() |
DELETE /assignment_groups/{id} |
โ | โ | |
assignApp() |
POST .../apps/{appId} |
โ | โ | |
unassignApp() |
DELETE .../apps/{appId} |
โ | โ | |
assignDevice() |
POST .../devices/{deviceId} |
โ | โ | |
unassignDevice() |
DELETE .../devices/{deviceId} |
โ | โ | |
assignDeviceGroup() |
POST .../device_groups/{id} |
โ | โญ๏ธ | |
unassignDeviceGroup() |
DELETE .../device_groups/{id} |
โ | โญ๏ธ | |
pushApps() |
POST .../push_apps |
โ | โ | |
updateApps() |
POST .../update_apps |
โ | โ | |
syncProfiles() |
POST .../sync_profiles |
โ | โญ๏ธ | |
clone() |
POST .../clone |
โ | โ | |
assignProfile() |
POST .../profiles/{id} |
โ | โญ๏ธ | |
unassignProfile() |
DELETE .../profiles/{id} |
โ | โญ๏ธ | |
getCustomAttributes() |
GET .../custom_attribute_values |
โ | โ | |
setCustomAttribute() |
PUT .../custom_attribute_values/{n} |
โ | โ | |
| ๐ท๏ธ Custom Attributes | list() |
GET /custom_attributes |
โ | โ |
get() |
GET /custom_attributes/{id} |
โ | โ | |
create() |
POST /custom_attributes |
โ | โ | |
update() |
PATCH /custom_attributes/{id} |
โ | โ | |
delete() |
DELETE /custom_attributes/{id} |
โ | โ | |
| ๐ Custom Config Profiles | list() |
GET /custom_configuration_profiles |
โ | โ |
create() |
POST /custom_configuration_profiles |
โ | โ | |
update() |
PATCH .../{id} |
โ | โ | |
delete() |
DELETE .../{id} |
โ | โ | |
download() |
GET .../download |
โ | โ | |
pushToDevice() |
POST .../devices/{deviceId} |
โ | โ | |
removeFromDevice() |
DELETE .../devices/{deviceId} |
โ | โ | |
assignToDeviceGroup() |
POST .../device_groups/{id} |
โ | ๐ถ | |
unassignFromDeviceGroup() |
DELETE .../device_groups/{id} |
โ | ๐ถ | |
| ๐ Custom Declarations | list() |
GET /custom_declarations |
โ | โ |
create() |
POST /custom_declarations |
โ | โ | |
update() |
PATCH /custom_declarations/{id} |
โ | โ | |
delete() |
DELETE /custom_declarations/{id} |
โ | โ | |
download() |
GET .../download |
โ | โ | |
pushToDevice() |
POST .../devices/{deviceId} |
โ | โญ๏ธ iOS 15+ | |
removeFromDevice() |
DELETE .../devices/{deviceId} |
โ | โญ๏ธ iOS 15+ | |
| ๐ DEP Servers | list() |
GET /dep_servers |
โ | โ |
get() |
GET /dep_servers/{id} |
โ | โญ๏ธ | |
listDevices() |
GET .../dep_devices |
โ | โญ๏ธ | |
getDevice() |
GET .../dep_devices/{id} |
โ | โญ๏ธ | |
sync() |
POST .../sync |
โ | โญ๏ธ | |
| ๐ฑ Device Groups | list() |
GET /device_groups |
โ | โ |
get() |
GET /device_groups/{id} |
โ | โญ๏ธ | |
assignDevice() |
POST .../devices/{deviceId} |
โ | โญ๏ธ | |
clone() |
POST .../clone |
โ | โญ๏ธ | |
getCustomAttributes() |
GET .../custom_attribute_values |
โ | ๐ถ | |
setCustomAttribute() |
PUT .../custom_attribute_values/{n} |
โ | ๐ถ | |
| ๐ฑ Devices | list() |
GET /devices |
โ | โ |
get() |
GET /devices/{id} |
โ | โ | |
create() |
POST /devices |
โ | โ | |
update() |
PATCH /devices/{id} |
โ | โ | |
delete() |
DELETE /devices/{id} |
โ | โ | |
listInstalledApps() |
GET .../installed_apps |
โ | โ | |
listProfiles() |
GET .../profiles |
โ | โ | |
listUsers() |
GET .../users |
โ | โ | |
listCustomAttributes() |
GET .../custom_attribute_values |
โ | โ | |
setCustomAttribute() |
PUT .../custom_attribute_values/{n} |
โ | โ | |
setCustomAttributes() |
PUT .../custom_attribute_values |
โ | โ | |
refresh() |
POST .../refresh |
โ | โ | |
lock() |
POST .../lock |
โ | โ | |
clearPasscode() |
POST .../clear_passcode |
โ | โ | |
clearRestrictionsPassword() |
POST .../clear_restrictions_password |
โ | โ | |
updateOS() |
POST .../update_os |
โ | โ | |
restart() |
POST .../restart |
โ | โ | |
shutdown() |
POST .../shutdown |
โ | โ | |
pushApps() |
POST .../push_apps |
โ | โ | |
setTimeZone() |
POST .../set_time_zone |
โ | โ | |
enableBluetooth() |
POST .../bluetooth |
โ | โ | |
disableBluetooth() |
DELETE .../bluetooth |
โ | โ | |
wipe() |
POST .../wipe |
โ | โ | |
unenroll() |
POST .../unenroll |
โ | โ | |
deleteUser() |
DELETE .../users/{userId} |
โ | ๐ถ | |
enableRemoteDesktop() |
POST .../remote_desktop |
โ | ๐ฅ๏ธ | |
disableRemoteDesktop() |
DELETE .../remote_desktop |
โ | ๐ฅ๏ธ | |
rotateFirmwarePassword() |
POST .../rotate_firmware_password |
โ | ๐ฅ๏ธ | |
clearFirmwarePassword() |
POST .../clear_firmware_password |
โ | ๐ฅ๏ธ | |
clearRecoveryLockPassword() |
POST .../clear_recovery_lock_password |
โ | ๐ฅ๏ธ | |
rotateRecoveryLockPassword() |
POST .../rotate_recovery_lock_password |
โ | ๐ฅ๏ธ | |
rotateFileVaultKey() |
POST .../rotate_filevault_key |
โ | ๐ฅ๏ธ | |
setAdminPassword() |
POST .../set_admin_password |
โ | ๐ฅ๏ธ | |
rotateAdminPassword() |
POST .../rotate_admin_password |
โ | ๐ฅ๏ธ | |
| ๐ง Enrollments | list() |
GET /enrollments |
โ | โ |
get() |
GET /enrollments/{id} |
โ | โ | |
delete() |
DELETE /enrollments/{id} |
โ | โ | |
sendInvitation() |
POST .../invitations |
โ | โ | |
| ๐ฒ Installed Apps | get() |
GET /installed_apps/{id} |
โ | โญ๏ธ |
update() |
POST .../update |
โ | ๐ถ | |
delete() |
DELETE /installed_apps/{id} |
โ | ๐ถ | |
| ๐ Logs | list() |
GET /logs |
โ | โ |
get() |
GET /logs/{id} |
โ | โ | |
| ๐ Lost Mode | enable() |
POST .../lost_mode |
โ | โ |
disable() |
DELETE .../lost_mode |
โ | โ | |
playSound() |
POST .../play_sound |
โ | โ | |
updateLocation() |
POST .../update_location |
โ | โ | |
| โ๏ธ Managed App Configs | list() |
GET /apps/{id}/managed_configs |
โ | โ |
create() |
POST .../managed_configs |
โ | โ | |
push() |
POST .../managed_configs/push |
โ | โ | |
delete() |
DELETE .../managed_configs/{id} |
โ | โ | |
| ๐ก๏ธ Profiles | list() |
GET /profiles |
โ | โ |
get() |
GET /profiles/{id} |
โ | โญ๏ธ | |
assignToDevice() |
POST .../devices/{deviceId} |
โ | โญ๏ธ | |
unassignFromDevice() |
DELETE .../devices/{deviceId} |
โ | โญ๏ธ | |
assignToDeviceGroup() |
POST .../device_groups/{id} |
โ | โญ๏ธ | |
unassignFromDeviceGroup() |
DELETE .../device_groups/{id} |
โ | โญ๏ธ | |
| ๐ Push Certificate | get() |
GET /push_certificate |
โ | โ |
update() |
PUT /push_certificate |
โ | โ | |
getSignedCsr() |
GET .../scsr |
โ | โ | |
| ๐ Scripts | list() |
GET /scripts |
โ | โ |
get() |
GET /scripts/{id} |
โ | โ | |
create() |
POST /scripts |
โ | โ | |
update() |
PATCH /scripts/{id} |
โ | โ | |
delete() |
DELETE /scripts/{id} |
โ | โ | |
| โถ๏ธ Script Jobs | list() |
GET /script_jobs |
โ | โ |
get() |
GET /script_jobs/{id} |
โ | โญ๏ธ | |
create() |
POST /script_jobs |
โ | โญ๏ธ ๐ฅ๏ธ | |
cancel() |
DELETE /script_jobs/{id} |
โ | โญ๏ธ ๐ฅ๏ธ |
๐ Legend
| Icon | Meaning |
|---|---|
| โ | Tested and validated against the real SimpleMDM API |
| โญ๏ธ | Skipped โ resource not available in the test environment or missing prerequisites |
| ๐ถ | Not integration tested โ covered by unit tests only (HTTP mocks) |
| ๐ฅ๏ธ | macOS only โ requires an enrolled macOS device, unit tested only |
| โ | Intentionally not tested โ destructive/irreversible action (wipe, unenroll, push certificate) |
โ ๏ธ Limitations and Warnings
๐ด This SDK is in beta. Integration tests were run against a limited SimpleMDM environment (a single supervised iPad running iOS 12.5, no macOS device). While 100% of endpoints are covered by unit tests and the majority are validated through integration tests, some production scenarios could not be verified.
Endpoints not integration tested:
- ๐ฅ๏ธ macOS only โ Remote desktop, firmware/recovery/admin passwords, FileVault, script jobs. Require an enrolled macOS device.
- ๐ DDM (Declarative Device Management) โ Assigning custom declarations to a device requires iOS 15+. The test iPad runs iOS 12.
- โ Wipe / Unenroll โ Not tested as a precaution (destructive and irreversible).
- โ Push Certificate update โ Not tested (risk of breaking MDM communications).
- ๐ก๏ธ Profiles / Device Groups โ Skipped if no profile or group is configured in the account.
๐ก Running tests with your own environment:
# 1. Copy the environment file cp .env.example .env # 2. Set your SimpleMDM API key # SIMPLEMDM_API_KEY=your-api-key # 3. Delete cassettes to re-record with your environment rm -rf tests/Fixtures/cassettes/*.json # 4. Run tests vendor/bin/pest
๐ก Tip: VCR cassettes record real API responses. Once recorded, tests can be re-run without an API connection. Delete a cassette to force re-recording for that endpoint.
๐ OpenAPI Specification
An OpenAPI 3.0 specification file is available at openapi.yaml. It covers all SimpleMDM API v1 endpoints documented in this SDK (~130 endpoints). This file can be used with AI CLI tools, Swagger UI, or any OpenAPI-compatible tool to explore and interact with the API.
Note: This specification has been generated from the SDK source code and has not been extensively tested against the live API. Use it as a reference, not as a guaranteed contract.
๐ Requirements
- PHP 8.1+
- Guzzle HTTP 7.0+
๐ License
MIT