{
  "openapi": "3.1.0",
  "info": {
    "title": "Sarucci Public API",
    "description": "External Partner-facing REST API for Sarucci's price-recommendation engines.\n\n## Authentication\n\n1. Sarucci provisions your Partner account and gives you an `access_key` and\n   `secret_key`.\n2. `POST /auth/token` with those keys to receive a short-lived JWT.\n3. Send the JWT as `Authorization: Bearer <token>` on every request (in\n   Swagger UI, paste it into the green **Authorize** button).\n\nThere is no refresh token; re-exchange your keys when the JWT expires.\n\n## Errors\n\nEvery 4xx / 5xx response uses the envelope:\n\n```\n{\"error\": {\"code\": \"<machine_code>\", \"message\": \"<human>\", \"request_id\": \"<uuid>\"}}\n```\n\n## CORS / browser access\n\nThis API is designed for server-side clients. Browser-origin (CORS) access is\ndisabled unless Sarucci configures an explicit origin allowlist\n(`PUBLIC_API_CORS_ORIGINS`).\n",
    "contact": {
      "name": "Sarucci API Support",
      "email": "developers@sarucci.com"
    },
    "version": "1.0.0"
  },
  "servers": [
    {
      "url": "/public/v1",
      "description": "Public API (mounted on the main app)."
    }
  ],
  "paths": {
    "/auth/token": {
      "post": {
        "tags": ["Auth"],
        "summary": "Exchange an access/secret key pair for a JWT",
        "description": "Send your `access_key` and `secret_key` to receive a short-lived JWT. Use it as `Authorization: Bearer <token>` on every subsequent request. There is no refresh token; re-exchange your keys when the JWT expires.",
        "operationId": "public.auth.token",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TokenRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenResponse"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/companies": {
      "get": {
        "tags": ["Companies"],
        "summary": "List all Companies owned by the authenticated Partner",
        "description": "Return every Company mapped to the authenticated Partner.",
        "operationId": "public.companies.list",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CompanyList"
                }
              }
            }
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ]
      },
      "post": {
        "tags": ["Companies"],
        "summary": "Create a new Company under the authenticated Partner",
        "description": "The new Company is attached to the Partner via the partner-company mapping. Recommendations are unavailable until a PMS integration is connected or seed data is uploaded.",
        "operationId": "public.companies.create",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CompanyCreate"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CompanyOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ]
      }
    },
    "/companies/{company_id}": {
      "get": {
        "tags": ["Companies"],
        "summary": "Fetch a single Company",
        "description": "The Partner must own the Company; otherwise a 404 is returned (the API never reveals a company the caller cannot access).",
        "operationId": "public.companies.get",
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "parameters": [
          {
            "name": "company_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "title": "Company Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CompanyOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/recommendations": {
      "post": {
        "tags": ["V1 Pricing (rule-based)"],
        "summary": "Compute V1 (rule-based) recommendations for a date range",
        "description": "Pure function over the company's currently configured budgets, demands, events, strategy rules, and competitor data. Recomputed on every call and not persisted server-side.\n\n**Response shape:**\n- `current_rate` — the hotel's own rate for the day, when ingested.\n- `market_rate` — currently always `null`; placeholder until a   real market-rate source is wired.\n- `competitor_rate` — average competitor rate for the day.\n- `recommended_rate` — the V1 engine's output.\n- `justification` — human-readable explanation for display.",
        "operationId": "public.v1.recommendations.compute",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V1RecommendationRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1RecommendationRun"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ]
      }
    },
    "/v2/recommendations": {
      "get": {
        "tags": ["V2 Pricing (RL)"],
        "summary": "Read persisted RL recommendations (latest by default)",
        "description": "Returns recommendations from a persisted run, filtered to [start_date, end_date]. runs_ago selects the run by recency (0 = latest, the default; 1 = the run before it). Use GET /v2/runs to see how many runs exist. Returns 404 no_run_at_offset when no run exists at that offset.",
        "operationId": "public.v2.recommendations.read",
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "parameters": [
          {
            "name": "company_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "Company to read runs for.",
              "title": "Company Id"
            },
            "description": "Company to read runs for."
          },
          {
            "name": "start_date",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date",
              "description": "First date in the window (inclusive).",
              "title": "Start Date"
            },
            "description": "First date in the window (inclusive)."
          },
          {
            "name": "end_date",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date",
              "description": "Last date in the window (inclusive).",
              "title": "End Date"
            },
            "description": "Last date in the window (inclusive)."
          },
          {
            "name": "runs_ago",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "description": "0 = latest run, 1 = the one before it, and so on.",
              "default": 0,
              "title": "Runs Ago"
            },
            "description": "0 = latest run, 1 = the one before it, and so on."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V2RecommendationRun"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": ["V2 Pricing (RL)"],
        "summary": "Compute V2 (RL) recommendations on demand",
        "description": "Invokes the RL orchestrator synchronously. Expensive — prefer `GET /v2/recommendations` and rely on the nightly batch. Set `dry_run=true` to compute without persisting.",
        "operationId": "public.v2.recommendations.compute",
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V2RecommendationRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V2RecommendationRun"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/v2/runs": {
      "get": {
        "tags": ["V2 Pricing (RL)"],
        "summary": "List persisted RL runs (newest first)",
        "description": "Returns run metadata so you can pick a `runs_ago` offset for `GET /v2/recommendations`.",
        "operationId": "public.v2.runs.list",
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "parameters": [
          {
            "name": "company_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "Company to list runs for.",
              "title": "Company Id"
            },
            "description": "Company to list runs for."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V2RunList"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/v2/training": {
      "post": {
        "tags": ["V2 Pricing (RL)"],
        "summary": "Train a fresh RL policy artifact for a company",
        "description": "Synchronous training (typically 30s-5min; set your client timeout accordingly). The fresh artifact is persisted to GCS as the **latest** policy. Set `auto_approve=true` to also promote it to **approved** in the same call.",
        "operationId": "public.v2.training.run",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V2TrainingRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V2TrainingResponse"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ]
      }
    },
    "/v2/model/approve": {
      "post": {
        "tags": ["V2 Pricing (RL)"],
        "summary": "Promote a trained policy artifact to the approved slot",
        "description": "Use after training with `auto_approve=false`. The `model_id` must match the current latest artifact, otherwise you get `409 model_id_stale` (read `GET /v2/model` for the current id).",
        "operationId": "public.v2.model.approve",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V2TrainingApproveRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V2TrainingApproveResponse"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ]
      }
    },
    "/v2/model": {
      "get": {
        "tags": ["V2 Pricing (RL)"],
        "summary": "Inspect the active V2 policy",
        "description": "Reports which artifact backs RL inference for the company: `approved`, `latest`, or `rule` (rule-based fallback).",
        "operationId": "public.v2.model.status",
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "parameters": [
          {
            "name": "company_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "Company to inspect.",
              "title": "Company Id"
            },
            "description": "Company to inspect."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V2ModelStatusResponse"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "CompanyCreate": {
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 255,
            "minLength": 1,
            "title": "Name",
            "examples": ["Acme Beach Resort"]
          },
          "country": {
            "type": "string",
            "title": "Country",
            "description": "ISO-3166 country code or name; canonicalized on save.",
            "examples": ["GR", "Greece"]
          },
          "currency": {
            "type": "string",
            "title": "Currency",
            "description": "ISO-4217 currency code or name; canonicalized on save.",
            "examples": ["EUR", "USD", "GBP"]
          },
          "timezone": {
            "type": "string",
            "title": "Timezone",
            "description": "IANA timezone, e.g. `Europe/Athens`.",
            "examples": ["Europe/Athens"]
          },
          "total_rooms": {
            "type": "integer",
            "maximum": 100000.0,
            "minimum": 1.0,
            "title": "Total Rooms",
            "examples": [120]
          }
        },
        "type": "object",
        "required": ["name", "country", "currency", "timezone", "total_rooms"],
        "title": "CompanyCreate",
        "description": "Minimum-viable Company creation payload."
      },
      "CompanyList": {
        "properties": {
          "items": {
            "items": {
              "$ref": "#/components/schemas/CompanyOut"
            },
            "type": "array",
            "title": "Items"
          },
          "total": {
            "type": "integer",
            "title": "Total"
          }
        },
        "type": "object",
        "required": ["items", "total"],
        "title": "CompanyList",
        "description": "List response for `GET /companies`."
      },
      "CompanyOut": {
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "title": "Id"
          },
          "name": {
            "type": "string",
            "title": "Name"
          },
          "country": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Country"
          },
          "currency": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Currency"
          },
          "timezone": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Timezone"
          },
          "total_rooms": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Total Rooms"
          },
          "onboarded": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Onboarded",
            "description": "Onboarding progress flags for the Company (Sarucci-managed); partners read this to see how far data/integration setup has got."
          },
          "partner_id": {
            "anyOf": [
              {
                "type": "string",
                "format": "uuid"
              },
              {
                "type": "null"
              }
            ],
            "title": "Partner Id"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "title": "Created At"
          },
          "data_pipeline_inbox_email": {
            "type": "string",
            "title": "Data Pipeline Inbox Email",
            "description": "Send hotel data files (history & forecast, on-the-books, booking pace, cancellations, detailed availability) to this address — Sarucci's data pipeline parses incoming mail at this inbox and ingests the attachments for this Company.",
            "examples": ["support+datareports_1b5f37df-2115-4517-88be-74db629889ac@sarucci.com"]
          }
        },
        "type": "object",
        "required": [
          "id",
          "name",
          "country",
          "currency",
          "timezone",
          "total_rooms",
          "partner_id",
          "created_at",
          "data_pipeline_inbox_email"
        ],
        "title": "CompanyOut",
        "description": "Public view of a Company row, scoped to fields a partner can read."
      },
      "FeatureName": {
        "type": "string",
        "enum": [
          "pace_index",
          "booking_pace",
          "availability",
          "comp_delta",
          "cancel_prob",
          "channel_direct_ratio",
          "event_impact",
          "days_remaining"
        ],
        "title": "FeatureName",
        "description": "Closed set of feature dimensions consumed by the RL policy.\n\nString values are the on-wire names persisted in RL artifacts on GCS\nand returned in ``TopDriver.feature`` over the API. As a ``StrEnum``\nsubclass these values serialize to JSON as plain strings, so the wire\nformat is unchanged when Python code switches from raw strings to\nenum members."
      },
      "HTTPValidationError": {
        "properties": {
          "detail": {
            "items": {
              "$ref": "#/components/schemas/ValidationError"
            },
            "type": "array",
            "title": "Detail"
          }
        },
        "type": "object",
        "title": "HTTPValidationError"
      },
      "ModelArtifact": {
        "properties": {
          "model_id": {
            "type": "string",
            "title": "Model Id"
          },
          "trained_at": {
            "type": "string",
            "title": "Trained At"
          },
          "feature_schema_hash": {
            "type": "string",
            "title": "Feature Schema Hash"
          },
          "phase_feature_weights": {
            "additionalProperties": {
              "additionalProperties": {
                "type": "number"
              },
              "propertyNames": {
                "$ref": "#/components/schemas/FeatureName"
              },
              "type": "object"
            },
            "type": "object",
            "title": "Phase Feature Weights"
          },
          "approved": {
            "type": "boolean",
            "title": "Approved",
            "default": false
          },
          "approved_at": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Approved At"
          }
        },
        "type": "object",
        "required": ["model_id", "trained_at", "feature_schema_hash", "phase_feature_weights"],
        "title": "ModelArtifact",
        "description": "Versioned policy artifact persisted to GCS.\n\nThis is the on-wire shape of the ``latest_model.json`` and\n``approved_model.json`` blobs. It crosses a serialization boundary, so\nit lives as a Pydantic model (not a dataclass) to validate the payload\nat load time and catch schema drift early."
      },
      "TokenRequest": {
        "properties": {
          "access_key": {
            "type": "string",
            "maxLength": 64,
            "minLength": 8,
            "title": "Access Key"
          },
          "secret_key": {
            "type": "string",
            "maxLength": 128,
            "minLength": 16,
            "title": "Secret Key"
          }
        },
        "type": "object",
        "required": ["access_key", "secret_key"],
        "title": "TokenRequest",
        "description": "Exchange an access_key + secret_key for a partner JWT."
      },
      "TokenResponse": {
        "properties": {
          "access_token": {
            "type": "string",
            "title": "Access Token",
            "description": "JWT to send as `Authorization: Bearer …`."
          },
          "token_type": {
            "type": "string",
            "title": "Token Type",
            "default": "Bearer"
          },
          "expires_in": {
            "type": "integer",
            "title": "Expires In",
            "description": "Seconds until the JWT expires."
          }
        },
        "type": "object",
        "required": ["access_token", "expires_in"],
        "title": "TokenResponse",
        "description": "Response from `POST /auth/token`."
      },
      "V1Recommendation": {
        "properties": {
          "date": {
            "type": "string",
            "format": "date",
            "title": "Date"
          },
          "current_rate": {
            "anyOf": [
              {
                "type": "string",
                "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"
              },
              {
                "type": "null"
              }
            ],
            "title": "Current Rate",
            "description": "The hotel's current rate for this day, sourced from the `is_self=true` competitor-pricing row. Null when no self pricing has been ingested for this date."
          },
          "market_rate": {
            "anyOf": [
              {
                "type": "string",
                "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"
              },
              {
                "type": "null"
              }
            ],
            "title": "Market Rate",
            "description": "The market reference rate for this day. Currently always null — no real backing source is wired yet. Will populate when the data source is available."
          },
          "competitor_rate": {
            "anyOf": [
              {
                "type": "string",
                "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"
              },
              {
                "type": "null"
              }
            ],
            "title": "Competitor Rate",
            "description": "Average rate across the company's competitor set for this day. Null when no competitor pricing has been ingested."
          },
          "recommended_rate": {
            "type": "string",
            "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$",
            "title": "Recommended Rate",
            "description": "V1 engine output, rounded to 2 decimals."
          },
          "justification": {
            "type": "string",
            "title": "Justification",
            "description": "Human-readable explanation of how the recommended rate was derived. Suitable for display in a partner dashboard."
          }
        },
        "type": "object",
        "required": ["date", "recommended_rate", "justification"],
        "title": "V1Recommendation",
        "description": "One day's V1 recommendation, shaped for UI display."
      },
      "V1RecommendationRequest": {
        "properties": {
          "company_id": {
            "type": "string",
            "format": "uuid",
            "title": "Company Id",
            "description": "Company to compute recommendations for."
          },
          "start_date": {
            "type": "string",
            "format": "date",
            "title": "Start Date",
            "description": "First date in the window (inclusive)."
          },
          "end_date": {
            "type": "string",
            "format": "date",
            "title": "End Date",
            "description": "Last date in the window (inclusive)."
          }
        },
        "type": "object",
        "required": ["company_id", "start_date", "end_date"],
        "title": "V1RecommendationRequest",
        "description": "Body for `POST /v1/recommendations`."
      },
      "V1RecommendationRun": {
        "properties": {
          "run_id": {
            "type": "string",
            "format": "uuid",
            "title": "Run Id"
          },
          "company_id": {
            "type": "string",
            "format": "uuid",
            "title": "Company Id"
          },
          "business_date": {
            "type": "string",
            "format": "date",
            "title": "Business Date",
            "description": "Server clock day at the moment of computation."
          },
          "currency": {
            "type": "string",
            "title": "Currency",
            "description": "ISO-4217 currency code on the company."
          },
          "recommendations": {
            "items": {
              "$ref": "#/components/schemas/V1Recommendation"
            },
            "type": "array",
            "title": "Recommendations"
          },
          "warnings": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "title": "Warnings"
          },
          "computed_at": {
            "type": "string",
            "format": "date-time",
            "title": "Computed At"
          }
        },
        "type": "object",
        "required": [
          "run_id",
          "company_id",
          "business_date",
          "currency",
          "recommendations",
          "computed_at"
        ],
        "title": "V1RecommendationRun",
        "description": "Top-level response for `POST /v1/recommendations`.\n\n`run_id` is a fresh UUID — V1 is a pure function and its output is\nnot persisted server-side."
      },
      "V2Driver": {
        "properties": {
          "feature": {
            "type": "string",
            "title": "Feature",
            "description": "Feature name (e.g. `pace_index`)."
          },
          "label": {
            "type": "string",
            "title": "Label",
            "description": "Humanised feature name for display."
          },
          "impact": {
            "type": "number",
            "title": "Impact",
            "description": "Contribution magnitude."
          },
          "direction": {
            "type": "string",
            "title": "Direction",
            "description": "`up` | `down`."
          }
        },
        "type": "object",
        "required": ["feature", "label", "impact", "direction"],
        "title": "V2Driver",
        "description": "A ranked feature that drove the recommendation."
      },
      "V2GuardrailFlag": {
        "properties": {
          "code": {
            "type": "string",
            "title": "Code",
            "description": "Machine-readable guardrail code."
          },
          "label": {
            "type": "string",
            "title": "Label",
            "description": "Humanised label for display."
          }
        },
        "type": "object",
        "required": ["code", "label"],
        "title": "V2GuardrailFlag",
        "description": "A guardrail that fired for the recommendation."
      },
      "V2ModelStatusResponse": {
        "properties": {
          "company_id": {
            "type": "string",
            "format": "uuid",
            "title": "Company Id"
          },
          "approved_exists": {
            "type": "boolean",
            "title": "Approved Exists"
          },
          "latest_exists": {
            "type": "boolean",
            "title": "Latest Exists"
          },
          "active_policy_source": {
            "type": "string",
            "title": "Active Policy Source",
            "description": "Which artifact backs current inference: `approved` | `latest` | `rule` (rule-based fallback when no artifact is available)."
          },
          "approved_model_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Approved Model Id"
          },
          "latest_model_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Latest Model Id"
          }
        },
        "type": "object",
        "required": ["company_id", "approved_exists", "latest_exists", "active_policy_source"],
        "title": "V2ModelStatusResponse",
        "description": "Response for `GET /v2/model`."
      },
      "V2Recommendation": {
        "properties": {
          "date": {
            "type": "string",
            "format": "date",
            "title": "Date"
          },
          "room_type": {
            "type": "string",
            "title": "Room Type"
          },
          "recommended_rate": {
            "type": "string",
            "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$",
            "title": "Recommended Rate"
          },
          "delta_pct": {
            "type": "string",
            "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$",
            "title": "Delta Pct",
            "description": "Percent diff vs the room's current rate at compute time."
          },
          "confidence": {
            "type": "number",
            "title": "Confidence",
            "description": "Policy confidence in [0, 1]."
          },
          "phase": {
            "type": "string",
            "title": "Phase",
            "description": "`DISTRESSED` | `BUILD` | `YIELD` | `SPIKE` | …"
          },
          "drivers": {
            "items": {
              "$ref": "#/components/schemas/V2Driver"
            },
            "type": "array",
            "title": "Drivers"
          },
          "guardrails": {
            "items": {
              "$ref": "#/components/schemas/V2GuardrailFlag"
            },
            "type": "array",
            "title": "Guardrails"
          },
          "hitl_required": {
            "type": "boolean",
            "title": "Hitl Required",
            "default": false
          },
          "explanation": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Explanation"
          }
        },
        "type": "object",
        "required": ["date", "room_type", "recommended_rate", "delta_pct", "confidence", "phase"],
        "title": "V2Recommendation",
        "description": "One (date, room_type) recommendation produced by the RL policy."
      },
      "V2RecommendationRequest": {
        "properties": {
          "company_id": {
            "type": "string",
            "format": "uuid",
            "title": "Company Id",
            "description": "Company to compute recommendations for."
          },
          "business_date": {
            "anyOf": [
              {
                "type": "string",
                "format": "date"
              },
              {
                "type": "null"
              }
            ],
            "title": "Business Date",
            "description": "Anchor date for the run. Defaults to today (server clock) when omitted. The horizon extends forward from this date."
          },
          "horizon_days": {
            "type": "integer",
            "maximum": 365.0,
            "minimum": 1.0,
            "title": "Horizon Days",
            "description": "How many days forward from `business_date` to score.",
            "default": 90
          },
          "dry_run": {
            "type": "boolean",
            "title": "Dry Run",
            "description": "When true, the run is computed but not persisted (no row in `rl_pricing_run`). Use for sandboxing — `GET /v2/recommendations` won't see dry-run results.",
            "default": false
          }
        },
        "type": "object",
        "required": ["company_id"],
        "title": "V2RecommendationRequest",
        "description": "Body for `POST /v2/recommendations` (compute on demand).\n\nField-compatible with Sarucci's internal RL run request so partners and\ninternal callers share the same vocabulary."
      },
      "V2RecommendationRun": {
        "properties": {
          "run_id": {
            "type": "string",
            "format": "uuid",
            "title": "Run Id"
          },
          "company_id": {
            "type": "string",
            "format": "uuid",
            "title": "Company Id"
          },
          "business_date": {
            "type": "string",
            "format": "date",
            "title": "Business Date"
          },
          "currency": {
            "type": "string",
            "title": "Currency"
          },
          "fallback_used": {
            "type": "boolean",
            "title": "Fallback Used",
            "default": false
          },
          "recommendations": {
            "items": {
              "$ref": "#/components/schemas/V2Recommendation"
            },
            "type": "array",
            "title": "Recommendations"
          },
          "warnings": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "title": "Warnings"
          },
          "computed_at": {
            "type": "string",
            "format": "date-time",
            "title": "Computed At"
          }
        },
        "type": "object",
        "required": [
          "run_id",
          "company_id",
          "business_date",
          "currency",
          "recommendations",
          "computed_at"
        ],
        "title": "V2RecommendationRun",
        "description": "Top-level response for any `/v2/recommendations*` endpoint."
      },
      "V2RunList": {
        "properties": {
          "company_id": {
            "type": "string",
            "format": "uuid",
            "title": "Company Id"
          },
          "items": {
            "items": {
              "$ref": "#/components/schemas/V2RunSummary"
            },
            "type": "array",
            "title": "Items"
          },
          "total": {
            "type": "integer",
            "title": "Total"
          }
        },
        "type": "object",
        "required": ["company_id", "items", "total"],
        "title": "V2RunList",
        "description": "Response for `GET /v2/runs` — newest first."
      },
      "V2RunSummary": {
        "properties": {
          "runs_ago": {
            "type": "integer",
            "title": "Runs Ago",
            "description": "0 = latest, 1 = the run before it, and so on."
          },
          "run_id": {
            "type": "string",
            "format": "uuid",
            "title": "Run Id"
          },
          "business_date": {
            "type": "string",
            "format": "date",
            "title": "Business Date"
          },
          "recommendation_count": {
            "type": "integer",
            "title": "Recommendation Count"
          },
          "fallback_used": {
            "type": "boolean",
            "title": "Fallback Used"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "title": "Created At"
          }
        },
        "type": "object",
        "required": [
          "runs_ago",
          "run_id",
          "business_date",
          "recommendation_count",
          "fallback_used",
          "created_at"
        ],
        "title": "V2RunSummary",
        "description": "One persisted run's metadata, for run discovery."
      },
      "V2TrainingApproveRequest": {
        "properties": {
          "company_id": {
            "type": "string",
            "format": "uuid",
            "title": "Company Id",
            "description": "Company whose model to approve."
          },
          "model_id": {
            "type": "string",
            "title": "Model Id",
            "description": "Opaque artifact id (from `GET /v2/model` `latest_model_id`) to promote. Must match the current latest artifact or you get `409 model_id_stale`."
          }
        },
        "type": "object",
        "required": ["company_id", "model_id"],
        "title": "V2TrainingApproveRequest",
        "description": "Body for `POST /v2/model/approve`."
      },
      "V2TrainingApproveResponse": {
        "properties": {
          "company_id": {
            "type": "string",
            "format": "uuid",
            "title": "Company Id"
          },
          "model_id": {
            "type": "string",
            "title": "Model Id"
          },
          "approved_artifact_path": {
            "type": "string",
            "title": "Approved Artifact Path"
          },
          "artifact": {
            "$ref": "#/components/schemas/ModelArtifact"
          }
        },
        "type": "object",
        "required": ["company_id", "model_id", "approved_artifact_path", "artifact"],
        "title": "V2TrainingApproveResponse",
        "description": "Response for `POST /v2/model/approve`."
      },
      "V2TrainingRequest": {
        "properties": {
          "company_id": {
            "type": "string",
            "format": "uuid",
            "title": "Company Id",
            "description": "Company to train a policy for."
          },
          "business_date": {
            "anyOf": [
              {
                "type": "string",
                "format": "date"
              },
              {
                "type": "null"
              }
            ],
            "title": "Business Date",
            "description": "Defaults to today."
          },
          "horizon_days": {
            "type": "integer",
            "maximum": 365.0,
            "minimum": 1.0,
            "title": "Horizon Days",
            "default": 90
          },
          "epochs": {
            "type": "integer",
            "maximum": 200.0,
            "minimum": 1.0,
            "title": "Epochs",
            "default": 12
          },
          "clip_epsilon": {
            "type": "number",
            "maximum": 1.0,
            "exclusiveMinimum": 0.0,
            "title": "Clip Epsilon",
            "default": 0.2
          },
          "learning_rate": {
            "type": "number",
            "maximum": 1.0,
            "exclusiveMinimum": 0.0,
            "title": "Learning Rate",
            "default": 0.05
          },
          "auto_approve": {
            "type": "boolean",
            "title": "Auto Approve",
            "description": "When true, the freshly-trained model is also promoted to the approved slot in one call. When false, run `POST /v2/model/approve` (or set this flag on the next training call) to start using it for inference.",
            "default": false
          }
        },
        "type": "object",
        "required": ["company_id"],
        "title": "V2TrainingRequest",
        "description": "Body for `POST /v2/training`."
      },
      "V2TrainingResponse": {
        "properties": {
          "company_id": {
            "type": "string",
            "format": "uuid",
            "title": "Company Id"
          },
          "business_date": {
            "type": "string",
            "format": "date",
            "title": "Business Date"
          },
          "horizon_days": {
            "type": "integer",
            "title": "Horizon Days"
          },
          "state_count": {
            "type": "integer",
            "title": "State Count",
            "description": "Number of (date, room_type) states fed into the trainer."
          },
          "warnings": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "title": "Warnings"
          },
          "model_id": {
            "type": "string",
            "title": "Model Id"
          },
          "trained_at": {
            "type": "string",
            "title": "Trained At"
          },
          "latest_artifact_path": {
            "type": "string",
            "title": "Latest Artifact Path",
            "description": "GCS URI of the trained artifact. Operator-only; partners should rely on the inline `artifact` payload below for model contents."
          },
          "approved_artifact_path": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Approved Artifact Path",
            "description": "GCS URI of the approved artifact, set only when `auto_approved=true`. Operator-only."
          },
          "auto_approved": {
            "type": "boolean",
            "title": "Auto Approved"
          },
          "artifact": {
            "$ref": "#/components/schemas/ModelArtifact",
            "description": "Full trained-model artifact (model_id, trained_at, feature_schema_hash, per-phase feature weights, approval state). Inspect this to understand exactly what the trainer produced — partners do not have direct GCS access."
          }
        },
        "type": "object",
        "required": [
          "company_id",
          "business_date",
          "horizon_days",
          "state_count",
          "model_id",
          "trained_at",
          "latest_artifact_path",
          "auto_approved",
          "artifact"
        ],
        "title": "V2TrainingResponse",
        "description": "Response for `POST /v2/training`."
      },
      "ValidationError": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": ["loc", "msg", "type"],
        "title": "ValidationError"
      }
    },
    "securitySchemes": {
      "HTTPBearer": {
        "type": "http",
        "description": "Partner JWT obtained from POST /auth/token.",
        "scheme": "bearer"
      }
    }
  },
  "tags": [
    {
      "name": "Auth",
      "description": "Exchange keys for a JWT."
    },
    {
      "name": "Companies",
      "description": "Manage Companies under your Partner account."
    },
    {
      "name": "V1 Pricing (rule-based)",
      "description": "Sarucci's rule-based engine. Pure function over your configured budgets, demands, events, and competitor data. Recomputed on every call; not persisted."
    },
    {
      "name": "V2 Pricing (RL)",
      "description": "Reinforcement-learning policy. Read persisted runs, recompute on demand, retrain, or inspect the active policy."
    }
  ]
}
