# Firmennamen Generator API Documentation

**Last Updated:** 2026-04-10

Complete API documentation for the Firmennamen Generator endpoint.

## Endpoint

**URL:** `/v2/api/generate-company-names.php`  
**Method:** `POST`  
**Content-Type:** `application/json`

## Security and abuse resistance

The endpoint is **public** (lead-acquisition tool) but **must not** allow unbounded Gemini usage. Server-side controls:

| Control | Purpose |
|---------|---------|
| **Rate limits** | Per client IP (first `X-Forwarded-For` hop when behind a proxy): burst window + rolling 24h cap. Returns **429** with `Retry-After` and `retry_after_seconds`. |
| **CORS** | `Access-Control-Allow-Origin` only for configured HTTPS origins (not `*`). Missing `Origin` (e.g. `curl`) is allowed for ops/testing. Wrong `Origin` → **403**. |
| **Industry allowlist** | `industry` must match the same fixed list as the UI (prevents oversized or arbitrary prompt injection strings). |
| **Body size** | JSON body capped (default 32 KB); larger requests → **413**. |
| **Kill switch** | `FIRMENNAMEN_DISABLE_GEMINI=1` → **503** (maintenance / emergency cost stop). |

**Environment variables** (optional; defaults in parentheses):

| Variable | Meaning |
|----------|---------|
| `FIRMENNAMEN_RL_BURST_MAX` | Max requests per IP per burst window (default **10**). |
| `FIRMENNAMEN_RL_BURST_WINDOW` | Burst window in seconds (default **600** = 10 minutes). |
| `FIRMENNAMEN_RL_DAILY_MAX` | Max requests per IP per rolling 24 hours (default **50**). |
| `FIRMENNAMEN_CORS_ORIGINS` | Comma-separated allowed `Origin` values. If **unset**, defaults include `https://www.ordio.com`, `https://ordio.com`, and **the same origin as the request** when `HTTP_HOST` is `localhost*`, `127.0.0.1*`, or `*.local` (so local browser tests work without env). Override this var to fully control the list (then add `http://localhost:PORT` yourself if needed). |
| `FIRMENNAMEN_MAX_BODY_BYTES` | Max JSON body size (default **32768**). |
| `FIRMENNAMEN_DISABLE_GEMINI` | Set to `1` to disable generation (503). |
| `FIRMENNAMEN_MODEL` | `flash-lite` (default, cost) or `flash` (quality); legacy `flash-2.0` maps to Flash. |

**Logging:** Rate-limit denials log a short **SHA-256 hash** of the client IP (not the raw IP) when `ordio_log` is available.

**Implementation:** `v2/helpers/firmennamen-api-security.php`

**CLI test (rate limiter only):** `php v2/scripts/dev-helpers/test-firmennamen-rate-limit.php`

**Threat model:** Attackers may script POSTs without a browser; IP-based limits reduce cost exposure. For higher assurance, rely on **edge/WAF** (e.g. Cloudflare) in production and monitor 429/5xx rates.

## Request Format

### Headers

```
Content-Type: application/json
```

### Request Body

```json
{
  "industry": "Gastronomie",
  "style": "kreativ",
  "keywords": "tasty, sassy, queen",
  "description": "Ein modernes Café mit nachhaltigen Produkten",
  "count": 10
}
```

### Parameters

| Parameter | Type | Required | Description | Constraints |
|-----------|------|----------|-------------|-------------|
| `industry` | string | Yes | Industry/branch of the company | Must be one of: Technologie, Gastronomie, Handwerk, Beratung, Handel, Gesundheit, Bildung, Design, Marketing, Immobilien, Finanzen, Transport, Sonstiges |
| `style` | string | No | Style preference for names | Default: `modern`. Options: `modern`, `klassisch`, `kreativ`, `professionell` |
| `keywords` | string | No | Comma-separated keywords | Max 100 characters. Optimal: 2-5 keywords. More than 5 keywords may reduce quality. |
| `description` | string | No | Business description | Max 300 characters. When provided, used as primary context (competitor pattern). |
| `count` | integer | No | Number of names to generate | Default: 5. Range: 1-10 |

