๐Ÿ”
Authentication
RoleDescription
ADMINFull access including user management, settings, and destructive operations
TECHNICIANRead/write access to all org data. Cannot manage users or settings
VIEWERRead-only. Blocked from vault entirely
GET /auth/sso-status SSO provider status
Public

Returns which SSO providers are enabled.

Response
{ "msEnabled": true, "googleEnabled": false }
GET /auth/google Redirect to Google OAuth2

Redirects to Google OAuth2 authorization. Requires Google SSO to be configured in settings.

GET /auth/google/callback Google OAuth2 callback

OAuth2 callback. Redirects to FRONTEND_URL/login with accessToken, refreshToken, and user (base64 JSON) as query params. Auto-creates account with VIEWER role on first login.

GET /auth/microsoft Redirect to Microsoft Entra

Redirects to Microsoft Entra ID authorization.

GET /auth/microsoft/callback Microsoft OAuth2 callback

OAuth2 callback. Same redirect pattern as Google callback.

POST /auth/register Register new user

Creates a new user. First registration is always public and creates an ADMIN. Subsequent registrations require an authenticated ADMIN.

Body
{
  "email": "[email protected]",
  "password": "minimum12chars",
  "name": "Jane Smith",
  "role": "TECHNICIAN"
}
{ "user": { "id": "...", "email": "...", "name": "...", "role": "TECHNICIAN" } }
POST /auth/login Login

Rate limited to 10/min. Supports local auth, LDAP auth, and TOTP MFA. If MFA is enabled and mfaCode is omitted, returns 403 with { "requireMfa": true }.

Body
{
  "email": "[email protected]",
  "password": "yourpassword",
  "mfaCode": "123456"
}
Response
{
  "accessToken": "eyJ...",
  "refreshToken": "uuid",
  "user": { "id": "...", "email": "...", "name": "...", "role": "ADMIN", "mfaEnabled": true }
}
POST /auth/refresh Refresh access token

Exchange a refresh token for a new access token.

Body
{ "refreshToken": "uuid" }
Response
{ "accessToken": "eyJ..." }
POST /auth/logout Logout

Requires auth. Blacklists the current access token in Redis and deletes all refresh tokens for the user.

GET /auth/me Current user

Returns the current authenticated user.

GET /auth/mfa/generate Generate TOTP secret + QR

Returns a TOTP secret and QR code data URL for the authenticated user to scan with their authenticator app.

Response
{ "secret": "BASE32SECRET", "qrCodeUrl": "data:image/png;base64,..." }
POST /auth/mfa/enable Enable MFA

Verifies the TOTP token against the secret and enables MFA for the user.

Body
{ "token": "123456", "secret": "BASE32SECRET" }
POST /auth/mfa/disable Disable MFA

Requires the user’s current password (verified against LDAP for LDAP users). Clears MFA secret.

Body
{ "password": "currentpassword" }
๐Ÿข
Organizations

All org routes accept either a UUID or 9-digit shortId as the :id parameter.

GET /organizations List organizations

Returns paginated list of organizations.

Query Params
page
Response
{
  "items": [{ "id": "...", "shortId": 482039471, "name": "Acme Corp", "logoUrl": "data:image/png;base64,..." }],
  "total": 1, "page": 1, "limit": 20, "pages": 1
}
GET /organizations/:id Get organization

Returns full organization with contacts and asset/vault counts.

GET /organizations/:id/sidebar-counts Get organization

Returns item counts for every sidebar section for a given org. Used to show badge counts in the sidebar without loading full data.

POST /organizations Create organization

Creates a new organization. shortId is auto-generated (9-digit random integer, guaranteed unique).

Body Fields
name (required)shortNametypewebsitephoneaddressnotestags
PATCH /organizations/:id Update organization

Updates an organization. Saves a revision snapshot before updating. Fires push to ConnectWise or NinjaRMM if externalSource is set.

DELETE /organizations/:id Delete organization
Admin only

Hard deletes the org and all cascading records.

GET /organizations/:id/contacts Get organization
POST /organizations/:id/contacts
PATCH /organizations/:id/contacts/:contactId Update organization
DELETE /organizations/:id/contacts/:contactId Delete organization

