Files
optima/API_ROUTES.md
T

86 KiB

Optima API Routes Documentation

This document provides a comprehensive overview of all API routes available in the Optima API.

Base URL

http://localhost:3000/v1

Authentication Routes

Get Authentication URI

GET /auth/uri

Get the Microsoft OAuth authentication URI for user login.

Authentication Required: No

Response:

{
  "status": 200,
  "message": "Successfully fetch Auth URI",
  "data": {
    "uri": "https://login.microsoftonline.com/...",
    "callbackKey": "ck123..."
  },
  "successful": true
}

OAuth Redirect Handler

GET /auth/redirect

Handles the OAuth redirect callback from Microsoft. This endpoint processes the authorization code and creates a user session.

Authentication Required: No

Query Parameters:

  • code - Authorization code from Microsoft
  • state - Callback key for WebSocket notification

Response: Closes the browser window and emits authentication tokens via WebSocket.


Refresh Access Token

POST /auth/refresh

Refresh an expired access token using a valid refresh token.

Authentication Required: Yes (Refresh Token)

Headers:

  • x-refresh-token - The refresh token

Response:

{
  "status": 201,
  "message": "Token refreshed successfully!",
  "data": {
    "accessToken": "eyJhbGc...",
    "refreshToken": "eyJhbGc..."
  },
  "successful": true
}

User Routes

Get Current User

GET /user/@me

Fetch the currently authenticated user's information.

Authentication Required: Yes

Required Scopes: user.read

Response:

{
  "status": 200,
  "message": "Fetched user.",
  "data": {
    "id": "ckx...",
    "name": "John Doe",
    "email": "john.doe@example.com",
    "login": "john.doe",
    "image": "https://...",
    "roles": ["admin"],
    "createdAt": "2026-01-01T00:00:00.000Z",
    "updatedAt": "2026-02-14T00:00:00.000Z"
  },
  "successful": true
}

Update Current User

PATCH /user/@me

Update the currently authenticated user's information.

Authentication Required: Yes

Required Scopes: user.write

Request Body:

{
  "name": "Jane Doe",
  "image": "https://example.com/avatar.jpg"
}

Response:

{
  "status": 200,
  "message": "Successfully updated user.",
  "data": {
    "id": "ckx...",
    "name": "Jane Doe",
    "email": "jane.doe@example.com",
    "image": "https://example.com/avatar.jpg"
  },
  "successful": true
}

Check User Permissions

POST /user/@me/check-permission

Check if the currently authenticated user has specific permissions. Accepts an array of permission nodes and returns the status for each.

Authentication Required: Yes

Required Scopes: user.read

Request Body:

{
  "permissions": ["user.read", "company.create", "credential.write"]
}

Response:

{
  "status": 200,
  "message": "Permission check completed.",
  "data": {
    "results": [
      {
        "permission": "user.read",
        "hasPermission": true
      },
      {
        "permission": "company.create",
        "hasPermission": false
      },
      {
        "permission": "credential.write",
        "hasPermission": true
      }
    ]
  },
  "successful": true
}

Other User Routes

Get All Users

GET /user/users

Fetch a list of all users.

Authentication Required: Yes

Required Permissions: user.read.other, user.list.other

Response:

{
  "status": 200,
  "message": "Users Fetched Successfully!",
  "data": [
    {
      "id": "ckx...",
      "name": "John Doe",
      "email": "john.doe@example.com",
      "login": "john.doe",
      "image": "https://...",
      "roles": ["admin"]
    }
  ],
  "successful": true
}

Get User by ID

GET /user/users/:identifier

Fetch a specific user by their ID.

Authentication Required: Yes

Required Permissions: user.read.other

Path Parameters:

  • identifier - The user's ID

Response:

{
  "status": 200,
  "message": "User Fetched Successfully!",
  "data": {
    "id": "ckx...",
    "name": "John Doe",
    "email": "john.doe@example.com",
    "login": "john.doe",
    "image": "https://...",
    "roles": ["admin"]
  },
  "successful": true
}

Error Response (404):

{
  "status": 404,
  "message": "User with identifier 'ckx...' was not found.",
  "error": "UserNotFound",
  "successful": false
}

Update User by ID

PATCH /user/users/:identifier

Update a specific user's information. Supports updating profile fields, roles, and direct permissions.

Authentication Required: Yes

Required Permissions: user.write.other

Conditional Permissions:

  • If roles is included in the body: user.roles.other is also required
  • If permissions is included in the body: user.permissions.other is also required

Path Parameters:

  • identifier - The user's ID

Request Body:

All fields are optional. Include only the fields you want to update.

{
  "name": "Jane Doe",
  "image": "https://example.com/avatar.jpg",
  "roles": ["admin", "moderator"],
  "permissions": ["credential.fetch", "company.fetch"]
}
Field Type Description
name string The user's display name
image string URL to the user's avatar image
roles string[] Array of role ids or monikers to assign (replaces all roles)
permissions string[] Array of permission nodes to assign (replaces all permissions)

Response:

{
  "status": 200,
  "message": "User Updated Successfully!",
  "data": {
    "id": "ckx...",
    "name": "Jane Doe",
    "email": "jane.doe@example.com",
    "login": "jane.doe",
    "image": "https://example.com/avatar.jpg",
    "roles": ["admin", "moderator"]
  },
  "successful": true
}

Error Response (403 - Missing role permission):

{
  "status": 403,
  "message": "You do not have permission to modify roles on another user.",
  "error": "InsufficientPermission",
  "successful": false
}

Delete User by ID

DELETE /user/users/:identifier

Delete a specific user.

Authentication Required: Yes

Required Permissions: user.delete.other

Path Parameters:

  • identifier - The user's ID

Response:

{
  "status": 200,
  "message": "User Deleted Successfully!",
  "data": {
    "id": "ckx...",
    "name": "John Doe",
    "email": "john.doe@example.com",
    "login": "john.doe",
    "image": "https://...",
    "roles": ["admin"]
  },
  "successful": true
}

Get User Roles

GET /user/users/:identifier/roles

Fetch all roles assigned to a specific user.

Authentication Required: Yes

Required Permissions: user.read.other, role.read

Path Parameters:

  • identifier - The user's ID

Response:

{
  "status": 200,
  "message": "User Roles Fetched Successfully!",
  "data": [
    {
      "id": "uuid...",
      "title": "Administrator",
      "moniker": "admin",
      "permissions": ["*"]
    }
  ],
  "successful": true
}

Check User Permissions (Other User)

POST /user/users/:identifier/check-permission

Check if a specific user has certain permissions.

Authentication Required: Yes

Required Permissions: user.read.other

Path Parameters:

  • identifier - The user's ID

Request Body:

{
  "permissions": ["user.read", "company.fetch", "credential.write"]
}

Response:

{
  "status": 200,
  "message": "Permission check completed.",
  "data": {
    "results": [
      {
        "permission": "user.read",
        "hasPermission": true
      },
      {
        "permission": "company.fetch",
        "hasPermission": false
      },
      {
        "permission": "credential.write",
        "hasPermission": true
      }
    ]
  },
  "successful": true
}

Company Routes

Get All Companies

GET /company/companies

Fetch a paginated list of all companies with optional search functionality.

Authentication Required: Yes

Required Permissions: company.fetch.many

Query Parameters:

  • page (optional) - Page number (default: 1)
  • rpp (optional) - Records per page (default: 30)
  • search (optional) - Search query to filter companies

Response:

{
  "status": 200,
  "message": "Companies Fetched Successfully!",
  "data": [
    {
      "id": "ckx...",
      "name": "Acme Corp",
      "cw_CompanyId": 12345,
      "cw_Identifier": "AcmeCorp"
    }
  ],
  "pagination": {
    "previousPage": null,
    "currentPage": 1,
    "nextPage": 2,
    "totalPages": 10,
    "totalRecords": 300,
    "listedRecords": 30
  },
  "successful": true
}

Get Company by ID

GET /company/companies/:identifier

Fetch a single company by its ID. Automatically fetches fresh data from ConnectWise and returns it along with internal company data.

Authentication Required: Yes

Required Permissions:

  • company.fetch (base permission)
  • company.fetch.address (required when includeAddress=true)
  • company.fetch.contacts (required when includeAllContacts=true)

URL Parameters:

  • identifier - Company ID (internal database ID)

Query Parameters:

  • includeAddress (optional) - Set to "true" to include full address information. Requires company.fetch.address permission. (default: false)
  • includePrimaryContact (optional) - Set to "true" to include the company's default contact from ConnectWise. (default: false)
  • includeAllContacts (optional) - Set to "true" to include all contacts for the company from ConnectWise. Requires company.fetch.contacts permission. (default: false)

Response (without optional query params):

{
  "status": 200,
  "message": "Company Fetched Successfully!",
  "data": {
    "id": "ckx...",
    "name": "Acme Corp",
    "cw_CompanyId": 12345,
    "cw_Identifier": "AcmeCorp",
    "cw_Data": {}
  },
  "successful": true
}

Response (with includeAddress=true):

{
  "status": 200,
  "message": "Company Fetched Successfully!",
  "data": {
    "id": "ckx...",
    "name": "Acme Corp",
    "cw_CompanyId": 12345,
    "cw_Identifier": "AcmeCorp",
    "cw_Data": {
      "address": {
        "line1": "123 Main St",
        "line2": null,
        "city": "Springfield",
        "state": "IL",
        "zip": "62701",
        "country": "United States"
      }
    }
  },
  "successful": true
}

Response (with includePrimaryContact=true):

{
  "status": 200,
  "message": "Company Fetched Successfully!",
  "data": {
    "id": "ckx...",
    "name": "Acme Corp",
    "cw_CompanyId": 12345,
    "cw_Identifier": "AcmeCorp",
    "cw_Data": {
      "primaryContact": {
        "firstName": "John",
        "lastName": "Doe",
        "cwId": 456,
        "inactive": false,
        "title": "IT Manager",
        "phone": "555-0123",
        "email": "john.doe@acmecorp.com"
      }
    }
  },
  "successful": true
}

Response (with includeAllContacts=true):

