{"openapi":"3.0.3","info":{"title":"Boltt Device Services Platform — UAT API","version":"1.0.0","description":"REST API for the **Boltt Device Services Platform (DSP)** — device activation, customer OTP registration, warranty, OTA firmware delivery, telemetry, crash reporting and device lifecycle management.\n\nImplements **Technical Specification v1.0** (Bitmisri / DSP Team, June 2026). **TEST ENVIRONMENT ONLY** — all data is seeded and simulated; no real SMS or S3 traffic.\n\n### Authentication\n| Token | Obtained from | Header |\n|---|---|---|\n| SDK API Key | credentials report (`/report`) | `X-SDK-API-Key: <key>` |\n| `device_token` (JWT, 90 d) | `POST /device/activate` | `Authorization: Bearer <token>` |\n| `user_token` (JWT, 30 d) | `POST /user/verify-otp` | `Authorization: Bearer <token>` |\n| `admin_token` (JWT, 8 h) | `POST /auth/admin/login` | `Authorization: Bearer <token>` |\n\nAll errors use the standard envelope with a `DSP-xxx` error code, human-readable `message`, field-level `details[]` where applicable, and a `trace_id` for support. Ready-to-use seeded credentials live in the [credentials report](/report).","contact":{"name":"Bitmisri / DSP Team"}},"servers":[{"url":"https://boltt-aws.rad0.dev/dsp/v1","description":"UAT (this host)"}],"tags":[{"name":"Activation","description":"Device onboarding — SDK API key auth"},{"name":"Customer & OTP","description":"Customer registration and device-user linking"},{"name":"Warranty","description":"Warranty registration and business rules"},{"name":"OTA","description":"Firmware update check and rollout reporting"},{"name":"Telemetry & Health","description":"Heartbeat, telemetry batches, crash reports"},{"name":"Device Lifecycle","description":"Configuration, profile, unregistration"},{"name":"Admin","description":"UAT admin token issuance"},{"name":"Console","description":"Admin Console — review & ratings management (admin_token + RBAC permission)"},{"name":"Platform","description":"Health and metadata"}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"device_token, user_token or admin_token depending on endpoint"},"sdkApiKey":{"type":"apiKey","in":"header","name":"X-SDK-API-Key","description":"SDK API key — used only by POST /device/activate"}},"schemas":{"ErrorResponse":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error_code":{"type":"string","example":"DSP-001"},"error":{"type":"string","example":"IMEI_ALREADY_REGISTERED"},"message":{"type":"string"},"details":{"type":"array","description":"Field-level validation explanations (when applicable)","items":{"type":"object","properties":{"field":{"type":"string"},"issue":{"type":"string"},"hint":{"type":"string"}}}},"trace_id":{"type":"string","example":"trace-20260612-abc12345"},"timestamp":{"type":"string","format":"date-time"}}},"Configuration":{"type":"object","properties":{"heartbeat_interval_seconds":{"type":"integer","example":21600},"telemetry_enabled":{"type":"boolean","example":true},"crash_report_enabled":{"type":"boolean","example":true},"ota_check_interval_hours":{"type":"integer","example":24},"max_log_level":{"type":"string","example":"warn"},"feature_flags":{"type":"object","additionalProperties":true}}}}},"paths":{"/device/activate":{"post":{"tags":["Activation"],"summary":"Activate a device","operationId":"activateDevice","description":"Registers a new Boltt device. Called by the Activation SDK on first boot. Validates IMEI (15-digit Luhn), checks the model allow-list, creates the registry record and returns a 90-day `device_token`.\n\n**Auth:** `X-SDK-API-Key` header — no JWT at activation.\n\n**Rate limit:** 3 attempts per IMEI per 24 h.","security":[{"sdkApiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["imei1","serial_number","model_code","model_name","device_type","android_version","build_id","sdk_version"],"properties":{"imei1":{"type":"string","description":"15-digit numeric, Luhn-valid","example":"356938035643809"},"imei2":{"type":"string","description":"Optional secondary IMEI (dual-SIM); must differ from imei1","example":"490154203237518"},"serial_number":{"type":"string","example":"BOLTT2026XP001234"},"model_code":{"type":"string","example":"BOLTT-PH5G-2026"},"model_name":{"type":"string","example":"Boltt Phantom 5G"},"device_type":{"type":"string","enum":["smartphone","tablet","wearable","iot"]},"android_version":{"type":"string","example":"13.0"},"build_id":{"type":"string","example":"INCAR-BOLTT-PH5G-260601.001"},"sdk_version":{"type":"string","example":"1.2.3"},"country":{"type":"string","example":"IN"}}}}}},"responses":{"201":{"description":"Device activated; returns device_id, device_token and initial configuration","content":{"application/json":{"example":{"success":true,"device_id":"d8f3a1c2-4e56-7890-abcd-ef1234567890","device_token":"eyJhbGciOiJIUzI1NiIs...","token_expiry":"2026-09-10T00:00:00Z","configuration":{"heartbeat_interval_seconds":21600,"telemetry_enabled":true,"crash_report_enabled":true,"ota_check_interval_hours":24},"message":"Device activated successfully"}}}},"401":{"description":"**DSP-004 INVALID_SDK_API_KEY** (HTTP 401) — SDK key missing or revoked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-004","error":"INVALID_SDK_API_KEY","message":"SDK key missing or revoked","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"409":{"description":"**DSP-001 IMEI_ALREADY_REGISTERED** (HTTP 409) — Device already activated; existing device_id is returned in the body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-001","error":"IMEI_ALREADY_REGISTERED","message":"Device already activated; existing device_id is returned in the body","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"422":{"description":"**DSP-002 INVALID_IMEI_FORMAT** (HTTP 422) — IMEI fails Luhn/length check, or model not supported (DSP-003), or other field validation (DSP-998)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-002","error":"INVALID_IMEI_FORMAT","message":"IMEI fails Luhn/length check, or model not supported (DSP-003), or other field validation (DSP-998)","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"429":{"description":"**DSP-005 RATE_LIMIT_EXCEEDED** (HTTP 429) — More than 3 activation attempts for this IMEI in 24 h","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-005","error":"RATE_LIMIT_EXCEEDED","message":"More than 3 activation attempts for this IMEI in 24 h","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/user/request-otp":{"post":{"tags":["Customer & OTP"],"summary":"Request OTP","operationId":"requestOtp","description":"Generates a 6-digit OTP for the customer mobile (Indian E.164). SMS delivery is **simulated in UAT** — verify with the UAT bypass code from the credentials report.\n\n**Auth:** `device_token`.\n\n**Rate limit:** 5 OTPs per mobile per hour. OTP TTL: 10 minutes.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["mobile_number","purpose","device_id"],"properties":{"mobile_number":{"type":"string","example":"+919876543210"},"purpose":{"type":"string","enum":["registration","warranty","account_recovery"]},"device_id":{"type":"string","format":"uuid"}}}}}},"responses":{"200":{"description":"OTP generated and (simulated) sent","content":{"application/json":{"example":{"success":true,"request_id":"otp-req-a1b2c3d4","expires_in_seconds":600,"masked_mobile":"+91XXXXXX3210","message":"OTP sent successfully"}}}},"401":{"description":"**DSP-013 INVALID_DEVICE_TOKEN** (HTTP 401) — Bearer token missing, expired or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-013","error":"INVALID_DEVICE_TOKEN","message":"Bearer token missing, expired or invalid","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"404":{"description":"**DSP-012 DEVICE_NOT_FOUND** (HTTP 404) — device_id not in registry","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-012","error":"DEVICE_NOT_FOUND","message":"device_id not in registry","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"422":{"description":"**DSP-010 INVALID_MOBILE_NUMBER** (HTTP 422) — Not a valid +91 Indian mobile in E.164","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-010","error":"INVALID_MOBILE_NUMBER","message":"Not a valid +91 Indian mobile in E.164","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"429":{"description":"**DSP-011 OTP_RATE_LIMIT_EXCEEDED** (HTTP 429) — 5 OTPs/hour limit reached for this number","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-011","error":"OTP_RATE_LIMIT_EXCEEDED","message":"5 OTPs/hour limit reached for this number","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/user/verify-otp":{"post":{"tags":["Customer & OTP"],"summary":"Verify OTP","operationId":"verifyOtp","description":"Verifies the OTP, creates/updates the customer record, links customer ↔ device and returns a 30-day `user_token`. Maximum 3 attempts per OTP; then a 30-minute lockout.\n\n**Auth:** `device_token`.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["mobile_number","otp_code","request_id","device_id"],"properties":{"mobile_number":{"type":"string","example":"+919876543210"},"otp_code":{"type":"string","example":"123456"},"request_id":{"type":"string","example":"otp-req-a1b2c3d4"},"device_id":{"type":"string","format":"uuid"}}}}}},"responses":{"200":{"description":"OTP verified; user_token issued","content":{"application/json":{"example":{"success":true,"user_token":"eyJhbGciOiJIUzI1NiIs...","customer_id":"c1a2b3c4-5d6e-7f8a-9012-345678901234","token_expiry":"2026-07-12T00:00:00Z","is_new_customer":true,"message":"OTP verified successfully"}}}},"401":{"description":"**DSP-020 INVALID_OTP** (HTTP 401) — OTP does not match (or DSP-021 OTP_EXPIRED)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-020","error":"INVALID_OTP","message":"OTP does not match (or DSP-021 OTP_EXPIRED)","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"404":{"description":"**DSP-023 REQUEST_ID_NOT_FOUND** (HTTP 404) — No active OTP request for this request_id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-023","error":"REQUEST_ID_NOT_FOUND","message":"No active OTP request for this request_id","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"429":{"description":"**DSP-022 OTP_MAX_ATTEMPTS_EXCEEDED** (HTTP 429) — 3 failed attempts; locked 30 minutes","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-022","error":"OTP_MAX_ATTEMPTS_EXCEEDED","message":"3 failed attempts; locked 30 minutes","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/warranty/register":{"post":{"tags":["Warranty"],"summary":"Register warranty","operationId":"registerWarranty","description":"Registers device warranty for the OTP-verified customer. Purchase date must not be in the future and must fall within 90 days of first activation; one active warranty per device; `retailer_code` (optional) must exist in the Retailer Master.\n\n**Auth:** `user_token`.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["device_id","purchase_date"],"properties":{"device_id":{"type":"string","format":"uuid"},"purchase_date":{"type":"string","format":"date","example":"2026-06-01"},"retailer_code":{"type":"string","example":"RET-MH-001"},"invoice_number":{"type":"string","example":"INV-2026-00456"},"purchase_city":{"type":"string","example":"Mumbai"}}}}}},"responses":{"201":{"description":"Warranty registered","content":{"application/json":{"example":{"success":true,"warranty_id":"w9x8y7z6-5432-1098-abcd-ef0987654321","warranty_start":"2026-06-01","warranty_expiry":"2027-06-01","warranty_period_months":12,"warranty_status":"active","message":"Warranty registered successfully"}}}},"404":{"description":"**DSP-034 DEVICE_NOT_FOUND** (HTTP 404) — device_id not in registry","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-034","error":"DEVICE_NOT_FOUND","message":"device_id not in registry","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"409":{"description":"**DSP-030 DEVICE_ALREADY_HAS_WARRANTY** (HTTP 409) — Active warranty already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-030","error":"DEVICE_ALREADY_HAS_WARRANTY","message":"Active warranty already exists","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"422":{"description":"**DSP-031 PURCHASE_DATE_INVALID** (HTTP 422) — Future/invalid date (or DSP-032 outside 90-day window, DSP-033 unknown retailer)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-031","error":"PURCHASE_DATE_INVALID","message":"Future/invalid date (or DSP-032 outside 90-day window, DSP-033 unknown retailer)","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/ota/check-update":{"post":{"tags":["OTA"],"summary":"Check for firmware update","operationId":"checkUpdate","description":"Device polls for an eligible OTA release. Battery below 20% → `update_available:false` with reason `BATTERY_TOO_LOW`. On cellular networks only delta packages are offered. Download URL is a (simulated) 2-hour pre-signed link.\n\n**Auth:** `device_token`.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["device_id","current_fw_version","model_code"],"properties":{"device_id":{"type":"string","format":"uuid"},"current_fw_version":{"type":"string","example":"1.2.0"},"model_code":{"type":"string","example":"BOLTT-PH5G-2026"},"device_type":{"type":"string","example":"smartphone"},"battery_level":{"type":"integer","example":65},"network_type":{"type":"string","enum":["wifi","4g","5g","offline"]}}}}}},"responses":{"200":{"description":"Update decision (update_available true/false — battery-low and no-update cases also return 200)","content":{"application/json":{"examples":{"available":{"value":{"update_available":true,"update_id":"ota-upd-20260601-001","target_fw_version":"1.3.0","package_type":"delta","download_url":"https://boltt-backend.rad0.dev/ota/packages/...","file_size_bytes":52428800,"sha256_checksum":"e3b0c44298fc1c149af...","kms_signature":"AQIDBA...","release_notes":"Performance improvements and bug fixes.","url_expires_at":"2026-06-12T14:00:00Z"}},"batteryLow":{"value":{"update_available":false,"reason":"BATTERY_TOO_LOW","message":"Battery at 15% is below the 20% minimum required for an OTA update."}},"none":{"value":{"update_available":false,"reason":"NO_UPDATE_AVAILABLE","message":"No firmware newer than 1.3.0 is currently rolling out for BOLTT-PH5G-2026."}}}}}},"404":{"description":"**DSP-040 DEVICE_NOT_FOUND** (HTTP 404) — device_id does not exist in registry","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-040","error":"DEVICE_NOT_FOUND","message":"device_id does not exist in registry","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/ota/report-status":{"post":{"tags":["OTA"],"summary":"Report OTA install status","operationId":"reportOtaStatus","description":"Device reports installation outcome. `installed_fw_version` required on success; `error_code` required otherwise. One report per device per update — duplicates return DSP-051.\n\n**Auth:** `device_token`.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["device_id","update_id","status"],"properties":{"device_id":{"type":"string","format":"uuid"},"update_id":{"type":"string","example":"ota-upd-20260601-001"},"status":{"type":"string","enum":["success","failed","aborted","checksum_error","signature_error","insufficient_storage"]},"installed_fw_version":{"type":"string","example":"1.3.0"},"install_duration_seconds":{"type":"integer","example":247},"error_code":{"type":"string","nullable":true},"error_message":{"type":"string","nullable":true}}}}}},"responses":{"200":{"description":"Status recorded","content":{"application/json":{"example":{"success":true,"message":"OTA status recorded","next_check_in_hours":24}}}},"404":{"description":"**DSP-050 UPDATE_ID_NOT_FOUND** (HTTP 404) — OTA release ID not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-050","error":"UPDATE_ID_NOT_FOUND","message":"OTA release ID not found","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"409":{"description":"**DSP-051 STATUS_ALREADY_REPORTED** (HTTP 409) — This device already reported for this update","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-051","error":"STATUS_ALREADY_REPORTED","message":"This device already reported for this update","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"422":{"description":"**DSP-052 INVALID_STATUS_VALUE** (HTTP 422) — status not in the allowed set","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-052","error":"INVALID_STATUS_VALUE","message":"status not in the allowed set","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/device/telemetry":{"post":{"tags":["Telemetry & Health"],"summary":"Submit telemetry","operationId":"submitTelemetry","description":"Single event (flat body) or batch (`{ \"device_id\": ..., \"events\": [...] }`, max 10). Ranges: battery 0–100, temp −20…80 °C, CPU 0–100.\n\n**Auth:** `device_token`.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["device_id"],"properties":{"device_id":{"type":"string","format":"uuid"},"timestamp":{"type":"string","format":"date-time"},"battery_level":{"type":"integer","example":78},"battery_temp":{"type":"number","example":32.5},"cpu_usage":{"type":"integer","example":23},"memory_free_mb":{"type":"integer","example":1840},"storage_free_gb":{"type":"number","example":48.2},"network_type":{"type":"string","enum":["wifi","4g","5g","offline"]},"signal_strength":{"type":"integer","example":-85},"fw_version":{"type":"string","example":"1.3.0"},"events":{"type":"array","description":"Optional batch form (max 10)","items":{"type":"object"}}}}}}},"responses":{"200":{"description":"Telemetry recorded","content":{"application/json":{"example":{"success":true,"message":"Telemetry recorded (1 event)"}}}},"404":{"description":"**DSP-061 DEVICE_NOT_FOUND** (HTTP 404) — device_id not in registry","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-061","error":"DEVICE_NOT_FOUND","message":"device_id not in registry","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"422":{"description":"**DSP-060 INVALID_TELEMETRY_PAYLOAD** (HTTP 422) — Field range/enum violations — see details[]","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-060","error":"INVALID_TELEMETRY_PAYLOAD","message":"Field range/enum violations — see details[]","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/device/heartbeat":{"post":{"tags":["Telemetry & Health"],"summary":"Heartbeat ping","operationId":"heartbeat","description":"Lightweight ping every N hours. Updates `last_seen` and `current_fw_version`; returns current configuration. Blacklisted devices receive DSP-071 (403).\n\n**Auth:** `device_token`.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["device_id","fw_version","sdk_version","battery_level","network_type"],"properties":{"device_id":{"type":"string","format":"uuid"},"fw_version":{"type":"string","example":"1.3.0"},"sdk_version":{"type":"string","example":"1.2.3"},"battery_level":{"type":"integer","example":82},"network_type":{"type":"string","enum":["wifi","4g","5g","offline"]}}}}}},"responses":{"200":{"description":"Heartbeat accepted; configuration returned","content":{"application/json":{"example":{"success":true,"configuration_updated":false,"configuration":{"heartbeat_interval_seconds":21600,"telemetry_enabled":true,"crash_report_enabled":true,"ota_check_interval_hours":24}}}}},"403":{"description":"**DSP-071 DEVICE_BLACKLISTED** (HTTP 403) — Device has been blacklisted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-071","error":"DEVICE_BLACKLISTED","message":"Device has been blacklisted","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"404":{"description":"**DSP-070 DEVICE_NOT_FOUND** (HTTP 404) — device_id not in registry","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-070","error":"DEVICE_NOT_FOUND","message":"device_id not in registry","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/device/crash-report":{"post":{"tags":["Telemetry & Health"],"summary":"Upload crash report","operationId":"crashReport","description":"Uploads a crash report. `stack_trace` over 50,000 chars → DSP-080 (413) with a pre-signed upload URL. `crash_timestamp` must not be in the future.\n\n**Auth:** `device_token`.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["device_id","crash_type","package_name","crash_timestamp","stack_trace"],"properties":{"device_id":{"type":"string","format":"uuid"},"crash_type":{"type":"string","enum":["fatal","anr","native","java"]},"package_name":{"type":"string","example":"com.boltt.launcher"},"crash_timestamp":{"type":"string","format":"date-time"},"fw_version":{"type":"string","example":"1.3.0"},"android_version":{"type":"string","example":"13.0"},"free_memory_mb":{"type":"integer","example":342},"stack_trace":{"type":"string","example":"java.lang.NullPointerException\n   at com.boltt.launcher.MainActivity..."}}}}}},"responses":{"201":{"description":"Crash report stored","content":{"application/json":{"example":{"success":true,"crash_id":"crsh-uuid-1234-5678-90ab","message":"Crash report received"}}}},"413":{"description":"**DSP-080 STACK_TRACE_TOO_LARGE** (HTTP 413) — Use the returned pre-signed URL for large traces","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-080","error":"STACK_TRACE_TOO_LARGE","message":"Use the returned pre-signed URL for large traces","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"422":{"description":"**DSP-081 INVALID_CRASH_TYPE** (HTTP 422) — crash_type not in fatal | anr | native | java","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-081","error":"INVALID_CRASH_TYPE","message":"crash_type not in fatal | anr | native | java","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/device/configuration":{"get":{"tags":["Device Lifecycle"],"summary":"Get remote configuration","operationId":"getConfiguration","description":"Returns the model-specific remote configuration: intervals, log level and feature flags.\n\n**Auth:** `device_token`; `device_id` must match the token.","security":[{"bearerAuth":[]}],"parameters":[{"name":"device_id","in":"query","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Current configuration","content":{"application/json":{"example":{"success":true,"device_id":"d8f3a1c2-4e56-7890-abcd-ef1234567890","configuration":{"heartbeat_interval_seconds":21600,"telemetry_enabled":true,"crash_report_enabled":true,"ota_check_interval_hours":24,"max_log_level":"warn","feature_flags":{"advanced_telemetry":false,"beta_ota":false}},"config_version":"v1.4","updated_at":"2026-05-30T12:00:00Z"}}}},"401":{"description":"**DSP-091 INVALID_DEVICE_TOKEN** (HTTP 401) — Token/device mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-091","error":"INVALID_DEVICE_TOKEN","message":"Token/device mismatch","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"404":{"description":"**DSP-090 DEVICE_NOT_FOUND** (HTTP 404) — device_id not in registry","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-090","error":"DEVICE_NOT_FOUND","message":"device_id not in registry","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/device/profile":{"get":{"tags":["Device Lifecycle"],"summary":"Get device profile","operationId":"getProfile","description":"Complete device profile with masked customer linkage and warranty status. `customer`/`warranty` are `null` when not linked/registered.\n\n**Auth:** `device_token` (own device) or `user_token` (linked device).","security":[{"bearerAuth":[]}],"parameters":[{"name":"device_id","in":"query","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Profile","content":{"application/json":{"example":{"success":true,"device":{"device_id":"d8f3a1c2-4e56-7890-abcd-ef1234567890","model_name":"Boltt Phantom 5G","device_type":"smartphone","activation_status":"active","activation_date":"2026-06-01T08:00:00Z","current_fw_version":"1.3.0","last_seen":"2026-06-12T10:30:00Z"},"customer":{"customer_id":"c1a2b3c4-5d6e-7f8a-9012-345678901234","mobile_number":"+91XXXXXX3210"},"warranty":{"warranty_id":"w9x8y7z6-5432-1098-abcd-ef0987654321","warranty_status":"active","warranty_expiry":"2027-06-01"}}}}},"403":{"description":"**DSP-101 UNAUTHORIZED_ACCESS** (HTTP 403) — Token does not own / is not linked to this device","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-101","error":"UNAUTHORIZED_ACCESS","message":"Token does not own / is not linked to this device","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"404":{"description":"**DSP-100 DEVICE_NOT_FOUND** (HTTP 404) — device_id not in registry","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-100","error":"DEVICE_NOT_FOUND","message":"device_id not in registry","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/device/unregister":{"post":{"tags":["Device Lifecycle"],"summary":"Unregister device (admin)","operationId":"unregisterDevice","description":"Deactivates a device (factory reset, resale, replacement). `reported_stolen` blacklists the IMEI instead. Device tokens are revoked immediately; an audit log entry is written.\n\n**Auth:** `admin_token` with `device:unregister` permission.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["device_id","reason"],"properties":{"device_id":{"type":"string","format":"uuid"},"reason":{"type":"string","enum":["customer_resale","factory_reset","device_replacement","reported_stolen","admin_action"]},"initiated_by":{"type":"string","example":"admin@boltt.com"},"notes":{"type":"string"}}}}}},"responses":{"200":{"description":"Device deactivated (or blacklisted for reported_stolen)","content":{"application/json":{"example":{"success":true,"device_id":"d8f3a1c2-4e56-7890-abcd-ef1234567890","status":"deactivated","deactivated_at":"2026-06-12T11:00:00Z","message":"Device unregistered successfully"}}}},"403":{"description":"**DSP-112 INSUFFICIENT_PERMISSIONS** (HTTP 403) — Admin role required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-112","error":"INSUFFICIENT_PERMISSIONS","message":"Admin role required","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"404":{"description":"**DSP-110 DEVICE_NOT_FOUND** (HTTP 404) — device_id not in registry","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-110","error":"DEVICE_NOT_FOUND","message":"device_id not in registry","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"409":{"description":"**DSP-111 DEVICE_ALREADY_DEACTIVATED** (HTTP 409) — Already deactivated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-111","error":"DEVICE_ALREADY_DEACTIVATED","message":"Already deactivated","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/auth/admin/login":{"post":{"tags":["Admin"],"summary":"Admin login (UAT token issuance)","operationId":"adminLogin","description":"Issues an 8-hour `admin_token`. In the spec admin tokens are issued by the DSP team per request — this endpoint plays that role in UAT. Seeded credentials are listed in the [credentials report](/report).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","password"],"properties":{"email":{"type":"string","example":"admin@boltt.com"},"password":{"type":"string"}}}}}},"responses":{"200":{"description":"admin_token issued","content":{"application/json":{"example":{"success":true,"admin_token":"eyJhbGciOiJIUzI1NiIs...","role":"release_manager","permissions":["device:unregister","ota:approve"],"token_expiry":"2026-06-12T19:00:00Z","message":"Admin authenticated successfully"}}}},"401":{"description":"**DSP-120 INVALID_ADMIN_CREDENTIALS** (HTTP 401) — Email or password incorrect","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-120","error":"INVALID_ADMIN_CREDENTIALS","message":"Email or password incorrect","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/admin/review/models":{"get":{"tags":["Console"],"summary":"List review-link config for all models","operationId":"consoleReviewModels","description":"Returns every model with its per-model review redirect URL and enabled flag.\n\n**Auth:** `admin_token` with `review:read`.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Models with review config","content":{"application/json":{"example":{"success":true,"models":[{"model_code":"BOLTT-PH5G-2026","model_name":"Boltt Phantom 5G","review_url":"https://www.flipkart.com/...","review_enabled":true,"is_active":true}]}}}},"403":{"description":"**DSP-112 INSUFFICIENT_PERMISSIONS** (HTTP 403) — review:read permission required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-112","error":"INSUFFICIENT_PERMISSIONS","message":"review:read permission required","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/admin/review/models/{code}":{"put":{"tags":["Console"],"summary":"Update a model review link","operationId":"consoleUpdateReviewModel","description":"Sets a model's review redirect URL (must be absolute https) and enabled flag. Writes an audit log entry.\n\n**Auth:** `admin_token` with `review:write`.","security":[{"bearerAuth":[]}],"parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string"},"example":"BOLTT-PH5G-2026"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["review_url"],"properties":{"review_url":{"type":"string","example":"https://www.flipkart.com/search?q=boltt"},"review_enabled":{"type":"boolean","example":true}}}}}},"responses":{"200":{"description":"Review link updated","content":{"application/json":{"example":{"success":true,"message":"Review link updated."}}}},"403":{"description":"**DSP-112 INSUFFICIENT_PERMISSIONS** (HTTP 403) — review:write permission required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-112","error":"INSUFFICIENT_PERMISSIONS","message":"review:write permission required","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"422":{"description":"**DSP-998 VALIDATION_FAILED** (HTTP 422) — review_url missing or not https (or DSP-003 unknown model)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-998","error":"VALIDATION_FAILED","message":"review_url missing or not https (or DSP-003 unknown model)","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/admin/review/taps":{"get":{"tags":["Console"],"summary":"Review-tap analytics","operationId":"consoleReviewTaps","description":"Aggregates `/r` redirector taps over a window: daily series, by model, by outcome, and total.\n\n**Auth:** `admin_token` with `review:read`.","security":[{"bearerAuth":[]}],"parameters":[{"name":"range","in":"query","required":false,"schema":{"type":"string","enum":["7d","30d","90d"],"default":"30d"}}],"responses":{"200":{"description":"Aggregated tap analytics","content":{"application/json":{"example":{"success":true,"range":"30d","total":312,"series":[{"date":"2026-06-01","count":12}],"by_model":[{"model_code":"BOLTT-PH5G-2026","count":140}],"by_outcome":[{"outcome":"redirected","count":300}]}}}},"403":{"description":"**DSP-112 INSUFFICIENT_PERMISSIONS** (HTTP 403) — review:read permission required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-112","error":"INSUFFICIENT_PERMISSIONS","message":"review:read permission required","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/admin/review/settings":{"get":{"tags":["Console"],"summary":"Get review settings","operationId":"consoleGetReviewSettings","description":"Returns the global review fallback URL and reminder cadence.\n\n**Auth:** `admin_token` with `review:read`.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Current review settings","content":{"application/json":{"example":{"success":true,"settings":{"fallback_url":"https://www.boltt.com/rate","reminder_days":10}}}}},"403":{"description":"**DSP-112 INSUFFICIENT_PERMISSIONS** (HTTP 403) — review:read permission required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-112","error":"INSUFFICIENT_PERMISSIONS","message":"review:read permission required","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}},"put":{"tags":["Console"],"summary":"Update review settings","operationId":"consoleUpdateReviewSettings","description":"Upserts the global review fallback URL (absolute https) and reminder cadence (integer 1–60 days). Writes an audit log entry.\n\n**Auth:** `admin_token` with `review:write`.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["fallback_url","reminder_days"],"properties":{"fallback_url":{"type":"string","example":"https://www.boltt.com/rate"},"reminder_days":{"type":"integer","minimum":1,"maximum":60,"example":10}}}}}},"responses":{"200":{"description":"Settings updated","content":{"application/json":{"example":{"success":true,"settings":{"fallback_url":"https://www.boltt.com/rate","reminder_days":10}}}}},"403":{"description":"**DSP-112 INSUFFICIENT_PERMISSIONS** (HTTP 403) — review:write permission required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-112","error":"INSUFFICIENT_PERMISSIONS","message":"review:write permission required","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"422":{"description":"**DSP-998 VALIDATION_FAILED** (HTTP 422) — fallback_url not https or reminder_days out of 1–60","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-998","error":"VALIDATION_FAILED","message":"fallback_url not https or reminder_days out of 1–60","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/admin/devices":{"get":{"tags":["Console"],"summary":"List devices","operationId":"consoleListDevices","description":"Paginated device registry. `q` matches imei1 / serial / model_code / model_name / mobile_number (ILIKE); `status` filters by activation status. Owner contact is masked.\n\n**Auth:** `admin_token` with `device:read`.","security":[{"bearerAuth":[]}],"parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":25}},{"name":"q","in":"query","required":false,"schema":{"type":"string"}},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["active","deactivated","blacklisted"]}}],"responses":{"200":{"description":"Device list","content":{"application/json":{"example":{"success":true,"items":[{"device_id":"uuid","imei1":"350000000000017","model_code":"BOLTT-PH5G-2026","model_name":"Boltt Pulse 5G","device_type":"smartphone","activation_status":"active","activation_date":"2026-05-01T00:00:00Z","current_fw_version":"1.4.0","last_seen":"2026-06-12T08:00:00Z","has_warranty":true,"customer_masked":"+91XXXXXX4521"}],"page":1,"limit":25,"total":63}}}},"403":{"description":"**DSP-112 INSUFFICIENT_PERMISSIONS** (HTTP 403) — device:read permission required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-112","error":"INSUFFICIENT_PERMISSIONS","message":"device:read permission required","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/admin/devices/{imei}":{"get":{"tags":["Console"],"summary":"Device profile","operationId":"consoleGetDevice","description":"Full device profile by imei1: device record (owner masked), activation, warranty (or null), latest telemetry, OTA history and recent crashes.\n\n**Auth:** `admin_token` with `device:read`.","security":[{"bearerAuth":[]}],"parameters":[{"name":"imei","in":"path","required":true,"schema":{"type":"string"},"example":"350000000000017"}],"responses":{"200":{"description":"Device profile","content":{"application/json":{"example":{"success":true,"device":{},"activation":{"activation_date":"2026-05-01T00:00:00Z","activation_status":"active","retailer_code":"RTL-001"},"warranty":null,"telemetry_latest":null,"ota_history":[],"crashes_recent":[]}}}},"404":{"description":"**DSP-110 DEVICE_NOT_FOUND** (HTTP 404) — No device with that imei1","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-110","error":"DEVICE_NOT_FOUND","message":"No device with that imei1","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/admin/activations":{"get":{"tags":["Console"],"summary":"Activation log","operationId":"consoleListActivations","description":"Activation log derived from the device registry (each device is a completed activation). Paginated, `q`/`status` filters. A `note` documents provenance.\n\n**Auth:** `admin_token` with `activation:read`.","security":[{"bearerAuth":[]}],"parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":25}},{"name":"q","in":"query","required":false,"schema":{"type":"string"}},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["active","deactivated","blacklisted"]}}],"responses":{"200":{"description":"Activation log","content":{"application/json":{"example":{"success":true,"note":"Derived from the device registry (each active device is a completed activation).","items":[{"imei1":"350000000000017","model_code":"BOLTT-PH5G-2026","model_name":"Boltt Pulse 5G","activation_date":"2026-05-01T00:00:00Z","activation_status":"active","retailer_code":"RTL-001","customer_masked":"+91XXXXXX4521"}],"page":1,"limit":25,"total":63}}}},"403":{"description":"**DSP-112 INSUFFICIENT_PERMISSIONS** (HTTP 403) — activation:read permission required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-112","error":"INSUFFICIENT_PERMISSIONS","message":"activation:read permission required","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/admin/warranties":{"get":{"tags":["Console"],"summary":"List warranties","operationId":"consoleListWarranties","description":"Warranties joined to devices + customers, ordered by soonest expiry. Owner contact masked. Paginated, `q`/`status` filters.\n\n**Auth:** `admin_token` with `warranty:read`.","security":[{"bearerAuth":[]}],"parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":25}},{"name":"q","in":"query","required":false,"schema":{"type":"string"}},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["active","expired","void","claimed"]}}],"responses":{"200":{"description":"Warranty list","content":{"application/json":{"example":{"success":true,"items":[{"warranty_id":"uuid","device_imei":"350000000000017","model_code":"BOLTT-PH5G-2026","customer_masked":"+91XXXXXX4521","purchase_date":"2026-05-01","retailer_code":"RTL-001","invoice_number":"INV-001","warranty_start":"2026-05-01","warranty_expiry":"2027-05-01","warranty_period_months":12,"warranty_status":"active"}],"page":1,"limit":25,"total":21}}}},"403":{"description":"**DSP-112 INSUFFICIENT_PERMISSIONS** (HTTP 403) — warranty:read permission required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-112","error":"INSUFFICIENT_PERMISSIONS","message":"warranty:read permission required","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/admin/warranties/{id}/void":{"post":{"tags":["Console"],"summary":"Void a warranty","operationId":"consoleVoidWarranty","description":"Sets `warranty_status` to `void`. Allowed from any status except `void` itself (re-voiding is a no-op conflict). Writes an audit log.\n\n**Auth:** `admin_token` with `warranty:admin`.\n\n**Errors:** DSP-998 for unknown `warranty_id` or illegal state; DSP-112 if permission missing.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"example":"wty-uuid-0001"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["reason"],"properties":{"reason":{"type":"string","example":"Fraudulent purchase — duplicate serial detected."}}}}}},"responses":{"200":{"description":"Warranty voided","content":{"application/json":{"example":{"success":true,"message":"Warranty wty-uuid-0001 has been voided.","warranty":{"warranty_id":"wty-uuid-0001","warranty_status":"void","warranty_expiry":"2027-05-01","warranty_period_months":12}}}}},"403":{"description":"**DSP-112 INSUFFICIENT_PERMISSIONS** (HTTP 403) — warranty:admin permission required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-112","error":"INSUFFICIENT_PERMISSIONS","message":"warranty:admin permission required","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"422":{"description":"**DSP-998 VALIDATION_FAILED** (HTTP 422) — Unknown warranty_id, missing reason, or warranty already void","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-998","error":"VALIDATION_FAILED","message":"Unknown warranty_id, missing reason, or warranty already void","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/admin/warranties/{id}/extend":{"post":{"tags":["Console"],"summary":"Extend a warranty","operationId":"consoleExtendWarranty","description":"Pushes `warranty_expiry` forward by `months` and increments `warranty_period_months`. Sets `warranty_status` to `active` (extending reinstates coverage). Allowed only when current status is `active` or `expired`. Writes an audit log.\n\n**Auth:** `admin_token` with `warranty:admin`.\n\n**Errors:** DSP-998 for unknown `warranty_id`, invalid `months`, or illegal state; DSP-112 if permission missing.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"example":"wty-uuid-0001"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["months"],"properties":{"months":{"type":"integer","minimum":1,"maximum":36,"example":12}}}}}},"responses":{"200":{"description":"Warranty extended","content":{"application/json":{"example":{"success":true,"message":"Warranty wty-uuid-0001 extended by 12 month(s). New expiry: 2028-05-01.","warranty":{"warranty_id":"wty-uuid-0001","warranty_status":"active","warranty_expiry":"2028-05-01","warranty_period_months":24}}}}},"403":{"description":"**DSP-112 INSUFFICIENT_PERMISSIONS** (HTTP 403) — warranty:admin permission required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-112","error":"INSUFFICIENT_PERMISSIONS","message":"warranty:admin permission required","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"422":{"description":"**DSP-998 VALIDATION_FAILED** (HTTP 422) — Unknown warranty_id, months out of range 1–36, or warranty is void/claimed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-998","error":"VALIDATION_FAILED","message":"Unknown warranty_id, months out of range 1–36, or warranty is void/claimed","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/admin/warranties/{id}/mark-claimed":{"post":{"tags":["Console"],"summary":"Mark a warranty as claimed","operationId":"consoleMarkWarrantyClaimed","description":"Sets `warranty_status` to `claimed`. Allowed unless the warranty is already `void` (voided warranties cannot be claimed) or already `claimed`. Writes an audit log.\n\n**Auth:** `admin_token` with `warranty:admin`.\n\n**Errors:** DSP-998 for unknown `warranty_id`, missing reason, or illegal state (void or already claimed); DSP-112 if permission missing.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"example":"wty-uuid-0001"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["reason"],"properties":{"reason":{"type":"string","example":"Screen replacement under warranty, invoice INV-2026-04401."}}}}}},"responses":{"200":{"description":"Warranty marked claimed","content":{"application/json":{"example":{"success":true,"message":"Warranty wty-uuid-0001 has been marked as claimed.","warranty":{"warranty_id":"wty-uuid-0001","warranty_status":"claimed","warranty_expiry":"2027-05-01","warranty_period_months":12}}}}},"403":{"description":"**DSP-112 INSUFFICIENT_PERMISSIONS** (HTTP 403) — warranty:admin permission required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-112","error":"INSUFFICIENT_PERMISSIONS","message":"warranty:admin permission required","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"422":{"description":"**DSP-998 VALIDATION_FAILED** (HTTP 422) — Unknown warranty_id, missing reason, warranty is void, or already claimed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-998","error":"VALIDATION_FAILED","message":"Unknown warranty_id, missing reason, warranty is void, or already claimed","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/admin/customers":{"get":{"tags":["Console"],"summary":"List customers","operationId":"consoleListCustomers","description":"Paginated device-owner directory. `q` matches full_name / mobile_number / email (ILIKE). Contact PII is always masked — `mobile_masked` and `email_masked` never carry a raw value. `device_count` / `warranty_count` are per-customer links. Ordered by newest first.\n\n**Auth:** `admin_token` with `device:read`.","security":[{"bearerAuth":[]}],"parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":25}},{"name":"q","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Customer list","content":{"application/json":{"example":{"success":true,"items":[{"customer_id":"uuid","full_name":"Ravi Kumar","mobile_masked":"+91XXXXXX4521","email_masked":"r***@example.in","otp_verified":true,"device_count":1,"warranty_count":1,"created_at":"2026-05-01T00:00:00Z"}],"page":1,"limit":25,"total":26}}}},"403":{"description":"**DSP-112 INSUFFICIENT_PERMISSIONS** (HTTP 403) — device:read permission required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-112","error":"INSUFFICIENT_PERMISSIONS","message":"device:read permission required","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/admin/customers/{id}":{"patch":{"tags":["Console"],"summary":"Suspend or activate a customer","operationId":"consolePatchCustomer","description":"Toggles the administrative `is_active` flag on a customer account — the owner-side analogue of deactivating a device. `is_active: false` marks the customer suspended; `is_active: true` reinstates them. This records the suspension for the console + audit trail; it does NOT itself revoke existing device tokens or block the OTP flow today (device-plane revocation is done via device unregister / IMEI block — gating the OTP flow on customer `is_active` is a roadmap item). The response never returns PII — only `customer_id` and `is_active`. Writes an audit log.\n\n**Auth:** `admin_token` with `device:unregister`.\n\n**Errors:** DSP-998 for unknown `customer_id` or missing/non-boolean `is_active`; DSP-112 if permission missing.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"example":"cust-uuid-0001"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["is_active"],"properties":{"is_active":{"type":"boolean","example":false}}}}}},"responses":{"200":{"description":"Customer updated","content":{"application/json":{"example":{"success":true,"message":"Customer cust-uuid-0001 has been deactivated.","customer":{"customer_id":"cust-uuid-0001","is_active":false}}}}},"403":{"description":"**DSP-112 INSUFFICIENT_PERMISSIONS** (HTTP 403) — device:unregister permission required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-112","error":"INSUFFICIENT_PERMISSIONS","message":"device:unregister permission required","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"422":{"description":"**DSP-998 VALIDATION_FAILED** (HTTP 422) — Unknown customer_id, or is_active missing/non-boolean","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-998","error":"VALIDATION_FAILED","message":"Unknown customer_id, or is_active missing/non-boolean","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/admin/retailers":{"get":{"tags":["Console"],"summary":"List retailers","operationId":"consoleListRetailers","description":"Authorised retail partners (reference table), ordered by retailer_code. Small list — not paginated.\n\n**Auth:** `admin_token` with `device:read`.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Retailer list","content":{"application/json":{"example":{"success":true,"retailers":[{"retailer_code":"RET-MH-001","name":"Boltt Store Mumbai","city":"Mumbai","state":"Maharashtra","is_active":true}]}}}},"403":{"description":"**DSP-112 INSUFFICIENT_PERMISSIONS** (HTTP 403) — device:read permission required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-112","error":"INSUFFICIENT_PERMISSIONS","message":"device:read permission required","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}},"post":{"tags":["Console"],"summary":"Create a retailer","operationId":"consoleCreateRetailer","description":"Creates a new retail partner record. `retailer_code` is the permanent primary key — 2–50 chars, starts with an alphanumeric character, body A-Z a-z 0-9 _ -. `name` is required and non-blank. `city`, `state` and `is_active` are optional (default: `is_active: true`). Duplicate `retailer_code` → DSP-998. Writes an audit log.\n\n**Auth:** `admin_token` with `warranty:admin`.\n\n**Errors:** DSP-998 for validation failures or duplicate `retailer_code`; DSP-112 if permission missing.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["retailer_code","name"],"properties":{"retailer_code":{"type":"string","example":"RET-DL-002"},"name":{"type":"string","example":"Boltt Store Delhi"},"city":{"type":"string","example":"New Delhi"},"state":{"type":"string","example":"Delhi"},"is_active":{"type":"boolean","default":true,"example":true}}}}}},"responses":{"200":{"description":"Retailer created","content":{"application/json":{"example":{"success":true,"message":"Retailer 'RET-DL-002' created.","retailer":{"retailer_code":"RET-DL-002","name":"Boltt Store Delhi","city":"New Delhi","state":"Delhi","is_active":true}}}}},"403":{"description":"**DSP-112 INSUFFICIENT_PERMISSIONS** (HTTP 403) — warranty:admin permission required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-112","error":"INSUFFICIENT_PERMISSIONS","message":"warranty:admin permission required","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"422":{"description":"**DSP-998 VALIDATION_FAILED** (HTTP 422) — Missing required fields, invalid retailer_code format, or duplicate retailer_code","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-998","error":"VALIDATION_FAILED","message":"Missing required fields, invalid retailer_code format, or duplicate retailer_code","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/admin/retailers/{code}":{"patch":{"tags":["Console"],"summary":"Update a retailer","operationId":"consolePatchRetailer","description":"Edits one or more of `name`, `city`, `state`, `is_active` on an existing retailer. At least one field is required. `city` and `state` can be cleared by sending `null` or an empty string. Unknown `code` → DSP-033 (RETAILER_CODE_NOT_FOUND). Writes an audit log.\n\n**Auth:** `admin_token` with `warranty:admin`.\n\n**Errors:** DSP-033 for unknown `code`; DSP-998 for empty patch or bad field types; DSP-112 if permission missing.","security":[{"bearerAuth":[]}],"parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string"},"example":"RET-MH-001"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","example":"Boltt Store Mumbai (Revised)"},"city":{"type":"string","nullable":true,"example":"Thane"},"state":{"type":"string","nullable":true,"example":"Maharashtra"},"is_active":{"type":"boolean","example":false}}}}}},"responses":{"200":{"description":"Retailer updated","content":{"application/json":{"example":{"success":true,"message":"Retailer 'RET-MH-001' updated.","retailer":{"retailer_code":"RET-MH-001","name":"Boltt Store Mumbai (Revised)","city":"Thane","state":"Maharashtra","is_active":true}}}}},"403":{"description":"**DSP-112 INSUFFICIENT_PERMISSIONS** (HTTP 403) — warranty:admin permission required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-112","error":"INSUFFICIENT_PERMISSIONS","message":"warranty:admin permission required","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"422":{"description":"**DSP-033 RETAILER_CODE_NOT_FOUND** (HTTP 422) — Unknown retailer_code, or DSP-998 if patch body is empty/invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-033","error":"RETAILER_CODE_NOT_FOUND","message":"Unknown retailer_code, or DSP-998 if patch body is empty/invalid","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/admin/models":{"get":{"tags":["Console"],"summary":"List models with their device configuration","operationId":"consoleListModels","description":"Every model with its live device-configuration blob (`configuration`), `config_version`, warranty period, review config and active flag. `device_type` is derived from registered devices (best-effort, may be null).\n\n**Auth:** `admin_token` with `model:read`.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Models with configuration","content":{"application/json":{"example":{"success":true,"models":[{"model_code":"BOLTT-PH5G-2026","model_name":"Boltt Phantom 5G","device_type":"smartphone","warranty_period_months":12,"config_version":"v1.4","configuration":{"heartbeat_interval_seconds":21600,"telemetry_enabled":true,"crash_report_enabled":true,"ota_check_interval_hours":24,"max_log_level":"warn","feature_flags":{"advanced_telemetry":false,"beta_ota":false}},"review_url":"https://www.flipkart.com/...","review_enabled":true,"is_active":true}]}}}},"403":{"description":"**DSP-112 INSUFFICIENT_PERMISSIONS** (HTTP 403) — model:read permission required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-112","error":"INSUFFICIENT_PERMISSIONS","message":"model:read permission required","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/admin/models/{code}/config":{"put":{"tags":["Console"],"summary":"Update a model’s device configuration","operationId":"consoleUpdateModelConfig","description":"Updates the device-configuration blob served to a model’s fleet via `GET /device/configuration` — changing device behaviour SERVER-SIDE with no app update. The submitted `configuration` is MERGED over the stored one (feature_flags merge key-by-key), so a partial patch only changes the keys it sends. `config_version` is bumped on every write (the trailing number is incremented, e.g. `v1.4` → `v1.5`). The change is reflected on the device endpoint immediately. Audited with the diff.\n\n**Auth:** `admin_token` with `model:write`.","security":[{"bearerAuth":[]}],"parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string"},"example":"BOLTT-PH5G-2026"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["configuration"],"properties":{"configuration":{"type":"object","properties":{"heartbeat_interval_seconds":{"type":"integer","minimum":300,"maximum":604800,"example":3600},"ota_check_interval_hours":{"type":"integer","minimum":1,"maximum":168,"example":24},"telemetry_enabled":{"type":"boolean","example":true},"crash_report_enabled":{"type":"boolean","example":true},"max_log_level":{"type":"string","enum":["error","warn","info","debug","verbose"],"example":"warn"},"feature_flags":{"type":"object","additionalProperties":{"type":"boolean"},"example":{"advanced_telemetry":true,"beta_ota":false}}}}}}}}},"responses":{"200":{"description":"Configuration updated; config_version bumped","content":{"application/json":{"example":{"success":true,"message":"Configuration for BOLTT-PH5G-2026 updated; config_version is now v1.5. Devices of this model will pick it up on their next configuration fetch.","model":{"model_code":"BOLTT-PH5G-2026","model_name":"Boltt Phantom 5G","config_version":"v1.5","configuration":{"heartbeat_interval_seconds":3600,"telemetry_enabled":true,"crash_report_enabled":true,"ota_check_interval_hours":24,"max_log_level":"warn","feature_flags":{"advanced_telemetry":true,"beta_ota":false}},"review_url":"https://www.flipkart.com/...","review_enabled":true,"is_active":true}}}}},"403":{"description":"**DSP-112 INSUFFICIENT_PERMISSIONS** (HTTP 403) — model:write permission required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-112","error":"INSUFFICIENT_PERMISSIONS","message":"model:write permission required","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"422":{"description":"**DSP-998 VALIDATION_FAILED** (HTTP 422) — invalid config value (or DSP-003 MODEL_NOT_SUPPORTED for an unknown model)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-998","error":"VALIDATION_FAILED","message":"invalid config value (or DSP-003 MODEL_NOT_SUPPORTED for an unknown model)","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/admin/ota":{"get":{"tags":["Console"],"summary":"List OTA updates","operationId":"consoleListOta","description":"OTA updates with a per-update `reports_summary` (total / success / failed). Paginated, `status` filter.\n\n**Auth:** `admin_token` with `ota:read`.","security":[{"bearerAuth":[]}],"parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":25}},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["draft","pending_approval","approved","signing","rolling_out","paused","completed","rolled_back"]}}],"responses":{"200":{"description":"OTA update list","content":{"application/json":{"example":{"success":true,"items":[{"update_id":"ota-001","model_code":"BOLTT-PH5G-2026","fw_version_target":"1.5.0","fw_version_min":"1.3.0","package_type":"delta","rollout_percentage":25,"release_status":"rolling_out","approved_by":"admin@boltt.com","approved_at":"2026-06-01T00:00:00Z","release_notes":"Stability fixes","created_at":"2026-05-30T00:00:00Z","reports_summary":{"total":5,"success":4,"failed":1}}],"page":1,"limit":25,"total":6}}}},"403":{"description":"**DSP-112 INSUFFICIENT_PERMISSIONS** (HTTP 403) — ota:read permission required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-112","error":"INSUFFICIENT_PERMISSIONS","message":"ota:read permission required","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}},"post":{"tags":["Console"],"summary":"Create an OTA release (draft)","operationId":"consoleCreateOta","description":"Creates a new OTA release as a `draft` at 0% rollout. Packaging artefacts (S3 url, file size, sha256) are simulated for UAT. The release must be approved before any device is offered it.\n\n**Auth:** `admin_token` with `ota:approve`.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["model_code","fw_version_target","fw_version_min","package_type"],"properties":{"model_code":{"type":"string","example":"BOLTT-PH5G-2026"},"fw_version_target":{"type":"string","example":"1.5.0"},"fw_version_min":{"type":"string","example":"1.3.0"},"package_type":{"type":"string","enum":["full","delta"]},"release_notes":{"type":"string","example":"Stability fixes"}}}}}},"responses":{"200":{"description":"Draft release created","content":{"application/json":{"example":{"success":true,"message":"Draft release ota-upd-20260613-a1b2c3 created for BOLTT-PH5G-2026. Approve it to make it eligible to devices.","release":{"update_id":"ota-upd-20260613-a1b2c3","model_code":"BOLTT-PH5G-2026","fw_version_target":"1.5.0","fw_version_min":"1.3.0","package_type":"delta","s3_package_url":"https://boltt-backend.rad0.dev/ota/packages/ota-upd-20260613-a1b2c3.zip","file_size_bytes":22341600,"sha256_checksum":"e3b0c44298fc1c14...","rollout_percentage":0,"release_status":"draft","release_notes":"Stability fixes","created_at":"2026-06-13T10:00:00Z"}}}}},"403":{"description":"**DSP-112 INSUFFICIENT_PERMISSIONS** (HTTP 403) — ota:approve permission required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-112","error":"INSUFFICIENT_PERMISSIONS","message":"ota:approve permission required","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"422":{"description":"**DSP-003 MODEL_NOT_SUPPORTED** (HTTP 422) — model_code is not a supported model","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-003","error":"MODEL_NOT_SUPPORTED","message":"model_code is not a supported model","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/admin/ota/{id}/approve":{"post":{"tags":["Console"],"summary":"Approve an OTA release","operationId":"consoleApproveOta","description":"Approves a `draft` or `pending_approval` release → `approved`, recording the approver and minting the (simulated) KMS signature. Approval is what makes the release eligible to devices via `/ota/check-update`. In production the signature is produced by the Boltt-owned AWS KMS asymmetric key; this UAT mints a `KMS-SIM-…` stand-in.\n\n**Auth:** `admin_token` with `ota:approve`.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"example":"ota-upd-20260601-003"}],"responses":{"200":{"description":"Release approved","content":{"application/json":{"example":{"success":true,"message":"Release ota-upd-20260601-003 approved by admin@boltt.com; it is now eligible to devices.","release":{"update_id":"ota-upd-20260601-003","release_status":"approved","approved_by":"admin@boltt.com","approved_at":"2026-06-13T10:00:00Z","kms_signature":"KMS-SIM-1a2b3c…"}}}}},"403":{"description":"**DSP-112 INSUFFICIENT_PERMISSIONS** (HTTP 403) — ota:approve permission required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-112","error":"INSUFFICIENT_PERMISSIONS","message":"ota:approve permission required","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"404":{"description":"**DSP-050 UPDATE_ID_NOT_FOUND** (HTTP 404) — No release with that update_id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-050","error":"UPDATE_ID_NOT_FOUND","message":"No release with that update_id","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"409":{"description":"**DSP-051 STATUS_ALREADY_REPORTED** (HTTP 409) — Release is already approved or further along","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-051","error":"STATUS_ALREADY_REPORTED","message":"Release is already approved or further along","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/admin/ota/{id}/rollout":{"patch":{"tags":["Console"],"summary":"Set OTA rollout percentage","operationId":"consoleRolloutOta","description":"Sets the rollout percentage (0..100) of an approved release. Allowed only from `approved`, `rolling_out`, or `paused`. A percentage > 0 moves the release to `rolling_out`; 100 marks it `completed`; 0 returns it to `approved`.\n\n**Auth:** `admin_token` with `ota:approve`.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"example":"ota-upd-20260601-003"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["rollout_percentage"],"properties":{"rollout_percentage":{"type":"integer","minimum":0,"maximum":100,"example":50}}}}}},"responses":{"200":{"description":"Rollout updated","content":{"application/json":{"example":{"success":true,"message":"Release ota-upd-20260601-003 rollout set to 50% (status: rolling_out).","release":{"update_id":"ota-upd-20260601-003","rollout_percentage":50,"release_status":"rolling_out"}}}}},"403":{"description":"**DSP-112 INSUFFICIENT_PERMISSIONS** (HTTP 403) — ota:approve permission required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-112","error":"INSUFFICIENT_PERMISSIONS","message":"ota:approve permission required","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"404":{"description":"**DSP-050 UPDATE_ID_NOT_FOUND** (HTTP 404) — No release with that update_id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-050","error":"UPDATE_ID_NOT_FOUND","message":"No release with that update_id","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"409":{"description":"**DSP-051 STATUS_ALREADY_REPORTED** (HTTP 409) — Release must be approved before rolling out","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-051","error":"STATUS_ALREADY_REPORTED","message":"Release must be approved before rolling out","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"422":{"description":"**DSP-998 VALIDATION_FAILED** (HTTP 422) — rollout_percentage out of range","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-998","error":"VALIDATION_FAILED","message":"rollout_percentage out of range","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/admin/ota/{id}/pause":{"post":{"tags":["Console"],"summary":"Pause a rolling-out OTA release","operationId":"consolePauseOta","description":"Pauses a `rolling_out` release → `paused`; devices stop being offered the update while paused.\n\n**Auth:** `admin_token` with `ota:approve`.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"example":"ota-upd-20260601-003"}],"responses":{"200":{"description":"Release paused","content":{"application/json":{"example":{"success":true,"message":"Release ota-upd-20260601-003 paused; devices will no longer be offered this update.","release":{"update_id":"ota-upd-20260601-003","release_status":"paused"}}}}},"403":{"description":"**DSP-112 INSUFFICIENT_PERMISSIONS** (HTTP 403) — ota:approve permission required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-112","error":"INSUFFICIENT_PERMISSIONS","message":"ota:approve permission required","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"404":{"description":"**DSP-050 UPDATE_ID_NOT_FOUND** (HTTP 404) — No release with that update_id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-050","error":"UPDATE_ID_NOT_FOUND","message":"No release with that update_id","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"409":{"description":"**DSP-051 STATUS_ALREADY_REPORTED** (HTTP 409) — Release is not currently rolling out","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-051","error":"STATUS_ALREADY_REPORTED","message":"Release is not currently rolling out","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/admin/ota/{id}/resume":{"post":{"tags":["Console"],"summary":"Resume a paused OTA release","operationId":"consoleResumeOta","description":"Resumes a `paused` release → `rolling_out`; it is offered to devices again.\n\n**Auth:** `admin_token` with `ota:approve`.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"example":"ota-upd-20260601-003"}],"responses":{"200":{"description":"Release resumed","content":{"application/json":{"example":{"success":true,"message":"Release ota-upd-20260601-003 resumed; it is rolling out again.","release":{"update_id":"ota-upd-20260601-003","release_status":"rolling_out"}}}}},"403":{"description":"**DSP-112 INSUFFICIENT_PERMISSIONS** (HTTP 403) — ota:approve permission required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-112","error":"INSUFFICIENT_PERMISSIONS","message":"ota:approve permission required","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"404":{"description":"**DSP-050 UPDATE_ID_NOT_FOUND** (HTTP 404) — No release with that update_id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-050","error":"UPDATE_ID_NOT_FOUND","message":"No release with that update_id","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"409":{"description":"**DSP-051 STATUS_ALREADY_REPORTED** (HTTP 409) — Release is not currently paused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-051","error":"STATUS_ALREADY_REPORTED","message":"Release is not currently paused","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/admin/ota/{id}/rollback":{"post":{"tags":["Console"],"summary":"Roll back an OTA release","operationId":"consoleRollbackOta","description":"Moves a deployed release to `rolled_back` — permanently removing it from the device offer queue. The device-facing `/ota/check-update` filters on `release_status IN (approved, rolling_out, completed)`, so `rolled_back` is auto-excluded with no change to device routes. `rollout_percentage` is left as-is (historical reach record). Allowed from `approved`, `rolling_out`, `paused`, or `completed`. Not allowed from `draft`, `pending_approval`, or `rolled_back` — those yield DSP-051. Writes an audit log.\n\n**Auth:** `admin_token` with `ota:approve`.\n\n**Errors:** DSP-050 if release not found; DSP-051 if the release is in an ineligible state; DSP-112 if permission missing.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"example":"ota-upd-20260601-003"}],"responses":{"200":{"description":"Release rolled back","content":{"application/json":{"example":{"success":true,"message":"Release ota-upd-20260601-003 has been rolled back (was: completed). Devices will no longer be offered this update.","release":{"update_id":"ota-upd-20260601-003","release_status":"rolled_back","rollout_percentage":100}}}}},"403":{"description":"**DSP-112 INSUFFICIENT_PERMISSIONS** (HTTP 403) — ota:approve permission required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-112","error":"INSUFFICIENT_PERMISSIONS","message":"ota:approve permission required","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"404":{"description":"**DSP-050 UPDATE_ID_NOT_FOUND** (HTTP 404) — No release with that update_id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-050","error":"UPDATE_ID_NOT_FOUND","message":"No release with that update_id","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"409":{"description":"**DSP-051 STATUS_ALREADY_REPORTED** (HTTP 409) — Release is not in a deployable state (draft/pending_approval/already rolled_back)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-051","error":"STATUS_ALREADY_REPORTED","message":"Release is not in a deployable state (draft/pending_approval/already rolled_back)","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/admin/telemetry":{"get":{"tags":["Console"],"summary":"Fleet telemetry & health","operationId":"consoleTelemetry","description":"Fleet vitals aggregated from the latest telemetry per device + crash counts, plus the most recent telemetry rows and crashes.\n\n**Auth:** `admin_token` with `telemetry:read`.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Fleet telemetry","content":{"application/json":{"example":{"success":true,"fleet":{"avg_battery":71.4,"avg_cpu":32.1,"avg_memory_free_mb":1820,"avg_storage_free_gb":24.6,"devices_reporting":60,"crash_count_24h":2,"network_mix":{"wifi":30,"4g":20,"5g":8,"offline":2}},"recent":[],"crashes":[]}}}},"403":{"description":"**DSP-112 INSUFFICIENT_PERMISSIONS** (HTTP 403) — telemetry:read permission required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-112","error":"INSUFFICIENT_PERMISSIONS","message":"telemetry:read permission required","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/admin/imei/{imei}/block":{"post":{"tags":["Console"],"summary":"Block (blacklist) a device","operationId":"consoleBlockImei","description":"Sets the device activation_status to `blacklisted` (CEIR / stolen / fraud). Once blacklisted, that device's device_token calls return DSP-071. Writes an audit log.\n\n**Auth:** `admin_token` with `device:block`.","security":[{"bearerAuth":[]}],"parameters":[{"name":"imei","in":"path","required":true,"schema":{"type":"string"},"example":"350000000000017"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["reason","reference"],"properties":{"reason":{"type":"string","enum":["stolen","fraud","ceir_directive","other"]},"reference":{"type":"string","example":"FIR-2026-00123"}}}}}},"responses":{"200":{"description":"Device blocked","content":{"application/json":{"example":{"success":true,"message":"Device 350000000000017 has been blocked (stolen). All API access is now revoked.","device":{"imei1":"350000000000017","activation_status":"blacklisted"}}}}},"404":{"description":"**DSP-110 DEVICE_NOT_FOUND** (HTTP 404) — No device with that imei1","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-110","error":"DEVICE_NOT_FOUND","message":"No device with that imei1","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"409":{"description":"**DSP-111 DEVICE_ALREADY_DEACTIVATED** (HTTP 409) — Device is already blocked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-111","error":"DEVICE_ALREADY_DEACTIVATED","message":"Device is already blocked","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"422":{"description":"**DSP-998 VALIDATION_FAILED** (HTTP 422) — reason not in enum or reference missing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-998","error":"VALIDATION_FAILED","message":"reason not in enum or reference missing","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/admin/imei/{imei}/unblock":{"post":{"tags":["Console"],"summary":"Unblock a device","operationId":"consoleUnblockImei","description":"Restores a blacklisted device to `active` (e.g. recovered / CEIR clearance). Writes an audit log.\n\n**Auth:** `admin_token` with `device:block`.","security":[{"bearerAuth":[]}],"parameters":[{"name":"imei","in":"path","required":true,"schema":{"type":"string"},"example":"350000000000017"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["reason","reference"],"properties":{"reason":{"type":"string","example":"recovered"},"reference":{"type":"string","example":"FIR-2026-00123"}}}}}},"responses":{"200":{"description":"Device unblocked","content":{"application/json":{"example":{"success":true,"message":"Device 350000000000017 has been unblocked and restored to active.","device":{"imei1":"350000000000017","activation_status":"active"}}}}},"404":{"description":"**DSP-110 DEVICE_NOT_FOUND** (HTTP 404) — No device with that imei1","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-110","error":"DEVICE_NOT_FOUND","message":"No device with that imei1","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"409":{"description":"**DSP-111 DEVICE_ALREADY_DEACTIVATED** (HTTP 409) — Device is not currently blocked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-111","error":"DEVICE_ALREADY_DEACTIVATED","message":"Device is not currently blocked","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"422":{"description":"**DSP-998 VALIDATION_FAILED** (HTTP 422) — reason or reference missing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-998","error":"VALIDATION_FAILED","message":"reason or reference missing","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/admin/imei/{imei}/unregister":{"post":{"tags":["Console"],"summary":"Unregister (deactivate) a device","operationId":"consoleUnregisterImei","description":"Sets the device `activation_status` to `deactivated`. Intended for legitimate de-listing — a device leaving the fleet, a trade-in, or an owner request. NOT the same as blocking (stolen / CEIR); blacklisted devices must be unblocked via `/admin/imei/{imei}/unblock` before they can be unregistered (blacklist takes precedence). `reason` is required; `reference` is optional (e.g. a trade-in or service ticket number). Writes an audit log.\n\n**Auth:** `admin_token` with `device:unregister`.\n\n**Errors:** DSP-110 if device not found; DSP-111 if already deactivated; DSP-998 if the device is blacklisted (unblock first); DSP-112 if permission missing.","security":[{"bearerAuth":[]}],"parameters":[{"name":"imei","in":"path","required":true,"schema":{"type":"string"},"example":"350000000000017"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["reason"],"properties":{"reason":{"type":"string","example":"Trade-in — customer returned device."},"reference":{"type":"string","example":"TIN-2026-00412"}}}}}},"responses":{"200":{"description":"Device unregistered","content":{"application/json":{"example":{"success":true,"message":"Device 350000000000017 has been unregistered and deactivated. All API access is now revoked.","device":{"imei1":"350000000000017","activation_status":"deactivated"}}}}},"403":{"description":"**DSP-112 INSUFFICIENT_PERMISSIONS** (HTTP 403) — device:unregister permission required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-112","error":"INSUFFICIENT_PERMISSIONS","message":"device:unregister permission required","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"404":{"description":"**DSP-110 DEVICE_NOT_FOUND** (HTTP 404) — No device with that imei1","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-110","error":"DEVICE_NOT_FOUND","message":"No device with that imei1","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"409":{"description":"**DSP-111 DEVICE_ALREADY_DEACTIVATED** (HTTP 409) — Device is already deactivated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-111","error":"DEVICE_ALREADY_DEACTIVATED","message":"Device is already deactivated","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"422":{"description":"**DSP-998 VALIDATION_FAILED** (HTTP 422) — Device is blacklisted — unblock it first before unregistering; or reason missing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-998","error":"VALIDATION_FAILED","message":"Device is blacklisted — unblock it first before unregistering; or reason missing","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/admin/users":{"get":{"tags":["Console"],"summary":"List admin users","operationId":"consoleListUsers","description":"Lists all admin users (ordered by email). Never returns `password_hash`.\n\n**Auth:** `admin_token` with `rbac:manage`.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Admin users","content":{"application/json":{"example":{"success":true,"users":[{"admin_id":"…","email":"admin@boltt.com","full_name":"Owner","role":"owner","permissions":["rbac:manage"],"is_active":true,"created_at":"2026-06-01T00:00:00Z"}]}}}},"403":{"description":"**DSP-112 INSUFFICIENT_PERMISSIONS** (HTTP 403) — rbac:manage permission required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-112","error":"INSUFFICIENT_PERMISSIONS","message":"rbac:manage permission required","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}},"post":{"tags":["Console"],"summary":"Create an admin user","operationId":"consoleCreateUser","description":"Creates an admin user. Permissions are derived from the supplied `role_key` (must exist). Writes an audit log; the password is never logged or returned.\n\n**Auth:** `admin_token` with `rbac:manage`.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","full_name","role_key","password"],"properties":{"email":{"type":"string","example":"rm.test@boltt.com"},"full_name":{"type":"string","example":"RM Test"},"role_key":{"type":"string","example":"review_manager"},"password":{"type":"string","minLength":8,"example":"Test#12345"}}}}}},"responses":{"200":{"description":"User created","content":{"application/json":{"example":{"success":true,"message":"Admin rm.test@boltt.com created with role review_manager.","user":{"admin_id":"…","email":"rm.test@boltt.com","role":"review_manager","is_active":true}}}}},"403":{"description":"**DSP-112 INSUFFICIENT_PERMISSIONS** (HTTP 403) — rbac:manage permission required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-112","error":"INSUFFICIENT_PERMISSIONS","message":"rbac:manage permission required","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"422":{"description":"**DSP-998 VALIDATION_FAILED** (HTTP 422) — Missing/invalid fields, unknown role_key, or duplicate email","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-998","error":"VALIDATION_FAILED","message":"Missing/invalid fields, unknown role_key, or duplicate email","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/admin/users/{id}":{"patch":{"tags":["Console"],"summary":"Update an admin user","operationId":"consoleUpdateUser","description":"Updates an admin user's role (recomputing permissions), active flag, and/or password. A guardrail prevents removing the last active `rbac:manage` administrator. Writes an audit log; the password value is never logged.\n\n**Auth:** `admin_token` with `rbac:manage`.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"role_key":{"type":"string","example":"ops_admin"},"is_active":{"type":"boolean","example":false},"password":{"type":"string","minLength":8}}}}}},"responses":{"200":{"description":"User updated","content":{"application/json":{"example":{"success":true,"message":"Admin user updated.","user":{"admin_id":"…","email":"rm.test@boltt.com","role":"ops_admin","is_active":true}}}}},"403":{"description":"**DSP-112 INSUFFICIENT_PERMISSIONS** (HTTP 403) — rbac:manage permission required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-112","error":"INSUFFICIENT_PERMISSIONS","message":"rbac:manage permission required","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"422":{"description":"**DSP-998 VALIDATION_FAILED** (HTTP 422) — Unknown id, unknown role_key, no fields, or last-owner guardrail","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-998","error":"VALIDATION_FAILED","message":"Unknown id, unknown role_key, no fields, or last-owner guardrail","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/admin/roles":{"get":{"tags":["Console"],"summary":"List roles + permission catalogue","operationId":"consoleListRoles","description":"Lists all roles with `user_count`, plus the full `catalogue` of permission keys for the matrix UI.\n\n**Auth:** `admin_token` with `rbac:manage`.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Roles + catalogue","content":{"application/json":{"example":{"success":true,"roles":[{"role_key":"owner","name":"Owner","description":"…","permissions":["rbac:manage"],"is_system":true,"updated_at":"2026-06-01T00:00:00Z","user_count":1}],"catalogue":["review:read","rbac:manage"]}}}},"403":{"description":"**DSP-112 INSUFFICIENT_PERMISSIONS** (HTTP 403) — rbac:manage permission required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-112","error":"INSUFFICIENT_PERMISSIONS","message":"rbac:manage permission required","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}},"post":{"tags":["Console"],"summary":"Create a custom role","operationId":"consoleCreateRole","description":"Creates a non-system role. `permissions` must be a subset of the catalogue. Writes an audit log.\n\n**Auth:** `admin_token` with `rbac:manage`.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["role_key","name","permissions"],"properties":{"role_key":{"type":"string","example":"tmp_role"},"name":{"type":"string","example":"Temp"},"description":{"type":"string"},"permissions":{"type":"array","items":{"type":"string"},"example":["review:read"]}}}}}},"responses":{"200":{"description":"Role created","content":{"application/json":{"example":{"success":true,"message":"Role tmp_role created.","role":{"role_key":"tmp_role","name":"Temp","permissions":["review:read"],"is_system":false}}}}},"403":{"description":"**DSP-112 INSUFFICIENT_PERMISSIONS** (HTTP 403) — rbac:manage permission required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-112","error":"INSUFFICIENT_PERMISSIONS","message":"rbac:manage permission required","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"422":{"description":"**DSP-998 VALIDATION_FAILED** (HTTP 422) — Bad role_key, unknown permissions, or duplicate role_key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-998","error":"VALIDATION_FAILED","message":"Bad role_key, unknown permissions, or duplicate role_key","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/admin/roles/{key}":{"patch":{"tags":["Console"],"summary":"Update a role","operationId":"consoleUpdateRole","description":"Edits a role's name/description/permissions (system roles included). Permission changes propagate to every admin user holding the role. A guardrail forbids removing `rbac:manage` from the `owner` role. Writes an audit log.\n\n**Auth:** `admin_token` with `rbac:manage`.","security":[{"bearerAuth":[]}],"parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string"},"example":"review_manager"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"permissions":{"type":"array","items":{"type":"string"},"example":["review:read","review:write","telemetry:read","audit:read"]}}}}}},"responses":{"200":{"description":"Role updated","content":{"application/json":{"example":{"success":true,"message":"Role review_manager updated.","role":{"role_key":"review_manager","permissions":["review:read"]},"users_updated":2}}}},"403":{"description":"**DSP-112 INSUFFICIENT_PERMISSIONS** (HTTP 403) — rbac:manage permission required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-112","error":"INSUFFICIENT_PERMISSIONS","message":"rbac:manage permission required","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"422":{"description":"**DSP-998 VALIDATION_FAILED** (HTTP 422) — Unknown key, unknown permissions, no fields, or owner guardrail","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-998","error":"VALIDATION_FAILED","message":"Unknown key, unknown permissions, no fields, or owner guardrail","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}},"delete":{"tags":["Console"],"summary":"Delete a custom role","operationId":"consoleDeleteRole","description":"Deletes a non-system role with no assigned users. Writes an audit log.\n\n**Auth:** `admin_token` with `rbac:manage`.","security":[{"bearerAuth":[]}],"parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string"},"example":"tmp_role"}],"responses":{"200":{"description":"Role deleted","content":{"application/json":{"example":{"success":true,"message":"Role tmp_role deleted."}}}},"403":{"description":"**DSP-112 INSUFFICIENT_PERMISSIONS** (HTTP 403) — rbac:manage permission required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-112","error":"INSUFFICIENT_PERMISSIONS","message":"rbac:manage permission required","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"422":{"description":"**DSP-998 VALIDATION_FAILED** (HTTP 422) — System role, unknown key, or users still assigned","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-998","error":"VALIDATION_FAILED","message":"System role, unknown key, or users still assigned","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/admin/audit":{"get":{"tags":["Console"],"summary":"Read the audit log","operationId":"consoleAudit","description":"Paginated audit-log read, newest-first, with optional exact-match `entity_type` and `action` filters.\n\n**Auth:** `admin_token` with `audit:read`.","security":[{"bearerAuth":[]}],"parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","default":1}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":25,"maximum":100}},{"name":"entity_type","in":"query","required":false,"schema":{"type":"string"},"example":"model"},{"name":"action","in":"query","required":false,"schema":{"type":"string"},"example":"review.link.update"}],"responses":{"200":{"description":"Audit log page","content":{"application/json":{"example":{"success":true,"items":[{"id":1,"actor":"admin@boltt.com","action":"review.link.update","entity_type":"model","entity_id":"BOLTT-PH5G-2026","detail":{},"created_at":"2026-06-12T10:30:00Z"}],"page":1,"limit":25,"total":1}}}},"403":{"description":"**DSP-112 INSUFFICIENT_PERMISSIONS** (HTTP 403) — audit:read permission required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-112","error":"INSUFFICIENT_PERMISSIONS","message":"audit:read permission required","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/admin/settings":{"get":{"tags":["Console"],"summary":"Read all platform settings","operationId":"consoleGetSettings","description":"Read all platform settings (review, branding, environment, integrations). `integrations` is declarative and read-only (the canonical region + service-readiness map). The `review` slice carries the global review fallback, reminder cadence, model-param mode and firmware URL host.\n\n**Auth:** `admin_token` — any authenticated admin; no specific permission is required.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"All platform settings (each slice merged over its defaults)","content":{"application/json":{"example":{"success":true,"settings":{"review":{"fallback_url":"https://www.boltt.com/rate","reminder_days":10,"model_param_mode":"both","firmware_url_host":"https://review.boltt.com/r"},"branding":{"console_title":"Boltt DSP Console","partner_label":"Boltt × Bitmisri","support_email":"support@boltt.com","interstitial_headline":"Taking you to rate your {device}"},"environment":{"mode":"uat"},"integrations":{"region":"AWS Mumbai (ap-south-1)","services":[{"key":"kms","label":"AWS KMS — OTA signing","uat":"simulated","production_ready":true,"note":"Boltt-owned signing key; UAT signs with a simulated key."}]}}}}}}}},"put":{"tags":["Console"],"summary":"Update platform settings","operationId":"consoleUpdateSettings","description":"Update platform settings (partial: any of `review`, `branding`, `environment`; `integrations` is read-only and ignored). Only the slices present in the body are validated and merged over the stored values; each changed slice writes one audit log entry. Returns the full merged settings (same shape as GET).\n\n**Validation:** `review.fallback_url` / `review.firmware_url_host` must be absolute https (and free of `<` `>` `\"` `'` / control chars); `review.reminder_days` an integer 1–60; `review.model_param_mode` one of `code | name | both`; `branding` text fields non-empty, length-bounded and free of `<`/`>`; `branding.support_email` a valid email; `environment.mode` one of `uat | production`.\n\n**Auth:** `admin_token` with `settings:write` (granted to `owner` and `ops_admin`).","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"Partial — send only the slices you want to change.","properties":{"review":{"type":"object","properties":{"fallback_url":{"type":"string","example":"https://www.boltt.com/rate"},"reminder_days":{"type":"integer","minimum":1,"maximum":60,"example":10},"model_param_mode":{"type":"string","enum":["code","name","both"],"example":"both"},"firmware_url_host":{"type":"string","example":"https://review.boltt.com/r"}}},"branding":{"type":"object","properties":{"console_title":{"type":"string","example":"Boltt DSP Console"},"partner_label":{"type":"string","example":"Boltt × Bitmisri"},"support_email":{"type":"string","example":"support@boltt.com"},"interstitial_headline":{"type":"string","example":"Taking you to rate your {device}"}}},"environment":{"type":"object","properties":{"mode":{"type":"string","enum":["uat","production"],"example":"production"}}}},"example":{"branding":{"console_title":"Boltt DSP Console"},"environment":{"mode":"production"}}}}}},"responses":{"200":{"description":"Settings updated; full merged settings returned","content":{"application/json":{"example":{"success":true,"settings":{"review":{"fallback_url":"https://www.boltt.com/rate","reminder_days":10,"model_param_mode":"both","firmware_url_host":"https://review.boltt.com/r"},"branding":{"console_title":"Boltt DSP Console","partner_label":"Boltt × Bitmisri","support_email":"support@boltt.com","interstitial_headline":"Taking you to rate your {device}"},"environment":{"mode":"production"},"integrations":{"region":"AWS Mumbai (ap-south-1)","services":[{"key":"kms","label":"AWS KMS — OTA signing","uat":"simulated","production_ready":true,"note":"Boltt-owned signing key; UAT signs with a simulated key."}]}}}}}},"403":{"description":"**DSP-112 INSUFFICIENT_PERMISSIONS** (HTTP 403) — settings:write permission required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-112","error":"INSUFFICIENT_PERMISSIONS","message":"settings:write permission required","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}},"422":{"description":"**DSP-998 VALIDATION_FAILED** (HTTP 422) — Invalid value — non-https URL, reminder_days out of 1–60, bad model_param_mode, bad environment.mode, or < / > in a branding field (see details[])","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error_code":"DSP-998","error":"VALIDATION_FAILED","message":"Invalid value — non-https URL, reminder_days out of 1–60, bad model_param_mode, bad environment.mode, or < / > in a branding field (see details[])","trace_id":"trace-20260612-abc12345","timestamp":"2026-06-12T10:30:00Z"}}}}}}},"/admin/readiness":{"get":{"tags":["Console"],"summary":"Go-live readiness checklist","operationId":"consoleReadiness","description":"Go-live readiness checklist computed from live data. Introspects the database and platform settings and returns a short checklist (store links, review fallback, OTA approval, environment mode, tailored role, active devices, branding), each with an advisory `status` of `ok` or `warn` and a `fix_hash` deep-link. A `warn` is advisory, never an error — several `warn` states (branded default review link, UAT mode pre-launch) are perfectly valid. Exposes no PII — only counts and config values.\n\n**Auth:** `admin_token` — any authenticated admin; no specific permission is required.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Readiness checklist","content":{"application/json":{"example":{"success":true,"ready_count":4,"total":7,"items":[{"key":"environment","label":"Environment mode","status":"warn","detail":"Running in UAT — flip to production when go-live is signed off.","fix_hash":"#/settings"},{"key":"active_devices","label":"Active devices","status":"ok","detail":"62 device(s) are activated.","fix_hash":"#/devices"}]}}}}}}},"/health":{"get":{"tags":["Platform"],"summary":"Service health","operationId":"health","responses":{"200":{"description":"Service and database status","content":{"application/json":{"example":{"success":true,"service":"boltt-dsp-uat","version":"1.0.0","database":"up","uptime_seconds":12345,"timestamp":"2026-06-12T10:30:00Z"}}}}}}}}}