A modern PHP API client for the official UniFi Network Application API, built on Saloon with a fluent interface for easy integration and powerful features.
This client provides a clean, intuitive way to interact with your UniFi Network Application, supporting all major operations including site management, device control, client monitoring, network configuration, WiFi management, and more.
It is not a direct successor to the UniFi API client which has been developed for the legacy, "unofficial" UniFi API. At this point in time, the "unofficial" API supports more endpoints than the official API. If your integration requirements can be met with the official API, we recommend using this client. For a richer set of features, we currently recommend using the legacy UniFi API client.
- Built on Saloon v3 for robust HTTP communication
- Fluent interface with method chaining for elegant code
- Full support for the official UniFi Network Application API (v10.1.84+)
- Comprehensive coverage of all API endpoints
- Strongly typed using PHP 8.1+ features
- Easy to use for beginners, flexible for advanced users
- Well-documented with inline PHPDoc for IDE auto-completion
- PSR-4 autoloading
- Sends a
User-Agentheader with every request for easy troubleshooting
- PHP 8.1 or higher
- Composer
- A UniFi OS Server or UniFi OS console with API key access to the Network Application
- Network access to your UniFi Controller
Install via Composer:
composer require art-of-wifi/unifi-network-application-api-clientYou must generate an API key from your UniFi Network Application to use this client:
- Log into your UniFi Network Application
- Navigate to Settings → Integrations or straight to Integrations from the sidebar with the latest versions of the UI
- Click Create New API Key
- Give it a descriptive name and save the key securely
- Use this key when initializing the client
- API keys are site-specific and tied to your user account
- The account generating the API key must have appropriate permissions
- API keys can be revoked at any time from the Integrations page
- For local controllers with self-signed certificates, you may need to disable SSL verification (not recommended for production)
Here's the simplest example to get you started:
<?php
require_once 'vendor/autoload.php';
use ArtOfWiFi\UnifiNetworkApplicationApi\UnifiClient;
// Initialize the client
$apiClient = new UnifiClient(
baseUrl: 'https://192.168.1.1', // Your controller URL
apiKey: 'your-api-key-here', // Your generated API key
verifySsl: false // Set to true for production with valid SSL
);
// Get all sites
$response = $apiClient->sites()->list();
$sites = $response->json();
// Display site names
foreach ($sites['data'] ?? [] as $site) {
echo "Site: {$site['name']}\n";
}That's it! You're now connected to your UniFi Network Application.
Most UniFi API operations require a site ID. You can set this once, and it will be used for all subsequent calls:
<?php
use ArtOfWiFi\UnifiNetworkApplicationApi\UnifiClient;
$apiClient = new UnifiClient('https://192.168.1.1', 'your-api-key');
// Set the site ID (get this from the sites list)
$apiClient->setSiteId('550e8400-e29b-41d4-a716-446655440000');
// Now all operations use this site automatically
$devices = $apiClient->devices()->listAdopted();
$clients = $apiClient->clients()->list();Note: The site ID is a UUID (not the short site name). You can retrieve site IDs using $apiClient->sites()->list().
<?php
// List all adopted devices
$response = $apiClient->devices()->listAdopted();
$devices = $response->json();
// Get a specific device by ID
$device = $apiClient->devices()->get('device-uuid-here');
// Get device statistics
$stats = $apiClient->devices()->getStatistics('device-uuid-here');
// Execute an action on a device (only RESTART is documented)
$apiClient->devices()->executeAction('device-uuid-here', [
'action' => 'RESTART'
]);
// Adopt a pending device by MAC address
$apiClient->devices()->adopt('00:11:22:33:44:55');
// Adopt a device, ignoring the device limit
$apiClient->devices()->adopt('00:11:22:33:44:55', ignoreDeviceLimit: true);
// Remove (unadopt) a device
$apiClient->devices()->remove('device-uuid-here');<?php
// List all connected clients
$response = $apiClient->clients()->list();
$clients = $response->json();
// Get details for a specific client
$apiClient->clients()->get('client-uuid-here');
// Authorize a guest client
// Requires a lookup for the client's MAC address using $apiClient->clients()->list() with an appropriate filter first.
// Until the Official API supports client device creation, this approach does imply you cannot pre-authorize guests using
// the API because they need to be connected to the network first.
$apiClient->clients()->executeAction('client-uuid-here', [
'action' => 'AUTHORIZE_GUEST_ACCESS',
'timeLimitMinutes' => 60 // Grant access for 60 minutes
]);<?php
// List all networks
$networks = $apiClient->networks()->list();
// Create a new UNMANAGED network (simple VLAN)
$apiClient->networks()->create([
'management' => 'UNMANAGED',
'name' => 'Guest Network',
'enabled' => true,
'vlanId' => 10
]);
// Update a network
$apiClient->networks()->update('network-uuid-here', [
'name' => 'Updated Guest Network'
]);
// Delete a network
$apiClient->networks()->delete('network-uuid-here');<?php
// List all WiFi broadcasts (SSIDs)
$wifiNetworks = $apiClient->wifiBroadcasts()->list();
// Create a new WiFi network (requires complex nested structure - see examples)
// Refer to examples/05-wifi-management.php for complete structure
// Update WiFi settings
$apiClient->wifiBroadcasts()->update('wifi-uuid', [
'name' => 'Updated WiFi Name'
]);
// Delete a WiFi network
$apiClient->wifiBroadcasts()->delete('wifi-uuid-here');<?php
// Create vouchers for guest access
$apiClient->hotspot()->createVouchers([
'count' => 10,
'timeLimitMinutes' => 480,
'authorizedGuestLimit' => 1 // How many guests can use same voucher
]);
// List all vouchers
$vouchers = $apiClient->hotspot()->listVouchers();
// Delete a voucher
$apiClient->hotspot()->deleteVoucher('voucher-uuid-here');Access a variety of resources for the UniFi Network Application configuration:
<?php
// List available WAN interfaces
$wans = $apiClient->supportingResources()->listWanInterfaces();
// List DPI (Deep Packet Inspection) categories
$dpiCategories = $apiClient->supportingResources()->listDpiCategories();
// List DPI applications
$dpiApps = $apiClient->supportingResources()->listDpiApplications();
// List countries (for regulatory compliance)
$countries = $apiClient->supportingResources()->listCountries();
// List RADIUS profiles
$radiusProfiles = $apiClient->supportingResources()->listRadiusProfiles();
// List device tags
$deviceTags = $apiClient->supportingResources()->listDeviceTags();
// List site-to-site VPN tunnels
$vpnTunnels = $apiClient->supportingResources()->listSiteToSiteVpnTunnels();
// List VPN servers
$vpnServers = $apiClient->supportingResources()->listVpnServers();<?php
// List firewall zones
$zones = $apiClient->firewall()->listZones();
// Create a firewall zone
$apiClient->firewall()->createZone([
'name' => 'DMZ',
'networkIds' => [] // Array of network UUIDs
]);
// List firewall policies
$policies = $apiClient->firewall()->listPolicies();
// Create a firewall policy
$apiClient->firewall()->createPolicy([
'name' => 'Block IoT to LAN',
'enabled' => true,
'action' => 'BLOCK',
'source' => ['zoneId' => 'source-zone-uuid'],
'destination' => ['zoneId' => 'destination-zone-uuid'],
]);
// Partially update a firewall policy (PATCH - only send changed fields)
$apiClient->firewall()->patchPolicy('policy-uuid', [
'enabled' => false
]);
// Get/update firewall policy ordering between two zones
$ordering = $apiClient->firewall()->getPolicyOrdering(
sourceFirewallZoneId: 'source-zone-uuid',
destinationFirewallZoneId: 'destination-zone-uuid'
);
$apiClient->firewall()->updatePolicyOrdering(
sourceFirewallZoneId: 'source-zone-uuid',
destinationFirewallZoneId: 'destination-zone-uuid',
data: ['orderedFirewallPolicyIds' => ['policy-1-uuid', 'policy-2-uuid']]
);
// List ACL rules
$rules = $apiClient->aclRules()->list();
// Create an ACL rule (requires complex structure - see API docs)
$apiClient->aclRules()->create([
'type' => 'IPV4', // or 'MAC'
'name' => 'Block Social Media',
'enabled' => true,
'action' => 'BLOCK', // or 'ALLOW'
'index' => 1000,
// ... additional filters required
]);
// Get/update ACL rule ordering
$ordering = $apiClient->aclRules()->getOrdering();
$apiClient->aclRules()->updateOrdering([
'orderedAclRuleIds' => ['rule-1-uuid', 'rule-2-uuid', 'rule-3-uuid']
]);<?php
// List all DNS policies
$policies = $apiClient->dnsPolicies()->list();
// Create a DNS A record
$apiClient->dnsPolicies()->create([
'type' => 'A',
'enabled' => true,
'domain' => 'myapp.local',
'ipv4Address' => '192.168.1.100',
'ttlSeconds' => 3600,
]);
// Create a DNS CNAME record
$apiClient->dnsPolicies()->create([
'type' => 'CNAME',
'enabled' => true,
'domain' => 'alias.local',
'targetDomain' => 'myapp.local',
'ttlSeconds' => 3600,
]);
// Update a DNS policy
$apiClient->dnsPolicies()->update('dns-policy-uuid', [
'enabled' => false,
'ipv4Address' => '192.168.1.200',
]);
// Delete a DNS policy
$apiClient->dnsPolicies()->delete('dns-policy-uuid');All list endpoints use offset-based pagination:
<?php
// Example: List adopted devices with pagination
$response = $apiClient->devices()->listAdopted(
offset: 100, // Skip first 100 results
limit: 50 // Get 50 results
);
$data = $response->json();
// Response structure for list endpoints:
// {
// "offset": 100,
// "limit": 50,
// "count": 50, // Number of items in current response
// "totalCount": 1000, // Total items available
// "data": [...] // Array of results
// }The offset parameter specifies how many results to skip, while limit specifies the maximum number of results to return. All paginated endpoints follow this pattern.
The UniFi API supports advanced filtering on many endpoints. You can use either raw filter strings or the type-safe filter builders.
For type-safe, IDE-friendly filtering with autocomplete, use the fluent filter builders:
<?php
use ArtOfWiFi\UnifiNetworkApplicationApi\Filters\Devices\DeviceFilter;
use ArtOfWiFi\UnifiNetworkApplicationApi\Filters\Clients\ClientFilter;
use ArtOfWiFi\UnifiNetworkApplicationApi\Enums\ClientType;
use ArtOfWiFi\UnifiNetworkApplicationApi\Enums\ClientAccessType;
// Simple filtering - find access points
$devices = $apiClient->devices()->listAdopted(
filter: DeviceFilter::name()->like('AP-*')
);
// Find devices by model
$devices = $apiClient->devices()->listAdopted(
filter: DeviceFilter::model()->in(['U6-LR', 'U6-PRO', 'U6-ENTERPRISE'])
);
// Complex filtering with AND - wireless guest clients
$clients = $apiClient->clients()->list(
filter: ClientFilter::and(
ClientFilter::type()->equals(ClientType::WIRELESS),
ClientFilter::accessType()->equals(ClientAccessType::GUEST)
)
);
// Complex filtering with OR - APs or Switches
$devices = $apiClient->devices()->listAdopted(
filter: DeviceFilter::or(
DeviceFilter::model()->like('U6*'),
DeviceFilter::model()->like('USW*')
)
);
// Multiple conditions - devices needing updates
$devices = $apiClient->devices()->listAdopted(
filter: DeviceFilter::and(
DeviceFilter::firmwareUpdatable()->equals(true),
DeviceFilter::supported()->equals(true)
)
);
// Null checks - clients with IP addresses
$clients = $apiClient->clients()->list(
filter: ClientFilter::ipAddress()->isNotNull()
);
// Set operations - devices with WiFi 6
$devices = $apiClient->devices()->listAdopted(
filter: DeviceFilter::features()->contains('wifi6')
);
// Preset filters for common use cases
$aps = $apiClient->devices()->listAdopted(
filter: DeviceFilter::accessPoints()
);
$wirelessGuests = $apiClient->clients()->list(
filter: ClientFilter::wirelessGuests()
);Countries Filter (Easy to Test!):
The Countries endpoint is perfect for testing filters as it works without needing a site ID and has lots of data:
use ArtOfWiFi\UnifiNetworkApplicationApi\Filters\SupportingResources\CountriesFilter;
// Find United States
$countries = $apiClient->supportingResources()->listCountries(
filter: CountriesFilter::unitedStates()
);
// Find countries with "Kingdom" in the name
$countries = $apiClient->supportingResources()->listCountries(
filter: CountriesFilter::name()->like('*Kingdom*')
);
// Find multiple specific countries
$countries = $apiClient->supportingResources()->listCountries(
filter: CountriesFilter::code()->in(['US', 'GB', 'CA', 'AU'])
);
// Find North American countries
$countries = $apiClient->supportingResources()->listCountries(
filter: CountriesFilter::northAmerica()
);Available Filter Classes:
DeviceFilter- For device filteringClientFilter- For client filteringNetworkFilter- For network filteringSitesFilter- For site filteringFirewallPolicyFilter- For firewall policy filteringDnsPolicyFilter- For DNS policy filtering (with presets for record types)CountriesFilter- For country filtering (easy to test!)DpiCategoriesFilter- For DPI category filteringDpiApplicationsFilter- For DPI application filteringSiteToSiteVpnTunnelsFilter- For VPN tunnel filteringVpnServersFilter- For VPN server filteringRadiusProfilesFilter- For RADIUS profile filteringDeviceTagsFilter- For device tag filtering
Available Enums:
ClientType- WIRED, WIRELESS, VPN, TELEPORTClientAccessType- DEFAULT, GUEST
Benefits of Filter Builders:
- Full IDE autocomplete support
- Type safety - catches errors at development time
- Better readability for complex filters
- Property-specific methods for easy discovery
- Preset filters for common use cases
- Automatic value escaping and formatting
If preferred, you can also use raw filter strings from the Network Application API documentation in your controller:
<?php
// Filter devices by name
$response = $apiClient->devices()->listAdopted(
filter: "name.like('AP-*')"
);
// Filter clients by type and access (wireless guests only)
$response = $apiClient->clients()->list(
filter: 'and(type.eq("WIRELESS"), access.type.eq("GUEST"))'
);According to the official API specification, the following properties are filterable for clients:
id(UUID) -eq,ne,in,notIntype(STRING) -eq,ne,in,notIn(Valid values:WIRED,WIRELESS,VPN,TELEPORT)macAddress(STRING) -isNull,isNotNull,eq,ne,in,notInipAddress(STRING) -isNull,isNotNull,eq,ne,in,notInconnectedAt(TIMESTAMP) -isNull,isNotNull,eq,ne,gt,ge,lt,leaccess.type(STRING) -eq,ne,in,notIn(Valid values:DEFAULT,GUEST)access.authorized(BOOLEAN) -isNull,isNotNull,eq,ne
According to the official API specification, the following properties are filterable for adopted devices:
id(UUID) -eq,ne,in,notInmacAddress(STRING) -eq,ne,in,notInipAddress(STRING) -eq,ne,in,notInname(STRING) -eq,ne,in,notIn,likemodel(STRING) -eq,ne,in,notInstate(STRING) -eq,ne,in,notInsupported(BOOLEAN) -eq,nefirmwareVersion(STRING) -isNull,isNotNull,eq,ne,gt,ge,lt,le,like,in,notInfirmwareUpdatable(BOOLEAN) -eq,nefeatures(SET(STRING)) -isEmpty,contains,containsAny,containsAll,containsExactlyinterfaces(SET(STRING)) -isEmpty,contains,containsAny,containsAll,containsExactly
According to the official API specification, the following properties are filterable for networks:
management(STRING) -eq,ne,in,notInid(UUID) -eq,ne,in,notInname(STRING) -eq,ne,in,notIn,likeenabled(BOOLEAN) -eq,nevlanId(INTEGER) -eq,ne,gt,ge,lt,le,in,notIndeviceId(UUID) -eq,ne,in,notIn,isNull,isNotNullmetadata.origin(STRING) -eq,ne,in,notIn
According to the official API specification, the following properties are filterable for firewall policies:
id(UUID) -eq,ne,in,notInname(STRING) -eq,ne,in,notIn,likesource.zoneId(UUID) -eq,ne,in,notIndestination.zoneId(UUID) -eq,ne,in,notInmetadata.origin(STRING) -eq,ne,in,notIn
According to the official API specification, the following properties are filterable for DNS policies:
type(STRING) -eq,ne,in,notIn(Valid values:A,AAAA,CNAME,MX,TXT,SRV,FORWARD_DOMAIN)id(UUID) -eq,ne,in,notInenabled(BOOLEAN) -eq,nedomain(STRING) -eq,ne,in,notIn,likeipv4Address(STRING) -eq,ne,in,notInipv6Address(STRING) -eq,ne,in,notIntargetDomain(STRING) -eq,ne,in,notIn,likemailServerDomain(STRING) -eq,ne,in,notIn,liketext(STRING) -eq,ne,in,notIn,likeserverDomain(STRING) -eq,ne,in,notIn,likeipAddress(STRING) -eq,ne,in,notInttlSeconds(INTEGER) -eq,ne,gt,ge,lt,lepriority(INTEGER) -eq,ne,gt,ge,lt,leservice(STRING) -eq,ne,in,notInprotocol(STRING) -eq,ne,in,notInport(INTEGER) -eq,ne,gt,ge,lt,leweight(INTEGER) -eq,ne,gt,ge,lt,le
For full filtering syntax documentation, see the Network Application API documentation in your controller.
All methods return a Saloon Response object with helpful methods:
<?php
$response = $apiClient->sites()->list();
// Get response as array
$data = $response->json();
// Get response as object
$data = $response->object();
// Check HTTP status
$status = $response->status();
$isSuccessful = $response->successful();
// Access headers
$contentType = $response->header('Content-Type');
// Get raw body
$body = $response->body();IMPORTANT: The UniFi API returns different response structures depending on the endpoint type:
List Endpoints (e.g., list(), listAdopted(), listVouchers()) return paginated data:
<?php
// List endpoints return data in a 'data' array with pagination metadata
$response = $apiClient->devices()->listAdopted();
$result = $response->json();
// Access the array of items
$devices = $result['data']; // Array of devices
// Pagination metadata is also available
$total = $result['total'] ?? null;
$offset = $result['offset'] ?? null;
$limit = $result['limit'] ?? null;
// Iterate through items
foreach ($result['data'] as $device) {
echo $device['name'];
}Single Item Endpoints (e.g., get($uuid), getVoucher($uuid)) return the object directly:
<?php
// Single item endpoints return the object WITHOUT a 'data' wrapper
$response = $apiClient->devices()->get($deviceId);
$device = $response->json();
// Access properties directly (NO 'data' key!)
echo $device['name']; // ✓ Correct
echo $device['ipAddress']; // ✓ Correct
// NOT like this:
// echo $device['data']['name']; // ✗ Wrong! Will cause errorsKey Differences:
- List endpoints: Use
$result['data']to access items + pagination metadata - Single item endpoints: Access properties directly, no
datawrapper, no pagination metadata
Quick Reference:
| Method Type | Response Structure | Example Access |
|---|---|---|
list(), listAdopted(), etc. |
{ "data": [...], "total": X, ... } |
$response->json()['data'][0] |
get($id), getVoucher($id), etc. |
{ "id": "...", "name": "...", ... } |
$response->json()['name'] |
Handle API errors gracefully using try-catch blocks:
<?php
use Saloon\Exceptions\Request\RequestException;
use Saloon\Exceptions\Request\ClientException;
use Saloon\Exceptions\Request\ServerException;
try {
$response = $apiClient->devices()->get('invalid-uuid-here');
$device = $response->json();
} catch (ClientException $e) {
// 4xx errors (client errors like 404 Not Found)
echo "Client error: " . $e->getMessage();
echo "Status code: " . $e->getResponse()->status();
} catch (ServerException $e) {
// 5xx errors (server errors)
echo "Server error: " . $e->getMessage();
} catch (RequestException $e) {
// General request errors
echo "Request failed: " . $e->getMessage();
}For production environments with valid SSL certificates, enable SSL verification:
<?php
$apiClient = new UnifiClient(
baseUrl: 'https://unifi.example.com',
apiKey: 'your-api-key',
verifySsl: true // Verify SSL certificates
);For local development with self-signed certificates, you can disable verification:
<?php
$apiClient = new UnifiClient(
baseUrl: 'https://192.168.1.1',
apiKey: 'your-api-key',
verifySsl: false // Skip SSL verification (not recommended for production)
);You can retrieve the client library version at runtime, which is useful for troubleshooting and logging:
<?php
echo $apiClient->getVersion(); // e.g., "1.0.0"The version is also sent with every API request as a User-Agent header (unifi-api-client-php/1.0.0), which can help
when debugging API issues in controller logs. The version constant is also available directly
via UnifiConnector::VERSION.
The client provides access to the following resources:
| Resource | Description |
|---|---|
applicationInfo() |
General application information and metadata |
sites() |
Site management and listing |
devices() |
Device management, monitoring, actions, adoption, and removal |
clients() |
Connected client management and guest authorization |
networks() |
Network configuration (VLANs, DHCP, etc.) |
wifiBroadcasts() |
WiFi network (SSID) management |
hotspot() |
Guest voucher management |
firewall() |
Firewall zone and policy management, including policy ordering |
aclRules() |
Access Control List (ACL) rule management, including rule ordering |
dnsPolicies() |
DNS policy management (A, AAAA, CNAME, MX, TXT, SRV, forward domains) |
trafficMatchingLists() |
Port and IP address lists for firewall policies |
supportingResources() |
Reference data (WAN interfaces, DPI categories, countries, RADIUS profiles, device tags) |
See the examples/ directory for complete working examples:
- Basic Usage - Getting started with the client
- Device Management - Working with UniFi devices (including adopt/remove)
- Client Operations - Managing connected clients
- Network Configuration - Creating and managing networks
- WiFi Management - WiFi broadcast configuration
- Error Handling - Proper exception handling
- Firewall Policies & DNS Policies - Firewall policies, ACL ordering, and DNS policies
If you're migrating from the legacy UniFi API client, this new Saloon-based client offers:
- Modern PHP 8.1+ syntax with typed properties
- Fluent interface for more readable code
- Better error handling with exceptions
- Comprehensive IDE support through PHPDoc
- Based on the official API (not the legacy private API)
The main differences:
- Authentication: Uses API keys instead of username/password login
- Return values: Returns Saloon Response objects instead of arrays directly
- Method names: More descriptive and consistent naming
- Site handling: Explicit site ID setting with
setSiteId()
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
If you encounter any issues or have questions:
- Check the examples directory for working code samples
- Review the official UniFi API documentation within your controller
- Open an issue on GitHub
This library is developed and maintained by Art of WiFi and is developed for the official UniFi Network Application API.
Built with Saloon by Sammyjo20.
This project is licensed under the MIT License - see the LICENSE file for details.