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 techchat-session JWT (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 JWT and client session token; a short value is a brute-forceable
auth bypass. - Placeholder-looking values are refused (anything containing
changeme,
secret,password,placeholder, and similar, case-insensitive) —
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 ofAUTOTASK_PRIORITY_P1..P4
resolves 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 with
npx tsx scripts/discover-picklists.tsand set all four.
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 — reference-instance value) | 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 is only correct for the reference AutoTask instance. Your
instance's role IDs are different, 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, 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. |
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. |
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 | file:./techchat.db (code) / file:/app/data/techchat.db (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 overwrites the header from the actual connection
address). Any other value — including unset, and even with
NODE_ENV=production — means the header is never trusted: a naked,
un-proxied deployment must not let a client spoof its IP simply by sending the
header itself. When multiple comma-separated hops are present, only the
right-most (proxy-appended) entry is used — never the attacker-controlled
left-most one.
Operational consequence of forgetting it: without trust, every request's IP
resolves to "unknown", so all per-IP rate limits (chat starts, uploads)
collapse into one shared bucket for all visitors. 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). Oversized bodies are rejected from the
declaredContent-Lengthbefore the request body is buffered (HTTP
413), with the post-parse size check retained as defense-in-depth. - Type allowlist: PNG, JPEG, GIF, HEIC, PDF, XLSX, DOCX, EML, ZIP, TXT,
CSV. Checked by MIME type, with an extension fallback for types browsers
often mislabel (.eml,.heic,.zip,.txt,.csv). - Magic-byte content validation: for every type with a reliable signature,
the actual file bytes must match the declared type — a file claiming to be a
PNG must actually start like a PNG. Files are stored under
collision-proof UUID-prefixed names, and served withX-Content-Type-Options: nosniffand a fixed content-type map; only trueimage/*types render
inline (everything else downloads as an attachment). - Access control: clients can only read their own session's attachments
(session-token auth); an active assigned session's attachments are
owner-engineer-only, while closed sessions' attachments are readable by any
authenticated engineer (archives semantics).
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, enforced before auth/parsing (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 is physically incapable of modifying a device, running
a script, or triggering an action, no matter what a bug in AVA did. - 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.
Demo mode
.env.example also lists a DEMO_* block (DEMO_MODE plus lead-capture SMTP
settings). These exist solely for running a public, self-guided product demo
against in-memory fakes — never set any of them in a production
deployment. With DEMO_MODE unset (the default), none of the demo code
paths can activate.
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.
Updated about 1 hour ago