{
  "openapi": "3.1.0",
  "info": {
    "title": "Rikskampen X Theme 5 API — shared components",
    "version": "0.1.0",
    "description": "Shared security schemes, parameters, schemas and error responses referenced by every Theme 5 area file (API01, decision D11). Proposal until the contract is verified by the runner."
  },
  "paths": {},
  "components": {
    "securitySchemes": {
      "coachAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "Coach session. The server authorizes every request: the coach must be assigned to the client in the path."
      },
      "adminAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "Administrator session (role admin). Required for configuration such as the safety floor; every change is audited."
      }
    },
    "parameters": {
      "ClientId": {
        "name": "clientId",
        "in": "path",
        "required": true,
        "description": "Opaque client identifier.",
        "schema": {
          "type": "string",
          "pattern": "^[A-Za-z0-9_-]{1,64}$"
        }
      },
      "Week": {
        "name": "week",
        "in": "path",
        "required": true,
        "description": "Program week, counted from the client's program start date.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 52
        }
      },
      "DayIndex": {
        "name": "dayIndex",
        "in": "path",
        "required": true,
        "description": "Day of the program week, Monday = 0 to Sunday = 6 (decision D9). A day whose Europe/Stockholm date is before today is locked.",
        "schema": {
          "type": "integer",
          "minimum": 0,
          "maximum": 6
        }
      },
      "MealType": {
        "name": "mealType",
        "in": "path",
        "required": true,
        "description": "Meal of the day; \"Snacks\" is shown as Snack 1.",
        "schema": {
          "$ref": "#/components/schemas/MealType"
        }
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": true,
        "description": "Client-generated key; repeating a write with the same key and body returns the first result and never applies it twice.",
        "schema": {
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        }
      },
      "RecordId": {
        "name": "recordId",
        "in": "path",
        "required": true,
        "description": "Opaque raw activity record identifier.",
        "schema": {
          "type": "string",
          "pattern": "^[A-Za-z0-9_-]{1,64}$"
        }
      },
      "SuggestionId": {
        "name": "suggestionId",
        "in": "path",
        "required": true,
        "description": "Opaque AI suggestion identifier (AI addendum).",
        "schema": {
          "type": "string",
          "pattern": "^suggestion-[0-9]+$"
        }
      }
    },
    "schemas": {
      "MealType": {
        "type": "string",
        "enum": [
          "Breakfast",
          "Snacks",
          "Lunch",
          "Snack 2",
          "Dinner"
        ],
        "description": "Canonical order Breakfast, Snack 1 (Snacks), Lunch, optional Snack 2, Dinner."
      },
      "Revision": {
        "type": "integer",
        "minimum": 0,
        "description": "Optimistic-concurrency revision; increases by one with every applied write."
      },
      "AuditStamp": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "auditId",
          "actorId",
          "at"
        ],
        "properties": {
          "auditId": {
            "type": "string"
          },
          "actorId": {
            "type": "string"
          },
          "at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Warning": {
        "type": "string",
        "enum": [
          "CLAMPED_TO_REACHABLE",
          "BELOW_SAFETY_FLOOR"
        ],
        "description": "CLAMPED_TO_REACHABLE: the requested total was not reachable with the meals present and was clamped (D1). BELOW_SAFETY_FLOOR: the coach acknowledged a Current target below the configured safety floor (D4, audited; real-client gate)."
      },
      "Error": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "code",
          "message"
        ],
        "properties": {
          "code": {
            "type": "string",
            "enum": [
              "VALIDATION_FAILED",
              "UNAUTHENTICATED",
              "FORBIDDEN",
              "NOT_FOUND",
              "STALE_REVISION",
              "DAY_LOCKED",
              "IDEMPOTENCY_KEY_REUSED",
              "OUT_OF_BOUNDS",
              "CONFIRMATION_REQUIRED",
              "DUPLICATE_SOURCE_EVENT",
              "ALREADY_DECIDED",
              "AI_SUGGESTION_REJECTED",
              "AI_UNAVAILABLE"
            ]
          },
          "message": {
            "type": "string"
          },
          "details": {
            "type": [
              "array",
              "null"
            ],
            "description": "Field-level reasons for VALIDATION_FAILED and OUT_OF_BOUNDS; null otherwise.",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "field",
                "reason"
              ],
              "properties": {
                "field": {
                  "type": "string"
                },
                "reason": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "VALIDATION_FAILED: a path, query or header parameter that does not match its schema, or request-body structure — a missing required field, a wrong type, an unknown property, a malformed date, an unknown enum value or duplicate array items. Nothing was changed.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "No valid session (UNAUTHENTICATED).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "The caller may not act on this client or setting (FORBIDDEN).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "The client, week, day or record does not exist (NOT_FOUND).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Conflict": {
        "description": "Stale expectedRevision (STALE_REVISION), a locked past day (DAY_LOCKED), an Idempotency-Key reused with a different body (IDEMPOTENCY_KEY_REUSED) or a source event that is already stored (DUPLICATE_SOURCE_EVENT). Nothing was changed.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unprocessable": {
        "description": "A well-formed request-body value of the right type that breaks a value bound — minimum, exclusiveMinimum, maximum, multipleOf, minLength or maxLength (OUT_OF_BOUNDS) — or a required confirmation flag that is false (CONFIRMATION_REQUIRED). Nothing was changed.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "SuggestionConflict": {
        "description": "A locked past day (DAY_LOCKED), an Idempotency-Key reused with a different body (IDEMPOTENCY_KEY_REUSED) or a suggestion that is already decided (ALREADY_DECIDED). Nothing was changed.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "AiSuggestionRejected": {
        "description": "The AI suggestion breaks the explicit backend rules (AI_SUGGESTION_REJECTED). Nothing was changed.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "AiUnavailable": {
        "description": "The self-hosted AI provider is not available (AI_UNAVAILABLE). Nothing was changed.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  }
}