{
  "status": 200,
  "message": "Company Fetched Successfully!",
  "data": {
    "id": "ckx...",
    "name": "Acme Corp",
    "cw_CompanyId": 12345,
    "cw_Identifier": "AcmeCorp",
    "cw_Data": {
      "allContacts": [
        {
          "firstName": "John",
          "lastName": "Doe",
          "cwId": 456,
          "inactive": false,
          "title": "IT Manager",
          "phone": "555-0123",
          "email": "john.doe@acmecorp.com"
        },
        {
          "firstName": "Jane",
          "lastName": "Smith",
          "cwId": 789,
          "inactive": false,
          "title": "CTO",
          "phone": "555-0456",
          "email": "jane.smith@acmecorp.com"
        }
      ]
    }
  },
  "successful": true
}

Get Company Configurations

GET /company/companies/:identifier/configurations

Fetch configurations for a specific company from ConnectWise.

Authentication Required: Yes

Required Permissions: company.fetch, company.fetch.configurations

URL Parameters:

  • identifier - Company ID

Response:

{
  "status": 200,
  "message": "Company Configurations Fetched Successfully!",
  "data": {
    // ConnectWise configuration data
  },
  "successful": true
}

Get Company UniFi Sites

GET /company/companies/:identifier/unifi/sites

Fetch all UniFi sites linked to a specific company.

Authentication Required: Yes

Required Permissions: unifi.access, company.fetch

URL Parameters:

  • identifier - Company ID

Response:

{
  "status": 200,
  "message": "Company UniFi Sites Fetched Successfully!",
  "data": [
    {
      "id": "ckx...",
      "name": "Main Office",
      "siteId": "abc123",
      "companyId": "ckx...",
      "createdAt": "2025-01-01T00:00:00.000Z",
      "updatedAt": "2025-01-01T00:00:00.000Z"
    }
  ],
  "successful": true
}

Credential Routes

Get Value Types

GET /credential/valuetypes

Returns all available field value types for credential type fields.

Authentication Required: Yes

Response:

{
  "status": 200,
  "message": "Value Types Fetched Successfully!",
  "data": [
    "plain_text",
    "license_key",
    "ip_address",
    "generic_secret",
    "bitlocker_key",
    "password",
    "multi_credential"
  ],
  "successful": true
}

Get Credential by ID

GET /credential/credentials/:id

Fetch a single credential by its ID.

Authentication Required: Yes

Required Permissions: credential.fetch

URL Parameters:

  • id - Credential ID

Response:

{
  "status": 200,
  "message": "Credential Fetched Successfully!",
  "data": {
    "id": "ckx...",
    "name": "AWS Credentials",
    "notes": null,
    "typeId": "cky...",
    "companyId": "ckz...",
    "fields": [
      {
        "id": "accessKeyId",
        "name": "Access Key ID",
        "secure": false,
        "required": true,
        "valueType": "plain_text",
        "value": "AKIAIOSFODNN7EXAMPLE"
      },
      {
        "id": "secretAccessKey",
        "name": "Secret Access Key",
        "secure": true,
        "required": true,
        "valueType": "password",
        "value": null
      }
    ],
    "type": {
      "id": "cky...",
      "name": "AWS",
      "fields": [...],
      "permissionScope": "aws.credentials"
    },
    "company": {
      "id": "ckz...",
      "name": "Acme Corp"
    },
    "createdAt": "2026-01-01T00:00:00.000Z",
    "updatedAt": "2026-02-14T00:00:00.000Z"
  },
  "successful": true
}

Get Credentials by Company

GET /credential/credentials/company/:companyId

Fetch all credentials associated with a specific company.

Authentication Required: Yes

Required Permissions: credential.fetch.many

URL Parameters:

  • companyId - Company ID

Response:

{
  "status": 200,
  "message": "Company Credentials Fetched Successfully!",
  "data": [
    {
      "id": "ckx...",
      "name": "AWS Credentials",
      "notes": null,
      "typeId": "cky...",
      "companyId": "ckz...",
      "fields": [
        {
          "id": "accessKeyId",
          "name": "Access Key ID",
          "secure": false,
          "required": true,
          "valueType": "plain_text",
          "value": "AKIAIOSFODNN7EXAMPLE"
        },
        {
          "id": "secretAccessKey",
          "name": "Secret Access Key",
          "secure": true,
          "required": true,
          "valueType": "password",
          "value": null
        }
      ],
      "type": {...},
      "company": {...}
    }
  ],
  "successful": true
}

Create Credential

POST /credential/credentials

Create a new credential with validated and encrypted fields.

Authentication Required: Yes

Required Permissions: credential.create

Request Body:

{
  "name": "Production AWS Credentials",
  "notes": "Used for production S3 access",
  "typeId": "cky...",
  "companyId": "ckz...",
  "fields": [
    {
      "id": "ckx1...",
      "fieldId": "accessKeyId",
      "value": "AKIAIOSFODNN7EXAMPLE"
    },
    {
      "id": "ckx2...",
      "fieldId": "secretAccessKey",
      "value": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
    }
  ],
  "subCredentials": {
    "tunnels": [
      {
        "name": "Tunnel 1",
        "fields": [
          { "fieldId": "server", "value": "vpn1.example.com" },
          { "fieldId": "port", "value": "443" }
        ]
      }
    ]
  }
}
Field Type Required Description
name string Yes Display name for the credential
notes string No Optional notes
typeId string Yes The credential type ID
companyId string Yes The company ID this credential belongs to
fields array Yes Array of field values ({ fieldId, value })
subCredentials object No Keyed by multi-credential field ID. Each value is an array of { name, fields } objects for inline sub-credentials

**Response:**

