litenet-website/public/api-spec/openapi.json
rocord01 e7a7839f26
All checks were successful
Deploy to Server / deploy (push) Successful in 1m55s
LiteNet Developer API Release
2026-06-19 11:07:06 -04:00

1456 lines
41 KiB
JSON

{
"openapi": "3.0.3",
"info": {
"title": "LiteNet API",
"description": "LiteNet is a free community PBX based on FreePBX. This API allows you to\nmanage your extension, access voicemails, view conferences, and interact\nwith the LiteNet PBX programmatically.\n\n**Authentication:** Most endpoints require a Bearer token obtained via\nDiscord OAuth. Pass it in the `Authorization` header:\n`Authorization: Bearer *** version: \"1.0.0\"\n",
"contact": {
"name": "LiteNet",
"url": "https://litenet.tel"
}
},
"servers": [
{
"url": "https://api.litenet.tel",
"description": "Production API"
},
{
"url": "http://localhost:3001",
"description": "Local development"
}
],
"paths": {
"/extensions/me": {
"get": {
"summary": "Get current extension",
"description": "Returns the authenticated user's extension details, including their Discord profile and PBX device configuration.",
"tags": [
"Extensions"
],
"security": [
{
"BearerAuth": []
}
],
"responses": {
"200": {
"description": "Extension details",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ExtensionDetails"
}
}
}
},
"401": {
"description": "Unauthorized"
}
}
}
},
"/extensions/me/devicestatus": {
"get": {
"summary": "Get device registration status",
"description": "Returns whether the user's SIP device is currently registered with the PBX. Polled every 5 seconds on the dashboard.",
"tags": [
"Extensions"
],
"security": [
{
"BearerAuth": []
}
],
"responses": {
"200": {
"description": "Device status",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"extension": {
"type": "string",
"description": "The extension number",
"example": "1010"
},
"deviceState": {
"type": "string",
"description": "Human-readable state (e.g. \"Registered\", \"In use\")",
"example": "In use"
},
"activeChannels": {
"type": "string",
"nullable": true,
"description": "Active channel info or null if none",
"example": null
}
}
}
}
}
}
}
}
},
"/extensions/me/calls": {
"get": {
"summary": "List active calls",
"description": "Returns all currently active calls for the authenticated user's extension. Polled every 5 seconds on the dashboard.",
"tags": [
"Calls"
],
"security": [
{
"BearerAuth": []
}
],
"responses": {
"200": {
"description": "List of active calls",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ActiveCall"
}
}
}
}
}
}
},
"delete": {
"summary": "Hangup all calls",
"description": "Hangs up every active call for the user's extension.",
"tags": [
"Calls"
],
"security": [
{
"BearerAuth": []
}
],
"responses": {
"200": {
"description": "All calls hung up",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"message": {
"type": "string",
"description": "Status message",
"example": "Successfully requested termination for 1 call(s) for extension 1010."
},
"terminatedChannels": {
"type": "array",
"description": "List of terminated channel IDs",
"items": {
"type": "string"
},
"example": [
"PJSIP/1010-00000002"
]
}
}
}
}
}
},
"401": {
"description": "Unauthorized"
}
}
}
},
"/extensions/me/callme": {
"post": {
"summary": "Call me",
"description": "Triggers the PBX to call the user's extension. Supports music on hold and echo test modes.",
"tags": [
"Extensions"
],
"security": [
{
"BearerAuth": []
}
],
"parameters": [
{
"in": "query",
"name": "mode",
"required": true,
"schema": {
"type": "string",
"enum": [
"hold",
"echo"
]
},
"description": "`hold` \u2014 play music on hold\n`echo` \u2014 echo test (play back what you say)\n"
},
{
"in": "query",
"name": "callerId",
"required": false,
"schema": {
"type": "string"
},
"description": "Custom caller ID to display (defaults to the extension number)"
},
{
"in": "query",
"name": "autoAnswerMode",
"required": false,
"schema": {
"type": "string"
},
"description": "Auto-answer mode for the call"
}
],
"responses": {
"200": {
"description": "Call initiated",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"message": {
"type": "string",
"description": "Status message",
"example": "Successfully initiated hold call to extension 1010."
}
}
}
}
}
}
}
}
},
"/extensions/me/resetsecret": {
"post": {
"summary": "Reset SIP secret",
"description": "Resets the SIP password for the extension. Returns the new secret once \u2014 it cannot be retrieved again.",
"tags": [
"Extensions"
],
"security": [
{
"BearerAuth": []
}
],
"responses": {
"200": {
"description": "Secret reset",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"newSecret": {
"type": "string",
"description": "The new SIP secret (displayed once)",
"example": "aB3xK9mP2qR7"
}
}
}
}
}
}
}
}
},
"/extensions/me/dnd": {
"get": {
"summary": "Get Do Not Disturb status",
"description": "Returns whether Do Not Disturb is currently enabled.",
"tags": [
"Extensions"
],
"security": [
{
"BearerAuth": []
}
],
"responses": {
"200": {
"description": "DND status",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"extension": {
"type": "string",
"description": "The extension number",
"example": "1010"
},
"dndStatus": {
"type": "boolean",
"description": "Whether DND is enabled",
"example": false
}
}
}
}
}
}
}
},
"post": {
"summary": "Toggle Do Not Disturb",
"description": "Enables or disables Do Not Disturb for the extension.",
"tags": [
"Extensions"
],
"security": [
{
"BearerAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"dndStatus"
],
"properties": {
"dndStatus": {
"type": "boolean",
"description": "true to enable, false to disable"
}
}
}
}
}
},
"responses": {
"200": {
"description": "DND updated",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"message": {
"type": "string",
"description": "Status message",
"example": "DND status for extension 1010 set to true."
},
"extension": {
"type": "string",
"description": "The extension number",
"example": "1010"
},
"dndStatus": {
"type": "boolean",
"description": "Updated DND state",
"example": true
}
}
}
}
}
}
}
}
},
"/extensions/me/endpoint": {
"get": {
"summary": "List registered endpoints",
"description": "Returns all SIP endpoints/devices registered to the user's extension. Polled every 10 seconds on the dashboard.",
"tags": [
"Extensions"
],
"security": [
{
"BearerAuth": []
}
],
"responses": {
"200": {
"description": "List of registered devices",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/RegisteredDevice"
}
}
}
}
}
}
}
},
"/extensions/me/voicemails": {
"get": {
"summary": "List voicemails",
"description": "Returns voicemail messages for the authenticated user.",
"tags": [
"Voicemail"
],
"security": [
{
"BearerAuth": []
}
],
"responses": {
"200": {
"description": "List of voicemails",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Voicemail"
}
}
}
}
}
}
}
},
"/extensions/me/voicemails/{messageId}/download": {
"get": {
"summary": "Download voicemail audio",
"description": "Downloads the WAV audio file for a specific voicemail.",
"tags": [
"Voicemail"
],
"security": [
{
"BearerAuth": []
}
],
"parameters": [
{
"in": "path",
"name": "messageId",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "WAV audio file",
"content": {
"audio/wav": {
"schema": {
"type": "string",
"format": "binary"
}
}
}
}
}
}
},
"/extensions/me/voicemails/{messageId}/move": {
"patch": {
"summary": "Move voicemail to folder",
"description": "Moves a voicemail into a different folder for organization.",
"tags": [
"Voicemail"
],
"security": [
{
"BearerAuth": []
}
],
"parameters": [
{
"in": "path",
"name": "messageId",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"targetFolder"
],
"properties": {
"targetFolder": {
"type": "string",
"enum": [
"INBOX",
"Family",
"Friends",
"Old",
"Work",
"Urgent"
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Voicemail moved",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"message": {
"type": "string",
"description": "Status message",
"example": "Voicemail moved from Old to Friends."
},
"messageId": {
"type": "string",
"description": "The voicemail message ID",
"example": "849c9236"
},
"sourceFolder": {
"type": "string",
"description": "Original folder",
"example": "Old"
},
"targetFolder": {
"type": "string",
"description": "Destination folder",
"example": "Friends"
},
"newMessageNumber": {
"type": "string",
"description": "New message number after move",
"example": "msg0001"
}
}
}
}
}
}
}
}
},
"/extensions/me/voicemails/{messageId}": {
"delete": {
"summary": "Delete voicemail",
"description": "Permanently deletes a voicemail message.",
"tags": [
"Voicemail"
],
"security": [
{
"BearerAuth": []
}
],
"parameters": [
{
"in": "path",
"name": "messageId",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Voicemail deleted",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"message": {
"type": "string",
"description": "Status message",
"example": "Voicemail msg0033 deleted permanently from Old."
},
"messageId": {
"type": "string",
"description": "The voicemail message ID",
"example": "37aca380"
},
"folder": {
"type": "string",
"description": "The folder the voicemail was in",
"example": "Old"
},
"originalMessageId": {
"type": "string",
"description": "The original message number",
"example": "msg0033"
}
}
}
}
}
}
}
}
},
"/conferences": {
"get": {
"summary": "List conference rooms",
"description": "Returns all currently active conference rooms and their participant counts.",
"tags": [
"Conferences"
],
"responses": {
"200": {
"description": "List of conference rooms",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ConferenceRoom"
}
}
}
}
}
}
}
},
"/conferences/{conferenceId}": {
"get": {
"summary": "Get conference participants",
"description": "Returns the list of participants currently in a specific conference room.",
"tags": [
"Conferences"
],
"parameters": [
{
"in": "path",
"name": "conferenceId",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "List of participants",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Participant"
}
}
}
}
}
}
}
},
"/conferences/{conferenceId}/connectme": {
"post": {
"summary": "Connect to conference",
"description": "Dials the authenticated user's extension and bridges them into the specified conference room.",
"tags": [
"Conferences"
],
"parameters": [
{
"in": "path",
"name": "conferenceId",
"required": true,
"schema": {
"type": "string"
}
}
],
"security": [
{
"BearerAuth": []
}
],
"responses": {
"200": {
"description": "Connected to conference"
}
}
}
},
"/system/records": {
"get": {
"summary": "Get call statistics",
"description": "Returns aggregate call statistics for the LiteNet PBX. Public endpoint \u2014 no authentication required.",
"tags": [
"System"
],
"responses": {
"200": {
"description": "Call statistics",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CallRecords"
}
}
}
}
}
}
},
"/system/survey": {
"get": {
"summary": "Get hardware survey data",
"description": "Returns aggregated hardware survey data showing device types, brands, and devices needing categorization. Public endpoint \u2014 no authentication required.",
"tags": [
"System"
],
"responses": {
"200": {
"description": "Survey data",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SurveyData"
}
}
}
}
}
}
}
},
"components": {
"securitySchemes": {
"BearerAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "UUID",
"description": "Obtain a token via Discord OAuth at `/auth/discord`.\nPass it as `Authorization: Bearer *** schemas:\n"
},
"ExtensionDetails": {
"type": "object",
"properties": {
"extensionId": {
"type": "string",
"description": "The 4-digit extension number",
"example": "1010"
},
"user": {
"type": "object",
"description": "Discord user profile",
"properties": {
"id": {
"type": "string",
"description": "Discord user ID",
"example": "123456789012345678"
},
"name": {
"type": "string",
"description": "Discord display name",
"example": "rocord"
},
"avatar": {
"type": "string",
"description": "Discord avatar hash",
"example": "a1b2c3d4e5f6"
},
"voicemail": {
"type": "string",
"description": "Voicemail context",
"example": "default"
},
"extPassword": {
"type": "string",
"description": "SIP secret (masked in dashboard)"
}
}
},
"coreDevice": {
"type": "object",
"description": "PBX device configuration",
"properties": {
"deviceid": {
"type": "string",
"example": "1010"
},
"tech": {
"type": "string",
"example": "SIP"
},
"dial": {
"type": "string",
"example": "SIP/1010"
}
}
}
}
},
"ActiveCall": {
"type": "object",
"properties": {
"uniqueId": {
"type": "string",
"description": "Unique call identifier"
},
"caller": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"number": {
"type": "string"
}
}
},
"connectedLine": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"number": {
"type": "string"
}
}
},
"state": {
"type": "string",
"description": "Call state (e.g. \"Up\", \"Ring\")"
},
"duration": {
"type": "integer",
"description": "Call duration in seconds"
}
}
},
"RegisteredDevice": {
"type": "object",
"properties": {
"uri": {
"type": "string",
"description": "SIP contact URI",
"example": "sip:1010@192.168.1.100:5060"
},
"useragent": {
"type": "string",
"description": "User-Agent header from the device",
"example": "Linphone/4.4.0"
},
"ip": {
"type": "string",
"description": "Client IP address",
"example": "192.168.1.100"
},
"port": {
"type": "string",
"description": "Client port",
"example": "5060"
},
"pingMs": {
"type": "string",
"description": "Round-trip latency in milliseconds",
"example": "12.34"
}
}
},
"Voicemail": {
"type": "object",
"properties": {
"messageId": {
"type": "string",
"description": "Unique voicemail identifier"
},
"callerIdNum": {
"type": "string",
"description": "Caller's phone number"
},
"callerIdName": {
"type": "string",
"description": "Caller's name (if available)"
},
"date": {
"type": "string",
"format": "date-time",
"description": "Timestamp of the voicemail"
},
"duration": {
"type": "number",
"description": "Duration in seconds"
},
"fileSize": {
"type": "integer",
"description": "Audio file size in bytes"
},
"hasAudio": {
"type": "boolean",
"description": "Whether audio is available for playback"
},
"folder": {
"type": "string",
"enum": [
"INBOX",
"Family",
"Friends",
"Old",
"Work",
"Urgent"
],
"description": "Current folder"
},
"originalMessageId": {
"type": "string",
"description": "Original message ID before any moves"
}
}
},
"ConferenceRoom": {
"type": "object",
"properties": {
"conferenceId": {
"type": "string",
"description": "Conference room identifier",
"example": 2000
},
"parties": {
"type": "integer",
"description": "Number of participants",
"example": 3
},
"locked": {
"type": "boolean",
"description": "Whether the room is locked"
}
}
},
"Participant": {
"type": "object",
"properties": {
"channel": {
"type": "string",
"description": "Asterisk channel identifier"
},
"callerIdNum": {
"type": "string",
"description": "Participant's extension number"
},
"callerIdName": {
"type": "string",
"description": "Participant's display name"
},
"muted": {
"type": "boolean",
"description": "Whether the participant is muted"
},
"admin": {
"type": "boolean",
"description": "Whether the participant is a conference admin"
},
"talking": {
"type": "boolean",
"description": "Whether the participant is currently speaking"
}
}
},
"CallRecords": {
"type": "object",
"properties": {
"records": {
"type": "object",
"properties": {
"total_calls_ever_placed": {
"type": "integer",
"description": "Total calls since launch",
"example": 5324
},
"record_calls": {
"type": "object",
"description": "Single-day call record",
"properties": {
"count": {
"type": "integer"
},
"date": {
"type": "string",
"format": "date"
}
}
},
"last_updated": {
"type": "string",
"format": "date-time"
}
},
"additionalProperties": {
"type": "integer",
"description": "Monthly call totals (e.g. monthly_total_2025-01)"
}
}
}
},
"SurveyData": {
"type": "object",
"properties": {
"phoneTypes": {
"type": "object",
"description": "Device types and their counts",
"additionalProperties": {
"type": "integer"
},
"example": {
"softphone": 45,
"ip_phone": 23,
"ata": 8
}
},
"brands": {
"type": "object",
"description": "Device brands and their counts",
"additionalProperties": {
"type": "integer"
},
"example": {
"Polycom": 12,
"Cisco": 8,
"Yealink": 7
}
},
"needsCategorization": {
"type": "array",
"description": "Devices that couldn't be automatically categorized",
"items": {
"type": "object",
"properties": {
"ua": {
"type": "string",
"description": "User-Agent string"
}
}
}
},
"lastUpdated": {
"type": "string",
"format": "date-time",
"description": "When the survey data was last refreshed"
}
}
}
},
"schemas": {
"ExtensionDetails": {
"type": "object",
"properties": {
"status": {
"type": "boolean",
"description": "Whether the request was successful",
"example": true
},
"message": {
"type": "string",
"description": "Status message",
"example": "Extension found successfully"
},
"id": {
"type": "string",
"description": "Base64-encoded extension identifier",
"example": "ZXh0ZW5zaW9uOjEwMTA="
},
"extensionId": {
"type": "string",
"description": "The 4-digit extension number",
"example": "1010"
},
"user": {
"type": "object",
"description": "User profile and PBX settings",
"properties": {
"name": {
"type": "string",
"description": "Discord display name",
"example": "rocord"
},
"outboundCid": {
"type": "string",
"description": "Outbound caller ID override",
"example": ""
},
"voicemail": {
"type": "string",
"description": "Voicemail context",
"example": "default"
},
"ringtimer": {
"type": "integer",
"description": "Ring time in seconds before voicemail",
"example": 0
},
"noanswer": {
"type": "string",
"description": "No-answer destination",
"example": ""
},
"noanswerDestination": {
"type": "string",
"description": "No-answer destination context",
"example": ""
},
"noanswerCid": {
"type": "string",
"description": "Caller ID on no-answer",
"example": ""
},
"busyCid": {
"type": "string",
"description": "Caller ID on busy",
"example": ""
},
"sipname": {
"type": "string",
"description": "SIP display name",
"example": ""
},
"extPassword": {
"type": "string",
"description": "SIP secret (masked in dashboard)",
"example": "REDACTED"
}
}
},
"coreDevice": {
"type": "object",
"description": "PBX device configuration",
"properties": {
"deviceId": {
"type": "string",
"description": "Device identifier",
"example": "1010"
},
"dial": {
"type": "string",
"description": "Asterisk dial string",
"example": "PJSIP/1010"
},
"devicetype": {
"type": "string",
"description": "Device type (fixed, adhoc, etc.)",
"example": "fixed"
},
"description": {
"type": "string",
"description": "Human-readable device description",
"example": "rocord"
},
"emergencyCid": {
"type": "string",
"description": "Emergency caller ID override",
"example": ""
},
"tech": {
"type": "string",
"description": "SIP technology driver",
"example": "pjsip"
}
}
}
}
},
"ActiveCall": {
"type": "object",
"properties": {
"channel": {
"type": "string",
"description": "Asterisk channel identifier",
"example": "PJSIP/1010-00000002"
},
"uniqueId": {
"type": "string",
"description": "Unique call identifier",
"example": "1781687772.6"
},
"caller": {
"type": "object",
"properties": {
"number": {
"type": "string",
"description": "Caller number",
"example": "1010"
},
"name": {
"type": "string",
"description": "Caller display name",
"example": "Call Me Test (MusicOnHold)"
}
}
},
"connectedLine": {
"type": "object",
"properties": {
"number": {
"type": "string",
"description": "Connected line number",
"example": "1010"
},
"name": {
"type": "string",
"description": "Connected line display name",
"example": "Call Me Test (MusicOnHold)"
}
}
},
"state": {
"type": "string",
"description": "Call state (e.g. \"Up\", \"Ring\")",
"example": "Up"
},
"duration": {
"type": "string",
"description": "Call duration in HH:MM:SS format",
"example": "00:01:49"
},
"application": {
"type": "string",
"description": "Asterisk application name",
"example": "MusicOnHold"
},
"applicationData": {
"type": "string",
"description": "Application data/arguments",
"example": ""
}
}
},
"RegisteredDevice": {
"type": "object",
"properties": {
"uri": {
"type": "string",
"description": "SIP contact URI",
"example": "sip:1010@192.168.1.100:5060"
},
"useragent": {
"type": "string",
"description": "User-Agent header from the device",
"example": "Linphone/4.4.0"
},
"ip": {
"type": "string",
"description": "Client IP address",
"example": "192.168.1.100"
},
"port": {
"type": "string",
"description": "Client port",
"example": "5060"
},
"pingMs": {
"type": "string",
"description": "Round-trip latency in milliseconds",
"example": "12.34"
}
}
},
"Voicemail": {
"type": "object",
"properties": {
"messageId": {
"type": "string",
"description": "Unique voicemail identifier"
},
"callerIdNum": {
"type": "string",
"description": "Caller's phone number"
},
"callerIdName": {
"type": "string",
"description": "Caller's name (if available)"
},
"date": {
"type": "string",
"description": "Timestamp of the voicemail",
"example": "Wed Mar 18 11:57:09 PM UTC 2026"
},
"duration": {
"type": "number",
"description": "Duration in seconds"
},
"fileSize": {
"type": "integer",
"description": "Audio file size in bytes"
},
"hasAudio": {
"type": "boolean",
"description": "Whether audio is available for playback"
},
"folder": {
"type": "string",
"enum": [
"INBOX",
"Family",
"Friends",
"Old",
"Work",
"Urgent"
],
"description": "Current folder"
},
"originalMessageId": {
"type": "string",
"description": "Original message ID before any moves"
}
}
},
"ConferenceRoom": {
"type": "object",
"properties": {
"conferenceId": {
"type": "string",
"description": "Conference room identifier",
"example": "400"
},
"parties": {
"type": "integer",
"description": "Number of participants",
"example": 1
},
"marked": {
"type": "integer",
"description": "Number of marked participants",
"example": 0
},
"locked": {
"type": "boolean",
"description": "Whether the room is locked",
"example": false
}
}
},
"Participant": {
"type": "object",
"properties": {
"channel": {
"type": "string",
"description": "Asterisk channel identifier"
},
"callerIdNum": {
"type": "string",
"description": "Participant's extension number"
},
"callerIdName": {
"type": "string",
"description": "Participant's display name"
},
"muted": {
"type": "boolean",
"description": "Whether the participant is muted"
},
"admin": {
"type": "boolean",
"description": "Whether the participant is a conference admin"
},
"talking": {
"type": "boolean",
"description": "Whether the participant is currently speaking"
}
}
},
"CallRecords": {
"type": "object",
"properties": {
"records": {
"type": "object",
"properties": {
"total_calls_ever_placed": {
"type": "integer",
"description": "Total calls since launch",
"example": 5324
},
"record_calls": {
"type": "object",
"description": "Single-day call record",
"properties": {
"count": {
"type": "integer"
},
"date": {
"type": "string",
"format": "date"
}
}
},
"last_updated": {
"type": "string",
"format": "date-time"
}
},
"additionalProperties": {
"type": "integer",
"description": "Monthly call totals (e.g. monthly_total_2025-01)"
}
}
}
},
"SurveyData": {
"type": "object",
"properties": {
"phoneTypes": {
"type": "object",
"description": "Device types and their counts",
"additionalProperties": {
"type": "integer"
},
"example": {
"softphone": 45,
"ip_phone": 23,
"ata": 8
}
},
"brands": {
"type": "object",
"description": "Device brands and their counts",
"additionalProperties": {
"type": "integer"
},
"example": {
"Polycom": 12,
"Cisco": 8,
"Yealink": 7
}
},
"needsCategorization": {
"type": "array",
"description": "Devices that couldn't be automatically categorized",
"items": {
"type": "object",
"properties": {
"ua": {
"type": "string",
"description": "User-Agent string"
}
}
}
},
"lastUpdated": {
"type": "string",
"format": "date-time",
"description": "When the survey data was last refreshed"
}
}
}
}
}
}