Configuration Reference
Every environment variable AVA reads, grouped by area, with its type, default, whether it is required, and what it changes. The authoritative template is .env.example in the repo root — this document explains it.
How config is loaded. In development the custom server loads .env.local from the repo root (with override: true, so .env.local wins over shell vars). In the container, your env file is mounted read-only at /app/.env.local and docker-entrypoint.sh validates the required set before the server starts. Almost every non-NEXT_PUBLIC_* value is read at call time, so you can change it and restart the container with no rebuild.
Two special cases:
NEXT_PUBLIC_*variables are compiled into the browser bundle at build time (Dockerfile build args). Changing them requires a rebuild, not just a restart.AUTOTASK_API_SECRETis read raw from the env file (bypassing dotenv variable-expansion) because it commonly contains$. Wrap it in double quotes.
Legend: Required = the container refuses to start without it (or it is required for the configured AI provider). Recommended = boots without it but you should set it for a correct deployment. Optional = safe default.
Required to boot
These are validated by docker-entrypoint.sh; a missing one aborts startup with a clear error naming the variable.
| Variable | Type | Effect |
|---|---|---|
NEXTAUTH_SECRET | string (32+ chars) | Signs the engineer session cookie and client session tokens (HS256, 24 h). Generate with openssl rand -base64 48. |
AUTOTASK_API_USER | string (email) | AutoTask API-only user. |
AUTOTASK_API_SECRET | string | AutoTask API secret. Quote it (may contain $). |
AUTOTASK_API_INTEGRATION_CODE | string | AutoTask API tracking integration code. |
AUTOTASK_API_ZONE_URL | URL | Your AutoTask REST zone base. Trailing slash optional; /V1.0 appended if absent. |
AZURE_AD_CLIENT_ID | GUID | Entra app registration client ID. |
AZURE_AD_CLIENT_SECRET | string | Entra app registration client secret. |
AZURE_AD_TENANT_ID | GUID | Entra directory (tenant) ID. |
ENGINEERING_GROUP_ID | GUID | Entra security-group object ID; only members may sign in (checked against the groups claim, fail-closed). |
RECAPTCHA_SECRET_KEY | string | reCAPTCHA v3 server-side secret. |
SMTP2GO_USER | string | SMTP2GO username. |
SMTP2GO_PASSWORD | string | SMTP2GO password. |
SUPPORT_FALLBACK_EMAIL | Recipient for the after-hours "email a ticket" fallback form. No vendor default — AVA never falls back to a built-in address, so the container refuses to start without it. | |
| AI provider key | string | The key for whichever provider LLM_FAST / LLM_QUALITY use — see AI / LLM providers. With defaults, this is ANTHROPIC_API_KEY. |
NEXTAUTH_SECRET strength checks
NEXTAUTH_SECRET strength checksBeyond mere presence, the entrypoint enforces secret quality at boot:
- Shorter than 32 characters → startup aborts. This secret signs every engineer and client session, so it must be long and random.
- Placeholder-looking values are refused (for example anything containing
changeme) — a value copy-pasted from documentation cannot reach production.
Generate a real one:
openssl rand -base64 48The entrypoint also warns (without failing) if the mounted env file is group- or world-readable — it holds live secrets, so keep it chmod 600.
AutoTask — default ticket values
Picklist IDs are specific to your AutoTask instance; discover them with npx tsx scripts/discover-picklists.ts. Do not copy another instance's numbers.
| Variable | Type | Default | Req. | Effect |
|---|---|---|---|---|
AUTOTASK_DEFAULT_TICKET_STATUS | int | 1 | Recommended | Status ID for new chat tickets ("New"). |
AUTOTASK_DEFAULT_TICKET_PRIORITY | int | 3 | Recommended | Fallback priority ID ("Normal"). |
AUTOTASK_DEFAULT_TICKET_QUEUE_ID | int | — | Recommended | Helpdesk queue the ticket lands in. |
AUTOTASK_DEFAULT_TICKET_SOURCE | int | — | Recommended | Ticket "source" ID (create a "Chat" source in AT if needed). |
AUTOTASK_DEFAULT_TICKET_TYPE | int | — | Recommended | Ticket type for chat-originated tickets. |
AUTOTASK_PRIORITY_P1 | int | (falls back to AUTOTASK_DEFAULT_TICKET_PRIORITY, then 3) | Recommended | AT priority ID mapped from triage P1 (critical). |
AUTOTASK_PRIORITY_P2 | int | (falls back to AUTOTASK_DEFAULT_TICKET_PRIORITY, then 3) | Recommended | AT priority ID mapped from triage P2 (high). |
AUTOTASK_PRIORITY_P3 | int | (falls back to AUTOTASK_DEFAULT_TICKET_PRIORITY, then 3) | Recommended | AT priority ID mapped from triage P3 (normal). |
AUTOTASK_PRIORITY_P4 | int | (falls back to AUTOTASK_DEFAULT_TICKET_PRIORITY, then 3) | Recommended | AT priority ID mapped from triage P4 (low). |
AUTOTASK_NOTE_PUBLISH_EXTERNAL | int | 1 | Recommended | The publish value for the client-facing close note. Per-instance picklist — 1 = "Internal Only" is universally valid; only change to a value your instance defines, or the note post fails with a 500. |
⚠️ Warning — the four priority vars are not a graduated default. There is no built-in per-priority-level mapping. Each of
AUTOTASK_PRIORITY_P1..P4resolves independently: if a givenPnis unset, it falls through to the single sharedAUTOTASK_DEFAULT_TICKET_PRIORITY, and if that is also unset, to a hardcoded3. If you do not set these four vars, ALL AI-triaged chats file at the same AT priority, regardless of what the AI determined — P1/critical and P4/low tickets become indistinguishable in AutoTask. Discover your instance's priority picklist IDs withnpx tsx scripts/discover-picklists.tsand set all four.
Priority editing in the engineer console (v1.8.0). Engineers can change a ticket's priority from the chat's ticket panel or with /priority. That is only enabled when all four of AUTOTASK_PRIORITY_P1..P4 are set and pairwise distinct, and every one of the four IDs is an active value of your live AT priority picklist — otherwise AVA cannot tell which AT priority a P-level means (or AT would refuse the write), so the Priority field is read-only (hint: "Priority editing isn't configured — ask your administrator") and AVA refuses priority changes instead of writing a guessed value. A typical mapping, using your own picklist IDs:
AUTOTASK_PRIORITY_P1=4 # your "Critical" priority id
AUTOTASK_PRIORITY_P2=1 # your "High" priority id
AUTOTASK_PRIORITY_P3=2 # your "Medium" priority id
AUTOTASK_PRIORITY_P4=3 # your "Low" priority idA priority change also updates the chat's P-level in the queue and header. Status editing needs no config: the status list is read live from your AT picklist, and "Complete" is never offered there (tickets are completed through the wrap-up flow). Complete is recognised by its label and by its picklist ID — AT's system value 5 — so a renamed or localized Complete status is still refused; if your instance's Complete status uses a different ID, set AUTOTASK_COMPLETE_STATUS_ID (below).
Engineer internal notes & ticket edits (v1.8.0)
| Variable | Type | Default | Req. | Effect |
|---|---|---|---|---|
AUTOTASK_NOTE_PUBLISH_INTERNAL | positive int | 1 | Recommended | The publish value for every engineer-only AT note: live internal notes, the close-time "Chat Transcript", the "Chat internal notes (not synced live)" note and the wrap-up internal note. Must be an Internal-Only value on your instance — verify it with the TicketNotes entityInformation/fields check in autotask-setup.md (Step 7, "Internal notes"). An invalid value logs one warning and falls back to 1. |
AUTOTASK_SYNC_INTERNAL_NOTES | bool | true | Optional | Live sync of engineer internal notes to the AT ticket. Set false and notes are kept in AVA and reach AT only at close (in the "not synced live" note and the transcript). Use this if an AT workflow rule would email the contact when a note is added. |
AUTOTASK_WEB_URL | https URL | derived from AUTOTASK_API_ZONE_URL | Optional | Origin of your AutoTask web UI, used for ticket deep links in the engineer console. Only https: is accepted and only the origin is kept. When unset, AVA derives it from the API zone (webservicesN.autotask.net → https://wwN.autotask.net); if neither works, ticket numbers render as plain text. Copy webUrl from the zoneInformation call (autotask-setup.md, Step 3). |
AUTOTASK_COMPLETE_STATUS_ID | non-negative int | 5 | Optional | Picklist ID of your AT "Complete" ticket status. The console never lets an engineer set it mid-chat (wrap-up completes the ticket); it is matched by this ID as well as by the label "Complete". An invalid value falls back to 5. |
TICKET_WRITE_RATE_LIMIT_PER_MIN | positive int | 10 | Optional | Per-engineer limit (per minute) on ticket status/priority writes from the console (ticket panel, /status, /priority). Excess writes get "Slow down". An invalid value (including 0) logs a warning and uses 10. |
Internal notes are never shown to the client — not in the chat, not in emails, not in the client-facing AT note, the resolution field or the time entry. AutoTask workflow rules still apply to every write AVA makes, so read the workflow-rule caution in autotask-setup.md (Step 7, "Workflow rules and the engineer console") before go-live. How these features look to engineers is covered in engineer-console.md.
Resource role ID — AUTOTASK_DEFAULT_ROLE_ID
AUTOTASK_DEFAULT_ROLE_ID| Variable | Type | Default | Req. | Effect |
|---|---|---|---|---|
AUTOTASK_DEFAULT_ROLE_ID | positive int | (built-in fallback) | Set it | Resource role ID used when assigning a ticket to the accepting engineer and when posting the billed time entry on close. |
Resolution is warn-not-fail: if unset — or set to something that isn't a positive integer — AVA logs a one-time warning and falls back to a built-in role ID that will not match your AutoTask instance. Role IDs are per-instance, so always set your own (find them with GET /Roles or via your AT admin's Roles list). An invalid value never blocks startup; it just degrades to the fallback — which would post time entries under a role your instance doesn't recognize.
Catchall company — AUTOTASK_CATCHALL_COMPANY_ID
AUTOTASK_CATCHALL_COMPANY_ID| Variable | Type | Default | Req. | Effect |
|---|---|---|---|---|
AUTOTASK_CATCHALL_COMPANY_ID | int (AT company ID) | (unset) | Recommended | Company that receives tickets from email domains not tied to any AT company (personal Gmail, etc.). |
If unset, unknown-domain chats start without an AutoTask ticket and fall into a degraded recovery path (late ticket creation at close, or an engineer-confirmed close-without-sync). Set it to avoid that path entirely. Note 0 is a valid AutoTask company ID — AVA treats "unset" and "0" differently, so an explicit 0 does enable the catchall. Find a company's ID with npx tsx scripts/test-autotask.ts <domain>.
Microsoft Entra ID
| Variable | Type | Default | Req. | Effect |
|---|---|---|---|---|
AZURE_AD_CLIENT_ID | GUID | — | Required | App registration client ID. |
AZURE_AD_CLIENT_SECRET | string | — | Required | App registration client secret. |
AZURE_AD_TENANT_ID | GUID | — | Required | Tenant ID (forms the MSAL authority URL). |
AZURE_AD_REDIRECT_URI | URL | http://localhost:3000/api/auth/callback | Recommended | OAuth callback; must match the app registration exactly. In production set it to https://<your-chat-domain>/api/auth/callback. |
ENGINEERING_GROUP_ID | GUID | — | Required | Security-group object ID; sign-in requires membership (fail-closed if the groups claim is missing or malformed). |
AI / LLM
AVA's AI layer (triage, similar-resolution suggestions, trend summaries, field summarization, the engineer /draft / /rewrite / /summarize commands, and the close-flow ticket summary) is provider-agnostic: it runs on the Vercel AI SDK behind two role-based model slots, LLM_FAST and LLM_QUALITY, supporting Anthropic (default), OpenAI, Google Gemini, and OpenRouter.
The full reference — provider matrix, provider/model string format, conditional key requirements, which features use which tier, and the timeout caveat — lives in ai-providers.md. Quick summary:
| Variable | Default | Notes |
|---|---|---|
LLM_FAST | anthropic/claude-haiku-4-5-20251001 | Triage, suggestions, trends, field summarization, engineer AI commands. |
LLM_QUALITY | anthropic/claude-sonnet-4-6 | Final ticket summary. |
ANTHROPIC_API_KEY / OPENAI_API_KEY / GOOGLE_GENERATIVE_AI_API_KEY / OPENROUTER_API_KEY | — | Only the key(s) for the provider(s) you actually reference are required. |
ANTHROPIC_TIMEOUT_MS | 15000 | Per-call AI timeout — applies to all providers despite the name. |
Engineer AI cost guard (v1.8.0)
The engineer-console AI commands (/draft, /rewrite, /summarize, the wrap-up Summarize and "Draft all" buttons, similar resolutions and trends) share an in-memory budget. Client-side AI triage is not counted (it is rate-limited per IP instead).
| Variable | Type | Default | Effect |
|---|---|---|---|
AI_MAX_CALLS_PER_SESSION | int ≥ 0 | 30 | AI calls allowed per chat. 0 disables the per-chat AI commands (they are greyed out). |
AI_MAX_CALLS_PER_ENGINEER_PER_HOUR | int ≥ 0 | 60 | AI calls per engineer in a fixed one-hour window. 0 disables all engineer AI; the AI commands are greyed out. |
Only one call per command per chat runs at a time (a double-click is refused). Counters reset when the container restarts. Each call writes one [ai-usage] log line with ids, model, token counts, latency and outcome — never the prompt or the output — so you can track spend from your logs. With the defaults and a Haiku-class fast model (~6k input + 350 output tokens per call), the worst case is roughly $0.25 per chat and $0.50 per engineer-hour. Invalid values log a warning and use the default.
Application & networking
| Variable | Type | Default | Req. | Effect |
|---|---|---|---|---|
NEXT_PUBLIC_APP_URL | URL | (build arg) | Required | Public app URL. Pins the Socket.io CORS origin (server hard-fails in production if unset) and is baked into the client bundle. Build-time. |
NEXT_PUBLIC_SOCKET_URL | URL (wss://) | (build arg) | Required | WebSocket URL the client connects to. Build-time. |
NEXT_PUBLIC_RECAPTCHA_SITE_KEY | string | (build arg) | Required | reCAPTCHA v3 site key (public by design). Build-time. |
NODE_ENV | production|development | production (image) | Optional | Standard Node environment. The image sets production. |
PORT | int | 3000 | Optional | Listen port. |
DATABASE_URL | libsql/SQLite URL | a SQLite file in the repo root (local) / on the data volume under /app/data/ (image) | Optional | SQLite database location. The image points it at the data volume; override only if you relocate the DB. |
Trusted proxy — NGINX_TRUSTED_PROXY
NGINX_TRUSTED_PROXY| Variable | Type | Default | Req. | Effect |
|---|---|---|---|---|
NGINX_TRUSTED_PROXY | 1 | (unset — never trust) | Set 1 behind your proxy | Whether AVA trusts X-Forwarded-For / X-Real-IP when determining the real client IP for per-IP rate limiting. |
Trust is opt-in only. Set it to exactly 1 when — and only when — the app always sits behind your own reverse proxy (as in the provided compose + nginx templates, where nginx sets the header from the actual connection address). Any other value — including unset, and even with NODE_ENV=production — means the header is ignored. When multiple comma-separated hops are present, AVA uses the entry appended by the proxy nearest to it.
If you forget it behind a proxy: AVA can't tell visitors apart, so every visitor shares one set of per-IP limits (chat starts, uploads) and clients start hitting "too many sessions" together. Behind the template nginx, always set NGINX_TRUSTED_PROXY=1.
Bot protection & email
| Variable | Type | Default | Req. | Effect |
|---|---|---|---|---|
RECAPTCHA_SECRET_KEY | string | — | Required | reCAPTCHA v3 server-side secret (siteverify). |
RECAPTCHA_DEV_BYPASS | true | (unset — fail closed) | Optional (dev only) | See below. Refused in production. |
SMTP2GO_HOST | host | mail.smtp2go.com | Optional | SMTP2GO host. |
SMTP2GO_PORT | int | 2525 | Optional | SMTP2GO port. |
SMTP2GO_USER | string | — | Required | SMTP2GO username. |
SMTP2GO_PASSWORD | string | — | Required | SMTP2GO password. |
SMTP2GO_FROM | (none — fail closed) | Set it | Sender address on all outbound mail. No vendor default: when unset, every email-sending route fails closed — the send is skipped and the route returns an error rather than mailing from a placeholder address. Set it to an address on your own domain. | |
WEEKLY_REPORT_TOKEN | string | (unset) | Optional | Bearer token guarding the weekly-survey-report cron endpoint. Generate with openssl rand -hex 32; required only if you wire up that cron. |
Recipient addresses
| Variable | Type | Default | Req. | Effect |
|---|---|---|---|---|
SUPPORT_FALLBACK_EMAIL | (none) | Required to boot | Recipient for the after-hours "email a ticket" fallback (listed in Required to boot). | |
FEEDBACK_EMAIL | (none — fail closed) | Optional | Recipient for engineer feedback. When unset, the feedback route fails closed: it skips the send and logs an error — it never falls back to a vendor address. | |
REPORTS_EMAIL | (none — fail closed) | Optional | Recipient for the weekly survey report. Same fail-closed contract as FEEDBACK_EMAIL. |
reCAPTCHA verification behavior
reCAPTCHA verification is fail-closed by default everywhere: a missing secret, a missing token, a network error reaching Google, or a low score all reject the request.
RECAPTCHA_DEV_BYPASS is the one narrow escape hatch, for development and test environments only. It fails open (allows the request) when — and only when — both hold:
NODE_ENVis notproduction, andRECAPTCHA_DEV_BYPASSis set to exactlytrue(not1, notyes).
In production it is refused outright — when NODE_ENV=production, no value of this variable can re-enable the bypass; verification always fails closed. Leave it unset everywhere except a local dev box that has no real reCAPTCHA key. The siteverify call also enforces an outbound timeout (RECAPTCHA_TIMEOUT_MS, below) so a hung Google endpoint cannot stall the request.
File uploads
Chat participants (client and engineer) can attach files to a session. The constraints, all enforced server-side:
- Per-file size cap: 5 MB (fixed). Larger files are rejected with HTTP
413. - Type allowlist: PNG, JPEG, GIF, HEIC, PDF, XLSX, DOCX, EML, ZIP, TXT, CSV.
- Content check: a file's content must match the type it claims. Files are stored under generated names; images display inline, and every other type downloads as an attachment.
- Access: clients can only open their own chat's attachments; an active chat's attachments are visible to the engineer handling it, and a closed chat's attachments to any signed-in engineer (Archives).
Upload budget, quota & retention
All optional; defaults shown. Invalid values warn once and fall back to the default.
| Variable | Type | Default | Effect |
|---|---|---|---|
UPLOAD_MAX_PER_IP_PER_HOUR | positive int | 30 | Per-IP upload budget per hour (HTTP 429 when exceeded). |
UPLOAD_TOTAL_MAX_BYTES | positive number (bytes) | 2147483648 (2 GB) | Disk ceiling across all sessions' uploads combined. A new upload that would exceed it is rejected with HTTP 507. |
UPLOAD_RETENTION_ENABLED | bool | true | Scheduled retention job that deletes a closed session's upload directory once it ages past the window below — regardless of whether the files made it into AutoTask. Set exactly false to disable. |
UPLOAD_RETENTION_DAYS | positive int | 30 | Retention window in days. |
UPLOAD_RETENTION_INTERVAL_HOURS | positive number (may be fractional) | 6 | How often the retention job runs (in-process interval — no external cron needed). |
Business hours
All optional; the defaults reproduce Mon–Fri 6 AM–6 PM Pacific. Invalid values validate-and-fall-back (they never throw). The configured schedule also drives the client-facing availability copy — the off-hours message quotes your actual hours, not a hardcoded string.
| Variable | Type | Default | Effect |
|---|---|---|---|
BUSINESS_HOURS_BYPASS | bool | false | Exactly true ignores the schedule entirely (chat always "open"). Testing only — set false for real clients. |
BUSINESS_HOURS_TZ | IANA TZ | America/Los_Angeles | Timezone the open/close hours are evaluated in. Invalid zone names warn once and fall back. |
BUSINESS_HOURS_OPEN | int 0–23 | 6 | Opening hour (local to BUSINESS_HOURS_TZ). |
BUSINESS_HOURS_CLOSE | int 0–23 | 18 | Closing hour. |
BUSINESS_HOURS_DAYS | CSV of 1–7 (ISO, 1=Mon) | 1,2,3,4,5 | Open days. |
White-label / branding
AVA ships vendor-neutral: with none of these set, the app presents as a generic "Your Company" deployment with no phone number and no client-portal link. Every deploying MSP sets its identity here. All values are read at call time — set them in your runtime env file and restart; no rebuild. See white-label.md for the full walkthrough, worked example, and verification checklist.
| Variable | Type | Default | Effect |
|---|---|---|---|
COMPANY_NAME | string | Your Company | Full display name across the UI (page metadata, contact form, dashboard). |
COMPANY_DESCRIPTOR | string | IT Support | Descriptor appended after the company name in prose — the page metadata description and the AI summary prompt (<COMPANY_NAME> <COMPANY_DESCRIPTOR>). |
COMPANY_MONOGRAM | string (≤4 chars) | AVA | Dashboard rail tile monogram (silently truncated to 4 characters). |
COMPANY_TAGLINE | string | Powered by AVA | Footer identity line on the client page and dashboard. |
WELCOME_MESSAGE | string | (neutral greeting — see below) | System greeting posted at session start. Default: "Welcome to Tech Support! Your session has been created and an engineer will be with you shortly. …" including the recorded-conversation and no-passwords notices. |
SUPPORT_PHONE | string | (unset — hidden) | Displayed support phone. When unset or empty, every phone element (banner, contact form, off-hours copy, timeout overlay) is hidden and prose adjusts. When set, a tel: href is derived automatically (bare 10-digit numbers get +1; +-prefixed international numbers pass through). |
AT_CLIENT_PORTAL_URL | URL | (unset — plain ticket number) | Base URL for client-portal ticket links. When unset or empty, the UI shows a plain ticket number instead of a link. |
LOGO_PATH | public path | /ava-logo.svg | Public path of the brand logo shown on the client page and dashboard mobile bar. An empty value falls back to the default. The file must exist under public/ in the built image. |
DISPLAY_TZ | IANA TZ | → BUSINESS_HOURS_TZ → America/Los_Angeles | Timezone for all human-facing timestamps (message times, day dividers, transcript, wrap-up/billing draft). Invalid zones warn once and fall back. |
COMPANY_PROFILE | string | a managed service provider (MSP) supporting business clients | Clientele description injected into the AI triage prompt to sharpen classification (e.g. an MSP serving medical and dental practices). |
Recipient/sender addresses (SUPPORT_FALLBACK_EMAIL, FEEDBACK_EMAIL, REPORTS_EMAIL, SMTP2GO_FROM) are part of the same identity surface but are documented under Bot protection & email since they carry required/fail-closed semantics.
Session limits
| Variable | Type | Default | Effect |
|---|---|---|---|
MAX_ACTIVE_SESSIONS | int 1–10 | 2 | Per-engineer concurrent active-chat cap. Out-of-range/invalid values warn and fall back to 2. |
External-call timeouts
All optional; defaults shown. Milliseconds.
| Variable | Type | Default | Effect |
|---|---|---|---|
AUTOTASK_TIMEOUT_MS | int (ms) | 15000 | Per-request AutoTask REST timeout. |
SMTP_TIMEOUT_MS | int (ms) | 10000 | SMTP connect + socket timeout. |
RECAPTCHA_TIMEOUT_MS | int (ms) | 10000 | reCAPTCHA siteverify fetch timeout. |
ANTHROPIC_TIMEOUT_MS | int (ms) | 15000 | AI per-call timeout (all providers) — see ai-providers.md. |
NinjaOne (optional, read-only device glance)
Off by default. With NINJAONE_BASE_URL / NINJAONE_CLIENT_ID / NINJAONE_CLIENT_SECRET unset, the feature is completely dormant — the engineer dashboard's client-context panel behaves byte-for-byte as if it didn't exist (no Device section renders, no NinjaOne network calls are ever made). Set all three to turn it on.
What it does. When a client starts a chat, AVA tries to identify their machine and shows the engineer a read-only "device at a glance" card in the chat's context panel — online/offline status, last boot, disk free, pending/failed OS patches, antivirus state, public/private IP, make/model/RAM, last logged-on user, and recent alerts — plus a one-click "Open in NinjaOne" deep link to the device's dashboard page. It never modifies anything: the NinjaOne API credential is scoped to monitoring only, and the NinjaOne client exposes GET requests exclusively. Two resolution paths work independently of each other:
- AutoTask-only baseline (no NinjaOne credentials needed). If your AutoTask instance already syncs NinjaOne data onto Configuration Items (the
NinjaRMM-RemoteUrl/-BackgroundUrl/-Antivirus/-PrivateIpAddress/-DeviceUIDUDFs), AVA matches the chat's AutoTask company + reported hostname to a Configuration Item and shows those UDF facts plus the deep link parsed out of the remote-URL UDF — even withNINJAONE_*fully unset. - Live NinjaOne API layer (optional, adds live telemetry). When the three vars above are set, AVA additionally queries the NinjaOne API directly for live device data and can fall back to a hostname/user search when no Configuration Item matched.
Create the API client (NinjaOne admin)
- In NinjaOne: Administration → Apps → API → Client App IDs → Add.
- Platform: API Services (machine-to-machine).
- Scope: Monitoring only — do not grant
managementor any write scope. This is a least-privilege design choice, not just a convention: the resulting credential cannot modify a device, run a script, or trigger an action at all. - Grant type: Client Credentials. No redirect URI is needed (this is a server-to-server, not an interactive OAuth flow).
- Copy the generated Client ID and Client Secret into your env (below).
Env vars
| Variable | Type | Default | Req. | Effect |
|---|---|---|---|---|
NINJAONE_BASE_URL | URL | (unset) | Required to enable | Your NinjaOne region's API base URL — see Region base URLs below. Trailing slash optional. |
NINJAONE_CLIENT_ID | string | (unset) | Required to enable | The Client App ID from the step above. |
NINJAONE_CLIENT_SECRET | string | (unset) | Required to enable | The Client App secret. Quote it in your env file — read via the same raw-file mechanism as AUTOTASK_API_SECRET, so a value containing $ or other shell-special characters is safe. |
NINJAONE_TIMEOUT_MS | int (ms) | 15000 | Optional | Per-request timeout for NinjaOne API calls. |
NINJAONE_ENRICH_TICKETS | bool | false (unset) | Optional | Sole enable switch for AT ticket writes — attaches the resolved Configuration Item (configurationItemID) and, when available, the NinjaRMM-DeviceUID UDF, on any chat that resolves a device at high confidence. Read-only device glance itself is unaffected either way — this only gates the best-effort write-back onto your AutoTask ticket. Do not set this to true until you have run the UDF merge pre-flight against your own AutoTask instance — see below. |
All three required vars must be set together — the feature is all-or-nothing. NINJAONE_ENRICH_TICKETS is independent of those three and defaults to off even when they're set.
UDF merge pre-flight (before enabling NINJAONE_ENRICH_TICKETS)
NINJAONE_ENRICH_TICKETS)AT's PATCH /Tickets handling of the userDefinedFields array is instance-dependent: some configurations merge the array, others could blank UDFs the PATCH omits. Before flipping the flag on, verify your instance merges:
- Create a throwaway scrap ticket in AutoTask.
- Seed two different UDFs on it with known values.
- PATCH the ticket updating only one of the two UDFs.
- Confirm the other UDF survives untouched.
Only enable NINJAONE_ENRICH_TICKETS=true after step 4 passes. Another instance's pre-flight result does not transfer to yours.
Region base URLs
Use the same NinjaOne data-center region your admin console login URL shows:
| Region | NINJAONE_BASE_URL |
|---|---|
| Americas (default/US) | https://app.ninjarmm.com |
| Americas 2 | https://us2.ninjarmm.com |
| Canada | https://ca.ninjarmm.com |
| Europe | https://eu.ninjarmm.com |
| Oceania | https://oc.ninjarmm.com |
Systray URL setup — auto-identifying the client's machine
AVA resolves "which machine is this?" most reliably when the client reaches the chat page via a link that carries systray-substituted hints. NinjaOne's systray custom URL items support ${HOSTNAME} / $USERNAME / ${DOMAIN} placeholders, substituted at click time. In your NinjaOne branding/systray policy, set the chat link to:
https://<your-chat-domain>/?host=${HOSTNAME}&user=$USERNAME&domain=${DOMAIN}
AVA captures these once on page load, forwards them with the chat-start request, and stores them on the session as display + lookup hints only — never an authorization signal. Omitting the query string entirely still works; the device card simply has less to search on (falls back to a user/email-based search when NinjaOne is configured, or shows a "no managed device matched" empty state otherwise).
Validate your setup
npx tsx scripts/test-ninjaone.ts [hostname]Confirms config presence, exercises the real OAuth2 client-credentials token exchange, lists organizations and a few devices, and — with a hostname argument — runs the same hostname search the device-resolution pipeline uses.
Honest limitations
- The "Open in NinjaOne" deep link requires the engineer to already be logged into NinjaOne in their browser — it is a console URL, not a standalone remote-session launcher.
- No remote-session launch, no reboot/script actions — this is a strictly read-only "glance" feature. Any action capability is a separate, not-yet-built tier.
- Systray hints are client-reported, exactly like the browser user agent — useful for lookup, never trusted for identity/authorization.
- Live telemetry values (disk, patches, AV, alerts) reflect NinjaOne's own last-check-in data for that device, not a real-time poll triggered by the chat.
See also
quickstart.md— the ordered bring-up that sets these.ai-providers.md— the full AI provider & model reference.white-label.md— branding your deployment.requirements.md— accounts and prerequisites.engineer-console.md— what the engineer-console variables above look like to your engineers.
Updated 15 days ago