## Context

`codex-lb` currently proxies Responses and Chat Completions flows, but does not expose transcription endpoints. Recent Codex voice-input behavior sends multipart audio to `/backend-api/transcribe` for ChatGPT-auth mode, and OpenAI-compatible clients use `/v1/audio/transcriptions` with `model=gpt-4o-transcribe`.

The current proxy stack already provides:
- account selection and token refresh in `ProxyService`
- upstream HTTP clients in `app/core/clients/proxy.py`
- OpenAI-format error handling in proxy routers
- API key auth, allowed-model checks, and model-scoped limit reservation

The design must add transcription support without regressing existing proxy routes.

## Goals / Non-Goals

**Goals:**
- Add native transcription pass-through route: `POST /backend-api/transcribe`.
- Add OpenAI-compatible transcription route: `POST /v1/audio/transcriptions`.
- Enforce strict OpenAI compatibility model validation (`gpt-4o-transcribe` only) on `/v1/audio/transcriptions`.
- Preserve optional `prompt` as pass-through.
- Apply existing API key auth + model restriction + request-limit enforcement using effective model `gpt-4o-transcribe`.
- Maintain robust account routing even if model registry does not list transcription model.

**Non-Goals:**
- No streaming transcription protocol.
- No transcription-specific usage accounting beyond existing reservation lifecycle.
- No new dashboard UI or settings for transcription routes.
- No changes to `/v1/models` exposure semantics.

## Decisions

### 1) Add dedicated transcription endpoints in proxy API module

- Implement `POST /backend-api/transcribe` and `POST /v1/audio/transcriptions` in `app/modules/proxy/api.py`.
- Keep endpoint ownership in existing proxy module to reuse auth dependencies and error envelope behavior.

**Alternatives considered**
- New `transcriptions` module: rejected to avoid duplicating proxy dependency wiring.
- Implement only one route and alias the other: rejected because native and OpenAI-compatible contracts differ.

### 2) Enforce strict model contract only on OpenAI-compatible route

- `/v1/audio/transcriptions` validates `model == "gpt-4o-transcribe"`.
- On mismatch, return 400 OpenAI error envelope with `invalid_request_error` and `param: "model"`.
- `/backend-api/transcribe` does not require a `model` field.

**Alternatives considered**
- Accept multiple model aliases: rejected to keep deterministic compatibility behavior.
- Forward provided model upstream: rejected because upstream route is fixed `/transcribe`.

### 3) Reuse existing model restriction and limit checks with an effective model

- Both routes use an internal effective model constant: `gpt-4o-transcribe`.
- Apply `_validate_model_access(api_key, effective_model)`.
- Apply `_enforce_request_limits(api_key, request_model=effective_model)`.
- Release reservation at request completion (success or failure), because transcription responses do not provide token usage required for finalize accounting.

**Alternatives considered**
- Skip request-limit enforcement on transcription: rejected to preserve proxy policy consistency.
- Use finalize with zero usage: unnecessary complexity; release is simpler and equivalent for no-usage responses.

### 4) Bypass model-registry plan filtering for transcription account selection

- In transcription service flow, call `LoadBalancer.select_account(..., model=None)`.
- Rationale: registry may not include `gpt-4o-transcribe`; model-based plan filtering would incorrectly return no accounts.
- On 401 refresh retry, recompute upstream `chatgpt-account-id` from the refreshed account metadata.

**Alternatives considered**
- Select with `model="gpt-4o-transcribe"`: rejected due to false-negative account exhaustion when model registry is partial.
- Add transcription model to registry forcibly: rejected due to coupling with external model catalog behavior.

### 5) Add multipart upstream client helper

- Add a dedicated core client function for multipart transcription forwarding.
- Keep existing header filtering and auth header construction.
- Strip `Content-Type` case-insensitively before multipart requests so boundary is generated by the upstream client.

**Alternatives considered**
- Reuse JSON response helper with manual multipart assembly in service: rejected to keep transport concerns in core client layer.

## Risks / Trade-offs

- **[Risk] Reservation release path regressions for new route** -> Mitigation: always release in API `finally` using existing `_release_reservation`.
- **[Risk] Unexpected upstream non-JSON response** -> Mitigation: map to 502 `upstream_error` via `ProxyResponseError`.
- **[Risk] Route scope drift from API key spec expectations** -> Mitigation: update `api-keys` delta spec route scope scenarios for new endpoints.
- **[Trade-off] No usage increment for transcription requests** -> This preserves compatibility but does not consume token/cost quotas for transcription responses lacking usage metadata.

## Migration Plan

1. Add OpenSpec delta specs and tasks for transcription capability and api-keys scope update.
2. Implement backend routes, service flow, and core multipart client.
3. Add integration tests for:
   - happy path forwarding
   - model validation
   - auth scope
   - allowed-model restriction
   - model-registry filtering bypass behavior
4. Run targeted and full test suite segments for proxy/api-key paths.
5. Validate OpenSpec artifacts with `openspec validate --specs`.

Rollback:
- Revert new endpoint route registrations and service/client function additions.
- Existing proxy routes remain unaffected.

## Open Questions

- None. Implementation decisions are fixed for this change.