### Keyword Processing

The API automatically processes keywords:

1. **Parsing**: Comma-separated keywords are split into an array
2. **Normalization**: Keywords are trimmed, lowercased, and duplicates removed
3. **Limiting**: Maximum 7 keywords accepted (first 7 used)
4. **Prioritization**: First 2-3 keywords are prioritized in the prompt, rest used as inspiration
5. **Validation**: Keywords with >5 entries trigger a warning but generation continues

**Best Practice:** Use 2-5 keywords for optimal results.

### Description Field (New in 2026-03-04)

The `description` field follows competitor patterns (Qonto, Squarespace, Canva):

- **When provided**: Used as primary context for name generation
- **When empty**: Falls back to industry + style + keywords approach
- **Character limit**: 300 characters (validated)
- **Format**: Natural language description of the business

**Example:**
```json
{
  "description": "Ein nachhaltiges Café mit lokalen Produkten und modernem Ambiente"
}
```

## Response Format

### Success Response

```json
{
  "success": true,
  "names": [
    {
      "name": "NachhaltigGenuss",
      "explanation": "Ein Name, der Nachhaltigkeit und Genuss verbindet, ideal für ein nachhaltiges Café."
    }
  ],
  "count": 10,
  "model": "gemini-2.5-flash",
  "attempt": 1,
  "response_time_ms": 8234,
  "keywords_count": 3,
  "cached": false,
  "ab_test_variant": null
}
```

### Response Fields

| Field | Type | Description |
|-------|------|-------------|
| `success` | boolean | Always `true` for successful responses |
| `names` | array | Array of name objects with `name` and `explanation` |
| `count` | integer | Number of names generated |
| `model` | string | Gemini model used (`gemini-2.5-flash`, `gemini-2.5-flash-lite`, etc.) |
| `attempt` | integer | API attempt number (1-3, indicates retries) |
| `response_time_ms` | integer | Total API response time in milliseconds |
| `keywords_count` | integer | Number of keywords parsed from input |
| `cached` | boolean | Whether response was served from cache |
| `ab_test_variant` | string\|null | A/B test variant if applicable |
| `finish_reason` | string\|null | Gemini API finish reason (`MAX_TOKENS`, `STOP`, etc.) |
| `partial` | boolean | Whether response contains partial results (only present if true) |
| `warning` | string | Warning message for partial results (only present if partial) |

### Error Response

```json
{
  "success": false,
  "error": "Fehler beim Generieren der Namen. Bitte versuche es später erneut.",
  "suggestion": "Tipp: Verwende maximal 5 Keywords für beste Ergebnisse.",
  "details": "Invalid JSON response from API"
}
```

### Error Types

| HTTP Code | Error Message | Description |
|-----------|---------------|-------------|
| 400 | `Branche ist erforderlich.` | Industry parameter missing |
| 400 | `Ungültige Branche. ...` | Industry not in the server allowlist (must match UI options) |
| 400 | `Beschreibung zu lang. Maximal 300 Zeichen.` | Description exceeds 300 characters |
| 403 | `Ungültige Herkunft der Anfrage.` | `Origin` header not in CORS allowlist |
| 413 | `Anfrage zu groß.` | JSON body over configured max size |
| 429 | `Zu viele Anfragen. ...` | Rate limit; body may include `retry_after_seconds`; header `Retry-After` |
| 503 | Service unavailable | Maintenance / `FIRMENNAMEN_DISABLE_GEMINI` |
| 405 | `Method not allowed. Use POST.` | Wrong HTTP method |
| 500 | Various | Server error (see `details` field) |

### Production Troubleshooting

**Works locally but fails in production (403/500):**