Contact CRUD scoped to an organization. PATCH saves a revision snapshot.

๐Ÿ–ฅ
Assets

Configurations, servers, workstations, network devices, etc.

Types: SERVER WORKSTATION LAPTOP NETWORK_DEVICE PRINTER PHONE VM OTHER

Statuses: ACTIVE INACTIVE DECOMMISSIONED SPARE

GET /assets List assets
Query Params
page
GET /assets/:id Get asset
POST /assets Create asset

Creates an asset. createdById is set from the JWT.

PATCH /assets/:id Update asset

organizationId is immutable. Saves revision snapshot. Fires fire-and-forget push to ConnectWise or NinjaRMM if asset has externalSource set. Push failures are non-fatal and logged as warnings.

DELETE /assets/:id Delete asset
Admin only
๐Ÿ‘ค
Contacts

See [Organizations](#organizations) โ€” contacts are managed under /organizations/:id/contacts.

๐Ÿ“
Locations
GET /locations List locations
Query Params
organizationId
GET /locations/:id Get location
POST /locations Create location

Fields: name (required), address, city, state, zip, country, phone, notes

PATCH /locations/:id Update location
DELETE /locations/:id Delete location
Admin only
๐Ÿ”‘
Vault

Passwords are never returned in plaintext unless ?reveal=true is explicitly set. VIEWER role is blocked from all vault operations.

GET /vault List vault entries

Bulk reveal is audited with the count of entries revealed.

Query Params
organizationId
GET /vault/:id Get vault entry

Both read and reveal are individually audited.

Query Params
reveal
POST /vault Create vault entry

TOTP secrets have spaces stripped before encryption. Passwords encrypted with AES-256-GCM.

Body Fields
organizationIdnameusernamepassword (required)totpSecreturlipAddressnotestagscategory
PATCH /vault/:id Update vault entry

organizationId is immutable. Saves revision snapshot (encrypted state). Pass empty string for totpSecret to clear it.

DELETE /vault/:id Delete vault entry
Admin only
๐Ÿ“„
Documents

All document routes accept either a UUID or shortId as :orgId.

GET /docs/:orgId/folders Returns all folders for an org (flat list, build tree client

Returns all folders for an org (flat list, build tree client-side using parentId).

POST /docs/:orgId/folders
Body Fields
name (required)parentId (nullable)
PATCH /docs/:orgId/folders/:folderId
Body Fields
nameparentId (nullable). Cannot set a folder as its own parent.
DELETE /docs/:orgId/folders/:folderId Admin only
Admin only

Cascades to child folders and files.

GET /docs/:orgId/files Returns metadata only โ€” never includes bytes.

Returns metadata only โ€” never includes data bytes.

Query Params
folderId
GET /docs/:orgId/files/:fileId Returns file metadata including (for MARKDOWN) and (for bl

Returns file metadata including content (for MARKDOWN) and blocks (for block-based editor). Never returns binary data.

POST /docs/:orgId/files Creates a new Markdown document.

Creates a new Markdown document.

Body Fields
name (required)titlecontentfolderId
POST /docs/:orgId/files/upload Multipart upload for binary files (PDF, DOCX, XLSX)

Multipart upload for binary files (PDF, DOCX, XLSX). Max 100MB. Stores binary data in data column.

Form Fields
file
POST /docs/:orgId/files/import-markdown Converts a DOCX/TXT/MD file to a Markdown block document

Converts a DOCX/TXT/MD file to a Markdown block document. DOCX is converted via mammoth โ†’ HTML โ†’ Markdown.

Form Fields
file
GET /docs/:orgId/files/:fileId/download Streams the binary file with appropriate header

Streams the binary file with appropriate Content-Disposition header. Only works for non-MARKDOWN types.

PATCH /docs/:orgId/files/:fileId Updates name, folderId, or content (MARKDOWN only).

Updates name, folderId, or content (MARKDOWN only).

DELETE /docs/:orgId/files/:fileId Admin only.
Admin only
PUT /docs/:orgId/files/:fileId/blocks Saves the full block array for a block-based Markdown docume

Saves the full block array for a block-based Markdown document.

Body Fields
title (required)blocks (array)status (DRAFT | PUBLISHED)
POST /docs/:orgId/files/:fileId/images Upload an image for a gallery block

Upload an image for a gallery block. HEIC/HEIF auto-converted to JPEG. Max 100MB.

Form Fields
image
GET /docs/:orgId/files/:fileId/images/:imageId Streams raw image bytes with 1-hour cache header.

Streams raw image bytes with 1-hour cache header.

DELETE /docs/:orgId/files/:fileId/images/:imageId
๐Ÿ“Ž
Attachments

Polymorphic โ€” can be attached to any record type via sourceType + sourceId.

GET /attachments Returns metadata only โ€” no binary data in list response (use

Returns metadata only โ€” no binary data in list response (use /download for file bytes).

Query Params
sourceId
GET /attachments/:id/download Streams the file with header.

Streams the file with Content-Disposition: attachment header.

POST /attachments Multipart upload

Multipart upload. Max 300MB. HEIC auto-converted to JPEG. Backward compatible: also accepts assetId field (translated to sourceType: asset).

Form Fields
file
PATCH /attachments/:id Update the display label.

Update the display label.

Body Fields
{ "label": "string" }
DELETE /attachments/:id
๐Ÿ—
Infrastructure Records

Generic key-value infrastructure tracker. infraType is a free-form string key (e.g. firewall, wan, ad, email).

GET /infra When is omitted, results sort by (for dashboard widgets)

When infraType is omitted, results sort by updatedAt desc (for dashboard widgets). When specified, sorts by name asc.

Query Params
organizationId
GET /infra/:id
POST /infra
Body Fields
organizationIdnameinfraTypestatusnotescustomFields (JSON object)
PATCH /infra/:id and are immutable

organizationId and infraType are immutable. Saves revision snapshot.

DELETE /infra/:id
๐Ÿ“ฆ
Applications
GET /applications
Query Params
organizationId
GET /applications/:id
POST /applications
Body Fields
organizationIdname (required)categoryversionimportancebusinessImpactapplicationChampionapplicationServersvendorlicensingProfileinstallPathFullinstallPathLetterdatabaseNamenotes
PATCH /applications/:id Saves revision snapshot.

Saves revision snapshot.

DELETE /applications/:id Admin only.
Admin only
๐ŸŒ
Domains

DNS and WHOIS data is automatically fetched on create (background, non-blocking) and refreshed weekly by the cron job.

GET /domains Returns lightweight list (no raw WHOIS/DNS data).

Returns lightweight list (no raw WHOIS/DNS data).

Query Params
organizationId
GET /domains/:id Returns full record including all DNS and WHOIS fields.

Returns full record including all DNS and WHOIS fields.

POST /domains Creates the record and immediately kicks off background DNS

Creates the record and immediately kicks off background DNS + WHOIS enrichment.

Body Fields
organizationIddomainName (required)notes
PATCH /domains/:id Only is updatable

Only notes is updatable. Saves revision snapshot.

DELETE /domains/:id Admin only.
Admin only
๐Ÿ”’
Certificates
GET /certificates
Query Params
organizationId
GET /certificates/:id
POST /certificates
Body Fields
organizationIdhostname (required)issuercertType (DV|OV|EV|WILDCARD)issuedAt (ISO datetime)expiresAt (ISO datetime)autoRenew (boolean)notesrelatedAssetId
PATCH /certificates/:id Saves revision snapshot.

Saves revision snapshot.

DELETE /certificates/:id Admin only.
Admin only
๐Ÿชช
Licenses
GET /licenses
Query Params
organizationId
GET /licenses/:id
POST /licenses
Body Fields
organizationIdproductName (required)vendorlicenseKeylicenseType (PER_SEAT|PER_DEVICE|VOLUME|SUBSCRIPTION)seatCountpurchaseDate (ISO)expiryDate (ISO)purchasePrice (decimal)notes
PATCH /licenses/:id Saves revision snapshot.

Saves revision snapshot.

DELETE /licenses/:id Admin only.
Admin only
๐Ÿ•“
Revisions

All routes return the last 3 revisions per record. Older revisions are pruned automatically.

Supported sourceTypes: asset vault application contact location domain certificate license organization infra

GET /revisions/:sourceType/:sourceId Returns up to 3 most recent revisions for a record, newest f

Returns up to 3 most recent revisions for a record, newest first.

GET /revisions/:id Get revision snapshot

Returns a specific revision’s full JSON snapshot.

GET /revisions/:id/reveal Get revision snapshot
Admin only Audited

Technician/ Decrypts and returns password and totpSecret from a vault revision snapshot.

POST /revisions/:id/restore Restores a record to the state captured in the revision

Restores a record to the state captured in the revision. Before restoring, saves the current state as a new revision. Strips id, createdAt, and updatedAt from the restored data to avoid overwriting primary keys.

โ˜ฐ
Sidebar

Singleton configuration (id: global) controlling the navigation structure.

GET /sidebar Get sidebar config

Returns the current sidebar config, or the default config if none has been saved.

PUT /sidebar Replace sidebar config
Admin only

Replaces the full sidebar config. Each item has: id, label, icon (Lucide icon name), linkType (filtered|markdown|builtin|custom), linkTarget, builtinPath, fields (for custom sections).

Body Fields
{ "config": [ ...sections ] }
POST /sidebar/reset Reset to default
Admin only

Resets to the default config.

โš™๏ธ
Settings
GET /settings/public Get public settings
Public

Returns SSO and MFA config needed by the login page.

{ "ssoEnabled": true, "googleSsoEnabled": false, "localMfaEnabled": true }
GET /settings Get all settings
Admin only

Returns all settings. Sensitive fields (client secrets, private keys, LDAP bind password, enrollment token) are never returned โ€” only presence is indicated via has* boolean flags.

PATCH /settings Update settings
Admin only

Secrets are only updated if a new non-masked value is provided. Passing an empty string explicitly clears a secret.

POST /settings/ldap/sync LDAP user sync
Admin only

Triggers a full LDAP user sync. Returns count of synced users. Role mapping is applied only if ldapAdminGroup or ldapTechGroup is configured.

๐Ÿ‘ฅ
Users
GET /users List users
Admin only

Paginated list. Query params: search, page, limit (max 100)

Query Params
search
GET /users/:id Get user
Admin only
PATCH /users/:id Update user
Admin only

Updatable fields: name, role, isActive, resetMfa. Self-protection rules: – Cannot change your own role – Cannot deactivate your own account

POST /users/:id/reset-password Admin only
Admin only

Min 12 characters. Invalidates all existing refresh tokens for the user.

Body Fields
{ "password": "newpassword123" }
POST /users/me/change-password Change own password

Any authenticated user. Requires current password. Min 12 characters.

Body Fields
`{ “currentPassword”: “…”“newPassword”: “…” }`
DELETE /users/:id Delete user
Admin only

Soft delete โ€” sets isActive: false, never hard-deletes. Invalidates all refresh tokens.

GET /users/me/preferences Get preferences

Returns the current user’s preferences JSON blob.

PATCH /users/me/preferences Update preferences

Replaces the preferences blob. Body is any JSON object.

๐Ÿ“‹
Audit Logs
GET /audit-logs List audit logs
Admin only

Paginated, newest first. Query params: search (action/resource/user name/email), page, limit (max implied 200 via take)

Query Params
search
Response
{
  "items": [{
    "id": "...", "action": "vault.reveal", "resource": "VaultEntry:uuid",
    "ipAddress": "1.2.3.4", "userAgent": "...", "meta": {},
    "createdAt": "...", "user": { "name": "Admin", "email": "admin@..." }
  }],
  "total": 500, "page": 1, "limit": 50, "pages": 10
}
๐Ÿ”Œ
Integrations

All integration routes are Admin only.

GET /integrations/status Integration status

Returns enabled/configured status and last sync info for ConnectWise and NinjaRMM.

GET /integrations/sync-logs Sync logs
Query Params
source
POST /integrations/connectwise/test Test ConnectWise credentials

Tests ConnectWise credentials without saving. Hits /system/info.

Body Fields
cwCompanyIdcwServerUrlcwPublicKeycwPrivateKey
POST /integrations/connectwise/save Save ConnectWise settings

Saves ConnectWise settings. Private key is AES-256-GCM encrypted at rest. Masked values (starting with โ€ข) are not re-encrypted.

Body Fields
cwEnabledcwCompanyIdcwServerUrlcwPublicKeycwPrivateKeycwSyncSchedule
POST /integrations/connectwise/sync ConnectWise full sync

Triggers a full pull sync (Companies โ†’ Orgs, Contacts, Configurations โ†’ Assets). Returns sync result counts. Creates a SyncLog entry.

POST /integrations/connectwise/push/:type/:id Pushes a single record to ConnectWise

Pushes a single record to ConnectWise. :type must be organization, contact, or asset.

POST /integrations/ninjarmm/test Tests NinjaRMM OAuth2 client credentials.

Tests NinjaRMM OAuth2 client credentials.

Body Fields
ninjaClientIdninjaClientSecretninjaInstanceUrl
POST /integrations/ninjarmm/save
Body Fields
ninjaEnabledninjaClientIdninjaClientSecretninjaInstanceUrlninjaSyncSchedule
POST /integrations/ninjarmm/sync Full pull sync (Organizations, Devices โ†’ Assets, Contacts)

Full pull sync (Organizations, Devices โ†’ Assets, Contacts). Name-based deduplication against existing orgs.

POST /integrations/ninjarmm/push/:type/:id
๐Ÿ–ฑ
RustDesk
POST /rustdesk/enroll Enroll device
Public

endpoint โ€” pre-shared token auth, no JWT required. Called by the PowerShell enrollment script. Resolves org by shortId. Matches existing asset by hostname (case-insensitive) or auto-creates one. Asset type inferred from OS name (SERVER if contains “server”, otherwise WORKSTATION). Password stored AES-256-GCM encrypted in vault.

Body
{
  "enrollmentToken": "plain-token",
  "orgShortId": 482039471,
  "rustdeskId": "1571237679",
  "password": "generated-password",
  "hostname": "DESKTOP-ABC123",
  "ipAddress": "192.168.1.10",
  "osName": "Windows 10 Pro",
  "osVersion": "10.0.19045",
  "macAddress": "AA:BB:CC:DD:EE:FF"
}
GET /rustdesk/asset/:assetId JWT auth
Audited

JWT auth. Returns RustDesk ID, decrypted password, and a ready-to-use rustdesk:// connect URI.

Response
{
  "rustdeskId": "1571237679",
  "password": "decryptedpassword",
  "connectUri": "rustdesk://1571237679?password=decryptedpassword"
}
GET /rustdesk/settings Get RustDesk settings
Admin only

Returns current RustDesk settings. Enrollment token presence indicated by hasEnrollmentToken boolean โ€” the token itself is never returned.

POST /rustdesk/settings Update RustDesk settings
Admin only

Pass rotateToken: true to generate a new enrollment token. The new plain token is returned once in the response and never again.

Body Fields
rustdeskEnabledrustdeskServerUrlrustdeskConfigrotateToken
GET /rustdesk/enrollment-script Download enrollment script
Admin only

Downloads a pre-filled enroll-rustdesk.ps1 PowerShell script. All four variables (%%ITDOCS_URL%%, %%ENROLLMENT_TOKEN%%, %%ORG_SHORT_ID%%, %%RUSTDESK_CONFIG%%) are replaced server-side before delivery.

Query Params
token
DELETE /rustdesk/asset/:assetId/enroll Admin only
Admin only Audited

Clears rustdeskId from the asset and deletes the associated vault entry.

๐Ÿ’พ
Backups

All backup routes are Admin only.

POST /backups/export Export encrypted backup

Exports an encrypted backup archive. Uses AES-256-GCM with PBKDF2-SHA512 key derivation (200,000 iterations, random 16-byte salt per backup). File format: [16B salt][12B IV][16B auth tag][ciphertext]. – db โ€” Full database export as JSON (includes all records, vault entries decrypted then re-encrypted for portability) – files โ€” Binary attachments, uploaded documents, and block images as raw files

Body Fields
`{ “password”: “min8chars”“type”: “db” | “files” }`
POST /backups/import Import backup

Multipart. Auto-detects backup type from ZIP contents. – wipe โ€” Deletes all current data and replaces with backup – merge โ€” Upserts records, preserves existing data not in backup For file backups:

Form Fields
file
Response
{ "success": true, "mode": "wipe" }
{ "success": true, "mode": "files", "restored": 12, "skipped": 2 }
GET /backups/config Get backup config

Returns the scheduled backup configuration plus env var status.

PATCH /backups/config Update DB backup schedule

Updates DB backup schedule settings. Cron expression is validated before saving.

Body Fields
enabledcronExpressionretention
PATCH /backups/config/files Update file backup schedule

Updates file backup schedule settings.

Body Fields
filesEnabledfilesCronExpressionfilesRetention
POST /backups/run-now Trigger DB backup now

Triggers a scheduled DB backup immediately. Requires BACKUP_PATH and BACKUP_PASSWORD env vars.

POST /backups/run-files-now Trigger file backup now

Triggers an incremental file backup immediately. Requires BACKUP_FILES_PATH env var.

GET /backups/list List backup files

Lists .enc backup files in BACKUP_PATH, newest first.

Response
{
  "files": [{ "filename": "itdocs_db_backup_2026-03-25_020000.enc", "sizeBytes": 1048576, "createdAt": "..." }]
}
POST /backups/restore/:filename Restores from a named file on the server

Restores from a named .enc file on the server. Always wipe mode. Path traversal protected. Decrypts using BACKUP_PASSWORD env var.

GET /backups/files-tree File backup tree

Walks BACKUP_FILES_PATH and returns the directory tree grouped by org. Resolves shortId folder names to human-readable org names.

Response
{
  "orgs": [{
    "org": "482039471 - Acme Corp",
    "orgLabel": "Acme Corp",
    "files": [{ "path": "482039471 - Acme Corp/attachments/photo.jpg", "name": "photo.jpg", "type": "attachments", "sizeBytes": 45000, "modifiedAt": "..." }]
  }]
}
POST /backups/restore-files Restore selected files

Restores selected files from BACKUP_FILES_PATH back into the database. Uses .meta.json sidecars to upsert records โ€” works even if the original DB record was deleted. Falls back to name/ID matching for legacy backups without sidecars.

Body Fields
{ "paths": ["482039471 - Acme Corp/attachments/photo.jpg"] }
๐Ÿ›ก
Roles & Permissions
Endpoint categoryVIEWERTECHNICIANADMIN
Read all org dataโœ…โœ…โœ…
Create/update recordsโŒโœ…โœ…
Delete recordsโŒโŒโœ…
Vault readโŒโœ…โœ…
Vault reveal passwordโŒโœ…โœ…
User managementโŒโŒโœ…
SettingsโŒโŒโœ…
Audit logsโŒโŒโœ…
IntegrationsโŒโŒโœ…
BackupsโŒโŒโœ…
RustDesk connectโŒโœ…โœ…
RustDesk settingsโŒโŒโœ…
โš ๏ธ
Error Responses

All errors return a JSON body with an error field.

StatusMeaning
400Bad request / validation failed
401Unauthenticated or token revoked
403Authenticated but insufficient role
404Record not found
409Unique constraint violation (record already exists)
422Zod validation failed โ€” includes issues field with field-level errors
429Rate limit exceeded
500Internal server error (internals not exposed)
Validation error example (422):
{
  "error": "Validation failed",
  "issues": {
    "email": ["Invalid email"],
    "password": ["String must contain at least 12 character(s)"]
  }
}
โš™๏ธ
Environment Variables
VariableRequiredDescription
DATABASE_URLโœ…PostgreSQL connection string
REDIS_HOSTโœ…Redis hostname
REDIS_PORTRedis port (default: 6379)
REDIS_PASSWORDโœ…Redis password
JWT_SECRETโœ…Min 32 characters
VAULT_ENCRYPTION_KEYโœ…64 hex chars (32 bytes) โ€” generate with openssl rand -hex 32
CORS_ORIGINAllowed CORS origin (default: http://localhost:3000)
FRONTEND_URLUsed for SSO redirects
PORTAPI port (default: 4000)
HOSTBind address (default: 0.0.0.0)
LOG_LEVELPino log level (default: info)
BACKUP_PATHServer path for scheduled DB backups
BACKUP_PASSWORDEncryption password for DB backups (min 8 chars)
BACKUP_FILES_PATHServer path for incremental file backups
CW_CLIENT_IDConnectWise API client ID header (default: itdocs-integration)