Server Setup

Indx is a self-hosted server that wraps the Indx search engine and exposes it over HTTP. It has three interfaces:

  • Web UI — a Blazor dashboard where you can register users, upload and configure datasets, preview search results, manage teams, and check engine health. No code required.
  • REST API — the full HTTP API, used from any language or framework. All operations available in the UI are also available programmatically.
  • Monitor — a live view of the running instance: what each dataset is doing, what the process is using, and what just happened. In the console under Admin → Monitor, and in the terminal you started the server from. See Monitor.

Swagger UI is available at /swagger for interactive API exploration.


Quick Start

bash
git clone https://github.com/indxSearch/Indx
cd Indx
dotnet run

Requires .NET 10 SDK. The server starts at https://localhost:5001.


Register a User

  1. Open https://localhost:5001/Account/Register in a browser
  2. Enter your email and password
  3. Submit — you're now logged in as the first user

By default, registration is open. See Registration Control below to restrict who can sign up before deploying to production.


Get an API Key

To call the REST API you need an API key.

  1. Log in at https://localhost:5001/Account/Login
  2. Go to Account → API keys (https://localhost:5001/account/api-key) and click New API key
  3. Choose the team, the access level and, optionally, the datasets the key may reach:
    • Search only — for websites and apps. The key is visible in the browser, and this is all it can do.
    • Read only — for exports, reporting and agents. Keep it on a server.
    • Full access — for your own servers and pipelines: everything your team role allows.
  4. Choose how long it lasts (30, 90, 180 or 365 days), click Create key and copy it — it is shown only once (Search only and Read only keys can be shown again from the list)

A key never exceeds your role in the team, and cannot be changed later: create a new one and revoke the old. See Authentication for what each level can call.

For a production website or a shared server, use a team key. A team Admin creates it under Manage team → API keys. It belongs to the team, so it keeps working when the person who created it leaves, and every Admin of the team can see and revoke it. A team Search only key can be set to never expire. See Personal keys and team keys.

Send the key as a Bearer token on every request:

bash
curl -H "Authorization: Bearer <token>" https://localhost:5001/api/...

What You Can Do in the UI

FeatureDescription
Upload data
Stream a JSON file into a dataset
Configure fields
Set searchable, filterable, facetable, sortable, and weight for each field
Index
Trigger an index build and monitor progress
Categories
A catalogue's hierarchy as a tree: every category with its product count, the chosen one with its products, a step to boost it. Read from a path field such as product_type or from fields that each hold a level; see the how-to
Search preview
Run searches directly in the browser and inspect results
Statistics
Searches, clicks and conversions for the last 7, 30 or 90 days: when people search, where from, which result they choose, which languages, how they type, and a profile of the window against the one before; top queries, fuzzy finds, top documents, browsing and the dataset's own changes
Health & status
See engine state, document count, index time, and whether a shadow build is in progress
Teams
Manage team membership and roles (Admin / Editor / Viewer)
Transfer dataset
Move a dataset to another team

Teams

Indx organises access around teams. A team owns datasets, and every member of the team has the same role on all of the team's datasets. A user can belong to multiple teams, with a different role on each. Membership is managed from the account portal's team pages.

Roles

RoleCan searchCan modify data & fieldsCan delete / transfer
Admin
✓
✓
✓
Editor
✓
✓
—
Viewer
✓
—
—

Inviting people

Invite a colleague straight into a team: in the team's settings, choose Invite member, enter their email address and pick a role.

  • If they already have an account on the server, they are added to the team at once.
  • If they don't, they get an email with a link to register. When they register, they join the team in that role, and no team of their own is made for them. They can create one later with New team.

An invitation lasts 14 days. Pending invitations are listed in the team's settings, where you can send one again (which gives it another 14 days) or revoke it.

Team editors can add people who already have an account. Inviting someone new takes a team admin. A server admin can turn that off under Admin, Settings, Registration, and then only server admins can invite new people. Server admins can also invite from Admin, Users, to any team or to the server alone, in which case the new user gets a team of their own.

An invited address can register whatever the registration mode is, even when registration is closed. The mode decides who may register without an invitation.

Transferring a dataset between teams

An Admin of both the source and target team can move a dataset:

bash
curl -X POST https://localhost:5001/api/teams/acme/datasets/products/transfer \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{ "targetTeamName": "research" }'

Deploy to Azure App Service

  1. Create an App Service with Windows OS and .NET 10 runtime
  2. Publish with dotnet publish -c Release and deploy
  3. Set environment variables (use __ as the section separator):
    • Jwt__Key — required
    • Registration__Mode — recommended: EmailDomain or Closed
  4. Configure CORS with your frontend origins
  5. Place the .license file in D:\home\data\ for unlimited documents

Production Checklist

  1. Change the JWT signing key
  2. Set registration mode
  3. Configure CORS
  4. Set up an email provider if you want email confirmation
  5. Place the license file
  6. Set up OAuth if needed

Monitor

A live view of the instance: what each dataset is doing right now, what the process is using, and what just happened. Two ways in, reading the same thing.

In the console, under Admin → Monitor: the event stream and the instance figures, the process's CPU and memory against what the machine allows, the native memory pool, searches a minute and every dataset's state, with no terminal needed. This is the one to use on a hosted deployment. It is admin only, because an event names the team whose dataset changed, and the events are held in memory, so they start again when the server restarts.

In the terminal, if you started the server from one, drawn there automatically.

The dataset table gives state, document count, records on disk, time since last use and the idle-eviction countdown, coloured by state to match the chips in the web UI. Below it an event stream carries dataset state changes, idle sweeps and background jobs as they occur.

Ctrl+Q detaches the monitor and leaves the server running. Ctrl+C stops the server as usual, and F10 shuts it down after a confirmation. The divider between the two panes can be dragged, and where you leave it is remembered.

Piping it somewhere else

Azure log stream, docker logs and the systemd journal are pipes rather than terminals, so they cannot show a full-screen application. There the monitor prints a compact status block instead, appended every 30 seconds, with events as they happen. It is off by default, since log volume is usually billed:

json
{
  "Indx": {
    "Monitor": {
      "Enabled": true,
      "StatusSeconds": 30
    }
  }
}

Set Indx__Monitor__Enabled to true in App Service application settings to get the same thing in the portal's log stream.

To turn the monitor off everywhere, set Indx:Monitor:Enabled to false or start with --no-monitor. A startup that fails never draws it, so an error such as a port already in use is printed the way it always was.


Configuration Reference

All settings live in appsettings.json or as environment variables.

JWT Security

A strong signing key is generated on first run and stored in ./IndxData/jwt.key, so a single instance needs nothing. Set Jwt:Key explicitly (Key Vault, user secrets or the Jwt__Key environment variable) when several instances must accept each other's API keys:

bash
dotnet user-secrets set "Jwt:Key" "your-secret-key-minimum-32-characters"

Updates

Once a day the server asks license.indx.co which Indx release is the latest. When it is newer than the one running, every admin gets one notification in the console, and the admin area shows the new version under the running one. An ordinary release is not emailed unless an admin turns that on; a security release is. The request is anonymous: no token, no instance id. Switch it off with Updates__Check=false.

Rate limits

Three limits, all under RateLimits in appsettings.json or as environment variables (RateLimits__Api__RequestsPerSecond and so on). Every one answers 429 with code: rateLimited, retryAfterSeconds and a Retry-After header, so clients need a single handler. Set Enabled: false to switch one off.

SectionDefaultProtectsSettings
Auth
on
Login and password forms from guessing, per client address
PermitLimit 10 attempts per WindowSeconds 60
Api
off
Other users of the instance from one runaway integration; one token bucket per API key
RequestsPerSecond 50, Burst 200
Anon
on
Scanners hammering the API without a valid token, per client address
PermitLimit 30 per WindowSeconds 60
json
{
  "RateLimits": {
    "Auth": { "Enabled": true,  "PermitLimit": 10, "WindowSeconds": 60 },
    "Api":  { "Enabled": false, "RequestsPerSecond": 50, "Burst": 200 },
    "Anon": { "Enabled": true,  "PermitLimit": 30, "WindowSeconds": 60 }
  }
}

Sizing notes:

  • Api is off on self-host because on your own hardware the ceiling is the hardware. Turn it on when several teams share an instance and one of them could monopolise it.
  • Anon covers requests whose bearer token is missing or does not validate, which all end in 401 anyway. Raise it only if a client of yours legitimately polls while unauthenticated; CORS preflight is already exempt.
  • Rejections are logged under IndxServer.RateLimit and counted as indx.ratelimit.rejections, tagged by which limit fired.
  • There is no instance-wide cap on concurrent work. A dataset that is already rebuilding answers 409 rather than queueing another rebuild behind it, and the engine bounds its own searches per dataset.
  • Behind a reverse proxy or Azure App Service, set ASPNETCORE_FORWARDEDHEADERS_ENABLED=true so the per-address limit sees the client's address and not the proxy's; otherwise every caller shares one window.

Registration Control

Controls who can create an account:

json
{
  "Registration": {
    "Mode": "EmailDomain",
    "AllowedDomains": ["yourcompany.com"]
  }
}
ModeBehavior
Open
Anyone can register (default)
EmailDomain
Only emails from the listed domains
Closed
No new registrations

Email Provider

Indx uses Azure Communication Services for email (password reset, confirmation). If not configured, all emails are printed to the console instead — fine for development.

json
{
  "AzureCommunicationServices": {
    "ConnectionString": "endpoint=https://...",
    "SenderAddress": "noreply@yourcompany.com"
  }
}

OAuth (Optional)

Google and Microsoft login are supported. Configure via user-secrets or environment variables:

json
{
  "Authentication": {
    "Google": {
      "ClientId": "...",
      "ClientSecret": "..."
    },
    "Microsoft": {
      "ClientId": "...",
      "ClientSecret": "..."
    }
  }
}

Database

Three SQLite databases are created automatically on first run — no setup required:

  • identity.db — user accounts and authentication
  • indx.db — your documents, field configurations, synonyms and boost rules
  • stats.db — search statistics, clicks and conversions (Statistics:DbFile moves it)

The search index itself lives in memory. Documents are kept in indx.db, so after a restart the server rebuilds every dataset's index on its own, in the background; a search that arrives first loads its dataset on demand. Nothing has to be loaded again by hand.

License

Place the .license file in ./IndxData/ (local) or D:\home\data\ (Azure) to remove the 100,000 document limit. See Licensing.