# Configuration Guide How to get credentials, configure brands, and wire everything together. --- ## 1. Anthropic API key (Claude Haiku) The service uses Claude Haiku to generate domain name candidates from natural-language queries. ### Get a key 1. Go to [console.anthropic.com](https://console.anthropic.com) and sign in. 2. Navigate to **API Keys** in the left sidebar. 3. Click **Create Key**, give it a name (e.g., `ai-domain-search-prod`), and copy the key. The key starts with `sk-ant-`. ### Set it ```env ANTHROPIC_API_KEY=sk-ant-api03-... ``` ### Optional overrides ```env # Override the model (default: claude-haiku-4-5-20251001) ANTHROPIC_MODEL=claude-haiku-4-5-20251001 # Timeout for a single LLM call in seconds (default: 2.5) LLM_TIMEOUT_SECONDS=2.5 # Max concurrent Anthropic requests across all brands (default: 10) ANTHROPIC_MAX_CONCURRENT_REQUESTS=10 ``` --- ## 2. CNR (CentralNic Reseller) credentials The service uses the CNR API to check domain availability and detect premium pricing. ### Get OT&E credentials OT&E is CNR's test environment. Start here — never use production credentials for development. 1. Log in to your CNR reseller control panel. 2. Navigate to **Account** → **API Access** (or contact your CNR account manager). 3. Note your **login** (username) and **password** for the OT&E environment. OT&E endpoint: `https://api-ote.rrpproxy.net/api/call` ### Get production credentials Production uses the same username/password format but a different endpoint: `https://api.rrpproxy.net/api/call` Contact CNR support if you need separate production API credentials. ### Set them ```env CNR_S_LOGIN=your-cnr-username CNR_S_PW=your-cnr-password # Use "ote" for testing, "production" for live traffic CNR_ENVIRONMENT=ote ``` ### Premium price validation (required before going live) Before enabling production traffic, verify that the X-FEE amount returned by CNR's `CheckDomain` command contains the **registry SRP** (the price shown to end users), not a wholesale cost. Test against a known premium domain on OT&E using the X-FEE extension: ```bash curl -s 'https://api-ote.rrpproxy.net/api/call' \ -d 's_login=YOUR_LOGIN' \ -d 's_pw=YOUR_PW' \ -d 's_opmode=OTE' \ -d 'command=CheckDomain' \ -d 'DOMAIN=coffee.shop' \ -d 'x-fee-command0=create' \ -d 'x-fee-domain0=coffee.shop' \ -d 'x-fee-period0=1' \ -d 'x-fee-periodtype0=YEAR' \ -d 'x-fee-command1=renew' \ -d 'x-fee-domain1=coffee.shop' \ -d 'x-fee-period1=1' \ -d 'x-fee-periodtype1=YEAR' ``` Look for `PROPERTY[X-FEE-CLASS][0]` (should be `premium`), `PROPERTY[X-FEE-AMOUNT][0]` (registration price), `PROPERTY[X-FEE-AMOUNT][1]` (renewal price), and `PROPERTY[X-FEE-CURRENCY][0]` in the response. Confirm the registration price is what you would charge a customer. --- ## 3. Brand API keys Each brand that calls the service needs its own unique API key. Callers include: - Brand webshops making `POST /suggest` HTTP calls - AI agents using the MCP server ### Generate a key A brand API key is a random secret string — at least 32 characters. Generate one with: ```bash python3 -c "import secrets; print(secrets.token_urlsafe(32))" ``` Example output: `xK9mP2nL8qR5vY3wZ7jA4cT1uE6sB0oF` Generate a **different key for each brand**. The service rejects duplicate keys at startup. ### Store the key Brand API keys are never stored in YAML files. They live in environment variables only. Name the env var `BRAND__API_KEY` where `` is your brand slug (uppercase, underscores instead of hyphens): | Brand slug | Env var name | |------------|-------------| | `my-brand` | `BRAND_MY_BRAND_API_KEY` | | `acme` | `BRAND_ACME_API_KEY` | | `brand-a` | `BRAND_BRAND_A_API_KEY` | ```env BRAND_MY_BRAND_API_KEY=xK9mP2nL8qR5vY3wZ7jA4cT1uE6sB0oF BRAND_ACME_API_KEY=another-random-secret-key-here ``` --- ## 4. Brand config files Each brand needs one YAML file in the `brands/` directory. ### File format ```yaml # brands/my-brand.yaml # api_key_ref: name of the environment variable that holds the brand API key. # The actual key is NEVER stored here — only the env var name. api_key_ref: BRAND_MY_BRAND_API_KEY # tlds: only domains with these TLDs will be suggested and checked. # Callers never see or control the TLD list — it is server-side only. tlds: - .com - .nl - .shop ``` ### Naming rules - The filename (without `.yaml`) becomes the brand **slug**. - Slugs must match `[a-z0-9-]+` — lowercase letters, digits, and hyphens only. - Valid: `my-brand.yaml`, `acme.yaml`, `brand-a.yaml` - Invalid: `My_Brand.yaml`, `brand a.yaml` ### Premium domains (optional) By default the service detects and returns premium domains with live pricing. If your brand cannot yet handle premium domain registration, set `premium_enabled: false` to skip the X-FEE pricing phase entirely — only standard available domains are returned, with no premium badges or prices. ```yaml # brands/my-brand.yaml api_key_ref: BRAND_MY_BRAND_API_KEY tlds: - .com - .no - .shop premium_enabled: false # omit or set true to enable premium detection ``` When disabled, availability checking still runs — the only difference is that X-FEE calls are skipped, so results arrive faster and no premium pricing is returned. Enable it once your checkout flow supports `X-ACCEPT-PREMIUMPRICE=1` on the CNR `AddDomain` call. ### TLD prioritisation (optional) If your brand serves a specific market, add a `primary_tlds` field to bias the LLM towards the most relevant TLDs. This does not restrict results — the full `tlds` list is still used — it just makes the LLM generate more suggestions with the listed TLDs. ```yaml # brands/norway-brand.yaml api_key_ref: BRAND_NORWAY_BRAND_API_KEY tlds: - .no - .com - .shop - .nl primary_tlds: - .no - .com ``` All entries in `primary_tlds` must also appear in `tlds`. The service rejects configs where they don't match. ### Common TLDs Add only the TLDs your brand actively sells. The LLM uses this list to scope suggestions — a shorter, focused list produces better results. ```yaml tlds: - .com - .net - .org - .nl - .shop - .io - .co - .store - .online ``` ### Adding a brand 1. Create `brands/.yaml` on the host or volume. 2. Add `BRAND__API_KEY=` to your environment. 3. The service picks up the new brand within 60 seconds (or immediately on restart). 4. No code changes or image rebuild required. ### Removing a brand 1. Delete the YAML file from `brands/`. 2. Optionally remove the env var. 3. Restart the service (or wait for cache TTL). ### Updating a brand's TLD list 1. Edit the YAML file. 2. The service reloads within 60 seconds (controlled by `CONFIG_CACHE_TTL_SECONDS`). --- ## 5. Complete environment variable reference In production (Docker Compose), create `/etc/ai-domain-search/secrets` with the key=value pairs below and `chmod 600` it. For local bare-metal development, set these as shell environment variables or a local `.env` file. See [IMPLEMENTATION.md](IMPLEMENTATION.md) for deployment details. ### Required | Variable | Example | Description | |----------|---------|-------------| | `ANTHROPIC_API_KEY` | `sk-ant-...` | Anthropic API key for Claude Haiku | | `CNR_S_LOGIN` | `myreseller` | CNR account login | | `CNR_S_PW` | `secret` | CNR account password | | `CNR_ENVIRONMENT` | `ote` or `production` | Which CNR endpoint to use | | `BRAND__API_KEY` | (per brand) | Bearer token for each brand | ### Optional (with defaults) | Variable | Default | Description | |----------|---------|-------------| | `ANTHROPIC_MODEL` | `claude-haiku-4-5-20251001` | Claude model to use | | `LLM_TIMEOUT_SECONDS` | `2.5` | Seconds before LLM call is aborted | | `ANTHROPIC_MAX_CONCURRENT_REQUESTS` | `10` | Max parallel LLM calls across all brands | | `BRAND_MAX_CONCURRENT_REQUESTS` | `5` | Max parallel requests per brand (abuse fuse) | | `CONFIG_CACHE_TTL_SECONDS` | `60` | How often brand configs are reloaded from disk | | `CORS_ALLOW_ORIGINS` | _(empty — CORS disabled)_ | Comma-separated allowed origins for browser calls | | `REDIS_URL` | _(empty — in-memory cache)_ | Redis connection URL, e.g. `redis://localhost:6379` | | `QUERY_CACHE_TTL_SECONDS` | `30` | How long query results are cached (seconds) | | `LOG_LEVEL` | `INFO` | Logging verbosity: `DEBUG`, `INFO`, `WARNING`, `ERROR` | | `ADMIN_KEY` | _(empty — no auth required)_ | If set, `GET /metrics` requires `X-Admin-Key: ` | | `LLM_CB_FAILURE_THRESHOLD` | `5` | LLM circuit breaker: consecutive failures before opening (per brand) | | `LLM_CB_RECOVERY_SECONDS` | `60` | LLM circuit breaker: seconds before attempting recovery (per brand) | | `CNR_CB_FAILURE_THRESHOLD` | `5` | CNR circuit breaker: consecutive failures before opening (per brand) | | `CNR_CB_RECOVERY_SECONDS` | `30` | CNR circuit breaker: seconds before attempting recovery (per brand) | ### MCP server only | Variable | Default | Description | |----------|---------|-------------| | `MCP_BRAND_API_KEY` | _(required)_ | The API key for the brand this MCP instance serves | | `MCP_SUGGEST_TIMEOUT_SECONDS` | `35` | Wall-clock timeout for result accumulation | --- ## 6. Redis caching By default the service uses a short-lived in-process cache. For multi-instance deployments or to survive process restarts, point `REDIS_URL` at a Redis instance. The Docker Compose file ships a `redis:7-alpine` service pre-configured. ### Enable Redis ```env REDIS_URL=redis://localhost:6379 ``` In Docker Compose this is set automatically via the `environment` block — no manual change required. ### Cache TTL Results are cached for `QUERY_CACHE_TTL_SECONDS` (default 30 s). Identical queries within that window replay from cache without hitting the LLM or CNR, saving latency and API cost. ### Fallback behaviour If `REDIS_URL` is set but Redis is unreachable at startup (or fails mid-run), the service logs a warning and falls back to in-memory caching automatically. No requests are dropped. The in-memory fallback is a bounded LRU cache capped at 1000 entries. When the limit is reached the oldest entry is evicted to make room. This prevents unbounded memory growth if Redis stays unavailable under load. ### Persistence The Docker Compose Redis service uses `appendonly yes` and a named volume (`redis_data`) so the cache survives container restarts. For long-running production deployments, set `QUERY_CACHE_TTL_SECONDS` to a value that matches your expected query repeat rate (30–120 s is typical). --- ## 7. CORS configuration CORS is disabled by default. Enable it only if brand webshops call POST /suggest directly from a browser (client-side JavaScript). ```env CORS_ALLOW_ORIGINS=https://shop.my-brand.com,https://checkout.other-brand.nl ``` Each origin should be the exact scheme + hostname + optional port of the calling page. **Do not use `*`** — wildcard CORS means anyone who discovers a brand API key can make requests on behalf of that brand from any origin. --- ## 8. End-to-end configuration checklist Use this checklist when setting up the service for the first time or adding a new brand. ### Service setup (once) - [ ] Anthropic API key obtained and set as `ANTHROPIC_API_KEY` - [ ] CNR OT&E credentials obtained and set as `CNR_S_LOGIN` / `CNR_S_PW` - [ ] `CNR_ENVIRONMENT=ote` set (start with OT&E) - [ ] Premium price field validated on OT&E (see section 2) - [ ] `CNR_ENVIRONMENT=production` set after validation passes ### Per brand - [ ] Brand slug chosen (e.g., `my-brand` → filename `brands/my-brand.yaml`) - [ ] API key generated (`python3 -c "import secrets; print(secrets.token_urlsafe(32))"`) - [ ] API key stored in env var `BRAND_MY_BRAND_API_KEY=` - [ ] `brands/my-brand.yaml` created with `api_key_ref: BRAND_MY_BRAND_API_KEY` and TLD list - [ ] Service restarted (or waited 60s for cache reload) - [ ] API key shared securely with the brand team ### If using the MCP server - [ ] Brand API key set as `MCP_BRAND_API_KEY` in the MCP server process environment - [ ] MCP server URL added to Claude Desktop / agent framework config ### If serving browser clients - [ ] `CORS_ALLOW_ORIGINS` set to the brand webshop origins - [ ] TLS termination in place (Caddy or equivalent) --- ## 9. Circuit breakers The service runs independent circuit breakers for both the LLM (Anthropic) and CNR availability checks. Each breaker is **scoped per brand** — a single brand experiencing failures (e.g. a traffic spike or a misconfigured key) cannot open the breaker and block requests for other brands. | Breaker | Controls | Opens after | Recovers after | |---------|----------|-------------|---------------| | LLM (per brand) | Calls to Claude Haiku | `LLM_CB_FAILURE_THRESHOLD` consecutive failures | `LLM_CB_RECOVERY_SECONDS` seconds | | CNR (per brand) | CentralNic availability checks | `CNR_CB_FAILURE_THRESHOLD` consecutive failures | `CNR_CB_RECOVERY_SECONDS` seconds | When a breaker is open, the `/suggest` endpoint returns HTTP 503. The breaker enters HALF_OPEN after the recovery timeout and allows one probe call through. A successful probe closes the breaker; a failed probe re-opens it. Current circuit states (per brand) are visible at `GET /metrics` (requires `X-Admin-Key` if `ADMIN_KEY` is set). ## 10. Security notes - Never commit secrets files, `.env` files, or actual API keys to git. In production, keep credentials in `/etc/ai-domain-search/secrets` (chmod 600), not checked-in files. - Brand YAML files in `brands/` contain no secrets — only env var names. They are safe to commit. - The `Authorization: Bearer` header value is compared using a constant-time comparison (`hmac.compare_digest`) to prevent timing attacks. - The service never logs API key values or query text. Error messages are intentionally generic. - Per-brand concurrency is capped at `BRAND_MAX_CONCURRENT_REQUESTS` (default 5) as an abuse fuse — a leaked key cannot flood the upstream APIs.