Quickstart

Bring AVA up on your own AutoTask instance, from clone to the app running
locally with real AutoTask, Entra ID, and AI wiring. Every command is
copy-pasteable; every environment variable name is exact.

Work through requirements.md first — you'll need the
AutoTask API user, the Entra app registration + security group, reCAPTCHA v3
keys, and an SMTP2GO account in hand.

Placeholders like <your-host> are yours to fill in. Shell examples assume
Linux/macOS. When you're done here, continue to
production-deployment.md for the production
stack and first-login.md for engineer onboarding and the
end-to-end smoke test.


Step 1 — Clone and install

git clone <clone URL provided with your license> ava-chat
cd ava-chat
npm install

Step 2 — Create your env file from the template

cp .env.example .env.local

You will fill this in across the next steps. .env.local is gitignored and is
never baked into the Docker image — keep it out of version control.
configuration.md documents every variable; this
quickstart sets the minimum to boot plus the AutoTask picklists.

Secret with a $: the AutoTask secret often contains $. Wrap it in
double quotes in .env.local, e.g. AUTOTASK_API_SECRET="ab$cd$ef".

Step 3 — AutoTask API user + zone discovery

Set the four AutoTask credential variables in .env.local:

[email protected]
AUTOTASK_API_SECRET="your-api-secret"
AUTOTASK_API_INTEGRATION_CODE=your-integration-code
AUTOTASK_API_ZONE_URL=            # discovered next

AutoTask assigns each account to a data-center "zone". Discover your REST base
URL by querying the (unauthenticated) zone-information endpoint with your API
user's email:

curl "https://webservices.autotask.net/atservicesrest/v1.0/[email protected]"

The response's url field is your zone base (e.g.
https://webservices5.autotask.net/ATServicesRest/V1.0). Put it in
AUTOTASK_API_ZONE_URL. AVA normalizes this value — a trailing slash is fine,
and it appends /V1.0 if you omit it — so either of these works:

AUTOTASK_API_ZONE_URL=https://webservices5.autotask.net/ATServicesRest/V1.0
AUTOTASK_API_ZONE_URL=https://webservices5.autotask.net/ATServicesRest

Verify connectivity (Test 1 checks the zone URL + credentials; the optional
domain/email arguments run company + contact lookups):

npx tsx scripts/test-autotask.ts
# or, exercising the domain→company and email→contact matchers:
npx tsx scripts/test-autotask.ts aclientdomain.com [email protected]

A PASS on Test 1 confirms the credentials, integration code, and zone URL are
correct. A 401 means the credential/integration-code/user triple is wrong (or
the API user is locked); a 403 means the security level is missing an entity.

Step 4 — Discover your picklist IDs

AutoTask picklist IDs are instance-specific. List yours:

npx tsx scripts/discover-picklists.ts

This prints values for status, priority, queueID, source, ticketType,
ticketCategory, issueType, and subIssueType, marking each [DEFAULT] /
[INACTIVE] / [SYSTEM]. Map them into .env.local:

AUTOTASK_DEFAULT_TICKET_STATUS=1          # your "New" status id
AUTOTASK_DEFAULT_TICKET_PRIORITY=3        # your "Normal" priority id
AUTOTASK_DEFAULT_TICKET_QUEUE_ID=         # your helpdesk queue id
AUTOTASK_DEFAULT_TICKET_SOURCE=           # your "Chat" source id (create it in AT if needed)
AUTOTASK_DEFAULT_TICKET_TYPE=             # ticket type for chat-originated tickets

# Triage priority (P1-P4) -> your AT priority picklist ids. MUST discover and
# set all four: if a Pn is left empty, it falls back to the single shared
# AUTOTASK_DEFAULT_TICKET_PRIORITY above, so ALL AI-triaged chats file at the
# same AT priority regardless of severity.
AUTOTASK_PRIORITY_P1=
AUTOTASK_PRIORITY_P2=
AUTOTASK_PRIORITY_P3=
AUTOTASK_PRIORITY_P4=

# Client-facing note publish value — per-instance picklist; 1 = "Internal Only"
# is universally valid. Only change if your instance defines an external value.
AUTOTASK_NOTE_PUBLISH_EXTERNAL=1

# Resource role id for engineer time entries on close — SET THIS for your
# instance (unset warns at runtime and bills against a fallback role id that
# will not match your instance).
AUTOTASK_DEFAULT_ROLE_ID=

The note-publish and role-id values are the two easiest to get wrong.
AUTOTASK_NOTE_PUBLISH_EXTERNAL must be a publish value your instance
actually defines (a nonexistent value makes the close-note post fail with a
500). AUTOTASK_DEFAULT_ROLE_ID warns-not-fails when unset, so a missing
value boots fine but bills against the wrong role — set it deliberately.

Step 5 — Catchall company

Pick (or create) an AutoTask company to receive tickets from unknown/personal
email domains, and set its numeric ID. To find a company's ID, use the
connectivity script's domain lookup:

npx tsx scripts/test-autotask.ts yourdomain.com
# prints:  - <Company Name> (ID: 1234, web: ...)
AUTOTASK_CATCHALL_COMPANY_ID=1234

Leaving it unset lets unknown-domain chats start without a ticket (they
recover at close), so setting it is strongly recommended.

Step 6 — Microsoft Entra ID

From your app registration and security group (see
requirements.md §2), set:

AZURE_AD_CLIENT_ID=your-app-client-id
AZURE_AD_CLIENT_SECRET=your-app-client-secret
AZURE_AD_TENANT_ID=your-tenant-id
AZURE_AD_REDIRECT_URI=https://<your-host>/api/auth/callback
ENGINEERING_GROUP_ID=your-security-group-object-id

