# stapel-recordings 0.8.1

Recording lifecycle: audio/video upload (presigned single-PUT and multipart), storage, a configurable processing pipeline (convert, transcribe, diarize, merge) producing a unified speaker-attributed transcript, optional automatic summaries, and a watchdog that recovers stuck or abandoned recordings.

Contract: axes 1 · extension points 7 · operations 5 · error codes 47.
Generated from docs/capabilities.json by `stapel-llms-txt` — do not edit; drift-gated by `make contract-check`.

## Configuration axes — what a product switches on
Settings keys; `default` is what you get by saying nothing. Turning an axis off unmounts the operations it gates.
- SUMMARIZE_ENABLED [bool, default true] — Automatic meeting summaries
  When on, each completed transcript is additionally summarized (via the agent module's language model) and the summary is stored on the recording; the transcript itself is always produced.

## Extension points — what a product replaces, fork-free
- NORMALIZER [dotted_path]
  Swap the audio-normalization step: ffmpeg by default, a passthrough for environments without it, or a custom converter.
- PIPELINE [ordered_list]
  The flagship seam: the processing pipeline is an ordered list of stage names run by a generic driver — reorder, subset (e.g. skip diarization) or insert stages (e.g. PII redaction) purely from settings, no fork.
- PIPELINE_RESOLVER [dotted_path]
  Source the stage list at runtime per recording — point it at a DB or per-workspace definition so operators can edit pipelines in a UI without a redeploy.
- SOURCE_TYPES [merge_registry]
  Extend the recording source-kind registry ({key: label}, merged over the built-in meet/dictaphone/upload/other) — add zoom, teams, phone and so on from settings without editing a code enum.
- STAGES [merge_registry]
  Replace, remove or add stage handlers ({name: dotted-path | None}, merged over the built-ins) or register them at runtime via register_stage().
- STORAGE [dotted_path]
  Swap the object-storage backend (RecordingStorage implementation): any Django storage by default, bundled S3/MinIO backend, or your own.
- serializer_seams [class_override]
  Every view declares request/response serializer seams (SerializerSeamMixin) — subclass the view, override the attribute, remount the URL.

## Fits with — fleet dependencies
- stapel-agent (optional) — provides the llm.transcribe and llm.summarize comm Functions the transcribe/merge stages call; recordings passes audio and options by comm name, no import
- stapel-auth (optional) — every endpoint requires an authenticated user (IsAuthenticated); stapel-auth is the shelf's session issuer — any stapel-core-compatible JWT issuer satisfies the check
- stapel-core (required) — comm bus (recording.stage pipeline driving, llm.transcribe / llm.summarize calls), JWT authentication, GDPR provider registry, AppSettings config layer

## HTTP operations (5) — call by operationId, never by a typed path
Paths are relative to `/recordings/api/v1/`.
### Recordings
- POST /recordings — recordings_api_v1_recordings_create
- POST /recordings/{recording_id}/finalize — recordings_api_v1_recordings_finalize_create
- GET /recordings — recordings_api_v1_recordings_list
- POST /recordings/{recording_id}/reprocess — recordings_api_v1_recordings_reprocess_create
- GET /recordings/{recording_id} — recordings_api_v1_recordings_retrieve

## Error codes (47) — the StapelError envelope
Render `t(code, params)`; branch UX on the remediation. Localized text lives in docs/errors.<lang>.md, not here.
- error.400.bad_request [400] fix_input
- error.400.captcha_invalid [400] retry
- error.400.captcha_required [400] retry
- error.400.expected_list [400] fix_input
- error.400.field.blank [400] fix_input {field}
- error.400.field.does_not_exist [400] fix_input {field}
- error.400.field.invalid [400] fix_input {field}
- error.400.field.invalid_choice [400] fix_input {field}
- error.400.field.max_length [400] fix_input {field,max_length}
- error.400.field.max_value [400] fix_input {field,max_value}
- error.400.field.min_length [400] fix_input {field,min_length}
- error.400.field.min_value [400] fix_input {field,min_value}
- error.400.field.null [400] fix_input {field}
- error.400.field.required [400] fix_input {field}
- error.400.field.unique [400] fix_input {field}
- error.400.invalid_ad_id [400] fix_input
- error.400.recording_invalid_state [400] fix_input
- error.400.validation_error [400] fix_input
- error.400.verification_failed [400] verify
- error.400.verification_invalid_factor [400] verify
- error.401.unauthorized [401] reauthenticate
- error.402.payment_required [402] retry
- error.403.forbidden [403] retry
- error.403.network_blocked [403] contact_support
- error.403.recording_workspace_forbidden [403] retry
- error.403.verification_enrollment_required [403] verify
- error.403.verification_required [403] verify
- error.404.ad_not_found [404] retry
- error.404.not_found [404] retry
- error.404.recording_not_found [404] retry
- error.404.verification_challenge_not_found [404] verify
- error.405.method_not_allowed [405] retry
- error.406.not_acceptable [406] retry
- error.408.request_timeout [408] retry
- error.409.conflict [409] fix_input
- error.409.recording_invalid_state [409] fix_input
- error.410.gone [410] retry
- error.413.payload_too_large [413] retry
- error.413.recording_too_large [413] retry
- error.415.recording_unsupported_media [415] retry
- error.415.unsupported_media_type [415] retry
- error.422.unprocessable_entity [422] wait_and_retry
- error.423.locked [423] wait_and_retry
- error.423.verification_locked [423] wait_and_retry
- error.429.rate_limit [429] wait_and_retry {retry_after_minutes}
- error.429.too_many_requests [429] wait_and_retry
- error.500.internal [500] contact_support
