{"openapi":"3.1.0","info":{"title":"stagemod API","version":"1.0.0","summary":"Headless, API-first ECU tuning.","description":"Private, API-first ECU tuning platform. Every step of the tuning workflow — discover ECUs and the machine-readable tuning catalogue, author reusable mappacks, create per-customer projects, upload original reads, attach datalogs and fault codes, and emit safe tuned versions — is driveable headlessly with a single Bearer API key.\n\nThe platform is AI-driven: the catalogue is curated through the API (create/update/delete rules and categories under `catalogue:write`), and every project carries an append-only AI decision graph (`/projects/{id}/decisions` and `/projects/{id}/graph`) that records the agent's observations, hypotheses, decisions, changes, and results as typed nodes linked by typed edges (`caused`, `resulted_in`, `derived_from`, `supersedes`, `addresses`, `references`, `informed_by`).\n\nConventions:\n- All JSON keys are `snake_case`.\n- IDs are prefixed, k-sortable strings (`ecu_…`, `mp_…`, `prj_…`, `ver_…`, `log_…`, `dtc_…`, `cat_…` / rule ids, `dec_…`, `key_…`).\n- Timestamps are RFC 3339 UTC strings (`created_at`, `updated_at`).\n- Lists are cursor-paginated (`page_size`, `cursor`) and wrapped in a `list` envelope.\n- Errors are RFC 9457 `application/problem+json`.\n- Each operation declares the API-key scope it needs via `x-required-scope`.\n\nAccess: the **entire `/api/v1` surface is admin-only** — every endpoint (session cookie OR API key) requires an admin principal. Members never call the API directly; they interact with their own tickets exclusively through the session web UI. The ticket endpoints below are therefore the admin/AI control surface for the support queue.","contact":{"name":"stagemod","url":"https://stagemod.com"}},"servers":[{"url":"https://stagemod.com/api/v1","description":"Production"}],"tags":[{"name":"Meta","description":"Health, identity, and the OpenAPI document."},{"name":"Catalogue","description":"Machine-readable tuning knowledge with hard limits the API enforces."},{"name":"ECUs","description":"ECU hardware records."},{"name":"Mappacks","description":"The MAP CATALOGUE (descriptive): registered map definitions per ECU software — named sectors, addresses, axes, checksum regions. Powers the 2D/3D viewer. /definitions is the deprecated alias. See docs/concepts.md."},{"name":"Projects","description":"Per-user customer/vehicle tuning jobs."},{"name":"Original File","description":"The original ECU read attached to a project."},{"name":"Files","description":"Token-gated binary transfer through the Worker (no S3 presign). Init routes return a short-lived HMAC-signed URL; the agent PUTs/GETs raw bytes here. No Authorization header — the token is the credential."},{"name":"Versions","description":"Tuned outputs derived from the original or a prior version."},{"name":"Logs","description":"Performance datalogs and their parsed parameter series."},{"name":"DTCs","description":"Diagnostic trouble codes attached to a project."},{"name":"Decisions","description":"The per-project AI decision graph: typed reasoning nodes and the typed edges between them."},{"name":"Operations","description":"The OPERATION taxonomy — the verbs (stage1, dpf_off, egr_off…) a Solution is classified by; recipes are the per-ECU concrete Solutions."},{"name":"Detection","description":"ECU detection: score an uploaded read or its identifiers against the catalogue."},{"name":"API Keys","description":"Per-user API keys and their scopes."},{"name":"Tickets","description":"Admin/AI support queue. Members create and follow their own tickets via the web UI only; this API surface is admin-only. Resolution always carries a mandatory note and emits an AI decision-graph result node."},{"name":"Solutions","description":"Applicable one-click mods for an ECU/SW: authored tune-packages + operation recipes, classified by Operation, quality-ranked. GET ?ecu= = ranked reuse; POST = author a pack."},{"name":"Definitions","description":"DEPRECATED alias of /mappacks (the map catalogue). Use /mappacks; these routes remain for back-compat."},{"name":"Maps","description":"Project map decoding for the 2D/3D viewer: list a project ECU maps + decode a map cell grid from the binary."},{"name":"Identifiers","description":"Read-out identifiers mined from a flash (VIN/SW/HW/part): catalogue + reuse query."},{"name":"Sources","description":"Provenance source catalogue."},{"name":"Datalogs","description":"Datalog headroom analysis + readiness."},{"name":"Audit","description":"Completeness + readiness scorecards."},{"name":"Code-notes","description":"The logic/strategy knowledge layer (distinct from Mappacks=calibration): how features work, routine locations, checksum schemes, AI/Ghidra findings — keyed by ECU/family/SW, tagged, reusable. See docs/architecture.md."}],"security":[{"bearerApiKey":[]}],"paths":{"/health":{"get":{"operationId":"getHealth","tags":["Meta"],"summary":"Liveness probe","description":"Unauthenticated liveness check.","security":[],"x-required-scope":null,"responses":{"200":{"description":"Service is live.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"},"example":{"object":"health","status":"ok","version":"1","time":"2026-06-02T14:30:00Z"}}}}}}},"/openapi.json":{"get":{"operationId":"getOpenApi","tags":["Meta"],"summary":"OpenAPI 3.1 document","description":"Returns this OpenAPI 3.1 specification as JSON. Unauthenticated, so an agent can self-configure before holding a key.","security":[],"x-required-scope":null,"responses":{"200":{"description":"The OpenAPI document.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}}},"/me":{"get":{"operationId":"getMe","tags":["Meta"],"summary":"Current identity and scopes","description":"Returns the authenticated user, role, auth method, and the effective scopes of the presented credential. Any valid credential may call this.","x-required-scope":null,"responses":{"200":{"description":"Caller identity.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Me"},"example":{"object":"me","user":{"id":"usr_01HXAGENT","email":"agent@example.com","name":"Tuning Agent","role":"member"},"via":"api-key","scopes":["catalogue:read","mappacks:read","mappacks:write","projects:read","projects:write"],"api_key_id":"key_01HXKEY"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/catalogue":{"get":{"operationId":"listCatalogueRules","tags":["Catalogue"],"summary":"List tuning rules","description":"List machine-readable tuning rules. Each rule carries hard limits the API enforces on mappack/version creation plus human-readable guidance the agent reasons over.","x-required-scope":"catalogue:read","parameters":[{"name":"fuel_type","in":"query","schema":{"$ref":"#/components/schemas/CatalogueFuelType"}},{"name":"category","in":"query","description":"Filter by tuning category id/key.","schema":{"type":"string"}},{"name":"tags","in":"query","description":"Comma-separated tags; matches rules carrying any of them.","schema":{"type":"string"}},{"name":"q","in":"query","description":"Free-text search across title, param, and tags.","schema":{"type":"string"}},{"$ref":"#/components/parameters/PageSize"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"$ref":"#/components/responses/CatalogueRuleList"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}},"post":{"operationId":"createCatalogueRule","tags":["Catalogue"],"summary":"Create a tuning rule","description":"Add a machine-readable tuning rule to the catalogue. `category_id` must resolve to an existing tuning category. `severity: hard` rules define hard limits the API then enforces on mappack/version creation.","x-required-scope":"catalogue:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CatalogueRuleCreate"}}}},"responses":{"201":{"description":"The created tuning rule.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CatalogueRule"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/catalogue/categories":{"get":{"operationId":"listCatalogueCategories","tags":["Catalogue"],"summary":"List tuning categories","description":"List the tuning categories rules are grouped under.","x-required-scope":"catalogue:read","parameters":[{"$ref":"#/components/parameters/PageSize"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"$ref":"#/components/responses/CatalogueCategoryList"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}},"post":{"operationId":"createCatalogueCategory","tags":["Catalogue"],"summary":"Create a tuning category","description":"Add a tuning category that rules can be grouped under.","x-required-scope":"catalogue:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CatalogueCategoryCreate"}}}},"responses":{"201":{"description":"The created tuning category.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CatalogueCategory"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/catalogue/{id}":{"parameters":[{"$ref":"#/components/parameters/CatalogueId"}],"get":{"operationId":"getCatalogueRule","tags":["Catalogue"],"summary":"Get one tuning rule","x-required-scope":"catalogue:read","responses":{"200":{"description":"The tuning rule.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CatalogueRule"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"operationId":"updateCatalogueRule","tags":["Catalogue"],"summary":"Update a tuning rule","description":"Partial update of a tuning rule. Bumps `version`. Re-validates that `category_id` (if changed) resolves.","x-required-scope":"catalogue:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CatalogueRuleUpdate"}}}},"responses":{"200":{"description":"The updated tuning rule.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CatalogueRule"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}},"delete":{"operationId":"deleteCatalogueRule","tags":["Catalogue"],"summary":"Delete a tuning rule","description":"Delete (deactivate) a tuning rule. Rules still cited by mappacks may be soft-deactivated rather than hard-deleted.","x-required-scope":"catalogue:write","responses":{"204":{"description":"Deleted. No content."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/ecus":{"get":{"operationId":"listEcus","tags":["ECUs"],"summary":"List ECUs","x-required-scope":"catalogue:read","parameters":[{"name":"vendor","in":"query","description":"Filter by ECU vendor (e.g. Bosch).","schema":{"type":"string"}},{"name":"model","in":"query","description":"Filter by ECU model (e.g. EDC17C64).","schema":{"type":"string"}},{"name":"fuel_type","in":"query","schema":{"$ref":"#/components/schemas/FuelType"}},{"name":"q","in":"query","description":"Free-text search across vendor, model, family.","schema":{"type":"string"}},{"$ref":"#/components/parameters/PageSize"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"$ref":"#/components/responses/EcuList"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}},"post":{"operationId":"createEcu","tags":["ECUs"],"summary":"Create an ECU (admin)","description":"Admin-only. Requires the `admin` scope.","x-required-scope":"admin","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EcuCreate"}}}},"responses":{"201":{"description":"The created ECU.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ecu"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/ecus/{id}":{"parameters":[{"$ref":"#/components/parameters/EcuId"}],"get":{"operationId":"getEcu","tags":["ECUs"],"summary":"Get one ECU","x-required-scope":"catalogue:read","responses":{"200":{"description":"The ECU.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ecu"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/ecus/{id}/mappacks":{"parameters":[{"$ref":"#/components/parameters/EcuId"}],"get":{"operationId":"listEcuMappacks","tags":["ECUs","Mappacks"],"summary":"The map catalogues (definitions) for one ECU. (Applicable solutions: GET /solutions?ecu=.)","x-required-scope":"definitions:read","parameters":[{"name":"stage","in":"query","schema":{"type":"string"}},{"name":"fuel_type","in":"query","schema":{"$ref":"#/components/schemas/FuelType"}},{"name":"status","in":"query","schema":{"$ref":"#/components/schemas/MappackStatus"}},{"$ref":"#/components/parameters/PageSize"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"$ref":"#/components/responses/MappackList"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/operations":{"get":{"operationId":"listOperations","tags":["Operations"],"summary":"List operations (the verb catalogue)","description":"List the ECU-independent operation verbs the agent programs against (`stage1`, `egr_off`, `dpf_off`, `pops_bangs`, `dtc_off`, `vmax_off`, …). Each operation carries its composition metadata — `requires`, `conflicts_with`, `supersedes`, `apply_order`, `is_staged`/`stage_level`, `inverse_code`, `reversible`, `affects_emissions`, `off_road_only` — but never any map values (those live in per-ECU recipes).","x-required-scope":"operations:read","parameters":[{"name":"kind","in":"query","schema":{"$ref":"#/components/schemas/OperationKind"}},{"name":"category","in":"query","description":"Filter by tuning category key (e.g. fueling, boost, egr, dpf).","schema":{"type":"string"}},{"name":"fuel_scope","in":"query","schema":{"$ref":"#/components/schemas/FuelScope"}},{"name":"q","in":"query","description":"Free-text search across code, label, and summary.","schema":{"type":"string"}},{"$ref":"#/components/parameters/PageSize"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"$ref":"#/components/responses/OperationList"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}},"post":{"operationId":"createOperation","tags":["Operations"],"summary":"Create an operation (admin)","description":"Admin-only. Define a new operation verb. `code` is the stable agent-facing key and must be unique.","x-required-scope":"admin","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationCreate"}}}},"responses":{"201":{"description":"The created operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Operation"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/ecus/{code}/operations":{"parameters":[{"$ref":"#/components/parameters/EcuCode"}],"get":{"operationId":"listEcuOperations","tags":["Operations","ECUs"],"summary":"List operation availability for an ECU","description":"Resolve, for the ECU identified by `code` (its stable `code`/`variant`, not the `ecu_…` id), which operations are available and how each would be realized. Each entry's `availability` is `deterministic` when an active per-ECU recipe exists, `ai_synthesized` when the ECU is in scope but no recipe exists yet (the agent must pass an `inline_recipe` on apply), or `unsupported`. Optionally scope to a specific calibration via `software_id`.","x-required-scope":"operations:read","parameters":[{"name":"software_id","in":"query","description":"Narrow recipe lookup to a specific ECU calibration.","schema":{"type":"string"}},{"name":"kind","in":"query","schema":{"$ref":"#/components/schemas/OperationKind"}}],"responses":{"200":{"description":"Per-operation availability for the ECU.","content":{"application/json":{"schema":{"type":"object","properties":{"object":{"const":"operation_availability_list"},"ecu":{"$ref":"#/components/schemas/Ecu"},"data":{"type":"array","items":{"$ref":"#/components/schemas/OperationAvailability"}}},"required":["object","data"]},"example":{"object":"operation_availability_list","data":[{"object":"operation_availability","code":"egr_off","operation_id":"op_01HXEGR","availability":"deterministic","recipe_id":"rcp_01HXEGR","confidence":1,"is_staged":false},{"object":"operation_availability","code":"dpf_off","operation_id":"op_01HXDPF","availability":"deterministic","recipe_id":"rcp_01HXDPF","confidence":1,"is_staged":false},{"object":"operation_availability","code":"stage1","operation_id":"op_01HXST1","availability":"ai_synthesized","recipe_id":null,"confidence":null,"is_staged":true,"stage_level":1}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/ecus/detect":{"post":{"operationId":"detectEcu","tags":["Detection","ECUs"],"summary":"Detect an ECU from a read / identifiers","description":"Score the supplied identifiers (any of `code`, `sw_number`, `hw_number`, `file_size`, `ident`, `filename`, `vin`) against the in-scope ECU catalogue and its software/signature records. Returns ranked candidates, best first. An exact `code`/`variant` match ranks highest; software-number and signature hits accumulate weighted scores. An empty array means no candidate matched.","x-required-scope":"catalogue:read","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DetectInput"}}}},"responses":{"200":{"description":"Ranked detection candidates (may be empty).","content":{"application/json":{"schema":{"type":"object","properties":{"object":{"const":"detection_result"},"data":{"type":"array","items":{"$ref":"#/components/schemas/DetectionCandidate"}}},"required":["object","data"]},"example":{"object":"detection_result","data":[{"object":"detection_candidate","score":190,"matched":["code_exact","sw_number"],"ecu":{"object":"ecu","id":"ecu_01HXY2","code":"EDC17C64","variant":"EDC17C64"},"software":{"id":"sw_01HX","sw_number":"03L906022AB"}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/operations/recipes":{"get":{"operationId":"listOperationRecipes","tags":["Operations"],"summary":"List operation recipes","description":"List per-ECU operation recipes — the deterministic, described map-change templates that realize an operation on a specific ECU (optionally a specific calibration). Filterable by `ecu_id`, `operation_id`, `software_id`, `status`, and `source`.","x-required-scope":"operations:read","parameters":[{"name":"ecu_id","in":"query","schema":{"type":"string"}},{"name":"operation_id","in":"query","schema":{"type":"string"}},{"name":"software_id","in":"query","schema":{"type":"string"}},{"name":"status","in":"query","schema":{"$ref":"#/components/schemas/RecipeStatus"}},{"name":"source","in":"query","schema":{"$ref":"#/components/schemas/RecipeSource"}},{"$ref":"#/components/parameters/PageSize"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"$ref":"#/components/responses/OperationRecipeList"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}},"post":{"operationId":"createOperationRecipe","tags":["Operations"],"summary":"Author an operation recipe","description":"Author a deterministic recipe for `(ecu_id, operation_id[, software_id])`. Every `catalogue_refs` entry and every `map_changes.changes[].constraint_ref` must resolve to a real tuning rule, and the `safety_envelope` must stay within those rules' hard limits (else 422). Recipes default to `status: \"active\"`; AI-synthesized recipes are typically written as `candidate`.","x-required-scope":"operations:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationRecipeCreate"}}}},"responses":{"201":{"description":"The created recipe.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationRecipe"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/operations/recipes/{id}":{"parameters":[{"$ref":"#/components/parameters/RecipeId"}],"patch":{"operationId":"updateOperationRecipe","tags":["Operations"],"summary":"Update an operation recipe","description":"Partial update. Bumps `version`. Re-validates catalogue refs and the safety envelope. A common use is promoting an AI-synthesized `candidate` recipe to `active` once reviewed.","x-required-scope":"operations:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationRecipeUpdate"}}}},"responses":{"200":{"description":"The updated recipe.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationRecipe"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}},"delete":{"operationId":"deleteOperationRecipe","tags":["Operations"],"summary":"Delete (deprecate) an operation recipe","description":"Remove a recipe. Active recipes are typically soft-deprecated rather than hard-deleted so existing versions remain reproducible.","x-required-scope":"operations:write","responses":{"204":{"description":"Deleted. No content."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/operations/recipes/from-mappack":{"post":{"operationId":"synthesizeRecipesFromMappack","tags":["Operations"],"summary":"Derive recipes from a mappack","description":"Normalize an existing mappack into one or more per-ECU operation recipes (source `from_mappack`). The mappack's `map_changes` and `safety_envelope` seed the recipe(s) for the operation(s) the mappack is mapped to (see `/mappacks/{id}/operations`); the mappack's `ecu_id` becomes the recipe's `ecu_id`.","x-required-scope":"operations:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"mappack_id":{"type":"string"},"operation_id":{"type":"string","description":"Optional: restrict to one mapped operation; otherwise all mapped operations are derived."},"status":{"$ref":"#/components/schemas/RecipeStatus"}},"required":["mappack_id"]}}}},"responses":{"201":{"description":"The derived recipe(s).","content":{"application/json":{"schema":{"type":"object","properties":{"object":{"const":"list"},"data":{"type":"array","items":{"$ref":"#/components/schemas/OperationRecipe"}}},"required":["object","data"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/catalogue/vehicles":{"get":{"operationId":"listCatalogueVehicles","tags":["Catalogue"],"summary":"List vehicle makes in the catalogue","description":"List the vehicle makes covered by the in-scope ECU catalogue, with a per-make count of distinct models/ECUs. Use it to navigate the vehicle taxonomy before drilling into `/catalogue/vehicles/{make}`.","x-required-scope":"catalogue:read","parameters":[{"name":"q","in":"query","description":"Free-text filter on make.","schema":{"type":"string"}}],"responses":{"200":{"description":"Vehicle makes.","content":{"application/json":{"schema":{"type":"object","properties":{"object":{"const":"vehicle_make_list"},"data":{"type":"array","items":{"type":"object","properties":{"make":{"type":"string","example":"VW"},"ecu_count":{"type":"integer"},"model_count":{"type":"integer"}},"required":["make"]}}},"required":["object","data"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/catalogue/vehicles/{make}":{"parameters":[{"name":"make","in":"path","required":true,"schema":{"type":"string","example":"VW"}}],"get":{"operationId":"getCatalogueVehicleMake","tags":["Catalogue"],"summary":"List models and ECUs for a make","description":"List the models covered for a single make and the in-scope ECUs (with their `code`/`variant` and `supported_operations`) behind each, so the agent can map a customer vehicle to an ECU and the operations it supports.","x-required-scope":"catalogue:read","responses":{"200":{"description":"Models and ECUs for the make.","content":{"application/json":{"schema":{"type":"object","properties":{"object":{"const":"vehicle_make"},"make":{"type":"string","example":"VW"},"models":{"type":"array","items":{"type":"object","properties":{"model":{"type":"string","example":"Golf VII GTD"},"ecus":{"type":"array","items":{"$ref":"#/components/schemas/Ecu"}}},"required":["model"]}}},"required":["object","make","models"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/projects/{id}/operations":{"parameters":[{"$ref":"#/components/parameters/ProjectId"}],"post":{"operationId":"applyProjectOperations","tags":["Operations","Projects"],"summary":"Apply / revert operations (one-click)","description":"The one-click engine. Compute the project's new op-state from its current state by `apply[]`-ing and `revert[]`-ing operation codes, then resolve every operation's recipe (deterministic) or `inline_recipes[]` (AI-synthesized), compose them into ONE effective change-set, validate it against cited catalogue hard limits, and — unless `dry_run` — emit a new version (always derived from ORI) plus decision-graph nodes.\n\nKey semantics: applying an `inverse` op clears its counterpart (`dpf_on` removes `dpf_off`); `supersedes` collapses ladders (`stage2` drops `stage1`); `return_to_ori: true` produces an empty op-state (a clean ORI-equivalent version). Non-deterministic operations (typically stages) have no recipe and MUST be supplied via `inline_recipes[]` synthesized by the agent from the rich ECU record + catalogue; otherwise they surface as a `missing_recipe` warning (dry-run) or a 422 (commit).\n\nThe response always carries `operation_result` (the resolved op-state, dropped ops, per-op `resolved[]`, the merged `effective_changes`, `shadowed[]` overrides, tightened `envelope`, `expected_gains`, `warnings[]`, and `validation`). On a non-dry-run, `version` is the persisted version.","x-required-scope":"projects:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationApplyRequest"},"examples":{"dry_run_stack":{"summary":"Dry-run a deactivation + stage stack","value":{"apply":["egr_off","dpf_off","stage1"],"dry_run":true,"inline_recipes":[{"code":"stage1","changes":[{"domain":"fuel","table":"injection_quantity_limiter","operation":"scale","value_pct":12,"constraint_ref":"dsl-egt-pre-turbo-sustained"},{"domain":"boost","table":"boost_target","operation":"offset","value_kpa":20,"constraint_ref":"dsl-boost-vgt"}],"safety_envelope":{"egt_max_c":720,"afr_min":13.2},"catalogue_refs":["dsl-egt-pre-turbo-sustained","dsl-boost-vgt"],"confidence":0.82}]}},"return_to_ori":{"summary":"Produce a clean ORI-equivalent version","value":{"return_to_ori":true,"label":"Return to stock"}},"revert_one":{"summary":"Revert a single op off the current state","value":{"revert":["dpf_off"]}}}}}},"responses":{"200":{"description":"Dry-run result — resolved, composed and validated, but not persisted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationResult"}}}},"201":{"description":"Committed — the new version plus the full operation result.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/mappacks":{"get":{"operationId":"listMappacks","tags":["Mappacks"],"summary":"List map catalogues (ECU map definitions). The Mappack concept = descriptive map catalogue.","x-required-scope":"definitions:read","parameters":[{"name":"ecu_id","in":"query","schema":{"type":"string"}},{"name":"page_size","in":"query","schema":{"type":"integer"}},{"name":"cursor","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Mappack (definition) list","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Unauthorized"}}},"post":{"operationId":"registerMappack","tags":["Mappacks"],"summary":"Register a map catalogue (definition) for an ECU/software.","x-required-scope":"definitions:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["ecu_id"],"properties":{"ecu_id":{"type":"string"},"software_id":{"type":"string"},"format":{"type":"string"},"filename":{"type":"string"},"primary_source_id":{"type":"string"},"provenance":{"type":"object"}}}}}},"responses":{"201":{"description":"Registered","content":{"application/json":{"schema":{"type":"object"}}}},"422":{"description":"Validation error"}},"description":"Register a map catalogue (definition) for an ECU/software. Then POST /mappacks/{id}/import a stagemod.import/1 payload (built LOCALLY from a DAMOS/A2L/XDF for the exact software, e.g. SW 1037368578) to populate the named maps — they render in the project Maps tab (2D heatmap + 3D surface) and become resolvable addresses via POST /maps/resolve. /definitions* is the deprecated alias."}},"/mappacks/{id}":{"get":{"operationId":"getMappack","tags":["Mappacks"],"summary":"A map catalogue (definition).","x-required-scope":"definitions:read","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Mappack","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Not found"}}}},"/projects":{"get":{"operationId":"listProjects","tags":["Projects"],"summary":"List projects","description":"List the caller's own projects. Admins may pass `scope=all` to see every project.","x-required-scope":"projects:read","parameters":[{"name":"status","in":"query","schema":{"$ref":"#/components/schemas/ProjectStatus"}},{"name":"ecu_id","in":"query","schema":{"type":"string"}},{"name":"scope","in":"query","description":"Admin-only: `all` returns projects across all users.","schema":{"type":"string","enum":["mine","all"]}},{"name":"q","in":"query","schema":{"type":"string"}},{"$ref":"#/components/parameters/PageSize"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"$ref":"#/components/responses/ProjectList"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}},"post":{"operationId":"createProject","tags":["Projects"],"summary":"Create a project","x-required-scope":"projects:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectCreate"}}}},"responses":{"201":{"description":"The created project.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Project"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/projects/{id}":{"parameters":[{"$ref":"#/components/parameters/ProjectId"}],"get":{"operationId":"getProject","tags":["Projects"],"summary":"Get one project","description":"Returns one project. The full read inlines (under `embedded`) the resolved `ecu`, the `original` read, capped `versions[]`, `logs[]`, `dtcs[]`, and the AI `decisions` graph as `{ nodes, edges }`, alongside `counts`. Pass `embed` to narrow which child collections are inlined and save round-trips.","x-required-scope":"projects:read","parameters":[{"name":"embed","in":"query","description":"Comma-separated child collections to inline: `ecu`, `original`, `versions`, `logs`, `dtcs`, `decisions`.","schema":{"type":"string","example":"ecu,original,versions,logs,dtcs,decisions"}}],"responses":{"200":{"description":"The project.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Project"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"operationId":"updateProject","tags":["Projects"],"summary":"Update a project","x-required-scope":"projects:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectUpdate"}}}},"responses":{"200":{"description":"The updated project.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Project"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}},"delete":{"operationId":"deleteProject","tags":["Projects"],"summary":"Soft-delete a project","x-required-scope":"projects:write","responses":{"204":{"description":"Deleted. No content."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/projects/{id}/decisions":{"parameters":[{"$ref":"#/components/parameters/ProjectId"}],"get":{"operationId":"listDecisions","tags":["Decisions"],"summary":"List decision-graph nodes","description":"List the project's AI decision-graph nodes (observations, hypotheses, decisions, changes, results, notes) newest-first. Filterable by `kind`, `status`, and the `version_id`/`mappack_id` they are scoped to.","x-required-scope":"projects:read","parameters":[{"name":"kind","in":"query","schema":{"$ref":"#/components/schemas/DecisionKind"}},{"name":"status","in":"query","schema":{"$ref":"#/components/schemas/DecisionStatus"}},{"name":"version_id","in":"query","schema":{"type":"string"}},{"name":"mappack_id","in":"query","schema":{"type":"string"}},{"$ref":"#/components/parameters/PageSize"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"$ref":"#/components/responses/DecisionList"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"operationId":"createDecision","tags":["Decisions"],"summary":"Record a decision-graph node","description":"Append a typed node to the project's AI decision graph. Optionally scope it to a `version_id`/`mappack_id`, cite catalogue rules via `catalogue_refs`, and link it to existing nodes via `links[]` (each `{ to, relation, note }`).","x-required-scope":"projects:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DecisionCreate"}}}},"responses":{"201":{"description":"The created decision node.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Decision"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/projects/{id}/decisions/{did}":{"parameters":[{"$ref":"#/components/parameters/ProjectId"},{"$ref":"#/components/parameters/DecisionId"}],"get":{"operationId":"getDecision","tags":["Decisions"],"summary":"Get one decision node","description":"Returns one decision node and its incident edges.","x-required-scope":"projects:read","responses":{"200":{"description":"The decision node.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Decision"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"operationId":"updateDecision","tags":["Decisions"],"summary":"Update a decision node","description":"Update a node's mutable fields (e.g. flip `status` from `proposed` to `applied`/`validated`/`reverted`, or attach a `result`). The graph is append-only for nodes/edges; this records the node's resolution. New outgoing `links[]` may be added.","x-required-scope":"projects:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DecisionUpdate"}}}},"responses":{"200":{"description":"The updated decision node.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Decision"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/projects/{id}/graph":{"parameters":[{"$ref":"#/components/parameters/ProjectId"}],"get":{"operationId":"getProjectGraph","tags":["Decisions"],"summary":"Get the full decision graph","description":"Returns the project's entire AI decision graph as `{ nodes, edges }` for reconstructing the reasoning trail — the typed nodes plus the typed edges between them.","x-required-scope":"projects:read","responses":{"200":{"description":"The decision graph.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Graph"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/projects/{id}/original":{"parameters":[{"$ref":"#/components/parameters/ProjectId"}],"get":{"operationId":"getProjectOriginal","tags":["Original File"],"summary":"Get the original file (with download URL)","description":"Returns the original read's metadata plus a short-lived download URL when the file is `ready`.","x-required-scope":"projects:read","responses":{"200":{"description":"The original file.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FileWithDownload"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"operationId":"initProjectOriginalUpload","tags":["Original File"],"summary":"Init the original-file upload","description":"Creates a pending file row and returns an `upload_intent`. PUT the bytes to `upload.url`; that route verifies the SHA-256 and size and flips the file to `ready` (no separate complete step).","x-required-scope":"projects:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadInit"}}}},"responses":{"201":{"description":"Upload intent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadIntent"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/projects/{id}/versions":{"parameters":[{"$ref":"#/components/parameters/ProjectId"}],"get":{"operationId":"listVersions","tags":["Versions"],"summary":"List versions","x-required-scope":"projects:read","parameters":[{"name":"status","in":"query","schema":{"$ref":"#/components/schemas/VersionStatus"}},{"$ref":"#/components/parameters/PageSize"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"$ref":"#/components/responses/VersionList"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"operationId":"createVersion","tags":["Versions"],"summary":"Create a tuned version","description":"Create a tuned output by applying a mappack and/or inline `changes` to the original (or a parent version). The applied mappack's ECU must match the project's ECU (else 422 `ecu_mismatch`). Catalogue refs are resolved and the merged effective change-set is snapshotted onto the version. The binary is uploaded separately via the version `file` endpoint.","x-required-scope":"projects:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VersionCreate"}}}},"responses":{"201":{"description":"The created version (file pending).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Version"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/projects/{id}/versions/{vid}":{"parameters":[{"$ref":"#/components/parameters/ProjectId"},{"$ref":"#/components/parameters/VersionId"}],"get":{"operationId":"getVersion","tags":["Versions"],"summary":"Get one version","description":"Returns one version. The full read inlines (under `embedded`) the snapshotted `effective_changes`, the cited `catalogue_refs` expanded to full rule objects, and the AI decision-graph nodes/edges linked to this version.","x-required-scope":"projects:read","responses":{"200":{"description":"The version.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Version"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/projects/{id}/versions/{vid}/file":{"parameters":[{"$ref":"#/components/parameters/ProjectId"},{"$ref":"#/components/parameters/VersionId"}],"post":{"operationId":"initVersionFileUpload","tags":["Versions"],"summary":"Init the version-binary upload","description":"Returns an `upload_intent` for the modified `.bin`. PUT the bytes to `upload.url`; the upload route verifies SHA-256/size and flips the version's file to `ready`, updating the project's `latest_version_id`.","x-required-scope":"projects:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadInit"}}}},"responses":{"201":{"description":"Upload intent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadIntent"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/projects/{id}/versions/{vid}/download":{"parameters":[{"$ref":"#/components/parameters/ProjectId"},{"$ref":"#/components/parameters/VersionId"}],"get":{"operationId":"downloadVersion","tags":["Versions"],"summary":"Get a download URL for the version binary","description":"Returns a short-lived download URL for the version's `ready` binary.","x-required-scope":"projects:read","responses":{"200":{"description":"Download intent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DownloadIntent"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/projects/{id}/logs":{"parameters":[{"$ref":"#/components/parameters/ProjectId"}],"get":{"operationId":"listLogs","tags":["Logs"],"summary":"List performance logs","description":"List datalogs (summary form — `samples` omitted).","x-required-scope":"projects:read","parameters":[{"name":"version_id","in":"query","schema":{"type":"string"}},{"$ref":"#/components/parameters/PageSize"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"$ref":"#/components/responses/LogList"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"operationId":"createLog","tags":["Logs"],"summary":"Attach a performance log","description":"Attach a datalog with a parsed parameter `summary` (the headroom the agent reasons over) and optional inline `samples`. For large raw files, init a file upload first and reference its `raw_file_id`.","x-required-scope":"projects:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LogCreate"}}}},"responses":{"201":{"description":"The created log.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Log"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/projects/{id}/logs/{lid}":{"parameters":[{"$ref":"#/components/parameters/ProjectId"},{"$ref":"#/components/parameters/LogId"}],"get":{"operationId":"getLog","tags":["Logs"],"summary":"Get one log","description":"Returns one log. Pass `include=samples` to return the full parsed series (omitted by default to keep payloads small).","x-required-scope":"projects:read","parameters":[{"name":"include","in":"query","description":"Set to `samples` to include the full sample series.","schema":{"type":"string","enum":["samples"]}}],"responses":{"200":{"description":"The log.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Log"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/projects/{id}/dtcs":{"parameters":[{"$ref":"#/components/parameters/ProjectId"}],"get":{"operationId":"listDtcs","tags":["DTCs"],"summary":"List fault codes","x-required-scope":"projects:read","parameters":[{"name":"status","in":"query","schema":{"$ref":"#/components/schemas/DtcStatus"}},{"name":"version_id","in":"query","schema":{"type":"string"}},{"$ref":"#/components/parameters/PageSize"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"$ref":"#/components/responses/DtcList"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"operationId":"createDtcs","tags":["DTCs"],"summary":"Attach fault code(s)","description":"Attach one or more DTCs to the project via the `dtcs` array.","x-required-scope":"projects:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DtcCreateBatch"}}}},"responses":{"201":{"description":"The created DTCs.","content":{"application/json":{"schema":{"type":"object","properties":{"object":{"const":"list"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Dtc"}}},"required":["object","data"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/projects/{id}/dtcs/{did}":{"parameters":[{"$ref":"#/components/parameters/ProjectId"},{"$ref":"#/components/parameters/DtcId"}],"patch":{"operationId":"updateDtc","tags":["DTCs"],"summary":"Update a fault code","description":"Update a DTC's status (e.g. `active` → `cleared`).","x-required-scope":"projects:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DtcUpdate"}}}},"responses":{"200":{"description":"The updated DTC.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Dtc"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/keys":{"get":{"operationId":"listKeys","tags":["API Keys"],"summary":"List API keys","description":"List the caller's API keys. Secrets are never returned after creation.","x-required-scope":null,"parameters":[{"$ref":"#/components/parameters/PageSize"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"$ref":"#/components/responses/ApiKeyList"},"401":{"$ref":"#/components/responses/Unauthorized"}}},"post":{"operationId":"createKey","tags":["API Keys"],"summary":"Create an API key","description":"Mint a new API key. The plaintext secret is returned exactly once in `secret`. Requested scopes cannot exceed the caller's own permissions.","x-required-scope":null,"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreate"}}}},"responses":{"201":{"description":"The created key, including the one-time `secret`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyWithSecret"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/keys/{id}":{"parameters":[{"$ref":"#/components/parameters/KeyId"}],"get":{"operationId":"getKey","tags":["API Keys"],"summary":"Get one API key","x-required-scope":null,"responses":{"200":{"description":"The API key (no secret).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKey"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"operationId":"revokeKey","tags":["API Keys"],"summary":"Revoke an API key","description":"Revoke (permanently disable) an API key.","x-required-scope":null,"responses":{"204":{"description":"Revoked. No content."},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/tickets":{"get":{"operationId":"listTickets","tags":["Tickets"],"summary":"List tickets","description":"List tickets across the queue (admin-only). Filter by status, priority, or requester.","x-required-scope":"tickets:read","parameters":[{"name":"status","in":"query","schema":{"$ref":"#/components/schemas/TicketStatus"}},{"name":"priority","in":"query","schema":{"$ref":"#/components/schemas/TicketPriority"}},{"name":"requester_id","in":"query","description":"Filter to one member's tickets.","schema":{"type":"string"}},{"name":"q","in":"query","description":"Free-text search across subject and body.","schema":{"type":"string"}},{"$ref":"#/components/parameters/PageSize"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"$ref":"#/components/responses/TicketList"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}},"post":{"operationId":"createTicket","tags":["Tickets"],"summary":"Open a ticket","description":"Create a ticket on behalf of a member. A subject and body are required.","x-required-scope":"tickets:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TicketCreate"}}}},"responses":{"201":{"description":"The created ticket.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ticket"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/tickets/{id}":{"parameters":[{"$ref":"#/components/parameters/TicketId"}],"get":{"operationId":"getTicket","tags":["Tickets"],"summary":"Get a ticket","description":"Fetch a single ticket with its internal fields.","x-required-scope":"tickets:read","responses":{"200":{"description":"The ticket.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ticket"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"operationId":"updateTicket","tags":["Tickets"],"summary":"Update a ticket","description":"Patch mutable fields (priority, category, assignee, …). Status changes go through `/transition` and `/resolve`.","x-required-scope":"tickets:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TicketUpdate"}}}},"responses":{"200":{"description":"The updated ticket.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ticket"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/tickets/{id}/messages":{"parameters":[{"$ref":"#/components/parameters/TicketId"}],"get":{"operationId":"listTicketMessages","tags":["Tickets"],"summary":"List ticket messages","description":"The ticket's message thread, including `internal` admin/AI notes.","x-required-scope":"tickets:read","parameters":[{"$ref":"#/components/parameters/PageSize"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"$ref":"#/components/responses/TicketMessageList"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"operationId":"addTicketMessage","tags":["Tickets"],"summary":"Post a ticket message","description":"Add a message to the thread. Defaults to a public admin reply; set `visibility: internal` for a private working note.","x-required-scope":"tickets:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TicketMessageCreate"}}}},"responses":{"201":{"description":"The created message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TicketMessage"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/tickets/{id}/events":{"parameters":[{"$ref":"#/components/parameters/TicketId"}],"get":{"operationId":"listTicketEvents","tags":["Tickets"],"summary":"List ticket events","description":"The append-only timeline of everything that happened to the ticket.","x-required-scope":"tickets:read","parameters":[{"$ref":"#/components/parameters/PageSize"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"$ref":"#/components/responses/TicketEventList"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/tickets/{id}/links":{"parameters":[{"$ref":"#/components/parameters/TicketId"}],"get":{"operationId":"listTicketLinks","tags":["Tickets"],"summary":"List ticket links","description":"Entities this ticket is linked to.","x-required-scope":"tickets:read","parameters":[{"$ref":"#/components/parameters/PageSize"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"$ref":"#/components/responses/TicketLinkList"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"operationId":"addTicketLink","tags":["Tickets"],"summary":"Link an entity to a ticket","description":"Attach a typed link from the ticket to an internal entity (project, version, decision, …).","x-required-scope":"tickets:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TicketLinkCreate"}}}},"responses":{"201":{"description":"The created link.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TicketLink"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/tickets/{id}/transition":{"parameters":[{"$ref":"#/components/parameters/TicketId"}],"post":{"operationId":"transitionTicket","tags":["Tickets"],"summary":"Transition a ticket","description":"Move the ticket to an allowed next status. Transitioning to `resolved` is rejected — use `/resolve` (a resolution note is mandatory). A 422 lists the allowed targets when the move is invalid.","x-required-scope":"tickets:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TicketTransition"}}}},"responses":{"200":{"description":"The ticket at its new status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ticket"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/tickets/{id}/resolve":{"parameters":[{"$ref":"#/components/parameters/TicketId"}],"post":{"operationId":"resolveTicket","tags":["Tickets"],"summary":"Resolve a ticket","description":"Resolve the ticket with a MANDATORY `resolution_summary`. The note is recorded as an AI decision-graph `result` node before the status flips to `resolved`; an empty note is rejected with a 422. Requires the `tickets:resolve` scope.","x-required-scope":"tickets:resolve","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TicketResolve"}}}},"responses":{"200":{"description":"The resolved ticket.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ticket"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/audit/completeness":{"get":{"operationId":"getCompletenessAudit","tags":["Audit"],"summary":"Model Completeness Index — how well-documented + sourced the knowledge base is.","description":"Admin-only scorecard: per-domain documentation coverage (change notes, provenance, trust grade, rationale) plus an overall index. The AI reads this to find and close knowledge gaps.","x-required-scope":"projects:read","responses":{"200":{"description":"Completeness scorecard","content":{"application/json":{"schema":{"type":"object","properties":{"object":{"type":"string","enum":["completeness_report"]},"overall":{"type":"number","description":"Weighted mean of domain scores, 0..1."},"provenance_coverage":{"type":"number","description":"Fraction of provenance-bearing rows with provenance, 0..1."},"generated_at":{"type":"string","format":"date-time"},"domains":{"type":"array","items":{"type":"object","properties":{"domain":{"type":"string"},"total":{"type":"integer"},"documented":{"type":"integer"},"with_provenance":{"type":["integer","null"]},"trusted":{"type":["integer","null"]},"score":{"type":"number"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/sources":{"get":{"operationId":"listSources","tags":["Sources"],"summary":"List sources (cursor-paginated; ?kind= filter).","x-required-scope":"sources:read","parameters":[{"name":"kind","in":"query","schema":{"type":"string"}},{"name":"page_size","in":"query","schema":{"type":"integer"}},{"name":"cursor","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Source list","content":{"application/json":{"schema":{"type":"object","properties":{"object":{"type":"string"},"data":{"type":"array","items":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"string"},"kind":{"type":"string"},"title":{"type":"string"},"url":{"type":["string","null"]},"canonical_key":{"type":"string"},"publisher":{"type":["string","null"]},"author":{"type":["string","null"]},"license_kind":{"type":"string"},"spdx_id":{"type":["string","null"]},"trust_score":{"type":"number"},"trust_grade":{"type":"string"},"trust_basis":{"type":["string","null"]},"ingest_safety":{"type":"string"},"redistribution":{"type":["string","null"]},"do_not_ingest":{"type":"boolean"},"notes":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}}}}}}}}},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"}}},"post":{"operationId":"registerSource","tags":["Sources"],"summary":"Register a source (deduped by canonical_key; do-not-ingest is trust-clamped).","x-required-scope":"sources:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title"],"properties":{"kind":{"type":"string"},"title":{"type":"string"},"url":{"type":"string"},"canonical_key":{"type":"string"},"publisher":{"type":"string"},"author":{"type":"string"},"license_kind":{"type":"string"},"spdx_id":{"type":"string"},"trust_score":{"type":"number"},"trust_basis":{"type":"string"},"ingest_safety":{"type":"string"},"redistribution":{"type":"string"},"notes":{"type":"string"}}}}}},"responses":{"201":{"description":"Registered source","content":{"application/json":{"schema":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"string"},"kind":{"type":"string"},"title":{"type":"string"},"url":{"type":["string","null"]},"canonical_key":{"type":"string"},"publisher":{"type":["string","null"]},"author":{"type":["string","null"]},"license_kind":{"type":"string"},"spdx_id":{"type":["string","null"]},"trust_score":{"type":"number"},"trust_grade":{"type":"string"},"trust_basis":{"type":["string","null"]},"ingest_safety":{"type":"string"},"redistribution":{"type":["string","null"]},"do_not_ingest":{"type":"boolean"},"notes":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}}}}}},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"422":{"description":"Validation error"}}}},"/sources/{id}":{"get":{"operationId":"getSource","tags":["Sources"],"summary":"A source with its links.","x-required-scope":"sources:read","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Source + links","content":{"application/json":{"schema":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"string"},"kind":{"type":"string"},"title":{"type":"string"},"url":{"type":["string","null"]},"canonical_key":{"type":"string"},"publisher":{"type":["string","null"]},"author":{"type":["string","null"]},"license_kind":{"type":"string"},"spdx_id":{"type":["string","null"]},"trust_score":{"type":"number"},"trust_grade":{"type":"string"},"trust_basis":{"type":["string","null"]},"ingest_safety":{"type":"string"},"redistribution":{"type":["string","null"]},"do_not_ingest":{"type":"boolean"},"notes":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Not found"}}}},"/sources/{id}/links":{"post":{"operationId":"linkSource","tags":["Sources"],"summary":"Link a source to a knowledge row; set_primary promotes it to primary_source_id + propagates trust.","x-required-scope":"sources:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["entity_type","entity_id"],"properties":{"entity_type":{"type":"string"},"entity_id":{"type":"string"},"role":{"type":"string"},"note":{"type":"string"},"set_primary":{"type":"boolean"}}}}}},"responses":{"201":{"description":"Source link","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Unauthorized"},"404":{"description":"Not found"},"422":{"description":"Validation error"}}}},"/definitions":{"get":{"operationId":"listDefinitions","tags":["Definitions"],"summary":"List ECU map definitions (?ecu_id= filter).","x-required-scope":"definitions:read","responses":{"200":{"description":"Definition list","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Unauthorized"}},"deprecated":true,"description":"DEPRECATED alias of /mappacks*."},"post":{"operationId":"registerDefinition","tags":["Definitions"],"summary":"Register an ECU map definition (local parser then imports its maps).","x-required-scope":"definitions:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["ecu_id"],"properties":{"ecu_id":{"type":"string"},"software_id":{"type":"string"},"format":{"type":"string"},"filename":{"type":"string"},"r2_key":{"type":"string"},"primary_source_id":{"type":"string"},"provenance":{"type":"object"}}}}}},"responses":{"201":{"description":"Registered","content":{"application/json":{"schema":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"string"},"ecu_id":{"type":"string"},"software_id":{"type":["string","null"]},"format":{"type":"string"},"status":{"type":"string"},"map_count":{"type":"integer"}}}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}},"deprecated":true,"description":"DEPRECATED alias of /mappacks*."}},"/definitions/{id}":{"get":{"operationId":"getDefinition","tags":["Definitions"],"summary":"A definition.","x-required-scope":"definitions:read","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Definition","content":{"application/json":{"schema":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"string"},"ecu_id":{"type":"string"},"software_id":{"type":["string","null"]},"format":{"type":"string"},"status":{"type":"string"},"map_count":{"type":"integer"}}}}}},"404":{"description":"Not found"}},"deprecated":true,"description":"DEPRECATED alias of /mappacks*."}},"/definitions/{id}/import":{"post":{"operationId":"importDefinition","tags":["Definitions"],"summary":"Import a stagemod.import/1 normalized payload (axes/maps/checksum_regions) into a map catalogue/definition.","description":"Deprecated alias of `POST /mappacks/{id}/import` (identical behaviour). Upserts axes, maps and checksum regions for the definition's (ecu, software) and returns the definition with import `counts`.","deprecated":true,"x-required-scope":"definitions:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportPayload"}}}},"responses":{"201":{"description":"Imported; the definition plus software_id and counts.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/EcuDefinition"},{"type":"object","properties":{"software_id":{"type":["string","null"]},"counts":{"type":"object","properties":{"maps":{"type":"integer"},"axes":{"type":"integer"},"checksum_regions":{"type":"integer"}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/ecus/{id}/maps":{"get":{"operationId":"listEcuMaps","tags":["Definitions"],"summary":"Concrete map definitions for an ECU (?software_id=, ?domain=).","x-required-scope":"definitions:read","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"software_id","in":"query","schema":{"type":"string"}},{"name":"domain","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Map list","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Unauthorized"}}}},"/maps/resolve":{"post":{"operationId":"resolveMaps","tags":["Definitions"],"summary":"Resolve symbolic MapChanges to concrete ecu_maps for an (ecu, software).","x-required-scope":"definitions:read","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["ecu_id","software_id","changes"],"properties":{"ecu_id":{"type":"string"},"software_id":{"type":"string"},"changes":{"type":"array"}}}}}},"responses":{"200":{"description":"Per-change resolution + tally","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Unauthorized"}}}},"/operations/recipes/{id}/quality":{"post":{"operationId":"scoreRecipeQuality","tags":["Solutions"],"summary":"(Re)score a recipe solution quality.","x-required-scope":"operations:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Quality score","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Not found"}}}},"/solutions":{"get":{"operationId":"findSolutions","tags":["Solutions"],"summary":"Ranked reuse query: existing solutions for (ecu, operation?) by Fit*Quality*Trust.","x-required-scope":"operations:read","parameters":[{"name":"ecu","in":"query","required":true,"schema":{"type":"string"}},{"name":"operation","in":"query","schema":{"type":"string"}},{"name":"software_id","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Ranked solution list","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Bad request"}},"description":"Applicable Solutions for an ECU. With ?ecu= → the ranked REUSE query (\"has this SW/ECU been solved before, and how good?\", Fit·Quality·Trust); match by exact software via GET /identifiers/solutions?sw_number=. Without ?ecu= → list authored tune-package Solutions. POST authors a tune-package; manage at /solutions/{id}/{publish,quality,duplicates}."}},"/solutions/link":{"post":{"operationId":"linkSolution","tags":["Solutions"],"summary":"Relate two solutions; supersedes/duplicate_of deprecates the loser.","x-required-scope":"mappacks:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["from_type","from_id","to_type","to_id","relation"],"properties":{"from_type":{"type":"string"},"from_id":{"type":"string"},"to_type":{"type":"string"},"to_id":{"type":"string"},"relation":{"type":"string"},"note":{"type":"string"}}}}}},"responses":{"201":{"description":"Solution link","content":{"application/json":{"schema":{"type":"object"}}}},"422":{"description":"Validation error"}}}},"/projects/{id}/logs/{lid}/analyze":{"post":{"operationId":"analyzeLog","tags":["Datalogs"],"summary":"Tier-1 headroom analysis (measured peak/min vs hard limits → verdict).","x-required-scope":"projects:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"lid","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"measured":{"type":"array","items":{"type":"object","properties":{"channel":{"type":"string"},"value":{"type":"number"}}}}}}}}},"responses":{"201":{"description":"Analysis","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Not found"}}}},"/projects/{id}/logs/{lid}/analysis":{"get":{"operationId":"getLogAnalysis","tags":["Datalogs"],"summary":"Latest analysis of a log.","x-required-scope":"projects:read","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"lid","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Analysis","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Not found"}}}},"/projects/{id}/logs/compare":{"get":{"operationId":"compareLogs","tags":["Datalogs"],"summary":"Before/after headroom comparison of two analyzed logs.","x-required-scope":"projects:read","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"before","in":"query","required":true,"schema":{"type":"string"}},{"name":"after","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Comparison","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Not found"}}}},"/audit/readiness":{"get":{"operationId":"getReadinessAudit","tags":["Audit"],"summary":"Ecosystem readiness scorecard (composes completeness + coverage).","x-required-scope":"projects:read","responses":{"200":{"description":"Readiness report","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Unauthorized"}}}},"/identifiers":{"get":{"operationId":"searchIdentifiers","tags":["Identifiers"],"summary":"Search read-out identifiers (?vin=&sw_number=&hw_number=&kind=&subject_id=).","x-required-scope":"identifiers:read","parameters":[{"name":"vin","in":"query","schema":{"type":"string"}},{"name":"sw_number","in":"query","schema":{"type":"string"}},{"name":"hw_number","in":"query","schema":{"type":"string"}},{"name":"kind","in":"query","schema":{"type":"string"}},{"name":"subject_id","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Identifier list","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Unauthorized"}}},"post":{"operationId":"registerIdentifier","tags":["Identifiers"],"summary":"Register an extracted identifier; sw/hw reconcile into ecu_software.","x-required-scope":"identifiers:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["subject_type","subject_id","kind","value"],"properties":{"subject_type":{"type":"string"},"subject_id":{"type":"string"},"kind":{"type":"string"},"value":{"type":"string"},"ecu_id":{"type":"string"},"software_id":{"type":"string"},"byte_offset":{"type":"integer"},"confidence":{"type":"number"},"extracted_by":{"type":"string"},"provenance":{"type":"object"}}}}}},"responses":{"201":{"description":"Identifier","content":{"application/json":{"schema":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"string"},"subject_type":{"type":"string"},"subject_id":{"type":"string"},"ecu_id":{"type":["string","null"]},"software_id":{"type":["string","null"]},"kind":{"type":"string"},"value":{"type":"string"},"normalized_value":{"type":"string"},"confidence":{"type":["number","null"]},"extracted_by":{"type":"string"}}}}}},"422":{"description":"Validation error"}}}},"/identifiers/solutions":{"get":{"operationId":"solutionsByIdentifier","tags":["Identifiers"],"summary":"Has this exact SW/HW/VIN been solved before? Ranked existing solutions.","x-required-scope":"identifiers:read","parameters":[{"name":"sw_number","in":"query","schema":{"type":"string"}},{"name":"hw_number","in":"query","schema":{"type":"string"}},{"name":"vin","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Matched software + ranked solutions","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Bad request"}}}},"/reads/{id}/scan":{"post":{"operationId":"scanRead","tags":["Identifiers"],"summary":"Cheap in-Worker identifier scan of a small read-out (?register=true persists).","x-required-scope":"identifiers:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"register","in":"query","schema":{"type":"boolean"}}],"responses":{"200":{"description":"Scan candidates","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Not found"},"422":{"description":"Too large for in-Worker scan"}}}},"/projects/{id}/maps":{"get":{"operationId":"listProjectMaps","tags":["Maps"],"summary":"Map selector data — ecu_maps for the project ECU grouped by domain + binary sources.","x-required-scope":"projects:read","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"software_id","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Maps + sources","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Unauthorized"},"404":{"description":"Not found"}}}},"/projects/{id}/maps/{mapId}/grid":{"get":{"operationId":"getMapGrid","tags":["Maps"],"summary":"Decode a map cell grid from a binary in R2 (source=original|version:<id>, optional compare= for a per-cell delta).","x-required-scope":"projects:read","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"mapId","in":"path","required":true,"schema":{"type":"string"}},{"name":"source","in":"query","schema":{"type":"string"},"description":"original | version:<versionId>"},{"name":"compare","in":"query","schema":{"type":"string"},"description":"a second source for a per-cell delta"}],"responses":{"200":{"description":"Decoded grid + axes (+ delta when comparing)","content":{"application/json":{"schema":{"type":"object","properties":{"object":{"type":"string"},"rows":{"type":"integer"},"cols":{"type":"integer"},"x_axis":{"type":"array","items":{"type":"number"}},"y_axis":{"type":"array","items":{"type":"number"}},"grid":{"type":"array","items":{"type":"array","items":{"type":"number"}}},"min":{"type":"number"},"max":{"type":"number"}}}}}},"404":{"description":"Not found"},"422":{"description":"Binary not uploaded / too large / decode error"}}}},"/mappacks/{id}/import":{"post":{"operationId":"importMappack","tags":["Mappacks"],"summary":"Import a stagemod.import/1 payload (axes/maps/checksum regions) into a map catalogue.","x-required-scope":"definitions:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"201":{"description":"Imported (+counts)","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Not found"},"422":{"description":"Validation error"}},"description":"Import the maps for a catalogue. Build the stagemod.import/1 payload locally (axes[] + maps[] with address/rows×cols/data_type/endian/conversion + checksum_regions[]) from a DAMOS/A2L/XDF for the exact software — the Worker only stores + decodes. Contract: scripts/parse-defs/README.md. Imported maps render in the project Maps tab."}},"/mappacks/{id}/maps":{"get":{"operationId":"listMappackMaps","tags":["Mappacks"],"summary":"The concrete maps catalogued by a definition (?domain=).","x-required-scope":"definitions:read","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"domain","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Map list","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Not found"}}}},"/solutions/{id}":{"get":{"operationId":"getSolution","tags":["Solutions"],"summary":"A Solution (tune-package mp_ or recipe rcp_).","x-required-scope":"operations:read","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Solution","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Not found"}}},"patch":{"operationId":"updateSolution","tags":["Solutions"],"summary":"Patch an authored tune-package solution (mp_…).","x-required-scope":"mappacks:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Not found"}}}},"/solutions/{id}/publish":{"post":{"operationId":"publishSolution","tags":["Solutions"],"summary":"Publish an authored tune-package solution (draft → published).","x-required-scope":"mappacks:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Published","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Not found"}}}},"/solutions/{id}/quality":{"post":{"operationId":"scoreSolutionQuality","tags":["Solutions"],"summary":"(Re)score a solution quality (pack or recipe).","x-required-scope":"mappacks:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Quality","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Not found"}}}},"/solutions/{id}/duplicates":{"get":{"operationId":"solutionDuplicates","tags":["Solutions"],"summary":"Near-duplicate solutions on the same ECU (simhash; pack or recipe).","x-required-scope":"mappacks:read","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"threshold","in":"query","schema":{"type":"integer"}}],"responses":{"200":{"description":"Duplicate set","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Not found"}}}},"/code-notes":{"get":{"operationId":"searchCodeNotes","tags":["Code-notes"],"summary":"Search the logic/strategy knowledge catalogue for reuse (?ecu_id=&family=&software_id=&kind=&topic=&q=).","x-required-scope":"codenotes:read","parameters":[{"name":"ecu_id","in":"query","schema":{"type":"string"}},{"name":"family","in":"query","schema":{"type":"string"}},{"name":"software_id","in":"query","schema":{"type":"string"}},{"name":"kind","in":"query","schema":{"type":"string"}},{"name":"topic","in":"query","schema":{"type":"string"}},{"name":"q","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Code-note list","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Unauthorized"}}},"post":{"operationId":"createCodeNote","tags":["Code-notes"],"summary":"Author a code-note (AI/Ghidra finding, how-to, checksum scheme, strategy), keyed by ECU/family/SW.","x-required-scope":"codenotes:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title","body"],"properties":{"ecu_id":{"type":"string"},"family":{"type":"string"},"software_id":{"type":"string"},"scope":{"type":"string"},"kind":{"type":"string"},"topic":{"type":"string"},"title":{"type":"string"},"body":{"type":"string"},"tags":{"type":"array"},"address_ranges":{"type":"array"},"confidence":{"type":"number"},"provenance":{"type":"object"}}}}}},"responses":{"201":{"description":"Code-note","content":{"application/json":{"schema":{"type":"object"}}}},"422":{"description":"Validation error"}}}},"/code-notes/{id}":{"get":{"operationId":"getCodeNote","tags":["Code-notes"],"summary":"A code-note + its links.","x-required-scope":"codenotes:read","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Code-note","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Not found"}}},"patch":{"operationId":"updateCodeNote","tags":["Code-notes"],"summary":"Patch a code-note (body/topic/kind/tags/status/confidence).","x-required-scope":"codenotes:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Not found"}}}},"/code-notes/{id}/links":{"post":{"operationId":"linkCodeNote","tags":["Code-notes"],"summary":"Link a code-note to what it explains/derives (solution/mappack/map/project/...).","x-required-scope":"codenotes:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["entity_type","entity_id"],"properties":{"entity_type":{"type":"string"},"entity_id":{"type":"string"},"role":{"type":"string"},"note":{"type":"string"}}}}}},"responses":{"201":{"description":"Link","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Not found"}}}},"/solutions/{id}/concretize":{"post":{"operationId":"concretizeSolution","tags":["Solutions"],"summary":"Derive a per-SW Concretization of a Strategy recipe by re-resolving its symbolic changes against the target software mappack. Returns the candidate recipe + the gaps the AI must still synthesize.","x-required-scope":"operations:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["software_id"],"properties":{"software_id":{"type":"string"}}}}}},"responses":{"200":{"description":"Nothing resolved (needs full synthesis)","content":{"application/json":{"schema":{"type":"object"}}}},"201":{"description":"Candidate concretization created","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/projects/{id}/decisions/{did}/promote":{"post":{"operationId":"promoteDecision","tags":["Decisions"],"summary":"Promote a project finding into a shared, reusable Code-note (provenance back).","x-required-scope":"codenotes:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"did","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"201":{"description":"Code-note","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Not found"}}}},"/projects/{id}/versions/{vid}/promote":{"post":{"operationId":"promoteVersion","tags":["Versions"],"summary":"Promote a version effective change-set into a shared tune-pack Solution.","x-required-scope":"mappacks:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"vid","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"201":{"description":"Solution","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Not found"}}}},"/solutions/{id}/promote":{"post":{"operationId":"promoteSolution","tags":["Solutions"],"summary":"Promote a candidate recipe (e.g. a concretization) to active (re-scored).","x-required-scope":"operations:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Promoted recipe","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Not found"}}}},"/files/upload":{"put":{"operationId":"uploadFile","tags":["Files"],"summary":"Token-gated raw-bytes PUT into R2; verifies the HMAC token (and sha256 if declared), writes the object, and finalizes the owning DB row (status → ready).","description":"No `Authorization` header — the `?token=` query param (from an upload intent) is the credential. The request body is the raw file bytes (`application/octet-stream`). On success the owning row (original file, version, log, or solution-pack file) is stamped with the checksum and size and flipped to `ready`.","security":[],"parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string"},"description":"HMAC upload token (op=put) returned by an upload intent."}],"requestBody":{"required":true,"content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"responses":{"200":{"description":"Bytes stored and the owning row finalized.","content":{"application/json":{"schema":{"type":"object","properties":{"object":{"const":"file"},"id":{"type":"string","example":"file_01HY0C2D3E4F5G6H7J8K"},"status":{"$ref":"#/components/schemas/FileStatus"},"size_bytes":{"type":"integer"},"checksum":{"type":"string","description":"Hex SHA-256 of the stored bytes."}},"required":["object","id","status","size_bytes","checksum"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/files/download":{"get":{"operationId":"downloadFile","tags":["Files"],"summary":"Token-gated raw-bytes GET from R2; streams the named object back as an attachment.","description":"No `Authorization` header — the `?token=` query param (op=get, from a download intent) is the credential. Responds with the object bytes (`Content-Disposition: attachment`).","security":[],"parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string"},"description":"HMAC download token (op=get) returned by a download intent."}],"responses":{"200":{"description":"The object bytes.","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/mappacks/{id}/primary":{"post":{"operationId":"setMappackPrimary","tags":["Mappacks"],"summary":"Mark this mappack (map catalogue/definition) as PRIMARY for its (ecu, software); clears the flag on siblings and wins in map-change resolution.","x-required-scope":"definitions:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The updated catalogue/definition (is_primary=true).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EcuDefinition"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/definitions/{id}/artifact":{"post":{"operationId":"initDefinitionArtifactUpload","tags":["Definitions"],"summary":"Init a token-signed upload of the raw catalogue artifact (a2l and/or parsed JSON) for a definition; returns an upload_intent to PUT the bytes to.","description":"Records the intended filename/key on the catalogue row and returns an `upload_intent`. The agent PUTs the bytes to the returned URL (`PUT /files/upload?token=…`), which writes to R2 and flips the row's status to `imported` (checksum/size verified there).","x-required-scope":"definitions:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadInit"}}}},"responses":{"201":{"description":"Upload intent for the artifact bytes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadIntent"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/ecus/{id}/software":{"get":{"operationId":"listEcuSoftware","tags":["ECUs"],"summary":"List the ecu_software identities (sw/hw number, version, checksum layout) registered for one ECU. Cursor-paginated.","x-required-scope":"catalogue:read","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"$ref":"#/components/parameters/PageSize"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"description":"A page of ecu_software rows.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListEnvelope"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"post":{"operationId":"createEcuSoftware","tags":["ECUs"],"summary":"Register a new ecu_software identity under an ECU.","x-required-scope":"catalogue:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EcuSoftwareCreate"}}}},"responses":{"201":{"description":"The created ecu_software.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EcuSoftware"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"}}}}},"components":{"securitySchemes":{"bearerApiKey":{"type":"http","scheme":"bearer","bearerFormat":"sk_live_…","description":"Per-user API key: `Authorization: Bearer sk_live_<id>_<secret>`. Browser callers may instead use an Auth.js session cookie. Each operation declares the scope it needs in `x-required-scope`."}},"parameters":{"PageSize":{"name":"page_size","in":"query","description":"Items per page (1–100, default 25).","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}},"Cursor":{"name":"cursor","in":"query","description":"Opaque pagination cursor from a prior response's `page.next_cursor`.","schema":{"type":"string"}},"EcuId":{"name":"id","in":"path","required":true,"schema":{"type":"string","example":"ecu_01HXY2A3B4C5D6E7F8G9"}},"EcuCode":{"name":"code","in":"path","required":true,"description":"The ECU's stable `code`/`variant` (not the `ecu_…` id).","schema":{"type":"string","example":"EDC17C64"}},"RecipeId":{"name":"id","in":"path","required":true,"schema":{"type":"string","example":"rcp_01HXR1A2B3C4D5E6F7G8"}},"MappackId":{"name":"id","in":"path","required":true,"schema":{"type":"string","example":"mp_01HXZ9Q8R7S6T5U4V3W2"}},"ProjectId":{"name":"id","in":"path","required":true,"schema":{"type":"string","example":"prj_01HY0AB1C2D3E4F5G6H7"}},"VersionId":{"name":"vid","in":"path","required":true,"schema":{"type":"string","example":"ver_01HY0H1J2K3L4M5N6P7Q"}},"LogId":{"name":"lid","in":"path","required":true,"schema":{"type":"string","example":"log_01HY0F1A2B3C4D5E6F7G"}},"DtcId":{"name":"did","in":"path","required":true,"schema":{"type":"string","example":"dtc_01HY0G1A2B3C4D5E6F7G"}},"DecisionId":{"name":"did","in":"path","required":true,"schema":{"type":"string","example":"dec_01HY0M1A2B3C4D5E6F7G"}},"CatalogueId":{"name":"id","in":"path","required":true,"schema":{"type":"string","example":"cat_diesel_smoke_egt"}},"KeyId":{"name":"id","in":"path","required":true,"schema":{"type":"string","example":"key_01HXKEY01234567890AB"}},"TicketId":{"name":"id","in":"path","required":true,"schema":{"type":"string","example":"tkt_01HY0AB1C2D3E4F5G6H7"}}},"responses":{"BadRequest":{"description":"Malformed JSON or query parameter.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"Unauthorized":{"description":"Missing or invalid credential.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"Forbidden":{"description":"The credential lacks the required scope or you are not the owner/admin.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"NotFound":{"description":"Resource not found or not visible to the caller.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"Conflict":{"description":"Duplicate or state conflict.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"Unprocessable":{"description":"Semantic validation failure with per-field `errors` (e.g. a cited catalogue hard limit was breached).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"example":{"type":"https://stagemod.com/errors/unprocessable_entity","title":"Validation failed","status":422,"code":"unprocessable_entity","detail":"The tune violates one or more cited catalogue rules.","request_id":"req_01HY0K8M9N","errors":[{"field":"safety_envelope.egt_max_c","code":"exceeds_hard_limit","message":"egt_max_c 810 exceeds cat_diesel_smoke_egt.egt_pre_turbo hard limit (780 celsius).","constraint_ref":"cat_diesel_smoke_egt"}]}}}},"CatalogueRuleList":{"description":"A page of tuning rules.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListEnvelope"},"examples":{"rules":{"value":{"object":"list","data":[],"page":{"next_cursor":null,"has_more":false,"page_size":25}}}}}}},"CatalogueCategoryList":{"description":"A page of tuning categories.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListEnvelope"}}}},"DecisionList":{"description":"A page of decision-graph nodes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListEnvelope"}}}},"EcuList":{"description":"A page of ECUs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListEnvelope"}}}},"MappackList":{"description":"A page of mappacks (summary form).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListEnvelope"}}}},"ProjectList":{"description":"A page of projects.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListEnvelope"}}}},"VersionList":{"description":"A page of versions.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListEnvelope"}}}},"LogList":{"description":"A page of logs (summary form).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListEnvelope"}}}},"DtcList":{"description":"A page of DTCs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListEnvelope"}}}},"ApiKeyList":{"description":"A page of API keys (no secrets).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListEnvelope"}}}},"OperationList":{"description":"A page of operations.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListEnvelope"}}}},"OperationRecipeList":{"description":"A page of operation recipes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListEnvelope"}}}},"TicketList":{"description":"A page of tickets.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListEnvelope"}}}},"TicketMessageList":{"description":"A page of ticket messages.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListEnvelope"}}}},"TicketEventList":{"description":"The ticket's timeline events.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListEnvelope"}}}},"TicketLinkList":{"description":"A page of ticket links.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListEnvelope"}}}}},"schemas":{"FuelType":{"type":"string","description":"Engine fuel type.","enum":["gasoline","diesel","flex","hybrid","other"]},"CatalogueFuelType":{"type":"string","description":"Fuel type a catalogue rule applies to. `any` matches all fuels.","enum":["gasoline","diesel","flex","hybrid","any"]},"MappackStatus":{"type":"string","enum":["draft","published","deprecated"]},"ProjectStatus":{"type":"string","enum":["open","draft","in_progress","testing","complete","archived"]},"VersionStatus":{"type":"string","enum":["draft","flashed","reverted","validated"]},"DtcStatus":{"type":"string","enum":["active","pending","stored","cleared","permanent"]},"DtcCodeSystem":{"type":"string","enum":["OBD2","UDS","manufacturer","powertrain","other"]},"FileStatus":{"type":"string","enum":["pending","ready"]},"Scope":{"type":"string","description":"API-key capability scope. `*` grants everything the owning user can do. `operations:read`/`operations:write` gate the operations engine: applying/reverting operations on a project is allowed under `projects:write`, while authoring recipes requires `operations:write` (or `admin`).","enum":["*","catalogue:read","catalogue:write","mappacks:read","mappacks:write","projects:read","projects:write","operations:read","operations:write","admin"]},"MapChangeOperation":{"type":"string","description":"How a change is applied to the map table values.","enum":["set","scale","offset","delta","absolute","clamp"]},"DecisionKind":{"type":"string","description":"The type of AI decision-graph node.","enum":["decision","change","observation","result","note","hypothesis"]},"DecisionStatus":{"type":"string","description":"Lifecycle state of a decision-graph node.","enum":["proposed","applied","validated","reverted","noted"]},"DecisionEdgeRelation":{"type":"string","description":"The typed relationship a decision-graph edge expresses.","enum":["caused","resulted_in","derived_from","supersedes","addresses","references","informed_by"]},"OperationKind":{"type":"string","description":"The category of an operation verb.","enum":["performance","deactivation","activation","feature","diagnostic","economy","restore"]},"FuelScope":{"type":"string","description":"Fuel type an operation applies to. `any` applies to all fuels.","enum":["gasoline","diesel","flex","hybrid","any"]},"RecipeStatus":{"type":"string","enum":["candidate","active","deprecated"]},"RecipeSource":{"type":"string","description":"Provenance of a recipe.","enum":["authored","from_mappack","ai_synthesized","imported"]},"OpResolveMode":{"type":"string","description":"How a single operation was resolved into a change-set.","enum":["deterministic","ai_synthesized"]},"OpAvailability":{"type":"string","description":"Whether an operation can be realized on an ECU: `deterministic` (an active recipe exists), `ai_synthesized` (in scope but no recipe — the agent must supply an inline recipe), or `unsupported`.","enum":["deterministic","ai_synthesized","unsupported"]},"Timestamp":{"type":"string","format":"date-time","examples":["2026-06-02T14:30:00Z"]},"Health":{"type":"object","properties":{"object":{"const":"health"},"status":{"type":"string","enum":["ok"]},"version":{"type":"string","example":"1"},"time":{"$ref":"#/components/schemas/Timestamp"}},"required":["object","status"]},"Me":{"type":"object","properties":{"object":{"const":"me"},"user":{"type":"object","properties":{"id":{"type":"string","example":"usr_01HXAGENT"},"email":{"type":"string","format":"email"},"name":{"type":["string","null"]},"role":{"type":"string","enum":["admin","member"]}},"required":["id","email","role"]},"via":{"type":"string","enum":["session","api-key"]},"scopes":{"type":"array","items":{"$ref":"#/components/schemas/Scope"}},"api_key_id":{"type":["string","null"],"example":"key_01HXKEY"}},"required":["object","user","via","scopes"]},"MapChange":{"type":"object","description":"A single structured change to an ECU map table.","properties":{"domain":{"type":"string","description":"e.g. fuel, boost, timing, torque, egr.","example":"boost"},"table":{"type":"string","example":"boost_target"},"operation":{"$ref":"#/components/schemas/MapChangeOperation"},"value_pct":{"type":"number"},"value_kpa":{"type":"number"},"value_nm":{"type":"number"},"value":{"type":"number"},"unit":{"type":"string"},"axis":{"type":"array","items":{"type":"string"}},"region":{"type":"string"},"min":{"type":"number"},"max":{"type":"number"},"constraint_ref":{"type":"string","description":"Catalogue rule id this change is bounded by.","example":"cat_diesel_boost_vgt"},"note":{"type":"string"}},"required":["domain","table","operation"]},"MapChanges":{"type":"object","description":"Versioned envelope around a list of map changes.","properties":{"schema":{"const":"stagemod.mapchanges/1"},"summary":{"type":"string"},"changes":{"type":"array","items":{"$ref":"#/components/schemas/MapChange"}},"guards":{"type":"object","additionalProperties":{"type":"number"}}},"required":["schema","changes"]},"SafetyEnvelope":{"type":"object","description":"Numeric limits a mappack/version commits to staying within. Validated against cited rules' hard limits.","properties":{"egt_max_c":{"type":"number"},"afr_min":{"type":"number"},"lambda_min":{"type":"number"},"boost_max_kpa":{"type":"number"},"rail_pressure_max_bar":{"type":"number"},"knock_retard_max_deg":{"type":"number"},"torque_limit_nm":{"type":"number"},"notes":{"type":"string"}},"additionalProperties":true},"ExpectedGains":{"type":"object","properties":{"power_hp":{"type":"number"},"torque_nm":{"type":"number"},"delta_power_hp":{"type":"number"},"delta_torque_nm":{"type":"number"}},"additionalProperties":false},"VehicleInfo":{"type":"object","properties":{"vin":{"type":"string"},"make":{"type":"string"},"model":{"type":"string"},"year":{"type":"integer"},"mileage_km":{"type":"integer"},"gearbox":{"type":"string"},"engine_code":{"type":"string"}},"additionalProperties":false},"LogSummary":{"type":"object","description":"Parsed headroom the agent reasons over first.","properties":{"rpm_range":{"type":"array","items":{"type":"number"},"minItems":2,"maxItems":2},"boost_peak_kpa":{"type":"number"},"egt_peak_c":{"type":"number"},"afr_min":{"type":"number"},"lambda_min":{"type":"number"},"knock_events":{"type":"integer"}},"additionalProperties":true},"FreezeFrame":{"type":"object","properties":{"rpm":{"type":"number"},"coolant_c":{"type":"number"},"load_pct":{"type":"number"}},"additionalProperties":{"type":"number"}},"Ecu":{"type":"object","properties":{"object":{"const":"ecu"},"id":{"type":"string","example":"ecu_01HXY2A3B4C5D6E7F8G9"},"slug":{"type":"string","example":"bosch-edc17c64"},"vendor":{"type":"string","example":"Bosch"},"model":{"type":"string","example":"EDC17C64"},"code":{"type":["string","null"],"description":"Stable ECU code used by `/ecus/{code}/operations` and detection.","example":"EDC17C64"},"variant":{"type":["string","null"],"example":"EDC17C64"},"cpu":{"type":["string","null"],"example":"TC1797"},"generation":{"type":["string","null"]},"in_scope":{"type":"boolean","description":"Whether this ECU is part of the supported in-scope catalogue.","example":true},"family":{"type":["string","null"],"example":"edc17"},"fuel_type":{"$ref":"#/components/schemas/FuelType"},"protocol":{"type":["string","null"]},"era":{"type":["string","null"]},"checksum_family":{"type":["string","null"],"example":"edc17"},"file_sizes":{"type":"array","items":{"type":"integer"},"description":"Known read sizes in bytes (used by detection)."},"file_formats":{"type":"array","items":{"type":"string"}},"security_notes":{"type":["string","null"]},"supported_operations":{"type":"array","items":{"type":"string"},"description":"Operation codes this ECU supports.","example":["egr_off","dpf_off","stage1"]},"notes":{"type":["string","null"]},"created_at":{"$ref":"#/components/schemas/Timestamp"},"updated_at":{"$ref":"#/components/schemas/Timestamp"}},"required":["object","id","slug","vendor","model","fuel_type","created_at"]},"EcuCreate":{"type":"object","properties":{"vendor":{"type":"string"},"model":{"type":"string"},"slug":{"type":"string","description":"Optional; derived from vendor+model when omitted."},"family":{"type":"string"},"fuel_type":{"$ref":"#/components/schemas/FuelType"},"protocol":{"type":"string"},"era":{"type":"string"},"checksum_family":{"type":"string"},"notes":{"type":"string"}},"required":["vendor","model","fuel_type"]},"Mappack":{"type":"object","properties":{"object":{"const":"mappack"},"id":{"type":"string","example":"mp_01HXZ9Q8R7S6T5U4V3W2"},"ecu_id":{"type":"string","example":"ecu_01HXY2A3B4C5D6E7F8G9"},"name":{"type":"string","example":"EA189 CLLA — Stage 1 (Diesel, DSG-safe)"},"slug":{"type":"string"},"stage":{"type":["string","null"],"example":"1"},"goal":{"type":["string","null"]},"fuel_type":{"$ref":"#/components/schemas/FuelType"},"gearbox":{"type":["string","null"],"example":"dsg_dq250"},"octane_min":{"type":["integer","null"]},"summary":{"type":["string","null"]},"description":{"type":["string","null"]},"expected_gains":{"$ref":"#/components/schemas/ExpectedGains"},"safety_envelope":{"$ref":"#/components/schemas/SafetyEnvelope"},"map_changes":{"$ref":"#/components/schemas/MapChanges","description":"Omitted in list/summary form."},"catalogue_refs":{"type":"array","items":{"type":"string"}},"author_id":{"type":["string","null"]},"visibility":{"type":"string","enum":["public","unlisted","private"]},"status":{"$ref":"#/components/schemas/MappackStatus"},"version":{"type":"integer"},"created_at":{"$ref":"#/components/schemas/Timestamp"},"updated_at":{"$ref":"#/components/schemas/Timestamp"}},"required":["object","id","ecu_id","name","fuel_type","status","version","created_at"]},"MappackCreate":{"type":"object","properties":{"ecu_id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"stage":{"type":"string"},"goal":{"type":"string"},"fuel_type":{"$ref":"#/components/schemas/FuelType"},"gearbox":{"type":"string"},"octane_min":{"type":"integer"},"summary":{"type":"string"},"description":{"type":"string"},"expected_gains":{"$ref":"#/components/schemas/ExpectedGains"},"safety_envelope":{"$ref":"#/components/schemas/SafetyEnvelope"},"changes":{"type":"array","items":{"$ref":"#/components/schemas/MapChange"}},"catalogue_refs":{"type":"array","items":{"type":"string"}},"visibility":{"type":"string","enum":["public","unlisted","private"]}},"required":["ecu_id","name","fuel_type"]},"MappackUpdate":{"type":"object","description":"Partial update; all fields optional.","properties":{"name":{"type":"string"},"stage":{"type":"string"},"goal":{"type":"string"},"gearbox":{"type":"string"},"octane_min":{"type":"integer"},"summary":{"type":"string"},"description":{"type":"string"},"expected_gains":{"$ref":"#/components/schemas/ExpectedGains"},"safety_envelope":{"$ref":"#/components/schemas/SafetyEnvelope"},"changes":{"type":"array","items":{"$ref":"#/components/schemas/MapChange"}},"catalogue_refs":{"type":"array","items":{"type":"string"}},"visibility":{"type":"string","enum":["public","unlisted","private"]}},"additionalProperties":false},"Project":{"type":"object","properties":{"object":{"const":"project"},"id":{"type":"string","example":"prj_01HY0AB1C2D3E4F5G6H7"},"owner_id":{"type":"string"},"name":{"type":"string"},"description":{"type":["string","null"]},"ecu_id":{"type":["string","null"]},"engine_ref_id":{"type":["string","null"]},"vehicle":{"$ref":"#/components/schemas/VehicleInfo"},"status":{"$ref":"#/components/schemas/ProjectStatus"},"original_file_id":{"type":["string","null"]},"latest_version_id":{"type":["string","null"]},"counts":{"type":"object","description":"Cardinality of the project's child collections.","properties":{"versions":{"type":"integer"},"logs":{"type":"integer"},"dtcs":{"type":"integer"},"decisions":{"type":"integer"}}},"embedded":{"type":"object","description":"The fully-loaded project GET inlines its child collections here: the resolved `ecu`, the `original` read, and capped `versions[]`, `logs[]`, `dtcs[]`, plus the AI `decisions` graph as `{ nodes, edges }`. (Which collections are present can be narrowed via `?embed=`.)","properties":{"ecu":{"oneOf":[{"$ref":"#/components/schemas/Ecu"},{"type":"null"}]},"original":{"oneOf":[{"$ref":"#/components/schemas/FileWithDownload"},{"type":"null"}]},"versions":{"type":"array","items":{"$ref":"#/components/schemas/Version"}},"logs":{"type":"array","items":{"$ref":"#/components/schemas/Log"}},"dtcs":{"type":"array","items":{"$ref":"#/components/schemas/Dtc"}},"decisions":{"$ref":"#/components/schemas/Graph","description":"The AI decision graph as `{ nodes, edges }`."}}},"created_at":{"$ref":"#/components/schemas/Timestamp"},"updated_at":{"$ref":"#/components/schemas/Timestamp"}},"required":["object","id","owner_id","name","vehicle","status","created_at"]},"ProjectCreate":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"ecu_id":{"type":"string"},"engine_ref_id":{"type":"string"},"vehicle":{"$ref":"#/components/schemas/VehicleInfo"},"notes":{"type":"string","description":"Alias for description on create."}},"required":["name"]},"ProjectUpdate":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"ecu_id":{"type":"string"},"engine_ref_id":{"type":"string"},"vehicle":{"$ref":"#/components/schemas/VehicleInfo"},"status":{"$ref":"#/components/schemas/ProjectStatus"}},"additionalProperties":false},"Version":{"type":"object","properties":{"object":{"const":"version"},"id":{"type":"string","example":"ver_01HY0H1J2K3L4M5N6P7Q"},"project_id":{"type":"string"},"version_no":{"type":"integer","example":1},"parent_version_id":{"type":["string","null"]},"applied_mappack_id":{"type":["string","null"]},"op_state":{"type":"array","items":{"type":"string"},"description":"The set of operation codes currently applied on this version (its identity). Always composed from ORI.","example":["egr_off","dpf_off","stage1"]},"derived_from":{"type":"string","enum":["original","version"],"description":"Whether this version was composed from the original read or a parent version."},"label":{"type":["string","null"]},"change_summary":{"type":["string","null"]},"changes":{"$ref":"#/components/schemas/MapChanges","description":"Inline changes as submitted."},"effective_changes":{"$ref":"#/components/schemas/MapChanges","description":"Snapshotted merged change-set (mappack + inline)."},"expected_gains":{"$ref":"#/components/schemas/ExpectedGains"},"applied_catalogue_refs":{"type":"array","items":{"type":"string"}},"based_on_logs":{"type":"array","items":{"type":"string"}},"addresses_dtcs":{"type":"array","items":{"type":"string"}},"checksum_corrected":{"type":"boolean"},"file":{"type":"object","properties":{"status":{"$ref":"#/components/schemas/FileStatus"},"filename":{"type":["string","null"]},"size_bytes":{"type":["integer","null"]},"checksum":{"type":["string","null"]},"checksum_algo":{"type":"string","example":"sha256"}},"required":["status"]},"status":{"$ref":"#/components/schemas/VersionStatus"},"embedded":{"type":"object","description":"The full version read inlines the snapshotted `effective_changes`, the `catalogue_refs` expanded to full rule objects, and the decision-graph nodes/edges linked to this version.","properties":{"effective_changes":{"$ref":"#/components/schemas/MapChanges","description":"Snapshotted merged change-set (mappack + inline) — mirrors the top-level field."},"catalogue_refs":{"type":"array","items":{"$ref":"#/components/schemas/CatalogueRule"},"description":"Cited `applied_catalogue_refs` expanded to full rule objects."},"decisions":{"$ref":"#/components/schemas/Graph","description":"Decision-graph nodes (and edges) scoped to this version, as `{ nodes, edges }`."}}},"created_by":{"type":["string","null"]},"created_at":{"$ref":"#/components/schemas/Timestamp"}},"required":["object","id","project_id","version_no","status","file","created_at"]},"VersionCreate":{"type":"object","properties":{"label":{"type":"string"},"base":{"type":"string","enum":["original","version"],"default":"original","description":"Derive from the original read or a parent version."},"parent_version_id":{"type":"string","description":"Required when base = version."},"mappack_id":{"type":"string"},"changes":{"type":"array","items":{"$ref":"#/components/schemas/MapChange"}},"expected_gains":{"$ref":"#/components/schemas/ExpectedGains"},"applied_catalogue_refs":{"type":"array","items":{"type":"string"}},"checksum_corrected":{"type":"boolean","default":false},"based_on_logs":{"type":"array","items":{"type":"string"}},"addresses_dtcs":{"type":"array","items":{"type":"string"}},"notes":{"type":"string"}}},"Log":{"type":"object","properties":{"object":{"const":"log"},"id":{"type":"string","example":"log_01HY0F1A2B3C4D5E6F7G"},"project_id":{"type":"string"},"version_id":{"type":["string","null"]},"label":{"type":["string","null"]},"source_tool":{"type":["string","null"],"example":"vcds"},"raw_file_id":{"type":["string","null"]},"channels":{"type":"array","items":{"type":"string"}},"summary":{"$ref":"#/components/schemas/LogSummary"},"sample_rate_hz":{"type":["number","null"]},"sample_count":{"type":["integer","null"]},"duration_s":{"type":["number","null"]},"samples":{"type":"array","description":"Full parsed series. Omitted unless requested via `?include=samples`.","items":{"type":"object","additionalProperties":{"type":"number"}}},"captured_at":{"oneOf":[{"$ref":"#/components/schemas/Timestamp"},{"type":"null"}]},"created_by":{"type":["string","null"]},"created_at":{"$ref":"#/components/schemas/Timestamp"}},"required":["object","id","project_id","channels","summary","created_at"]},"LogCreate":{"type":"object","properties":{"name":{"type":"string"},"source":{"type":"string","example":"vcds"},"version_id":{"type":"string"},"raw_file_id":{"type":"string"},"sample_rate_hz":{"type":"number"},"channels":{"type":"array","items":{"type":"string"}},"summary":{"$ref":"#/components/schemas/LogSummary"},"samples":{"type":"array","items":{"type":"object","additionalProperties":{"type":"number"}}},"captured_at":{"$ref":"#/components/schemas/Timestamp"}},"required":["name"]},"Dtc":{"type":"object","properties":{"object":{"const":"dtc"},"id":{"type":"string","example":"dtc_01HY0G1A2B3C4D5E6F7G"},"project_id":{"type":"string"},"version_id":{"type":["string","null"]},"log_id":{"type":["string","null"]},"code":{"type":"string","example":"P0401"},"code_system":{"$ref":"#/components/schemas/DtcCodeSystem"},"description":{"type":["string","null"]},"status":{"$ref":"#/components/schemas/DtcStatus"},"source":{"type":["string","null"],"example":"obd"},"freeze_frame":{"$ref":"#/components/schemas/FreezeFrame"},"captured_at":{"oneOf":[{"$ref":"#/components/schemas/Timestamp"},{"type":"null"}]},"cleared_at":{"oneOf":[{"$ref":"#/components/schemas/Timestamp"},{"type":"null"}]},"created_by":{"type":["string","null"]},"created_at":{"$ref":"#/components/schemas/Timestamp"}},"required":["object","id","project_id","code","code_system","status","created_at"]},"DtcInput":{"type":"object","properties":{"code":{"type":"string"},"system":{"$ref":"#/components/schemas/DtcCodeSystem"},"code_system":{"$ref":"#/components/schemas/DtcCodeSystem"},"description":{"type":"string"},"status":{"$ref":"#/components/schemas/DtcStatus"},"source":{"type":"string"},"version_id":{"type":"string"},"log_id":{"type":"string"},"freeze_frame":{"$ref":"#/components/schemas/FreezeFrame"}},"required":["code"]},"DtcCreateBatch":{"type":"object","properties":{"dtcs":{"type":"array","items":{"$ref":"#/components/schemas/DtcInput"},"minItems":1}},"required":["dtcs"]},"DtcUpdate":{"type":"object","properties":{"status":{"$ref":"#/components/schemas/DtcStatus"},"description":{"type":"string"},"version_id":{"type":"string"}},"additionalProperties":false},"CatalogueRule":{"type":"object","description":"A machine-readable tuning rule. `severity: hard` rows define limits the API enforces on mappack/version creation.","properties":{"object":{"const":"catalogue_rule"},"id":{"type":"string","example":"cat_diesel_smoke_egt"},"category_id":{"type":"string","example":"fuelling"},"fuel_type":{"$ref":"#/components/schemas/CatalogueFuelType"},"param":{"type":"string","example":"egt_pre_turbo"},"title":{"type":["string","null"],"example":"Diesel fuelling vs. smoke & EGT limits"},"applies_to":{"type":["string","null"]},"target_min":{"type":["number","null"]},"target_max":{"type":["number","null"]},"hard_limit":{"type":["number","null"],"example":780},"limit_kind":{"type":["string","null"],"enum":["max","min","range",null]},"unit":{"type":["string","null"],"example":"celsius"},"severity":{"type":"string","enum":["info","warn","hard"]},"rationale":{"type":["string","null"]},"risk_if_violated":{"type":["string","null"]},"guidance":{"type":"array","items":{"type":"string"}},"related_rules":{"type":"array","items":{"type":"string"}},"tags":{"type":"array","items":{"type":"string"}},"source":{"type":["string","null"]},"is_active":{"type":"boolean"},"version":{"type":"integer"},"created_at":{"$ref":"#/components/schemas/Timestamp"},"updated_at":{"$ref":"#/components/schemas/Timestamp"}},"required":["object","id","category_id","fuel_type","param","severity","version","created_at"]},"CatalogueRuleCreate":{"type":"object","description":"Create a machine-readable tuning rule. `category_id` must resolve to an existing tuning category.","properties":{"id":{"type":"string","description":"Optional stable id; derived from category + param when omitted.","example":"cat_diesel_smoke_egt"},"category_id":{"type":"string","example":"fuelling"},"fuel_type":{"$ref":"#/components/schemas/CatalogueFuelType"},"param":{"type":"string","example":"egt_pre_turbo"},"title":{"type":"string"},"applies_to":{"type":"string"},"target_min":{"type":"number"},"target_max":{"type":"number"},"hard_limit":{"type":"number"},"limit_kind":{"type":"string","enum":["max","min","range"]},"unit":{"type":"string"},"severity":{"type":"string","enum":["info","warn","hard"]},"rationale":{"type":"string"},"risk_if_violated":{"type":"string"},"guidance":{"type":"array","items":{"type":"string"}},"related_rules":{"type":"array","items":{"type":"string"}},"tags":{"type":"array","items":{"type":"string"}},"source":{"type":"string"},"is_active":{"type":"boolean","default":true}},"required":["category_id","fuel_type","param","severity"]},"CatalogueRuleUpdate":{"type":"object","description":"Partial update of a tuning rule; all fields optional.","properties":{"category_id":{"type":"string"},"fuel_type":{"$ref":"#/components/schemas/CatalogueFuelType"},"param":{"type":"string"},"title":{"type":"string"},"applies_to":{"type":"string"},"target_min":{"type":"number"},"target_max":{"type":"number"},"hard_limit":{"type":"number"},"limit_kind":{"type":"string","enum":["max","min","range"]},"unit":{"type":"string"},"severity":{"type":"string","enum":["info","warn","hard"]},"rationale":{"type":"string"},"risk_if_violated":{"type":"string"},"guidance":{"type":"array","items":{"type":"string"}},"related_rules":{"type":"array","items":{"type":"string"}},"tags":{"type":"array","items":{"type":"string"}},"source":{"type":"string"},"is_active":{"type":"boolean"}},"additionalProperties":false},"CatalogueCategory":{"type":"object","description":"A grouping that tuning rules are organised under.","properties":{"object":{"const":"catalogue_category"},"id":{"type":"string","example":"fuelling"},"label":{"type":"string","example":"Fuelling"},"description":{"type":["string","null"]},"sort_order":{"type":"integer"},"created_at":{"$ref":"#/components/schemas/Timestamp"},"updated_at":{"$ref":"#/components/schemas/Timestamp"}},"required":["object","id","label"]},"CatalogueCategoryCreate":{"type":"object","properties":{"id":{"type":"string","description":"Optional stable id/key; derived from label when omitted."},"label":{"type":"string"},"description":{"type":"string"},"sort_order":{"type":"integer"}},"required":["label"]},"Decision":{"type":"object","description":"One node in a project's AI decision graph. An append-only, typed record of the agent's reasoning. `links`/incident edges are surfaced via the graph endpoint.","properties":{"object":{"const":"decision"},"id":{"type":"string","example":"dec_01HY0M1A2B3C4D5E6F7G"},"project_id":{"type":["string","null"]},"version_id":{"type":["string","null"],"description":"Version this node is scoped to, if any."},"mappack_id":{"type":["string","null"],"description":"Mappack this node is scoped to, if any."},"kind":{"$ref":"#/components/schemas/DecisionKind"},"title":{"type":"string","example":"Raise boost target +8% in mid-range"},"rationale":{"type":["string","null"],"description":"Why the agent took this step."},"result":{"type":["string","null"],"description":"Outcome once known (for change/decision nodes)."},"status":{"$ref":"#/components/schemas/DecisionStatus"},"data":{"type":"object","description":"Arbitrary structured payload (e.g. the change snapshot or observed metrics).","additionalProperties":true},"catalogue_refs":{"type":"array","items":{"type":"string"},"description":"Catalogue rule ids cited by this node."},"confidence":{"type":["number","null"],"description":"Agent confidence 0–1."},"created_by":{"type":["string","null"]},"via":{"type":["string","null"],"description":"Origin of the node (e.g. `api-key`, `session`)."},"created_at":{"$ref":"#/components/schemas/Timestamp"}},"required":["object","id","kind","title","status","created_at"]},"DecisionEdge":{"type":"object","description":"A typed, directed edge between two decision-graph nodes.","properties":{"object":{"const":"decision_edge"},"id":{"type":"string","example":"dee_01HY0N1A2B3C4D5E6F7G"},"project_id":{"type":["string","null"]},"from":{"type":"string","description":"Source decision node id.","example":"dec_01HY0M1A2B3C4D5E6F7G"},"to":{"type":"string","description":"Target decision node id.","example":"dec_01HY0P1A2B3C4D5E6F7G"},"relation":{"$ref":"#/components/schemas/DecisionEdgeRelation"},"note":{"type":["string","null"]},"created_at":{"$ref":"#/components/schemas/Timestamp"}},"required":["object","id","from","to","relation"]},"DecisionLink":{"type":"object","description":"An outgoing edge to create alongside a node.","properties":{"to":{"type":"string","description":"Target decision node id."},"relation":{"$ref":"#/components/schemas/DecisionEdgeRelation"},"note":{"type":"string"}},"required":["to","relation"]},"DecisionCreate":{"type":"object","properties":{"kind":{"$ref":"#/components/schemas/DecisionKind"},"title":{"type":"string"},"rationale":{"type":"string"},"result":{"type":"string"},"status":{"$ref":"#/components/schemas/DecisionStatus"},"data":{"type":"object","additionalProperties":true},"catalogue_refs":{"type":"array","items":{"type":"string"}},"confidence":{"type":"number"},"version_id":{"type":"string"},"mappack_id":{"type":"string"},"links":{"type":"array","items":{"$ref":"#/components/schemas/DecisionLink"}}},"required":["kind","title"]},"DecisionUpdate":{"type":"object","description":"Update a node's mutable fields. Nodes/edges are otherwise append-only.","properties":{"title":{"type":"string"},"rationale":{"type":"string"},"result":{"type":"string"},"status":{"$ref":"#/components/schemas/DecisionStatus"},"data":{"type":"object","additionalProperties":true},"catalogue_refs":{"type":"array","items":{"type":"string"}},"confidence":{"type":"number"},"links":{"type":"array","items":{"$ref":"#/components/schemas/DecisionLink"},"description":"New outgoing edges to append."}},"additionalProperties":false},"Graph":{"type":"object","description":"A project's full AI decision graph.","properties":{"object":{"const":"graph"},"nodes":{"type":"array","items":{"$ref":"#/components/schemas/Decision"}},"edges":{"type":"array","items":{"$ref":"#/components/schemas/DecisionEdge"}}},"required":["object","nodes","edges"]},"ApiKey":{"type":"object","description":"An API key's metadata. The secret is never returned after creation.","properties":{"object":{"const":"api_key"},"id":{"type":"string","example":"key_01HXKEY01234567890AB"},"name":{"type":"string"},"key_prefix":{"type":"string","description":"The public, non-secret prefix used to look the key up.","example":"sk_live_aBcD3fGh1jKl"},"scopes":{"type":"array","items":{"$ref":"#/components/schemas/Scope"}},"last_used_at":{"oneOf":[{"$ref":"#/components/schemas/Timestamp"},{"type":"null"}]},"expires_at":{"oneOf":[{"$ref":"#/components/schemas/Timestamp"},{"type":"null"}]},"revoked_at":{"oneOf":[{"$ref":"#/components/schemas/Timestamp"},{"type":"null"}]},"created_at":{"$ref":"#/components/schemas/Timestamp"}},"required":["object","id","name","key_prefix","scopes","created_at"]},"ApiKeyWithSecret":{"allOf":[{"$ref":"#/components/schemas/ApiKey"},{"type":"object","properties":{"secret":{"type":"string","description":"The full plaintext key, shown exactly once.","example":"sk_live_aBcD3fGh1jKl_M0n1p2Q3r4S5t6U7v8W9x0Y1z2A3b4"}},"required":["secret"]}]},"ApiKeyCreate":{"type":"object","properties":{"name":{"type":"string"},"scopes":{"type":"array","items":{"$ref":"#/components/schemas/Scope"},"description":"Requested scopes; cannot exceed the caller's own permissions."},"expires_at":{"$ref":"#/components/schemas/Timestamp"}},"required":["name","scopes"]},"UploadInit":{"type":"object","description":"Declares a file the agent is about to upload. The init route returns an `upload_intent`; the agent PUTs bytes to it.","properties":{"filename":{"type":"string","example":"CLLA_stock_read.bin"},"size_bytes":{"type":"integer","example":4194304},"content_type":{"type":"string","default":"application/octet-stream"},"sha256":{"type":"string","description":"Hex SHA-256 of the bytes; verified on upload.","example":"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"}},"required":["filename","size_bytes","sha256"]},"UploadIntent":{"type":"object","properties":{"object":{"const":"upload_intent"},"file_id":{"type":"string","example":"file_01HY0C2D3E4F5G6H7J8K"},"upload":{"type":"object","properties":{"method":{"const":"PUT"},"url":{"type":"string","format":"uri","description":"Token-signed Worker URL; PUT the raw bytes here."},"headers":{"type":"object","additionalProperties":{"type":"string"}},"expires_at":{"$ref":"#/components/schemas/Timestamp"}},"required":["method","url","headers","expires_at"]},"r2_key":{"type":"string","example":"projects/prj_01HY0AB1/original/file_01HY0C2D.bin"}},"required":["object","file_id","upload","r2_key"]},"DownloadIntent":{"type":"object","properties":{"object":{"const":"download_intent"},"url":{"type":"string","format":"uri"},"expires_at":{"$ref":"#/components/schemas/Timestamp"}},"required":["object","url","expires_at"]},"FileWithDownload":{"type":"object","properties":{"object":{"const":"file"},"id":{"type":"string","example":"file_01HY0C2D3E4F5G6H7J8K"},"project_id":{"type":"string"},"filename":{"type":"string"},"content_type":{"type":["string","null"]},"size_bytes":{"type":["integer","null"]},"checksum":{"type":["string","null"]},"checksum_algo":{"type":"string","example":"sha256"},"status":{"$ref":"#/components/schemas/FileStatus"},"download":{"oneOf":[{"$ref":"#/components/schemas/DownloadIntent"},{"type":"null"}],"description":"Present only when status is `ready`."},"created_at":{"$ref":"#/components/schemas/Timestamp"}},"required":["object","id","project_id","filename","status","created_at"]},"Operation":{"type":"object","description":"An ECU-independent operation verb the agent programs against. Carries composition metadata but no map values.","properties":{"object":{"const":"operation"},"id":{"type":"string","example":"op_01HXEGR0123456789AB"},"code":{"type":"string","description":"Stable agent-facing key.","example":"egr_off"},"label":{"type":"string","example":"EGR off"},"kind":{"$ref":"#/components/schemas/OperationKind"},"category":{"type":["string","null"],"example":"egr"},"fuel_scope":{"$ref":"#/components/schemas/FuelScope"},"is_staged":{"type":"boolean","description":"Part of a mutually-exclusive stage ladder (stage1<stage2<stage3)."},"stage_level":{"type":["integer","null"],"example":1},"reversible":{"type":"boolean"},"inverse_code":{"type":["string","null"],"description":"The counterpart that undoes this op; applying it clears this op.","example":"egr_on"},"affects_emissions":{"type":"boolean"},"off_road_only":{"type":"boolean"},"requires":{"type":"array","items":{"type":"string"},"description":"Operation codes that must also be present."},"conflicts_with":{"type":"array","items":{"type":"string"}},"supersedes":{"type":"array","items":{"type":"string"},"description":"Operation codes this one collapses out of the op-state.","example":["stage1"]},"apply_order":{"type":"integer","description":"Deterministic compose order; lower applies first.","example":100},"default_catalogue_refs":{"type":"array","items":{"type":"string"}},"summary":{"type":["string","null"]},"description":{"type":["string","null"]},"risks":{"type":["string","null"]},"legality":{"type":["string","null"]},"created_at":{"$ref":"#/components/schemas/Timestamp"},"updated_at":{"$ref":"#/components/schemas/Timestamp"}},"required":["object","id","code","label","kind","fuel_scope","apply_order","created_at"]},"OperationCreate":{"type":"object","properties":{"code":{"type":"string"},"label":{"type":"string"},"kind":{"$ref":"#/components/schemas/OperationKind"},"category":{"type":"string"},"fuel_scope":{"$ref":"#/components/schemas/FuelScope"},"is_staged":{"type":"boolean"},"stage_level":{"type":"integer"},"reversible":{"type":"boolean"},"inverse_code":{"type":"string"},"affects_emissions":{"type":"boolean"},"off_road_only":{"type":"boolean"},"requires":{"type":"array","items":{"type":"string"}},"conflicts_with":{"type":"array","items":{"type":"string"}},"supersedes":{"type":"array","items":{"type":"string"}},"apply_order":{"type":"integer"},"default_catalogue_refs":{"type":"array","items":{"type":"string"}},"summary":{"type":"string"},"description":{"type":"string"},"risks":{"type":"string"},"legality":{"type":"string"}},"required":["code","label","kind"]},"OperationAvailability":{"type":"object","description":"How a single operation would be realized on a given ECU.","properties":{"object":{"const":"operation_availability"},"code":{"type":"string","example":"egr_off"},"operation_id":{"type":"string"},"label":{"type":"string"},"availability":{"$ref":"#/components/schemas/OpAvailability"},"recipe_id":{"type":["string","null"],"description":"The active recipe id when `availability` is `deterministic`."},"confidence":{"type":["number","null"]},"is_staged":{"type":"boolean"},"stage_level":{"type":["integer","null"]}},"required":["object","code","operation_id","availability"]},"OperationRecipe":{"type":"object","description":"The deterministic, described map-change template that realizes an operation on a specific ECU (optionally a specific calibration).","properties":{"object":{"const":"operation_recipe"},"id":{"type":"string","example":"rcp_01HXR1A2B3C4D5E6F7G8"},"ecu_id":{"type":"string"},"operation_id":{"type":"string"},"software_id":{"type":["string","null"]},"stage_level":{"type":["integer","null"]},"map_changes":{"$ref":"#/components/schemas/MapChanges"},"safety_envelope":{"$ref":"#/components/schemas/SafetyEnvelope"},"expected_gains":{"$ref":"#/components/schemas/ExpectedGains"},"catalogue_refs":{"type":"array","items":{"type":"string"}},"source":{"$ref":"#/components/schemas/RecipeSource"},"source_mappack_id":{"type":["string","null"]},"confidence":{"type":["number","null"]},"status":{"$ref":"#/components/schemas/RecipeStatus"},"notes":{"type":["string","null"]},"version":{"type":"integer"},"created_by":{"type":["string","null"]},"created_at":{"$ref":"#/components/schemas/Timestamp"},"updated_at":{"$ref":"#/components/schemas/Timestamp"}},"required":["object","id","ecu_id","operation_id","map_changes","source","status","version","created_at"]},"OperationRecipeCreate":{"type":"object","properties":{"ecu_id":{"type":"string"},"operation_id":{"type":"string"},"software_id":{"type":"string"},"stage_level":{"type":"integer"},"changes":{"type":"array","items":{"$ref":"#/components/schemas/MapChange"},"description":"Convenience: wrapped into `map_changes` server-side."},"map_changes":{"$ref":"#/components/schemas/MapChanges"},"safety_envelope":{"$ref":"#/components/schemas/SafetyEnvelope"},"expected_gains":{"$ref":"#/components/schemas/ExpectedGains"},"catalogue_refs":{"type":"array","items":{"type":"string"}},"source":{"$ref":"#/components/schemas/RecipeSource"},"source_mappack_id":{"type":"string"},"confidence":{"type":"number"},"status":{"$ref":"#/components/schemas/RecipeStatus"},"notes":{"type":"string"}},"required":["ecu_id","operation_id"]},"OperationRecipeUpdate":{"type":"object","description":"Partial update; all fields optional.","properties":{"stage_level":{"type":"integer"},"changes":{"type":"array","items":{"$ref":"#/components/schemas/MapChange"}},"map_changes":{"$ref":"#/components/schemas/MapChanges"},"safety_envelope":{"$ref":"#/components/schemas/SafetyEnvelope"},"expected_gains":{"$ref":"#/components/schemas/ExpectedGains"},"catalogue_refs":{"type":"array","items":{"type":"string"}},"confidence":{"type":"number"},"status":{"$ref":"#/components/schemas/RecipeStatus"},"notes":{"type":"string"}},"additionalProperties":false},"MappackOperation":{"type":"object","description":"A mapping declaring that a mappack realizes an operation verb.","properties":{"object":{"const":"mappack_operation"},"id":{"type":"string","example":"mpo_01HXMO1234567890AB"},"mappack_id":{"type":"string"},"operation_id":{"type":"string"},"stage_level":{"type":["integer","null"]},"is_primary":{"type":"boolean"},"created_at":{"$ref":"#/components/schemas/Timestamp"}},"required":["object","id","mappack_id","operation_id","is_primary","created_at"]},"DetectInput":{"type":"object","description":"Any subset of identifiers to score against the catalogue.","properties":{"code":{"type":"string","example":"EDC17C64"},"sw_number":{"type":"string","example":"03L906022AB"},"hw_number":{"type":"string"},"file_size":{"type":"integer","example":2097152},"ident":{"type":"string","description":"An identification string read from the file."},"filename":{"type":"string","example":"stock.bin"},"vin":{"type":"string"}}},"DetectionCandidate":{"type":"object","description":"One ranked ECU detection candidate.","properties":{"object":{"const":"detection_candidate"},"score":{"type":"number","description":"Accumulated match score; higher is a better match."},"matched":{"type":"array","items":{"type":"string"},"description":"Which signals matched (e.g. code_exact, sw_number, file_size).","example":["code_exact","sw_number"]},"ecu":{"$ref":"#/components/schemas/Ecu"},"software":{"oneOf":[{"$ref":"#/components/schemas/EcuSoftware"},{"type":"null"}]}},"required":["object","score","matched","ecu"]},"EcuSoftware":{"type":"object","description":"A specific ECU calibration / software record.","properties":{"id":{"type":"string","example":"sw_01HXSW0123456789AB"},"ecu_id":{"type":"string"},"sw_number":{"type":["string","null"],"example":"03L906022AB"},"hw_number":{"type":["string","null"]},"sw_version":{"type":["string","null"]},"file_size_bytes":{"type":["integer","null"]},"checksum_layout":{"type":["string","null"]},"notes":{"type":["string","null"]},"provenance":{"type":"object","additionalProperties":true,"description":"Free-form provenance metadata (source, importer, etc.)."},"source_trust":{"type":"string","enum":["high","medium","low","unverified"]}},"required":["id","ecu_id"]},"InlineRecipe":{"type":"object","description":"An agent-synthesized recipe for an operation that has no deterministic recipe on this ECU (typically a stage). The agent builds it from the rich ECU record + catalogue reads.","properties":{"code":{"type":"string","description":"The operation code this inline recipe realizes.","example":"stage1"},"changes":{"type":"array","items":{"$ref":"#/components/schemas/MapChange"}},"safety_envelope":{"$ref":"#/components/schemas/SafetyEnvelope"},"expected_gains":{"$ref":"#/components/schemas/ExpectedGains"},"catalogue_refs":{"type":"array","items":{"type":"string"}},"confidence":{"type":"number"},"persist_as_candidate":{"type":"boolean","description":"When true, write the synthesized recipe back as a `candidate` so the next call is deterministic (the catalogue self-heals).","default":false}},"required":["code","changes"]},"ResolvedOp":{"type":"object","description":"One operation resolved to its change-set before composition.","properties":{"code":{"type":"string"},"operation_id":{"type":"string"},"apply_order":{"type":"integer"},"mode":{"$ref":"#/components/schemas/OpResolveMode"},"recipe_id":{"type":["string","null"]},"confidence":{"type":["number","null"]},"changes":{"type":"array","items":{"$ref":"#/components/schemas/MapChange"}},"envelope":{"$ref":"#/components/schemas/SafetyEnvelope"},"expected_gains":{"$ref":"#/components/schemas/ExpectedGains"},"catalogue_refs":{"type":"array","items":{"type":"string"}}},"required":["code","operation_id","apply_order","mode","changes"]},"ShadowRecord":{"type":"object","description":"A record that one operation's change terminally overrode an earlier one at the same (domain, table, region) key.","properties":{"op":{"type":"string"},"key":{"type":"string","example":"torque::lim::"},"superseded_by":{"type":"string"},"reason":{"type":"string","example":"set overrides earlier set"}},"required":["op","key","superseded_by","reason"]},"OperationWarning":{"type":"object","properties":{"code":{"type":"string","enum":["missing_recipe","low_confidence","operation_dropped","off_road_only","affects_emissions"],"example":"missing_recipe"},"operation":{"type":["string","null"],"example":"stage1"},"message":{"type":"string"}},"required":["code","message"]},"OperationApplyRequest":{"type":"object","description":"Drive the one-click engine. Provide `apply`/`revert` (and/or `return_to_ori`), optionally `dry_run`, and `inline_recipes[]` for any non-deterministic operation.","properties":{"apply":{"type":"array","items":{"type":"string"},"description":"Operation codes to add to the current op-state.","example":["egr_off","dpf_off","stage1"]},"revert":{"type":"array","items":{"type":"string"},"description":"Operation codes to remove from the current op-state.","example":["dpf_off"]},"return_to_ori":{"type":"boolean","description":"Clear the entire op-state and produce a clean ORI-equivalent version.","default":false},"dry_run":{"type":"boolean","description":"Resolve, compose and validate without persisting a version.","default":false},"software_id":{"type":"string","description":"Narrow recipe lookup to a specific ECU calibration."},"inline_recipes":{"type":"array","items":{"$ref":"#/components/schemas/InlineRecipe"},"description":"Agent-synthesized recipes for non-deterministic operations (typically stages)."},"label":{"type":"string","description":"Label for the emitted version."},"based_on_logs":{"type":"array","items":{"type":"string"}},"addresses_dtcs":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"OperationResult":{"type":"object","description":"The result of an apply/revert/dry-run: the resolved op-state, every operation resolved to a change-set, the composed effective change-set with shadow records, validation, and (on commit) the emitted version.","properties":{"object":{"const":"operation_result"},"dry_run":{"type":"boolean"},"op_state":{"type":"array","items":{"type":"string"},"description":"The resulting set of applied operation codes.","example":["egr_off","dpf_off","stage1"]},"dropped":{"type":"array","items":{"type":"string"},"description":"Operations collapsed out of the op-state by `supersedes`/`inverse`.","example":["stage1"]},"resolved":{"type":"array","items":{"$ref":"#/components/schemas/ResolvedOp"},"description":"Each operation in the op-state resolved (deterministic recipe or inline)."},"effective_changes":{"$ref":"#/components/schemas/MapChanges","description":"The single merged, deterministically-composed change-set."},"envelope":{"$ref":"#/components/schemas/SafetyEnvelope","description":"The tightened safety envelope across all resolved operations."},"expected_gains":{"$ref":"#/components/schemas/ExpectedGains"},"catalogue_refs":{"type":"array","items":{"type":"string"}},"shadowed":{"type":"array","items":{"$ref":"#/components/schemas/ShadowRecord"},"description":"Terminal overrides recorded during composition."},"warnings":{"type":"array","items":{"$ref":"#/components/schemas/OperationWarning"}},"validation":{"type":"object","description":"Catalogue + op-set validation outcome. On a non-dry-run commit, `valid: false` would have produced a 422 instead.","properties":{"valid":{"type":"boolean"},"errors":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"code":{"type":"string","example":"missing_prerequisite"},"message":{"type":"string"},"constraint_ref":{"type":"string"}},"required":["field","code","message"]}}},"required":["valid"]},"version":{"oneOf":[{"$ref":"#/components/schemas/Version"},{"type":"null"}],"description":"The emitted version (null on a dry-run)."}},"required":["object","dry_run","op_state","resolved","effective_changes","validation"]},"ListEnvelope":{"type":"object","description":"Cursor-paginated list wrapper. `data` holds the resource objects; loop while `page.has_more`, passing `cursor=page.next_cursor`.","properties":{"object":{"const":"list"},"data":{"type":"array","items":{"type":"object","additionalProperties":true}},"page":{"type":"object","properties":{"next_cursor":{"type":["string","null"]},"has_more":{"type":"boolean"},"page_size":{"type":"integer"}},"required":["next_cursor","has_more","page_size"]}},"required":["object","data","page"]},"ProblemDetails":{"type":"object","description":"RFC 9457 problem detail. Served as `application/problem+json`.","properties":{"type":{"type":"string","format":"uri","example":"https://stagemod.com/errors/unprocessable_entity"},"title":{"type":"string","example":"Validation failed"},"status":{"type":"integer","example":422},"code":{"type":"string","enum":["bad_request","unauthorized","invalid_api_key","insufficient_scope","forbidden","not_found","conflict","unprocessable_entity","rate_limited","internal_error"]},"detail":{"type":"string"},"request_id":{"type":"string","example":"req_01HY0K8M9N"},"errors":{"type":"array","description":"Per-field validation errors (422).","items":{"type":"object","properties":{"field":{"type":"string"},"code":{"type":"string"},"message":{"type":"string"},"constraint_ref":{"type":"string"}},"required":["field","code","message"]}}},"required":["type","title","status","code"]},"TicketStatus":{"type":"string","description":"Ticket lifecycle state. `resolved` is reached only through the resolve action (never a plain transition); `cancelled` is terminal.","enum":["open","triaged","in_progress","awaiting_customer","needs_admin","resolved","closed","reopened","cancelled"]},"TicketPriority":{"type":"string","enum":["low","normal","high","urgent"]},"TicketCategory":{"type":"string","enum":["tune_request","problem","question","feature","other"]},"TicketLinkEntity":{"type":"string","description":"Type of internal entity a ticket links to.","enum":["project","version","decision","mappack","operation","log","dtc"]},"TicketLinkRole":{"type":"string","description":"How the linked entity relates to the ticket.","enum":["related","produced","addresses","references"]},"TicketMessageAuthorRole":{"type":"string","enum":["member","admin","ai","system"]},"TicketMessageVisibility":{"type":"string","description":"`internal` messages are admin/AI-only working notes hidden from the member.","enum":["public","internal"]},"Ticket":{"type":"object","description":"A support ticket. Members own and view their own tickets via the web UI; this admin API exposes the full queue plus internal fields.","properties":{"object":{"const":"ticket"},"id":{"type":"string","example":"tkt_01HY0AB1C2D3E4F5G6H7"},"requester_id":{"type":"string","description":"User id of the member who opened the ticket."},"subject":{"type":"string"},"body":{"type":"string","description":"The opening description."},"category":{"$ref":"#/components/schemas/TicketCategory"},"vehicle":{"$ref":"#/components/schemas/VehicleInfo"},"ecu_code":{"type":["string","null"]},"status":{"$ref":"#/components/schemas/TicketStatus"},"priority":{"$ref":"#/components/schemas/TicketPriority"},"severity":{"type":["string","null"]},"assignee_id":{"type":["string","null"]},"sla_due_at":{"oneOf":[{"$ref":"#/components/schemas/Timestamp"},{"type":"null"}]},"first_response_at":{"oneOf":[{"$ref":"#/components/schemas/Timestamp"},{"type":"null"}]},"resolution_summary":{"type":["string","null"],"description":"The mandatory note captured when the ticket was resolved."},"resolution_decision_id":{"type":["string","null"],"description":"Id of the AI decision-graph `result` node emitted on resolution."},"resolved_at":{"oneOf":[{"$ref":"#/components/schemas/Timestamp"},{"type":"null"}]},"closed_at":{"oneOf":[{"$ref":"#/components/schemas/Timestamp"},{"type":"null"}]},"created_at":{"$ref":"#/components/schemas/Timestamp"},"updated_at":{"$ref":"#/components/schemas/Timestamp"}},"required":["object","id","requester_id","subject","body","category","status","priority","created_at","updated_at"]},"TicketCreate":{"type":"object","description":"Open a ticket on behalf of a member. `requester_id` is the member the ticket belongs to.","properties":{"requester_id":{"type":"string"},"subject":{"type":"string","minLength":1},"body":{"type":"string","minLength":1},"category":{"$ref":"#/components/schemas/TicketCategory"},"vehicle":{"$ref":"#/components/schemas/VehicleInfo"},"ecu_code":{"type":"string"},"priority":{"$ref":"#/components/schemas/TicketPriority"}},"required":["requester_id","subject","body"],"additionalProperties":false},"TicketUpdate":{"type":"object","description":"Patch mutable ticket fields. Status is changed via the `/transition` and `/resolve` actions, not here.","properties":{"subject":{"type":"string"},"category":{"$ref":"#/components/schemas/TicketCategory"},"priority":{"$ref":"#/components/schemas/TicketPriority"},"severity":{"type":"string"},"assignee_id":{"type":["string","null"]},"ecu_code":{"type":["string","null"]},"vehicle":{"$ref":"#/components/schemas/VehicleInfo"}},"additionalProperties":false},"TicketMessage":{"type":"object","properties":{"object":{"const":"ticket_message"},"id":{"type":"string","example":"tmsg_01HY0AB1C2D3E4F5G6H7"},"ticket_id":{"type":"string"},"author_id":{"type":["string","null"]},"author_role":{"$ref":"#/components/schemas/TicketMessageAuthorRole"},"visibility":{"$ref":"#/components/schemas/TicketMessageVisibility"},"body":{"type":"string"},"created_at":{"$ref":"#/components/schemas/Timestamp"}},"required":["object","id","ticket_id","author_role","visibility","body","created_at"]},"TicketMessageCreate":{"type":"object","description":"Post a message to the ticket thread. `internal` messages are admin/AI working notes hidden from the member.","properties":{"body":{"type":"string","minLength":1},"author_role":{"$ref":"#/components/schemas/TicketMessageAuthorRole"},"visibility":{"$ref":"#/components/schemas/TicketMessageVisibility"}},"required":["body"],"additionalProperties":false},"TicketEvent":{"type":"object","description":"An append-only audit entry in the ticket timeline.","properties":{"object":{"const":"ticket_event"},"id":{"type":"string","example":"tevt_01HY0AB1C2D3E4F5G6H7"},"ticket_id":{"type":"string"},"actor_id":{"type":["string","null"]},"actor_role":{"type":["string","null"]},"kind":{"type":"string","description":"e.g. `created`, `message`, `status_changed`, `linked`, `resolved`."},"from_status":{"type":["string","null"]},"to_status":{"type":["string","null"]},"summary":{"type":["string","null"]},"data":{"type":"object","additionalProperties":true},"created_at":{"$ref":"#/components/schemas/Timestamp"}},"required":["object","id","ticket_id","kind","created_at"]},"TicketLink":{"type":"object","description":"A typed link from a ticket to an internal entity (project, version, decision, mappack, operation, log, dtc).","properties":{"object":{"const":"ticket_link"},"id":{"type":"string","example":"tlnk_01HY0AB1C2D3E4F5G6H7"},"ticket_id":{"type":"string"},"entity_type":{"$ref":"#/components/schemas/TicketLinkEntity"},"entity_id":{"type":"string"},"role":{"$ref":"#/components/schemas/TicketLinkRole"},"note":{"type":["string","null"]},"created_at":{"$ref":"#/components/schemas/Timestamp"}},"required":["object","id","ticket_id","entity_type","entity_id","role","created_at"]},"TicketLinkCreate":{"type":"object","properties":{"entity_type":{"$ref":"#/components/schemas/TicketLinkEntity"},"entity_id":{"type":"string"},"role":{"$ref":"#/components/schemas/TicketLinkRole"},"note":{"type":"string"}},"required":["entity_type","entity_id"],"additionalProperties":false},"TicketTransition":{"type":"object","description":"Move the ticket to a new status. The target must be allowed from the current status; moving to `resolved` is rejected here — use the `/resolve` action so the mandatory note is captured.","properties":{"to_status":{"$ref":"#/components/schemas/TicketStatus"},"summary":{"type":"string"}},"required":["to_status"],"additionalProperties":false},"TicketResolve":{"type":"object","description":"Resolve the ticket. A non-empty `resolution_summary` is MANDATORY: it is stored on the ticket and recorded as an AI decision-graph `result` node before the status flips. Optionally link the artifacts the resolution produced.","properties":{"resolution_summary":{"type":"string","minLength":1,"description":"What was done and why. Required."},"links":{"type":"array","description":"Entities produced/addressed by the resolution.","items":{"type":"object","properties":{"entity_type":{"$ref":"#/components/schemas/TicketLinkEntity"},"entity_id":{"type":"string"},"role":{"type":"string","enum":["produced","addresses","references"]}},"required":["entity_type","entity_id"]}}},"required":["resolution_summary"],"additionalProperties":false},"EcuDefinition":{"type":"object","description":"A map catalogue / definition (where each map lives in a binary). Returned by the definition/mappack-catalogue endpoints; `object` is `ecu_definition`.","properties":{"object":{"const":"ecu_definition"},"id":{"type":"string","example":"mp_seed_clla_def"},"ecu_id":{"type":"string"},"software_id":{"type":["string","null"]},"format":{"type":["string","null"]},"filename":{"type":["string","null"]},"r2_key":{"type":["string","null"]},"checksum":{"type":["string","null"]},"map_count":{"type":["integer","null"]},"source_trust":{"type":["string","null"]},"primary_source_id":{"type":["string","null"]},"is_primary":{"type":"boolean"},"status":{"type":["string","null"]},"notes":{"type":["string","null"]},"created_at":{"$ref":"#/components/schemas/Timestamp"},"updated_at":{"$ref":"#/components/schemas/Timestamp"}},"required":["object","id","ecu_id"]},"EcuSoftwareCreate":{"type":"object","description":"Register an ecu_software identity under an ECU.","properties":{"sw_number":{"type":"string","example":"03L906022AB"},"hw_number":{"type":"string"},"sw_version":{"type":"string"},"file_size_bytes":{"type":"integer","minimum":0},"checksum_layout":{"type":"string"},"notes":{"type":"string"},"provenance":{"type":"object","additionalProperties":true},"source_trust":{"type":"string","enum":["high","medium","low","unverified"]}}},"ImportPayload":{"type":"object","description":"Normalized `stagemod.import/1` payload produced by the local a2l/DAMOS parser: axes, maps and checksum regions for a definition's (ecu, software).","properties":{"schema":{"const":"stagemod.import/1"},"software_id":{"type":"string"},"software":{"type":"object","properties":{"sw_number":{"type":"string"},"hw_number":{"type":"string"}}},"axes":{"type":"array","items":{"type":"object","properties":{"ref":{"type":"string","description":"Payload-local id."},"name":{"type":"string"},"kind":{"type":"string","enum":["x","y","z","static"]},"data_type":{"type":"string"},"address":{"type":"integer"},"address_kind":{"type":"string","enum":["cpu","file"]},"length":{"type":"integer"},"unit":{"type":"string"},"values":{"type":"array","items":{"type":"number"}}},"required":["ref","name"]}},"maps":{"type":"array","default":[],"items":{"type":"object","properties":{"name":{"type":"string"},"domain":{"type":"string"},"category":{"type":"string"},"address":{"type":"integer"},"address_kind":{"type":"string","enum":["cpu","file"]},"segment_base":{"type":"integer"},"data_type":{"type":"string"},"endian":{"type":"string","enum":["big","little"]},"rows":{"type":"integer","minimum":1},"cols":{"type":"integer","minimum":1},"x_axis_ref":{"type":"string"},"y_axis_ref":{"type":"string"},"unit":{"type":"string"},"min":{"type":"number"},"max":{"type":"number"},"record_layout":{"type":"string"},"note":{"type":"string"}},"required":["name","domain","address"]}},"checksum_regions":{"type":"array","items":{"type":"object","properties":{"algo":{"type":"string","enum":["sum16","sum32","crc16","crc32","bosch_rsa","custom","unknown"]},"start_address":{"type":"integer"},"end_address":{"type":"integer"},"store_address":{"type":"integer"},"address_kind":{"type":"string","enum":["cpu","file"]},"notes":{"type":"string"}}}}},"required":["maps"]}}},"externalDocs":{"description":"Concept model: Mappack (descriptive map catalogue) vs Solution (applicable mod) vs Operation (verb).","url":"https://stagemod.com/docs"}}