dimer47/simplemdm-php-sdk

PHP SDK for the SimpleMDM API - Manage Apple devices programmatically

Maintainers

Package info

github.com/dimer47/simplemdm-php-sdk

pkg:composer/dimer47/simplemdm-php-sdk

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

dev-main 2026-02-15 10:48 UTC

This package is auto-updated.

Last update: 2026-03-15 11:08:04 UTC


README

PHP Guzzle Laravel License Tests Beta

โš ๏ธ 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