1. **Gemini API key**: On the **live server**, set **`GEMINI_API_KEY`** (environment or PHP bootstrap constant), or deploy **`v2/config/gemini-api-key.php`** (gitignored; env wins). Same pattern as ShiftOps Maps keys — see `docs/development/GEMINI_API_KEY_PRODUCTION.md`. Use a key with **Generative Language API** allowed (not a Maps-only key). A 403 from Gemini is returned as 500 with a generic message. Check logs for `Firmennamen Generator: Gemini API 403` or `Gemini API key not configured`.
2. **WAF/ModSecurity**: If the request returns 403 before reaching PHP, add `X-Requested-With: XMLHttpRequest` and `Accept: application/json` headers (already included in the frontend). Some hosting WAFs block JSON POST requests; contact the host if the issue persists.

### Partial Results Response

When MAX_TOKENS retries are exhausted but some names were extracted from truncated JSON:

```json
{
  "success": true,
  "names": [...],
  "count": 8,
  "partial": true,
  "warning": "Nicht alle Namen konnten generiert werden. Bitte versuche es mit weniger Namen oder einer kürzeren Beschreibung.",
  "finish_reason": "MAX_TOKENS"
}
```

**Partial Results Criteria:**
- At least 3 names extracted from truncated JSON
- All retries exhausted
- `partial: true` flag indicates incomplete response
- `warning` field provides user guidance

## Model Selection

The API supports multiple Gemini models:

