Configuration
The API is configured through a config.yaml file in the project root. A JSON Schema is available at schemas/config.schema.json for editor autocompletion.
Environment variable interpolation
Section titled “Environment variable interpolation”Reference environment variables anywhere in the config with ${VAR_NAME} syntax:
apiKey: ${API_KEY}indexes: my_index: host: ${ES_HOST} apiKey: ${ES_API_KEY}The server will exit with an error if a referenced variable is not set.
Use ${VAR_NAME:-default} to provide a fallback value when the variable is unset:
port: ${PORT:-3000}Top-level settings
Section titled “Top-level settings”| Setting | Type | Default | Description |
|---|---|---|---|
port | number | 3000 | Server listen port |
apiKey | string? | — | Bearer token for API authentication |
corsOrigins | "*" | string[] | — | Allowed CORS origins. "*" for all, array for specific, omit to disable. |
When apiKey is set (or the API_KEY env var exists), all requests except /health and /openapi* require a Authorization: Bearer <token> header.
Environment variables
Section titled “Environment variables”These environment variables are read directly (not from config.yaml):
| Variable | Description |
|---|---|
API_KEY | Bearer token for API auth (alternative to apiKey in config) |
REDIS_URL | Redis connection URL to enable response caching. See Caching. |
Index configuration
Section titled “Index configuration”Each key under indexes becomes the URL handle for that index’s endpoints.
indexes: collections: engine: elasticsearch host: https://elasticsearch.example.com apiKey: ${ES_API_KEY} indexName: museum_collections| Setting | Type | Required | Description |
|---|---|---|---|
engine | "elasticsearch" | "opensearch" | "meilisearch" | "typesense" | Yes | Search engine backend |
host | string | Yes | Search engine host URL |
apiKey | string? | No | API key (ES apiKey auth, or Meilisearch API key) |
username | string? | No | Basic auth username (ES, OpenSearch) |
password | string? | No | Basic auth password (ES, OpenSearch) |
indexName | string | string[] | Yes | Index name(s). Multi-index arrays are supported by ES/OpenSearch; Meilisearch and Typesense use only the first entry. |
dateFields | string[]? | No | Field names containing dates stored as Unix timestamps. Used by Typesense to convert int64 values back to ISO 8601 strings in responses. |
Engine-specific notes
Section titled “Engine-specific notes”- Supports
apiKeyorusername/passwordauth - Full feature support including histograms, geo grid, and phrase suggestions
- Multi-index search via array
indexName
- Supports
username/passwordauth only (the OpenSearch JS client does not supportapiKeyauth) - Uses the same query DSL as Elasticsearch — full feature support
- Multi-index search via array
indexName
- Uses
apiKeyfor auth - Filters use Meilisearch string syntax (translated automatically)
- Histograms, geo grid, and phrase suggestions are not supported and return empty/default values
- Boosts and
searchableFieldsare managed at the Meilisearch index level, not at query time - Multi-index arrays are not supported — only the first entry is used
- Uses
apiKeyfor auth (set viaX-TYPESENSE-API-KEYheader) - Dates are stored as int64 (Unix timestamps) for sorting/filtering — use
dateFieldsto convert back to ISO strings in responses - Supports boosts via
query_by_weightsandsearchableFieldsviaquery_by - Histograms, geo grid, and phrase suggestions are not supported and return empty/default values
- Multi-index arrays are not supported — only the first entry is used
Multi-index search
Section titled “Multi-index search”Pass an array to indexName to search across multiple Elasticsearch/OpenSearch indexes with a single handle. Results are unified and relevance-ranked. Each hit includes an _index field identifying its source. Meilisearch uses only the first entry in the array.
indexes: all_content: engine: elasticsearch host: https://elasticsearch.example.com indexName: - museum_collections - library_catalogueIndex defaults
Section titled “Index defaults”Optional defaults applied when query parameters are omitted:
indexes: collections: # ... defaults: perPage: 20 facets: - category - period highlight: true suggestField: title| Setting | Type | Description |
|---|---|---|
perPage | number | Default results per page (overridden by query) |
facets | string[] | Default facet fields returned with every search |
highlight | boolean | Enable highlighting by default |
suggestField | string | Field for phrase suggestions. Should have a shingle analyzer configured in your ES mapping. See Search — Phrase suggestions. |
Fields
Section titled “Fields”The fields config provides a unified way to configure field boost weights, searchability, and field name aliases. Define it at the index level:
indexes: collections: # ... fields: title: weight: 10 description: weight: 2 body: searchable: true country: field: placeCountry region: field: placeRegion| Property | Type | Description |
|---|---|---|
weight | number? | Boost weight for relevance scoring. Implies the field is searchable. |
searchable | boolean? | Include in search queries without boosting. Only applies when weight is not set. |
field | string? | Maps this key as a consumer-friendly alias to the real field name. Works with all engines — aliasing operates at the API route layer before reaching the engine. |
nestedPath | string? | Elasticsearch nested path for fields inside nested object arrays. When set, aggregations and filters are automatically wrapped in nested queries. Only used by ES/OpenSearch. |
How field properties interact
Section titled “How field properties interact”weight— The field gets a boost multiplier in relevance scoring. This implicitly makes it searchable; you don’t need to also setsearchable: true.searchable: true(withoutweight) — The field is included in search queries at default relevance. Useful for fields you want to search but don’t want to boost.field— The key acts as an alias. API consumers use the alias; the API translates to the real field name automatically and bidirectionally.- No
fieldsconfig — All fields are searched with default relevance (equivalent to["*"]).
Nested fields via nestedPath
Section titled “Nested fields via nestedPath”Elasticsearch nested objects are arrays of objects stored as separate hidden documents. Aggregations and filters on nested fields require special nested query wrapping — set nestedPath to handle this automatically.
fields: facilityType: field: facilities.type nestedPath: facilitiesWhen nestedPath is set:
- Filters on the field are wrapped in
{ nested: { path, query } } - Aggregations are wrapped in
{ nested: { path }, aggs: { ... } } - Post-filters (for disjunctive facets) are wrapped correctly
- The facets endpoint wraps both filters and aggregations
This works with field aliases — define both on the same field. Only used by Elasticsearch and OpenSearch engines.
Field aliases via field
Section titled “Field aliases via field”When field is set, the key acts as a consumer-friendly alias. Translation is automatic and bidirectional — use aliases in query parameters and they appear in responses.
See Field Aliases for details on which parameters support aliases.
Full example
Section titled “Full example”port: 3000apiKey: ${API_KEY}corsOrigins: "*" # or an array: ["https://example.com"]
indexes: collections: engine: elasticsearch host: ${ES_HOST} apiKey: ${ES_API_KEY} indexName: museum_collections defaults: perPage: 20 facets: - category - period highlight: true suggestField: title fields: title: weight: 10 description: weight: 2 country: field: placeCountry region: field: placeRegion
catalogue: engine: opensearch host: ${OS_HOST} username: ${OS_USERNAME} password: ${OS_PASSWORD} indexName: library_catalogue
products: engine: meilisearch host: ${MEILI_HOST} apiKey: ${MEILI_API_KEY} indexName: products defaults: perPage: 20 facets: - category
blog: engine: typesense host: ${TYPESENSE_HOST} apiKey: ${TYPESENSE_API_KEY} indexName: blog_posts dateFields: - postDate defaults: perPage: 20 facets: - category