Confirm two things in the Entra portal:

  1. The redirect URI on the app registration matches AZURE_AD_REDIRECT_URI
    exactly (scheme, host, path).
  2. Token configuration → Add groups claim (Security groups) is enabled for
    the ID token, so the groups claim is emitted. Without it, every
    sign-in is denied with access_denied (fail-closed by design).

Step 7 — reCAPTCHA, session secret, email, AI key

# reCAPTCHA v3
NEXT_PUBLIC_RECAPTCHA_SITE_KEY=your-recaptcha-site-key
RECAPTCHA_SECRET_KEY=your-recaptcha-secret-key

# Session signing — generate a strong random string (>=32 chars is ENFORCED
# at container start; placeholder-looking values are rejected):
#   openssl rand -base64 48
NEXTAUTH_SECRET=your-generated-random-string

# SMTP2GO
SMTP2GO_HOST=mail.smtp2go.com
SMTP2GO_PORT=2525
SMTP2GO_USER=your-smtp2go-user
SMTP2GO_PASSWORD=your-smtp2go-password
[email protected]   # no default — unset means every send fails closed

# Where the after-hours email-a-ticket form delivers. REQUIRED — the
# container refuses to start without it (no vendor default).
[email protected]

# AI provider key — the defaults use Anthropic, so ANTHROPIC_API_KEY is
# required unless you change LLM_FAST / LLM_QUALITY to another provider
# (see configuration.md for the provider matrix).
ANTHROPIC_API_KEY=your-anthropic-api-key

# Public URLs (used by the app and, for the container build, baked into the
# client bundle at build time).
NEXT_PUBLIC_APP_URL=https://<your-host>
NEXT_PUBLIC_SOCKET_URL=wss://<your-host>

Set your identity/branding and business hours now if you like — with nothing
set, AVA presents a neutral "Your Company" identity, Mon–Fri 6am–6pm Pacific
business hours, and no support phone or portal link. Overriding all of it is
covered in configuration.md and
white-label.md.

Step 8 — Initialize the database and run

Fastest path — local process (recommended for the first run)

The custom server loads .env.local directly, so no container is needed to
verify your AutoTask/Entra/AI wiring:

npx prisma generate
npx prisma db push          # creates the SQLite schema
npm run dev                 # custom server + Socket.io

Open http://localhost:3000. (For a local run, set NEXT_PUBLIC_APP_URL /
NEXT_PUBLIC_SOCKET_URL to http://localhost:3000 / ws://localhost:3000,
and temporarily set BUSINESS_HOURS_BYPASS=true if you're testing outside
your configured hours.)

Container path

The image never contains your secrets — .env.local is mounted at runtime as
/app/.env.local, and docker-entrypoint.sh validates the required variables
before the server starts. The NEXT_PUBLIC_* values are baked at build
time
, so pass them as build args:

docker build \
  --build-arg NEXT_PUBLIC_APP_URL=https://<your-host> \
  --build-arg NEXT_PUBLIC_SOCKET_URL=wss://<your-host> \
  --build-arg NEXT_PUBLIC_RECAPTCHA_SITE_KEY=your-recaptcha-site-key \
  -t ava:latest .

# Run just the app container, mounting your env file and the data volumes:
docker run -d --name ava \
  -p 3000:3000 \
  -v ava-data:/app/data \
  -v ava-uploads:/app/uploads \
  -v "$(pwd)/.env.local:/app/.env.local:ro" \
  ava:latest

docker logs -f ava   # expect "Environment validation passed." then "Syncing database schema..."

If a required variable is missing, the entrypoint prints
ERROR: Missing required environment variables in /app/.env.local: … and
exits — fix .env.local and re-run.

Your app is up locally. For the public production stack — nginx
terminating TLS with Let's Encrypt auto-renewal, the hardened container
settings, and the build-before-swap upgrade flow — continue to
production-deployment.md. Then walk
first-login.md to onboard your first engineer and run
the end-to-end smoke test that proves the AutoTask round-trip.


Boot-requirement checklist (what the container validates)

docker-entrypoint.sh refuses to start unless every one of these is present in
the mounted env file. The steps above set each — this is the cross-check:

Required variableSet in stepPurpose
NEXTAUTH_SECRET7Session-token signing (also checked for ≥ 32 chars, non-placeholder)
AUTOTASK_API_USER3AutoTask auth
AUTOTASK_API_SECRET3AutoTask auth
AUTOTASK_API_INTEGRATION_CODE3AutoTask auth
AUTOTASK_API_ZONE_URL3AutoTask REST base URL
AZURE_AD_CLIENT_ID6Entra app registration
AZURE_AD_CLIENT_SECRET6Entra app registration
AZURE_AD_TENANT_ID6Entra tenant
ENGINEERING_GROUP_ID6Engineer authorization (groups claim)
RECAPTCHA_SECRET_KEY7reCAPTCHA server verification
SMTP2GO_USER7Outbound email
SMTP2GO_PASSWORD7Outbound email
SUPPORT_FALLBACK_EMAIL7After-hours email-form recipient (no vendor default)
ANTHROPIC_API_KEY*7AI provider key for the configured provider

* The entrypoint requires the key for whichever provider(s) LLM_FAST /
LLM_QUALITY reference. Both default to Anthropic, so ANTHROPIC_API_KEY is
required out of the box; if you point a tier at openai/*, google/*, or
openrouter/*, that provider's key becomes the required one instead (see
configuration.md).

Everything else has a safe default or is optional. The NEXT_PUBLIC_* values
are validated by being present at build time, not by the entrypoint.


Did this page help you?