- **gemini-2.5-flash** (default): Best for creative tasks, ~8.6s latency
- **gemini-2.5-flash-lite**: Fastest option, ~5.0s latency, 6× cheaper
- **`flash-2.0` (legacy key):** Maps to **`gemini-2.5-flash`** — the old `gemini-2.0-flash` model is deprecated (shutdown 2026-06-01 per [Google deprecations](https://ai.google.dev/gemini-api/docs/deprecations)); do not expect 2.0-specific behavior.

**Configuration:**
- Set environment variable `FIRMENNAMEN_MODEL=flash-lite` to use Flash-Lite
- Default: `flash` (gemini-2.5-flash)
- A/B testing: 10% of requests randomly use Flash-Lite for quality comparison

## Caching

The API implements intelligent caching:

- **Cache Key**: `firmennamen:{industry}:{style}:{count}`
- **TTL**: 24 hours
- **Conditions**: Only caches requests without keywords or description (common patterns)
- **Cache Location**: `v2/cache/firmennamen/` (file-based)

Cached responses include `"cached": true` in the response.

## Quality Scoring

Generated names are automatically scored and filtered:

- **Length**: 2-3 words ideal (score: +2), 1 or 4 words acceptable (+1)
- **Explanation**: Meaningful content >20 chars (+1)
- **Brandability**: Pronounceable, not too many consonants (+0.5)

Names are **brand-style** by default (no GmbH/UG/AG in `name`). If the user explicitly mentions a legal form in the description or keywords, the model may include it.

Names with quality score < 2.0 are filtered out before returning.

## Performance Optimization

### Prompt Optimization

- Reduced prompt length from ~150 to ~100 tokens
- More direct language
- Role assignment: "Du bist ein erfahrener Branding-Experte"
- Clear structure: Kontext, Anforderungen, Format

### Generation Config

```php
'temperature' => 0.7,        // Balanced creativity (reduced from 0.8)
'maxOutputTokens' => dynamic, // Dynamic allocation based on count (2000-5000; max 10 names)
'topP' => 0.95                // Quality/speed balance
```

### Dynamic Token Allocation

The API dynamically allocates `maxOutputTokens` based on the requested count:

- **Base**: 2000 tokens for 1-10 names
- **Scaling**: +150 tokens per additional name
- **Formula**: `max(2000, count * 150)`
- **Cap**: Maximum 6000 tokens

**Token Allocation by Count:**

| Count | Tokens | Use Case |
|-------|--------|----------|
| 1-10 | 2000 | Standard requests |
| 11-15 | 3000 | Medium requests |
| 10 (max) | up to 5000 | Largest allowed batch |

This ensures sufficient tokens for complete responses without wasting API credits.

### Response Time Targets

- **Target**: <6s average response time
- **Current**: ~8-10s (before optimization)
- **Expected**: 25-40% improvement with optimizations

### Timeout Configuration

- **Backend Timeout**: 60 seconds (CURLOPT_TIMEOUT)
  - 60s timeout accommodates up to 10 names + keywords + description
  - Allows for Gemini API processing time (21-31s TTFT for complex requests)
- **Frontend Timeout**: 60 seconds (AbortController)
  - Matches backend timeout to prevent premature abort
  - Shows progress message after 15 seconds for longer requests
- **Connection Timeout**: 10 seconds (unchanged)
  - Network issues should fail fast

## Error Handling

### Retry Logic

- **Max Retries**: 3 attempts
- **Retry Delays**: Exponential backoff (1s, 2s, 4s) for network errors
- **MAX_TOKENS Retries**: 0.5s delay (usleep) with token tier escalation
- **Retry Conditions**: 
  - Network errors, HTTP 429, HTTP 500+
  - MAX_TOKENS truncation (escalates token tier)
  - Insufficient names generated (<50% of requested)

### MAX_TOKENS Handling

The API detects when responses are truncated due to token limits (`finishReason === 'MAX_TOKENS'`) and automatically retries with higher token limits.

**Token Tier System:**
- Tiers: `[2000, 3000, 4500, 5000]`
- Starts at calculated tier based on count
- Escalates to next tier on MAX_TOKENS
- Retries with 0.5s delay (faster than network retries)

**Behavior:**
1. If `finishReason === 'MAX_TOKENS'`:
   - Move to next token tier
   - Retry immediately (0.5s delay)
   - Log retry reason for monitoring
2. If insufficient names (<50% of requested) and MAX_TOKENS:
   - Escalate token tier
   - Retry with more tokens
3. If all retries exhausted:
   - Attempt to extract partial results from truncated JSON
   - Return partial results if ≥3 names extracted
   - Include warning message in response

### Error Logging

All errors are logged with context:
- Industry, style, keyword count
- Description presence
- Error type and message
- Response time

Logs available via `ordio_log()` structured logger.

## Best Practices

### Keyword Usage

1. **Optimal Count**: 2-5 keywords
2. **Format**: Comma-separated: `"keyword1, keyword2, keyword3"`
3. **Priority**: First keywords are prioritized
4. **Avoid**: Special characters that break parsing

### Description Usage

1. **Length**: Keep under 200 characters for best results
2. **Content**: Describe business, target audience, values
3. **Format**: Natural language, German
4. **Combination**: Can be combined with keywords for richer context

### Performance

1. **Common Patterns**: Use industry + style only (gets cached)
2. **Keywords**: Add only if needed (skips cache)
3. **Description**: Use for unique businesses (skips cache)
4. **Count**: Request 1-10 names for faster responses

## Examples

### Example 1: Simple Request (Cached)

```bash
curl -X POST http://localhost:8003/v2/api/generate-company-names.php \
  -H "Content-Type: application/json" \
  -d '{
    "industry": "Gastronomie",
    "style": "modern",
    "count": 10
  }'
```

### Example 2: With Keywords

```bash
curl -X POST http://localhost:8003/v2/api/generate-company-names.php \
  -H "Content-Type: application/json" \
  -d '{
    "industry": "Technologie",
    "style": "modern",
    "keywords": "KI, innovativ, digital",
    "count": 10
  }'
```

### Example 3: With Description (Competitor Pattern)

```bash
curl -X POST http://localhost:8003/v2/api/generate-company-names.php \
  -H "Content-Type: application/json" \
  -d '{
    "industry": "Gastronomie",
    "style": "kreativ",
    "description": "Ein nachhaltiges Café mit lokalen Produkten und modernem Ambiente",
    "count": 10
  }'
```

### Example 4: Combined Inputs

```bash
curl -X POST http://localhost:8003/v2/api/generate-company-names.php \
  -H "Content-Type: application/json" \
  -d '{
    "industry": "Technologie",
    "style": "modern",
    "keywords": "KI, nachhaltig",
    "description": "Ein Startup für nachhaltige KI-Lösungen",
    "count": 10
  }'
```

## Testing

### Test Scripts

1. **Keyword Parsing Test**: `v2/scripts/tools/test-keyword-parsing.php`
   - Tests 1, 2, 3, 4+ keywords
   - Measures success rate by keyword count

2. **Model Comparison Test**: `v2/scripts/tools/test-gemini-models.php`
   - Compares Flash-Lite vs Flash vs 2.0 Flash
   - Measures speed, quality, cost

3. **Comprehensive Test Suite**: `v2/scripts/tools/test-firmennamen-generator-comprehensive.php`
   - Tests all scenarios
   - Validates quality and performance
   - Generates test report

4. **Reliability Test Suite**: `v2/scripts/tools/test-reliability-fixes.php`
   - Tests dynamic token allocation (5, 10 names)
   - Validates MAX_TOKENS handling and retries
   - Tests timeout scenarios
   - Validates partial results handling
   - Measures success rates by count

## Troubleshooting

### Common Issues

**Issue**: Generation fails with multiple keywords (>5)
- **Solution**: API now handles up to 7 keywords, prioritizes first 5
- **Recommendation**: Use 2-5 keywords for best results

**Issue**: Slow response times (>10s)
- **Check**: Response time in `response_time_ms` field
- **Solution**: Use cached patterns (industry + style only), or switch to Flash-Lite model
- **Note**: Complex requests (10 names + keywords + description) may take 15-25s (normal)

**Issue**: MAX_TOKENS truncation
- **Check**: `finish_reason` field in response
- **Solution**: API automatically retries with higher token limits
- **If persists**: Request fewer names or a shorter description

**Issue**: Partial results returned
- **Check**: `partial: true` flag in response
- **Solution**: Request fewer names or shorter description
- **Note**: Partial results (≥3 names) are better than complete failure

**Issue**: Low quality names
- **Check**: Quality scoring filters names < 2.0
- **Solution**: Provide better description or more specific keywords

**Issue**: Description too long error
- **Solution**: Reduce description to ≤300 characters
- **Recommendation**: Keep under 200 characters for best results

## Changelog

### 2026-03-04 (Reliability Fixes)

- **Dynamic Token Allocation**: Token limits scale with requested count (2000-5000 tokens; max 10 names)
- **MAX_TOKENS Handling**: Automatic retry with token tier escalation (2000 → 3000 → 4500 → 5000)
- **Increased Timeouts**: Backend 60s, frontend 60s (from 30s/25s) for complex requests
- **Partial Results**: Extract and return partial results from truncated JSON (≥3 names)
- **Enhanced JSON Parsing**: Robust handling of truncated responses from MAX_TOKENS scenarios
- **Progress Indicators**: Frontend shows estimated time and progress messages for longer requests
- **Better Error Messages**: User-friendly messages for MAX_TOKENS and timeout scenarios
- **Enhanced Logging**: Logs finishReason, token tiers, retry reasons, partial result scenarios

### 2026-03-04 (Initial Improvements)

- Added `description` parameter (optional, 300 chars max)
- Improved keyword parsing (handles up to 7 keywords, prioritizes first 5)
- Added quality scoring and filtering
- Implemented response caching for common patterns
- Added response time tracking
- Optimized prompt (reduced from ~150 to ~100 tokens)
- Optimized generation config (temperature 0.7, maxOutputTokens 1500)
- Added A/B testing framework (10% Flash-Lite)
- Improved error messages with suggestions
- Added analytics and monitoring

### Previous Versions

- Initial implementation with basic keyword support
- Industry + style + keywords structure