```json
{
  "status": 201,
  "message": "Credential Created Successfully!",
  "data": {
    "id": "ckx...",
    "name": "Production AWS Credentials",
    "typeId": "cky...",
    "companyId": "ckz...",
    "fields": [
      {
        "id": "accessKeyId",
        "name": "Access Key ID",
        "secure": false,
        "required": true,
        "valueType": "plain_text",
        "value": "AKIAIOSFODNN7EXAMPLE"
      },
      {
        "id": "secretAccessKey",
        "name": "Secret Access Key",
        "secure": true,
        "required": true,
        "valueType": "password",
        "value": null
      }
    ],
    "type": {...},
    "company": {...}
  },
  "successful": true
}

Update Credential

PATCH /credential/credentials/:id

Update a credential's basic properties (name, notes) and/or field values. Secure fields are automatically encrypted.

Authentication Required: Yes

Required Permissions: credential.update

URL Parameters:

  • id - Credential ID

Request Body:

All properties are optional. Include only the properties you want to update.

{
  "name": "Updated Credential Name",
  "notes": "Updated notes for this credential",
  "fields": [
    {
      "fieldId": "accessKeyId",
      "value": "AKIAIOSFODNN7EXAMPLE"
    },
    {
      "fieldId": "secretAccessKey",
      "value": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
    }
  ]
}

Response:

{
  "status": 200,
  "message": "Credential Updated Successfully!",
  "data": {
    "id": "ckx...",
    "name": "Updated Credential Name",
    "notes": "Updated notes for this credential",
    "typeId": "cky...",
    "companyId": "ckz...",
    "fields": [
      {
        "id": "accessKeyId",
        "name": "Access Key ID",
        "secure": false,
        "required": true,
        "valueType": "plain_text",
        "value": "AKIAIOSFODNN7EXAMPLE"
      }
    ],
    "type": {...},
    "company": {...}
  },
  "successful": true
}

Update Credential Fields

PUT /credential/credentials/:id/fields

Validate and update credential field values. Secure fields are automatically encrypted.

Authentication Required: Yes

Required Permissions: credential.update, credential.fields.update

URL Parameters:

  • id - Credential ID

Request Body:

{
  "fields": [
    {
      "fieldId": "accessKeyId",
      "value": "AKIAIOSFODNN7NEWVALUE"
    },
    {
      "fieldId": "secretAccessKey",
      "value": "newSecretKeyValue123"
    }
  ]
}

Response:

{
  "status": 200,
  "message": "Credential Fields Updated Successfully!",
  "data": {
    "id": "ckx...",
    "name": "Production AWS Credentials",
    "notes": null,
    "typeId": "cky...",
    "companyId": "ckz...",
    "fields": [
      {
        "id": "accessKeyId",
        "name": "Access Key ID",
        "secure": false,
        "required": true,
        "valueType": "plain_text",
        "value": "AKIAIOSFODNN7NEWVALUE"
      },
      {
        "id": "secretAccessKey",
        "name": "Secret Access Key",
        "secure": true,
        "required": true,
        "valueType": "password",
        "value": null
      }
    ],
    "type": {...},
    "company": {...}
  },
  "successful": true
}

Get Credential Fields

GET /credential/credentials/:id/fields

Fetch all field values for a credential (secure fields returned encrypted).

Authentication Required: Yes

Required Permissions: credential.fetch, credential.fields.fetch

URL Parameters:

  • id - Credential ID

Response:

{
  "status": 200,
  "message": "Credential Fields Fetched Successfully!",
  "data": [
    {
      "id": "ckx-accessKeyId",
      "fieldId": "accessKeyId",
      "value": "AKIAIOSFODNN7EXAMPLE"
    },
    {
      "id": "ckx1...",
      "fieldId": "secretAccessKey",
      "value": "base64EncryptedValue=="
    }
  ],
  "successful": true
}

Read Secure Values

GET /credential/credentials/:id/secure-values

Decrypt and return all secure field values for a credential.

Authentication Required: Yes

Required Permissions: credential.fetch, credential.secure_values.read

URL Parameters:

  • id - Credential ID

Response:

{
  "status": 200,
  "message": "Secure Values Fetched Successfully!",
  "data": {
    "secretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
    "apiKey": "sk_live_123456789abcdef"
  },
  "successful": true
}

Read Single Secure Value

GET /credential/credentials/:id/secure-values/:fieldId

Decrypt and return a single secure field value for a credential.

Authentication Required: Yes

Required Permissions: credential.fetch, credential.secure_values.read

URL Parameters:

  • id - Credential ID
  • fieldId - The field ID of the secure value to read

Response:

{
  "status": 200,
  "message": "Secure Value Fetched Successfully!",
  "data": {
    "fieldId": "secretAccessKey",
    "value": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
  },
  "successful": true
}

Error Response (404):

{
  "status": 404,
  "message": "Secure field not found: unknownField",
  "error": "SecureFieldNotFound",
  "successful": false
}

Delete Credential

DELETE /credential/credentials/:id

Delete a credential and all associated secure values. Sub-credentials are cascade-deleted automatically.

Authentication Required: Yes

Required Permissions: credential.delete

URL Parameters:

  • id - Credential ID

Response:

{
  "status": 200,
  "message": "Credential Deleted Successfully!",
  "data": null,
  "successful": true
}

Get Sub-Credentials

GET /credential/credentials/:id/sub-credentials

Fetch all sub-credentials that belong to a specific parent credential.

Authentication Required: Yes

Required Permissions: credential.fetch, credential.sub_credentials.fetch

URL Parameters:

  • id - Parent Credential ID

Response:

{
  "status": 200,
  "message": "Sub-Credentials Fetched Successfully!",
  "data": [
    {
      "id": "ckx1...",
      "name": "Tunnel 1",
      "notes": null,
      "typeId": "cky...",
      "companyId": "ckz...",
      "subCredentialOfId": "ckx...",
      "fields": [
        { "id": "server", "value": "vpn1.example.com", "secure": false },
        { "id": "port", "value": "443", "secure": false }
      ],
      "type": { "..." },
      "company": { "..." },
      "createdAt": "2026-02-20T00:00:00.000Z",
      "updatedAt": "2026-02-20T00:00:00.000Z"
    }
  ],
  "successful": true
}

Add Sub-Credential

POST /credential/credentials/:id/sub-credentials

Create a new sub-credential under an existing parent credential for a specific multi-credential field.

Authentication Required: Yes

Required Permissions: credential.fetch, credential.sub_credentials.create

URL Parameters:

  • id - Parent Credential ID

Request Body:

{
  "fieldId": "tunnels",
  "name": "Tunnel 2",
  "fields": [
    { "fieldId": "server", "value": "vpn2.example.com" },
    { "fieldId": "port", "value": "1194" }
  ]
}
Field Type Required Description
fieldId string Yes The multi-credential field ID on the parent credential type
name string Yes Display name for the sub-credential
fields array Yes Array of field values matching the multi-credential's subFields

Response:

{
  "status": 201,
  "message": "Sub-Credential Created Successfully!",
  "data": {
    "id": "ckx2...",
    "name": "Tunnel 2",
    "typeId": "cky...",
    "companyId": "ckz...",
    "subCredentialOfId": "ckx...",
    "fields": [
      { "id": "server", "value": "vpn2.example.com", "secure": false },
      { "id": "port", "value": "1194", "secure": false }
    ],
    "type": { "..." },
    "company": { "..." },
    "createdAt": "2026-02-20T00:00:00.000Z",
    "updatedAt": "2026-02-20T00:00:00.000Z"
  },
  "successful": true
}

Remove Sub-Credential

DELETE /credential/credentials/:id/sub-credentials/:subId

Delete a sub-credential and remove its reference from the parent credential's multi-credential field.

Authentication Required: Yes

Required Permissions: credential.fetch, credential.sub_credentials.delete

URL Parameters:

  • id - Parent Credential ID
  • subId - Sub-Credential ID to remove

Response:

{
  "status": 200,
  "message": "Sub-Credential Removed Successfully!",
  "data": null,
  "successful": true
}

Error Response (404):

{
  "status": 404,
  "message": "Sub-credential not found",
  "error": "SubCredentialNotFound",
  "successful": false
}

Credential Type Routes

Get Credential Type by ID or Name

GET /credential-type/:identifier

Fetch a single credential type by its ID or name.

Authentication Required: Yes

Required Permissions: credential_type.fetch

URL Parameters:

  • identifier - Credential Type ID or name

Response:

{
  "status": 200,
  "message": "Credential Type Fetched Successfully!",
  "data": {
    "id": "cky...",
    "name": "AWS",
    "permissionScope": "aws.credentials",
    "icon": "https://aws.amazon.com/favicon.ico",
    "fields": [
      {
        "id": "accessKeyId",
        "name": "Access Key ID",
        "required": true,
        "secure": false,
        "valueType": "plain_text"
      },
      {
        "id": "secretAccessKey",
        "name": "Secret Access Key",
        "required": true,
        "secure": true,
        "valueType": "password"
      }
    ],
    "credentialCount": 5,
    "createdAt": "2026-01-01T00:00:00.000Z",
    "updatedAt": "2026-02-14T00:00:00.000Z"
  },
  "successful": true
}

Get All Credential Types

GET /credential-type

Fetch all credential types in the system.

Authentication Required: Yes

Required Permissions: credential_type.fetch.many

Response:

{
  "status": 200,
  "message": "Credential Types Fetched Successfully!",
  "data": [
    {
      "id": "cky...",
      "name": "AWS",
      "permissionScope": "aws.credentials",
      "icon": "https://aws.amazon.com/favicon.ico",
      "fields": [...],
      "credentialCount": 5
    },
    {
      "id": "ckz...",
      "name": "Azure",
      "permissionScope": "azure.credentials",
      "icon": "https://azure.microsoft.com/favicon.ico",
      "fields": [...],
      "credentialCount": 3
    }
  ],
  "successful": true
}

Create Credential Type

POST /credential-type

Create a new credential type with field definitions.

Authentication Required: Yes

Required Permissions: credential_type.create

Request Body:

{
  "name": "GitHub",
  "permissionScope": "github.credentials",
  "icon": "https://github.com/favicon.ico",
  "fields": [
    {
      "id": "username",
      "name": "Username",
      "required": true,
      "secure": false,
      "valueType": "plain_text"
    },
    {
      "id": "personalAccessToken",
      "name": "Personal Access Token",
      "required": true,
      "secure": true,
      "valueType": "password"
    }
  ]
}

Multi-Credential Example:

Fields with valueType: "multi_credential" support an optional subFields array that defines the field structure for each sub-credential entry. Sub-fields use the same schema and can be nested recursively.

{
  "name": "VPN Config",
  "permissionScope": "vpn.credentials",
  "icon": "https://example.com/vpn.ico",
  "fields": [
    {
      "id": "hostname",
      "name": "Hostname",
      "required": true,
      "secure": false,
      "valueType": "plain_text"
    },
    {
      "id": "tunnels",
      "name": "Tunnels",
      "required": false,
      "secure": false,
      "valueType": "multi_credential",
      "subFields": [
        {
          "id": "server",
          "name": "Server",
          "required": true,
          "secure": false,
          "valueType": "plain_text"
        },
        {
          "id": "psk",
          "name": "Pre-Shared Key",
          "required": true,
          "secure": true,
          "valueType": "password"
        }
      ]
    }
  ]
}
Field Type Required Description
fields[].id string Yes Unique identifier for the field
fields[].name string Yes Display name for the field
fields[].required bool Yes Whether the field is required when creating a credential
fields[].secure bool Yes Whether the field value should be encrypted at rest
fields[].valueType string Yes One of the supported value types (see GET /credential/valuetypes)
fields[].subFields array No Only for multi_credential fields. Defines the field structure for each nested sub-credential

Response:

{
  "status": 201,
  "message": "Credential Type Created Successfully!",
  "data": {
    "id": "ck1...",
    "name": "GitHub",
    "permissionScope": "github.credentials",
    "icon": "https://github.com/favicon.ico",
    "fields": [...]
  },
  "successful": true
}

Update Credential Type

PATCH /credential-type/:id

Update a credential type's properties or field definitions.

Authentication Required: Yes

Required Permissions: credential_type.update

URL Parameters:

  • id - Credential Type ID

Request Body:

{
  "name": "GitHub Enterprise",
  "icon": "https://github.enterprise.com/favicon.ico",
  "fields": [
    {
      "id": "username",
      "name": "Username",
      "required": true,
      "secure": false,
      "valueType": "plain_text"
    },
    {
      "id": "personalAccessToken",
      "name": "Personal Access Token",
      "required": true,
      "secure": true,
      "valueType": "password"
    },
    {
      "id": "enterpriseUrl",
      "name": "Enterprise URL",
      "required": true,
      "secure": false,
      "valueType": "plain_text"
    }
  ]
}

Note: Fields with valueType: "multi_credential" support an optional subFields array — see the Create Credential Type section for the full schema and example.

Response:

{
  "status": 200,
  "message": "Credential Type Updated Successfully!",
  "data": {
    "id": "ck1...",
    "name": "GitHub Enterprise",
    "fields": [...]
  },
  "successful": true
}

Delete Credential Type

DELETE /credential-type/:id

Delete a credential type. This will cascade delete all credentials of this type.

Authentication Required: Yes

Required Permissions: credential_type.delete

URL Parameters:

  • id - Credential Type ID

Response:

{
  "status": 200,
  "message": "Credential Type Deleted Successfully!",
  "data": null,
  "successful": true
}

Get Credentials by Type

GET /credential-type/:id/credentials

Fetch all credentials that use a specific credential type.

Authentication Required: Yes

Required Permissions: credential_type.fetch, credential.fetch.many

URL Parameters:

  • id - Credential Type ID

Response:

{
  "status": 200,
  "message": "Credentials Fetched Successfully!",
  "data": [
    {
      "id": "ckx...",
      "name": "Production AWS",
      "typeId": "cky...",
      "companyId": "ckz...",
      "fields": {...}
    },
    {
      "id": "ck2...",
      "name": "Staging AWS",
      "typeId": "cky...",
      "companyId": "ckz...",
      "fields": {...}
    }
  ],
  "successful": true
}

Role Routes

Create Role

POST /role

Create a new role with a title, moniker, and optional permissions.

Authentication Required: Yes

Required Permissions: role.create

Request Body:

{
  "title": "System Administrator",
  "moniker": "system_admin",
  "permissions": [
    "user.read",
    "user.write",
    "company.fetch",
    "credential.create"
  ]
}

Response:

{
  "status": 201,
  "message": "Role Created Successfully!",
  "data": {
    "id": "ckx...",
    "title": "System Administrator",
    "moniker": "system_admin",
    "permissions": [
      "user.read",
      "user.write",
      "company.fetch",
      "credential.create"
    ],
    "createdAt": "2026-02-17T00:00:00.000Z",
    "updatedAt": "2026-02-17T00:00:00.000Z"
  },
  "successful": true
}

Get Role by ID or Moniker

GET /role/:identifier

Fetch a single role by its ID or moniker.

Authentication Required: Yes

Required Permissions: role.read

URL Parameters:

  • identifier - Role ID or moniker

Response:

{
  "status": 200,
  "message": "Role Fetched Successfully!",
  "data": {
    "id": "ckx...",
    "title": "System Administrator",
    "moniker": "system_admin",
    "permissions": [
      "user.read",
      "user.write",
      "company.fetch",
      "credential.create"
    ],
    "createdAt": "2026-02-17T00:00:00.000Z",
    "updatedAt": "2026-02-17T00:00:00.000Z"
  },
  "successful": true
}

Get All Roles

GET /role

Fetch all roles in the system.

Authentication Required: Yes

Required Permissions: role.read, role.list

Response:

{
  "status": 200,
  "message": "Roles Fetched Successfully!",
  "data": [
    {
      "id": "ckx...",
      "title": "System Administrator",
      "moniker": "system_admin",
      "permissions": ["user.read", "user.write"],
      "createdAt": "2026-02-17T00:00:00.000Z",
      "updatedAt": "2026-02-17T00:00:00.000Z"
    },
    {
      "id": "cky...",
      "title": "Viewer",
      "moniker": "viewer",
      "permissions": ["user.read", "company.fetch"],
      "createdAt": "2026-02-17T00:00:00.000Z",
      "updatedAt": "2026-02-17T00:00:00.000Z"
    }
  ],
  "successful": true
}

Update Role

PATCH /role/:identifier

Update a role's title, moniker, or permissions.

Authentication Required: Yes

Required Permissions: role.modify

URL Parameters:

  • identifier - Role ID or moniker

Request Body:

{
  "title": "Super Administrator",
  "moniker": "super_admin",
  "permissions": ["*"]
}

Response:

{
  "status": 200,
  "message": "Role Updated Successfully!",
  "data": {
    "id": "ckx...",
    "title": "Super Administrator",
    "moniker": "super_admin",
    "permissions": ["*"],
    "createdAt": "2026-02-17T00:00:00.000Z",
    "updatedAt": "2026-02-17T12:00:00.000Z"
  },
  "successful": true
}

Delete Role

DELETE /role/:identifier

Delete a role. This will remove the role from all users that have it assigned.

Authentication Required: Yes

Required Permissions: role.delete

URL Parameters:

  • identifier - Role ID or moniker

Response:

{
  "status": 200,
  "message": "Role Deleted Successfully!",
  "data": {
    "id": "ckx...",
    "title": "System Administrator",
    "moniker": "system_admin",
    "createdAt": "2026-02-17T00:00:00.000Z",
    "updatedAt": "2026-02-17T12:00:00.000Z"
  },
  "successful": true
}

Add Permissions to Role

POST /role/:identifier/permissions

Add one or more permissions to an existing role. The new permissions will be merged with existing permissions.

Authentication Required: Yes

Required Permissions: role.modify

URL Parameters:

  • identifier - Role ID or moniker

Request Body:

{
  "permissions": ["credential.update", "credential.delete"]
}

Response:

{
  "status": 200,
  "message": "Permissions Added Successfully!",
  "data": {
    "id": "ckx...",
    "title": "System Administrator",
    "moniker": "system_admin",
    "permissions": [
      "user.read",
      "user.write",
      "company.fetch",
      "credential.create",
      "credential.update",
      "credential.delete"
    ],
    "createdAt": "2026-02-17T00:00:00.000Z",
    "updatedAt": "2026-02-17T12:30:00.000Z"
  },
  "successful": true
}

Remove Permissions from Role

DELETE /role/:identifier/permissions

Remove one or more permissions from an existing role.

Authentication Required: Yes

Required Permissions: role.modify

URL Parameters:

  • identifier - Role ID or moniker

Request Body:

{
  "permissions": ["credential.delete"]
}

Response:

{
  "status": 200,
  "message": "Permissions Removed Successfully!",
  "data": {
    "id": "ckx...",
    "title": "System Administrator",
    "moniker": "system_admin",
    "permissions": [
      "user.read",
      "user.write",
      "company.fetch",
      "credential.create",
      "credential.update"
    ],
    "createdAt": "2026-02-17T00:00:00.000Z",
    "updatedAt": "2026-02-17T12:45:00.000Z"
  },
  "successful": true
}

Get Users with Role

GET /role/:identifier/users

Fetch all users that have been assigned a specific role.

Authentication Required: Yes

Required Permissions: role.read, user.read

URL Parameters:

  • identifier - Role ID or moniker

Response:

{
  "status": 200,
  "message": "Users Fetched Successfully!",
  "data": [
    {
      "id": "cku...",
      "name": "John Doe",
      "login": "john.doe",
      "roles": ["ckx..."],
      "createdAt": "2026-01-15T00:00:00.000Z",
      "updatedAt": "2026-02-10T00:00:00.000Z"
    },
    {
      "id": "ckv...",
      "name": "Jane Smith",
      "login": "jane.smith",
      "roles": ["ckx...", "cky..."],
      "createdAt": "2026-01-20T00:00:00.000Z",
      "updatedAt": "2026-02-12T00:00:00.000Z"
    }
  ],
  "successful": true
}

Permission Routes

Get All Permission Nodes (Categorized)

GET /permissions

Fetch all permission nodes organized by category. Returns the full permission node definition object with categories as keys.

Authentication Required: Yes

Required Permissions: role.read

Response:

{
  "status": 200,
  "message": "Permission Nodes Fetched Successfully!",
  "data": {
    "global": {
      "name": "Global Permissions",
      "description": "Global wildcard permissions that grant access to all resources",
      "permissions": [
        {
          "node": "*",
          "description": "Full access to all resources and actions (administrator role)",
          "usedIn": []
        }
      ]
    },
    "company": { "..." },
    "credential": { "..." },
    "...additional categories": { "..." }
  },
  "successful": true
}

Get All Permission Nodes (Flat)

GET /permissions/nodes

Fetch a flat array of all permission nodes across all categories.

Authentication Required: Yes

Required Permissions: role.read

Response:

{
  "status": 200,
  "message": "All Permission Nodes Fetched Successfully!",
  "data": [
    {
      "node": "*",
      "description": "Full access to all resources and actions (administrator role)",
      "usedIn": []
    },
    {
      "node": "company.fetch",
      "description": "Fetch a single company",
      "usedIn": ["src/api/companies/[id]/fetch.ts"]
    },
    "...additional nodes"
  ],
  "successful": true
}

Get Permission Nodes by Category

GET /permissions/:category

Fetch all permission nodes for a specific category.

Authentication Required: Yes

Required Permissions: role.read

Path Parameters:

  • category - The category key (e.g., global, company, credential, credentialType, role, user, permission, uiNavigation, adminUI)

Response (Success):

{
  "status": 200,
  "message": "Permission Category Fetched Successfully!",
  "data": {
    "name": "Company Permissions",
    "description": "Permissions for accessing and managing company resources",
    "permissions": [
      {
        "node": "company.fetch",
        "description": "Fetch a single company",
        "usedIn": ["src/api/companies/[id]/fetch.ts"]
      },
      {
        "node": "company.fetch.address",
        "description": "View company address information",
        "usedIn": ["src/api/companies/[id]/fetch.ts"],
        "dependencies": ["company.fetch"]
      }
    ]
  },
  "successful": true
}

Response (Not Found):

{
  "status": 404,
  "message": "Permission category \"invalidCategory\" not found",
  "error": "NotFound",
  "successful": false
}

Utility Routes

Teapot

GET /teapot

A fun Easter egg endpoint that returns HTTP 418 (I'm a teapot).

Authentication Required: No

Response:

{
  "status": 418,
  "message": "I'm a teapot",
  "successful": false
}

Procurement Routes

Get All Catalog Items

GET /procurement/items

Fetch a paginated list of catalog items. Supports search.

Authentication Required: Yes

Required Permissions: procurement.catalog.fetch.many

Query Parameters:

  • page (optional, default 1) — Page number
  • rpp (optional, default 30) — Records per page
  • search (optional) — Search by name, description, part number, vendor SKU, or manufacturer
  • includeInactive (optional, default false) — Include inactive catalog items in results

Response:

{
  "status": 200,
  "message": "Catalog items fetched successfully!",
  "data": [
    {
      "id": "clx...",
      "cwCatalogId": 123,
      "name": "Dell OptiPlex 7020",
      "description": "Dell OptiPlex 7020 SFF Desktop",
      "customerDescription": "Business Desktop Computer",
      "internalNotes": null,
      "manufacturer": "Dell",
      "manufactureCwId": 45,
      "partNumber": "OPT7020-SFF",
      "vendorName": "Dell Direct",
      "vendorSku": "DELL-OPT7020",
      "vendorCwId": 12,
      "price": 899.99,
      "cost": 650.0,
      "inactive": false,
      "salesTaxable": true,
      "onHand": 5,
      "cwLastUpdated": "2026-02-25T10:00:00.000Z",
      "createdAt": "2026-01-15T00:00:00.000Z",
      "updatedAt": "2026-02-25T10:00:00.000Z"
    }
  ],
  "meta": {
    "pagination": {
      "previousPage": null,
      "currentPage": 1,
      "nextPage": 2,
      "totalPages": 10,
      "totalRecords": 300,
      "listedRecords": 30
    }
  },
  "successful": true
}

Get Catalog Item

GET /procurement/items/:identifier

Fetch a single catalog item by its internal ID or ConnectWise catalog ID.

Authentication Required: Yes

Required Permissions: procurement.catalog.fetch

Path Parameters:

  • identifier — Internal ID (cuid) or ConnectWise catalog ID (numeric)

Query Parameters:

  • includeLinkedItems (optional, default false) — Include linked catalog items in the response

Response:

{
  "status": 200,
  "message": "Catalog item fetched successfully!",
  "data": {
    "id": "clx...",
    "cwCatalogId": 123,
    "name": "Dell OptiPlex 7020",
    "description": "Dell OptiPlex 7020 SFF Desktop",
    "customerDescription": "Business Desktop Computer",
    "internalNotes": null,
    "manufacturer": "Dell",
    "manufactureCwId": 45,
    "partNumber": "OPT7020-SFF",
    "vendorName": "Dell Direct",
    "vendorSku": "DELL-OPT7020",
    "vendorCwId": 12,
    "price": 899.99,
    "cost": 650.0,
    "inactive": false,
    "salesTaxable": true,
    "onHand": 5,
    "cwLastUpdated": "2026-02-25T10:00:00.000Z",
    "linkedItems": [
      {
        "id": "clx...",
        "cwCatalogId": 456,
        "name": "Dell Warranty - 3 Year"
      }
    ],
    "createdAt": "2026-01-15T00:00:00.000Z",
    "updatedAt": "2026-02-25T10:00:00.000Z"
  },
  "successful": true
}

Get Catalog Item Count

GET /procurement/count

Get the total number of catalog items.

Authentication Required: Yes

Required Permissions: procurement.catalog.fetch.many

Query Parameters:

  • activeOnly (optional, default false) — Only count active (non-inactive) items

Response:

{
  "status": 200,
  "message": "Catalog item count fetched successfully!",
  "data": {
    "count": 300
  },
  "successful": true
}

Refresh Catalog Item Inventory

POST /procurement/items/:identifier/refresh-inventory

Refresh the on-hand inventory count for a catalog item by fetching the latest data from ConnectWise.

Authentication Required: Yes

Required Permissions: procurement.catalog.inventory.refresh

Path Parameters:

  • identifier — Internal ID (cuid) or ConnectWise catalog ID (numeric)

Response:

{
  "status": 200,
  "message": "Inventory refreshed successfully!",
  "data": {
    "id": "clx...",
    "cwCatalogId": 123,
    "name": "Dell OptiPlex 7020",
    "onHand": 7,
    "price": 899.99,
    "cost": 650.0,
    "inactive": false,
    "createdAt": "2026-01-15T00:00:00.000Z",
    "updatedAt": "2026-02-26T12:00:00.000Z"
  },
  "successful": true
}

Get Linked Catalog Items

GET /procurement/items/:identifier/linked

Fetch all catalog items linked to a specific item.

Authentication Required: Yes

Required Permissions: procurement.catalog.fetch

Path Parameters:

  • identifier — Internal ID (cuid), CW identifier string, or CW catalog ID (numeric)

Response:

{
  "status": 200,
  "message": "Linked catalog items fetched successfully!",
  "data": [
    {
      "id": "clx...",
      "cwCatalogId": 456,
      "identifier": "DELL-WAR-3YR",
      "name": "Dell Warranty - 3 Year",
      "description": "Dell 3 Year ProSupport Warranty",
      "price": 199.99,
      "cost": 120.0,
      "inactive": false,
      "onHand": 0,
      "createdAt": "2026-01-15T00:00:00.000Z",
      "updatedAt": "2026-02-25T10:00:00.000Z"
    }
  ],
  "successful": true
}

POST /procurement/items/:identifier/link

Link a target catalog item to the specified source item. The source item is identified by the URL parameter and the target by the request body.

Authentication Required: Yes

Required Permissions: procurement.catalog.link

Path Parameters:

  • identifier — Internal ID (cuid), CW identifier string, or CW catalog ID (numeric) of the source item

Request Body:

{
  "targetId": "clx..."
}

Response:

{
  "status": 200,
  "message": "Catalog item linked successfully!",
  "data": {
    "id": "clx...",
    "cwCatalogId": 123,
    "identifier": "OPT7020-SFF",
    "name": "Dell OptiPlex 7020",
    "linkedItems": [
      {
        "id": "clx...",
        "cwCatalogId": 456,
        "identifier": "DELL-WAR-3YR",
        "name": "Dell Warranty - 3 Year"
      }
    ],
    "createdAt": "2026-01-15T00:00:00.000Z",
    "updatedAt": "2026-02-26T12:00:00.000Z"
  },
  "successful": true
}

POST /procurement/items/:identifier/unlink

Remove the link between a source catalog item and a target catalog item.

Authentication Required: Yes

Required Permissions: procurement.catalog.link

Path Parameters:

  • identifier — Internal ID (cuid), CW identifier string, or CW catalog ID (numeric) of the source item

Request Body:

{
  "targetId": "clx..."
}

Response:

{
  "status": 200,
  "message": "Catalog item unlinked successfully!",
  "data": {
    "id": "clx...",
    "cwCatalogId": 123,
    "identifier": "OPT7020-SFF",
    "name": "Dell OptiPlex 7020",
    "linkedItems": [],
    "createdAt": "2026-01-15T00:00:00.000Z",
    "updatedAt": "2026-02-26T12:00:00.000Z"
  },
  "successful": true
}

Sales Routes

Sales routes serve opportunity data stored locally and synced from ConnectWise. List, search, and count operations read from the local database. Sub-resource routes (forecasts, notes, contacts) fetch live data from ConnectWise using the opportunity's CW ID.

Get All Opportunities

GET /sales/opportunities

Fetch a paginated list of opportunities. Supports search.

Authentication Required: Yes

Required Permissions: sales.opportunity.fetch.many

Query Parameters:

  • page (optional, default 1) — Page number
  • rpp (optional, default 30) — Records per page
  • search (optional) — Search by opportunity name
  • includeClosed (optional, default false) — Include closed opportunities in results

Response:

{
  "status": 200,
  "message": "Opportunities fetched successfully!",
  "data": [
    {
      "id": "clx...",
      "cwOpportunityId": 456,
      "name": "Acme Corp Network Refresh",
      "notes": "Full network redesign and hardware refresh",
      "type": { "id": 1, "name": "New" },
      "stage": { "id": 3, "name": "Proposal" },
      "status": { "id": 1, "name": "Open" },
      "priority": { "id": 2, "name": "High" },
      "rating": { "id": 1, "name": "Hot" },
      "source": "Referral",
      "campaign": null,
      "primarySalesRep": {
        "id": 10,
        "identifier": "JDoe",
        "name": "John Doe"
      },
      "secondarySalesRep": null,
      "company": { "id": 100, "name": "Acme Corp" },
      "contact": { "id": 200, "name": "Jane Smith" },
      "site": { "id": 50, "name": "Main Office" },
      "customerPO": null,
      "totalSalesTax": 0,
      "location": { "id": 1, "name": "Murray" },
      "department": { "id": 5, "name": "Sales" },
      "expectedCloseDate": "2026-04-15T00:00:00.000Z",
      "pipelineChangeDate": "2026-02-20T00:00:00.000Z",
      "dateBecameLead": "2026-01-10T00:00:00.000Z",
      "closedDate": null,
      "closedFlag": false,
      "closedBy": null,
      "companyId": "clx...",
      "cwLastUpdated": "2026-02-26T10:00:00.000Z",
      "createdAt": "2026-02-01T00:00:00.000Z",
      "updatedAt": "2026-02-26T10:00:00.000Z"
    }
  ],
  "meta": {
    "pagination": {
      "previousPage": null,
      "currentPage": 1,
      "nextPage": 2,
      "totalPages": 5,
      "totalRecords": 150,
      "listedRecords": 30
    }
  },
  "successful": true
}

Get Opportunity Count

GET /sales/opportunities/count

Get the total number of opportunities.

Authentication Required: Yes

Required Permissions: sales.opportunity.fetch.many

Query Parameters:

  • openOnly (optional, default false) — Only count open (non-closed) opportunities

Response:

{
  "status": 200,
  "message": "Opportunity count fetched successfully!",
  "data": {
    "count": 150
  },
  "successful": true
}

Get Opportunity

GET /sales/opportunities/:identifier

Fetch a single opportunity by its internal ID or ConnectWise opportunity ID.

Authentication Required: Yes

Required Permissions: sales.opportunity.fetch

Path Parameters:

  • identifier — Internal ID (cuid) or ConnectWise opportunity ID (numeric)

Response:

{
  "status": 200,
  "message": "Opportunity fetched successfully!",
  "data": {
    "id": "clx...",
    "cwOpportunityId": 456,
    "name": "Acme Corp Network Refresh",
    "notes": "Full network redesign and hardware refresh",
    "type": { "id": 1, "name": "New" },
    "stage": { "id": 3, "name": "Proposal" },
    "status": { "id": 1, "name": "Open" },
    "priority": { "id": 2, "name": "High" },
    "rating": { "id": 1, "name": "Hot" },
    "source": "Referral",
    "campaign": null,
    "primarySalesRep": {
      "id": 10,
      "identifier": "JDoe",
      "name": "John Doe"
    },
    "secondarySalesRep": null,
    "company": { "id": 100, "name": "Acme Corp" },
    "contact": { "id": 200, "name": "Jane Smith" },
    "site": { "id": 50, "name": "Main Office" },
    "customerPO": null,
    "totalSalesTax": 0,
    "location": { "id": 1, "name": "Murray" },
    "department": { "id": 5, "name": "Sales" },
    "expectedCloseDate": "2026-04-15T00:00:00.000Z",
    "pipelineChangeDate": "2026-02-20T00:00:00.000Z",
    "dateBecameLead": "2026-01-10T00:00:00.000Z",
    "closedDate": null,
    "closedFlag": false,
    "closedBy": null,
    "companyId": "clx...",
    "cwLastUpdated": "2026-02-26T10:00:00.000Z",
    "createdAt": "2026-02-01T00:00:00.000Z",
    "updatedAt": "2026-02-26T10:00:00.000Z"
  },
  "successful": true
}

Refresh Opportunity

POST /sales/opportunities/:identifier/refresh

Refresh an opportunity's local data by fetching the latest from ConnectWise.

Authentication Required: Yes

Required Permissions: sales.opportunity.refresh

Path Parameters:

  • identifier — Internal ID (cuid) or ConnectWise opportunity ID (numeric)

Response:

{
  "status": 200,
  "message": "Opportunity refreshed from ConnectWise successfully!",
  "data": {
    "id": "clx...",
    "cwOpportunityId": 456,
    "name": "Acme Corp Network Refresh",
    "notes": "Updated notes from CW",
    "type": { "id": 1, "name": "New" },
    "stage": { "id": 4, "name": "Negotiation" },
    "status": { "id": 1, "name": "Open" },
    "priority": { "id": 2, "name": "High" },
    "rating": { "id": 1, "name": "Hot" },
    "source": "Referral",
    "campaign": null,
    "primarySalesRep": {
      "id": 10,
      "identifier": "JDoe",
      "name": "John Doe"
    },
    "secondarySalesRep": null,
    "company": { "id": 100, "name": "Acme Corp" },
    "contact": { "id": 200, "name": "Jane Smith" },
    "site": { "id": 50, "name": "Main Office" },
    "customerPO": null,
    "totalSalesTax": 0,
    "location": { "id": 1, "name": "Murray" },
    "department": { "id": 5, "name": "Sales" },
    "expectedCloseDate": "2026-04-15T00:00:00.000Z",
    "pipelineChangeDate": "2026-02-25T00:00:00.000Z",
    "dateBecameLead": "2026-01-10T00:00:00.000Z",
    "closedDate": null,
    "closedFlag": false,
    "closedBy": null,
    "companyId": "clx...",
    "cwLastUpdated": "2026-02-26T14:00:00.000Z",
    "createdAt": "2026-02-01T00:00:00.000Z",
    "updatedAt": "2026-02-26T14:00:00.000Z"
  },
  "successful": true
}

Get Opportunity Forecasts

GET /sales/opportunities/:identifier/forecasts

Fetch forecast/revenue items for an opportunity. Data is fetched live from ConnectWise using the opportunity's CW ID.

Authentication Required: Yes

Required Permissions: sales.opportunity.fetch

Path Parameters:

  • identifier — Internal ID (cuid) or ConnectWise opportunity ID (numeric)

Response:

{
  "status": 200,
  "message": "Opportunity forecasts fetched successfully!",
  "data": [
    {
      "id": 1,
      "forecastType": "Revenue",
      "forecastMonth": "2026-03-01T00:00:00Z",
      "revenue": 50000.0,
      "cost": 30000.0,
      "forecastPercentage": 75,
      "status": { "id": 1, "name": "Open" },
      "includedFlag": true,
      "linkedFlag": false,
      "recurringFlag": false
    }
  ],
  "successful": true
}

Get Opportunity Notes

GET /sales/opportunities/:identifier/notes

Fetch notes for an opportunity. Data is fetched live from ConnectWise using the opportunity's CW ID.

Authentication Required: Yes

Required Permissions: sales.opportunity.fetch

Path Parameters:

  • identifier — Internal ID (cuid) or ConnectWise opportunity ID (numeric)

Response:

{
  "status": 200,
  "message": "Opportunity notes fetched successfully!",
  "data": [
    {
      "id": 1,
      "text": "Client expressed interest in a full network refresh.",
      "type": { "id": 2, "name": "Discussion" },
      "flagged": false,
      "enteredBy": "JDoe"
    }
  ],
  "successful": true
}

Get Opportunity Contacts

GET /sales/opportunities/:identifier/contacts

Fetch contacts associated with an opportunity. Data is fetched live from ConnectWise using the opportunity's CW ID.

Authentication Required: Yes

Required Permissions: sales.opportunity.fetch

Path Parameters:

  • identifier — Internal ID (cuid) or ConnectWise opportunity ID (numeric)

Response:

{
  "status": 200,
  "message": "Opportunity contacts fetched successfully!",
  "data": [
    {
      "id": 1,
      "contact": { "id": 200, "name": "Jane Smith" },
      "company": {
        "id": 100,
        "identifier": "AcmeCorp",
        "name": "Acme Corp"
      },
      "role": { "id": 1, "name": "Decision Maker" },
      "notes": "Primary point of contact for this deal",
      "referralFlag": false
    }
  ],
  "successful": true
}

UniFi Routes

All UniFi routes require the unifi.access permission in addition to their route-specific permission. This acts as a gate for the entire UniFi API.

Get All UniFi Sites

GET /unifi/sites

Fetch all UniFi site records from the database.

Authentication Required: Yes

Required Permissions: unifi.access, unifi.sites.fetch.many

Response:

{
  "status": 200,
  "message": "UniFi Sites Fetched Successfully!",
  "data": [
    {
      "id": "ckx...",
      "name": "Total Tech - Murray Office",
      "siteId": "km9b1v8i",
      "companyId": "ckx...",
      "company": {
        "id": "ckx...",
        "name": "Acme Corp"
      },
      "createdAt": "2025-01-01T00:00:00.000Z",
      "updatedAt": "2025-01-01T00:00:00.000Z"
    }
  ],
  "successful": true
}

Sync UniFi Sites

POST /unifi/sites/sync

Synchronize sites from the UniFi controller into the database. Creates new records for sites not yet tracked and updates names for existing ones.

Authentication Required: Yes

Required Permissions: unifi.access, unifi.sites.sync

Response:

{
  "status": 200,
  "message": "UniFi Sites Synced Successfully!",
  "data": [
    {
      "id": "ckx...",
      "name": "Total Tech - Murray Office",
      "siteId": "km9b1v8i",
      "companyId": null,
      "createdAt": "2025-01-01T00:00:00.000Z",
      "updatedAt": "2025-01-01T00:00:00.000Z"
    }
  ],
  "successful": true
}

Create UniFi Site

POST /unifi/sites/create

Create a new site on the UniFi controller and track it in the database.

Authentication Required: Yes

Required Permissions: unifi.access, unifi.sites.create

Request Body:

{
  "description": "New Office Site"
}
Field Type Required Description
description string Yes Human-readable name / description for the site

Response:

{
  "status": 200,
  "message": "UniFi Site Created Successfully!",
  "data": {
    "id": "ckx...",
    "name": "New Office Site",
    "siteId": "abc123",
    "companyId": null,
    "createdAt": "2025-01-01T00:00:00.000Z",
    "updatedAt": "2025-01-01T00:00:00.000Z"
  },
  "successful": true
}

Get UniFi Site

GET /unifi/site/:id

Fetch a single UniFi site record from the database by its internal ID.

Authentication Required: Yes

Required Permissions: unifi.access, unifi.sites.fetch

URL Parameters:

  • id - Internal UniFi site ID (database ID)

Response:

{
  "status": 200,
  "message": "UniFi Site Fetched Successfully!",
  "data": {
    "id": "ckx...",
    "name": "Total Tech - Murray Office",
    "siteId": "km9b1v8i",
    "companyId": "ckx...",
    "createdAt": "2025-01-01T00:00:00.000Z",
    "updatedAt": "2025-01-01T00:00:00.000Z"
  },
  "successful": true
}

Get Site Overview

GET /unifi/site/:id/overview

Fetch live site overview data from the UniFi controller, including health status, system info, and site information.

Authentication Required: Yes

Required Permissions: unifi.access, unifi.site.overview

URL Parameters:

  • id - Internal UniFi site ID (database ID)

Response:

{
  "status": 200,
  "message": "UniFi Site Overview Fetched Successfully!",
  "data": {
    "health": [
      {
        "subsystem": "wan",
        "status": "ok",
        "numAdopted": 1,
        "numGateway": 1
      }
    ],
    "sysInfo": {
      "timezone": "America/Denver",
      "hostname": "UniFi-Controller",
      "version": "8.x.x"
    },
    "siteInfo": {
      "description": "Total Tech - Murray Office",
      "name": "km9b1v8i"
    }
  },
  "successful": true
}

Get Site Devices

GET /unifi/site/:id/devices

Fetch live device list from the UniFi controller for a specific site.

Authentication Required: Yes

Required Permissions: unifi.access, unifi.site.devices

URL Parameters:

  • id - Internal UniFi site ID (database ID)

Response:

{
  "status": 200,
  "message": "UniFi Devices Fetched Successfully!",
  "data": [
    {
      "id": "abc123...",
      "mac": "00:11:22:33:44:55",
      "model": "U6-Pro",
      "name": "Office AP",
      "type": "uap",
      "state": "connected",
      "ip": "192.168.1.10",
      "version": "6.x.x",
      "uptime": 123456,
      "radios": [],
      "uplink": {}
    }
  ],
  "successful": true
}

Get Site WiFi Networks

GET /unifi/site/:id/wifi

Fetch live WiFi network (WLAN) configurations from the UniFi controller for a specific site.

Authentication Required: Yes

Required Permissions: unifi.access, unifi.site.wifi

Field-Level Gating: unifi.site.wifi.read.<field>

This route uses processObjectValuePerms to filter each WLAN object on a per-field basis. Only fields whose corresponding unifi.site.wifi.read.<field> permission the user holds are included in the response. For example, a user with unifi.site.wifi.read.name and unifi.site.wifi.read.enabled but without unifi.site.wifi.read.passphrase will receive objects containing only name and enabled. Use unifi.site.wifi.read.* to grant access to all fields.

All available field-level permission nodes
Permission Node Field
unifi.site.wifi.read.id id
unifi.site.wifi.read.name name
unifi.site.wifi.read.siteId siteId
unifi.site.wifi.read.enabled enabled
unifi.site.wifi.read.security security
unifi.site.wifi.read.wpaMode wpaMode
unifi.site.wifi.read.wpaEnc wpaEnc
unifi.site.wifi.read.wpa3Support wpa3Support
unifi.site.wifi.read.wpa3Transition wpa3Transition
unifi.site.wifi.read.wpa3FastRoaming wpa3FastRoaming
unifi.site.wifi.read.wpa3Enhanced192 wpa3Enhanced192
unifi.site.wifi.read.passphrase passphrase
unifi.site.wifi.read.passphraseAutogenerated passphraseAutogenerated
unifi.site.wifi.read.hideSSID hideSSID
unifi.site.wifi.read.isGuest isGuest
unifi.site.wifi.read.band band
unifi.site.wifi.read.bands bands
unifi.site.wifi.read.networkconfId networkconfId
unifi.site.wifi.read.usergroupId usergroupId
unifi.site.wifi.read.apGroupIds apGroupIds
unifi.site.wifi.read.apGroupMode apGroupMode
unifi.site.wifi.read.pmfMode pmfMode
unifi.site.wifi.read.groupRekey groupRekey
unifi.site.wifi.read.dtimMode dtimMode
unifi.site.wifi.read.dtimNg dtimNg
unifi.site.wifi.read.dtimNa dtimNa
unifi.site.wifi.read.dtim6e dtim6e
unifi.site.wifi.read.l2Isolation l2Isolation
unifi.site.wifi.read.fastRoamingEnabled fastRoamingEnabled
unifi.site.wifi.read.bssTransition bssTransition
unifi.site.wifi.read.uapsdEnabled uapsdEnabled
unifi.site.wifi.read.iappEnabled iappEnabled
unifi.site.wifi.read.proxyArp proxyArp
unifi.site.wifi.read.mcastenhanceEnabled mcastenhanceEnabled
unifi.site.wifi.read.macFilterEnabled macFilterEnabled
unifi.site.wifi.read.macFilterPolicy macFilterPolicy
unifi.site.wifi.read.macFilterList macFilterList
unifi.site.wifi.read.radiusDasEnabled radiusDasEnabled
unifi.site.wifi.read.radiusMacAuthEnabled radiusMacAuthEnabled
unifi.site.wifi.read.radiusMacaclFormat radiusMacaclFormat
unifi.site.wifi.read.minrateSettingPreference minrateSettingPreference
unifi.site.wifi.read.minrateNgEnabled minrateNgEnabled
unifi.site.wifi.read.minrateNgDataRateKbps minrateNgDataRateKbps
unifi.site.wifi.read.minrateNgAdvertisingRates minrateNgAdvertisingRates
unifi.site.wifi.read.minrateNaEnabled minrateNaEnabled
unifi.site.wifi.read.minrateNaDataRateKbps minrateNaDataRateKbps
unifi.site.wifi.read.minrateNaAdvertisingRates minrateNaAdvertisingRates
unifi.site.wifi.read.settingPreference settingPreference
unifi.site.wifi.read.no2ghzOui no2ghzOui
unifi.site.wifi.read.privatePreSharedKeysEnabled privatePreSharedKeysEnabled
unifi.site.wifi.read.privatePreSharedKeys privatePreSharedKeys
unifi.site.wifi.read.saeGroups saeGroups
unifi.site.wifi.read.saePsk saePsk
unifi.site.wifi.read.schedule schedule
unifi.site.wifi.read.scheduleWithDuration scheduleWithDuration
unifi.site.wifi.read.bcFilterList bcFilterList
unifi.site.wifi.read.externalId externalId

URL Parameters:

  • id - Internal UniFi site ID (database ID)

Response:

{
  "status": 200,
  "message": "UniFi WiFi Networks Fetched Successfully!",
  "data": [
    {
      "id": "66eb36e54bb53f0ae3fb82bd",
      "name": "TTAD",
      "siteId": "61ae856a8eae567c3905c54f",
      "enabled": true,
      "security": "wpapsk",
      "wpaMode": "wpa2",
      "wpaEnc": "ccmp",
      "wpa3Support": false,
      "wpa3Transition": false,
      "wpa3FastRoaming": false,
      "wpa3Enhanced192": false,
      "passphrase": "S3cur3Alarm!",
      "passphraseAutogenerated": false,
      "hideSSID": false,
      "isGuest": false,
      "band": "2g",
      "bands": ["2g"],
      "networkconfId": "66eb368d4bb53f0ae3fb7e0d",
      "usergroupId": "61ae856a8eae567c3905c55c",
      "apGroupIds": ["66eb36e44bb53f0ae3fb82bc"],
      "apGroupMode": "devices",
      "pmfMode": "disabled",
      "groupRekey": 0,
      "dtimMode": "default",
      "dtimNg": 1,
      "dtimNa": 3,
      "dtim6e": 3,
      "l2Isolation": false,
      "fastRoamingEnabled": false,
      "bssTransition": true,
      "uapsdEnabled": false,
      "iappEnabled": true,
      "proxyArp": false,
      "mcastenhanceEnabled": false,
      "macFilterEnabled": false,
      "macFilterPolicy": "allow",
      "macFilterList": [],
      "radiusDasEnabled": false,
      "radiusMacAuthEnabled": false,
      "radiusMacaclFormat": "none_lower",
      "minrateSettingPreference": "auto",
      "minrateNgEnabled": true,
      "minrateNgDataRateKbps": 1000,
      "minrateNgAdvertisingRates": false,
      "minrateNaEnabled": false,
      "minrateNaDataRateKbps": 6000,
      "minrateNaAdvertisingRates": false,
      "settingPreference": "manual",
      "no2ghzOui": true,
      "privatePreSharedKeysEnabled": false,
      "privatePreSharedKeys": [],
      "saeGroups": [],
      "saePsk": [],
      "schedule": [],
      "scheduleWithDuration": [],
      "bcFilterList": [],
      "externalId": "ba0f579c-6b21-4dfc-8d03-5a1a09243328"
    }
  ],
  "successful": true
}

Update WiFi Network

PATCH /unifi/site/:id/wifi/:wlanId

Update a WiFi network configuration on the UniFi controller.

Authentication Required: Yes

Required Permissions: unifi.access, unifi.site.wifi, unifi.site.wifi.update

URL Parameters:

  • id - Internal UniFi site ID (database ID)
  • wlanId - UniFi WLAN ID

Request Body:

{
  "name": "NewSSIDName",
  "x_passphrase": "NewPassword123",
  "enabled": true,
  "security": "wpapsk",
  "wpa_mode": "wpa2",
  "hide_ssid": false,
  "is_guest": false,
  "band": "both"
}

All fields are optional. Valid values:

  • security: "wpapsk", "wpaeap", "open"
  • wpa_mode: "wpa2", "wpa3", "wpa2wpa3"
  • band: "both", "2g", "5g"

Response:

{
  "status": 200,
  "message": "UniFi WiFi Network Updated Successfully!",
  "data": {
    "id": "66eb36e54bb53f0ae3fb82bd",
    "name": "NewSSIDName",
    "siteId": "61ae856a8eae567c3905c54f",
    "enabled": true,
    "security": "wpapsk",
    "wpaMode": "wpa2",
    "wpaEnc": "ccmp",
    "wpa3Support": false,
    "wpa3Transition": false,
    "wpa3FastRoaming": false,
    "wpa3Enhanced192": false,
    "passphrase": "NewPassword123",
    "passphraseAutogenerated": false,
    "hideSSID": false,
    "isGuest": false,
    "band": "both",
    "bands": ["2g", "5g"],
    "networkconfId": "66eb368d4bb53f0ae3fb7e0d",
    "usergroupId": "61ae856a8eae567c3905c55c",
    "apGroupIds": ["66eb36e44bb53f0ae3fb82bc"],
    "apGroupMode": "devices",
    "pmfMode": "disabled",
    "groupRekey": 0,
    "dtimMode": "default",
    "dtimNg": 1,
    "dtimNa": 3,
    "dtim6e": 3,
    "l2Isolation": false,
    "fastRoamingEnabled": false,
    "bssTransition": true,
    "uapsdEnabled": false,
    "iappEnabled": true,
    "proxyArp": false,
    "mcastenhanceEnabled": false,
    "macFilterEnabled": false,
    "macFilterPolicy": "allow",
    "macFilterList": [],
    "radiusDasEnabled": false,
    "radiusMacAuthEnabled": false,
    "radiusMacaclFormat": "none_lower",
    "minrateSettingPreference": "auto",
    "minrateNgEnabled": true,
    "minrateNgDataRateKbps": 1000,
    "minrateNgAdvertisingRates": false,
    "minrateNaEnabled": false,
    "minrateNaDataRateKbps": 6000,
    "minrateNaAdvertisingRates": false,
    "settingPreference": "manual",
    "no2ghzOui": true,
    "privatePreSharedKeysEnabled": false,
    "privatePreSharedKeys": [],
    "saeGroups": [],
    "saePsk": [],
    "schedule": [],
    "scheduleWithDuration": [],
    "bcFilterList": [],
    "externalId": "ba0f579c-6b21-4dfc-8d03-5a1a09243328"
  },
  "successful": true
}

Get Site Networks

GET /unifi/site/:id/networks

Fetch live network configurations from the UniFi controller for a specific site.

Authentication Required: Yes

Required Permissions: unifi.access, unifi.site.networks

URL Parameters:

  • id - Internal UniFi site ID (database ID)

Response:

{
  "status": 200,
  "message": "UniFi Networks Fetched Successfully!",
  "data": [
    {
      "id": "abc123...",
      "name": "LAN",
      "purpose": "corporate",
      "subnet": "192.168.1.0/24",
      "vlanId": null,
      "dhcpEnabled": true,
      "dhcpStart": "192.168.1.100",
      "dhcpStop": "192.168.1.254",
      "domainName": "localdomain",
      "isNat": true,
      "enabled": true
    }
  ],
  "successful": true
}

Get WLAN Groups

GET /unifi/site/:id/wlan-groups

Fetch WLAN groups (AP groups) from the UniFi controller for a specific site. WLAN groups define which WLANs are broadcast on which access points.

Authentication Required: Yes

Required Permissions: unifi.access, unifi.site.wlan-groups

URL Parameters:

  • id - Internal UniFi site ID (database ID)

Response:

{
  "status": 200,
  "message": "UniFi WLAN Groups Fetched Successfully!",
  "data": [
    {
      "id": "61ae856a8eae567c3905c55d",
      "name": "Default",
      "siteId": "61ae856a8eae567c3905c550",
      "noDelete": true,
      "noEdit": false,
      "hidden": false
    }
  ],
  "successful": true
}

Create WLAN Group

POST /unifi/site/:id/wlan-groups

Create a new WLAN group (AP broadcasting group) on the UniFi controller. WLAN groups control which WLANs are broadcast on which access points.

Authentication Required: Yes

Required Permissions: unifi.access, unifi.site.wlan-groups, unifi.site.wlan-groups.create

URL Parameters:

  • id - Internal UniFi site ID (database ID)

Request Body:

{
  "name": "Lobby APs"
}
Field Type Required Description
name string Yes Name of the WLAN group

Response (201):

{
  "status": 201,
  "message": "UniFi WLAN Group Created Successfully!",
  "data": {
    "id": "abc123...",
    "name": "Lobby APs",
    "siteId": "61ae856a8eae567c3905c550",
    "noDelete": false,
    "noEdit": false,
    "hidden": false
  },
  "successful": true
}

Get AP Groups

GET /unifi/site/:id/ap-groups

Fetch AP groups from the UniFi controller for a specific site. AP groups define collections of access points — individual WLANs can target specific AP groups to control which APs broadcast a given SSID.

Authentication Required: Yes

Required Permissions: unifi.access, unifi.site.ap-groups

URL Parameters:

  • id - Internal UniFi site ID (database ID)

Response:

{
  "status": 200,
  "message": "UniFi AP Groups Fetched Successfully!",
  "data": [
    {
      "id": "61ae856a8eae567c3905c565",
      "name": "All APs",
      "deviceMacs": [
        "18:e8:29:59:25:bc",
        "78:45:58:29:fa:87",
        "68:d7:9a:73:9a:28"
      ],
      "noDelete": true
    },
    {
      "id": "63c5be017e957d08189ed997",
      "name": "301Andrus",
      "deviceMacs": ["78:45:58:29:fa:87", "18:e8:29:59:25:bc"],
      "noDelete": false
    }
  ],
  "successful": true
}

Get Access Points

GET /unifi/site/:id/access-points

Fetch access points (UAPs only) from the UniFi controller for a specific site. This filters the full device list to only return wireless access points.

Authentication Required: Yes

Required Permissions: unifi.access, unifi.site.access-points

URL Parameters:

  • id - Internal UniFi site ID (database ID)

Response:

{
  "status": 200,
  "message": "UniFi Access Points Fetched Successfully!",
  "data": [
    {
      "id": "abc123...",
      "mac": "00:11:22:33:44:55",
      "model": "U6-Pro",
      "type": "uap",
      "name": "Office AP",
      "state": 1,
      "adopted": true,
      "ip": "192.168.1.10",
      "version": "7.1.68"
    }
  ],
  "successful": true
}

Get WiFi Limits

GET /unifi/site/:id/wifi-limits

Check the WiFi SSID limits per access point per radio band. UniFi access points support a maximum of 8 SSIDs per radio. This endpoint shows how many SSIDs are active on each radio and how many more can be added.

Authentication Required: Yes

Required Permissions: unifi.access, unifi.site.wifi-limits

URL Parameters:

  • id - Internal UniFi site ID (database ID)

Response:

{
  "status": 200,
  "message": "UniFi WiFi Limits Fetched Successfully!",
  "data": [
    {
      "apId": "abc123...",
      "apName": "Office AP",
      "mac": "00:11:22:33:44:55",
      "model": "U6-Pro",
      "radios": [
        {
          "radio": "ng",
          "band": "2g",
          "activeWlans": 3,
          "limit": 8,
          "remaining": 5,
          "wlanNames": ["Corporate", "Guest", "IoT"]
        },
        {
          "radio": "na",
          "band": "5g",
          "activeWlans": 3,
          "limit": 8,
          "remaining": 5,
          "wlanNames": ["Corporate", "Guest", "IoT"]
        }
      ]
    }
  ],
  "successful": true
}

Get Speed Profiles

GET /unifi/site/:id/speed-profiles

Fetch speed limit profiles (user groups) from the UniFi controller for a specific site. Speed profiles define bandwidth limits that can be applied to WiFi networks.

Authentication Required: Yes

Required Permissions: unifi.access, unifi.site.speed-profiles

URL Parameters:

  • id - Internal UniFi site ID (database ID)

Response:

{
  "status": 200,
  "message": "UniFi Speed Profiles Fetched Successfully!",
  "data": [
    {
      "id": "61ae856a8eae567c3905c55e",
      "name": "Default",
      "siteId": "61ae856a8eae567c3905c550",
      "noDelete": true,
      "downloadLimitKbps": -1,
      "uploadLimitKbps": -1
    },
    {
      "id": "abc123...",
      "name": "Guest 10Mbps",
      "siteId": "61ae856a8eae567c3905c550",
      "noDelete": false,
      "downloadLimitKbps": 10000,
      "uploadLimitKbps": 5000
    }
  ],
  "successful": true
}

Note: A value of -1 for downloadLimitKbps or uploadLimitKbps means unlimited.


Create Speed Profile

POST /unifi/site/:id/speed-profiles

Create a new speed limit profile (user group) on the UniFi controller.

Authentication Required: Yes

Required Permissions: unifi.access, unifi.site.speed-profiles, unifi.site.speed-profiles.create

URL Parameters:

  • id - Internal UniFi site ID (database ID)

Request Body:

{
  "name": "Guest 10Mbps",
  "downloadLimitKbps": 10000,
  "uploadLimitKbps": 5000
}
Field Type Required Description
name string Yes Name of the speed profile
downloadLimitKbps number No Download limit in Kbps (-1 or omit = unlimited)
uploadLimitKbps number No Upload limit in Kbps (-1 or omit = unlimited)

Response (201):

{
  "status": 201,
  "message": "UniFi Speed Profile Created Successfully!",
  "data": {
    "id": "abc123...",
    "name": "Guest 10Mbps",
    "siteId": "61ae856a8eae567c3905c550",
    "noDelete": false,
    "downloadLimitKbps": 10000,
    "uploadLimitKbps": 5000
  },
  "successful": true
}

Get Private PSKs

GET /unifi/site/:id/wifi/:wlanId/ppsk

Fetch private pre-shared keys (PPSKs) for a specific WiFi network. PPSKs allow different devices to connect to the same SSID with unique passwords.

Authentication Required: Yes

Required Permissions: unifi.access, unifi.site.wifi, unifi.site.wifi.ppsk

URL Parameters:

  • id - Internal UniFi site ID (database ID)
  • wlanId - UniFi WLAN configuration ID

Response:

{
  "status": 200,
  "message": "UniFi Private PSKs Fetched Successfully!",
  "data": [
    {
      "key": "mySecurePassword123",
      "name": "John's Laptop",
      "mac": null,
      "vlanId": null
    },
    {
      "key": "anotherPassword456",
      "name": "IoT Device",
      "mac": "AA:BB:CC:DD:EE:FF",
      "vlanId": 100
    }
  ],
  "successful": true
}

Create Private PSK

POST /unifi/site/:id/wifi/:wlanId/ppsk

Create a private pre-shared key on a specific WiFi network. This adds a new PPSK to the WLAN's list and enables PPSK mode if not already enabled.

Authentication Required: Yes

Required Permissions: unifi.access, unifi.site.wifi, unifi.site.wifi.ppsk, unifi.site.wifi.ppsk.create

URL Parameters:

  • id - Internal UniFi site ID (database ID)
  • wlanId - UniFi WLAN configuration ID

Request Body:

{
  "key": "mySecurePassword123",
  "name": "John's Laptop",
  "mac": "AA:BB:CC:DD:EE:FF",
  "vlanId": 100
}
Field Type Required Description
key string Yes The pre-shared key (min 8 characters)
name string Yes Descriptive name for this PSK
mac string No MAC address to lock this PSK to a specific device
vlanId number No VLAN ID to assign to clients using this PSK

Response (201):

{
  "status": 201,
  "message": "UniFi Private PSK Created Successfully!",
  "data": [
    {
      "key": "mySecurePassword123",
      "name": "John's Laptop",
      "mac": "AA:BB:CC:DD:EE:FF",
      "vlanId": 100
    }
  ],
  "successful": true
}

Note: The response returns the full updated list of all PPSKs on the WLAN after creation.


POST /unifi/site/:id/link

Link a UniFi site to a company.

Authentication Required: Yes

Required Permissions: unifi.access, unifi.sites.link

URL Parameters:

  • id - Internal UniFi site ID (database ID)

Request Body:

{
  "companyId": "ckx..."
}

Response:

{
  "status": 200,
  "message": "UniFi Site Linked to Company Successfully!",
  "data": {
    "id": "ckx...",
    "name": "Total Tech - Murray Office",
    "siteId": "km9b1v8i",
    "companyId": "ckx...",
    "createdAt": "2025-01-01T00:00:00.000Z",
    "updatedAt": "2025-01-01T00:00:00.000Z"
  },
  "successful": true
}

POST /unifi/site/:id/unlink

Unlink a UniFi site from its associated company.

Authentication Required: Yes

Required Permissions: unifi.access, unifi.sites.link

URL Parameters:

  • id - Internal UniFi site ID (database ID)

Response:

{
  "status": 200,
  "message": "UniFi Site Unlinked from Company Successfully!",
  "data": {
    "id": "ckx...",
    "name": "Total Tech - Murray Office",
    "siteId": "km9b1v8i",
    "companyId": null,
    "createdAt": "2025-01-01T00:00:00.000Z",
    "updatedAt": "2025-01-01T00:00:00.000Z"
  },
  "successful": true
}

Error Responses

All endpoints may return error responses in the following format:

400 Bad Request

{
  "status": 400,
  "message": "Validation error",
  "errors": [
    {
      "path": ["field"],
      "message": "Field is required"
    }
  ],
  "successful": false
}

401 Unauthorized

{
  "status": 401,
  "message": "Unauthorized",
  "successful": false
}

403 Forbidden

{
  "status": 403,
  "message": "Insufficient permissions",
  "successful": false
}

404 Not Found

{
  "status": 404,
  "message": "Resource not found",
  "successful": false
}

500 Internal Server Error

{
  "status": 500,
  "message": "Internal server error",
  "successful": false
}

Authentication

Most endpoints require authentication via an access token in the request headers:

Authorization: Bearer <access_token>

Tokens are obtained through the Microsoft OAuth flow:

  1. Call GET /auth/uri to get the authentication URL
  2. Redirect user to the Microsoft login page
  3. User authenticates and is redirected to /auth/redirect
  4. Access and refresh tokens are provided via WebSocket or response
  5. Use the access token in subsequent API requests

When the access token expires, use POST /auth/refresh with the refresh token to obtain a new access token.


Permission System

The API uses a granular permission system. Each endpoint requires specific permissions that are checked via the authMiddleware. Permissions are granted through roles assigned to users.

Common permission patterns:

  • resource.fetch - Read a single resource
  • resource.fetch.many - Read multiple resources
  • resource.create - Create a new resource
  • resource.update - Update a resource
  • resource.delete - Delete a resource
  • resource.field.action - Perform specific field operations

Users can have multiple roles, and permissions are accumulated from all assigned roles.