diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..de6fe15 --- /dev/null +++ b/.env.example @@ -0,0 +1,10 @@ +# Athena API +ATHENA_BASE_URL=https://athena.nttdata.com +ATHENA_API_KEY=your-api-key-here + +# Optional — pre-set the active study + tool so notebooks/CLI pick them up +# without editing config.py. +# +# PALLADIUM_REPORT_PUBLIC_ID= +# PALLADIUM_TOOL_PUBLIC_ID= +# PALLADIUM_PROPOSAL_ID= diff --git a/.gitignore b/.gitignore index 0c8bc9a..55b56d4 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,11 @@ .ipynb_checkpoints */.ipynb_checkpoints/* +# Palladium-specific +.env +studies/*/exports/* +!studies/*/exports/.gitkeep + # IPython profile_default/ ipython_config.py diff --git a/README.md b/README.md index 826fcce..9056c25 100644 --- a/README.md +++ b/README.md @@ -2,45 +2,47 @@ **TEI (Total Economic Impact) Calculator** — The strategic artifact that protects the business case. -Palladium is a Jupyter notebook-based calculator and Streamlit application for building Total Economic Impact analyses. It connects to [Athena](https://athena.nttdata.com) for data persistence, performs financial calculations (NPV, ROI, payback period), and exports structured data for the report generation pipeline. +Palladium is a Jupyter notebook + Streamlit toolkit for building Total Economic Impact analyses. It connects to [Athena](https://athena.nttdata.com) for data persistence, performs financial calculations (NPV, ROI, payback period), and exports structured data for the report generation pipeline. > *In Greek mythology, the Palladium was a sacred artifact of Athena that protected Troy. Whoever possessed it held strategic advantage. In our ecosystem, Palladium protects the deal — transforming discovery inputs into a financial case no CFO can ignore.* ## Architecture ``` -┌─────────────────────────────────────────────────────────┐ -│ Palladium │ -│ │ -│ ┌──────────────┐ ┌──────────────┐ ┌───────────┐ │ -│ │ Notebooks │ │ Streamlit │ │ Export │ │ -│ │ (Analysis) │ │ (Data Entry)│ │ (Report) │ │ -│ └──────┬───────┘ └──────┬───────┘ └─────┬─────┘ │ -│ │ │ │ │ -│ └───────────┬───────┘ │ │ -│ ▼ │ │ -│ ┌──────────────────┐ │ │ -│ │ TEI Client │ │ │ -│ │ (API Layer) │──────────────────┘ │ -│ └────────┬─────────┘ │ -└────────────────────┼────────────────────────────────────┘ - │ - ▼ - ┌─────────────┐ ┌──────────────────┐ - │ Athena │ │ Report Pipeline │ - │ (API) │ │ (html2docx) │ - └─────────────┘ └──────────────────┘ +┌──────────────────────────────────────────────────────────────────┐ +│ Palladium │ +│ │ +│ studies/202602_AmazonConnect/ ← one folder per TEI study │ +│ studies/YYYYMM_/ │ +│ ├─ notebooks/ ─┐ │ +│ ├─ seed_data.py │ │ +│ └─ config.py │ │ +│ ▼ │ +│ ┌──────────────┐ ┌──────────────┐ │ +│ │ core/ │ ←─ │ app/ │ │ +│ │ shared logic │ │ Streamlit │ │ +│ └──────┬───────┘ └──────┬───────┘ │ +│ │ │ │ +│ ▼ ▼ │ +│ tei_client → ───────────────────► Athena API │ +│ calculations │ +│ export ──────────────────────────► export.json │ +│ notebook_helpers │ +│ cli │ +└──────────────────────────────────────────────────────────────────┘ ``` ### Components | Component | Purpose | |-----------|---------| -| **TEI Client** | Python API client for Athena's TEI endpoints | -| **Calculations** | Financial logic — NPV, ROI, payback, risk adjustment | -| **Notebooks** | Interactive analysis — benefits, costs, business case | -| **Streamlit App** | Data entry UI with version management | -| **Export** | Structured JSON for the LLM report generation pipeline | +| **`core/tei_client`** | Python API client for Athena's TEI endpoints | +| **`core/calculations`** | Financial logic — NPV, ROI, payback, risk adjustment, scenarios | +| **`core/export`** | Builds the structured JSON envelope consumed by the report pipeline | +| **`core/notebook_helpers`** | Pandas tables, Plotly charts, IPython display widgets | +| **`core/cli`** | `python -m palladium` command-line interface | +| **`app/`** | Streamlit data-entry UI with version management — *study-agnostic* | +| **`studies/`** | One folder per TEI engagement (notebooks, seed data, config, source PDF) | --- @@ -82,26 +84,26 @@ ATHENA_API_KEY=your-api-key-here python -m palladium test ``` -Or in a notebook: +Or in Python: ```python -from tei_client import TEIClient +from core.tei_client import TEIClient client = TEIClient() -result = client.test_connection() -print(result) # {'status': 'ok', 'authenticated': True, ...} +print(client.test_connection()) # {'status': 'ok', 'authenticated': True, ...} ``` --- ## Usage -### Jupyter Notebooks +### Run a study end-to-end -The primary workflow for TEI analysis: +Each study lives in `studies//`. The reference study is the +February 2026 Forrester *Total Economic Impact™ Of Amazon Connect*: ```bash -jupyter lab notebooks/ +jupyter lab studies/202602_AmazonConnect/notebooks/ ``` | Notebook | Purpose | @@ -109,11 +111,15 @@ jupyter lab notebooks/ | `01_benefits.ipynb` | Quantify and risk-adjust benefit categories | | `02_costs.ipynb` | Document implementation and ongoing costs | | `03_business_case.ipynb` | Financial summary, scenario analysis, visualizations | -| `04_export.ipynb` | Generate report-ready JSON for html2docx pipeline | +| `04_export.ipynb` | Generate report-ready JSON for the html2docx pipeline | -### Streamlit Application +The Amazon Connect notebooks reproduce the published study totals within +rounding: **NPV $78.7M • ROI 342% • Payback <6 months**. -Interactive UI for data entry and version management: +### Streamlit application (study-agnostic) + +Interactive UI for data entry and version management. Works for any TEI +study because field definitions come from Athena at runtime: ```bash streamlit run app/main.py @@ -125,21 +131,56 @@ streamlit run app/main.py # Test connection python -m palladium test -# List TEI instances +# List TEI tool instances python -m palladium list -# Show financial summary +# List available report templates +python -m palladium reports + +# Show financial summary for a tool python -m palladium summary -# Export for report pipeline +# Trigger server-side recalculation +python -m palladium calculate + +# Export for the report pipeline python -m palladium export -o export.json ``` +### Tests + +```bash +pytest tests/ -v +``` + +50 tests cover the API client (mocked HTTP), the financial math, and the +export envelope shape. The Amazon Connect seed data is asserted against +the published Forrester totals. + +--- + +## Adding a new study + +```bash +cp -r studies/202602_AmazonConnect studies/202612_GenesysCloud +cd studies/202612_GenesysCloud +``` + +1. **`README.md`** — update the title, source citation, key numbers. +2. **`seed_data.py`** — replace `BENEFITS` and `COSTS` with the new study's rows. +3. **`config.py`** — set `STUDY_SLUG`, leave `TOOL_PUBLIC_ID` blank until provisioned. +4. **`docs/`** — drop the source PDF here. +5. Open the notebooks; the imports (`core.calculations`, `core.notebook_helpers`, + `core.tei_client`) are study-agnostic. Update the markdown narrative. + +The shared `core/` package and the `app/` Streamlit UI need no changes — +they introspect the TEI Report template via the API. + --- ## TEI Methodology -Palladium implements the Forrester TEI™ framework [1]: +Palladium implements the Forrester TEI™ framework. ### Benefit Categories @@ -154,25 +195,32 @@ Benefits are quantified across categories, risk-adjusted, and discounted to pres ### Risk Adjustment -Each benefit carries a risk adjustment factor (0–50%) reflecting implementation uncertainty. A 20% risk adjustment on a $10M benefit yields a risk-adjusted value of $8M. +Each benefit carries a risk-adjustment factor (0–50%) reflecting implementation uncertainty. +A 20% risk adjustment on a $10M benefit yields a risk-adjusted value of $8M. +**Costs** are risk-adjusted **upward** by the same factor (higher risk → higher modelled cost). ### Financial Metrics | Metric | Description | |--------|-------------| | **NPV** | Net Present Value — total risk-adjusted benefits minus costs, discounted | -| **ROI** | Return on Investment — (benefits - costs) / costs × 100 | +| **ROI** | Return on Investment — `(benefits − costs) / costs × 100` | | **Payback** | Months until cumulative benefits exceed cumulative costs | +The initial investment (year 0) is **not** discounted. Year-N cashflows are +discounted at the end of the year: `PV = CF_n / (1 + r)^n`. This matches +the Forrester methodology used in the published studies. + ### Scenario Analysis -Three scenarios model uncertainty in adoption and realization: +Three scenarios model uncertainty in adoption and realization +(see `core.calculations.SCENARIOS`): -| Scenario | Approach | -|----------|----------| -| Conservative | Higher risk adjustments, lower adoption rates | -| Moderate | Balanced assumptions (base case) | -| Aggressive | Lower risk adjustments, faster adoption | +| Scenario | Adoption | Risk delta | Effect | +|----------|----------|------------|--------| +| Conservative | 80% | +10pp on benefits | Lower benefits, higher modelled cost | +| Moderate | 100% | 0 | Base case (= published study) | +| Aggressive | 115% | –5pp on benefits | Higher benefits, lower padding on cost | --- @@ -180,40 +228,48 @@ Three scenarios model uncertainty in adoption and realization: ``` palladium/ -├── app/ # Streamlit application -│ ├── main.py # App entry point -│ ├── pages/ -│ │ ├── benefits.py # Benefits data entry -│ │ ├── costs.py # Costs data entry -│ │ ├── summary.py # Financial summary dashboard -│ │ └── versions.py # Version history & comparison -│ └── components/ -│ ├── charts.py # Visualization components -│ └── tables.py # Data table components -├── notebooks/ # Jupyter analysis notebooks -│ ├── 01_benefits.ipynb -│ ├── 02_costs.ipynb -│ ├── 03_business_case.ipynb -│ └── 04_export.ipynb -├── tei_client/ # Athena API client -│ ├── __init__.py -│ ├── client.py # HTTP client with auth -│ └── models.py # Response data models -├── calculations/ # Financial calculation engine -│ ├── __init__.py -│ ├── npv.py # Net present value -│ ├── roi.py # Return on investment -│ ├── payback.py # Payback period -│ └── scenarios.py # Scenario multipliers -├── export/ # Report pipeline export -│ ├── __init__.py -│ └── report_data.py # JSON export for html2docx -├── tests/ +├── core/ # Shared, study-agnostic Python package +│ ├── tei_client/ # Athena API client +│ │ ├── client.py # TEIClient with all /api/v1/tei/ methods +│ │ └── models.py # Optional dataclasses for typed access +│ ├── calculations/ # Pure-python financial math +│ │ ├── npv.py +│ │ ├── roi.py +│ │ ├── payback.py +│ │ └── scenarios.py +│ ├── export/ +│ │ └── report_data.py # JSON envelope for the report pipeline +│ ├── notebook_helpers/ +│ │ ├── tables.py # Pandas dataframe builders +│ │ ├── charts.py # Plotly figures +│ │ └── display.py # IPython KPI cards, alerts +│ └── cli/ +│ └── main.py # `python -m palladium ...` +├── palladium/ # CLI shim (just exposes `python -m palladium`) +│ └── __main__.py +├── app/ # Streamlit UI — works with any TEI study +│ ├── main.py # entry point +│ ├── pages/ # benefits, costs, summary, versions +│ └── components/ # tables, charts +├── studies/ # One folder per TEI engagement +│ └── 202602_AmazonConnect/ +│ ├── README.md +│ ├── config.py # TOOL_PUBLIC_ID, REPORT_PUBLIC_ID +│ ├── seed_data.py # 5 benefits + 3 costs from the PDF +│ ├── notebooks/ +│ │ ├── 01_benefits.ipynb +│ │ ├── 02_costs.ipynb +│ │ ├── 03_business_case.ipynb +│ │ └── 04_export.ipynb +│ ├── exports/ # generated; .gitignored +│ └── docs/ +│ └── 202602_TEI Report Amazon Connect.pdf +├── tests/ # 50 tests for core/ │ ├── test_client.py │ ├── test_calculations.py │ └── test_export.py +├── Athena API.yaml # OpenAPI reference ├── .env.example -├── .gitignore ├── requirements.txt ├── pyproject.toml └── README.md @@ -227,18 +283,36 @@ Palladium connects to Athena's TEI module for data persistence and cross-tool re ### API Endpoints Used +All endpoints are under `/api/v1/tei/` and require `Authorization: Api-Key {key}`. + | Endpoint | Purpose | |----------|---------| -| `GET /forge/api/tei/reports/` | List available TEI model templates | -| `GET /forge/api/tei/reports/{id}/fields/` | Get field definitions for a model | -| `POST /forge/api/tei/tools/` | Create new TEI instance | -| `GET /forge/api/tei/tools/{public_id}/` | Get instance metadata | -| `GET /forge/api/tei/tools/{public_id}/values/` | Get current field values | -| `PUT /forge/api/tei/tools/{public_id}/values/` | Bulk update values | -| `POST /forge/api/tei/tools/{public_id}/calculate/` | Trigger calculation | -| `GET /forge/api/tei/tools/{public_id}/summary/` | Get financial summary | -| `POST /forge/api/tei/tools/{public_id}/versions/` | Save version snapshot | -| `GET /forge/api/tei/tools/{public_id}/export/` | Export for report pipeline | +| `GET /api/v1/tei/reports/` | List available TEI report templates | +| `GET /api/v1/tei/reports/{public_id}/` | Get a report template | +| `GET /api/v1/tei/reports/{public_id}/fields/` | Get field definitions for a template | +| `POST /api/v1/tei/tools/` | Create a new TEI tool instance | +| `GET /api/v1/tei/tools/{public_id}/` | Get instance metadata | +| `PATCH /api/v1/tei/tools/{public_id}/` | Update name/status | +| `GET /api/v1/tei/tools/{public_id}/values/` | Get current field values | +| `PUT /api/v1/tei/tools/{public_id}/values/` | Bulk-update values | +| `PATCH /api/v1/tei/tools/{public_id}/values/{field_key}/` | Patch a single value | +| `POST /api/v1/tei/tools/{public_id}/calculate/` | Trigger calculation | +| `GET /api/v1/tei/tools/{public_id}/summary/` | Get financial summary | +| `GET /api/v1/tei/tools/{public_id}/versions/` | List version snapshots | +| `POST /api/v1/tei/tools/{public_id}/versions/` | Save a new version | +| `GET /api/v1/tei/tools/{public_id}/versions/{n}/` | Get a specific version | +| `GET /api/v1/tei/tools/{public_id}/export/` | Export for the report pipeline | +| `GET /api/v1/tei/summary/` | Aggregate NPV across all tools | + +### Object model + +| Athena object | Notes | +|---|---| +| **Opportunity** | Top-level sales record. Owns one or more **Proposals**. | +| **Proposal** | A specific bid/offer to a client. **A TEI tool is linked to a Proposal.** | +| **Engagement** | Optional — for active client engagements. A TEI tool may also link here. | +| **TEIReport** | Template (e.g. *Amazon Connect 2026*) — defines fields, discount rate, analysis horizon. | +| **TEITool** | Instance of a Report bound to a Proposal — holds values, summaries, versions. | ### Authentication @@ -258,7 +332,7 @@ Palladium's export produces structured JSON consumed by the LLM report generatio Palladium Export (JSON) │ ▼ -LLM generates HTML (following HTML_DOCUMENT_FORMAT.md) +Peitho — LLM generates HTML (following HTML_DOCUMENT_FORMAT.md) │ ▼ html2docx converts to native Word @@ -267,23 +341,23 @@ html2docx converts to native Word Professional TEI Report (.docx) ``` -The export JSON includes: +The export envelope (`core.export.build_report_data`) includes: - All benefit categories with risk-adjusted values -- All cost categories with yearly breakdown -- Financial summary (NPV, ROI, payback) -- Yearly cash flow data (for waterfall/bar charts) -- Scenario analysis results (conservative/moderate/aggressive) -- Metadata (client, opportunity, analysis period, discount rate) +- All cost categories with yearly breakdown (and Initial column) +- Financial summary (NPV, ROI, payback, yearly cashflow) +- Conservative / moderate / aggressive scenario analysis +- Metadata (study slug, proposal, engagement, generator stamp) +- The raw Athena `/export/` payload for reference --- ## Version Management -Palladium manages version history through the Streamlit UI: +Palladium manages version history through both the API and the Streamlit UI: -1. **Save Version** — Snapshots current values + financial summary with a descriptive note -2. **View History** — See all versions with headline metrics (NPV, ROI) -3. **Compare Versions** — Side-by-side diff showing what changed between any two versions +1. **Save Version** — Snapshots current values + summary with a descriptive note +2. **View History** — All versions with headline metrics (NPV, ROI) +3. **Compare Versions** — Side-by-side diff of value changes between any two versions 4. **Restore Version** — Load a previous version's values as the current state Version notes should capture: @@ -302,6 +376,10 @@ Version notes should capture: pytest tests/ -v ``` +Tests are designed to run without an Athena connection — HTTP is mocked +and the calculation suite uses the Amazon Connect seed data to verify the +Forrester numbers reproduce within rounding. + ### Code Style ```bash @@ -311,10 +389,11 @@ ruff format . ### Adding a New Benefit Category -1. Define the field in Athena's TEI Model admin (field name, type, category, defaults) -2. The field automatically appears in Palladium via the API -3. Update notebook analysis if category-specific logic is needed -4. Update export mapping if the report template expects specific structure +1. Define the field in Athena's TEI Report admin (field name, type, category, defaults) +2. The field automatically appears in Palladium via the API — no client changes +3. Update notebook prose if category-specific commentary is needed +4. If the report template exposes a new structure, extend the envelope in + `core/export/report_data.py` --- @@ -341,4 +420,3 @@ ruff format . | **Athena** | Platform API — data persistence, cross-tool reporting | | **Peitho** | Document generation — consumes Palladium's export JSON | | **html2docx** | Converts LLM-generated HTML to native Word documents | - diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/components/__init__.py b/app/components/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/components/charts.py b/app/components/charts.py new file mode 100644 index 0000000..e278fc5 --- /dev/null +++ b/app/components/charts.py @@ -0,0 +1,32 @@ +"""Streamlit-friendly chart wrappers (delegate to core.notebook_helpers.charts).""" + +from __future__ import annotations + +import streamlit as st + +from core.notebook_helpers import charts as core_charts + + +def cashflow(yearly_breakdown, *, initial_cost: float = 0.0) -> None: + fig = core_charts.cashflow_chart(yearly_breakdown, initial_cost=initial_cost) + st.plotly_chart(fig, use_container_width=True) + + +def benefits_bar(items) -> None: + fig = core_charts.benefits_bar(items) + st.plotly_chart(fig, use_container_width=True) + + +def cost_pie(items) -> None: + fig = core_charts.cost_breakdown_pie(items) + st.plotly_chart(fig, use_container_width=True) + + +def scenario_bars(scenarios) -> None: + fig = core_charts.scenario_comparison(scenarios) + st.plotly_chart(fig, use_container_width=True) + + +def waterfall(values) -> None: + fig = core_charts.waterfall(values) + st.plotly_chart(fig, use_container_width=True) diff --git a/app/components/tables.py b/app/components/tables.py new file mode 100644 index 0000000..a687485 --- /dev/null +++ b/app/components/tables.py @@ -0,0 +1,106 @@ +"""Streamlit data-editor wrappers for benefit/cost rows.""" + +from __future__ import annotations + +import pandas as pd +import streamlit as st + + +def _years_for_table(fields: list[dict], analysis_years: int) -> list[int]: + """Years 1..N — taken from analysis_period_years on the report.""" + return list(range(1, max(int(analysis_years or 3), 1) + 1)) + + +def value_editor( + table: str, + fields: list[dict], + values: list[dict], + *, + analysis_years: int, + key: str, +) -> pd.DataFrame: + """ + Render an ``st.data_editor`` for benefit or cost values. + + The editor shows one row per field (filtered to ``table``), with year + columns, an ``initial`` column for costs, a risk_adjustment column, and + a notes column. Returns the edited DataFrame; the caller is responsible + for converting it back to value-row dicts and PUTting to Athena. + """ + fields = [f for f in fields if f.get("table") == table] + fields.sort(key=lambda f: int(f.get("sort_order") or 0)) + + by_key = {v.get("field_key"): v for v in values} + years = _years_for_table(fields, analysis_years) + + rows: list[dict] = [] + for f in fields: + v = by_key.get(f["field_key"], {}) or {} + yv = v.get("year_values") or {} + row = { + "field_key": f["field_key"], + "label": f.get("label", f["field_key"]), + "category": f.get("category", "") or "", + } + if table == "costs": + row["Initial"] = float(v.get("initial") or 0.0) + for y in years: + row[f"Year {y}"] = float(yv.get(str(y)) or 0.0) + row["risk_adj"] = float(v.get("risk_adjustment") or 0.0) + row["notes"] = v.get("notes", "") or "" + rows.append(row) + + df = pd.DataFrame(rows) + + column_config: dict = { + "field_key": st.column_config.TextColumn("Key", disabled=True, width="small"), + "label": st.column_config.TextColumn("Field", disabled=True), + "category": st.column_config.TextColumn("Category", disabled=True, width="small"), + "risk_adj": st.column_config.NumberColumn( + "Risk Adj.", min_value=0.0, max_value=1.0, step=0.05, format="%.2f" + ), + "notes": st.column_config.TextColumn("Notes", width="medium"), + } + if table == "costs": + column_config["Initial"] = st.column_config.NumberColumn( + "Initial", format="$%.0f" + ) + for y in years: + column_config[f"Year {y}"] = st.column_config.NumberColumn( + f"Year {y}", format="$%.0f" + ) + + edited = st.data_editor( + df, + column_config=column_config, + use_container_width=True, + num_rows="fixed", + hide_index=True, + key=key, + ) + return edited + + +def df_to_values(df: pd.DataFrame, table: str, analysis_years: int) -> list[dict]: + """Convert an edited DataFrame back to wire-format value rows.""" + out: list[dict] = [] + years = list(range(1, max(int(analysis_years or 3), 1) + 1)) + for _, row in df.iterrows(): + item: dict = {"field_key": row["field_key"], "table": table} + yv = {} + for y in years: + col = f"Year {y}" + if col in df.columns: + yv[str(y)] = float(row[col] or 0) + if yv: + item["year_values"] = yv + if table == "costs" and "Initial" in df.columns: + item["initial"] = float(row["Initial"] or 0) + ra = row.get("risk_adj") + if ra is not None and not pd.isna(ra): + item["risk_adjustment"] = float(ra) + notes = row.get("notes") + if isinstance(notes, str) and notes.strip(): + item["notes"] = notes.strip() + out.append(item) + return out diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..6313934 --- /dev/null +++ b/app/main.py @@ -0,0 +1,138 @@ +""" +Palladium Streamlit app — TEI data entry, calculation, versioning, export. + +Run from the project root:: + + streamlit run app/main.py + +The app picks a TEI tool by ``public_id`` (or creates one from a Report +template) and exposes Benefits, Costs, Summary, and Versions pages. It is +study-agnostic — the field set is loaded dynamically from Athena based on +the linked Report template. +""" + +from __future__ import annotations + +import sys +from pathlib import Path + +# Allow `streamlit run app/main.py` from project root without `pip install -e .` +_ROOT = Path(__file__).resolve().parent.parent +if str(_ROOT) not in sys.path: + sys.path.insert(0, str(_ROOT)) + +import streamlit as st + +from core.tei_client import AthenaAPIError, TEIClient + +st.set_page_config( + page_title="Palladium — TEI Calculator", + page_icon="🛡️", + layout="wide", +) + + +@st.cache_resource(show_spinner=False) +def get_client() -> TEIClient: + return TEIClient() + + +def _safe_call(fn, *args, **kwargs): + """Run an API call, surfacing errors as Streamlit messages.""" + try: + return fn(*args, **kwargs) + except AthenaAPIError as e: + st.error(f"Athena API error {e.status_code}: {e.detail}") + except ValueError as e: + st.error(str(e)) + return None + + +def sidebar_tool_picker(client: TEIClient) -> dict | None: + """Sidebar: pick an existing TEI tool or create one from a report template.""" + st.sidebar.title("🛡️ Palladium") + st.sidebar.caption("TEI Calculator") + + tools = _safe_call(client.list_tools) or [] + if tools: + labels = { + f"{t.get('name', '(unnamed)')} — {t.get('id', '')[:8]}…": t for t in tools + } + choice = st.sidebar.selectbox("TEI Tool", list(labels.keys())) + tool = labels[choice] + else: + st.sidebar.info("No TEI tools yet. Create one below.") + tool = None + + with st.sidebar.expander("Create new tool"): + reports = _safe_call(client.list_reports) or [] + if not reports: + st.write("No report templates available.") + else: + report_labels = {f"{r['name']} ({r['vendor']} {r['version']})": r for r in reports} + r_choice = st.selectbox("Report template", list(report_labels.keys())) + new_name = st.text_input("Tool name (optional)", "") + proposal_id = st.number_input( + "Proposal ID (optional)", min_value=0, value=0, step=1 + ) + if st.button("Create"): + report = report_labels[r_choice] + created = _safe_call( + client.create_tool, + report_public_id=report["id"], + proposal=int(proposal_id) or None, + name=new_name or None, + ) + if created: + st.success(f"Created tool {created.get('id')}") + st.rerun() + + if tool: + st.sidebar.divider() + st.sidebar.markdown(f"**Public ID**: `{tool.get('id')}`") + st.sidebar.markdown(f"**Status**: {tool.get('status', '?')}") + st.sidebar.markdown(f"**Version**: {tool.get('current_version', 0)}") + if st.sidebar.button("🔄 Recalculate"): + _safe_call(client.calculate, tool["id"]) + st.toast("Recalculated.", icon="✅") + st.cache_data.clear() + return tool + + +def main() -> None: + st.title("Palladium — TEI Calculator") + try: + client = get_client() + except ValueError as e: + st.error(str(e)) + st.info("Set ATHENA_BASE_URL and ATHENA_API_KEY in your `.env` file.") + st.stop() + return + + tool = sidebar_tool_picker(client) + + if tool is None: + st.info("Pick or create a TEI tool from the sidebar to begin.") + return + + # Tab navigation — matches `app/pages/*` modules but kept as tabs so all + # views share the chosen tool/state without re-querying. + tabs = st.tabs(["📊 Summary", "💰 Benefits", "💸 Costs", "🕒 Versions"]) + + from app.pages import benefits as benefits_page + from app.pages import costs as costs_page + from app.pages import summary as summary_page + from app.pages import versions as versions_page + + with tabs[0]: + summary_page.render(client, tool) + with tabs[1]: + benefits_page.render(client, tool) + with tabs[2]: + costs_page.render(client, tool) + with tabs[3]: + versions_page.render(client, tool) + + +if __name__ == "__main__": + main() diff --git a/app/pages/__init__.py b/app/pages/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/pages/_helpers.py b/app/pages/_helpers.py new file mode 100644 index 0000000..619de63 --- /dev/null +++ b/app/pages/_helpers.py @@ -0,0 +1,28 @@ +"""Common helpers shared by the page modules.""" + +from __future__ import annotations + +import streamlit as st + +from core.tei_client import AthenaAPIError, TEIClient + + +def report_meta(client: TEIClient, tool: dict) -> dict: + """Fetch the linked report (handles both nested-object and id-only forms).""" + report_obj = tool.get("report") + if isinstance(report_obj, dict): + return report_obj + if isinstance(report_obj, str): + try: + return client.get_report(report_obj) + except AthenaAPIError as e: + st.error(f"Failed to load report template: {e}") + return {} + + +def safe(fn, *args, **kwargs): + try: + return fn(*args, **kwargs) + except AthenaAPIError as e: + st.error(f"Athena API error {e.status_code}: {e.detail}") + return None diff --git a/app/pages/benefits.py b/app/pages/benefits.py new file mode 100644 index 0000000..255d0ac --- /dev/null +++ b/app/pages/benefits.py @@ -0,0 +1,46 @@ +"""Benefits data-entry tab.""" + +from __future__ import annotations + +import streamlit as st + +from app.components.tables import df_to_values, value_editor +from app.pages._helpers import report_meta, safe +from core.tei_client import TEIClient + + +def render(client: TEIClient, tool: dict) -> None: + st.header("💰 Benefits") + public_id = tool["id"] + report = report_meta(client, tool) + analysis_years = int(report.get("analysis_period_years") or 3) + + fields = safe(client.list_fields, report.get("id"), "benefits") or [] + values = [v for v in safe(client.get_values, public_id) or [] if v.get("table") == "benefits"] + + if not fields: + st.info("This report template has no benefit fields defined.") + return + + edited = value_editor( + "benefits", + fields, + values, + analysis_years=analysis_years, + key=f"benefits_editor_{public_id}", + ) + + col1, col2 = st.columns([1, 4]) + with col1: + if st.button("💾 Save benefits", use_container_width=True): + payload = df_to_values(edited, "benefits", analysis_years) + result = safe(client.update_values, public_id, payload) + if result is not None: + st.success(f"Saved {len(payload)} benefit values.") + st.cache_data.clear() + with col2: + st.caption( + "Values are saved as nominal annual amounts. Risk adjustments are " + "applied at calculate time. Use the Recalculate button in the " + "sidebar after saving to refresh the summary." + ) diff --git a/app/pages/costs.py b/app/pages/costs.py new file mode 100644 index 0000000..24e8185 --- /dev/null +++ b/app/pages/costs.py @@ -0,0 +1,46 @@ +"""Costs data-entry tab.""" + +from __future__ import annotations + +import streamlit as st + +from app.components.tables import df_to_values, value_editor +from app.pages._helpers import report_meta, safe +from core.tei_client import TEIClient + + +def render(client: TEIClient, tool: dict) -> None: + st.header("💸 Costs") + public_id = tool["id"] + report = report_meta(client, tool) + analysis_years = int(report.get("analysis_period_years") or 3) + + fields = safe(client.list_fields, report.get("id"), "costs") or [] + values = [v for v in safe(client.get_values, public_id) or [] if v.get("table") == "costs"] + + if not fields: + st.info("This report template has no cost fields defined.") + return + + edited = value_editor( + "costs", + fields, + values, + analysis_years=analysis_years, + key=f"costs_editor_{public_id}", + ) + + col1, col2 = st.columns([1, 4]) + with col1: + if st.button("💾 Save costs", use_container_width=True): + payload = df_to_values(edited, "costs", analysis_years) + result = safe(client.update_values, public_id, payload) + if result is not None: + st.success(f"Saved {len(payload)} cost values.") + st.cache_data.clear() + with col2: + st.caption( + "The Initial column is undiscounted year-0 spend. Year columns " + "are end-of-year cashflows. Costs are risk-adjusted upward " + "(higher risk → higher cost)." + ) diff --git a/app/pages/summary.py b/app/pages/summary.py new file mode 100644 index 0000000..4e7e767 --- /dev/null +++ b/app/pages/summary.py @@ -0,0 +1,104 @@ +"""Financial summary dashboard tab.""" + +from __future__ import annotations + +import streamlit as st + +from app.components import charts +from app.pages._helpers import report_meta, safe +from core.export import build_report_data +from core.tei_client import AthenaAPIError, TEIClient + + +def render(client: TEIClient, tool: dict) -> None: + st.header("📊 Financial Summary") + public_id = tool["id"] + report = report_meta(client, tool) + + try: + summary = client.get_summary(public_id) + except AthenaAPIError as e: + if e.status_code == 404: + st.info( + "No summary yet — click **Recalculate** in the sidebar after " + "filling in benefits and costs." + ) + return + st.error(f"Athena API error: {e.detail}") + return + + npv = float(summary.get("npv") or 0) + roi = float(summary.get("roi") or summary.get("roi_pct") or 0) + payback = summary.get("payback_months") + bpv = float(summary.get("total_benefits_pv") or 0) + cpv = float(summary.get("total_costs_pv") or 0) + + cols = st.columns(5) + cols[0].metric("NPV", f"${npv/1_000_000:,.1f}M") + cols[1].metric("ROI", f"{roi:,.0f}%") + cols[2].metric( + "Payback", + f"{float(payback):.1f} months" if payback is not None else "N/A", + ) + cols[3].metric("Benefits PV", f"${bpv/1_000_000:,.1f}M") + cols[4].metric("Costs PV", f"${cpv/1_000_000:,.1f}M") + + st.divider() + + yb = summary.get("yearly_breakdown") or [] + initial = float(summary.get("initial_costs") or 0) + if yb: + charts.cashflow(yb, initial_cost=initial) + with st.expander("Cash flow table"): + st.dataframe(yb, use_container_width=True, hide_index=True) + else: + st.caption("No yearly breakdown in this summary.") + + # Scenario comparison — computed locally from current values + with st.expander("Scenario analysis (conservative / moderate / aggressive)"): + envelope = safe( + build_report_data, + client, + public_id, + include_scenarios=True, + study_slug=report.get("name", ""), + ) + if envelope and envelope.get("scenarios"): + charts.scenario_bars(envelope["scenarios"]) + rows = [ + { + "Scenario": k, + "Benefits PV": float(v.get("total_benefits_pv") or 0), + "Costs PV": float(v.get("total_costs_pv") or 0), + "NPV": float(v.get("npv") or 0), + "ROI %": float(v.get("roi_pct") or 0), + "Payback (months)": ( + round(float(v.get("payback_months") or 0), 1) + if v.get("payback_months") is not None + else None + ), + } + for k, v in envelope["scenarios"].items() + ] + st.dataframe(rows, use_container_width=True, hide_index=True) + + # Export button + st.divider() + if st.button("📦 Build export envelope (JSON)"): + envelope = safe( + build_report_data, + client, + public_id, + include_scenarios=True, + study_slug=report.get("name", ""), + ) + if envelope: + import json + + data = json.dumps(envelope, indent=2, default=str) + st.download_button( + "Download export.json", + data=data, + file_name=f"{public_id}_export.json", + mime="application/json", + ) diff --git a/app/pages/versions.py b/app/pages/versions.py new file mode 100644 index 0000000..f05da77 --- /dev/null +++ b/app/pages/versions.py @@ -0,0 +1,117 @@ +"""Version history tab — list, diff, save, restore.""" + +from __future__ import annotations + +import streamlit as st + +from app.pages._helpers import safe +from core.tei_client import TEIClient + + +def _flatten_values(values: list[dict]) -> dict[str, dict]: + """Index a values list by field_key for easy diffing.""" + return {v.get("field_key", ""): v for v in values} + + +def _diff_rows(a: dict[str, dict], b: dict[str, dict]) -> list[dict]: + """Return one row per field with side-by-side year values.""" + keys = sorted(set(a.keys()) | set(b.keys())) + rows: list[dict] = [] + for k in keys: + av = a.get(k, {}) or {} + bv = b.get(k, {}) or {} + ay = av.get("year_values") or {} + by = bv.get("year_values") or {} + years = sorted(set(ay.keys()) | set(by.keys()), key=lambda x: int(x)) + for y in years: + a_val = float(ay.get(y) or 0) + b_val = float(by.get(y) or 0) + if abs(a_val - b_val) < 1e-9: + continue + rows.append( + { + "field_key": k, + "year": y, + "left": a_val, + "right": b_val, + "delta": b_val - a_val, + } + ) + return rows + + +def render(client: TEIClient, tool: dict) -> None: + st.header("🕒 Versions") + public_id = tool["id"] + + versions = safe(client.list_versions, public_id) or [] + versions = sorted( + versions, key=lambda v: int(v.get("version_number") or 0), reverse=True + ) + + # Save new version + with st.expander("➕ Save current state as a new version", expanded=not versions): + note = st.text_area( + "Version note", + placeholder=( + "What changed? E.g. 'CFO confirmed 1.8M contacts/month; " + "raised legacy license cost from $160 to $180/agent.'" + ), + ) + if st.button("💾 Save version", disabled=not note.strip()): + result = safe(client.save_version, public_id, note.strip()) + if result: + st.success( + f"Saved version {result.get('version_number', '?')}." + ) + st.rerun() + + if not versions: + st.info("No versions saved yet.") + return + + # Listing + st.subheader("History") + rows = [] + for v in versions: + snap = v.get("summary_snapshot") or v.get("summary") or {} + rows.append( + { + "Version": v.get("version_number"), + "Date": v.get("created_at") or v.get("date"), + "NPV": float(snap.get("npv") or 0), + "ROI %": float(snap.get("roi") or snap.get("roi_pct") or 0), + "Note": v.get("note", ""), + } + ) + st.dataframe(rows, use_container_width=True, hide_index=True) + + # Compare two versions + st.subheader("Compare") + if len(versions) < 2: + st.caption("Save two or more versions to compare.") + return + labels = {f"v{v['version_number']} — {v.get('note', '')[:40]}": v for v in versions} + keys = list(labels.keys()) + c1, c2 = st.columns(2) + with c1: + left_label = st.selectbox("Left (older)", keys, index=min(1, len(keys) - 1)) + with c2: + right_label = st.selectbox("Right (newer)", keys, index=0) + + if left_label == right_label: + st.caption("Pick two different versions to see a diff.") + return + + left = safe(client.get_version, public_id, labels[left_label]["version_number"]) + right = safe(client.get_version, public_id, labels[right_label]["version_number"]) + if not (left and right): + return + + a_values = left.get("values_snapshot") or left.get("values") or [] + b_values = right.get("values_snapshot") or right.get("values") or [] + diff = _diff_rows(_flatten_values(a_values), _flatten_values(b_values)) + if not diff: + st.success("No value differences between these versions.") + else: + st.dataframe(diff, use_container_width=True, hide_index=True) diff --git a/core/__init__.py b/core/__init__.py new file mode 100644 index 0000000..e03e2ce --- /dev/null +++ b/core/__init__.py @@ -0,0 +1,3 @@ +"""Palladium core — shared TEI client, calculations, export, helpers.""" + +__version__ = "0.1.0" diff --git a/core/calculations/__init__.py b/core/calculations/__init__.py new file mode 100644 index 0000000..93678ee --- /dev/null +++ b/core/calculations/__init__.py @@ -0,0 +1,31 @@ +"""Pure-python TEI financial math.""" + +from core.calculations.npv import ( + discount_factor, + npv, + present_value, + present_value_series, +) +from core.calculations.payback import payback_months, payback_years +from core.calculations.roi import roi, roi_percentage +from core.calculations.scenarios import ( + SCENARIOS, + apply_scenario, + risk_adjust_benefit, + risk_adjust_cost, +) + +__all__ = [ + "SCENARIOS", + "apply_scenario", + "discount_factor", + "npv", + "payback_months", + "payback_years", + "present_value", + "present_value_series", + "risk_adjust_benefit", + "risk_adjust_cost", + "roi", + "roi_percentage", +] diff --git a/core/calculations/npv.py b/core/calculations/npv.py new file mode 100644 index 0000000..2b4fe1a --- /dev/null +++ b/core/calculations/npv.py @@ -0,0 +1,78 @@ +""" +Net Present Value and discounting. + +Convention (matches the Forrester TEI methodology used in the Amazon Connect +study): + +* The *Initial* investment (column "Initial" in TEI tables) is **not** + discounted — it occurs at time zero. +* Year-N cash flows are discounted at the end of the year: + ``PV = CF_n / (1 + r) ** n`` for ``n = 1, 2, …``. + +That matches the PDF note: *"The initial investment column contains costs +incurred at 'time 0' or at the beginning of Year 1 that are not discounted. +All other cash flows are discounted using the discount rate at the end of +the year."* +""" + +from __future__ import annotations + +from collections.abc import Iterable + + +def discount_factor(year: int, discount_rate: float) -> float: + """Return ``1 / (1 + r) ** year``. Year 0 → 1.0 (no discount).""" + if year < 0: + raise ValueError("year must be >= 0") + return 1.0 / ((1.0 + discount_rate) ** year) + + +def present_value(amount: float, year: int, discount_rate: float) -> float: + """Discount ``amount`` from end-of-year ``year`` to present.""" + return amount * discount_factor(year, discount_rate) + + +def present_value_series( + cashflows: Iterable[float], + discount_rate: float, + start_year: int = 1, +) -> float: + """ + Sum the present value of a stream of year-end cashflows. + + Args: + cashflows: iterable of year-N amounts (Y1, Y2, …). + discount_rate: e.g. 0.10 for 10%. + start_year: year of the first element. Default 1 (skip year-0). + """ + total = 0.0 + for offset, cf in enumerate(cashflows): + total += present_value(cf, start_year + offset, discount_rate) + return total + + +def npv( + cashflows: Iterable[float], + discount_rate: float, + initial: float = 0.0, +) -> float: + """ + Net Present Value. + + Args: + cashflows: year-end cashflows for Year 1, Year 2, … + discount_rate: e.g. 0.10 + initial: undiscounted year-0 cashflow (negative for a cost, + positive for a one-off benefit). + + Returns: + ``initial + Σ CF_n / (1 + r)^n`` for ``n = 1..len(cashflows)``. + + Example:: + + >>> # Amazon Connect total benefits PV ≈ $101.7M + >>> benefits = [27_279_019, 40_333_658, 57_983_794] + >>> round(npv(benefits, 0.10) / 1_000_000, 1) + 101.7 + """ + return initial + present_value_series(cashflows, discount_rate, start_year=1) diff --git a/core/calculations/payback.py b/core/calculations/payback.py new file mode 100644 index 0000000..087d59c --- /dev/null +++ b/core/calculations/payback.py @@ -0,0 +1,63 @@ +""" +Payback period. + +Linear interpolation within the crossing year: returns the moment when the +running net cashflow first turns non-negative. + +Inputs are *risk-adjusted, undiscounted* cashflows by convention (TEI shows +"<6 months" payback for the Amazon Connect composite using the risk-adjusted +nominal cashflows from the Cash Flow Analysis table). +""" + +from __future__ import annotations + +from collections.abc import Iterable + + +def payback_years( + initial_cost: float, + yearly_net_benefits: Iterable[float], +) -> float | None: + """ + Years until cumulative net benefits cover the initial cost. + + Args: + initial_cost: positive number — undiscounted year-0 outlay. + yearly_net_benefits: sequence of (benefits − costs) per year, Y1+. + + Returns: + Float number of years, or ``None`` if payback is never reached + within the supplied horizon. + + Example:: + + >>> # Amazon Connect: initial cost $1.196M, Y1 net $19.998M + >>> # → ~0.06 years ≈ 0.7 months — well under 6 months. + >>> round(payback_years(1_196_250, [19_997_953, 31_562_489, 47_443_905]), 3) + 0.06 + """ + remaining = float(initial_cost) + if remaining <= 0: + return 0.0 + cumulative_year = 0 + for cf in yearly_net_benefits: + cumulative_year += 1 + cf = float(cf) + if cf <= 0: + remaining += -cf # net loss this year increases the gap + continue + if cf >= remaining: + # Crossing happens partway through this year. + fraction = remaining / cf + return (cumulative_year - 1) + fraction + remaining -= cf + return None + + +def payback_months( + initial_cost: float, + yearly_net_benefits: Iterable[float], +) -> float | None: + """Same as :func:`payback_years`, expressed in months.""" + yrs = payback_years(initial_cost, yearly_net_benefits) + return yrs * 12.0 if yrs is not None else None diff --git a/core/calculations/roi.py b/core/calculations/roi.py new file mode 100644 index 0000000..9cd4b02 --- /dev/null +++ b/core/calculations/roi.py @@ -0,0 +1,27 @@ +"""Return on Investment.""" + +from __future__ import annotations + + +def roi(benefits_pv: float, costs_pv: float) -> float: + """ + Return on Investment as a fraction. + + ``ROI = (Benefits − Costs) / Costs`` + + Costs here are expressed as a positive present-value amount (the absolute + cost). Returns ``0.0`` when costs are zero (rather than dividing by zero). + + Example:: + + >>> round(roi(101_696_791, 22_983_076), 2) # Amazon Connect: 342% + 3.42 + """ + if costs_pv <= 0: + return 0.0 + return (benefits_pv - costs_pv) / costs_pv + + +def roi_percentage(benefits_pv: float, costs_pv: float) -> float: + """ROI as a percentage (e.g. 342.0 for 342%).""" + return roi(benefits_pv, costs_pv) * 100.0 diff --git a/core/calculations/scenarios.py b/core/calculations/scenarios.py new file mode 100644 index 0000000..a6ae49a --- /dev/null +++ b/core/calculations/scenarios.py @@ -0,0 +1,128 @@ +""" +Scenario modelling and risk adjustment. + +Forrester TEI applies a *downward* risk adjustment to benefits (subtract +``risk_factor × benefit``) and an *upward* adjustment to costs (add +``risk_factor × cost``). Scenarios scale both adoption (cashflow magnitude) +and uncertainty (risk factor). + +Default scenario multipliers are sensible starting points. Override per +study by editing the study's ``config.py`` and passing a custom dict to +:func:`apply_scenario`. +""" + +from __future__ import annotations + +from collections.abc import Iterable +from copy import deepcopy +from typing import Any + +#: Default scenario multipliers used by Palladium notebooks. +#: +#: * ``adoption`` scales nominal annual values up or down. +#: * ``risk_delta`` is *added* to the benefit's risk_adjustment factor and +#: *subtracted* from the cost's risk_adjustment factor (conservative +#: = more uncertainty on benefits, less padding on costs). +SCENARIOS: dict[str, dict[str, float]] = { + "conservative": {"adoption": 0.80, "risk_delta": 0.10}, + "moderate": {"adoption": 1.00, "risk_delta": 0.00}, + "aggressive": {"adoption": 1.15, "risk_delta": -0.05}, +} + + +def risk_adjust_benefit(amount: float, risk_factor: float) -> float: + """ + Apply a downward risk adjustment to a benefit. + + ``adjusted = amount × (1 − risk_factor)``. + + ``risk_factor`` is clamped to ``[0, 1]``. + """ + rf = max(0.0, min(1.0, float(risk_factor))) + return amount * (1.0 - rf) + + +def risk_adjust_cost(amount: float, risk_factor: float) -> float: + """ + Apply an upward risk adjustment to a cost. + + ``adjusted = amount × (1 + risk_factor)``. + + ``risk_factor`` is clamped to ``[0, 1]``. + """ + rf = max(0.0, min(1.0, float(risk_factor))) + return amount * (1.0 + rf) + + +def _scale_yearly(values: Iterable[float], factor: float) -> list[float]: + return [float(v) * factor for v in values] + + +def apply_scenario( + items: list[dict], + scenario: str = "moderate", + *, + multipliers: dict[str, dict[str, float]] | None = None, + table: str | None = None, +) -> list[dict]: + """ + Return a deep-copied list of value-rows with scenario adjustments applied. + + Each item is expected to have: + - ``table`` (``'benefits'`` or ``'costs'``) — required for sign of + risk_delta. If absent, defaults to ``benefits`` unless ``table=`` + is passed explicitly. + - ``year_values`` (dict of year-string → float) **or** a scalar + ``value``. + - ``risk_adjustment`` (optional float). + - ``initial`` (optional, costs only) — scaled by adoption. + + Args: + items: rows shaped like the ``_normalize_value`` output of + :class:`core.tei_client.TEIClient`. + scenario: key into ``multipliers`` (default ``SCENARIOS``). + multipliers: override map. Same shape as ``SCENARIOS``. + table: force a table when items lack one. + + Returns: + A new list — original items are not mutated. + """ + cfg = (multipliers or SCENARIOS).get(scenario) + if cfg is None: + raise KeyError(f"Unknown scenario: {scenario!r}") + adoption = float(cfg.get("adoption", 1.0)) + risk_delta = float(cfg.get("risk_delta", 0.0)) + + out: list[dict] = [] + for raw in items: + item: dict[str, Any] = deepcopy(raw) + item_table = item.get("table") or table or "benefits" + item["table"] = item_table + + # Adoption scaling + if "year_values" in item and isinstance(item["year_values"], dict): + item["year_values"] = { + k: float(v) * adoption for k, v in item["year_values"].items() + } + if "value" in item and item["value"] is not None: + try: + item["value"] = float(item["value"]) * adoption + except (TypeError, ValueError): + pass + if "initial" in item and item["initial"] is not None: + try: + item["initial"] = float(item["initial"]) * adoption + except (TypeError, ValueError): + pass + + # Risk adjustment delta + ra = item.get("risk_adjustment") + if ra is None: + ra = 0.0 + if item_table == "benefits": + new_ra = float(ra) + risk_delta + else: # costs: adverse scenario should *raise* costs less, so subtract + new_ra = float(ra) - risk_delta + item["risk_adjustment"] = max(0.0, min(1.0, new_ra)) + out.append(item) + return out diff --git a/core/cli/__init__.py b/core/cli/__init__.py new file mode 100644 index 0000000..dc0266e --- /dev/null +++ b/core/cli/__init__.py @@ -0,0 +1 @@ +"""Palladium CLI package — invoked via ``python -m palladium``.""" diff --git a/core/cli/main.py b/core/cli/main.py new file mode 100644 index 0000000..6973643 --- /dev/null +++ b/core/cli/main.py @@ -0,0 +1,229 @@ +""" +Palladium CLI implementation. + +Subcommands:: + + palladium test # Verify Athena connectivity + palladium list # List TEI tool instances + palladium reports # List TEI report templates + palladium summary # Print a tool's financial summary + palladium calculate # Trigger /calculate + palladium export -o file # Save export envelope as JSON + +The CLI is invoked via ``python -m palladium`` (root shim) which calls +:func:`main` here. +""" + +from __future__ import annotations + +import argparse +import json +import logging +import sys +from collections.abc import Sequence + +from core.export import build_report_data, write_report_data +from core.tei_client import AthenaAPIError, TEIClient + + +def _configure_logging(verbosity: int) -> None: + level = logging.WARNING + if verbosity == 1: + level = logging.INFO + elif verbosity >= 2: + level = logging.DEBUG + logging.basicConfig( + level=level, format="%(asctime)s %(levelname)s %(name)s: %(message)s" + ) + + +def _build_parser() -> argparse.ArgumentParser: + p = argparse.ArgumentParser( + prog="palladium", + description="Palladium — TEI Calculator CLI for Athena.", + ) + p.add_argument("-v", "--verbose", action="count", default=0) + sub = p.add_subparsers(dest="command", required=True) + + sub.add_parser("test", help="Verify Athena API connectivity") + sub.add_parser("list", help="List TEI tool instances") + sub.add_parser("reports", help="List TEI report templates") + + s_summary = sub.add_parser("summary", help="Print a tool's financial summary") + s_summary.add_argument("public_id") + + s_calc = sub.add_parser("calculate", help="Trigger calculation for a tool") + s_calc.add_argument("public_id") + + s_export = sub.add_parser("export", help="Export a tool's report data as JSON") + s_export.add_argument("public_id") + s_export.add_argument( + "-o", + "--output", + default="-", + help="Output path (default: stdout)", + ) + s_export.add_argument( + "--no-scenarios", + action="store_true", + help="Skip computing conservative/moderate/aggressive scenarios.", + ) + s_export.add_argument( + "--study-slug", + default=None, + help="Optional study identifier to embed in metadata.", + ) + + return p + + +def _print_table(rows: list[dict], columns: list[tuple[str, str]]) -> None: + """Tiny pure-stdlib pretty-printer.""" + widths = [len(label) for _, label in columns] + formatted: list[list[str]] = [] + for r in rows: + rec = [str(r.get(key, "") or "") for key, _ in columns] + formatted.append(rec) + for i, val in enumerate(rec): + if len(val) > widths[i]: + widths[i] = len(val) + fmt = " ".join(f"{{:<{w}}}" for w in widths) + print(fmt.format(*[label for _, label in columns])) + print(fmt.format(*["-" * w for w in widths])) + for rec in formatted: + print(fmt.format(*rec)) + + +def cmd_test(client: TEIClient, args) -> int: + result = client.test_connection() + print(json.dumps(result, indent=2)) + return 0 if result.get("status") == "ok" else 1 + + +def cmd_list(client: TEIClient, args) -> int: + tools = client.list_tools() + if not tools: + print("(no TEI tools)") + return 0 + rows = [] + for t in tools: + report = t.get("report") + if isinstance(report, dict): + report_name = report.get("name", "") + else: + report_name = report or "" + rows.append( + { + "id": t.get("id", ""), + "name": t.get("name", ""), + "report": report_name, + "status": t.get("status", ""), + "version": t.get("current_version", ""), + "modified": t.get("modified_date", ""), + } + ) + _print_table( + rows, + [ + ("id", "PUBLIC_ID"), + ("name", "NAME"), + ("report", "REPORT"), + ("status", "STATUS"), + ("version", "VER"), + ("modified", "MODIFIED"), + ], + ) + return 0 + + +def cmd_reports(client: TEIClient, args) -> int: + reports = client.list_reports() + if not reports: + print("(no reports)") + return 0 + rows = [ + { + "id": r.get("id", ""), + "name": r.get("name", ""), + "vendor": r.get("vendor", ""), + "version": r.get("version", ""), + "fields": r.get("field_count", 0), + "instances": r.get("instance_count", 0), + "status": r.get("status", ""), + } + for r in reports + ] + _print_table( + rows, + [ + ("id", "PUBLIC_ID"), + ("name", "NAME"), + ("vendor", "VENDOR"), + ("version", "VER"), + ("fields", "FIELDS"), + ("instances", "TOOLS"), + ("status", "STATUS"), + ], + ) + return 0 + + +def cmd_summary(client: TEIClient, args) -> int: + client.print_summary(args.public_id) + return 0 + + +def cmd_calculate(client: TEIClient, args) -> int: + client.calculate(args.public_id) + print(f"Recalculated {args.public_id}.") + client.print_summary(args.public_id) + return 0 + + +def cmd_export(client: TEIClient, args) -> int: + envelope = build_report_data( + client, + args.public_id, + include_scenarios=not args.no_scenarios, + study_slug=args.study_slug, + ) + if args.output in ("-", ""): + json.dump(envelope, sys.stdout, indent=2, default=str) + sys.stdout.write("\n") + else: + path = write_report_data(envelope, args.output) + print(f"Wrote {path}") + return 0 + + +COMMANDS = { + "test": cmd_test, + "list": cmd_list, + "reports": cmd_reports, + "summary": cmd_summary, + "calculate": cmd_calculate, + "export": cmd_export, +} + + +def main(argv: Sequence[str] | None = None) -> int: + parser = _build_parser() + args = parser.parse_args(argv) + _configure_logging(args.verbose) + + try: + client = TEIClient() + except ValueError as e: + print(f"error: {e}", file=sys.stderr) + return 2 + + handler = COMMANDS[args.command] + try: + return handler(client, args) + except AthenaAPIError as e: + print(f"Athena API error {e.status_code}: {e.detail}", file=sys.stderr) + return 1 + + +if __name__ == "__main__": # pragma: no cover + raise SystemExit(main()) diff --git a/core/export/__init__.py b/core/export/__init__.py new file mode 100644 index 0000000..041b097 --- /dev/null +++ b/core/export/__init__.py @@ -0,0 +1,5 @@ +"""Export utilities — build the LLM-ready JSON envelope.""" + +from core.export.report_data import build_report_data, write_report_data + +__all__ = ["build_report_data", "write_report_data"] diff --git a/core/export/report_data.py b/core/export/report_data.py new file mode 100644 index 0000000..a27a836 --- /dev/null +++ b/core/export/report_data.py @@ -0,0 +1,224 @@ +""" +Build the structured JSON consumed by the report pipeline. + +The Athena ``GET /tools/{public_id}/export/`` endpoint already returns most +of what we need; this module: + +1. Calls the export endpoint. +2. Optionally augments it with locally computed scenario analysis + (conservative / moderate / aggressive). +3. Stamps Palladium metadata (export timestamp, study slug, generator). +4. Serializes to a stable JSON file that html2docx / Peitho can consume. +""" + +from __future__ import annotations + +import json +from datetime import UTC, datetime +from pathlib import Path +from typing import Any + +from core import __version__ +from core.calculations import ( + apply_scenario, + npv, + payback_months, + risk_adjust_benefit, + risk_adjust_cost, + roi_percentage, +) + + +def _split_by_table(values: list[dict]) -> tuple[list[dict], list[dict]]: + benefits = [v for v in values if v.get("table") == "benefits"] + costs = [v for v in values if v.get("table") == "costs"] + return benefits, costs + + +def _yearly_totals(items: list[dict], analysis_years: int) -> list[float]: + totals = [0.0] * analysis_years + for it in items: + yv = it.get("year_values") or {} + for k, v in yv.items(): + try: + year = int(k) + except (TypeError, ValueError): + continue + if 1 <= year <= analysis_years: + totals[year - 1] += float(v or 0) + return totals + + +def _initial_total(items: list[dict]) -> float: + return sum(float(it.get("initial") or 0) for it in items) + + +def _risk_adjusted(items: list[dict], for_table: str) -> list[dict]: + out: list[dict] = [] + for it in items: + rf = float(it.get("risk_adjustment") or 0.0) + adj_year_values: dict[str, float] = {} + for k, v in (it.get("year_values") or {}).items(): + v = float(v or 0) + adj_year_values[str(k)] = ( + risk_adjust_benefit(v, rf) + if for_table == "benefits" + else risk_adjust_cost(v, rf) + ) + adj = dict(it) + adj["year_values"] = adj_year_values + if "initial" in adj and adj["initial"] is not None: + adj["initial"] = ( + risk_adjust_cost(float(adj["initial"]), rf) + if for_table == "costs" + else float(adj["initial"]) + ) + out.append(adj) + return out + + +def _compute_summary( + benefits: list[dict], + costs: list[dict], + discount_rate: float, + analysis_years: int, +) -> dict[str, Any]: + benefits_ra = _risk_adjusted(benefits, "benefits") + costs_ra = _risk_adjusted(costs, "costs") + + benefits_yr = _yearly_totals(benefits_ra, analysis_years) + costs_yr = _yearly_totals(costs_ra, analysis_years) + initial_costs = _initial_total(costs_ra) + + benefits_pv = npv(benefits_yr, discount_rate) + costs_pv = npv(costs_yr, discount_rate, initial=initial_costs) + nominal_benefits = sum(benefits_yr) + nominal_costs = sum(costs_yr) + initial_costs + + net_yearly = [b - c for b, c in zip(benefits_yr, costs_yr, strict=False)] + pb = payback_months(initial_costs, net_yearly) + + return { + "discount_rate": discount_rate, + "analysis_years": analysis_years, + "total_benefits_nominal": nominal_benefits, + "total_benefits_pv": benefits_pv, + "total_costs_nominal": nominal_costs, + "total_costs_pv": costs_pv, + "npv": benefits_pv - costs_pv, + "roi_pct": roi_percentage(benefits_pv, costs_pv), + "payback_months": pb, + "yearly_breakdown": [ + { + "year": idx + 1, + "benefits": benefits_yr[idx], + "costs": costs_yr[idx], + "net": net_yearly[idx], + "cumulative_net": sum(net_yearly[: idx + 1]) - initial_costs, + } + for idx in range(analysis_years) + ], + "initial_costs": initial_costs, + } + + +def build_report_data( + client, + public_id: str, + *, + include_scenarios: bool = True, + study_slug: str | None = None, +) -> dict[str, Any]: + """ + Build the full export envelope for a TEI tool. + + Args: + client: a :class:`core.tei_client.TEIClient` instance. + public_id: the TEI tool's public_id. + include_scenarios: if True, locally compute conservative / moderate / + aggressive summaries and attach them under ``scenarios``. + study_slug: optional human-friendly study identifier (e.g. + ``"202602_AmazonConnect"``) — written into ``metadata``. + + Returns: + A dict with keys:: + + { + "metadata": {...}, # client / opportunity / study / generator + "report": {...}, # report template echo + "values": {benefits, costs}, + "summary": {...}, # locally recomputed (mirrors Athena) + "athena_export": {...}, # raw payload from Athena (if available) + "scenarios": {...} # optional + } + """ + # Pull everything we need + bundle = client.get_tool_with_data(public_id) + tool = bundle["tool"] + fields = bundle["fields"] + values = bundle["values"] + + report_obj = tool.get("report") + if isinstance(report_obj, str): + report = client.get_report(report_obj) + elif isinstance(report_obj, dict): + report = report_obj + else: + report = {} + + discount_rate = float(report.get("discount_rate") or 0.10) + analysis_years = int(report.get("analysis_period_years") or 3) + + benefits, costs = _split_by_table(values) + summary = _compute_summary(benefits, costs, discount_rate, analysis_years) + + try: + athena_export = client.export(public_id) + except Exception as e: # pragma: no cover – best effort + athena_export = {"error": str(e)} + + envelope: dict[str, Any] = { + "metadata": { + "study_slug": study_slug or "", + "tool_public_id": public_id, + "tool_name": tool.get("name", ""), + "report_name": report.get("name", ""), + "report_vendor": report.get("vendor", ""), + "report_version": report.get("version", ""), + "report_public_id": report.get("id", ""), + "proposal": tool.get("proposal") or tool.get("opportunity"), + "engagement": tool.get("engagement"), + "generated_at": datetime.now(UTC).isoformat(), + "generator": f"palladium core {__version__}", + }, + "report": report, + "fields": fields, + "values": {"benefits": benefits, "costs": costs}, + "summary": summary, + "athena_export": athena_export, + } + + if include_scenarios: + scenario_results: dict[str, Any] = {} + for scenario_name in ("conservative", "moderate", "aggressive"): + sb = apply_scenario(benefits, scenario_name, table="benefits") + sc = apply_scenario(costs, scenario_name, table="costs") + scenario_results[scenario_name] = _compute_summary( + sb, sc, discount_rate, analysis_years + ) + envelope["scenarios"] = scenario_results + + return envelope + + +def write_report_data( + envelope: dict[str, Any], + output_path: str | Path, + *, + indent: int = 2, +) -> Path: + """Serialize ``envelope`` to ``output_path`` and return the Path.""" + path = Path(output_path) + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(envelope, indent=indent, default=str)) + return path diff --git a/core/notebook_helpers/__init__.py b/core/notebook_helpers/__init__.py new file mode 100644 index 0000000..0a4a282 --- /dev/null +++ b/core/notebook_helpers/__init__.py @@ -0,0 +1,5 @@ +"""Notebook helpers — pandas tables, plotly charts, IPython display.""" + +from core.notebook_helpers import charts, display, tables + +__all__ = ["charts", "display", "tables"] diff --git a/core/notebook_helpers/charts.py b/core/notebook_helpers/charts.py new file mode 100644 index 0000000..16c018a --- /dev/null +++ b/core/notebook_helpers/charts.py @@ -0,0 +1,193 @@ +""" +Plotly charts for TEI analyses. + +Each function returns a ``plotly.graph_objects.Figure`` so callers can +``.show()`` (notebook), pass to ``st.plotly_chart`` (Streamlit), or write to +HTML / image. No styling is hard-coded beyond a neutral default palette. +""" + +from __future__ import annotations + +from collections.abc import Iterable + +import plotly.graph_objects as go + +PALETTE = { + "benefits": "#2E7D32", # green + "costs": "#C62828", # red + "net_positive": "#1565C0", # blue + "net_negative": "#C62828", + "cumulative": "#616161", # grey +} + + +def cashflow_chart( + yearly_breakdown: list[dict], + *, + title: str = "Cash Flow Analysis (Risk-Adjusted)", + initial_cost: float = 0.0, +) -> go.Figure: + """ + Stacked bars of benefits & costs by year + cumulative net line. + + Mirrors the chart on page 25 of the Forrester Amazon Connect TEI study. + """ + if not yearly_breakdown: + return go.Figure(layout={"title": title}) + + years = ["Initial"] + [f"Year {row['year']}" for row in yearly_breakdown] + benefits = [0.0] + [float(row.get("benefits", 0)) for row in yearly_breakdown] + costs = [-float(initial_cost)] + [ + -float(row.get("costs", 0)) for row in yearly_breakdown + ] + # cumulative_net assumes initial cost has already been deducted + cumulative = [-float(initial_cost)] + [ + float(row.get("cumulative_net", 0)) for row in yearly_breakdown + ] + + fig = go.Figure() + fig.add_bar( + name="Total benefits", + x=years, + y=benefits, + marker_color=PALETTE["benefits"], + ) + fig.add_bar( + name="Total costs", + x=years, + y=costs, + marker_color=PALETTE["costs"], + ) + fig.add_scatter( + name="Cumulative net benefits", + x=years, + y=cumulative, + mode="lines+markers", + line={"color": PALETTE["cumulative"], "width": 3}, + ) + fig.update_layout( + title=title, + barmode="relative", + yaxis_tickformat="$,.0f", + legend={"orientation": "h", "y": -0.15}, + margin={"l": 40, "r": 20, "t": 60, "b": 40}, + ) + return fig + + +def benefits_bar(items: list[dict], *, title: str = "Benefits (Three-Year)") -> go.Figure: + """Horizontal bars of risk-adjusted three-year totals per benefit.""" + labels: list[str] = [] + totals: list[float] = [] + for it in items: + rf = float(it.get("risk_adjustment") or 0.0) + yv = it.get("year_values") or {} + ra_total = sum(float(v or 0) * (1.0 - rf) for v in yv.values()) + labels.append(it.get("label", "") or it.get("field_key", "")) + totals.append(ra_total) + + fig = go.Figure( + go.Bar( + x=totals, + y=labels, + orientation="h", + marker_color=PALETTE["benefits"], + text=[f"${t/1_000_000:,.1f}M" for t in totals], + textposition="auto", + ) + ) + fig.update_layout( + title=title, + xaxis_tickformat="$,.0f", + yaxis={"autorange": "reversed"}, + margin={"l": 40, "r": 20, "t": 60, "b": 40}, + ) + return fig + + +def cost_breakdown_pie( + items: list[dict], *, title: str = "Cost Breakdown (Three-Year, Risk-Adjusted)" +) -> go.Figure: + """Pie chart of risk-adjusted costs by category/label.""" + labels: list[str] = [] + values: list[float] = [] + for it in items: + rf = float(it.get("risk_adjustment") or 0.0) + yv = it.get("year_values") or {} + initial = float(it.get("initial") or 0.0) + ra_total = ( + initial * (1.0 + rf) + + sum(float(v or 0) * (1.0 + rf) for v in yv.values()) + ) + labels.append(it.get("label", "") or it.get("field_key", "")) + values.append(ra_total) + + fig = go.Figure(go.Pie(labels=labels, values=values, hole=0.35)) + fig.update_layout(title=title, margin={"l": 40, "r": 20, "t": 60, "b": 40}) + return fig + + +def scenario_comparison(scenarios: dict) -> go.Figure: + """Grouped bars comparing NPV and Costs PV across scenarios.""" + keys: list[str] = list(scenarios.keys()) + if not keys: + return go.Figure() + benefits = [float(scenarios[k].get("total_benefits_pv") or 0) for k in keys] + costs = [float(scenarios[k].get("total_costs_pv") or 0) for k in keys] + npvs = [float(scenarios[k].get("npv") or 0) for k in keys] + + fig = go.Figure() + fig.add_bar(name="Benefits PV", x=keys, y=benefits, marker_color=PALETTE["benefits"]) + fig.add_bar(name="Costs PV", x=keys, y=costs, marker_color=PALETTE["costs"]) + fig.add_bar(name="NPV", x=keys, y=npvs, marker_color=PALETTE["net_positive"]) + fig.update_layout( + title="Scenario Comparison", + barmode="group", + yaxis_tickformat="$,.0f", + legend={"orientation": "h", "y": -0.15}, + ) + return fig + + +def cumulative_benefits_chart( + yearly_breakdown: list[dict], + *, + title: str = "Cumulative Net Benefits", +) -> go.Figure: + """Single-line cumulative net benefits trajectory.""" + if not yearly_breakdown: + return go.Figure(layout={"title": title}) + years = [f"Year {row['year']}" for row in yearly_breakdown] + cumulative = [float(row.get("cumulative_net", 0)) for row in yearly_breakdown] + fig = go.Figure( + go.Scatter( + x=years, + y=cumulative, + mode="lines+markers", + fill="tozeroy", + line={"color": PALETTE["net_positive"], "width": 3}, + ) + ) + fig.update_layout(title=title, yaxis_tickformat="$,.0f") + return fig + + +def waterfall(values: Iterable[tuple[str, float]], *, title: str = "TEI Waterfall") -> go.Figure: + """ + Generic waterfall (pass tuples of (label, value)). + + Used by 03_business_case to show: Benefits PV → Costs PV → NPV. + """ + labels, amounts = zip(*values, strict=True) if values else ([], []) + measures = ["relative"] * (len(labels) - 1) + ["total"] if labels else [] + fig = go.Figure( + go.Waterfall( + x=list(labels), + y=list(amounts), + measure=measures, + text=[f"${v/1_000_000:,.1f}M" for v in amounts], + textposition="outside", + ) + ) + fig.update_layout(title=title, yaxis_tickformat="$,.0f") + return fig diff --git a/core/notebook_helpers/display.py b/core/notebook_helpers/display.py new file mode 100644 index 0000000..f593ba0 --- /dev/null +++ b/core/notebook_helpers/display.py @@ -0,0 +1,141 @@ +""" +IPython display helpers — KPI cards, formatted summary blocks, alerts. + +Functions are notebook-safe: they fall back to plain ``print`` when running +outside Jupyter / when IPython is not available. +""" + +from __future__ import annotations + +from typing import Any + +try: # pragma: no cover – IPython is a soft dep + from IPython.display import HTML, display + + _IPY = True +except Exception: # pragma: no cover + _IPY = False + + +def _money(value: Any, default: str = "—") -> str: + try: + v = float(value) + except (TypeError, ValueError): + return default + if abs(v) >= 1_000_000_000: + return f"${v/1_000_000_000:,.1f}B" + if abs(v) >= 1_000_000: + return f"${v/1_000_000:,.1f}M" + if abs(v) >= 1_000: + return f"${v/1_000:,.1f}K" + return f"${v:,.0f}" + + +def _pct(value: Any, default: str = "—") -> str: + try: + v = float(value) + except (TypeError, ValueError): + return default + return f"{v:,.0f}%" + + +def _months(value: Any, default: str = "N/A") -> str: + if value is None: + return default + try: + v = float(value) + except (TypeError, ValueError): + return default + if v < 6: + return f"<6 months ({v:.1f})" + return f"{v:.1f} months" + + +def kpi_cards(summary: dict, *, title: str | None = None) -> Any: + """ + Render a row of KPI cards (NPV, ROI, Payback, Benefits PV). + + In notebooks, returns/displays inline HTML. Outside IPython, prints a + plain text version. + """ + npv = _money(summary.get("npv")) + roi = _pct(summary.get("roi") or summary.get("roi_pct")) + payback = _months(summary.get("payback_months")) + benefits_pv = _money(summary.get("total_benefits_pv")) + costs_pv = _money(summary.get("total_costs_pv")) + + if not _IPY: # pragma: no cover + print(title or "TEI Summary") + print(f" NPV: {npv} ROI: {roi} Payback: {payback}") + print(f" Benefits PV: {benefits_pv} Costs PV: {costs_pv}") + return None + + title_html = ( + f'
' + f"{title}
" + if title + else "" + ) + card_style = ( + "flex:1;min-width:140px;padding:14px 18px;margin:4px;border-radius:8px;" + "background:#f7f9fc;border:1px solid #e3e8ee;" + ) + label_style = "font-size:0.78em;color:#6b7480;text-transform:uppercase;letter-spacing:0.04em;" + value_style = "font-size:1.6em;font-weight:600;color:#1a2540;margin-top:4px;" + + cards = [ + ("NPV", npv), + ("ROI", roi), + ("Payback", payback), + ("Benefits PV", benefits_pv), + ("Costs PV", costs_pv), + ] + cards_html = "".join( + f'
' + f'
{label}
' + f'
{value}
' + f"
" + for label, value in cards + ) + html = ( + f'
{title_html}' + f'
{cards_html}
' + f"
" + ) + return display(HTML(html)) + + +def summary_panel(summary: dict, *, title: str = "TEI Financial Summary") -> None: + """Plain-text bordered summary block (mirrors the PDF Cash Flow Analysis).""" + width = 60 + print("═" * width) + print(f" {title}") + print("═" * width) + print(f" Benefits PV : {_money(summary.get('total_benefits_pv')):>20}") + print(f" Costs PV : {_money(summary.get('total_costs_pv')):>20}") + print("─" * width) + print(f" NPV : {_money(summary.get('npv')):>20}") + roi_val = summary.get("roi") or summary.get("roi_pct") + print(f" ROI : {_pct(roi_val):>20}") + print(f" Payback : {_months(summary.get('payback_months')):>20}") + print("═" * width) + + +def alert(text: str, kind: str = "info") -> Any: + """Coloured alert box for notebooks ('info', 'success', 'warning', 'error').""" + colors = { + "info": ("#0277bd", "#e1f5fe"), + "success": ("#2e7d32", "#e8f5e9"), + "warning": ("#ef6c00", "#fff3e0"), + "error": ("#c62828", "#ffebee"), + } + fg, bg = colors.get(kind, colors["info"]) + if not _IPY: # pragma: no cover + print(f"[{kind.upper()}] {text}") + return None + html = ( + f'
' + f"{text}
" + ) + return display(HTML(html)) diff --git a/core/notebook_helpers/tables.py b/core/notebook_helpers/tables.py new file mode 100644 index 0000000..7ffc30b --- /dev/null +++ b/core/notebook_helpers/tables.py @@ -0,0 +1,127 @@ +""" +Pandas dataframe builders for benefit / cost / summary tables. + +Each builder accepts the value-row dicts produced by +``core.tei_client.TEIClient._normalize_value`` and returns a +nicely-formatted DataFrame for display in notebooks. +""" + +from __future__ import annotations + +from collections.abc import Iterable +from typing import Any + +import pandas as pd + +from core.calculations import risk_adjust_benefit, risk_adjust_cost + + +def _years_in_data(items: Iterable[dict]) -> list[int]: + years: set[int] = set() + for it in items: + for k in (it.get("year_values") or {}): + try: + years.add(int(k)) + except (TypeError, ValueError): + continue + return sorted(years) + + +def benefits_table(items: list[dict]) -> pd.DataFrame: + """Tidy benefits dataframe with one row per benefit, year columns, totals.""" + if not items: + return pd.DataFrame( + columns=["field_key", "label", "category", "risk_adjustment"] + ) + years = _years_in_data(items) + rows: list[dict[str, Any]] = [] + for it in items: + rf = float(it.get("risk_adjustment") or 0.0) + yv = it.get("year_values") or {} + row = { + "field_key": it.get("field_key", ""), + "label": it.get("label", "") or it.get("field_key", ""), + "category": it.get("category", ""), + "risk_adjustment": rf, + } + nominal_total = 0.0 + ra_total = 0.0 + for y in years: + v = float(yv.get(str(y)) or 0.0) + ra = risk_adjust_benefit(v, rf) + row[f"Year {y}"] = v + row[f"Year {y} (RA)"] = ra + nominal_total += v + ra_total += ra + row["Total"] = nominal_total + row["Total (RA)"] = ra_total + rows.append(row) + return pd.DataFrame(rows) + + +def costs_table(items: list[dict]) -> pd.DataFrame: + """Tidy costs dataframe — adds an Initial column when present.""" + if not items: + return pd.DataFrame( + columns=["field_key", "label", "category", "risk_adjustment", "Initial"] + ) + years = _years_in_data(items) + rows: list[dict[str, Any]] = [] + for it in items: + rf = float(it.get("risk_adjustment") or 0.0) + yv = it.get("year_values") or {} + initial = float(it.get("initial") or 0.0) + row = { + "field_key": it.get("field_key", ""), + "label": it.get("label", "") or it.get("field_key", ""), + "category": it.get("category", ""), + "risk_adjustment": rf, + "Initial": initial, + "Initial (RA)": risk_adjust_cost(initial, rf), + } + nominal_total = initial + ra_total = risk_adjust_cost(initial, rf) + for y in years: + v = float(yv.get(str(y)) or 0.0) + ra = risk_adjust_cost(v, rf) + row[f"Year {y}"] = v + row[f"Year {y} (RA)"] = ra + nominal_total += v + ra_total += ra + row["Total"] = nominal_total + row["Total (RA)"] = ra_total + rows.append(row) + return pd.DataFrame(rows) + + +def summary_table(summary: dict) -> pd.DataFrame: + """Single-row summary dataframe of headline KPIs.""" + pb = summary.get("payback_months") + pb_str = f"{float(pb):.1f} months" if pb not in (None, "") else "N/A" + data = { + "NPV": [float(summary.get("npv") or 0)], + "ROI %": [float(summary.get("roi") or summary.get("roi_pct") or 0)], + "Payback": [pb_str], + "Benefits PV": [float(summary.get("total_benefits_pv") or 0)], + "Costs PV": [float(summary.get("total_costs_pv") or 0)], + "Discount rate": [float(summary.get("discount_rate") or 0)], + "Analysis years": [int(summary.get("analysis_years") or 0)], + } + return pd.DataFrame(data) + + +def cashflow_table(summary: dict) -> pd.DataFrame: + """Per-year cashflow dataframe from a summary's ``yearly_breakdown``.""" + yb = summary.get("yearly_breakdown") or [] + if not yb: + return pd.DataFrame(columns=["Year", "Benefits", "Costs", "Net", "Cumulative"]) + df = pd.DataFrame(yb) + rename = { + "year": "Year", + "benefits": "Benefits", + "costs": "Costs", + "net": "Net", + "cumulative_net": "Cumulative", + } + df = df.rename(columns=rename) + return df diff --git a/core/tei_client/__init__.py b/core/tei_client/__init__.py new file mode 100644 index 0000000..2c483c3 --- /dev/null +++ b/core/tei_client/__init__.py @@ -0,0 +1,13 @@ +"""TEI Client — Athena API wrapper for Palladium.""" + +from core.tei_client.client import AthenaAPIError, TEIClient +from core.tei_client.models import TEIField, TEIReport, TEISummary, TEIValue + +__all__ = [ + "AthenaAPIError", + "TEIClient", + "TEIField", + "TEIReport", + "TEISummary", + "TEIValue", +] diff --git a/core/tei_client/client.py b/core/tei_client/client.py new file mode 100644 index 0000000..65cad60 --- /dev/null +++ b/core/tei_client/client.py @@ -0,0 +1,563 @@ +""" +TEI Client — Athena API wrapper for Palladium. + +Endpoints (per Athena API.yaml, all under ``/api/v1/tei/``): + + Reports (templates) + GET /reports/ list_reports + GET /reports/{public_id}/ get_report + GET /reports/{public_id}/fields/ list_fields + PATCH /reports/{public_id}/fields/reorder/ reorder_fields + + Tools (instances) + GET /tools/ list_tools + POST /tools/ create_tool + GET /tools/{public_id}/ get_tool + PATCH /tools/{public_id}/ update_tool + DELETE /tools/{public_id}/ delete_tool + + Values (data entry) + GET /tools/{public_id}/values/ get_values + PUT /tools/{public_id}/values/ update_values (bulk) + PATCH /tools/{public_id}/values/{field_key}/ patch_value (single) + + Calculation & summary + POST /tools/{public_id}/calculate/ calculate + GET /tools/{public_id}/summary/ get_summary + GET /summary/ aggregate_summary + + Versions + GET /tools/{public_id}/versions/ list_versions + POST /tools/{public_id}/versions/ save_version + GET /tools/{public_id}/versions/{n}/ get_version + + Export + GET /tools/{public_id}/export/ export + +Authentication uses the ``Authorization: Api-Key {key}`` header. +""" + +from __future__ import annotations + +import json +import logging +import os +from datetime import datetime +from typing import Any + +import requests +from dotenv import load_dotenv + +load_dotenv() + +logger = logging.getLogger(__name__) + +API_PREFIX = "/api/v1/tei" + + +class AthenaAPIError(Exception): + """Raised when Athena returns a non-success response.""" + + def __init__(self, status_code: int, detail: str, url: str): + self.status_code = status_code + self.detail = detail + self.url = url + super().__init__(f"Athena API {status_code} at {url}: {detail}") + + +class TEIClient: + """ + Client for Athena's TEI Calculator API. + + Wraps every TEI endpoint and provides a few convenience helpers used by + the Palladium notebooks and Streamlit app. + + Environment variables (read via python-dotenv): + ATHENA_BASE_URL e.g. https://athena.nttdata.com + ATHENA_API_KEY Api-Key value (admin-issued) + + Example:: + + from core.tei_client import TEIClient + client = TEIClient() + client.test_connection() + for r in client.list_reports(): + print(r["name"]) + """ + + def __init__( + self, + base_url: str | None = None, + api_key: str | None = None, + timeout: int = 30, + ): + self.base_url = (base_url or os.getenv("ATHENA_BASE_URL", "")).rstrip("/") + self.api_key = api_key or os.getenv("ATHENA_API_KEY", "") + self.timeout = timeout + + if not self.base_url: + raise ValueError( + "ATHENA_BASE_URL is required. Set it in .env or pass base_url." + ) + if not self.api_key: + raise ValueError( + "ATHENA_API_KEY is required. Set it in .env or pass api_key." + ) + + self.session = requests.Session() + self.session.headers.update( + { + "Authorization": f"Api-Key {self.api_key}", + "Content-Type": "application/json", + "Accept": "application/json", + } + ) + + logger.info("TEIClient initialised for %s", self.base_url) + + # ───────────────────────────────────────────── + # Internal HTTP helpers + # ───────────────────────────────────────────── + + def _url(self, path: str) -> str: + if not path.startswith("/"): + path = f"/{path}" + return f"{self.base_url}{path}" + + def _request( + self, + method: str, + path: str, + params: dict | None = None, + json_data: Any | None = None, + ) -> Any: + url = self._url(path) + logger.debug("%s %s", method.upper(), url) + + try: + response = self.session.request( + method=method, + url=url, + params=params, + json=json_data, + timeout=self.timeout, + ) + except requests.ConnectionError as e: + raise AthenaAPIError(0, f"Connection failed: {e}", url) from e + except requests.Timeout as e: + raise AthenaAPIError(408, "Request timed out", url) from e + + if response.status_code >= 400: + try: + payload = response.json() + detail = payload.get("detail") or json.dumps(payload) + except (json.JSONDecodeError, ValueError, AttributeError): + detail = response.text + raise AthenaAPIError(response.status_code, detail, url) + + if response.status_code == 204 or not response.content: + return {} + return response.json() + + def _get(self, path: str, params: dict | None = None) -> Any: + return self._request("GET", path, params=params) + + def _post(self, path: str, data: Any | None = None) -> Any: + return self._request("POST", path, json_data=data) + + def _put(self, path: str, data: Any | None = None) -> Any: + return self._request("PUT", path, json_data=data) + + def _patch(self, path: str, data: Any | None = None) -> Any: + return self._request("PATCH", path, json_data=data) + + def _delete(self, path: str) -> Any: + return self._request("DELETE", path) + + def _paginated(self, path: str, params: dict | None = None) -> list[dict]: + """ + Fetch all pages of a paginated list endpoint. + + Athena uses the standard DRF page/results envelope:: + + {"count": N, "next": url|None, "previous": ..., "results": [...]} + """ + out: list[dict] = [] + result = self._get(path, params=params) + while True: + if isinstance(result, list): + out.extend(result) + return out + if not isinstance(result, dict): + return out + out.extend(result.get("results", []) or []) + next_url = result.get("next") + if not next_url: + return out + # Follow absolute next URL + try: + result = self.session.get(next_url, timeout=self.timeout).json() + except Exception: # pragma: no cover – defensive + return out + + # ───────────────────────────────────────────── + # Connection test + # ───────────────────────────────────────────── + + def test_connection(self) -> dict: + """Verify API connectivity and authentication.""" + try: + result = self._get(f"{API_PREFIX}/reports/") + count = ( + result.get("count", len(result.get("results", []))) + if isinstance(result, dict) + else len(result) + ) + return { + "status": "ok", + "base_url": self.base_url, + "authenticated": True, + "reports_found": count, + "timestamp": datetime.now().isoformat(), + } + except AthenaAPIError as e: + return { + "status": "error", + "base_url": self.base_url, + "authenticated": e.status_code != 401, + "error_code": e.status_code, + "detail": e.detail, + "timestamp": datetime.now().isoformat(), + } + + # ───────────────────────────────────────────── + # Reports (templates) + # ───────────────────────────────────────────── + + def list_reports(self) -> list[dict]: + """List all TEI report templates (auto-paginated).""" + return self._paginated(f"{API_PREFIX}/reports/") + + def get_report(self, public_id: str) -> dict: + """Get a TEI report template by its public_id.""" + return self._get(f"{API_PREFIX}/reports/{public_id}/") + + def list_fields( + self, + report_public_id: str, + table: str | None = None, + ) -> list[dict]: + """ + Get field definitions for a report. + + Args: + report_public_id: The report template's public_id (12-char short UUID). + table: Optional filter — ``'benefits'`` or ``'costs'``. + + Returns a list of field-definition dicts. See ``TEIField.from_dict`` + for the expected shape. + """ + params = {"table": table} if table else None + rows = self._paginated( + f"{API_PREFIX}/reports/{report_public_id}/fields/", params=params + ) + # Defensive — server-side filter may not be implemented; filter locally. + if table: + rows = [r for r in rows if r.get("table") == table] + rows.sort(key=lambda r: (r.get("table", ""), r.get("sort_order") or 0)) + return rows + + def create_field(self, report_public_id: str, field: dict) -> dict: + """Create a new field definition under a report (admin only).""" + return self._post( + f"{API_PREFIX}/reports/{report_public_id}/fields/", data=field + ) + + def update_field(self, report_public_id: str, field_id: int, **changes) -> dict: + """Patch one field definition by its integer id.""" + return self._patch( + f"{API_PREFIX}/reports/{report_public_id}/fields/{field_id}/", + data=changes, + ) + + def delete_field(self, report_public_id: str, field_id: int) -> dict: + return self._delete( + f"{API_PREFIX}/reports/{report_public_id}/fields/{field_id}/" + ) + + def reorder_fields(self, report_public_id: str, field_ids: list[int]) -> dict: + """Bulk-reorder fields. Spec: PATCH /reports/{id}/fields/reorder/.""" + return self._patch( + f"{API_PREFIX}/reports/{report_public_id}/fields/reorder/", + data={"field_ids": field_ids}, + ) + + # ───────────────────────────────────────────── + # Tools (instances) + # ───────────────────────────────────────────── + + def list_tools(self) -> list[dict]: + """List TEI tool instances owned by the current API key.""" + return self._paginated(f"{API_PREFIX}/tools/") + + def get_tool(self, public_id: str) -> dict: + """Get a TEI tool instance by public_id.""" + return self._get(f"{API_PREFIX}/tools/{public_id}/") + + def create_tool( + self, + report_public_id: str, + proposal: int | None = None, + engagement: int | None = None, + name: str | None = None, + status: str = "draft", + ) -> dict: + """ + Create a new TEI tool instance from a report template. + + Athena scopes a TEI tool to a *Proposal* (which itself belongs to an + Opportunity) and/or an *Engagement*. Pass the integer PK of either or + both to link the tool. + """ + data: dict[str, Any] = {"report": report_public_id, "status": status} + if proposal is not None: + data["proposal"] = proposal + if engagement is not None: + data["engagement"] = engagement + if name: + data["name"] = name + return self._post(f"{API_PREFIX}/tools/", data=data) + + def update_tool( + self, + public_id: str, + name: str | None = None, + status: str | None = None, + ) -> dict: + """Update tool metadata. Only ``name`` and ``status`` are mutable.""" + data: dict[str, Any] = {} + if name is not None: + data["name"] = name + if status is not None: + data["status"] = status + return self._patch(f"{API_PREFIX}/tools/{public_id}/", data=data) + + def delete_tool(self, public_id: str) -> dict: + return self._delete(f"{API_PREFIX}/tools/{public_id}/") + + # ───────────────────────────────────────────── + # Values (data entry) + # ───────────────────────────────────────────── + + @staticmethod + def _normalize_value(value: dict) -> dict: + """ + Normalize a value-row dict into the shape the API expects. + + Accepts any of the following input forms and produces a uniform + wire-format dict:: + + # annual fields + {"field_key": "A1", "year_1": 100, "year_2": 200, "year_3": 300, ...} + {"field_key": "A1", "year_values": {"1": 100, "2": 200, "3": 300}, ...} + + # non-annual scalars + {"field_key": "rate", "value": 0.10, ...} + + Returns a dict like:: + + {"field_key": "A1", + "year_values": {"1": 100.0, "2": 200.0, "3": 300.0}, + "risk_adjustment": 0.15, + "notes": "…"} + """ + out: dict[str, Any] = {} + if "field_key" in value: + out["field_key"] = value["field_key"] + elif "field" in value: + out["field_key"] = value["field"] + + # Collect annual year_N keys into year_values + year_values: dict[str, float] = {} + if "year_values" in value and isinstance(value["year_values"], dict): + for k, v in value["year_values"].items(): + year_values[str(k)] = float(v) if v is not None else 0.0 + for key, raw in value.items(): + if key.startswith("year_"): + try: + n = int(key.split("_", 1)[1]) + except ValueError: + continue + year_values[str(n)] = float(raw) if raw is not None else 0.0 + + if year_values: + out["year_values"] = year_values + if "value" in value and value["value"] is not None and not year_values: + out["value"] = value["value"] + if value.get("initial") is not None: + out["initial"] = float(value["initial"]) + if value.get("risk_adjustment") is not None: + out["risk_adjustment"] = float(value["risk_adjustment"]) + if value.get("notes"): + out["notes"] = str(value["notes"]) + return out + + def get_values(self, public_id: str) -> list[dict]: + """Get all current field values for a TEI tool instance.""" + result = self._get(f"{API_PREFIX}/tools/{public_id}/values/") + if isinstance(result, dict): + # Could be {"values": [...]} envelope, the TEITool wrapper, or a page + if "values" in result and isinstance(result["values"], list): + return result["values"] + if "results" in result and isinstance(result["results"], list): + return result["results"] + return [] + if isinstance(result, list): + return result + return [] + + def update_values(self, public_id: str, values: list[dict]) -> dict: + """ + Bulk-update field values. See ``_normalize_value`` for accepted shapes. + """ + payload = {"values": [self._normalize_value(v) for v in values]} + return self._put(f"{API_PREFIX}/tools/{public_id}/values/", data=payload) + + def patch_value(self, public_id: str, field_key: str, **changes) -> dict: + """ + Patch a single field value by its ``field_key``. + + Accepts the same shorthand as ``update_values`` (``year_1=…``, etc). + """ + body = self._normalize_value({"field_key": field_key, **changes}) + body.pop("field_key", None) # carried in URL + return self._patch( + f"{API_PREFIX}/tools/{public_id}/values/{field_key}/", data=body + ) + + # ───────────────────────────────────────────── + # Calculation & summary + # ───────────────────────────────────────────── + + def calculate(self, public_id: str) -> dict: + """Trigger server-side calculation; returns the updated summary.""" + return self._post(f"{API_PREFIX}/tools/{public_id}/calculate/") + + def get_summary(self, public_id: str) -> dict: + """Return the most-recent summary (404 if never calculated).""" + return self._get(f"{API_PREFIX}/tools/{public_id}/summary/") + + def aggregate_summary(self) -> dict: + """Aggregate NPV across all tools owned by the current API key.""" + return self._get(f"{API_PREFIX}/summary/") + + # ───────────────────────────────────────────── + # Versions + # ───────────────────────────────────────────── + + def list_versions(self, public_id: str) -> list[dict]: + """List all saved version snapshots for a TEI tool.""" + result = self._get(f"{API_PREFIX}/tools/{public_id}/versions/") + if isinstance(result, list): + return result + if isinstance(result, dict): + if "results" in result and isinstance(result["results"], list): + return result["results"] + if "versions" in result and isinstance(result["versions"], list): + return result["versions"] + return [] + + def save_version(self, public_id: str, note: str = "") -> dict: + """Snapshot current values + summary as a new version.""" + return self._post( + f"{API_PREFIX}/tools/{public_id}/versions/", + data={"note": note}, + ) + + def get_version(self, public_id: str, version_number: int) -> dict: + """Get a single version's full snapshot.""" + return self._get( + f"{API_PREFIX}/tools/{public_id}/versions/{int(version_number)}/" + ) + + # ───────────────────────────────────────────── + # Export + # ───────────────────────────────────────────── + + def export(self, public_id: str) -> dict: + """ + Return the LLM-ready export payload for the report pipeline. + + The shape is determined by Athena and consumed by Peitho / + html2docx; Palladium's ``core.export.report_data`` builds on this. + """ + return self._get(f"{API_PREFIX}/tools/{public_id}/export/") + + # ───────────────────────────────────────────── + # Convenience + # ───────────────────────────────────────────── + + def get_benefits(self, public_id: str) -> list[dict]: + """Return only benefit-table values (table='benefits').""" + return [v for v in self.get_values(public_id) if v.get("table") == "benefits"] + + def get_costs(self, public_id: str) -> list[dict]: + """Return only cost-table values (table='costs').""" + return [v for v in self.get_values(public_id) if v.get("table") == "costs"] + + def get_tool_with_data(self, public_id: str) -> dict: + """ + Bundle a tool, its field definitions, current values, and summary. + + Convenience for notebook initialisation. The summary is allowed to + 404 (returned as ``None``) when the tool has never been calculated. + """ + tool = self.get_tool(public_id) + report_pid = tool.get("report") + if isinstance(report_pid, dict): + report_pid = report_pid.get("id") or report_pid.get("public_id") + fields = self.list_fields(report_pid) if report_pid else [] + values = self.get_values(public_id) + try: + summary = self.get_summary(public_id) + except AthenaAPIError as e: + if e.status_code == 404: + summary = None + else: + raise + return { + "tool": tool, + "fields": fields, + "values": values, + "summary": summary, + } + + # ───────────────────────────────────────────── + # Display + # ───────────────────────────────────────────── + + def __repr__(self) -> str: # pragma: no cover + return f"TEIClient(base_url='{self.base_url}')" + + def print_summary(self, public_id: str) -> None: + """Pretty-print a financial summary block for notebooks/REPL.""" + s = self.get_summary(public_id) + + def _f(v: Any, default: float = 0.0) -> float: + try: + return float(v) if v is not None else default + except (TypeError, ValueError): + return default + + print("═" * 56) + print(" TEI Financial Summary") + print("═" * 56) + print(f" Total Benefits (PV): ${_f(s.get('total_benefits_pv')):>16,.0f}") + print(f" Total Costs (PV): ${_f(s.get('total_costs_pv')):>16,.0f}") + print("─" * 56) + print(f" Net Present Value: ${_f(s.get('npv')):>16,.0f}") + print(f" ROI: {_f(s.get('roi')):>15,.0f}%") + payback = s.get("payback_months") + payback_str = f"{_f(payback):.1f} months" if payback is not None else "N/A" + print(f" Payback: {payback_str:>17}") + print("═" * 56) diff --git a/core/tei_client/models.py b/core/tei_client/models.py new file mode 100644 index 0000000..34b07c9 --- /dev/null +++ b/core/tei_client/models.py @@ -0,0 +1,194 @@ +""" +Lightweight dataclasses for TEI API responses. + +These are *optional* — the client returns raw dicts. Use these when you want +attribute access or IDE help in notebooks. + + >>> from core.tei_client import TEIClient, TEIReport + >>> raw = TEIClient().get_report("abc123") + >>> report = TEIReport.from_dict(raw) + >>> report.discount_rate + 0.10 +""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from decimal import Decimal +from typing import Any + + +def _as_float(value: Any, default: float = 0.0) -> float: + """Coerce Decimal/str/None into float.""" + if value is None or value == "": + return default + if isinstance(value, Decimal): + return float(value) + try: + return float(value) + except (TypeError, ValueError): + return default + + +@dataclass +class TEIReport: + """A TEI report template (model definition).""" + + id: str # public_id + name: str + vendor: str + version: str + description: str = "" + analysis_period_years: int = 3 + discount_rate: float = 0.10 + status: str = "active" + field_count: int = 0 + instance_count: int = 0 + created_at: str = "" + updated_at: str = "" + + @classmethod + def from_dict(cls, data: dict) -> TEIReport: + return cls( + id=str(data.get("id", "")), + name=data.get("name", ""), + vendor=data.get("vendor", ""), + version=data.get("version", ""), + description=data.get("description", "") or "", + analysis_period_years=int(data.get("analysis_period_years") or 3), + discount_rate=_as_float(data.get("discount_rate"), 0.10), + status=data.get("status", "active"), + field_count=int(data.get("field_count") or 0), + instance_count=int(data.get("instance_count") or 0), + created_at=data.get("created_at", "") or "", + updated_at=data.get("updated_at", "") or "", + ) + + +@dataclass +class TEIField: + """A field definition belonging to a TEI report template.""" + + id: int + table: str # 'benefits' | 'costs' + field_key: str + label: str + field_type: str # currency | percentage | integer | decimal | text + description: str = "" + category: str = "" + default_value: str = "" + is_annual: bool = True + risk_adjustment: float | None = None + sort_order: int = 0 + is_required: bool = False + source_notes: str = "" + + @classmethod + def from_dict(cls, data: dict) -> TEIField: + ra = data.get("risk_adjustment") + return cls( + id=int(data.get("id") or 0), + table=data.get("table", "benefits"), + field_key=data.get("field_key", ""), + label=data.get("label", ""), + field_type=data.get("field_type", "decimal"), + description=data.get("description", "") or "", + category=data.get("category", "") or "", + default_value=data.get("default_value", "") or "", + is_annual=bool(data.get("is_annual", True)), + risk_adjustment=_as_float(ra) if ra is not None else None, + sort_order=int(data.get("sort_order") or 0), + is_required=bool(data.get("is_required", False)), + source_notes=data.get("source_notes", "") or "", + ) + + +@dataclass +class TEIValue: + """ + A field value for a specific TEI tool instance. + + The exact wire format is not fully pinned in the OpenAPI spec; we use a + convention that the client `_normalize_value` helper builds: + + - annual fields: {field_key, year_values: {"1": ..., "2": ...}, + risk_adjustment, notes} + - non-annual scalar: {field_key, value, risk_adjustment, notes} + """ + + field_key: str + year_values: dict[str, float] = field(default_factory=dict) + value: float | None = None + risk_adjustment: float | None = None + notes: str = "" + + @classmethod + def from_dict(cls, data: dict) -> TEIValue: + ra = data.get("risk_adjustment") + yv_raw = data.get("year_values") or {} + year_values = {str(k): _as_float(v) for k, v in yv_raw.items()} + v = data.get("value") + return cls( + field_key=data.get("field_key", ""), + year_values=year_values, + value=_as_float(v) if v is not None else None, + risk_adjustment=_as_float(ra) if ra is not None else None, + notes=data.get("notes", "") or "", + ) + + def to_dict(self) -> dict: + out: dict[str, Any] = {"field_key": self.field_key} + if self.year_values: + out["year_values"] = self.year_values + if self.value is not None: + out["value"] = self.value + if self.risk_adjustment is not None: + out["risk_adjustment"] = self.risk_adjustment + if self.notes: + out["notes"] = self.notes + return out + + +@dataclass +class TEISummary: + """Calculated financial summary for a TEI tool instance.""" + + npv: float = 0.0 + roi: float = 0.0 + payback_months: float | None = None + discount_rate: float = 0.10 + analysis_years: int = 3 + total_benefits_nominal: float = 0.0 + total_benefits_risk_adjusted: float = 0.0 + total_benefits_pv: float = 0.0 + total_costs_nominal: float = 0.0 + total_costs_risk_adjusted: float = 0.0 + total_costs_pv: float = 0.0 + yearly_breakdown: list[dict] = field(default_factory=list) + category_breakdown: list[dict] = field(default_factory=list) + raw: dict = field(default_factory=dict) + + @classmethod + def from_dict(cls, data: dict) -> TEISummary: + return cls( + npv=_as_float(data.get("npv")), + roi=_as_float(data.get("roi")), + payback_months=( + _as_float(data.get("payback_months")) + if data.get("payback_months") is not None + else None + ), + discount_rate=_as_float(data.get("discount_rate"), 0.10), + analysis_years=int(data.get("analysis_years") or 3), + total_benefits_nominal=_as_float(data.get("total_benefits_nominal")), + total_benefits_risk_adjusted=_as_float( + data.get("total_benefits_risk_adjusted") + ), + total_benefits_pv=_as_float(data.get("total_benefits_pv")), + total_costs_nominal=_as_float(data.get("total_costs_nominal")), + total_costs_risk_adjusted=_as_float(data.get("total_costs_risk_adjusted")), + total_costs_pv=_as_float(data.get("total_costs_pv")), + yearly_breakdown=list(data.get("yearly_breakdown") or []), + category_breakdown=list(data.get("category_breakdown") or []), + raw=dict(data), + ) diff --git a/docs/Athena API.yaml b/docs/Athena API.yaml new file mode 100644 index 0000000..afce7a5 --- /dev/null +++ b/docs/Athena API.yaml @@ -0,0 +1,30709 @@ +openapi: 3.0.3 +info: + title: Athena API + version: 1.0.0 + description: "\n Comprehensive API for Athena advisory consulting platform.\n + \ \n ## Key Features\n \n - **Automatic Version Numbering**: Location + Tools use automatic sequential version numbering (1, 2, 3, etc.)\n - **RESTful + Design**: Standard HTTP methods and status codes\n - **JSON API**: All requests + and responses use JSON format\n - **API Key Authentication**: Secure access + with user-specific API keys\n - **Rate Limiting**: 1000 requests per hour per + API key\n - **Pagination**: Consistent pagination across all list endpoints\n + \ \n ## Location Tool Versioning\n \n The Location Tool implements + automatic version numbering to ensure data integrity:\n \n - Version numbers + are automatically assigned as sequential integers\n - No manual version input + is required or accepted\n - Concurrent version creation is handled safely\n + \ - All version numbers are stored as integers for proper sorting\n \n See + the Location Tools section for detailed examples and usage patterns.\n " +paths: + /api/v1/advisor/findings/: + get: + operationId: advisor_findings_list + description: Retrieve a list of findings across advisor tools owned by the current + user. + summary: List findings + parameters: + - in: query + name: ordering + schema: + type: string + description: 'Order by: name, impact_level, created_date' + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - in: query + name: search + schema: + type: string + description: Search findings by name, description, or source detail + tags: + - Advisor + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedFindingList' + description: '' + post: + operationId: advisor_findings_create + description: Create a new finding within an advisor tool. + summary: Create finding + tags: + - Advisor + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/FindingRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/FindingRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/FindingRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Finding' + description: '' + /api/v1/advisor/findings/{id}/: + get: + operationId: advisor_findings_retrieve + description: Retrieve detailed information about a specific finding. + summary: Get finding details + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this finding. + required: true + tags: + - Advisor + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Finding' + description: '' + put: + operationId: advisor_findings_update + description: Update all fields of a finding. + summary: Update finding + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this finding. + required: true + tags: + - Advisor + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/FindingRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/FindingRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/FindingRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Finding' + description: '' + patch: + operationId: advisor_findings_partial_update + description: Update specific fields of a finding. + summary: Partially update finding + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this finding. + required: true + tags: + - Advisor + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedFindingRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedFindingRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedFindingRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Finding' + description: '' + delete: + operationId: advisor_findings_destroy + description: Delete a finding. + summary: Delete finding + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this finding. + required: true + tags: + - Advisor + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/advisor/findings/by_impact/: + get: + operationId: advisor_findings_by_impact_retrieve + description: Filter findings by their impact level. + summary: Get findings by impact level + parameters: + - in: query + name: level + schema: + type: string + description: Impact level to filter by (e.g. High, Medium, Low) + required: true + tags: + - Advisor + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Finding' + description: '' + /api/v1/advisor/findings/by_source/: + get: + operationId: advisor_findings_by_source_retrieve + description: Filter findings by their source. + summary: Get findings by source + parameters: + - in: query + name: source + schema: + type: string + description: Source to filter findings by + required: true + tags: + - Advisor + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Finding' + description: '' + /api/v1/advisor/principles/: + get: + operationId: advisor_principles_list + description: Retrieve a list of principles across advisor tools owned by the + current user. + summary: List principles + parameters: + - in: query + name: ordering + schema: + type: string + description: 'Order by: name, weight, order, created_date' + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - in: query + name: search + schema: + type: string + description: Search principles by name or description + tags: + - Advisor + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedPrincipleList' + description: '' + post: + operationId: advisor_principles_create + description: Create a new principle within an advisor tool. + summary: Create principle + tags: + - Advisor + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PrincipleRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PrincipleRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PrincipleRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Principle' + description: '' + /api/v1/advisor/principles/{id}/: + get: + operationId: advisor_principles_retrieve + description: Retrieve detailed information about a specific principle. + summary: Get principle details + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this principle. + required: true + tags: + - Advisor + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Principle' + description: '' + put: + operationId: advisor_principles_update + description: Update all fields of a principle. + summary: Update principle + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this principle. + required: true + tags: + - Advisor + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PrincipleRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PrincipleRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PrincipleRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Principle' + description: '' + patch: + operationId: advisor_principles_partial_update + description: Update specific fields of a principle. + summary: Partially update principle + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this principle. + required: true + tags: + - Advisor + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedPrincipleRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedPrincipleRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedPrincipleRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Principle' + description: '' + delete: + operationId: advisor_principles_destroy + description: Delete a principle. + summary: Delete principle + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this principle. + required: true + tags: + - Advisor + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/advisor/recommendations/: + get: + operationId: advisor_recommendations_list + description: Retrieve a list of recommendations across advisor tools owned by + the current user. + summary: List recommendations + parameters: + - in: query + name: ordering + schema: + type: string + description: 'Order by: title, cost, complexity, implementation_days, created_date' + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - in: query + name: search + schema: + type: string + description: Search recommendations by title or description + tags: + - Advisor + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedRecommendationList' + description: '' + post: + operationId: advisor_recommendations_create + description: Create a new recommendation within an advisor tool. + summary: Create recommendation + tags: + - Advisor + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RecommendationRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/RecommendationRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/RecommendationRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Recommendation' + description: '' + /api/v1/advisor/recommendations/{id}/: + get: + operationId: advisor_recommendations_retrieve + description: Retrieve detailed information about a specific recommendation. + summary: Get recommendation details + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this recommendation. + required: true + tags: + - Advisor + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Recommendation' + description: '' + put: + operationId: advisor_recommendations_update + description: Update all fields of a recommendation. + summary: Update recommendation + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this recommendation. + required: true + tags: + - Advisor + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RecommendationRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/RecommendationRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/RecommendationRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Recommendation' + description: '' + patch: + operationId: advisor_recommendations_partial_update + description: Update specific fields of a recommendation. + summary: Partially update recommendation + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this recommendation. + required: true + tags: + - Advisor + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedRecommendationRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedRecommendationRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedRecommendationRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Recommendation' + description: '' + delete: + operationId: advisor_recommendations_destroy + description: Delete a recommendation. + summary: Delete recommendation + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this recommendation. + required: true + tags: + - Advisor + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/advisor/recommendations/{id}/add_alignment/: + post: + operationId: advisor_recommendations_add_alignment_create + description: Add a principle alignment record to a recommendation. + summary: Add principle alignment to recommendation + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this recommendation. + required: true + tags: + - Advisor + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RecommendationRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/RecommendationRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/RecommendationRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Recommendation' + description: '' + /api/v1/advisor/recommendations/{id}/add_impact/: + post: + operationId: advisor_recommendations_add_impact_create + description: Add an impact record to a recommendation. + summary: Add impact to recommendation + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this recommendation. + required: true + tags: + - Advisor + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RecommendationRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/RecommendationRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/RecommendationRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Recommendation' + description: '' + /api/v1/advisor/recommendations/by_complexity/: + get: + operationId: advisor_recommendations_by_complexity_retrieve + description: Filter recommendations by their implementation complexity. + summary: Get recommendations by complexity + parameters: + - in: query + name: complexity + schema: + type: string + description: Complexity level to filter by + required: true + tags: + - Advisor + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Recommendation' + description: '' + /api/v1/advisor/recommendations/by_timeframe/: + get: + operationId: advisor_recommendations_by_timeframe_retrieve + description: Filter recommendations by implementation timeframe. Short = ≤30 + days, Medium = 31–180 days, Long = >180 days. + summary: Get recommendations by timeframe + parameters: + - in: query + name: timeframe + schema: + type: string + description: 'Timeframe to filter by: Short, Medium, or Long' + required: true + tags: + - Advisor + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Recommendation' + description: '' + /api/v1/advisor/requirements/: + get: + operationId: advisor_requirements_list + description: Retrieve a list of requirements across advisor tools owned by the + current user. + summary: List requirements + parameters: + - in: query + name: ordering + schema: + type: string + description: 'Order by: name, type, created_date' + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - in: query + name: search + schema: + type: string + description: Search requirements by name, description, or success measure + tags: + - Advisor + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedRequirementList' + description: '' + post: + operationId: advisor_requirements_create + description: Create a new requirement within an advisor tool. + summary: Create requirement + tags: + - Advisor + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RequirementRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/RequirementRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/RequirementRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Requirement' + description: '' + /api/v1/advisor/requirements/{id}/: + get: + operationId: advisor_requirements_retrieve + description: Retrieve detailed information about a specific requirement. + summary: Get requirement details + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this requirement. + required: true + tags: + - Advisor + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Requirement' + description: '' + put: + operationId: advisor_requirements_update + description: Update all fields of a requirement. + summary: Update requirement + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this requirement. + required: true + tags: + - Advisor + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RequirementRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/RequirementRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/RequirementRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Requirement' + description: '' + patch: + operationId: advisor_requirements_partial_update + description: Update specific fields of a requirement. + summary: Partially update requirement + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this requirement. + required: true + tags: + - Advisor + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedRequirementRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedRequirementRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedRequirementRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Requirement' + description: '' + delete: + operationId: advisor_requirements_destroy + description: Delete a requirement. + summary: Delete requirement + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this requirement. + required: true + tags: + - Advisor + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/advisor/requirements/by_type/: + get: + operationId: advisor_requirements_by_type_retrieve + description: Filter requirements by their type. + summary: Get requirements by type + parameters: + - in: query + name: type + schema: + type: string + description: Requirement type to filter by + required: true + tags: + - Advisor + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Requirement' + description: '' + /api/v1/advisor/tools/: + get: + operationId: advisor_tools_list + description: Retrieve a list of advisor tools owned by the current user. + summary: List advisor tools + parameters: + - in: query + name: ordering + schema: + type: string + description: 'Order by: name, created_date' + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - in: query + name: search + schema: + type: string + description: Search advisor tools by name or description + tags: + - Advisor + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedAdvisorToolList' + description: '' + post: + operationId: advisor_tools_create + description: Create a new advisor tool. + summary: Create advisor tool + tags: + - Advisor + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AdvisorToolRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/AdvisorToolRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/AdvisorToolRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/AdvisorTool' + description: '' + /api/v1/advisor/tools/{id}/: + get: + operationId: advisor_tools_retrieve + description: Retrieve detailed information about a specific advisor tool. + summary: Get advisor tool details + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this Advisor Tool. + required: true + tags: + - Advisor + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/AdvisorTool' + description: '' + put: + operationId: advisor_tools_update + description: Update all fields of an advisor tool. + summary: Update advisor tool + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this Advisor Tool. + required: true + tags: + - Advisor + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AdvisorToolRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/AdvisorToolRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/AdvisorToolRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/AdvisorTool' + description: '' + patch: + operationId: advisor_tools_partial_update + description: Update specific fields of an advisor tool. + summary: Partially update advisor tool + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this Advisor Tool. + required: true + tags: + - Advisor + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedAdvisorToolRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedAdvisorToolRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedAdvisorToolRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/AdvisorTool' + description: '' + delete: + operationId: advisor_tools_destroy + description: Delete an advisor tool and all associated data. + summary: Delete advisor tool + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this Advisor Tool. + required: true + tags: + - Advisor + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/advisor/tools/{id}/summary/: + get: + operationId: advisor_tools_summary_retrieve + description: Retrieve a comprehensive summary of the advisor tool including + all findings, requirements, principles, recommendations, and statistics. + summary: Get advisor tool summary + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this Advisor Tool. + required: true + tags: + - Advisor + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/AdvisorTool' + description: '' + /api/v1/bmc/canvases/: + get: + operationId: bmc_canvases_list + description: Retrieve a list of Business Model Canvases owned by the current + user. + summary: List BMC canvases + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - BMC + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedBMCList' + description: '' + post: + operationId: bmc_canvases_create + description: Create a new Business Model Canvas. The version number is automatically + assigned. + summary: Create BMC canvas + tags: + - BMC + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/BMCRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/BMCRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/BMCRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/BMC' + description: '' + /api/v1/bmc/canvases/{id}/: + get: + operationId: bmc_canvases_retrieve + description: Retrieve detailed information about a specific Business Model Canvas. + summary: Get BMC canvas details + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this Business Model Canvas. + required: true + tags: + - BMC + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/BMC' + description: '' + put: + operationId: bmc_canvases_update + description: Update all fields of a Business Model Canvas. + summary: Update BMC canvas + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this Business Model Canvas. + required: true + tags: + - BMC + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/BMCRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/BMCRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/BMCRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/BMC' + description: '' + patch: + operationId: bmc_canvases_partial_update + description: Update specific fields of a Business Model Canvas. + summary: Partially update BMC canvas + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this Business Model Canvas. + required: true + tags: + - BMC + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedBMCRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedBMCRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedBMCRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/BMC' + description: '' + delete: + operationId: bmc_canvases_destroy + description: Delete a Business Model Canvas. + summary: Delete BMC canvas + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this Business Model Canvas. + required: true + tags: + - BMC + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/bmc/canvases/{id}/create_version/: + post: + operationId: bmc_canvases_create_version_create + description: Create a new incremented version of the current Business Model + Canvas. + summary: Create new version of BMC + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this Business Model Canvas. + required: true + tags: + - BMC + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/BMCRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/BMCRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/BMCRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/BMC' + description: '' + /api/v1/bmc/canvases/{id}/export_svg/: + get: + operationId: bmc_canvases_export_svg_retrieve + description: Export the Business Model Canvas as an SVG file download. + summary: Export BMC as SVG + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this Business Model Canvas. + required: true + tags: + - BMC + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + image/svg+xml: + schema: + type: string + format: binary + description: '' + /api/v1/bmc/canvases/{id}/save_as_template/: + post: + operationId: bmc_canvases_save_as_template_create + description: Save the current Business Model Canvas as a reusable template. + summary: Save BMC as template + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this Business Model Canvas. + required: true + tags: + - BMC + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/BMCRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/BMCRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/BMCRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/BMC' + description: '' + /api/v1/bmc/canvases/{id}/save_to_gallery/: + post: + operationId: bmc_canvases_save_to_gallery_create + description: Render and save the Business Model Canvas to the media gallery. + summary: Save BMC to gallery + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this Business Model Canvas. + required: true + tags: + - BMC + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/BMCRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/BMCRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/BMCRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/BMC' + description: '' + /api/v1/bmc/templates/: + get: + operationId: bmc_templates_list + description: Retrieve a list of active Business Model Canvas templates. + summary: List BMC templates + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - BMC + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedBMCTemplateList' + description: '' + post: + operationId: bmc_templates_create + description: Create a new Business Model Canvas template. + summary: Create BMC template + tags: + - BMC + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/BMCTemplateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/BMCTemplateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/BMCTemplateRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/BMCTemplate' + description: '' + /api/v1/bmc/templates/{id}/: + get: + operationId: bmc_templates_retrieve + description: Retrieve detailed information about a specific BMC template. + summary: Get BMC template details + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this bmc template. + required: true + tags: + - BMC + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/BMCTemplate' + description: '' + put: + operationId: bmc_templates_update + description: Update all fields of a BMC template. + summary: Update BMC template + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this bmc template. + required: true + tags: + - BMC + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/BMCTemplateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/BMCTemplateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/BMCTemplateRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/BMCTemplate' + description: '' + patch: + operationId: bmc_templates_partial_update + description: Update specific fields of a BMC template. + summary: Partially update BMC template + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this bmc template. + required: true + tags: + - BMC + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedBMCTemplateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedBMCTemplateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedBMCTemplateRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/BMCTemplate' + description: '' + delete: + operationId: bmc_templates_destroy + description: Delete a BMC template. + summary: Delete BMC template + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this bmc template. + required: true + tags: + - BMC + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/bmc/tools/: + get: + operationId: bmc_tools_list + description: Retrieve a list of Business Model Canvas tools owned by the current + user. + summary: List BMC tools + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - BMC + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedBMCToolList' + description: '' + post: + operationId: bmc_tools_create + description: Create a new Business Model Canvas tool. + summary: Create BMC tool + tags: + - BMC + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/BMCToolRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/BMCToolRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/BMCToolRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/BMCTool' + description: '' + /api/v1/bmc/tools/{id}/: + get: + operationId: bmc_tools_retrieve + description: Retrieve detailed information about a specific BMC tool. + summary: Get BMC tool details + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this bmc tool. + required: true + tags: + - BMC + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/BMCTool' + description: '' + put: + operationId: bmc_tools_update + description: Update all fields of a BMC tool. + summary: Update BMC tool + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this bmc tool. + required: true + tags: + - BMC + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/BMCToolRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/BMCToolRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/BMCToolRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/BMCTool' + description: '' + patch: + operationId: bmc_tools_partial_update + description: Update specific fields of a BMC tool. + summary: Partially update BMC tool + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this bmc tool. + required: true + tags: + - BMC + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedBMCToolRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedBMCToolRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedBMCToolRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/BMCTool' + description: '' + delete: + operationId: bmc_tools_destroy + description: Delete a BMC tool and all associated canvases. + summary: Delete BMC tool + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this bmc tool. + required: true + tags: + - BMC + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/bmc/tools/{id}/canvases/: + get: + operationId: bmc_tools_canvases_retrieve + description: Retrieve all canvas versions associated with a specific BMC tool, + ordered by version descending. + summary: List canvases for BMC tool + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this bmc tool. + required: true + tags: + - BMC + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/BMCTool' + description: '' + /api/v1/bmc/tools/{id}/create_canvas/: + post: + operationId: bmc_tools_create_canvas_create + description: Create a new canvas version for a specific BMC tool. The version + number is automatically incremented. + summary: Create canvas for BMC tool + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this bmc tool. + required: true + tags: + - BMC + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/BMCToolRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/BMCToolRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/BMCToolRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/BMCTool' + description: '' + /api/v1/calendar/events/: + get: + operationId: calendar_events_retrieve + description: Returns calendar events for the authenticated user within a specified + date range + summary: List calendar events + parameters: + - in: query + name: end + schema: + type: string + description: End date (ISO format) + - in: query + name: start + schema: + type: string + description: Start date (ISO format) + tags: + - calendar + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + description: List of calendar events + /api/v1/core/api-keys/: + get: + operationId: core_api_keys_list + description: Retrieve a list of API keys belonging to the current user. + summary: List user API keys + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - Core + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedAPIKeyList' + description: '' + /api/v1/core/api-keys/{id}/: + get: + operationId: core_api_keys_retrieve + description: Retrieve detailed information about a specific API key. + summary: Get API key details + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this api key. + required: true + tags: + - Core + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/APIKey' + description: '' + /api/v1/core/currencies/: + get: + operationId: core_currencies_list + description: Retrieve a list of all active currencies available in the system. + summary: List currencies + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - Core + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedCurrencyList' + description: '' + /api/v1/core/currencies/{code}/: + get: + operationId: core_currencies_retrieve + description: Retrieve detailed information about a specific currency. + summary: Get currency details + parameters: + - in: path + name: code + schema: + type: string + description: A unique value identifying this currency. + required: true + tags: + - Core + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Currency' + description: '' + /api/v1/core/divisions/: + get: + operationId: core_divisions_list + description: Retrieve a list of divisions with optional filtering by organization. + summary: List organization divisions + parameters: + - in: query + name: organization + schema: + type: integer + description: Filter divisions by organization ID + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - in: query + name: search + schema: + type: string + description: Search divisions by name or abbreviation + tags: + - Core + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedOrganizationDivisionList' + description: '' + post: + operationId: core_divisions_create + description: Create a new organizational division. + summary: Create new division + tags: + - Core + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/OrganizationDivisionRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/OrganizationDivisionRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/OrganizationDivisionRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/OrganizationDivision' + description: '' + /api/v1/core/divisions/{id}/: + get: + operationId: core_divisions_retrieve + description: Retrieve detailed information about a specific division including + hierarchy and counts. + summary: Get division details + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this Organization Division. + required: true + tags: + - Core + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/OrganizationDivision' + description: '' + put: + operationId: core_divisions_update + description: Update an existing division's information. + summary: Update division + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this Organization Division. + required: true + tags: + - Core + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/OrganizationDivisionRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/OrganizationDivisionRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/OrganizationDivisionRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/OrganizationDivision' + description: '' + patch: + operationId: core_divisions_partial_update + description: Partially update an existing division's information. + summary: Partially update division + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this Organization Division. + required: true + tags: + - Core + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedOrganizationDivisionRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedOrganizationDivisionRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedOrganizationDivisionRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/OrganizationDivision' + description: '' + delete: + operationId: core_divisions_destroy + description: Delete a division. Sub-divisions will have their parent cleared. + summary: Delete division + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this Organization Division. + required: true + tags: + - Core + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/core/divisions/{id}/sub_divisions/: + get: + operationId: core_divisions_sub_divisions_list + description: Retrieve all sub-divisions of a division recursively. + summary: Get sub-divisions + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this Organization Division. + required: true + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: search + required: false + in: query + description: A search term. + schema: + type: string + tags: + - Core + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedOrganizationDivisionList' + description: '' + /api/v1/core/fiscal-years/: + get: + operationId: core_fiscal_years_list + description: Retrieve a list of all fiscal years configured in the system. + summary: List fiscal years + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - Core + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedFiscalYearList' + description: '' + /api/v1/core/fiscal-years/{id}/: + get: + operationId: core_fiscal_years_retrieve + description: Retrieve detailed information about a specific fiscal year including + quarters. + summary: Get fiscal year details + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this fiscal year. + required: true + tags: + - Core + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/FiscalYear' + description: '' + /api/v1/core/organizations/: + get: + operationId: core_organizations_list + description: Retrieve a list of organizations with optional search by name. + summary: List organizations + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - in: query + name: search + schema: + type: string + description: Search organizations by name + tags: + - Core + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedOrganizationList' + description: '' + /api/v1/core/organizations/{id}/: + get: + operationId: core_organizations_retrieve + description: Retrieve detailed information about a specific organization. + summary: Get organization details + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this organization. + required: true + tags: + - Core + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Organization' + description: '' + /api/v1/core/profile/: + get: + operationId: core_profile_retrieve + description: Retrieve the current user's profile settings including timezone, + date format, and theme preferences. + summary: Get user profile + tags: + - Core + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/UserProfile' + description: '' + put: + operationId: core_profile_update + description: Update the current user's profile settings. + summary: Update user profile + tags: + - Core + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UserProfileRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/UserProfileRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/UserProfileRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/UserProfile' + description: '' + patch: + operationId: core_profile_partial_update + description: Partially update the current user's profile settings. + summary: Partially update user profile + tags: + - Core + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedUserProfileRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedUserProfileRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedUserProfileRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/UserProfile' + description: '' + /api/v1/core/stats/: + get: + operationId: core_stats_retrieve + description: Get API usage statistics and metrics. + summary: API Statistics + tags: + - System + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + type: object + properties: + total_requests: + type: integer + active_users: + type: integer + endpoints_available: + type: integer + rate_limit_info: + type: object + properties: + limit: + type: integer + remaining: + type: integer + reset_time: + type: string + format: date-time + description: '' + /api/v1/deckcraft/decks/: + get: + operationId: deckcraft_decks_list + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - deckcraft + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedDeckList' + description: '' + post: + operationId: deckcraft_decks_create + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + tags: + - deckcraft + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DeckRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/DeckRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/DeckRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Deck' + description: '' + /api/v1/deckcraft/decks/{id}/: + get: + operationId: deckcraft_decks_retrieve + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this deck. + required: true + tags: + - deckcraft + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Deck' + description: '' + put: + operationId: deckcraft_decks_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this deck. + required: true + tags: + - deckcraft + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DeckRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/DeckRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/DeckRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Deck' + description: '' + patch: + operationId: deckcraft_decks_partial_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this deck. + required: true + tags: + - deckcraft + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedDeckRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedDeckRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedDeckRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Deck' + description: '' + delete: + operationId: deckcraft_decks_destroy + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this deck. + required: true + tags: + - deckcraft + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/deckcraft/decks/{id}/download/: + get: + operationId: deckcraft_decks_download_retrieve + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this deck. + required: true + tags: + - deckcraft + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Deck' + description: '' + /api/v1/deckcraft/decks/{id}/generate/: + post: + operationId: deckcraft_decks_generate_create + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this deck. + required: true + tags: + - deckcraft + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DeckRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/DeckRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/DeckRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Deck' + description: '' + /api/v1/deckcraft/presentations/: + get: + operationId: deckcraft_presentations_list + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - deckcraft + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedPresentationList' + description: '' + post: + operationId: deckcraft_presentations_create + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + tags: + - deckcraft + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PresentationRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PresentationRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PresentationRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Presentation' + description: '' + /api/v1/deckcraft/presentations/{id}/: + get: + operationId: deckcraft_presentations_retrieve + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this presentation. + required: true + tags: + - deckcraft + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Presentation' + description: '' + put: + operationId: deckcraft_presentations_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this presentation. + required: true + tags: + - deckcraft + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PresentationRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PresentationRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PresentationRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Presentation' + description: '' + patch: + operationId: deckcraft_presentations_partial_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this presentation. + required: true + tags: + - deckcraft + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedPresentationRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedPresentationRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedPresentationRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Presentation' + description: '' + delete: + operationId: deckcraft_presentations_destroy + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this presentation. + required: true + tags: + - deckcraft + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/deckcraft/slides/: + get: + operationId: deckcraft_slides_list + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - deckcraft + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedDeckSlideList' + description: '' + post: + operationId: deckcraft_slides_create + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + tags: + - deckcraft + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DeckSlideRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/DeckSlideRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/DeckSlideRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/DeckSlide' + description: '' + /api/v1/deckcraft/slides/{id}/: + get: + operationId: deckcraft_slides_retrieve + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this deck slide. + required: true + tags: + - deckcraft + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DeckSlide' + description: '' + put: + operationId: deckcraft_slides_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this deck slide. + required: true + tags: + - deckcraft + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DeckSlideRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/DeckSlideRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/DeckSlideRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DeckSlide' + description: '' + patch: + operationId: deckcraft_slides_partial_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this deck slide. + required: true + tags: + - deckcraft + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedDeckSlideRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedDeckSlideRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedDeckSlideRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DeckSlide' + description: '' + delete: + operationId: deckcraft_slides_destroy + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this deck slide. + required: true + tags: + - deckcraft + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/deckcraft/slides/reorder/: + post: + operationId: deckcraft_slides_reorder_create + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + tags: + - deckcraft + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DeckSlideRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/DeckSlideRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/DeckSlideRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DeckSlide' + description: '' + /api/v1/deckcraft/table-sources/: + get: + operationId: deckcraft_table_sources_list + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - deckcraft + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedTableSourceList' + description: '' + /api/v1/deckcraft/table-sources/{id}/: + get: + operationId: deckcraft_table_sources_retrieve + parameters: + - in: path + name: id + schema: + type: string + required: true + tags: + - deckcraft + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/TableSource' + description: '' + /api/v1/deckcraft/table-sources/{id}/data/: + get: + operationId: deckcraft_table_sources_data_retrieve + parameters: + - in: path + name: id + schema: + type: string + required: true + tags: + - deckcraft + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/TableSource' + description: '' + /api/v1/deckcraft/templates/: + get: + operationId: deckcraft_templates_list + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - deckcraft + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedPowerPointTemplateList' + description: '' + post: + operationId: deckcraft_templates_create + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + tags: + - deckcraft + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PowerPointTemplateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PowerPointTemplateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PowerPointTemplateRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/PowerPointTemplate' + description: '' + /api/v1/deckcraft/templates/{id}/: + get: + operationId: deckcraft_templates_retrieve + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this power point template. + required: true + tags: + - deckcraft + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PowerPointTemplate' + description: '' + put: + operationId: deckcraft_templates_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this power point template. + required: true + tags: + - deckcraft + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PowerPointTemplateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PowerPointTemplateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PowerPointTemplateRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PowerPointTemplate' + description: '' + patch: + operationId: deckcraft_templates_partial_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this power point template. + required: true + tags: + - deckcraft + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedPowerPointTemplateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedPowerPointTemplateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedPowerPointTemplateRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PowerPointTemplate' + description: '' + delete: + operationId: deckcraft_templates_destroy + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this power point template. + required: true + tags: + - deckcraft + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/deckcraft/templates/{id}/layouts/: + get: + operationId: deckcraft_templates_layouts_retrieve + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this power point template. + required: true + tags: + - deckcraft + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PowerPointTemplate' + description: '' + /api/v1/deckcraft/templates/{id}/refresh/: + post: + operationId: deckcraft_templates_refresh_create + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this power point template. + required: true + tags: + - deckcraft + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PowerPointTemplateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PowerPointTemplateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PowerPointTemplateRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PowerPointTemplate' + description: '' + /api/v1/engagement/engagements/: + get: + operationId: engagement_engagements_list + description: Retrieve a paginated list of client engagements with summary information. + summary: List engagements + parameters: + - in: query + name: ordering + schema: + type: string + description: 'Order by: name, start_date, end_date, status, created_date' + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - in: query + name: search + schema: + type: string + description: Search engagements by name, description, or client name + tags: + - Engagement + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEngagementSummaryList' + description: '' + post: + operationId: engagement_engagements_create + description: Create a new client engagement. + summary: Create engagement + tags: + - Engagement + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/EngagementRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/EngagementRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/EngagementRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Engagement' + description: '' + /api/v1/engagement/engagements/{id}/: + get: + operationId: engagement_engagements_retrieve + description: Retrieve detailed information about a specific engagement. + summary: Get engagement details + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this engagement. + required: true + tags: + - Engagement + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Engagement' + description: '' + put: + operationId: engagement_engagements_update + description: Update all fields of an engagement record. + summary: Update engagement + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this engagement. + required: true + tags: + - Engagement + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/EngagementRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/EngagementRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/EngagementRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Engagement' + description: '' + patch: + operationId: engagement_engagements_partial_update + description: Update specific fields of an engagement record. + summary: Partially update engagement + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this engagement. + required: true + tags: + - Engagement + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedEngagementRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedEngagementRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedEngagementRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Engagement' + description: '' + delete: + operationId: engagement_engagements_destroy + description: Delete an engagement record. + summary: Delete engagement + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this engagement. + required: true + tags: + - Engagement + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/engagement/engagements/{id}/summary/: + get: + operationId: engagement_engagements_summary_retrieve + description: Retrieve comprehensive engagement summary including interviews, + workshops, and statistics. + summary: Get engagement summary + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this engagement. + required: true + tags: + - Engagement + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + type: object + properties: + engagement: + type: object + description: Engagement details + interviews: + type: array + description: List of interviews + workshops: + type: array + description: List of workshops + statistics: + type: object + properties: + total_interviews: + type: integer + total_workshops: + type: integer + duration_days: + type: integer + description: '' + /api/v1/engagement/engagements/by_client/: + get: + operationId: engagement_engagements_by_client_retrieve + description: Filter engagements by a specific client. + summary: Get engagements by client + parameters: + - in: query + name: client_id + schema: + type: string + format: uuid + description: ID of the client to filter engagements by + required: true + tags: + - Engagement + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Engagement' + description: '' + /api/v1/engagement/engagements/by_status/: + get: + operationId: engagement_engagements_by_status_retrieve + description: Filter engagements by their current status. + summary: Get engagements by status + parameters: + - in: query + name: status + schema: + type: string + description: Status to filter engagements by + required: true + tags: + - Engagement + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Engagement' + description: '' + /api/v1/engagement/interviews/: + get: + operationId: engagement_interviews_list + description: Retrieve a list of interviews for engagements owned by the current + user. + summary: List interviews + parameters: + - in: query + name: ordering + schema: + type: string + description: 'Order by: name, date, time' + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - in: query + name: search + schema: + type: string + description: Search interviews by name, notes, or contact name + tags: + - Engagement + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedInterviewList' + description: '' + post: + operationId: engagement_interviews_create + description: Create a new interview within an engagement. + summary: Create interview + tags: + - Engagement + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/InterviewRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/InterviewRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/InterviewRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Interview' + description: '' + /api/v1/engagement/interviews/{id}/: + get: + operationId: engagement_interviews_retrieve + description: Retrieve detailed information about a specific interview. + summary: Get interview details + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this interview. + required: true + tags: + - Engagement + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Interview' + description: '' + put: + operationId: engagement_interviews_update + description: Update all fields of an interview record. + summary: Update interview + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this interview. + required: true + tags: + - Engagement + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/InterviewRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/InterviewRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/InterviewRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Interview' + description: '' + patch: + operationId: engagement_interviews_partial_update + description: Update specific fields of an interview record. + summary: Partially update interview + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this interview. + required: true + tags: + - Engagement + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedInterviewRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedInterviewRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedInterviewRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Interview' + description: '' + delete: + operationId: engagement_interviews_destroy + description: Delete an interview record. + summary: Delete interview + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this interview. + required: true + tags: + - Engagement + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/engagement/interviews/by_engagement/: + get: + operationId: engagement_interviews_by_engagement_retrieve + description: Retrieve all interviews for a specific engagement. + summary: Get interviews by engagement + parameters: + - in: query + name: engagement_id + schema: + type: string + format: uuid + description: ID of the engagement to filter interviews by + required: true + tags: + - Engagement + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Interview' + description: '' + /api/v1/engagement/workshops/: + get: + operationId: engagement_workshops_list + description: Retrieve a list of workshops for engagements owned by the current + user. + summary: List workshops + parameters: + - in: query + name: ordering + schema: + type: string + description: 'Order by: name, date, time, workshop_type' + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - in: query + name: search + schema: + type: string + description: Search workshops by name, notes, or workshop type + tags: + - Engagement + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedWorkshopList' + description: '' + post: + operationId: engagement_workshops_create + description: Create a new workshop within an engagement. + summary: Create workshop + tags: + - Engagement + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/WorkshopRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/WorkshopRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/WorkshopRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Workshop' + description: '' + /api/v1/engagement/workshops/{id}/: + get: + operationId: engagement_workshops_retrieve + description: Retrieve detailed information about a specific workshop. + summary: Get workshop details + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this workshop. + required: true + tags: + - Engagement + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Workshop' + description: '' + put: + operationId: engagement_workshops_update + description: Update all fields of a workshop record. + summary: Update workshop + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this workshop. + required: true + tags: + - Engagement + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/WorkshopRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/WorkshopRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/WorkshopRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Workshop' + description: '' + patch: + operationId: engagement_workshops_partial_update + description: Update specific fields of a workshop record. + summary: Partially update workshop + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this workshop. + required: true + tags: + - Engagement + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedWorkshopRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedWorkshopRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedWorkshopRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Workshop' + description: '' + delete: + operationId: engagement_workshops_destroy + description: Delete a workshop record. + summary: Delete workshop + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this workshop. + required: true + tags: + - Engagement + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/engagement/workshops/by_engagement/: + get: + operationId: engagement_workshops_by_engagement_retrieve + description: Retrieve all workshops for a specific engagement. + summary: Get workshops by engagement + parameters: + - in: query + name: engagement_id + schema: + type: string + format: uuid + description: ID of the engagement to filter workshops by + required: true + tags: + - Engagement + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Workshop' + description: '' + /api/v1/engagement/workshops/by_type/: + get: + operationId: engagement_workshops_by_type_retrieve + description: Filter workshops by workshop type. + summary: Get workshops by type + parameters: + - in: query + name: type + schema: + type: string + description: Workshop type to filter by + required: true + tags: + - Engagement + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Workshop' + description: '' + /api/v1/gallery/collections/: + get: + operationId: gallery_collections_list + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + tags: + - gallery + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedMediaCollectionList' + description: '' + post: + operationId: gallery_collections_create + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + tags: + - gallery + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/MediaCollectionRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/MediaCollectionRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/MediaCollectionRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/MediaCollection' + description: '' + /api/v1/gallery/collections/{id}/: + get: + operationId: gallery_collections_retrieve + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this media collection. + required: true + tags: + - gallery + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/MediaCollection' + description: '' + put: + operationId: gallery_collections_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this media collection. + required: true + tags: + - gallery + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/MediaCollectionRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/MediaCollectionRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/MediaCollectionRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/MediaCollection' + description: '' + patch: + operationId: gallery_collections_partial_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this media collection. + required: true + tags: + - gallery + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedMediaCollectionRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedMediaCollectionRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedMediaCollectionRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/MediaCollection' + description: '' + delete: + operationId: gallery_collections_destroy + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this media collection. + required: true + tags: + - gallery + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/gallery/media/: + get: + operationId: gallery_media_list + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - name: ordering + required: false + in: query + description: Which field to use when ordering the results. + schema: + type: string + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - name: search + required: false + in: query + description: A search term. + schema: + type: string + tags: + - gallery + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedMediaItemList' + description: '' + post: + operationId: gallery_media_create + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + tags: + - gallery + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/MediaItemRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/MediaItemRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/MediaItemRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/MediaItem' + description: '' + /api/v1/gallery/media/{id}/: + get: + operationId: gallery_media_retrieve + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this media item. + required: true + tags: + - gallery + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/MediaItem' + description: '' + put: + operationId: gallery_media_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this media item. + required: true + tags: + - gallery + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/MediaItemRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/MediaItemRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/MediaItemRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/MediaItem' + description: '' + patch: + operationId: gallery_media_partial_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this media item. + required: true + tags: + - gallery + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedMediaItemRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedMediaItemRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedMediaItemRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/MediaItem' + description: '' + delete: + operationId: gallery_media_destroy + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this media item. + required: true + tags: + - gallery + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/gallery/media/by_type/: + get: + operationId: gallery_media_by_type_retrieve + description: Get media items filtered by type + tags: + - gallery + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + description: No response body + /api/v1/gallery/media/search/: + get: + operationId: gallery_media_search_retrieve + description: Search for media items with optional filtering + summary: Search media items + parameters: + - in: query + name: limit + schema: + type: integer + description: Maximum number of results + - in: query + name: q + schema: + type: string + description: Search query + - in: query + name: type + schema: + type: string + description: Filter by media type + tags: + - gallery + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + description: Search results + /api/v1/gallery/search/: + get: + operationId: gallery_search_retrieve + description: Search for media items with optional filtering + summary: Search media items + parameters: + - in: query + name: limit + schema: + type: integer + description: Maximum number of results + - in: query + name: q + schema: + type: string + description: Search query + - in: query + name: type + schema: + type: string + description: Filter by media type + tags: + - gallery + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + description: Search results + /api/v1/gallery/tags/: + get: + operationId: gallery_tags_list + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + tags: + - gallery + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedTagList' + description: '' + post: + operationId: gallery_tags_create + tags: + - gallery + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TagRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/TagRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/TagRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Tag' + description: '' + /api/v1/gallery/tags/{id}/: + get: + operationId: gallery_tags_retrieve + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this tag. + required: true + tags: + - gallery + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Tag' + description: '' + put: + operationId: gallery_tags_update + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this tag. + required: true + tags: + - gallery + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TagRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/TagRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/TagRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Tag' + description: '' + patch: + operationId: gallery_tags_partial_update + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this tag. + required: true + tags: + - gallery + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedTagRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedTagRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedTagRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Tag' + description: '' + delete: + operationId: gallery_tags_destroy + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this tag. + required: true + tags: + - gallery + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/location-tool/location-sets/{location_set_pk}/csv/: + get: + operationId: location_tool_location_sets_csv_retrieve + description: Export all locations in a location set to CSV format. + summary: Export locations to CSV + parameters: + - in: path + name: location_set_pk + schema: + type: integer + required: true + tags: + - CSV Operations + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + type: string + format: binary + description: '' + post: + operationId: location_tool_location_sets_csv_create + description: Import location data from a CSV file with validation and error + reporting. + summary: Import locations from CSV + parameters: + - in: path + name: location_set_pk + schema: + type: integer + required: true + tags: + - CSV Operations + requestBody: + content: + multipart/form-data: + schema: + $ref: '#/components/schemas/CSVImportRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/CSVImportRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + type: object + description: '' + /api/v1/location-tool/table-sources/: + get: + operationId: location_tool_table_sources_list + description: Retrieve table source configurations for location tools. + summary: List table sources + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - Table Sources + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedLocationTableSourceList' + description: '' + post: + operationId: location_tool_table_sources_create + description: Create a new table source configuration for a location tool. + summary: Create table source + tags: + - Table Sources + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/LocationTableSourceRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/LocationTableSourceRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/LocationTableSourceRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/LocationTableSource' + description: '' + /api/v1/location-tool/table-sources/{id}/: + get: + operationId: location_tool_table_sources_retrieve + description: Retrieve detailed information about a table source configuration. + summary: Get table source details + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this location table source. + required: true + tags: + - Table Sources + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/LocationTableSource' + description: '' + put: + operationId: location_tool_table_sources_update + description: Update an existing table source configuration. + summary: Update table source + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this location table source. + required: true + tags: + - Table Sources + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/LocationTableSourceRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/LocationTableSourceRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/LocationTableSourceRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/LocationTableSource' + description: '' + patch: + operationId: location_tool_table_sources_partial_update + description: |- + Location Table Source management endpoints. + + Provides CRUD access to table source configurations including: + - Field selection settings + - Schema generation + - Data access for other tools + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this location table source. + required: true + tags: + - location-tool + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedLocationTableSourceRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedLocationTableSourceRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedLocationTableSourceRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/LocationTableSource' + description: '' + delete: + operationId: location_tool_table_sources_destroy + description: Delete a table source configuration. + summary: Delete table source + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this location table source. + required: true + tags: + - Table Sources + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/location-tool/table-sources/{id}/data/: + get: + operationId: location_tool_table_sources_data_retrieve + description: Retrieve the actual data from this table source. + summary: Get table source data + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this location table source. + required: true + tags: + - Table Sources + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + type: array + items: + type: object + description: '' + /api/v1/location-tool/tools/: + get: + operationId: location_tool_tools_list + description: Retrieve a list of client location tools owned by the current user. + summary: List client location tools + parameters: + - name: ordering + required: false + in: query + description: Which field to use when ordering the results. + schema: + type: string + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: search + required: false + in: query + description: A search term. + schema: + type: string + tags: + - Location Tools + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedClientLocationToolList' + description: '' + post: + operationId: location_tool_tools_create + description: Create a new client location tool. + summary: Create location tool + tags: + - Location Tools + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ClientLocationToolRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/ClientLocationToolRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/ClientLocationToolRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/ClientLocationTool' + description: '' + /api/v1/location-tool/tools/{id}/: + get: + operationId: location_tool_tools_retrieve + description: Retrieve detailed information about a specific client location + tool. + summary: Get location tool details + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this Client Location Tool. + required: true + tags: + - Location Tools + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/ClientLocationTool' + description: '' + put: + operationId: location_tool_tools_update + description: Update an existing client location tool. + summary: Update location tool + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this Client Location Tool. + required: true + tags: + - Location Tools + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ClientLocationToolRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/ClientLocationToolRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/ClientLocationToolRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/ClientLocationTool' + description: '' + patch: + operationId: location_tool_tools_partial_update + description: Partially update an existing client location tool. + summary: Partially update location tool + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this Client Location Tool. + required: true + tags: + - Location Tools + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedClientLocationToolRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedClientLocationToolRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedClientLocationToolRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/ClientLocationTool' + description: '' + delete: + operationId: location_tool_tools_destroy + description: Delete a client location tool and all associated data. + summary: Delete location tool + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this Client Location Tool. + required: true + tags: + - Location Tools + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/location-tool/tools/{id}/create_version/: + post: + operationId: location_tool_tools_create_version_create + description: "\n Create a new version of the location tool with automatic + version numbering.\n \n This endpoint implements automatic version + numbering where:\n - Version numbers are automatically assigned as + sequential integers (1, 2, 3, etc.)\n - No version number should be + provided in the request\n - The system finds the highest existing version + and increments by 1\n - All data from the current version is copied + to the new version\n - The tool's current_version is updated to the + new version\n \n The response includes the automatically assigned + version number.\n \n Concurrent Safety:\n Multiple simultaneous + requests are handled safely using database constraints.\n If a conflict + occurs, an appropriate error message is returned.\n " + summary: Create new version + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this Client Location Tool. + required: true + tags: + - Location Tools + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/LocationSet' + description: '' + '400': + content: + application/json: + schema: + type: object + properties: + error: + type: string + example: Version creation failed due to duplicate version number. + This may be caused by concurrent version creation attempts. + description: '' + /api/v1/location-tool/tools/{id}/switch_version/: + post: + operationId: location_tool_tools_switch_version_create + description: "\n Switch the current active version of the location tool.\n + \ \n This endpoint allows switching between existing versions + of a location tool.\n The version parameter must be an integer corresponding + to an existing version.\n \n Version numbers are sequential + integers (1, 2, 3, etc.) that were automatically\n assigned when the + versions were created.\n " + summary: Switch version + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this Client Location Tool. + required: true + - in: query + name: version + schema: + type: integer + description: Version number to switch to (integer, e.g., 1, 2, 3) + required: true + tags: + - Location Tools + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ClientLocationToolRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/ClientLocationToolRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/ClientLocationToolRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/ClientLocationTool' + description: '' + '400': + content: + application/json: + schema: + type: object + properties: + error: + type: string + example: Version must be a valid integer + description: '' + '404': + content: + application/json: + schema: + type: object + properties: + error: + type: string + example: Version 5 does not exist + description: '' + /api/v1/location-tool/tools/{tool_pk}/location-sets/: + get: + operationId: location_tool_tools_location_sets_list + description: Retrieve location sets for a specific tool. + summary: List location sets + parameters: + - name: ordering + required: false + in: query + description: Which field to use when ordering the results. + schema: + type: string + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - in: path + name: tool_pk + schema: + type: string + required: true + tags: + - Location Sets + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedLocationSetList' + description: '' + post: + operationId: location_tool_tools_location_sets_create + description: Create a new location set for a tool. + summary: Create location set + parameters: + - in: path + name: tool_pk + schema: + type: string + required: true + tags: + - Location Sets + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/LocationSetRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/LocationSetRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/LocationSetRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/LocationSet' + description: '' + /api/v1/location-tool/tools/{tool_pk}/location-sets/{location_set_pk}/custom-fields/: + get: + operationId: location_tool_tools_location_sets_custom_fields_list + description: Retrieve custom field definitions for a location set. + summary: List custom field definitions + parameters: + - in: path + name: location_set_pk + schema: + type: integer + required: true + - name: ordering + required: false + in: query + description: Which field to use when ordering the results. + schema: + type: string + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - in: path + name: tool_pk + schema: + type: string + required: true + tags: + - Custom Fields + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedCustomFieldDefinitionList' + description: '' + post: + operationId: location_tool_tools_location_sets_custom_fields_create + description: Create a new custom field definition for a location set. + summary: Create custom field definition + parameters: + - in: path + name: location_set_pk + schema: + type: integer + required: true + - in: path + name: tool_pk + schema: + type: string + required: true + tags: + - Custom Fields + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CustomFieldDefinitionRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/CustomFieldDefinitionRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/CustomFieldDefinitionRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/CustomFieldDefinition' + description: '' + /api/v1/location-tool/tools/{tool_pk}/location-sets/{location_set_pk}/custom-fields/{id}/: + get: + operationId: location_tool_tools_location_sets_custom_fields_retrieve + description: Retrieve details about a specific custom field definition. + summary: Get custom field definition + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this custom field definition. + required: true + - in: path + name: location_set_pk + schema: + type: integer + required: true + - in: path + name: tool_pk + schema: + type: string + required: true + tags: + - Custom Fields + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CustomFieldDefinition' + description: '' + put: + operationId: location_tool_tools_location_sets_custom_fields_update + description: Update an existing custom field definition. + summary: Update custom field definition + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this custom field definition. + required: true + - in: path + name: location_set_pk + schema: + type: integer + required: true + - in: path + name: tool_pk + schema: + type: string + required: true + tags: + - Custom Fields + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CustomFieldDefinitionRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/CustomFieldDefinitionRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/CustomFieldDefinitionRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CustomFieldDefinition' + description: '' + patch: + operationId: location_tool_tools_location_sets_custom_fields_partial_update + description: |- + Custom Field Definition management endpoints. + + Provides CRUD access to custom field definitions including: + - Field type configuration + - Validation rules + - Default values and ordering + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this custom field definition. + required: true + - in: path + name: location_set_pk + schema: + type: integer + required: true + - in: path + name: tool_pk + schema: + type: string + required: true + tags: + - location-tool + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedCustomFieldDefinitionRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedCustomFieldDefinitionRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedCustomFieldDefinitionRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CustomFieldDefinition' + description: '' + delete: + operationId: location_tool_tools_location_sets_custom_fields_destroy + description: Delete a custom field definition and all associated values. + summary: Delete custom field definition + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this custom field definition. + required: true + - in: path + name: location_set_pk + schema: + type: integer + required: true + - in: path + name: tool_pk + schema: + type: string + required: true + tags: + - Custom Fields + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/location-tool/tools/{tool_pk}/location-sets/{location_set_pk}/locations/: + get: + operationId: location_tool_tools_location_sets_locations_list + description: Retrieve locations in a specific location set with filtering and + search. + summary: List locations + parameters: + - in: query + name: client + schema: + type: integer + description: Filter by client ID + - in: query + name: country + schema: + type: string + description: Filter by country + - in: query + name: division + schema: + type: integer + description: Filter by division ID + - in: path + name: location_set_pk + schema: + type: integer + required: true + - name: ordering + required: false + in: query + description: Which field to use when ordering the results. + schema: + type: string + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - in: query + name: search + schema: + type: string + description: Search across site name and address + - in: query + name: state + schema: + type: string + description: Filter by state + - in: path + name: tool_pk + schema: + type: string + required: true + tags: + - Locations + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedLocationList' + description: '' + post: + operationId: location_tool_tools_location_sets_locations_create + description: Create a new location in the location set. + summary: Create location + parameters: + - in: path + name: location_set_pk + schema: + type: integer + required: true + - in: path + name: tool_pk + schema: + type: string + required: true + tags: + - Locations + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/LocationRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/LocationRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/LocationRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Location' + description: '' + /api/v1/location-tool/tools/{tool_pk}/location-sets/{location_set_pk}/locations/{id}/: + get: + operationId: location_tool_tools_location_sets_locations_retrieve + description: Retrieve detailed information about a specific location. + summary: Get location details + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this location. + required: true + - in: path + name: location_set_pk + schema: + type: integer + required: true + - in: path + name: tool_pk + schema: + type: string + required: true + tags: + - Locations + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Location' + description: '' + put: + operationId: location_tool_tools_location_sets_locations_update + description: Update an existing location. + summary: Update location + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this location. + required: true + - in: path + name: location_set_pk + schema: + type: integer + required: true + - in: path + name: tool_pk + schema: + type: string + required: true + tags: + - Locations + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/LocationRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/LocationRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/LocationRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Location' + description: '' + patch: + operationId: location_tool_tools_location_sets_locations_partial_update + description: Partially update an existing location. + summary: Partially update location + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this location. + required: true + - in: path + name: location_set_pk + schema: + type: integer + required: true + - in: path + name: tool_pk + schema: + type: string + required: true + tags: + - Locations + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedLocationRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedLocationRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedLocationRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Location' + description: '' + delete: + operationId: location_tool_tools_location_sets_locations_destroy + description: Delete a location and all associated custom field values. + summary: Delete location + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this location. + required: true + - in: path + name: location_set_pk + schema: + type: integer + required: true + - in: path + name: tool_pk + schema: + type: string + required: true + tags: + - Locations + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/location-tool/tools/{tool_pk}/location-sets/{location_set_pk}/locations/{id}/set_custom_field/: + post: + operationId: location_tool_tools_location_sets_locations_set_custom_field_create + description: Update a custom field value for this location. + summary: Update custom field value + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this location. + required: true + - in: path + name: location_set_pk + schema: + type: integer + required: true + - in: path + name: tool_pk + schema: + type: string + required: true + tags: + - Locations + requestBody: + content: + application/json: + schema: + type: object + properties: + field_name: + type: string + value: + type: string + required: + - field_name + - value + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Location' + description: '' + /api/v1/location-tool/tools/{tool_pk}/location-sets/{location_set_pk}/locations/bulk_operations/: + post: + operationId: location_tool_tools_location_sets_locations_bulk_operations_create + description: Perform bulk operations on multiple locations. + summary: Bulk operations + parameters: + - in: path + name: location_set_pk + schema: + type: integer + required: true + - in: path + name: tool_pk + schema: + type: string + required: true + tags: + - Locations + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/BulkLocationOperationRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/BulkLocationOperationRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/BulkLocationOperationRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + type: object + description: '' + /api/v1/location-tool/tools/{tool_pk}/location-sets/{id}/: + get: + operationId: location_tool_tools_location_sets_retrieve + description: Retrieve detailed information about a specific location set. + summary: Get location set details + parameters: + - in: path + name: id + schema: + type: string + required: true + - in: path + name: tool_pk + schema: + type: string + required: true + tags: + - Location Sets + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/LocationSet' + description: '' + put: + operationId: location_tool_tools_location_sets_update + description: Update an existing location set. + summary: Update location set + parameters: + - in: path + name: id + schema: + type: string + required: true + - in: path + name: tool_pk + schema: + type: string + required: true + tags: + - Location Sets + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/LocationSetRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/LocationSetRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/LocationSetRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/LocationSet' + description: '' + patch: + operationId: location_tool_tools_location_sets_partial_update + description: |- + Location Set management endpoints. + + Provides CRUD access to location sets including: + - Version management + - Custom field definitions + - Location aggregations + parameters: + - in: path + name: id + schema: + type: string + required: true + - in: path + name: tool_pk + schema: + type: string + required: true + tags: + - location-tool + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedLocationSetRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedLocationSetRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedLocationSetRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/LocationSet' + description: '' + delete: + operationId: location_tool_tools_location_sets_destroy + description: Delete a location set and all associated locations. + summary: Delete location set + parameters: + - in: path + name: id + schema: + type: string + required: true + - in: path + name: tool_pk + schema: + type: string + required: true + tags: + - Location Sets + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/orbit/calendar-events/: + get: + operationId: orbit_calendar_events_list + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedCalendarEventList' + description: '' + post: + operationId: orbit_calendar_events_create + tags: + - orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CalendarEventRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/CalendarEventRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/CalendarEventRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/CalendarEvent' + description: '' + /api/v1/orbit/calendar-events/{id}/: + get: + operationId: orbit_calendar_events_retrieve + parameters: + - in: path + name: id + schema: + type: string + required: true + tags: + - orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CalendarEvent' + description: '' + put: + operationId: orbit_calendar_events_update + parameters: + - in: path + name: id + schema: + type: string + required: true + tags: + - orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CalendarEventRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/CalendarEventRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/CalendarEventRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CalendarEvent' + description: '' + patch: + operationId: orbit_calendar_events_partial_update + parameters: + - in: path + name: id + schema: + type: string + required: true + tags: + - orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedCalendarEventRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedCalendarEventRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedCalendarEventRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CalendarEvent' + description: '' + delete: + operationId: orbit_calendar_events_destroy + parameters: + - in: path + name: id + schema: + type: string + required: true + tags: + - orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/orbit/clients/: + get: + operationId: orbit_clients_list + description: Retrieve a paginated list of clients owned by the current user. + summary: List clients + parameters: + - in: query + name: ordering + schema: + type: string + description: 'Order results by: name, created_date' + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - in: query + name: search + schema: + type: string + description: Search clients by name, legal name, or overview + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedClientList' + description: '' + post: + operationId: orbit_clients_create + description: Create a new client record. + summary: Create client + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ClientRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/ClientRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/ClientRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Client' + description: '' + /api/v1/orbit/clients/{id}/: + get: + operationId: orbit_clients_retrieve + description: Retrieve detailed information about a specific client. + summary: Get client details + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this client. + required: true + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Client' + description: '' + put: + operationId: orbit_clients_update + description: Update all fields of a client record. + summary: Update client + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this client. + required: true + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ClientRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/ClientRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/ClientRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Client' + description: '' + patch: + operationId: orbit_clients_partial_update + description: Update specific fields of a client record. + summary: Partially update client + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this client. + required: true + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedClientRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedClientRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedClientRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Client' + description: '' + delete: + operationId: orbit_clients_destroy + description: Delete a client record. + summary: Delete client + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this client. + required: true + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/orbit/contacts/: + get: + operationId: orbit_contacts_list + description: Retrieve a paginated list of contacts from user's clients and vendors. + summary: List contacts + parameters: + - name: ordering + required: false + in: query + description: Which field to use when ordering the results. + schema: + type: string + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - in: query + name: search + schema: + type: string + description: Search contacts by name, job title, or email + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedContactList' + description: '' + post: + operationId: orbit_contacts_create + description: Create a new contact record. + summary: Create contact + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ContactRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/ContactRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/ContactRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Contact' + description: '' + /api/v1/orbit/contacts/{id}/: + get: + operationId: orbit_contacts_retrieve + description: Retrieve detailed information about a specific contact. + summary: Get contact details + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this contact. + required: true + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Contact' + description: '' + put: + operationId: orbit_contacts_update + description: Update all fields of a contact record. + summary: Update contact + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this contact. + required: true + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ContactRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/ContactRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/ContactRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Contact' + description: '' + patch: + operationId: orbit_contacts_partial_update + description: Update specific fields of a contact record. + summary: Partially update contact + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this contact. + required: true + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedContactRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedContactRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedContactRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Contact' + description: '' + delete: + operationId: orbit_contacts_destroy + description: Delete a contact record. + summary: Delete contact + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this contact. + required: true + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/orbit/contacts/search/: + get: + operationId: orbit_contacts_search_retrieve + description: Advanced contact search with organization type filtering. + summary: Search contacts + parameters: + - in: query + name: organization_type + schema: + type: string + description: 'Filter by organization type: client or vendor' + - in: query + name: search_term + schema: + type: string + description: Search term for name or job title + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Contact' + description: '' + /api/v1/orbit/digital-contact-types/: + get: + operationId: orbit_digital_contact_types_list + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedDigitalContactTypeList' + description: '' + /api/v1/orbit/digital-contact-types/{id}/: + get: + operationId: orbit_digital_contact_types_retrieve + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this digital contact type. + required: true + tags: + - orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DigitalContactType' + description: '' + /api/v1/orbit/digital-contacts/: + get: + operationId: orbit_digital_contacts_list + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedDigitalContactList' + description: '' + post: + operationId: orbit_digital_contacts_create + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + tags: + - orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DigitalContactRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/DigitalContactRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/DigitalContactRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/DigitalContact' + description: '' + /api/v1/orbit/digital-contacts/{id}/: + get: + operationId: orbit_digital_contacts_retrieve + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this digital contact. + required: true + tags: + - orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DigitalContact' + description: '' + put: + operationId: orbit_digital_contacts_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this digital contact. + required: true + tags: + - orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DigitalContactRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/DigitalContactRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/DigitalContactRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DigitalContact' + description: '' + patch: + operationId: orbit_digital_contacts_partial_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this digital contact. + required: true + tags: + - orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedDigitalContactRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedDigitalContactRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedDigitalContactRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DigitalContact' + description: '' + delete: + operationId: orbit_digital_contacts_destroy + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this digital contact. + required: true + tags: + - orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/orbit/opportunities/: + get: + operationId: orbit_opportunities_list + description: Retrieve a paginated list of sales opportunities. + summary: List opportunities + parameters: + - in: query + name: ordering + schema: + type: string + description: 'Order by: name, expected_close_date, value' + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - in: query + name: search + schema: + type: string + description: Search opportunities by name or description + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedOpportunityList' + description: '' + post: + operationId: orbit_opportunities_create + description: Create a new sales opportunity. + summary: Create opportunity + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/OpportunityRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/OpportunityRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/OpportunityRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Opportunity' + description: '' + /api/v1/orbit/opportunities/{id}/: + get: + operationId: orbit_opportunities_retrieve + description: Retrieve detailed information about a specific opportunity. + summary: Get opportunity details + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this opportunity. + required: true + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Opportunity' + description: '' + put: + operationId: orbit_opportunities_update + description: Update all fields of an opportunity record. + summary: Update opportunity + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this opportunity. + required: true + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/OpportunityRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/OpportunityRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/OpportunityRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Opportunity' + description: '' + patch: + operationId: orbit_opportunities_partial_update + description: Update specific fields of an opportunity record. + summary: Partially update opportunity + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this opportunity. + required: true + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedOpportunityRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedOpportunityRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedOpportunityRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Opportunity' + description: '' + delete: + operationId: orbit_opportunities_destroy + description: Delete an opportunity record. + summary: Delete opportunity + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this opportunity. + required: true + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/orbit/opportunities/by_status/: + get: + operationId: orbit_opportunities_by_status_retrieve + description: Retrieve opportunities filtered by status and optionally by client. + summary: Filter opportunities by status + parameters: + - in: query + name: client_id + schema: + type: integer + description: Filter by specific client ID + - in: query + name: status + schema: + type: string + description: 'Filter by opportunity status: active, won, lost, dropped' + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Opportunity' + description: '' + /api/v1/orbit/proposals/: + get: + operationId: orbit_proposals_list + description: Retrieve a paginated list of proposals for opportunities owned + by the current user. + summary: List proposals + parameters: + - in: query + name: opportunity_id + schema: + type: integer + description: Filter by opportunity ID + - in: query + name: ordering + schema: + type: string + description: 'Order by: name, due_date, submitted_date, created_date' + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - in: query + name: search + schema: + type: string + description: Search proposals by name + - in: query + name: status + schema: + type: string + description: 'Filter by status: Draft, In Review, Final' + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedProposalList' + description: '' + post: + operationId: orbit_proposals_create + description: Create a new proposal record. + summary: Create proposal + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ProposalRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/ProposalRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/ProposalRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Proposal' + description: '' + /api/v1/orbit/proposals/{id}/: + get: + operationId: orbit_proposals_retrieve + description: Retrieve detailed information about a specific proposal. + summary: Get proposal details + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this proposal. + required: true + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Proposal' + description: '' + put: + operationId: orbit_proposals_update + description: Update all fields of a proposal record. + summary: Update proposal + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this proposal. + required: true + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ProposalRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/ProposalRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/ProposalRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Proposal' + description: '' + patch: + operationId: orbit_proposals_partial_update + description: Update specific fields of a proposal record. + summary: Partially update proposal + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this proposal. + required: true + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedProposalRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedProposalRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedProposalRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Proposal' + description: '' + delete: + operationId: orbit_proposals_destroy + description: Delete a proposal record. + summary: Delete proposal + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this proposal. + required: true + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/orbit/stock/overview/{symbol}/: + get: + operationId: orbit_stock_overview_retrieve + description: Returns company overview data for a stock symbol + summary: Get company overview + parameters: + - in: path + name: symbol + schema: + type: string + description: Stock symbol + required: true + tags: + - stock + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + description: Company overview data + '403': + description: Access denied + '500': + description: Unable to fetch company data + /api/v1/orbit/stock/quote/{symbol}/: + get: + operationId: orbit_stock_quote_retrieve + description: Returns current stock quote data for a symbol + summary: Get stock quote + parameters: + - in: path + name: symbol + schema: + type: string + description: Stock symbol + required: true + tags: + - stock + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + description: Stock quote data + '403': + description: Access denied + '500': + description: Unable to fetch stock data + /api/v1/orbit/suggest-timezone/: + post: + operationId: orbit_suggest_timezone_create + description: Uses address components to suggest an appropriate timezone using + geocoding. + summary: Suggest timezone from address + tags: + - Orbit + requestBody: + content: + application/json: + schema: + type: object + properties: + street_address: + type: string + locality: + type: string + region: + type: string + postal_code: + type: string + country: + type: string + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + type: object + properties: + timezone: + type: string + success: + type: boolean + method: + type: string + description: '' + '400': + content: + application/json: + schema: + type: object + properties: + error: + type: string + success: + type: boolean + description: '' + /api/v1/orbit/vendor-solutions/: + get: + operationId: orbit_vendor_solutions_list + description: Retrieve a paginated list of vendor solutions. + summary: List vendor solutions + parameters: + - name: ordering + required: false + in: query + description: Which field to use when ordering the results. + schema: + type: string + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - in: query + name: search + schema: + type: string + description: Search solutions by name or description + - in: query + name: vendor + schema: + type: integer + description: Filter by vendor ID + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedVendorSolutionList' + description: '' + post: + operationId: orbit_vendor_solutions_create + description: Create a new vendor solution. + summary: Create vendor solution + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/VendorSolutionRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/VendorSolutionRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/VendorSolutionRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/VendorSolution' + description: '' + /api/v1/orbit/vendor-solutions/{id}/: + get: + operationId: orbit_vendor_solutions_retrieve + description: Retrieve detailed information about a specific vendor solution. + summary: Get vendor solution details + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this vendor solution. + required: true + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/VendorSolution' + description: '' + put: + operationId: orbit_vendor_solutions_update + description: Update all fields of a vendor solution record. + summary: Update vendor solution + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this vendor solution. + required: true + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/VendorSolutionRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/VendorSolutionRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/VendorSolutionRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/VendorSolution' + description: '' + patch: + operationId: orbit_vendor_solutions_partial_update + description: Update specific fields of a vendor solution record. + summary: Partially update vendor solution + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this vendor solution. + required: true + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedVendorSolutionRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedVendorSolutionRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedVendorSolutionRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/VendorSolution' + description: '' + delete: + operationId: orbit_vendor_solutions_destroy + description: Delete a vendor solution record. + summary: Delete vendor solution + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this vendor solution. + required: true + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/orbit/vendors/: + get: + operationId: orbit_vendors_list + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - name: ordering + required: false + in: query + description: Which field to use when ordering the results. + schema: + type: string + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: search + required: false + in: query + description: A search term. + schema: + type: string + tags: + - orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedVendorList' + description: '' + post: + operationId: orbit_vendors_create + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + tags: + - orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/VendorRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/VendorRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/VendorRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Vendor' + description: '' + /api/v1/orbit/vendors/{id}/: + get: + operationId: orbit_vendors_retrieve + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this vendor. + required: true + tags: + - orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Vendor' + description: '' + put: + operationId: orbit_vendors_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this vendor. + required: true + tags: + - orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/VendorRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/VendorRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/VendorRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Vendor' + description: '' + patch: + operationId: orbit_vendors_partial_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this vendor. + required: true + tags: + - orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedVendorRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedVendorRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedVendorRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Vendor' + description: '' + delete: + operationId: orbit_vendors_destroy + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this vendor. + required: true + tags: + - orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/pricing/line-items/: + get: + operationId: pricing_line_items_list + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - pricing + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedPriceLineItemList' + description: '' + post: + operationId: pricing_line_items_create + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + tags: + - pricing + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PriceLineItemRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PriceLineItemRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PriceLineItemRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/PriceLineItem' + description: '' + /api/v1/pricing/line-items/{id}/: + get: + operationId: pricing_line_items_retrieve + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this price line item. + required: true + tags: + - pricing + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PriceLineItem' + description: '' + put: + operationId: pricing_line_items_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this price line item. + required: true + tags: + - pricing + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PriceLineItemRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PriceLineItemRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PriceLineItemRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PriceLineItem' + description: '' + patch: + operationId: pricing_line_items_partial_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this price line item. + required: true + tags: + - pricing + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedPriceLineItemRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedPriceLineItemRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedPriceLineItemRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PriceLineItem' + description: '' + delete: + operationId: pricing_line_items_destroy + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this price line item. + required: true + tags: + - pricing + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/pricing/tools/: + get: + operationId: pricing_tools_list + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - pricing + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedPricingToolList' + description: '' + post: + operationId: pricing_tools_create + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + tags: + - pricing + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PricingToolRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PricingToolRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PricingToolRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/PricingTool' + description: '' + /api/v1/pricing/tools/{id}/: + get: + operationId: pricing_tools_retrieve + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this pricing tool. + required: true + tags: + - pricing + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PricingTool' + description: '' + put: + operationId: pricing_tools_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this pricing tool. + required: true + tags: + - pricing + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PricingToolRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PricingToolRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PricingToolRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PricingTool' + description: '' + patch: + operationId: pricing_tools_partial_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this pricing tool. + required: true + tags: + - pricing + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedPricingToolRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedPricingToolRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedPricingToolRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PricingTool' + description: '' + delete: + operationId: pricing_tools_destroy + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this pricing tool. + required: true + tags: + - pricing + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/pricing/tools/{id}/copy_version/: + post: + operationId: pricing_tools_copy_version_create + description: Copy an existing version + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this pricing tool. + required: true + tags: + - pricing + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PricingToolRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PricingToolRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PricingToolRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PricingTool' + description: '' + /api/v1/pricing/tools/{id}/versions/: + get: + operationId: pricing_tools_versions_retrieve + description: Get all price versions for this tool + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this pricing tool. + required: true + tags: + - pricing + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PricingTool' + description: '' + /api/v1/pricing/versions/: + get: + operationId: pricing_versions_list + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - pricing + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedPriceVersionList' + description: '' + post: + operationId: pricing_versions_create + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + tags: + - pricing + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PriceVersionRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PriceVersionRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PriceVersionRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/PriceVersion' + description: '' + /api/v1/pricing/versions/{id}/: + get: + operationId: pricing_versions_retrieve + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this price version. + required: true + tags: + - pricing + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PriceVersion' + description: '' + put: + operationId: pricing_versions_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this price version. + required: true + tags: + - pricing + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PriceVersionRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PriceVersionRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PriceVersionRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PriceVersion' + description: '' + patch: + operationId: pricing_versions_partial_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this price version. + required: true + tags: + - pricing + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedPriceVersionRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedPriceVersionRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedPriceVersionRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PriceVersion' + description: '' + delete: + operationId: pricing_versions_destroy + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this price version. + required: true + tags: + - pricing + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/pricing/versions/{id}/pricing_table/: + get: + operationId: pricing_versions_pricing_table_retrieve + description: Get formatted pricing table data + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this price version. + required: true + tags: + - pricing + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PriceVersion' + description: '' + /api/v1/raid/assumptions/: + get: + operationId: raid_assumptions_list + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - raid + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedAssumptionList' + description: '' + /api/v1/raid/assumptions/{id}/: + get: + operationId: raid_assumptions_retrieve + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this assumption. + required: true + tags: + - raid + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Assumption' + description: '' + /api/v1/raid/dependencies/: + get: + operationId: raid_dependencies_list + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - raid + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedDependencyList' + description: '' + /api/v1/raid/dependencies/{id}/: + get: + operationId: raid_dependencies_retrieve + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this dependency. + required: true + tags: + - raid + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Dependency' + description: '' + /api/v1/raid/issues/: + get: + operationId: raid_issues_list + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - raid + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedIssueList' + description: '' + /api/v1/raid/issues/{id}/: + get: + operationId: raid_issues_retrieve + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this issue. + required: true + tags: + - raid + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Issue' + description: '' + /api/v1/raid/risks/: + get: + operationId: raid_risks_list + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - raid + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedRiskList' + description: '' + /api/v1/raid/risks/{id}/: + get: + operationId: raid_risks_retrieve + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this risk. + required: true + tags: + - raid + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Risk' + description: '' + /api/v1/raid/tools/: + get: + operationId: raid_tools_list + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - raid + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedRAIDToolList' + description: '' + /api/v1/raid/tools/{id}/: + get: + operationId: raid_tools_retrieve + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this raid tool. + required: true + tags: + - raid + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/RAIDTool' + description: '' + /api/v1/raid/tools/{id}/table_data/: + get: + operationId: raid_tools_table_data_retrieve + description: Return RAID data formatted for table display + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this raid tool. + required: true + tags: + - raid + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/RAIDTool' + description: '' + /api/v1/requirements/requirements/: + get: + operationId: requirements_requirements_list + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - requirements + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedRequirementList' + description: '' + /api/v1/requirements/requirements/{id}/: + get: + operationId: requirements_requirements_retrieve + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this requirement. + required: true + tags: + - requirements + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Requirement' + description: '' + /api/v1/requirements/tools/: + get: + operationId: requirements_tools_list + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - requirements + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedRequirementsToolList' + description: '' + /api/v1/requirements/tools/{id}/: + get: + operationId: requirements_tools_retrieve + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this Requirements Tool. + required: true + tags: + - requirements + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/RequirementsTool' + description: '' + /api/v1/stakeholder/org-charts/: + get: + operationId: stakeholder_org_charts_list + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - stakeholder + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedOrgChartList' + description: '' + post: + operationId: stakeholder_org_charts_create + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + tags: + - stakeholder + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/OrgChartRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/OrgChartRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/OrgChartRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/OrgChart' + description: '' + /api/v1/stakeholder/org-charts/{id}/: + get: + operationId: stakeholder_org_charts_retrieve + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this org chart. + required: true + tags: + - stakeholder + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/OrgChart' + description: '' + put: + operationId: stakeholder_org_charts_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this org chart. + required: true + tags: + - stakeholder + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/OrgChartRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/OrgChartRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/OrgChartRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/OrgChart' + description: '' + patch: + operationId: stakeholder_org_charts_partial_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this org chart. + required: true + tags: + - stakeholder + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedOrgChartRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedOrgChartRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedOrgChartRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/OrgChart' + description: '' + delete: + operationId: stakeholder_org_charts_destroy + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this org chart. + required: true + tags: + - stakeholder + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/stakeholder/org-charts/{id}/levels/: + get: + operationId: stakeholder_org_charts_levels_retrieve + description: Get org chart data organized by levels + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this org chart. + required: true + tags: + - stakeholder + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/OrgChart' + description: '' + /api/v1/stakeholder/stakeholders/: + get: + operationId: stakeholder_stakeholders_list + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - stakeholder + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedStakeholderList' + description: '' + post: + operationId: stakeholder_stakeholders_create + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + tags: + - stakeholder + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/StakeholderRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/StakeholderRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/StakeholderRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Stakeholder' + description: '' + /api/v1/stakeholder/stakeholders/{id}/: + get: + operationId: stakeholder_stakeholders_retrieve + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this stakeholder. + required: true + tags: + - stakeholder + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Stakeholder' + description: '' + put: + operationId: stakeholder_stakeholders_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this stakeholder. + required: true + tags: + - stakeholder + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/StakeholderRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/StakeholderRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/StakeholderRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Stakeholder' + description: '' + patch: + operationId: stakeholder_stakeholders_partial_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this stakeholder. + required: true + tags: + - stakeholder + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedStakeholderRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedStakeholderRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedStakeholderRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Stakeholder' + description: '' + delete: + operationId: stakeholder_stakeholders_destroy + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this stakeholder. + required: true + tags: + - stakeholder + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/stakeholder/tools/: + get: + operationId: stakeholder_tools_list + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - stakeholder + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedStakeholderToolList' + description: '' + post: + operationId: stakeholder_tools_create + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + tags: + - stakeholder + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/StakeholderToolRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/StakeholderToolRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/StakeholderToolRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/StakeholderTool' + description: '' + /api/v1/stakeholder/tools/{id}/: + get: + operationId: stakeholder_tools_retrieve + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this stakeholder tool. + required: true + tags: + - stakeholder + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/StakeholderTool' + description: '' + put: + operationId: stakeholder_tools_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this stakeholder tool. + required: true + tags: + - stakeholder + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/StakeholderToolRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/StakeholderToolRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/StakeholderToolRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/StakeholderTool' + description: '' + patch: + operationId: stakeholder_tools_partial_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this stakeholder tool. + required: true + tags: + - stakeholder + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedStakeholderToolRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedStakeholderToolRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedStakeholderToolRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/StakeholderTool' + description: '' + delete: + operationId: stakeholder_tools_destroy + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this stakeholder tool. + required: true + tags: + - stakeholder + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/stakeholder/tools/{id}/power_interest_matrix/: + get: + operationId: stakeholder_tools_power_interest_matrix_retrieve + description: Get stakeholders organized by power/interest matrix + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this stakeholder tool. + required: true + tags: + - stakeholder + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/StakeholderTool' + description: '' + /api/v1/tei/reports/: + get: + operationId: tei_reports_list + description: CRUD for ``TEIReport``. Admin-only write side per spec §Permissions. + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - tei + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedTEIReportList' + description: '' + post: + operationId: tei_reports_create + description: CRUD for ``TEIReport``. Admin-only write side per spec §Permissions. + tags: + - tei + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TEIReportRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/TEIReportRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/TEIReportRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/TEIReport' + description: '' + /api/v1/tei/reports/{public_id}/: + get: + operationId: tei_reports_retrieve + description: CRUD for ``TEIReport``. Admin-only write side per spec §Permissions. + parameters: + - in: path + name: public_id + schema: + type: string + description: 12-character short UUID used in public API URLs. + required: true + tags: + - tei + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/TEIReport' + description: '' + put: + operationId: tei_reports_update + description: CRUD for ``TEIReport``. Admin-only write side per spec §Permissions. + parameters: + - in: path + name: public_id + schema: + type: string + description: 12-character short UUID used in public API URLs. + required: true + tags: + - tei + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TEIReportRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/TEIReportRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/TEIReportRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/TEIReport' + description: '' + patch: + operationId: tei_reports_partial_update + description: CRUD for ``TEIReport``. Admin-only write side per spec §Permissions. + parameters: + - in: path + name: public_id + schema: + type: string + description: 12-character short UUID used in public API URLs. + required: true + tags: + - tei + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedTEIReportRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedTEIReportRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedTEIReportRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/TEIReport' + description: '' + delete: + operationId: tei_reports_destroy + description: CRUD for ``TEIReport``. Admin-only write side per spec §Permissions. + parameters: + - in: path + name: public_id + schema: + type: string + description: 12-character short UUID used in public API URLs. + required: true + tags: + - tei + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/tei/reports/{report_public_id}/fields/: + get: + operationId: tei_reports_fields_list + description: Nested field CRUD under ``/reports/{public_id}/fields/``. + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - in: path + name: report_public_id + schema: + type: string + required: true + tags: + - tei + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedTEIReportFieldList' + description: '' + post: + operationId: tei_reports_fields_create + description: Nested field CRUD under ``/reports/{public_id}/fields/``. + parameters: + - in: path + name: report_public_id + schema: + type: string + required: true + tags: + - tei + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TEIReportFieldRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/TEIReportFieldRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/TEIReportFieldRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/TEIReportField' + description: '' + /api/v1/tei/reports/{report_public_id}/fields/{id}/: + get: + operationId: tei_reports_fields_retrieve + description: Nested field CRUD under ``/reports/{public_id}/fields/``. + parameters: + - in: path + name: id + schema: + type: integer + required: true + - in: path + name: report_public_id + schema: + type: string + required: true + tags: + - tei + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/TEIReportField' + description: '' + put: + operationId: tei_reports_fields_update + description: Nested field CRUD under ``/reports/{public_id}/fields/``. + parameters: + - in: path + name: id + schema: + type: integer + required: true + - in: path + name: report_public_id + schema: + type: string + required: true + tags: + - tei + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TEIReportFieldRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/TEIReportFieldRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/TEIReportFieldRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/TEIReportField' + description: '' + patch: + operationId: tei_reports_fields_partial_update + description: Nested field CRUD under ``/reports/{public_id}/fields/``. + parameters: + - in: path + name: id + schema: + type: integer + required: true + - in: path + name: report_public_id + schema: + type: string + required: true + tags: + - tei + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedTEIReportFieldRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedTEIReportFieldRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedTEIReportFieldRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/TEIReportField' + description: '' + delete: + operationId: tei_reports_fields_destroy + description: Nested field CRUD under ``/reports/{public_id}/fields/``. + parameters: + - in: path + name: id + schema: + type: integer + required: true + - in: path + name: report_public_id + schema: + type: string + required: true + tags: + - tei + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/tei/reports/{report_public_id}/fields/reorder/: + patch: + operationId: tei_reports_fields_reorder_partial_update + description: Bulk-reorder fields via ``PATCH .../fields/reorder/``. + parameters: + - in: path + name: report_public_id + schema: + type: string + required: true + tags: + - tei + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedTEIReportFieldRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedTEIReportFieldRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedTEIReportFieldRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/TEIReportField' + description: '' + /api/v1/tei/summary/: + get: + operationId: tei_summary_retrieve + description: GET ``/api/v1/tei/summary/`` — aggregate NPV across all tools. + tags: + - tei + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + description: No response body + /api/v1/tei/tools/: + get: + operationId: tei_tools_list + description: CRUD for ``TEITool`` with custom calculate / export / version actions. + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - tei + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedTEIToolList' + description: '' + post: + operationId: tei_tools_create + description: CRUD for ``TEITool`` with custom calculate / export / version actions. + tags: + - tei + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TEIToolCreateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/TEIToolCreateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/TEIToolCreateRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/TEIToolCreate' + description: '' + /api/v1/tei/tools/{public_id}/: + get: + operationId: tei_tools_retrieve + description: CRUD for ``TEITool`` with custom calculate / export / version actions. + parameters: + - in: path + name: public_id + schema: + type: string + description: 12-character short UUID used in public API URLs. + required: true + tags: + - tei + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/TEITool' + description: '' + put: + operationId: tei_tools_update + description: CRUD for ``TEITool`` with custom calculate / export / version actions. + parameters: + - in: path + name: public_id + schema: + type: string + description: 12-character short UUID used in public API URLs. + required: true + tags: + - tei + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TEIToolUpdateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/TEIToolUpdateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/TEIToolUpdateRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/TEIToolUpdate' + description: '' + patch: + operationId: tei_tools_partial_update + description: CRUD for ``TEITool`` with custom calculate / export / version actions. + parameters: + - in: path + name: public_id + schema: + type: string + description: 12-character short UUID used in public API URLs. + required: true + tags: + - tei + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedTEIToolUpdateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedTEIToolUpdateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedTEIToolUpdateRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/TEIToolUpdate' + description: '' + delete: + operationId: tei_tools_destroy + description: CRUD for ``TEITool`` with custom calculate / export / version actions. + parameters: + - in: path + name: public_id + schema: + type: string + description: 12-character short UUID used in public API URLs. + required: true + tags: + - tei + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/tei/tools/{public_id}/calculate/: + post: + operationId: tei_tools_calculate_create + description: Recalculate and persist the financial summary. + parameters: + - in: path + name: public_id + schema: + type: string + description: 12-character short UUID used in public API URLs. + required: true + tags: + - tei + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/TEITool' + description: '' + /api/v1/tei/tools/{public_id}/export/: + get: + operationId: tei_tools_export_retrieve + description: Return the LLM-ready export payload. + parameters: + - in: path + name: public_id + schema: + type: string + description: 12-character short UUID used in public API URLs. + required: true + tags: + - tei + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/TEITool' + description: '' + /api/v1/tei/tools/{public_id}/summary/: + get: + operationId: tei_tools_summary_retrieve + description: Return the stored summary (404 if never calculated). + parameters: + - in: path + name: public_id + schema: + type: string + description: 12-character short UUID used in public API URLs. + required: true + tags: + - tei + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/TEITool' + description: '' + /api/v1/tei/tools/{public_id}/values/: + get: + operationId: tei_tools_values_retrieve + description: GET current values / PUT bulk update. + parameters: + - in: path + name: public_id + schema: + type: string + description: 12-character short UUID used in public API URLs. + required: true + tags: + - tei + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/TEITool' + description: '' + put: + operationId: tei_tools_values_update + description: GET current values / PUT bulk update. + parameters: + - in: path + name: public_id + schema: + type: string + description: 12-character short UUID used in public API URLs. + required: true + tags: + - tei + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/TEITool' + description: '' + /api/v1/tei/tools/{public_id}/values/{field_key}/: + patch: + operationId: tei_tools_values_partial_update + description: PATCH one value row. + parameters: + - in: path + name: field_key + schema: + type: string + pattern: ^[\w-]+$ + required: true + - in: path + name: public_id + schema: + type: string + description: 12-character short UUID used in public API URLs. + required: true + tags: + - tei + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/TEITool' + description: '' + /api/v1/tei/tools/{public_id}/versions/: + get: + operationId: tei_tools_versions_retrieve + description: GET version list / POST save new version. + parameters: + - in: path + name: public_id + schema: + type: string + description: 12-character short UUID used in public API URLs. + required: true + tags: + - tei + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/TEITool' + description: '' + post: + operationId: tei_tools_versions_create + description: GET version list / POST save new version. + parameters: + - in: path + name: public_id + schema: + type: string + description: 12-character short UUID used in public API URLs. + required: true + tags: + - tei + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/TEITool' + description: '' + /api/v1/tei/tools/{public_id}/versions/{version_number}/: + get: + operationId: tei_tools_versions_retrieve_2 + description: Return the full snapshot for one version. + parameters: + - in: path + name: public_id + schema: + type: string + description: 12-character short UUID used in public API URLs. + required: true + - in: path + name: version_number + schema: + type: string + pattern: ^\d+$ + required: true + tags: + - tei + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/TEITool' + description: '' + /api/v1/timeline/milestones/: + get: + operationId: timeline_milestones_list + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - timeline + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedMilestoneList' + description: '' + post: + operationId: timeline_milestones_create + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + tags: + - timeline + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/MilestoneRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/MilestoneRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/MilestoneRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Milestone' + description: '' + /api/v1/timeline/milestones/{id}/: + get: + operationId: timeline_milestones_retrieve + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this milestone. + required: true + tags: + - timeline + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Milestone' + description: '' + put: + operationId: timeline_milestones_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this milestone. + required: true + tags: + - timeline + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/MilestoneRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/MilestoneRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/MilestoneRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Milestone' + description: '' + patch: + operationId: timeline_milestones_partial_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this milestone. + required: true + tags: + - timeline + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedMilestoneRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedMilestoneRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedMilestoneRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Milestone' + description: '' + delete: + operationId: timeline_milestones_destroy + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this milestone. + required: true + tags: + - timeline + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/timeline/milestones/{id}/mark_completed/: + post: + operationId: timeline_milestones_mark_completed_create + description: Mark milestone as completed + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this milestone. + required: true + tags: + - timeline + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/MilestoneRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/MilestoneRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/MilestoneRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Milestone' + description: '' + /api/v1/timeline/milestones/upcoming/: + get: + operationId: timeline_milestones_upcoming_retrieve + description: Get upcoming milestones + tags: + - timeline + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Milestone' + description: '' + /api/v1/timeline/stages/: + get: + operationId: timeline_stages_list + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - timeline + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedStageList' + description: '' + post: + operationId: timeline_stages_create + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + tags: + - timeline + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/StageRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/StageRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/StageRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Stage' + description: '' + /api/v1/timeline/stages/{id}/: + get: + operationId: timeline_stages_retrieve + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this stage. + required: true + tags: + - timeline + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Stage' + description: '' + put: + operationId: timeline_stages_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this stage. + required: true + tags: + - timeline + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/StageRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/StageRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/StageRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Stage' + description: '' + patch: + operationId: timeline_stages_partial_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this stage. + required: true + tags: + - timeline + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedStageRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedStageRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedStageRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Stage' + description: '' + delete: + operationId: timeline_stages_destroy + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this stage. + required: true + tags: + - timeline + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/timeline/timelines/: + get: + operationId: timeline_timelines_list + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - timeline + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedTimelineList' + description: '' + post: + operationId: timeline_timelines_create + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + tags: + - timeline + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TimelineRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/TimelineRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/TimelineRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Timeline' + description: '' + /api/v1/timeline/timelines/{id}/: + get: + operationId: timeline_timelines_retrieve + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this timeline. + required: true + tags: + - timeline + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Timeline' + description: '' + put: + operationId: timeline_timelines_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this timeline. + required: true + tags: + - timeline + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TimelineRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/TimelineRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/TimelineRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Timeline' + description: '' + patch: + operationId: timeline_timelines_partial_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this timeline. + required: true + tags: + - timeline + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedTimelineRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedTimelineRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedTimelineRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Timeline' + description: '' + delete: + operationId: timeline_timelines_destroy + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this timeline. + required: true + tags: + - timeline + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /api/v1/timeline/timelines/{id}/update_progress/: + post: + operationId: timeline_timelines_update_progress_create + description: Update timeline progress by updating milestone/stage statuses + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this timeline. + required: true + tags: + - timeline + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TimelineRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/TimelineRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/TimelineRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Timeline' + description: '' + /api/v1/timeline/tools/: + get: + operationId: timeline_tools_list + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - timeline + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedTimelineToolList' + description: '' + post: + operationId: timeline_tools_create + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + tags: + - timeline + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TimelineToolRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/TimelineToolRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/TimelineToolRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/TimelineTool' + description: '' + /api/v1/timeline/tools/{id}/: + get: + operationId: timeline_tools_retrieve + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this timeline tool. + required: true + tags: + - timeline + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/TimelineTool' + description: '' + put: + operationId: timeline_tools_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this timeline tool. + required: true + tags: + - timeline + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TimelineToolRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/TimelineToolRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/TimelineToolRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/TimelineTool' + description: '' + patch: + operationId: timeline_tools_partial_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this timeline tool. + required: true + tags: + - timeline + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedTimelineToolRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedTimelineToolRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedTimelineToolRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/TimelineTool' + description: '' + delete: + operationId: timeline_tools_destroy + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this timeline tool. + required: true + tags: + - timeline + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /deckcraft/api/decks/: + get: + operationId: deckcraft_api_decks_list + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - deckcraft + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedDeckList' + description: '' + post: + operationId: deckcraft_api_decks_create + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + tags: + - deckcraft + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DeckRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/DeckRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/DeckRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Deck' + description: '' + /deckcraft/api/decks/{id}/: + get: + operationId: deckcraft_api_decks_retrieve + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this deck. + required: true + tags: + - deckcraft + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Deck' + description: '' + put: + operationId: deckcraft_api_decks_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this deck. + required: true + tags: + - deckcraft + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DeckRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/DeckRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/DeckRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Deck' + description: '' + patch: + operationId: deckcraft_api_decks_partial_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this deck. + required: true + tags: + - deckcraft + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedDeckRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedDeckRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedDeckRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Deck' + description: '' + delete: + operationId: deckcraft_api_decks_destroy + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this deck. + required: true + tags: + - deckcraft + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /deckcraft/api/decks/{id}/download/: + get: + operationId: deckcraft_api_decks_download_retrieve + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this deck. + required: true + tags: + - deckcraft + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Deck' + description: '' + /deckcraft/api/decks/{id}/generate/: + post: + operationId: deckcraft_api_decks_generate_create + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this deck. + required: true + tags: + - deckcraft + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DeckRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/DeckRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/DeckRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Deck' + description: '' + /deckcraft/api/presentations/: + get: + operationId: deckcraft_api_presentations_list + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - deckcraft + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedPresentationList' + description: '' + post: + operationId: deckcraft_api_presentations_create + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + tags: + - deckcraft + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PresentationRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PresentationRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PresentationRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Presentation' + description: '' + /deckcraft/api/presentations/{id}/: + get: + operationId: deckcraft_api_presentations_retrieve + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this presentation. + required: true + tags: + - deckcraft + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Presentation' + description: '' + put: + operationId: deckcraft_api_presentations_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this presentation. + required: true + tags: + - deckcraft + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PresentationRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PresentationRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PresentationRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Presentation' + description: '' + patch: + operationId: deckcraft_api_presentations_partial_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this presentation. + required: true + tags: + - deckcraft + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedPresentationRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedPresentationRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedPresentationRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Presentation' + description: '' + delete: + operationId: deckcraft_api_presentations_destroy + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this presentation. + required: true + tags: + - deckcraft + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /deckcraft/api/slides/: + get: + operationId: deckcraft_api_slides_list + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - deckcraft + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedDeckSlideList' + description: '' + post: + operationId: deckcraft_api_slides_create + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + tags: + - deckcraft + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DeckSlideRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/DeckSlideRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/DeckSlideRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/DeckSlide' + description: '' + /deckcraft/api/slides/{id}/: + get: + operationId: deckcraft_api_slides_retrieve + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this deck slide. + required: true + tags: + - deckcraft + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DeckSlide' + description: '' + put: + operationId: deckcraft_api_slides_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this deck slide. + required: true + tags: + - deckcraft + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DeckSlideRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/DeckSlideRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/DeckSlideRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DeckSlide' + description: '' + patch: + operationId: deckcraft_api_slides_partial_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this deck slide. + required: true + tags: + - deckcraft + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedDeckSlideRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedDeckSlideRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedDeckSlideRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DeckSlide' + description: '' + delete: + operationId: deckcraft_api_slides_destroy + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this deck slide. + required: true + tags: + - deckcraft + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /deckcraft/api/slides/reorder/: + post: + operationId: deckcraft_api_slides_reorder_create + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + tags: + - deckcraft + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DeckSlideRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/DeckSlideRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/DeckSlideRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DeckSlide' + description: '' + /deckcraft/api/table-sources/: + get: + operationId: deckcraft_api_table_sources_list + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - deckcraft + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedTableSourceList' + description: '' + /deckcraft/api/table-sources/{id}/: + get: + operationId: deckcraft_api_table_sources_retrieve + parameters: + - in: path + name: id + schema: + type: string + required: true + tags: + - deckcraft + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/TableSource' + description: '' + /deckcraft/api/table-sources/{id}/data/: + get: + operationId: deckcraft_api_table_sources_data_retrieve + parameters: + - in: path + name: id + schema: + type: string + required: true + tags: + - deckcraft + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/TableSource' + description: '' + /deckcraft/api/templates/: + get: + operationId: deckcraft_api_templates_list + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - deckcraft + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedPowerPointTemplateList' + description: '' + post: + operationId: deckcraft_api_templates_create + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + tags: + - deckcraft + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PowerPointTemplateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PowerPointTemplateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PowerPointTemplateRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/PowerPointTemplate' + description: '' + /deckcraft/api/templates/{id}/: + get: + operationId: deckcraft_api_templates_retrieve + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this power point template. + required: true + tags: + - deckcraft + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PowerPointTemplate' + description: '' + put: + operationId: deckcraft_api_templates_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this power point template. + required: true + tags: + - deckcraft + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PowerPointTemplateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PowerPointTemplateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PowerPointTemplateRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PowerPointTemplate' + description: '' + patch: + operationId: deckcraft_api_templates_partial_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this power point template. + required: true + tags: + - deckcraft + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedPowerPointTemplateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedPowerPointTemplateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedPowerPointTemplateRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PowerPointTemplate' + description: '' + delete: + operationId: deckcraft_api_templates_destroy + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this power point template. + required: true + tags: + - deckcraft + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /deckcraft/api/templates/{id}/layouts/: + get: + operationId: deckcraft_api_templates_layouts_retrieve + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this power point template. + required: true + tags: + - deckcraft + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PowerPointTemplate' + description: '' + /deckcraft/api/templates/{id}/refresh/: + post: + operationId: deckcraft_api_templates_refresh_create + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this power point template. + required: true + tags: + - deckcraft + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PowerPointTemplateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PowerPointTemplateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PowerPointTemplateRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PowerPointTemplate' + description: '' + /orbit/api/calendar-events/: + get: + operationId: orbit_api_calendar_events_list + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedCalendarEventList' + description: '' + post: + operationId: orbit_api_calendar_events_create + tags: + - orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CalendarEventRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/CalendarEventRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/CalendarEventRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/CalendarEvent' + description: '' + /orbit/api/calendar-events/{id}/: + get: + operationId: orbit_api_calendar_events_retrieve + parameters: + - in: path + name: id + schema: + type: string + required: true + tags: + - orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CalendarEvent' + description: '' + put: + operationId: orbit_api_calendar_events_update + parameters: + - in: path + name: id + schema: + type: string + required: true + tags: + - orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CalendarEventRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/CalendarEventRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/CalendarEventRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CalendarEvent' + description: '' + patch: + operationId: orbit_api_calendar_events_partial_update + parameters: + - in: path + name: id + schema: + type: string + required: true + tags: + - orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedCalendarEventRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedCalendarEventRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedCalendarEventRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CalendarEvent' + description: '' + delete: + operationId: orbit_api_calendar_events_destroy + parameters: + - in: path + name: id + schema: + type: string + required: true + tags: + - orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /orbit/api/clients/: + get: + operationId: orbit_api_clients_list + description: Retrieve a paginated list of clients owned by the current user. + summary: List clients + parameters: + - in: query + name: ordering + schema: + type: string + description: 'Order results by: name, created_date' + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - in: query + name: search + schema: + type: string + description: Search clients by name, legal name, or overview + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedClientList' + description: '' + post: + operationId: orbit_api_clients_create + description: Create a new client record. + summary: Create client + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ClientRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/ClientRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/ClientRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Client' + description: '' + /orbit/api/clients/{id}/: + get: + operationId: orbit_api_clients_retrieve + description: Retrieve detailed information about a specific client. + summary: Get client details + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this client. + required: true + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Client' + description: '' + put: + operationId: orbit_api_clients_update + description: Update all fields of a client record. + summary: Update client + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this client. + required: true + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ClientRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/ClientRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/ClientRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Client' + description: '' + patch: + operationId: orbit_api_clients_partial_update + description: Update specific fields of a client record. + summary: Partially update client + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this client. + required: true + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedClientRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedClientRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedClientRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Client' + description: '' + delete: + operationId: orbit_api_clients_destroy + description: Delete a client record. + summary: Delete client + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this client. + required: true + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /orbit/api/contacts/: + get: + operationId: orbit_api_contacts_list + description: Retrieve a paginated list of contacts from user's clients and vendors. + summary: List contacts + parameters: + - name: ordering + required: false + in: query + description: Which field to use when ordering the results. + schema: + type: string + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - in: query + name: search + schema: + type: string + description: Search contacts by name, job title, or email + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedContactList' + description: '' + post: + operationId: orbit_api_contacts_create + description: Create a new contact record. + summary: Create contact + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ContactRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/ContactRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/ContactRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Contact' + description: '' + /orbit/api/contacts/{id}/: + get: + operationId: orbit_api_contacts_retrieve + description: Retrieve detailed information about a specific contact. + summary: Get contact details + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this contact. + required: true + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Contact' + description: '' + put: + operationId: orbit_api_contacts_update + description: Update all fields of a contact record. + summary: Update contact + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this contact. + required: true + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ContactRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/ContactRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/ContactRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Contact' + description: '' + patch: + operationId: orbit_api_contacts_partial_update + description: Update specific fields of a contact record. + summary: Partially update contact + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this contact. + required: true + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedContactRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedContactRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedContactRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Contact' + description: '' + delete: + operationId: orbit_api_contacts_destroy + description: Delete a contact record. + summary: Delete contact + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this contact. + required: true + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /orbit/api/contacts/search/: + get: + operationId: orbit_api_contacts_search_retrieve + description: Advanced contact search with organization type filtering. + summary: Search contacts + parameters: + - in: query + name: organization_type + schema: + type: string + description: 'Filter by organization type: client or vendor' + - in: query + name: search_term + schema: + type: string + description: Search term for name or job title + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Contact' + description: '' + /orbit/api/digital-contact-types/: + get: + operationId: orbit_api_digital_contact_types_list + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedDigitalContactTypeList' + description: '' + /orbit/api/digital-contact-types/{id}/: + get: + operationId: orbit_api_digital_contact_types_retrieve + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this digital contact type. + required: true + tags: + - orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DigitalContactType' + description: '' + /orbit/api/digital-contacts/: + get: + operationId: orbit_api_digital_contacts_list + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedDigitalContactList' + description: '' + post: + operationId: orbit_api_digital_contacts_create + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + tags: + - orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DigitalContactRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/DigitalContactRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/DigitalContactRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/DigitalContact' + description: '' + /orbit/api/digital-contacts/{id}/: + get: + operationId: orbit_api_digital_contacts_retrieve + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this digital contact. + required: true + tags: + - orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DigitalContact' + description: '' + put: + operationId: orbit_api_digital_contacts_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this digital contact. + required: true + tags: + - orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DigitalContactRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/DigitalContactRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/DigitalContactRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DigitalContact' + description: '' + patch: + operationId: orbit_api_digital_contacts_partial_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this digital contact. + required: true + tags: + - orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedDigitalContactRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedDigitalContactRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedDigitalContactRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DigitalContact' + description: '' + delete: + operationId: orbit_api_digital_contacts_destroy + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this digital contact. + required: true + tags: + - orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /orbit/api/opportunities/: + get: + operationId: orbit_api_opportunities_list + description: Retrieve a paginated list of sales opportunities. + summary: List opportunities + parameters: + - in: query + name: ordering + schema: + type: string + description: 'Order by: name, expected_close_date, value' + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - in: query + name: search + schema: + type: string + description: Search opportunities by name or description + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedOpportunityList' + description: '' + post: + operationId: orbit_api_opportunities_create + description: Create a new sales opportunity. + summary: Create opportunity + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/OpportunityRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/OpportunityRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/OpportunityRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Opportunity' + description: '' + /orbit/api/opportunities/{id}/: + get: + operationId: orbit_api_opportunities_retrieve + description: Retrieve detailed information about a specific opportunity. + summary: Get opportunity details + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this opportunity. + required: true + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Opportunity' + description: '' + put: + operationId: orbit_api_opportunities_update + description: Update all fields of an opportunity record. + summary: Update opportunity + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this opportunity. + required: true + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/OpportunityRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/OpportunityRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/OpportunityRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Opportunity' + description: '' + patch: + operationId: orbit_api_opportunities_partial_update + description: Update specific fields of an opportunity record. + summary: Partially update opportunity + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this opportunity. + required: true + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedOpportunityRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedOpportunityRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedOpportunityRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Opportunity' + description: '' + delete: + operationId: orbit_api_opportunities_destroy + description: Delete an opportunity record. + summary: Delete opportunity + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this opportunity. + required: true + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /orbit/api/opportunities/by_status/: + get: + operationId: orbit_api_opportunities_by_status_retrieve + description: Retrieve opportunities filtered by status and optionally by client. + summary: Filter opportunities by status + parameters: + - in: query + name: client_id + schema: + type: integer + description: Filter by specific client ID + - in: query + name: status + schema: + type: string + description: 'Filter by opportunity status: active, won, lost, dropped' + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Opportunity' + description: '' + /orbit/api/proposals/: + get: + operationId: orbit_api_proposals_list + description: Retrieve a paginated list of proposals for opportunities owned + by the current user. + summary: List proposals + parameters: + - in: query + name: opportunity_id + schema: + type: integer + description: Filter by opportunity ID + - in: query + name: ordering + schema: + type: string + description: 'Order by: name, due_date, submitted_date, created_date' + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - in: query + name: search + schema: + type: string + description: Search proposals by name + - in: query + name: status + schema: + type: string + description: 'Filter by status: Draft, In Review, Final' + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedProposalList' + description: '' + post: + operationId: orbit_api_proposals_create + description: Create a new proposal record. + summary: Create proposal + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ProposalRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/ProposalRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/ProposalRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Proposal' + description: '' + /orbit/api/proposals/{id}/: + get: + operationId: orbit_api_proposals_retrieve + description: Retrieve detailed information about a specific proposal. + summary: Get proposal details + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this proposal. + required: true + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Proposal' + description: '' + put: + operationId: orbit_api_proposals_update + description: Update all fields of a proposal record. + summary: Update proposal + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this proposal. + required: true + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ProposalRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/ProposalRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/ProposalRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Proposal' + description: '' + patch: + operationId: orbit_api_proposals_partial_update + description: Update specific fields of a proposal record. + summary: Partially update proposal + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this proposal. + required: true + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedProposalRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedProposalRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedProposalRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Proposal' + description: '' + delete: + operationId: orbit_api_proposals_destroy + description: Delete a proposal record. + summary: Delete proposal + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this proposal. + required: true + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /orbit/api/stock/overview/{symbol}/: + get: + operationId: orbit_api_stock_overview_retrieve + description: Returns company overview data for a stock symbol + summary: Get company overview + parameters: + - in: path + name: symbol + schema: + type: string + description: Stock symbol + required: true + tags: + - stock + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + description: Company overview data + '403': + description: Access denied + '500': + description: Unable to fetch company data + /orbit/api/stock/quote/{symbol}/: + get: + operationId: orbit_api_stock_quote_retrieve + description: Returns current stock quote data for a symbol + summary: Get stock quote + parameters: + - in: path + name: symbol + schema: + type: string + description: Stock symbol + required: true + tags: + - stock + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + description: Stock quote data + '403': + description: Access denied + '500': + description: Unable to fetch stock data + /orbit/api/suggest-timezone/: + post: + operationId: orbit_api_suggest_timezone_create + description: Uses address components to suggest an appropriate timezone using + geocoding. + summary: Suggest timezone from address + tags: + - Orbit + requestBody: + content: + application/json: + schema: + type: object + properties: + street_address: + type: string + locality: + type: string + region: + type: string + postal_code: + type: string + country: + type: string + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + type: object + properties: + timezone: + type: string + success: + type: boolean + method: + type: string + description: '' + '400': + content: + application/json: + schema: + type: object + properties: + error: + type: string + success: + type: boolean + description: '' + /orbit/api/v1/calendar-events/: + get: + operationId: orbit_api_v1_calendar_events_list + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedCalendarEventList' + description: '' + post: + operationId: orbit_api_v1_calendar_events_create + tags: + - orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CalendarEventRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/CalendarEventRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/CalendarEventRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/CalendarEvent' + description: '' + /orbit/api/v1/calendar-events/{id}/: + get: + operationId: orbit_api_v1_calendar_events_retrieve + parameters: + - in: path + name: id + schema: + type: string + required: true + tags: + - orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CalendarEvent' + description: '' + put: + operationId: orbit_api_v1_calendar_events_update + parameters: + - in: path + name: id + schema: + type: string + required: true + tags: + - orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CalendarEventRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/CalendarEventRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/CalendarEventRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CalendarEvent' + description: '' + patch: + operationId: orbit_api_v1_calendar_events_partial_update + parameters: + - in: path + name: id + schema: + type: string + required: true + tags: + - orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedCalendarEventRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedCalendarEventRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedCalendarEventRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CalendarEvent' + description: '' + delete: + operationId: orbit_api_v1_calendar_events_destroy + parameters: + - in: path + name: id + schema: + type: string + required: true + tags: + - orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /orbit/api/v1/clients/: + get: + operationId: orbit_api_v1_clients_list + description: Retrieve a paginated list of clients owned by the current user. + summary: List clients + parameters: + - in: query + name: ordering + schema: + type: string + description: 'Order results by: name, created_date' + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - in: query + name: search + schema: + type: string + description: Search clients by name, legal name, or overview + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedClientList' + description: '' + post: + operationId: orbit_api_v1_clients_create + description: Create a new client record. + summary: Create client + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ClientRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/ClientRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/ClientRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Client' + description: '' + /orbit/api/v1/clients/{id}/: + get: + operationId: orbit_api_v1_clients_retrieve + description: Retrieve detailed information about a specific client. + summary: Get client details + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this client. + required: true + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Client' + description: '' + put: + operationId: orbit_api_v1_clients_update + description: Update all fields of a client record. + summary: Update client + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this client. + required: true + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ClientRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/ClientRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/ClientRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Client' + description: '' + patch: + operationId: orbit_api_v1_clients_partial_update + description: Update specific fields of a client record. + summary: Partially update client + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this client. + required: true + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedClientRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedClientRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedClientRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Client' + description: '' + delete: + operationId: orbit_api_v1_clients_destroy + description: Delete a client record. + summary: Delete client + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this client. + required: true + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /orbit/api/v1/contacts/: + get: + operationId: orbit_api_v1_contacts_list + description: Retrieve a paginated list of contacts from user's clients and vendors. + summary: List contacts + parameters: + - name: ordering + required: false + in: query + description: Which field to use when ordering the results. + schema: + type: string + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - in: query + name: search + schema: + type: string + description: Search contacts by name, job title, or email + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedContactList' + description: '' + post: + operationId: orbit_api_v1_contacts_create + description: Create a new contact record. + summary: Create contact + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ContactRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/ContactRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/ContactRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Contact' + description: '' + /orbit/api/v1/contacts/{id}/: + get: + operationId: orbit_api_v1_contacts_retrieve + description: Retrieve detailed information about a specific contact. + summary: Get contact details + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this contact. + required: true + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Contact' + description: '' + put: + operationId: orbit_api_v1_contacts_update + description: Update all fields of a contact record. + summary: Update contact + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this contact. + required: true + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ContactRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/ContactRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/ContactRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Contact' + description: '' + patch: + operationId: orbit_api_v1_contacts_partial_update + description: Update specific fields of a contact record. + summary: Partially update contact + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this contact. + required: true + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedContactRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedContactRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedContactRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Contact' + description: '' + delete: + operationId: orbit_api_v1_contacts_destroy + description: Delete a contact record. + summary: Delete contact + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this contact. + required: true + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /orbit/api/v1/contacts/search/: + get: + operationId: orbit_api_v1_contacts_search_retrieve + description: Advanced contact search with organization type filtering. + summary: Search contacts + parameters: + - in: query + name: organization_type + schema: + type: string + description: 'Filter by organization type: client or vendor' + - in: query + name: search_term + schema: + type: string + description: Search term for name or job title + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Contact' + description: '' + /orbit/api/v1/digital-contact-types/: + get: + operationId: orbit_api_v1_digital_contact_types_list + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedDigitalContactTypeList' + description: '' + /orbit/api/v1/digital-contact-types/{id}/: + get: + operationId: orbit_api_v1_digital_contact_types_retrieve + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this digital contact type. + required: true + tags: + - orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DigitalContactType' + description: '' + /orbit/api/v1/digital-contacts/: + get: + operationId: orbit_api_v1_digital_contacts_list + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedDigitalContactList' + description: '' + post: + operationId: orbit_api_v1_digital_contacts_create + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + tags: + - orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DigitalContactRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/DigitalContactRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/DigitalContactRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/DigitalContact' + description: '' + /orbit/api/v1/digital-contacts/{id}/: + get: + operationId: orbit_api_v1_digital_contacts_retrieve + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this digital contact. + required: true + tags: + - orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DigitalContact' + description: '' + put: + operationId: orbit_api_v1_digital_contacts_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this digital contact. + required: true + tags: + - orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DigitalContactRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/DigitalContactRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/DigitalContactRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DigitalContact' + description: '' + patch: + operationId: orbit_api_v1_digital_contacts_partial_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this digital contact. + required: true + tags: + - orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedDigitalContactRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedDigitalContactRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedDigitalContactRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DigitalContact' + description: '' + delete: + operationId: orbit_api_v1_digital_contacts_destroy + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this digital contact. + required: true + tags: + - orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /orbit/api/v1/opportunities/: + get: + operationId: orbit_api_v1_opportunities_list + description: Retrieve a paginated list of sales opportunities. + summary: List opportunities + parameters: + - in: query + name: ordering + schema: + type: string + description: 'Order by: name, expected_close_date, value' + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - in: query + name: search + schema: + type: string + description: Search opportunities by name or description + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedOpportunityList' + description: '' + post: + operationId: orbit_api_v1_opportunities_create + description: Create a new sales opportunity. + summary: Create opportunity + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/OpportunityRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/OpportunityRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/OpportunityRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Opportunity' + description: '' + /orbit/api/v1/opportunities/{id}/: + get: + operationId: orbit_api_v1_opportunities_retrieve + description: Retrieve detailed information about a specific opportunity. + summary: Get opportunity details + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this opportunity. + required: true + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Opportunity' + description: '' + put: + operationId: orbit_api_v1_opportunities_update + description: Update all fields of an opportunity record. + summary: Update opportunity + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this opportunity. + required: true + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/OpportunityRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/OpportunityRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/OpportunityRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Opportunity' + description: '' + patch: + operationId: orbit_api_v1_opportunities_partial_update + description: Update specific fields of an opportunity record. + summary: Partially update opportunity + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this opportunity. + required: true + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedOpportunityRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedOpportunityRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedOpportunityRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Opportunity' + description: '' + delete: + operationId: orbit_api_v1_opportunities_destroy + description: Delete an opportunity record. + summary: Delete opportunity + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this opportunity. + required: true + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /orbit/api/v1/opportunities/by_status/: + get: + operationId: orbit_api_v1_opportunities_by_status_retrieve + description: Retrieve opportunities filtered by status and optionally by client. + summary: Filter opportunities by status + parameters: + - in: query + name: client_id + schema: + type: integer + description: Filter by specific client ID + - in: query + name: status + schema: + type: string + description: 'Filter by opportunity status: active, won, lost, dropped' + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Opportunity' + description: '' + /orbit/api/v1/proposals/: + get: + operationId: orbit_api_v1_proposals_list + description: Retrieve a paginated list of proposals for opportunities owned + by the current user. + summary: List proposals + parameters: + - in: query + name: opportunity_id + schema: + type: integer + description: Filter by opportunity ID + - in: query + name: ordering + schema: + type: string + description: 'Order by: name, due_date, submitted_date, created_date' + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - in: query + name: search + schema: + type: string + description: Search proposals by name + - in: query + name: status + schema: + type: string + description: 'Filter by status: Draft, In Review, Final' + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedProposalList' + description: '' + post: + operationId: orbit_api_v1_proposals_create + description: Create a new proposal record. + summary: Create proposal + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ProposalRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/ProposalRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/ProposalRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Proposal' + description: '' + /orbit/api/v1/proposals/{id}/: + get: + operationId: orbit_api_v1_proposals_retrieve + description: Retrieve detailed information about a specific proposal. + summary: Get proposal details + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this proposal. + required: true + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Proposal' + description: '' + put: + operationId: orbit_api_v1_proposals_update + description: Update all fields of a proposal record. + summary: Update proposal + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this proposal. + required: true + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ProposalRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/ProposalRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/ProposalRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Proposal' + description: '' + patch: + operationId: orbit_api_v1_proposals_partial_update + description: Update specific fields of a proposal record. + summary: Partially update proposal + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this proposal. + required: true + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedProposalRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedProposalRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedProposalRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Proposal' + description: '' + delete: + operationId: orbit_api_v1_proposals_destroy + description: Delete a proposal record. + summary: Delete proposal + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this proposal. + required: true + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /orbit/api/v1/stock/overview/{symbol}/: + get: + operationId: orbit_api_v1_stock_overview_retrieve + description: Returns company overview data for a stock symbol + summary: Get company overview + parameters: + - in: path + name: symbol + schema: + type: string + description: Stock symbol + required: true + tags: + - stock + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + description: Company overview data + '403': + description: Access denied + '500': + description: Unable to fetch company data + /orbit/api/v1/stock/quote/{symbol}/: + get: + operationId: orbit_api_v1_stock_quote_retrieve + description: Returns current stock quote data for a symbol + summary: Get stock quote + parameters: + - in: path + name: symbol + schema: + type: string + description: Stock symbol + required: true + tags: + - stock + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + description: Stock quote data + '403': + description: Access denied + '500': + description: Unable to fetch stock data + /orbit/api/v1/suggest-timezone/: + post: + operationId: orbit_api_v1_suggest_timezone_create + description: Uses address components to suggest an appropriate timezone using + geocoding. + summary: Suggest timezone from address + tags: + - Orbit + requestBody: + content: + application/json: + schema: + type: object + properties: + street_address: + type: string + locality: + type: string + region: + type: string + postal_code: + type: string + country: + type: string + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + type: object + properties: + timezone: + type: string + success: + type: boolean + method: + type: string + description: '' + '400': + content: + application/json: + schema: + type: object + properties: + error: + type: string + success: + type: boolean + description: '' + /orbit/api/v1/vendor-solutions/: + get: + operationId: orbit_api_v1_vendor_solutions_list + description: Retrieve a paginated list of vendor solutions. + summary: List vendor solutions + parameters: + - name: ordering + required: false + in: query + description: Which field to use when ordering the results. + schema: + type: string + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - in: query + name: search + schema: + type: string + description: Search solutions by name or description + - in: query + name: vendor + schema: + type: integer + description: Filter by vendor ID + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedVendorSolutionList' + description: '' + post: + operationId: orbit_api_v1_vendor_solutions_create + description: Create a new vendor solution. + summary: Create vendor solution + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/VendorSolutionRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/VendorSolutionRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/VendorSolutionRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/VendorSolution' + description: '' + /orbit/api/v1/vendor-solutions/{id}/: + get: + operationId: orbit_api_v1_vendor_solutions_retrieve + description: Retrieve detailed information about a specific vendor solution. + summary: Get vendor solution details + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this vendor solution. + required: true + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/VendorSolution' + description: '' + put: + operationId: orbit_api_v1_vendor_solutions_update + description: Update all fields of a vendor solution record. + summary: Update vendor solution + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this vendor solution. + required: true + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/VendorSolutionRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/VendorSolutionRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/VendorSolutionRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/VendorSolution' + description: '' + patch: + operationId: orbit_api_v1_vendor_solutions_partial_update + description: Update specific fields of a vendor solution record. + summary: Partially update vendor solution + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this vendor solution. + required: true + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedVendorSolutionRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedVendorSolutionRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedVendorSolutionRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/VendorSolution' + description: '' + delete: + operationId: orbit_api_v1_vendor_solutions_destroy + description: Delete a vendor solution record. + summary: Delete vendor solution + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this vendor solution. + required: true + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /orbit/api/v1/vendors/: + get: + operationId: orbit_api_v1_vendors_list + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - name: ordering + required: false + in: query + description: Which field to use when ordering the results. + schema: + type: string + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: search + required: false + in: query + description: A search term. + schema: + type: string + tags: + - orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedVendorList' + description: '' + post: + operationId: orbit_api_v1_vendors_create + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + tags: + - orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/VendorRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/VendorRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/VendorRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Vendor' + description: '' + /orbit/api/v1/vendors/{id}/: + get: + operationId: orbit_api_v1_vendors_retrieve + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this vendor. + required: true + tags: + - orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Vendor' + description: '' + put: + operationId: orbit_api_v1_vendors_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this vendor. + required: true + tags: + - orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/VendorRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/VendorRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/VendorRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Vendor' + description: '' + patch: + operationId: orbit_api_v1_vendors_partial_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this vendor. + required: true + tags: + - orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedVendorRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedVendorRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedVendorRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Vendor' + description: '' + delete: + operationId: orbit_api_v1_vendors_destroy + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this vendor. + required: true + tags: + - orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /orbit/api/vendor-solutions/: + get: + operationId: orbit_api_vendor_solutions_list + description: Retrieve a paginated list of vendor solutions. + summary: List vendor solutions + parameters: + - name: ordering + required: false + in: query + description: Which field to use when ordering the results. + schema: + type: string + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - in: query + name: search + schema: + type: string + description: Search solutions by name or description + - in: query + name: vendor + schema: + type: integer + description: Filter by vendor ID + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedVendorSolutionList' + description: '' + post: + operationId: orbit_api_vendor_solutions_create + description: Create a new vendor solution. + summary: Create vendor solution + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/VendorSolutionRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/VendorSolutionRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/VendorSolutionRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/VendorSolution' + description: '' + /orbit/api/vendor-solutions/{id}/: + get: + operationId: orbit_api_vendor_solutions_retrieve + description: Retrieve detailed information about a specific vendor solution. + summary: Get vendor solution details + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this vendor solution. + required: true + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/VendorSolution' + description: '' + put: + operationId: orbit_api_vendor_solutions_update + description: Update all fields of a vendor solution record. + summary: Update vendor solution + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this vendor solution. + required: true + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/VendorSolutionRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/VendorSolutionRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/VendorSolutionRequest' + required: true + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/VendorSolution' + description: '' + patch: + operationId: orbit_api_vendor_solutions_partial_update + description: Update specific fields of a vendor solution record. + summary: Partially update vendor solution + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this vendor solution. + required: true + tags: + - Orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedVendorSolutionRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedVendorSolutionRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedVendorSolutionRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/VendorSolution' + description: '' + delete: + operationId: orbit_api_vendor_solutions_destroy + description: Delete a vendor solution record. + summary: Delete vendor solution + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this vendor solution. + required: true + tags: + - Orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body + /orbit/api/vendors/: + get: + operationId: orbit_api_vendors_list + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - name: ordering + required: false + in: query + description: Which field to use when ordering the results. + schema: + type: string + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: search + required: false + in: query + description: A search term. + schema: + type: string + tags: + - orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedVendorList' + description: '' + post: + operationId: orbit_api_vendors_create + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + tags: + - orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/VendorRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/VendorRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/VendorRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Vendor' + description: '' + /orbit/api/vendors/{id}/: + get: + operationId: orbit_api_vendors_retrieve + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this vendor. + required: true + tags: + - orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Vendor' + description: '' + put: + operationId: orbit_api_vendors_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this vendor. + required: true + tags: + - orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/VendorRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/VendorRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/VendorRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Vendor' + description: '' + patch: + operationId: orbit_api_vendors_partial_update + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this vendor. + required: true + tags: + - orbit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedVendorRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedVendorRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedVendorRequest' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Vendor' + description: '' + delete: + operationId: orbit_api_vendors_destroy + description: |- + Mixin that adds automatic tenant scoping to any DRF ViewSet. + + Attributes: + tenant_field (str): + The ORM lookup path from the model to the ``subscriber`` FK. + Defaults to ``'subscriber'`` for models with a direct FK. + Set to a longer path for child models, e.g. + ``'advisor_tool__subscriber'``. + + tenant_owner_field (str | None): + Optional. The field name for the ``owner`` / ``user`` FK on the + model. When set, queryset is *also* filtered by + ``request.user`` (preserving existing per-user isolation within + a tenant). Set to ``None`` to skip user-level filtering. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this vendor. + required: true + tags: + - orbit + security: + - ApiKeyAuth: [] + - ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' + responses: + '204': + description: No response body +components: + schemas: + APIKey: + type: object + description: |- + API key information serializer. + + Provides API key metadata without exposing the actual key value: + - Key name and type + - Creation and usage timestamps + - Active status + properties: + id: + type: integer + readOnly: true + name: + type: string + maxLength: 100 + app_type: + $ref: '#/components/schemas/AppTypeEnum' + created_date: + type: string + format: date-time + readOnly: true + last_used: + type: string + format: date-time + readOnly: true + nullable: true + is_active: + type: boolean + days_since_created: + type: string + readOnly: true + days_since_used: + type: string + readOnly: true + required: + - app_type + - created_date + - days_since_created + - days_since_used + - id + - last_used + - name + AdvisorTool: + type: object + description: Serialize an AdvisorTool with computed counts and client/association + display fields. + properties: + id: + type: integer + readOnly: true + name: + type: string + maxLength: 255 + description: + type: string + proposal: + type: integer + nullable: true + engagement: + type: integer + nullable: true + categories: + description: Custom categories for findings and requirements + timeframe_short: + type: integer + maximum: 2147483647 + minimum: -2147483648 + description: Short timeframe in days + timeframe_medium: + type: integer + maximum: 2147483647 + minimum: -2147483648 + description: Medium timeframe in days + timeframe_long: + type: integer + maximum: 2147483647 + minimum: -2147483648 + description: Long timeframe in days + client_name: + type: string + readOnly: true + association_display: + type: string + readOnly: true + findings_count: + type: string + readOnly: true + requirements_count: + type: string + readOnly: true + recommendations_count: + type: string + readOnly: true + principles_count: + type: string + readOnly: true + created_date: + type: string + format: date-time + readOnly: true + modified_date: + type: string + format: date-time + readOnly: true + required: + - association_display + - client_name + - created_date + - description + - findings_count + - id + - modified_date + - name + - principles_count + - recommendations_count + - requirements_count + AdvisorToolRequest: + type: object + description: Serialize an AdvisorTool with computed counts and client/association + display fields. + properties: + name: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + minLength: 1 + proposal: + type: integer + nullable: true + engagement: + type: integer + nullable: true + categories: + description: Custom categories for findings and requirements + timeframe_short: + type: integer + maximum: 2147483647 + minimum: -2147483648 + description: Short timeframe in days + timeframe_medium: + type: integer + maximum: 2147483647 + minimum: -2147483648 + description: Medium timeframe in days + timeframe_long: + type: integer + maximum: 2147483647 + minimum: -2147483648 + description: Long timeframe in days + required: + - description + - name + AppTypeEnum: + enum: + - DAV + - API + - MCP + type: string + description: |- + * `DAV` - DAV Key (Calendar & Contacts) + * `API` - API Key (REST API) + * `MCP` - MCP Key (AI Assistant) + Assumption: + type: object + properties: + id: + type: integer + readOnly: true + title: + type: string + maxLength: 255 + description: + type: string + potential_impact: + $ref: '#/components/schemas/PotentialImpactEnum' + impact_details: + type: string + required: + - description + - id + - impact_details + - potential_impact + - title + BMC: + type: object + properties: + id: + type: integer + readOnly: true + version: + type: integer + maximum: 2147483647 + minimum: -2147483648 + customer_segments: + type: string + value_propositions: + type: string + channels: + type: string + customer_relationships: + type: string + revenue_streams: + type: string + key_resources: + type: string + key_activities: + type: string + key_partnerships: + type: string + cost_structure: + type: string + created_date: + type: string + format: date-time + readOnly: true + modified_date: + type: string + format: date-time + readOnly: true + required: + - created_date + - id + - modified_date + BMCRequest: + type: object + properties: + version: + type: integer + maximum: 2147483647 + minimum: -2147483648 + customer_segments: + type: string + value_propositions: + type: string + channels: + type: string + customer_relationships: + type: string + revenue_streams: + type: string + key_resources: + type: string + key_activities: + type: string + key_partnerships: + type: string + cost_structure: + type: string + BMCTemplate: + type: object + properties: + id: + type: integer + readOnly: true + name: + type: string + maxLength: 255 + description: + type: string + customer_segments: + type: string + value_propositions: + type: string + channels: + type: string + customer_relationships: + type: string + revenue_streams: + type: string + key_resources: + type: string + key_activities: + type: string + key_partnerships: + type: string + cost_structure: + type: string + is_active: + type: boolean + created_date: + type: string + format: date-time + readOnly: true + modified_date: + type: string + format: date-time + readOnly: true + required: + - created_date + - id + - modified_date + - name + BMCTemplateRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + customer_segments: + type: string + value_propositions: + type: string + channels: + type: string + customer_relationships: + type: string + revenue_streams: + type: string + key_resources: + type: string + key_activities: + type: string + key_partnerships: + type: string + cost_structure: + type: string + is_active: + type: boolean + required: + - name + BMCTool: + type: object + properties: + id: + type: integer + readOnly: true + name: + type: string + maxLength: 255 + description: + type: string + proposal: + type: integer + nullable: true + engagement: + type: integer + nullable: true + client_name: + type: string + readOnly: true + association_display: + type: string + readOnly: true + latest_bmc: + type: string + readOnly: true + bmc_count: + type: string + readOnly: true + created_date: + type: string + format: date-time + readOnly: true + modified_date: + type: string + format: date-time + readOnly: true + required: + - association_display + - bmc_count + - client_name + - created_date + - description + - id + - latest_bmc + - modified_date + - name + BMCToolRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + minLength: 1 + proposal: + type: integer + nullable: true + engagement: + type: integer + nullable: true + required: + - description + - name + BillingCycleEnum: + enum: + - one_time + - monthly + - annual + type: string + description: |- + * `one_time` - One Time + * `monthly` - Monthly + * `annual` - Annual + BlankEnum: + enum: + - '' + BulkLocationOperationRequest: + type: object + description: |- + Bulk location operation serializer. + + Handles bulk operations on multiple locations. + properties: + operation: + $ref: '#/components/schemas/OperationEnum' + location_ids: + type: array + items: + type: integer + description: List of location IDs to operate on + minItems: 1 + parameters: + type: object + additionalProperties: {} + description: Operation-specific parameters + required: + - location_ids + - operation + CSVImportRequest: + type: object + description: |- + CSV import request serializer. + + Handles CSV file upload and import configuration. + properties: + csv_file: + type: string + format: binary + description: CSV file containing location data + required: + - csv_file + CalendarEvent: + type: object + properties: + id: + type: integer + readOnly: true + title: + type: string + maxLength: 255 + description: + type: string + start_time: + type: string + format: date-time + end_time: + type: string + format: date-time + location: + type: string + maxLength: 255 + event_type: + $ref: '#/components/schemas/EventTypeEnum' + required: + - end_time + - id + - start_time + - title + CalendarEventRequest: + type: object + properties: + title: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + start_time: + type: string + format: date-time + end_time: + type: string + format: date-time + location: + type: string + maxLength: 255 + event_type: + $ref: '#/components/schemas/EventTypeEnum' + required: + - end_time + - start_time + - title + Client: + type: object + properties: + id: + type: integer + readOnly: true + name: + type: string + legal_name: + type: string + abbreviated_name: + type: string + overview: + type: string + history: + type: string + services_provided: + type: string + client_type: + type: string + vertical: + type: string + nullable: true + stock_exchange: + type: string + nullable: true + stock_symbol: + type: string + nullable: true + employee_count: + type: integer + nullable: true + revenue: + type: string + format: decimal + pattern: ^-?\d{0,13}(?:\.\d{0,2})?$ + nullable: true + assets: + type: string + format: decimal + pattern: ^-?\d{0,13}(?:\.\d{0,2})?$ + nullable: true + fiscal_year_end: + type: string + format: date + nullable: true + contact_center_agent_count: + type: integer + nullable: true + service_desk_agent_count: + type: integer + nullable: true + supervisor_count: + type: integer + nullable: true + location_count: + type: integer + nullable: true + repository_url: + type: string + parent_organization: + type: string + readOnly: true + parent_relationship_type: + type: string + readOnly: true + is_multi_role_organization: + type: string + readOnly: true + child_organizations_count: + type: string + readOnly: true + required: + - child_organizations_count + - id + - is_multi_role_organization + - parent_organization + - parent_relationship_type + ClientLocationTool: + type: object + description: |- + Client Location Tool serializer with automatic version management. + + Provides tool information including: + - Basic tool metadata + - Current version number (automatically managed) + - All location sets with automatic version numbering + - Table source configuration + + Automatic Version Management: + - current_version field shows the active version number (integer) + - location_sets array shows all versions ordered by version number + - Version creation is handled by the create_version API endpoint + - Version switching is handled by the switch_version API endpoint + - All version numbers are automatically assigned and read-only + properties: + id: + type: integer + readOnly: true + name: + type: string + maxLength: 255 + description: + type: string + current_version: + type: integer + readOnly: true + description: Current active version number (automatically managed) + proposal: + type: integer + nullable: true + engagement: + type: integer + nullable: true + owner: + type: integer + readOnly: true + current_location_set: + allOf: + - $ref: '#/components/schemas/LocationSet' + readOnly: true + location_sets: + type: array + items: + $ref: '#/components/schemas/LocationSet' + readOnly: true + table_source: + type: string + readOnly: true + created_date: + type: string + format: date-time + readOnly: true + modified_date: + type: string + format: date-time + readOnly: true + required: + - created_date + - current_location_set + - current_version + - description + - id + - location_sets + - modified_date + - name + - owner + - table_source + ClientLocationToolRequest: + type: object + description: |- + Client Location Tool serializer with automatic version management. + + Provides tool information including: + - Basic tool metadata + - Current version number (automatically managed) + - All location sets with automatic version numbering + - Table source configuration + + Automatic Version Management: + - current_version field shows the active version number (integer) + - location_sets array shows all versions ordered by version number + - Version creation is handled by the create_version API endpoint + - Version switching is handled by the switch_version API endpoint + - All version numbers are automatically assigned and read-only + properties: + name: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + minLength: 1 + proposal: + type: integer + nullable: true + engagement: + type: integer + nullable: true + required: + - description + - name + ClientRequest: + type: object + properties: + name: + type: string + legal_name: + type: string + abbreviated_name: + type: string + overview: + type: string + history: + type: string + services_provided: + type: string + client_type: + type: string + vertical: + type: string + nullable: true + stock_exchange: + type: string + nullable: true + stock_symbol: + type: string + nullable: true + employee_count: + type: integer + nullable: true + revenue: + type: string + format: decimal + pattern: ^-?\d{0,13}(?:\.\d{0,2})?$ + nullable: true + assets: + type: string + format: decimal + pattern: ^-?\d{0,13}(?:\.\d{0,2})?$ + nullable: true + fiscal_year_end: + type: string + format: date + nullable: true + contact_center_agent_count: + type: integer + nullable: true + service_desk_agent_count: + type: integer + nullable: true + supervisor_count: + type: integer + nullable: true + location_count: + type: integer + nullable: true + repository_url: + type: string + ComplexityEnum: + enum: + - High + - Medium + - Low + type: string + description: |- + * `High` - High + * `Medium` - Medium + * `Low` - Low + Contact: + type: object + properties: + id: + type: integer + readOnly: true + first_name: + type: string + maxLength: 100 + last_name: + type: string + maxLength: 100 + job_title: + type: string + maxLength: 100 + email: + type: string + format: email + maxLength: 254 + phones: + type: array + items: + $ref: '#/components/schemas/ContactPhone' + readOnly: true + preferred_phone: + type: string + readOnly: true + organization: + type: string + readOnly: true + reports_to: + type: integer + nullable: true + reports_to_name: + type: string + readOnly: true + direct_reports_count: + type: string + readOnly: true + is_client_contact: + type: string + readOnly: true + is_vendor_contact: + type: string + readOnly: true + is_multi_role_contact: + type: string + readOnly: true + organization_profiles: + type: string + readOnly: true + required: + - direct_reports_count + - first_name + - id + - is_client_contact + - is_multi_role_contact + - is_vendor_contact + - last_name + - organization + - organization_profiles + - phones + - preferred_phone + - reports_to_name + ContactPhone: + type: object + properties: + id: + type: integer + readOnly: true + number: + type: string + maxLength: 20 + type: + $ref: '#/components/schemas/ContactPhoneTypeEnum' + is_preferred: + type: boolean + required: + - id + - number + ContactPhoneRequest: + type: object + properties: + number: + type: string + minLength: 1 + maxLength: 20 + type: + $ref: '#/components/schemas/ContactPhoneTypeEnum' + is_preferred: + type: boolean + required: + - number + ContactPhoneTypeEnum: + enum: + - cell + - home + - work + - other + type: string + description: |- + * `cell` - Cell + * `home` - Home + * `work` - Work + * `other` - Other + ContactRequest: + type: object + properties: + first_name: + type: string + minLength: 1 + maxLength: 100 + last_name: + type: string + minLength: 1 + maxLength: 100 + job_title: + type: string + maxLength: 100 + email: + type: string + format: email + maxLength: 254 + organization_id: + type: integer + writeOnly: true + reports_to: + type: integer + nullable: true + required: + - first_name + - last_name + Content3TypeEnum: + enum: + - text + - image + - table + type: string + description: |- + * `text` - Text + * `image` - Image + * `table` - Table + Currency: + type: object + description: |- + Currency information serializer. + + Provides currency details including: + - ISO currency codes + - Display names and symbols + - Active status + properties: + code: + type: string + maxLength: 3 + name: + type: string + maxLength: 50 + symbol: + type: string + maxLength: 5 + is_active: + type: boolean + required: + - code + - name + - symbol + CustomFieldDefinition: + type: object + description: |- + Custom field definition serializer. + + Provides custom field schema information including: + - Field name, type, and description + - Validation rules and default values + - Display order + properties: + id: + type: integer + readOnly: true + name: + type: string + maxLength: 100 + field_type: + $ref: '#/components/schemas/CustomFieldDefinitionFieldTypeEnum' + description: + type: string + is_required: + type: boolean + default_value: + type: string + order: + type: integer + maximum: 2147483647 + minimum: 0 + required: + - field_type + - id + - name + CustomFieldDefinitionFieldTypeEnum: + enum: + - count + - boolean + - text + - rich_text + type: string + description: |- + * `count` - Count + * `boolean` - Boolean + * `text` - Text + * `rich_text` - Rich Text + CustomFieldDefinitionRequest: + type: object + description: |- + Custom field definition serializer. + + Provides custom field schema information including: + - Field name, type, and description + - Validation rules and default values + - Display order + properties: + name: + type: string + minLength: 1 + maxLength: 100 + field_type: + $ref: '#/components/schemas/CustomFieldDefinitionFieldTypeEnum' + description: + type: string + is_required: + type: boolean + default_value: + type: string + order: + type: integer + maximum: 2147483647 + minimum: 0 + required: + - field_type + - name + DateFormatEnum: + enum: + - YYYY-MM-DD + - DD/MM/YYYY + - MM/DD/YYYY + - DD.MM.YYYY + - DD-MM-YYYY + type: string + description: |- + * `YYYY-MM-DD` - 2024-12-25 + * `DD/MM/YYYY` - 25/12/2024 + * `MM/DD/YYYY` - 12/25/2024 + * `DD.MM.YYYY` - 25.12.2024 + * `DD-MM-YYYY` - 25-12-2024 + Deck: + type: object + properties: + id: + type: integer + readOnly: true + presentation: + type: integer + template: + type: integer + title: + type: string + maxLength: 255 + subtitle: + type: string + maxLength: 255 + presenter: + type: integer + nullable: true + status: + $ref: '#/components/schemas/DeckStatusEnum' + date: + type: string + format: date + slide_count: + type: string + readOnly: true + created_date: + type: string + format: date-time + readOnly: true + modified_date: + type: string + format: date-time + readOnly: true + required: + - created_date + - id + - modified_date + - presentation + - slide_count + - status + - template + DeckRequest: + type: object + properties: + presentation: + type: integer + template: + type: integer + title: + type: string + maxLength: 255 + subtitle: + type: string + maxLength: 255 + presenter: + type: integer + nullable: true + status: + $ref: '#/components/schemas/DeckStatusEnum' + date: + type: string + format: date + required: + - presentation + - status + - template + DeckSlide: + type: object + properties: + id: + type: integer + readOnly: true + deck: + type: integer + slide_type: + type: string + maxLength: 50 + order: + type: integer + maximum: 2147483647 + minimum: -2147483648 + content_mapping: {} + layout_name: + type: string + readOnly: true + content1_type: + $ref: '#/components/schemas/Content3TypeEnum' + content1_image: + type: integer + nullable: true + content1_table: + type: integer + nullable: true + content2_type: + $ref: '#/components/schemas/Content3TypeEnum' + content2_image: + type: integer + nullable: true + content2_table: + type: integer + nullable: true + content3_type: + $ref: '#/components/schemas/Content3TypeEnum' + content3_image: + type: integer + nullable: true + content3_table: + type: integer + nullable: true + required: + - deck + - id + - layout_name + - order + - slide_type + DeckSlideRequest: + type: object + properties: + deck: + type: integer + slide_type: + type: string + minLength: 1 + maxLength: 50 + order: + type: integer + maximum: 2147483647 + minimum: -2147483648 + content_mapping: {} + content1_type: + $ref: '#/components/schemas/Content3TypeEnum' + content1_image: + type: integer + nullable: true + content1_table: + type: integer + nullable: true + content2_type: + $ref: '#/components/schemas/Content3TypeEnum' + content2_image: + type: integer + nullable: true + content2_table: + type: integer + nullable: true + content3_type: + $ref: '#/components/schemas/Content3TypeEnum' + content3_image: + type: integer + nullable: true + content3_table: + type: integer + nullable: true + required: + - deck + - order + - slide_type + DeckStatusEnum: + enum: + - Draft + - Final + type: string + description: |- + * `Draft` - Draft + * `Final` - Final + Dependency: + type: object + properties: + id: + type: integer + readOnly: true + title: + type: string + maxLength: 255 + description: + type: string + potential_impact: + $ref: '#/components/schemas/PotentialImpactEnum' + owner: + type: integer + owner_name: + type: string + readOnly: true + priority: + $ref: '#/components/schemas/Priority758Enum' + external: + type: boolean + required: + - description + - id + - owner + - owner_name + - potential_impact + - priority + - title + DigitalContact: + type: object + properties: + id: + type: integer + readOnly: true + name: + type: string + maxLength: 100 + contact_type: + allOf: + - $ref: '#/components/schemas/DigitalContactType' + readOnly: true + url: + type: string + maxLength: 500 + display_url: + type: string + readOnly: true + notes: + type: string + client: + type: integer + nullable: true + vendor: + type: integer + nullable: true + required: + - contact_type + - display_url + - id + - name + - url + DigitalContactRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 100 + contact_type_id: + type: integer + writeOnly: true + url: + type: string + minLength: 1 + maxLength: 500 + notes: + type: string + client: + type: integer + nullable: true + vendor: + type: integer + nullable: true + required: + - contact_type_id + - name + - url + DigitalContactType: + type: object + properties: + id: + type: integer + readOnly: true + name: + type: string + maxLength: 50 + icon: + type: string + maxLength: 50 + required: + - id + - name + DigitalContactTypeRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 50 + icon: + type: string + maxLength: 50 + required: + - name + DurationEnum: + enum: + - 45 + - 60 + - 90 + type: integer + description: |- + * `45` - 45 minutes + * `60` - 60 minutes + * `90` - 90 minutes + Engagement: + type: object + properties: + id: + type: integer + readOnly: true + name: + type: string + maxLength: 255 + client: + allOf: + - $ref: '#/components/schemas/Client' + readOnly: true + start_date: + type: string + format: date + end_date: + type: string + format: date + status: + $ref: '#/components/schemas/Status557Enum' + description: + type: string + interviews: + type: array + items: + $ref: '#/components/schemas/Interview' + readOnly: true + workshops: + type: array + items: + $ref: '#/components/schemas/Workshop' + readOnly: true + interview_count: + type: string + readOnly: true + workshop_count: + type: string + readOnly: true + created_date: + type: string + format: date-time + readOnly: true + modified_date: + type: string + format: date-time + readOnly: true + required: + - client + - created_date + - end_date + - id + - interview_count + - interviews + - modified_date + - name + - start_date + - workshop_count + - workshops + EngagementRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 255 + client_id: + type: integer + writeOnly: true + opportunity_id: + type: integer + writeOnly: true + start_date: + type: string + format: date + end_date: + type: string + format: date + status: + $ref: '#/components/schemas/Status557Enum' + description: + type: string + required: + - client_id + - end_date + - name + - opportunity_id + - start_date + EngagementSummary: + type: object + description: Lightweight serializer for engagement summaries + properties: + id: + type: integer + readOnly: true + name: + type: string + maxLength: 255 + client_name: + type: string + readOnly: true + status: + $ref: '#/components/schemas/Status557Enum' + start_date: + type: string + format: date + end_date: + type: string + format: date + interview_count: + type: string + readOnly: true + workshop_count: + type: string + readOnly: true + required: + - client_name + - end_date + - id + - interview_count + - name + - start_date + - workshop_count + EventTypeEnum: + enum: + - meeting + - deadline + - reminder + - opportunity + - birthday + type: string + description: |- + * `meeting` - Meeting + * `deadline` - Deadline + * `reminder` - Reminder + * `opportunity` - Opportunity + * `birthday` - Birthday + Finding: + type: object + description: Serialize all Finding fields for read and write. + properties: + id: + type: integer + readOnly: true + name: + type: string + maxLength: 255 + description: + type: string + source: + $ref: '#/components/schemas/SourceEnum' + source_detail: + type: string + maxLength: 255 + category: + type: string + maxLength: 100 + impact_level: + $ref: '#/components/schemas/ImpactLevelEnum' + impact_description: + type: string + created_date: + type: string + format: date-time + readOnly: true + required: + - created_date + - description + - id + - impact_description + - impact_level + - name + - source + FindingRequest: + type: object + description: Serialize all Finding fields for read and write. + properties: + name: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + minLength: 1 + source: + $ref: '#/components/schemas/SourceEnum' + source_detail: + type: string + maxLength: 255 + category: + type: string + maxLength: 100 + impact_level: + $ref: '#/components/schemas/ImpactLevelEnum' + impact_description: + type: string + minLength: 1 + required: + - description + - impact_description + - impact_level + - name + - source + FiscalYear: + type: object + description: |- + Fiscal year information serializer. + + Provides fiscal year details including: + - Year designation + - Start date and calculated quarters + - Fiscal year periods + properties: + fiscal_year: + type: integer + readOnly: true + nullable: true + fiscal_year_start: + type: string + format: date + fiscal_quarter_1: + type: string + readOnly: true + nullable: true + fiscal_quarter_2: + type: string + readOnly: true + nullable: true + fiscal_quarter_3: + type: string + readOnly: true + nullable: true + fiscal_quarter_4: + type: string + readOnly: true + nullable: true + required: + - fiscal_quarter_1 + - fiscal_quarter_2 + - fiscal_quarter_3 + - fiscal_quarter_4 + - fiscal_year + - fiscal_year_start + HomeTimezoneEnum: + enum: + - Africa/Abidjan + - Africa/Accra + - Africa/Addis_Ababa + - Africa/Algiers + - Africa/Asmara + - Africa/Bamako + - Africa/Bangui + - Africa/Banjul + - Africa/Bissau + - Africa/Blantyre + - Africa/Brazzaville + - Africa/Bujumbura + - Africa/Cairo + - Africa/Casablanca + - Africa/Ceuta + - Africa/Conakry + - Africa/Dakar + - Africa/Dar_es_Salaam + - Africa/Djibouti + - Africa/Douala + - Africa/El_Aaiun + - Africa/Freetown + - Africa/Gaborone + - Africa/Harare + - Africa/Johannesburg + - Africa/Juba + - Africa/Kampala + - Africa/Khartoum + - Africa/Kigali + - Africa/Kinshasa + - Africa/Lagos + - Africa/Libreville + - Africa/Lome + - Africa/Luanda + - Africa/Lubumbashi + - Africa/Lusaka + - Africa/Malabo + - Africa/Maputo + - Africa/Maseru + - Africa/Mbabane + - Africa/Mogadishu + - Africa/Monrovia + - Africa/Nairobi + - Africa/Ndjamena + - Africa/Niamey + - Africa/Nouakchott + - Africa/Ouagadougou + - Africa/Porto-Novo + - Africa/Sao_Tome + - Africa/Tripoli + - Africa/Tunis + - Africa/Windhoek + - America/Adak + - America/Anchorage + - America/Anguilla + - America/Antigua + - America/Araguaina + - America/Argentina/Buenos_Aires + - America/Argentina/Catamarca + - America/Argentina/Cordoba + - America/Argentina/Jujuy + - America/Argentina/La_Rioja + - America/Argentina/Mendoza + - America/Argentina/Rio_Gallegos + - America/Argentina/Salta + - America/Argentina/San_Juan + - America/Argentina/San_Luis + - America/Argentina/Tucuman + - America/Argentina/Ushuaia + - America/Aruba + - America/Asuncion + - America/Atikokan + - America/Bahia + - America/Bahia_Banderas + - America/Barbados + - America/Belem + - America/Belize + - America/Blanc-Sablon + - America/Boa_Vista + - America/Bogota + - America/Boise + - America/Cambridge_Bay + - America/Campo_Grande + - America/Cancun + - America/Caracas + - America/Cayenne + - America/Cayman + - America/Chicago + - America/Chihuahua + - America/Ciudad_Juarez + - America/Costa_Rica + - America/Coyhaique + - America/Creston + - America/Cuiaba + - America/Curacao + - America/Danmarkshavn + - America/Dawson + - America/Dawson_Creek + - America/Denver + - America/Detroit + - America/Dominica + - America/Edmonton + - America/Eirunepe + - America/El_Salvador + - America/Fort_Nelson + - America/Fortaleza + - America/Glace_Bay + - America/Goose_Bay + - America/Grand_Turk + - America/Grenada + - America/Guadeloupe + - America/Guatemala + - America/Guayaquil + - America/Guyana + - America/Halifax + - America/Havana + - America/Hermosillo + - America/Indiana/Indianapolis + - America/Indiana/Knox + - America/Indiana/Marengo + - America/Indiana/Petersburg + - America/Indiana/Tell_City + - America/Indiana/Vevay + - America/Indiana/Vincennes + - America/Indiana/Winamac + - America/Inuvik + - America/Iqaluit + - America/Jamaica + - America/Juneau + - America/Kentucky/Louisville + - America/Kentucky/Monticello + - America/Kralendijk + - America/La_Paz + - America/Lima + - America/Los_Angeles + - America/Lower_Princes + - America/Maceio + - America/Managua + - America/Manaus + - America/Marigot + - America/Martinique + - America/Matamoros + - America/Mazatlan + - America/Menominee + - America/Merida + - America/Metlakatla + - America/Mexico_City + - America/Miquelon + - America/Moncton + - America/Monterrey + - America/Montevideo + - America/Montserrat + - America/Nassau + - America/New_York + - America/Nome + - America/Noronha + - America/North_Dakota/Beulah + - America/North_Dakota/Center + - America/North_Dakota/New_Salem + - America/Nuuk + - America/Ojinaga + - America/Panama + - America/Paramaribo + - America/Phoenix + - America/Port-au-Prince + - America/Port_of_Spain + - America/Porto_Velho + - America/Puerto_Rico + - America/Punta_Arenas + - America/Rankin_Inlet + - America/Recife + - America/Regina + - America/Resolute + - America/Rio_Branco + - America/Santarem + - America/Santiago + - America/Santo_Domingo + - America/Sao_Paulo + - America/Scoresbysund + - America/Sitka + - America/St_Barthelemy + - America/St_Johns + - America/St_Kitts + - America/St_Lucia + - America/St_Thomas + - America/St_Vincent + - America/Swift_Current + - America/Tegucigalpa + - America/Thule + - America/Tijuana + - America/Toronto + - America/Tortola + - America/Vancouver + - America/Whitehorse + - America/Winnipeg + - America/Yakutat + - Antarctica/Casey + - Antarctica/Davis + - Antarctica/DumontDUrville + - Antarctica/Macquarie + - Antarctica/Mawson + - Antarctica/McMurdo + - Antarctica/Palmer + - Antarctica/Rothera + - Antarctica/Syowa + - Antarctica/Troll + - Antarctica/Vostok + - Arctic/Longyearbyen + - Asia/Aden + - Asia/Almaty + - Asia/Amman + - Asia/Anadyr + - Asia/Aqtau + - Asia/Aqtobe + - Asia/Ashgabat + - Asia/Atyrau + - Asia/Baghdad + - Asia/Bahrain + - Asia/Baku + - Asia/Bangkok + - Asia/Barnaul + - Asia/Beirut + - Asia/Bishkek + - Asia/Brunei + - Asia/Chita + - Asia/Colombo + - Asia/Damascus + - Asia/Dhaka + - Asia/Dili + - Asia/Dubai + - Asia/Dushanbe + - Asia/Famagusta + - Asia/Gaza + - Asia/Hebron + - Asia/Ho_Chi_Minh + - Asia/Hong_Kong + - Asia/Hovd + - Asia/Irkutsk + - Asia/Jakarta + - Asia/Jayapura + - Asia/Jerusalem + - Asia/Kabul + - Asia/Kamchatka + - Asia/Karachi + - Asia/Kathmandu + - Asia/Khandyga + - Asia/Kolkata + - Asia/Krasnoyarsk + - Asia/Kuala_Lumpur + - Asia/Kuching + - Asia/Kuwait + - Asia/Macau + - Asia/Magadan + - Asia/Makassar + - Asia/Manila + - Asia/Muscat + - Asia/Nicosia + - Asia/Novokuznetsk + - Asia/Novosibirsk + - Asia/Omsk + - Asia/Oral + - Asia/Phnom_Penh + - Asia/Pontianak + - Asia/Pyongyang + - Asia/Qatar + - Asia/Qostanay + - Asia/Qyzylorda + - Asia/Riyadh + - Asia/Sakhalin + - Asia/Samarkand + - Asia/Seoul + - Asia/Shanghai + - Asia/Singapore + - Asia/Srednekolymsk + - Asia/Taipei + - Asia/Tashkent + - Asia/Tbilisi + - Asia/Tehran + - Asia/Thimphu + - Asia/Tokyo + - Asia/Tomsk + - Asia/Ulaanbaatar + - Asia/Urumqi + - Asia/Ust-Nera + - Asia/Vientiane + - Asia/Vladivostok + - Asia/Yakutsk + - Asia/Yangon + - Asia/Yekaterinburg + - Asia/Yerevan + - Atlantic/Azores + - Atlantic/Bermuda + - Atlantic/Canary + - Atlantic/Cape_Verde + - Atlantic/Faroe + - Atlantic/Madeira + - Atlantic/Reykjavik + - Atlantic/South_Georgia + - Atlantic/St_Helena + - Atlantic/Stanley + - Australia/Adelaide + - Australia/Brisbane + - Australia/Broken_Hill + - Australia/Darwin + - Australia/Eucla + - Australia/Hobart + - Australia/Lindeman + - Australia/Lord_Howe + - Australia/Melbourne + - Australia/Perth + - Australia/Sydney + - Europe/Amsterdam + - Europe/Andorra + - Europe/Astrakhan + - Europe/Athens + - Europe/Belgrade + - Europe/Berlin + - Europe/Bratislava + - Europe/Brussels + - Europe/Bucharest + - Europe/Budapest + - Europe/Busingen + - Europe/Chisinau + - Europe/Copenhagen + - Europe/Dublin + - Europe/Gibraltar + - Europe/Guernsey + - Europe/Helsinki + - Europe/Isle_of_Man + - Europe/Istanbul + - Europe/Jersey + - Europe/Kaliningrad + - Europe/Kirov + - Europe/Kyiv + - Europe/Lisbon + - Europe/Ljubljana + - Europe/London + - Europe/Luxembourg + - Europe/Madrid + - Europe/Malta + - Europe/Mariehamn + - Europe/Minsk + - Europe/Monaco + - Europe/Moscow + - Europe/Oslo + - Europe/Paris + - Europe/Podgorica + - Europe/Prague + - Europe/Riga + - Europe/Rome + - Europe/Samara + - Europe/San_Marino + - Europe/Sarajevo + - Europe/Saratov + - Europe/Simferopol + - Europe/Skopje + - Europe/Sofia + - Europe/Stockholm + - Europe/Tallinn + - Europe/Tirane + - Europe/Ulyanovsk + - Europe/Vaduz + - Europe/Vatican + - Europe/Vienna + - Europe/Vilnius + - Europe/Volgograd + - Europe/Warsaw + - Europe/Zagreb + - Europe/Zurich + - GMT + - Indian/Antananarivo + - Indian/Chagos + - Indian/Christmas + - Indian/Cocos + - Indian/Comoro + - Indian/Kerguelen + - Indian/Mahe + - Indian/Maldives + - Indian/Mauritius + - Indian/Mayotte + - Indian/Reunion + - Pacific/Apia + - Pacific/Auckland + - Pacific/Bougainville + - Pacific/Chatham + - Pacific/Chuuk + - Pacific/Easter + - Pacific/Efate + - Pacific/Fakaofo + - Pacific/Fiji + - Pacific/Funafuti + - Pacific/Galapagos + - Pacific/Gambier + - Pacific/Guadalcanal + - Pacific/Guam + - Pacific/Honolulu + - Pacific/Kanton + - Pacific/Kiritimati + - Pacific/Kosrae + - Pacific/Kwajalein + - Pacific/Majuro + - Pacific/Marquesas + - Pacific/Midway + - Pacific/Nauru + - Pacific/Niue + - Pacific/Norfolk + - Pacific/Noumea + - Pacific/Pago_Pago + - Pacific/Palau + - Pacific/Pitcairn + - Pacific/Pohnpei + - Pacific/Port_Moresby + - Pacific/Rarotonga + - Pacific/Saipan + - Pacific/Tahiti + - Pacific/Tarawa + - Pacific/Tongatapu + - Pacific/Wake + - Pacific/Wallis + - UTC + type: string + description: |- + * `Africa/Abidjan` - Africa/Abidjan + * `Africa/Accra` - Africa/Accra + * `Africa/Addis_Ababa` - Africa/Addis_Ababa + * `Africa/Algiers` - Africa/Algiers + * `Africa/Asmara` - Africa/Asmara + * `Africa/Bamako` - Africa/Bamako + * `Africa/Bangui` - Africa/Bangui + * `Africa/Banjul` - Africa/Banjul + * `Africa/Bissau` - Africa/Bissau + * `Africa/Blantyre` - Africa/Blantyre + * `Africa/Brazzaville` - Africa/Brazzaville + * `Africa/Bujumbura` - Africa/Bujumbura + * `Africa/Cairo` - Africa/Cairo + * `Africa/Casablanca` - Africa/Casablanca + * `Africa/Ceuta` - Africa/Ceuta + * `Africa/Conakry` - Africa/Conakry + * `Africa/Dakar` - Africa/Dakar + * `Africa/Dar_es_Salaam` - Africa/Dar_es_Salaam + * `Africa/Djibouti` - Africa/Djibouti + * `Africa/Douala` - Africa/Douala + * `Africa/El_Aaiun` - Africa/El_Aaiun + * `Africa/Freetown` - Africa/Freetown + * `Africa/Gaborone` - Africa/Gaborone + * `Africa/Harare` - Africa/Harare + * `Africa/Johannesburg` - Africa/Johannesburg + * `Africa/Juba` - Africa/Juba + * `Africa/Kampala` - Africa/Kampala + * `Africa/Khartoum` - Africa/Khartoum + * `Africa/Kigali` - Africa/Kigali + * `Africa/Kinshasa` - Africa/Kinshasa + * `Africa/Lagos` - Africa/Lagos + * `Africa/Libreville` - Africa/Libreville + * `Africa/Lome` - Africa/Lome + * `Africa/Luanda` - Africa/Luanda + * `Africa/Lubumbashi` - Africa/Lubumbashi + * `Africa/Lusaka` - Africa/Lusaka + * `Africa/Malabo` - Africa/Malabo + * `Africa/Maputo` - Africa/Maputo + * `Africa/Maseru` - Africa/Maseru + * `Africa/Mbabane` - Africa/Mbabane + * `Africa/Mogadishu` - Africa/Mogadishu + * `Africa/Monrovia` - Africa/Monrovia + * `Africa/Nairobi` - Africa/Nairobi + * `Africa/Ndjamena` - Africa/Ndjamena + * `Africa/Niamey` - Africa/Niamey + * `Africa/Nouakchott` - Africa/Nouakchott + * `Africa/Ouagadougou` - Africa/Ouagadougou + * `Africa/Porto-Novo` - Africa/Porto-Novo + * `Africa/Sao_Tome` - Africa/Sao_Tome + * `Africa/Tripoli` - Africa/Tripoli + * `Africa/Tunis` - Africa/Tunis + * `Africa/Windhoek` - Africa/Windhoek + * `America/Adak` - America/Adak + * `America/Anchorage` - America/Anchorage + * `America/Anguilla` - America/Anguilla + * `America/Antigua` - America/Antigua + * `America/Araguaina` - America/Araguaina + * `America/Argentina/Buenos_Aires` - America/Argentina/Buenos_Aires + * `America/Argentina/Catamarca` - America/Argentina/Catamarca + * `America/Argentina/Cordoba` - America/Argentina/Cordoba + * `America/Argentina/Jujuy` - America/Argentina/Jujuy + * `America/Argentina/La_Rioja` - America/Argentina/La_Rioja + * `America/Argentina/Mendoza` - America/Argentina/Mendoza + * `America/Argentina/Rio_Gallegos` - America/Argentina/Rio_Gallegos + * `America/Argentina/Salta` - America/Argentina/Salta + * `America/Argentina/San_Juan` - America/Argentina/San_Juan + * `America/Argentina/San_Luis` - America/Argentina/San_Luis + * `America/Argentina/Tucuman` - America/Argentina/Tucuman + * `America/Argentina/Ushuaia` - America/Argentina/Ushuaia + * `America/Aruba` - America/Aruba + * `America/Asuncion` - America/Asuncion + * `America/Atikokan` - America/Atikokan + * `America/Bahia` - America/Bahia + * `America/Bahia_Banderas` - America/Bahia_Banderas + * `America/Barbados` - America/Barbados + * `America/Belem` - America/Belem + * `America/Belize` - America/Belize + * `America/Blanc-Sablon` - America/Blanc-Sablon + * `America/Boa_Vista` - America/Boa_Vista + * `America/Bogota` - America/Bogota + * `America/Boise` - America/Boise + * `America/Cambridge_Bay` - America/Cambridge_Bay + * `America/Campo_Grande` - America/Campo_Grande + * `America/Cancun` - America/Cancun + * `America/Caracas` - America/Caracas + * `America/Cayenne` - America/Cayenne + * `America/Cayman` - America/Cayman + * `America/Chicago` - America/Chicago + * `America/Chihuahua` - America/Chihuahua + * `America/Ciudad_Juarez` - America/Ciudad_Juarez + * `America/Costa_Rica` - America/Costa_Rica + * `America/Coyhaique` - America/Coyhaique + * `America/Creston` - America/Creston + * `America/Cuiaba` - America/Cuiaba + * `America/Curacao` - America/Curacao + * `America/Danmarkshavn` - America/Danmarkshavn + * `America/Dawson` - America/Dawson + * `America/Dawson_Creek` - America/Dawson_Creek + * `America/Denver` - America/Denver + * `America/Detroit` - America/Detroit + * `America/Dominica` - America/Dominica + * `America/Edmonton` - America/Edmonton + * `America/Eirunepe` - America/Eirunepe + * `America/El_Salvador` - America/El_Salvador + * `America/Fort_Nelson` - America/Fort_Nelson + * `America/Fortaleza` - America/Fortaleza + * `America/Glace_Bay` - America/Glace_Bay + * `America/Goose_Bay` - America/Goose_Bay + * `America/Grand_Turk` - America/Grand_Turk + * `America/Grenada` - America/Grenada + * `America/Guadeloupe` - America/Guadeloupe + * `America/Guatemala` - America/Guatemala + * `America/Guayaquil` - America/Guayaquil + * `America/Guyana` - America/Guyana + * `America/Halifax` - America/Halifax + * `America/Havana` - America/Havana + * `America/Hermosillo` - America/Hermosillo + * `America/Indiana/Indianapolis` - America/Indiana/Indianapolis + * `America/Indiana/Knox` - America/Indiana/Knox + * `America/Indiana/Marengo` - America/Indiana/Marengo + * `America/Indiana/Petersburg` - America/Indiana/Petersburg + * `America/Indiana/Tell_City` - America/Indiana/Tell_City + * `America/Indiana/Vevay` - America/Indiana/Vevay + * `America/Indiana/Vincennes` - America/Indiana/Vincennes + * `America/Indiana/Winamac` - America/Indiana/Winamac + * `America/Inuvik` - America/Inuvik + * `America/Iqaluit` - America/Iqaluit + * `America/Jamaica` - America/Jamaica + * `America/Juneau` - America/Juneau + * `America/Kentucky/Louisville` - America/Kentucky/Louisville + * `America/Kentucky/Monticello` - America/Kentucky/Monticello + * `America/Kralendijk` - America/Kralendijk + * `America/La_Paz` - America/La_Paz + * `America/Lima` - America/Lima + * `America/Los_Angeles` - America/Los_Angeles + * `America/Lower_Princes` - America/Lower_Princes + * `America/Maceio` - America/Maceio + * `America/Managua` - America/Managua + * `America/Manaus` - America/Manaus + * `America/Marigot` - America/Marigot + * `America/Martinique` - America/Martinique + * `America/Matamoros` - America/Matamoros + * `America/Mazatlan` - America/Mazatlan + * `America/Menominee` - America/Menominee + * `America/Merida` - America/Merida + * `America/Metlakatla` - America/Metlakatla + * `America/Mexico_City` - America/Mexico_City + * `America/Miquelon` - America/Miquelon + * `America/Moncton` - America/Moncton + * `America/Monterrey` - America/Monterrey + * `America/Montevideo` - America/Montevideo + * `America/Montserrat` - America/Montserrat + * `America/Nassau` - America/Nassau + * `America/New_York` - America/New_York + * `America/Nome` - America/Nome + * `America/Noronha` - America/Noronha + * `America/North_Dakota/Beulah` - America/North_Dakota/Beulah + * `America/North_Dakota/Center` - America/North_Dakota/Center + * `America/North_Dakota/New_Salem` - America/North_Dakota/New_Salem + * `America/Nuuk` - America/Nuuk + * `America/Ojinaga` - America/Ojinaga + * `America/Panama` - America/Panama + * `America/Paramaribo` - America/Paramaribo + * `America/Phoenix` - America/Phoenix + * `America/Port-au-Prince` - America/Port-au-Prince + * `America/Port_of_Spain` - America/Port_of_Spain + * `America/Porto_Velho` - America/Porto_Velho + * `America/Puerto_Rico` - America/Puerto_Rico + * `America/Punta_Arenas` - America/Punta_Arenas + * `America/Rankin_Inlet` - America/Rankin_Inlet + * `America/Recife` - America/Recife + * `America/Regina` - America/Regina + * `America/Resolute` - America/Resolute + * `America/Rio_Branco` - America/Rio_Branco + * `America/Santarem` - America/Santarem + * `America/Santiago` - America/Santiago + * `America/Santo_Domingo` - America/Santo_Domingo + * `America/Sao_Paulo` - America/Sao_Paulo + * `America/Scoresbysund` - America/Scoresbysund + * `America/Sitka` - America/Sitka + * `America/St_Barthelemy` - America/St_Barthelemy + * `America/St_Johns` - America/St_Johns + * `America/St_Kitts` - America/St_Kitts + * `America/St_Lucia` - America/St_Lucia + * `America/St_Thomas` - America/St_Thomas + * `America/St_Vincent` - America/St_Vincent + * `America/Swift_Current` - America/Swift_Current + * `America/Tegucigalpa` - America/Tegucigalpa + * `America/Thule` - America/Thule + * `America/Tijuana` - America/Tijuana + * `America/Toronto` - America/Toronto + * `America/Tortola` - America/Tortola + * `America/Vancouver` - America/Vancouver + * `America/Whitehorse` - America/Whitehorse + * `America/Winnipeg` - America/Winnipeg + * `America/Yakutat` - America/Yakutat + * `Antarctica/Casey` - Antarctica/Casey + * `Antarctica/Davis` - Antarctica/Davis + * `Antarctica/DumontDUrville` - Antarctica/DumontDUrville + * `Antarctica/Macquarie` - Antarctica/Macquarie + * `Antarctica/Mawson` - Antarctica/Mawson + * `Antarctica/McMurdo` - Antarctica/McMurdo + * `Antarctica/Palmer` - Antarctica/Palmer + * `Antarctica/Rothera` - Antarctica/Rothera + * `Antarctica/Syowa` - Antarctica/Syowa + * `Antarctica/Troll` - Antarctica/Troll + * `Antarctica/Vostok` - Antarctica/Vostok + * `Arctic/Longyearbyen` - Arctic/Longyearbyen + * `Asia/Aden` - Asia/Aden + * `Asia/Almaty` - Asia/Almaty + * `Asia/Amman` - Asia/Amman + * `Asia/Anadyr` - Asia/Anadyr + * `Asia/Aqtau` - Asia/Aqtau + * `Asia/Aqtobe` - Asia/Aqtobe + * `Asia/Ashgabat` - Asia/Ashgabat + * `Asia/Atyrau` - Asia/Atyrau + * `Asia/Baghdad` - Asia/Baghdad + * `Asia/Bahrain` - Asia/Bahrain + * `Asia/Baku` - Asia/Baku + * `Asia/Bangkok` - Asia/Bangkok + * `Asia/Barnaul` - Asia/Barnaul + * `Asia/Beirut` - Asia/Beirut + * `Asia/Bishkek` - Asia/Bishkek + * `Asia/Brunei` - Asia/Brunei + * `Asia/Chita` - Asia/Chita + * `Asia/Colombo` - Asia/Colombo + * `Asia/Damascus` - Asia/Damascus + * `Asia/Dhaka` - Asia/Dhaka + * `Asia/Dili` - Asia/Dili + * `Asia/Dubai` - Asia/Dubai + * `Asia/Dushanbe` - Asia/Dushanbe + * `Asia/Famagusta` - Asia/Famagusta + * `Asia/Gaza` - Asia/Gaza + * `Asia/Hebron` - Asia/Hebron + * `Asia/Ho_Chi_Minh` - Asia/Ho_Chi_Minh + * `Asia/Hong_Kong` - Asia/Hong_Kong + * `Asia/Hovd` - Asia/Hovd + * `Asia/Irkutsk` - Asia/Irkutsk + * `Asia/Jakarta` - Asia/Jakarta + * `Asia/Jayapura` - Asia/Jayapura + * `Asia/Jerusalem` - Asia/Jerusalem + * `Asia/Kabul` - Asia/Kabul + * `Asia/Kamchatka` - Asia/Kamchatka + * `Asia/Karachi` - Asia/Karachi + * `Asia/Kathmandu` - Asia/Kathmandu + * `Asia/Khandyga` - Asia/Khandyga + * `Asia/Kolkata` - Asia/Kolkata + * `Asia/Krasnoyarsk` - Asia/Krasnoyarsk + * `Asia/Kuala_Lumpur` - Asia/Kuala_Lumpur + * `Asia/Kuching` - Asia/Kuching + * `Asia/Kuwait` - Asia/Kuwait + * `Asia/Macau` - Asia/Macau + * `Asia/Magadan` - Asia/Magadan + * `Asia/Makassar` - Asia/Makassar + * `Asia/Manila` - Asia/Manila + * `Asia/Muscat` - Asia/Muscat + * `Asia/Nicosia` - Asia/Nicosia + * `Asia/Novokuznetsk` - Asia/Novokuznetsk + * `Asia/Novosibirsk` - Asia/Novosibirsk + * `Asia/Omsk` - Asia/Omsk + * `Asia/Oral` - Asia/Oral + * `Asia/Phnom_Penh` - Asia/Phnom_Penh + * `Asia/Pontianak` - Asia/Pontianak + * `Asia/Pyongyang` - Asia/Pyongyang + * `Asia/Qatar` - Asia/Qatar + * `Asia/Qostanay` - Asia/Qostanay + * `Asia/Qyzylorda` - Asia/Qyzylorda + * `Asia/Riyadh` - Asia/Riyadh + * `Asia/Sakhalin` - Asia/Sakhalin + * `Asia/Samarkand` - Asia/Samarkand + * `Asia/Seoul` - Asia/Seoul + * `Asia/Shanghai` - Asia/Shanghai + * `Asia/Singapore` - Asia/Singapore + * `Asia/Srednekolymsk` - Asia/Srednekolymsk + * `Asia/Taipei` - Asia/Taipei + * `Asia/Tashkent` - Asia/Tashkent + * `Asia/Tbilisi` - Asia/Tbilisi + * `Asia/Tehran` - Asia/Tehran + * `Asia/Thimphu` - Asia/Thimphu + * `Asia/Tokyo` - Asia/Tokyo + * `Asia/Tomsk` - Asia/Tomsk + * `Asia/Ulaanbaatar` - Asia/Ulaanbaatar + * `Asia/Urumqi` - Asia/Urumqi + * `Asia/Ust-Nera` - Asia/Ust-Nera + * `Asia/Vientiane` - Asia/Vientiane + * `Asia/Vladivostok` - Asia/Vladivostok + * `Asia/Yakutsk` - Asia/Yakutsk + * `Asia/Yangon` - Asia/Yangon + * `Asia/Yekaterinburg` - Asia/Yekaterinburg + * `Asia/Yerevan` - Asia/Yerevan + * `Atlantic/Azores` - Atlantic/Azores + * `Atlantic/Bermuda` - Atlantic/Bermuda + * `Atlantic/Canary` - Atlantic/Canary + * `Atlantic/Cape_Verde` - Atlantic/Cape_Verde + * `Atlantic/Faroe` - Atlantic/Faroe + * `Atlantic/Madeira` - Atlantic/Madeira + * `Atlantic/Reykjavik` - Atlantic/Reykjavik + * `Atlantic/South_Georgia` - Atlantic/South_Georgia + * `Atlantic/St_Helena` - Atlantic/St_Helena + * `Atlantic/Stanley` - Atlantic/Stanley + * `Australia/Adelaide` - Australia/Adelaide + * `Australia/Brisbane` - Australia/Brisbane + * `Australia/Broken_Hill` - Australia/Broken_Hill + * `Australia/Darwin` - Australia/Darwin + * `Australia/Eucla` - Australia/Eucla + * `Australia/Hobart` - Australia/Hobart + * `Australia/Lindeman` - Australia/Lindeman + * `Australia/Lord_Howe` - Australia/Lord_Howe + * `Australia/Melbourne` - Australia/Melbourne + * `Australia/Perth` - Australia/Perth + * `Australia/Sydney` - Australia/Sydney + * `Europe/Amsterdam` - Europe/Amsterdam + * `Europe/Andorra` - Europe/Andorra + * `Europe/Astrakhan` - Europe/Astrakhan + * `Europe/Athens` - Europe/Athens + * `Europe/Belgrade` - Europe/Belgrade + * `Europe/Berlin` - Europe/Berlin + * `Europe/Bratislava` - Europe/Bratislava + * `Europe/Brussels` - Europe/Brussels + * `Europe/Bucharest` - Europe/Bucharest + * `Europe/Budapest` - Europe/Budapest + * `Europe/Busingen` - Europe/Busingen + * `Europe/Chisinau` - Europe/Chisinau + * `Europe/Copenhagen` - Europe/Copenhagen + * `Europe/Dublin` - Europe/Dublin + * `Europe/Gibraltar` - Europe/Gibraltar + * `Europe/Guernsey` - Europe/Guernsey + * `Europe/Helsinki` - Europe/Helsinki + * `Europe/Isle_of_Man` - Europe/Isle_of_Man + * `Europe/Istanbul` - Europe/Istanbul + * `Europe/Jersey` - Europe/Jersey + * `Europe/Kaliningrad` - Europe/Kaliningrad + * `Europe/Kirov` - Europe/Kirov + * `Europe/Kyiv` - Europe/Kyiv + * `Europe/Lisbon` - Europe/Lisbon + * `Europe/Ljubljana` - Europe/Ljubljana + * `Europe/London` - Europe/London + * `Europe/Luxembourg` - Europe/Luxembourg + * `Europe/Madrid` - Europe/Madrid + * `Europe/Malta` - Europe/Malta + * `Europe/Mariehamn` - Europe/Mariehamn + * `Europe/Minsk` - Europe/Minsk + * `Europe/Monaco` - Europe/Monaco + * `Europe/Moscow` - Europe/Moscow + * `Europe/Oslo` - Europe/Oslo + * `Europe/Paris` - Europe/Paris + * `Europe/Podgorica` - Europe/Podgorica + * `Europe/Prague` - Europe/Prague + * `Europe/Riga` - Europe/Riga + * `Europe/Rome` - Europe/Rome + * `Europe/Samara` - Europe/Samara + * `Europe/San_Marino` - Europe/San_Marino + * `Europe/Sarajevo` - Europe/Sarajevo + * `Europe/Saratov` - Europe/Saratov + * `Europe/Simferopol` - Europe/Simferopol + * `Europe/Skopje` - Europe/Skopje + * `Europe/Sofia` - Europe/Sofia + * `Europe/Stockholm` - Europe/Stockholm + * `Europe/Tallinn` - Europe/Tallinn + * `Europe/Tirane` - Europe/Tirane + * `Europe/Ulyanovsk` - Europe/Ulyanovsk + * `Europe/Vaduz` - Europe/Vaduz + * `Europe/Vatican` - Europe/Vatican + * `Europe/Vienna` - Europe/Vienna + * `Europe/Vilnius` - Europe/Vilnius + * `Europe/Volgograd` - Europe/Volgograd + * `Europe/Warsaw` - Europe/Warsaw + * `Europe/Zagreb` - Europe/Zagreb + * `Europe/Zurich` - Europe/Zurich + * `GMT` - GMT + * `Indian/Antananarivo` - Indian/Antananarivo + * `Indian/Chagos` - Indian/Chagos + * `Indian/Christmas` - Indian/Christmas + * `Indian/Cocos` - Indian/Cocos + * `Indian/Comoro` - Indian/Comoro + * `Indian/Kerguelen` - Indian/Kerguelen + * `Indian/Mahe` - Indian/Mahe + * `Indian/Maldives` - Indian/Maldives + * `Indian/Mauritius` - Indian/Mauritius + * `Indian/Mayotte` - Indian/Mayotte + * `Indian/Reunion` - Indian/Reunion + * `Pacific/Apia` - Pacific/Apia + * `Pacific/Auckland` - Pacific/Auckland + * `Pacific/Bougainville` - Pacific/Bougainville + * `Pacific/Chatham` - Pacific/Chatham + * `Pacific/Chuuk` - Pacific/Chuuk + * `Pacific/Easter` - Pacific/Easter + * `Pacific/Efate` - Pacific/Efate + * `Pacific/Fakaofo` - Pacific/Fakaofo + * `Pacific/Fiji` - Pacific/Fiji + * `Pacific/Funafuti` - Pacific/Funafuti + * `Pacific/Galapagos` - Pacific/Galapagos + * `Pacific/Gambier` - Pacific/Gambier + * `Pacific/Guadalcanal` - Pacific/Guadalcanal + * `Pacific/Guam` - Pacific/Guam + * `Pacific/Honolulu` - Pacific/Honolulu + * `Pacific/Kanton` - Pacific/Kanton + * `Pacific/Kiritimati` - Pacific/Kiritimati + * `Pacific/Kosrae` - Pacific/Kosrae + * `Pacific/Kwajalein` - Pacific/Kwajalein + * `Pacific/Majuro` - Pacific/Majuro + * `Pacific/Marquesas` - Pacific/Marquesas + * `Pacific/Midway` - Pacific/Midway + * `Pacific/Nauru` - Pacific/Nauru + * `Pacific/Niue` - Pacific/Niue + * `Pacific/Norfolk` - Pacific/Norfolk + * `Pacific/Noumea` - Pacific/Noumea + * `Pacific/Pago_Pago` - Pacific/Pago_Pago + * `Pacific/Palau` - Pacific/Palau + * `Pacific/Pitcairn` - Pacific/Pitcairn + * `Pacific/Pohnpei` - Pacific/Pohnpei + * `Pacific/Port_Moresby` - Pacific/Port_Moresby + * `Pacific/Rarotonga` - Pacific/Rarotonga + * `Pacific/Saipan` - Pacific/Saipan + * `Pacific/Tahiti` - Pacific/Tahiti + * `Pacific/Tarawa` - Pacific/Tarawa + * `Pacific/Tongatapu` - Pacific/Tongatapu + * `Pacific/Wake` - Pacific/Wake + * `Pacific/Wallis` - Pacific/Wallis + * `UTC` - UTC + ImpactEnum: + enum: + - High + - Medium + - Low + type: string + description: |- + * `High` - High + * `Medium` - Medium + * `Low` - Low + ImpactLevelEnum: + enum: + - High + - Medium + - Low + type: string + description: |- + * `High` - High + * `Medium` - Medium + * `Low` - Low + ImpactTypeEnum: + enum: + - Revenue + - CX Improvement + - EX Improvement + - Cost Reduction + - Risk Reduction + type: string + description: |- + * `Revenue` - Revenue + * `CX Improvement` - CX Improvement + * `EX Improvement` - EX Improvement + * `Cost Reduction` - Cost Reduction + * `Risk Reduction` - Risk Reduction + InterestEnum: + enum: + - High + - Medium + - Low + type: string + description: |- + * `High` - High + * `Medium` - Medium + * `Low` - Low + Interview: + type: object + properties: + id: + type: integer + readOnly: true + name: + type: string + maxLength: 255 + contact: + allOf: + - $ref: '#/components/schemas/Contact' + readOnly: true + date: + type: string + format: date + time: + type: string + format: time + notes: + type: string + required: + - contact + - date + - id + - name + - time + InterviewRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 255 + contact_id: + type: integer + writeOnly: true + date: + type: string + format: date + time: + type: string + format: time + notes: + type: string + required: + - contact_id + - date + - name + - time + Issue: + type: object + properties: + id: + type: integer + readOnly: true + title: + type: string + maxLength: 255 + description: + type: string + potential_impact: + $ref: '#/components/schemas/PotentialImpactEnum' + owner: + type: integer + owner_name: + type: string + readOnly: true + priority: + $ref: '#/components/schemas/Priority758Enum' + status: + $ref: '#/components/schemas/IssueStatusEnum' + due_date: + type: string + format: date + required: + - description + - due_date + - id + - owner + - owner_name + - potential_impact + - priority + - status + - title + IssueStatusEnum: + enum: + - Open + - In Progress + - Resolved + - Closed + type: string + description: |- + * `Open` - Open + * `In Progress` - In Progress + * `Resolved` - Resolved + * `Closed` - Closed + LocalTimezoneEnum: + enum: + - Africa/Abidjan + - Africa/Accra + - Africa/Addis_Ababa + - Africa/Algiers + - Africa/Asmara + - Africa/Bamako + - Africa/Bangui + - Africa/Banjul + - Africa/Bissau + - Africa/Blantyre + - Africa/Brazzaville + - Africa/Bujumbura + - Africa/Cairo + - Africa/Casablanca + - Africa/Ceuta + - Africa/Conakry + - Africa/Dakar + - Africa/Dar_es_Salaam + - Africa/Djibouti + - Africa/Douala + - Africa/El_Aaiun + - Africa/Freetown + - Africa/Gaborone + - Africa/Harare + - Africa/Johannesburg + - Africa/Juba + - Africa/Kampala + - Africa/Khartoum + - Africa/Kigali + - Africa/Kinshasa + - Africa/Lagos + - Africa/Libreville + - Africa/Lome + - Africa/Luanda + - Africa/Lubumbashi + - Africa/Lusaka + - Africa/Malabo + - Africa/Maputo + - Africa/Maseru + - Africa/Mbabane + - Africa/Mogadishu + - Africa/Monrovia + - Africa/Nairobi + - Africa/Ndjamena + - Africa/Niamey + - Africa/Nouakchott + - Africa/Ouagadougou + - Africa/Porto-Novo + - Africa/Sao_Tome + - Africa/Tripoli + - Africa/Tunis + - Africa/Windhoek + - America/Adak + - America/Anchorage + - America/Anguilla + - America/Antigua + - America/Araguaina + - America/Argentina/Buenos_Aires + - America/Argentina/Catamarca + - America/Argentina/Cordoba + - America/Argentina/Jujuy + - America/Argentina/La_Rioja + - America/Argentina/Mendoza + - America/Argentina/Rio_Gallegos + - America/Argentina/Salta + - America/Argentina/San_Juan + - America/Argentina/San_Luis + - America/Argentina/Tucuman + - America/Argentina/Ushuaia + - America/Aruba + - America/Asuncion + - America/Atikokan + - America/Bahia + - America/Bahia_Banderas + - America/Barbados + - America/Belem + - America/Belize + - America/Blanc-Sablon + - America/Boa_Vista + - America/Bogota + - America/Boise + - America/Cambridge_Bay + - America/Campo_Grande + - America/Cancun + - America/Caracas + - America/Cayenne + - America/Cayman + - America/Chicago + - America/Chihuahua + - America/Ciudad_Juarez + - America/Costa_Rica + - America/Coyhaique + - America/Creston + - America/Cuiaba + - America/Curacao + - America/Danmarkshavn + - America/Dawson + - America/Dawson_Creek + - America/Denver + - America/Detroit + - America/Dominica + - America/Edmonton + - America/Eirunepe + - America/El_Salvador + - America/Fort_Nelson + - America/Fortaleza + - America/Glace_Bay + - America/Goose_Bay + - America/Grand_Turk + - America/Grenada + - America/Guadeloupe + - America/Guatemala + - America/Guayaquil + - America/Guyana + - America/Halifax + - America/Havana + - America/Hermosillo + - America/Indiana/Indianapolis + - America/Indiana/Knox + - America/Indiana/Marengo + - America/Indiana/Petersburg + - America/Indiana/Tell_City + - America/Indiana/Vevay + - America/Indiana/Vincennes + - America/Indiana/Winamac + - America/Inuvik + - America/Iqaluit + - America/Jamaica + - America/Juneau + - America/Kentucky/Louisville + - America/Kentucky/Monticello + - America/Kralendijk + - America/La_Paz + - America/Lima + - America/Los_Angeles + - America/Lower_Princes + - America/Maceio + - America/Managua + - America/Manaus + - America/Marigot + - America/Martinique + - America/Matamoros + - America/Mazatlan + - America/Menominee + - America/Merida + - America/Metlakatla + - America/Mexico_City + - America/Miquelon + - America/Moncton + - America/Monterrey + - America/Montevideo + - America/Montserrat + - America/Nassau + - America/New_York + - America/Nome + - America/Noronha + - America/North_Dakota/Beulah + - America/North_Dakota/Center + - America/North_Dakota/New_Salem + - America/Nuuk + - America/Ojinaga + - America/Panama + - America/Paramaribo + - America/Phoenix + - America/Port-au-Prince + - America/Port_of_Spain + - America/Porto_Velho + - America/Puerto_Rico + - America/Punta_Arenas + - America/Rankin_Inlet + - America/Recife + - America/Regina + - America/Resolute + - America/Rio_Branco + - America/Santarem + - America/Santiago + - America/Santo_Domingo + - America/Sao_Paulo + - America/Scoresbysund + - America/Sitka + - America/St_Barthelemy + - America/St_Johns + - America/St_Kitts + - America/St_Lucia + - America/St_Thomas + - America/St_Vincent + - America/Swift_Current + - America/Tegucigalpa + - America/Thule + - America/Tijuana + - America/Toronto + - America/Tortola + - America/Vancouver + - America/Whitehorse + - America/Winnipeg + - America/Yakutat + - Antarctica/Casey + - Antarctica/Davis + - Antarctica/DumontDUrville + - Antarctica/Macquarie + - Antarctica/Mawson + - Antarctica/McMurdo + - Antarctica/Palmer + - Antarctica/Rothera + - Antarctica/Syowa + - Antarctica/Troll + - Antarctica/Vostok + - Arctic/Longyearbyen + - Asia/Aden + - Asia/Almaty + - Asia/Amman + - Asia/Anadyr + - Asia/Aqtau + - Asia/Aqtobe + - Asia/Ashgabat + - Asia/Atyrau + - Asia/Baghdad + - Asia/Bahrain + - Asia/Baku + - Asia/Bangkok + - Asia/Barnaul + - Asia/Beirut + - Asia/Bishkek + - Asia/Brunei + - Asia/Chita + - Asia/Colombo + - Asia/Damascus + - Asia/Dhaka + - Asia/Dili + - Asia/Dubai + - Asia/Dushanbe + - Asia/Famagusta + - Asia/Gaza + - Asia/Hebron + - Asia/Ho_Chi_Minh + - Asia/Hong_Kong + - Asia/Hovd + - Asia/Irkutsk + - Asia/Jakarta + - Asia/Jayapura + - Asia/Jerusalem + - Asia/Kabul + - Asia/Kamchatka + - Asia/Karachi + - Asia/Kathmandu + - Asia/Khandyga + - Asia/Kolkata + - Asia/Krasnoyarsk + - Asia/Kuala_Lumpur + - Asia/Kuching + - Asia/Kuwait + - Asia/Macau + - Asia/Magadan + - Asia/Makassar + - Asia/Manila + - Asia/Muscat + - Asia/Nicosia + - Asia/Novokuznetsk + - Asia/Novosibirsk + - Asia/Omsk + - Asia/Oral + - Asia/Phnom_Penh + - Asia/Pontianak + - Asia/Pyongyang + - Asia/Qatar + - Asia/Qostanay + - Asia/Qyzylorda + - Asia/Riyadh + - Asia/Sakhalin + - Asia/Samarkand + - Asia/Seoul + - Asia/Shanghai + - Asia/Singapore + - Asia/Srednekolymsk + - Asia/Taipei + - Asia/Tashkent + - Asia/Tbilisi + - Asia/Tehran + - Asia/Thimphu + - Asia/Tokyo + - Asia/Tomsk + - Asia/Ulaanbaatar + - Asia/Urumqi + - Asia/Ust-Nera + - Asia/Vientiane + - Asia/Vladivostok + - Asia/Yakutsk + - Asia/Yangon + - Asia/Yekaterinburg + - Asia/Yerevan + - Atlantic/Azores + - Atlantic/Bermuda + - Atlantic/Canary + - Atlantic/Cape_Verde + - Atlantic/Faroe + - Atlantic/Madeira + - Atlantic/Reykjavik + - Atlantic/South_Georgia + - Atlantic/St_Helena + - Atlantic/Stanley + - Australia/Adelaide + - Australia/Brisbane + - Australia/Broken_Hill + - Australia/Darwin + - Australia/Eucla + - Australia/Hobart + - Australia/Lindeman + - Australia/Lord_Howe + - Australia/Melbourne + - Australia/Perth + - Australia/Sydney + - Europe/Amsterdam + - Europe/Andorra + - Europe/Astrakhan + - Europe/Athens + - Europe/Belgrade + - Europe/Berlin + - Europe/Bratislava + - Europe/Brussels + - Europe/Bucharest + - Europe/Budapest + - Europe/Busingen + - Europe/Chisinau + - Europe/Copenhagen + - Europe/Dublin + - Europe/Gibraltar + - Europe/Guernsey + - Europe/Helsinki + - Europe/Isle_of_Man + - Europe/Istanbul + - Europe/Jersey + - Europe/Kaliningrad + - Europe/Kirov + - Europe/Kyiv + - Europe/Lisbon + - Europe/Ljubljana + - Europe/London + - Europe/Luxembourg + - Europe/Madrid + - Europe/Malta + - Europe/Mariehamn + - Europe/Minsk + - Europe/Monaco + - Europe/Moscow + - Europe/Oslo + - Europe/Paris + - Europe/Podgorica + - Europe/Prague + - Europe/Riga + - Europe/Rome + - Europe/Samara + - Europe/San_Marino + - Europe/Sarajevo + - Europe/Saratov + - Europe/Simferopol + - Europe/Skopje + - Europe/Sofia + - Europe/Stockholm + - Europe/Tallinn + - Europe/Tirane + - Europe/Ulyanovsk + - Europe/Vaduz + - Europe/Vatican + - Europe/Vienna + - Europe/Vilnius + - Europe/Volgograd + - Europe/Warsaw + - Europe/Zagreb + - Europe/Zurich + - GMT + - Indian/Antananarivo + - Indian/Chagos + - Indian/Christmas + - Indian/Cocos + - Indian/Comoro + - Indian/Kerguelen + - Indian/Mahe + - Indian/Maldives + - Indian/Mauritius + - Indian/Mayotte + - Indian/Reunion + - Pacific/Apia + - Pacific/Auckland + - Pacific/Bougainville + - Pacific/Chatham + - Pacific/Chuuk + - Pacific/Easter + - Pacific/Efate + - Pacific/Fakaofo + - Pacific/Fiji + - Pacific/Funafuti + - Pacific/Galapagos + - Pacific/Gambier + - Pacific/Guadalcanal + - Pacific/Guam + - Pacific/Honolulu + - Pacific/Kanton + - Pacific/Kiritimati + - Pacific/Kosrae + - Pacific/Kwajalein + - Pacific/Majuro + - Pacific/Marquesas + - Pacific/Midway + - Pacific/Nauru + - Pacific/Niue + - Pacific/Norfolk + - Pacific/Noumea + - Pacific/Pago_Pago + - Pacific/Palau + - Pacific/Pitcairn + - Pacific/Pohnpei + - Pacific/Port_Moresby + - Pacific/Rarotonga + - Pacific/Saipan + - Pacific/Tahiti + - Pacific/Tarawa + - Pacific/Tongatapu + - Pacific/Wake + - Pacific/Wallis + - UTC + type: string + description: |- + * `Africa/Abidjan` - Africa/Abidjan + * `Africa/Accra` - Africa/Accra + * `Africa/Addis_Ababa` - Africa/Addis_Ababa + * `Africa/Algiers` - Africa/Algiers + * `Africa/Asmara` - Africa/Asmara + * `Africa/Bamako` - Africa/Bamako + * `Africa/Bangui` - Africa/Bangui + * `Africa/Banjul` - Africa/Banjul + * `Africa/Bissau` - Africa/Bissau + * `Africa/Blantyre` - Africa/Blantyre + * `Africa/Brazzaville` - Africa/Brazzaville + * `Africa/Bujumbura` - Africa/Bujumbura + * `Africa/Cairo` - Africa/Cairo + * `Africa/Casablanca` - Africa/Casablanca + * `Africa/Ceuta` - Africa/Ceuta + * `Africa/Conakry` - Africa/Conakry + * `Africa/Dakar` - Africa/Dakar + * `Africa/Dar_es_Salaam` - Africa/Dar_es_Salaam + * `Africa/Djibouti` - Africa/Djibouti + * `Africa/Douala` - Africa/Douala + * `Africa/El_Aaiun` - Africa/El_Aaiun + * `Africa/Freetown` - Africa/Freetown + * `Africa/Gaborone` - Africa/Gaborone + * `Africa/Harare` - Africa/Harare + * `Africa/Johannesburg` - Africa/Johannesburg + * `Africa/Juba` - Africa/Juba + * `Africa/Kampala` - Africa/Kampala + * `Africa/Khartoum` - Africa/Khartoum + * `Africa/Kigali` - Africa/Kigali + * `Africa/Kinshasa` - Africa/Kinshasa + * `Africa/Lagos` - Africa/Lagos + * `Africa/Libreville` - Africa/Libreville + * `Africa/Lome` - Africa/Lome + * `Africa/Luanda` - Africa/Luanda + * `Africa/Lubumbashi` - Africa/Lubumbashi + * `Africa/Lusaka` - Africa/Lusaka + * `Africa/Malabo` - Africa/Malabo + * `Africa/Maputo` - Africa/Maputo + * `Africa/Maseru` - Africa/Maseru + * `Africa/Mbabane` - Africa/Mbabane + * `Africa/Mogadishu` - Africa/Mogadishu + * `Africa/Monrovia` - Africa/Monrovia + * `Africa/Nairobi` - Africa/Nairobi + * `Africa/Ndjamena` - Africa/Ndjamena + * `Africa/Niamey` - Africa/Niamey + * `Africa/Nouakchott` - Africa/Nouakchott + * `Africa/Ouagadougou` - Africa/Ouagadougou + * `Africa/Porto-Novo` - Africa/Porto-Novo + * `Africa/Sao_Tome` - Africa/Sao_Tome + * `Africa/Tripoli` - Africa/Tripoli + * `Africa/Tunis` - Africa/Tunis + * `Africa/Windhoek` - Africa/Windhoek + * `America/Adak` - America/Adak + * `America/Anchorage` - America/Anchorage + * `America/Anguilla` - America/Anguilla + * `America/Antigua` - America/Antigua + * `America/Araguaina` - America/Araguaina + * `America/Argentina/Buenos_Aires` - America/Argentina/Buenos_Aires + * `America/Argentina/Catamarca` - America/Argentina/Catamarca + * `America/Argentina/Cordoba` - America/Argentina/Cordoba + * `America/Argentina/Jujuy` - America/Argentina/Jujuy + * `America/Argentina/La_Rioja` - America/Argentina/La_Rioja + * `America/Argentina/Mendoza` - America/Argentina/Mendoza + * `America/Argentina/Rio_Gallegos` - America/Argentina/Rio_Gallegos + * `America/Argentina/Salta` - America/Argentina/Salta + * `America/Argentina/San_Juan` - America/Argentina/San_Juan + * `America/Argentina/San_Luis` - America/Argentina/San_Luis + * `America/Argentina/Tucuman` - America/Argentina/Tucuman + * `America/Argentina/Ushuaia` - America/Argentina/Ushuaia + * `America/Aruba` - America/Aruba + * `America/Asuncion` - America/Asuncion + * `America/Atikokan` - America/Atikokan + * `America/Bahia` - America/Bahia + * `America/Bahia_Banderas` - America/Bahia_Banderas + * `America/Barbados` - America/Barbados + * `America/Belem` - America/Belem + * `America/Belize` - America/Belize + * `America/Blanc-Sablon` - America/Blanc-Sablon + * `America/Boa_Vista` - America/Boa_Vista + * `America/Bogota` - America/Bogota + * `America/Boise` - America/Boise + * `America/Cambridge_Bay` - America/Cambridge_Bay + * `America/Campo_Grande` - America/Campo_Grande + * `America/Cancun` - America/Cancun + * `America/Caracas` - America/Caracas + * `America/Cayenne` - America/Cayenne + * `America/Cayman` - America/Cayman + * `America/Chicago` - America/Chicago + * `America/Chihuahua` - America/Chihuahua + * `America/Ciudad_Juarez` - America/Ciudad_Juarez + * `America/Costa_Rica` - America/Costa_Rica + * `America/Coyhaique` - America/Coyhaique + * `America/Creston` - America/Creston + * `America/Cuiaba` - America/Cuiaba + * `America/Curacao` - America/Curacao + * `America/Danmarkshavn` - America/Danmarkshavn + * `America/Dawson` - America/Dawson + * `America/Dawson_Creek` - America/Dawson_Creek + * `America/Denver` - America/Denver + * `America/Detroit` - America/Detroit + * `America/Dominica` - America/Dominica + * `America/Edmonton` - America/Edmonton + * `America/Eirunepe` - America/Eirunepe + * `America/El_Salvador` - America/El_Salvador + * `America/Fort_Nelson` - America/Fort_Nelson + * `America/Fortaleza` - America/Fortaleza + * `America/Glace_Bay` - America/Glace_Bay + * `America/Goose_Bay` - America/Goose_Bay + * `America/Grand_Turk` - America/Grand_Turk + * `America/Grenada` - America/Grenada + * `America/Guadeloupe` - America/Guadeloupe + * `America/Guatemala` - America/Guatemala + * `America/Guayaquil` - America/Guayaquil + * `America/Guyana` - America/Guyana + * `America/Halifax` - America/Halifax + * `America/Havana` - America/Havana + * `America/Hermosillo` - America/Hermosillo + * `America/Indiana/Indianapolis` - America/Indiana/Indianapolis + * `America/Indiana/Knox` - America/Indiana/Knox + * `America/Indiana/Marengo` - America/Indiana/Marengo + * `America/Indiana/Petersburg` - America/Indiana/Petersburg + * `America/Indiana/Tell_City` - America/Indiana/Tell_City + * `America/Indiana/Vevay` - America/Indiana/Vevay + * `America/Indiana/Vincennes` - America/Indiana/Vincennes + * `America/Indiana/Winamac` - America/Indiana/Winamac + * `America/Inuvik` - America/Inuvik + * `America/Iqaluit` - America/Iqaluit + * `America/Jamaica` - America/Jamaica + * `America/Juneau` - America/Juneau + * `America/Kentucky/Louisville` - America/Kentucky/Louisville + * `America/Kentucky/Monticello` - America/Kentucky/Monticello + * `America/Kralendijk` - America/Kralendijk + * `America/La_Paz` - America/La_Paz + * `America/Lima` - America/Lima + * `America/Los_Angeles` - America/Los_Angeles + * `America/Lower_Princes` - America/Lower_Princes + * `America/Maceio` - America/Maceio + * `America/Managua` - America/Managua + * `America/Manaus` - America/Manaus + * `America/Marigot` - America/Marigot + * `America/Martinique` - America/Martinique + * `America/Matamoros` - America/Matamoros + * `America/Mazatlan` - America/Mazatlan + * `America/Menominee` - America/Menominee + * `America/Merida` - America/Merida + * `America/Metlakatla` - America/Metlakatla + * `America/Mexico_City` - America/Mexico_City + * `America/Miquelon` - America/Miquelon + * `America/Moncton` - America/Moncton + * `America/Monterrey` - America/Monterrey + * `America/Montevideo` - America/Montevideo + * `America/Montserrat` - America/Montserrat + * `America/Nassau` - America/Nassau + * `America/New_York` - America/New_York + * `America/Nome` - America/Nome + * `America/Noronha` - America/Noronha + * `America/North_Dakota/Beulah` - America/North_Dakota/Beulah + * `America/North_Dakota/Center` - America/North_Dakota/Center + * `America/North_Dakota/New_Salem` - America/North_Dakota/New_Salem + * `America/Nuuk` - America/Nuuk + * `America/Ojinaga` - America/Ojinaga + * `America/Panama` - America/Panama + * `America/Paramaribo` - America/Paramaribo + * `America/Phoenix` - America/Phoenix + * `America/Port-au-Prince` - America/Port-au-Prince + * `America/Port_of_Spain` - America/Port_of_Spain + * `America/Porto_Velho` - America/Porto_Velho + * `America/Puerto_Rico` - America/Puerto_Rico + * `America/Punta_Arenas` - America/Punta_Arenas + * `America/Rankin_Inlet` - America/Rankin_Inlet + * `America/Recife` - America/Recife + * `America/Regina` - America/Regina + * `America/Resolute` - America/Resolute + * `America/Rio_Branco` - America/Rio_Branco + * `America/Santarem` - America/Santarem + * `America/Santiago` - America/Santiago + * `America/Santo_Domingo` - America/Santo_Domingo + * `America/Sao_Paulo` - America/Sao_Paulo + * `America/Scoresbysund` - America/Scoresbysund + * `America/Sitka` - America/Sitka + * `America/St_Barthelemy` - America/St_Barthelemy + * `America/St_Johns` - America/St_Johns + * `America/St_Kitts` - America/St_Kitts + * `America/St_Lucia` - America/St_Lucia + * `America/St_Thomas` - America/St_Thomas + * `America/St_Vincent` - America/St_Vincent + * `America/Swift_Current` - America/Swift_Current + * `America/Tegucigalpa` - America/Tegucigalpa + * `America/Thule` - America/Thule + * `America/Tijuana` - America/Tijuana + * `America/Toronto` - America/Toronto + * `America/Tortola` - America/Tortola + * `America/Vancouver` - America/Vancouver + * `America/Whitehorse` - America/Whitehorse + * `America/Winnipeg` - America/Winnipeg + * `America/Yakutat` - America/Yakutat + * `Antarctica/Casey` - Antarctica/Casey + * `Antarctica/Davis` - Antarctica/Davis + * `Antarctica/DumontDUrville` - Antarctica/DumontDUrville + * `Antarctica/Macquarie` - Antarctica/Macquarie + * `Antarctica/Mawson` - Antarctica/Mawson + * `Antarctica/McMurdo` - Antarctica/McMurdo + * `Antarctica/Palmer` - Antarctica/Palmer + * `Antarctica/Rothera` - Antarctica/Rothera + * `Antarctica/Syowa` - Antarctica/Syowa + * `Antarctica/Troll` - Antarctica/Troll + * `Antarctica/Vostok` - Antarctica/Vostok + * `Arctic/Longyearbyen` - Arctic/Longyearbyen + * `Asia/Aden` - Asia/Aden + * `Asia/Almaty` - Asia/Almaty + * `Asia/Amman` - Asia/Amman + * `Asia/Anadyr` - Asia/Anadyr + * `Asia/Aqtau` - Asia/Aqtau + * `Asia/Aqtobe` - Asia/Aqtobe + * `Asia/Ashgabat` - Asia/Ashgabat + * `Asia/Atyrau` - Asia/Atyrau + * `Asia/Baghdad` - Asia/Baghdad + * `Asia/Bahrain` - Asia/Bahrain + * `Asia/Baku` - Asia/Baku + * `Asia/Bangkok` - Asia/Bangkok + * `Asia/Barnaul` - Asia/Barnaul + * `Asia/Beirut` - Asia/Beirut + * `Asia/Bishkek` - Asia/Bishkek + * `Asia/Brunei` - Asia/Brunei + * `Asia/Chita` - Asia/Chita + * `Asia/Colombo` - Asia/Colombo + * `Asia/Damascus` - Asia/Damascus + * `Asia/Dhaka` - Asia/Dhaka + * `Asia/Dili` - Asia/Dili + * `Asia/Dubai` - Asia/Dubai + * `Asia/Dushanbe` - Asia/Dushanbe + * `Asia/Famagusta` - Asia/Famagusta + * `Asia/Gaza` - Asia/Gaza + * `Asia/Hebron` - Asia/Hebron + * `Asia/Ho_Chi_Minh` - Asia/Ho_Chi_Minh + * `Asia/Hong_Kong` - Asia/Hong_Kong + * `Asia/Hovd` - Asia/Hovd + * `Asia/Irkutsk` - Asia/Irkutsk + * `Asia/Jakarta` - Asia/Jakarta + * `Asia/Jayapura` - Asia/Jayapura + * `Asia/Jerusalem` - Asia/Jerusalem + * `Asia/Kabul` - Asia/Kabul + * `Asia/Kamchatka` - Asia/Kamchatka + * `Asia/Karachi` - Asia/Karachi + * `Asia/Kathmandu` - Asia/Kathmandu + * `Asia/Khandyga` - Asia/Khandyga + * `Asia/Kolkata` - Asia/Kolkata + * `Asia/Krasnoyarsk` - Asia/Krasnoyarsk + * `Asia/Kuala_Lumpur` - Asia/Kuala_Lumpur + * `Asia/Kuching` - Asia/Kuching + * `Asia/Kuwait` - Asia/Kuwait + * `Asia/Macau` - Asia/Macau + * `Asia/Magadan` - Asia/Magadan + * `Asia/Makassar` - Asia/Makassar + * `Asia/Manila` - Asia/Manila + * `Asia/Muscat` - Asia/Muscat + * `Asia/Nicosia` - Asia/Nicosia + * `Asia/Novokuznetsk` - Asia/Novokuznetsk + * `Asia/Novosibirsk` - Asia/Novosibirsk + * `Asia/Omsk` - Asia/Omsk + * `Asia/Oral` - Asia/Oral + * `Asia/Phnom_Penh` - Asia/Phnom_Penh + * `Asia/Pontianak` - Asia/Pontianak + * `Asia/Pyongyang` - Asia/Pyongyang + * `Asia/Qatar` - Asia/Qatar + * `Asia/Qostanay` - Asia/Qostanay + * `Asia/Qyzylorda` - Asia/Qyzylorda + * `Asia/Riyadh` - Asia/Riyadh + * `Asia/Sakhalin` - Asia/Sakhalin + * `Asia/Samarkand` - Asia/Samarkand + * `Asia/Seoul` - Asia/Seoul + * `Asia/Shanghai` - Asia/Shanghai + * `Asia/Singapore` - Asia/Singapore + * `Asia/Srednekolymsk` - Asia/Srednekolymsk + * `Asia/Taipei` - Asia/Taipei + * `Asia/Tashkent` - Asia/Tashkent + * `Asia/Tbilisi` - Asia/Tbilisi + * `Asia/Tehran` - Asia/Tehran + * `Asia/Thimphu` - Asia/Thimphu + * `Asia/Tokyo` - Asia/Tokyo + * `Asia/Tomsk` - Asia/Tomsk + * `Asia/Ulaanbaatar` - Asia/Ulaanbaatar + * `Asia/Urumqi` - Asia/Urumqi + * `Asia/Ust-Nera` - Asia/Ust-Nera + * `Asia/Vientiane` - Asia/Vientiane + * `Asia/Vladivostok` - Asia/Vladivostok + * `Asia/Yakutsk` - Asia/Yakutsk + * `Asia/Yangon` - Asia/Yangon + * `Asia/Yekaterinburg` - Asia/Yekaterinburg + * `Asia/Yerevan` - Asia/Yerevan + * `Atlantic/Azores` - Atlantic/Azores + * `Atlantic/Bermuda` - Atlantic/Bermuda + * `Atlantic/Canary` - Atlantic/Canary + * `Atlantic/Cape_Verde` - Atlantic/Cape_Verde + * `Atlantic/Faroe` - Atlantic/Faroe + * `Atlantic/Madeira` - Atlantic/Madeira + * `Atlantic/Reykjavik` - Atlantic/Reykjavik + * `Atlantic/South_Georgia` - Atlantic/South_Georgia + * `Atlantic/St_Helena` - Atlantic/St_Helena + * `Atlantic/Stanley` - Atlantic/Stanley + * `Australia/Adelaide` - Australia/Adelaide + * `Australia/Brisbane` - Australia/Brisbane + * `Australia/Broken_Hill` - Australia/Broken_Hill + * `Australia/Darwin` - Australia/Darwin + * `Australia/Eucla` - Australia/Eucla + * `Australia/Hobart` - Australia/Hobart + * `Australia/Lindeman` - Australia/Lindeman + * `Australia/Lord_Howe` - Australia/Lord_Howe + * `Australia/Melbourne` - Australia/Melbourne + * `Australia/Perth` - Australia/Perth + * `Australia/Sydney` - Australia/Sydney + * `Europe/Amsterdam` - Europe/Amsterdam + * `Europe/Andorra` - Europe/Andorra + * `Europe/Astrakhan` - Europe/Astrakhan + * `Europe/Athens` - Europe/Athens + * `Europe/Belgrade` - Europe/Belgrade + * `Europe/Berlin` - Europe/Berlin + * `Europe/Bratislava` - Europe/Bratislava + * `Europe/Brussels` - Europe/Brussels + * `Europe/Bucharest` - Europe/Bucharest + * `Europe/Budapest` - Europe/Budapest + * `Europe/Busingen` - Europe/Busingen + * `Europe/Chisinau` - Europe/Chisinau + * `Europe/Copenhagen` - Europe/Copenhagen + * `Europe/Dublin` - Europe/Dublin + * `Europe/Gibraltar` - Europe/Gibraltar + * `Europe/Guernsey` - Europe/Guernsey + * `Europe/Helsinki` - Europe/Helsinki + * `Europe/Isle_of_Man` - Europe/Isle_of_Man + * `Europe/Istanbul` - Europe/Istanbul + * `Europe/Jersey` - Europe/Jersey + * `Europe/Kaliningrad` - Europe/Kaliningrad + * `Europe/Kirov` - Europe/Kirov + * `Europe/Kyiv` - Europe/Kyiv + * `Europe/Lisbon` - Europe/Lisbon + * `Europe/Ljubljana` - Europe/Ljubljana + * `Europe/London` - Europe/London + * `Europe/Luxembourg` - Europe/Luxembourg + * `Europe/Madrid` - Europe/Madrid + * `Europe/Malta` - Europe/Malta + * `Europe/Mariehamn` - Europe/Mariehamn + * `Europe/Minsk` - Europe/Minsk + * `Europe/Monaco` - Europe/Monaco + * `Europe/Moscow` - Europe/Moscow + * `Europe/Oslo` - Europe/Oslo + * `Europe/Paris` - Europe/Paris + * `Europe/Podgorica` - Europe/Podgorica + * `Europe/Prague` - Europe/Prague + * `Europe/Riga` - Europe/Riga + * `Europe/Rome` - Europe/Rome + * `Europe/Samara` - Europe/Samara + * `Europe/San_Marino` - Europe/San_Marino + * `Europe/Sarajevo` - Europe/Sarajevo + * `Europe/Saratov` - Europe/Saratov + * `Europe/Simferopol` - Europe/Simferopol + * `Europe/Skopje` - Europe/Skopje + * `Europe/Sofia` - Europe/Sofia + * `Europe/Stockholm` - Europe/Stockholm + * `Europe/Tallinn` - Europe/Tallinn + * `Europe/Tirane` - Europe/Tirane + * `Europe/Ulyanovsk` - Europe/Ulyanovsk + * `Europe/Vaduz` - Europe/Vaduz + * `Europe/Vatican` - Europe/Vatican + * `Europe/Vienna` - Europe/Vienna + * `Europe/Vilnius` - Europe/Vilnius + * `Europe/Volgograd` - Europe/Volgograd + * `Europe/Warsaw` - Europe/Warsaw + * `Europe/Zagreb` - Europe/Zagreb + * `Europe/Zurich` - Europe/Zurich + * `GMT` - GMT + * `Indian/Antananarivo` - Indian/Antananarivo + * `Indian/Chagos` - Indian/Chagos + * `Indian/Christmas` - Indian/Christmas + * `Indian/Cocos` - Indian/Cocos + * `Indian/Comoro` - Indian/Comoro + * `Indian/Kerguelen` - Indian/Kerguelen + * `Indian/Mahe` - Indian/Mahe + * `Indian/Maldives` - Indian/Maldives + * `Indian/Mauritius` - Indian/Mauritius + * `Indian/Mayotte` - Indian/Mayotte + * `Indian/Reunion` - Indian/Reunion + * `Pacific/Apia` - Pacific/Apia + * `Pacific/Auckland` - Pacific/Auckland + * `Pacific/Bougainville` - Pacific/Bougainville + * `Pacific/Chatham` - Pacific/Chatham + * `Pacific/Chuuk` - Pacific/Chuuk + * `Pacific/Easter` - Pacific/Easter + * `Pacific/Efate` - Pacific/Efate + * `Pacific/Fakaofo` - Pacific/Fakaofo + * `Pacific/Fiji` - Pacific/Fiji + * `Pacific/Funafuti` - Pacific/Funafuti + * `Pacific/Galapagos` - Pacific/Galapagos + * `Pacific/Gambier` - Pacific/Gambier + * `Pacific/Guadalcanal` - Pacific/Guadalcanal + * `Pacific/Guam` - Pacific/Guam + * `Pacific/Honolulu` - Pacific/Honolulu + * `Pacific/Kanton` - Pacific/Kanton + * `Pacific/Kiritimati` - Pacific/Kiritimati + * `Pacific/Kosrae` - Pacific/Kosrae + * `Pacific/Kwajalein` - Pacific/Kwajalein + * `Pacific/Majuro` - Pacific/Majuro + * `Pacific/Marquesas` - Pacific/Marquesas + * `Pacific/Midway` - Pacific/Midway + * `Pacific/Nauru` - Pacific/Nauru + * `Pacific/Niue` - Pacific/Niue + * `Pacific/Norfolk` - Pacific/Norfolk + * `Pacific/Noumea` - Pacific/Noumea + * `Pacific/Pago_Pago` - Pacific/Pago_Pago + * `Pacific/Palau` - Pacific/Palau + * `Pacific/Pitcairn` - Pacific/Pitcairn + * `Pacific/Pohnpei` - Pacific/Pohnpei + * `Pacific/Port_Moresby` - Pacific/Port_Moresby + * `Pacific/Rarotonga` - Pacific/Rarotonga + * `Pacific/Saipan` - Pacific/Saipan + * `Pacific/Tahiti` - Pacific/Tahiti + * `Pacific/Tarawa` - Pacific/Tarawa + * `Pacific/Tongatapu` - Pacific/Tongatapu + * `Pacific/Wake` - Pacific/Wake + * `Pacific/Wallis` - Pacific/Wallis + * `UTC` - UTC + Location: + type: object + description: |- + Location information serializer. + + Provides location details including: + - Basic location information + - Client and division relationships + - Custom field values + properties: + id: + type: integer + readOnly: true + client: + type: integer + client_name: + type: string + readOnly: true + division: + type: integer + division_name: + type: string + readOnly: true + site_name: + type: string + maxLength: 255 + address: + type: string + state: + type: string + maxLength: 100 + country: + type: string + maxLength: 100 + custom_field_values: + type: array + items: + $ref: '#/components/schemas/LocationCustomFieldValue' + readOnly: true + created_date: + type: string + format: date-time + readOnly: true + modified_date: + type: string + format: date-time + readOnly: true + required: + - address + - client + - client_name + - country + - created_date + - custom_field_values + - division + - division_name + - id + - modified_date + - site_name + - state + LocationCustomFieldValue: + type: object + description: |- + Custom field value serializer. + + Provides dynamic field values with type-aware serialization. + properties: + field_name: + type: string + readOnly: true + field_type: + type: string + readOnly: true + value: + type: string + readOnly: true + required: + - field_name + - field_type + - value + LocationRequest: + type: object + description: |- + Location information serializer. + + Provides location details including: + - Basic location information + - Client and division relationships + - Custom field values + properties: + client: + type: integer + division: + type: integer + site_name: + type: string + minLength: 1 + maxLength: 255 + address: + type: string + minLength: 1 + state: + type: string + minLength: 1 + maxLength: 100 + country: + type: string + minLength: 1 + maxLength: 100 + required: + - address + - client + - country + - division + - site_name + - state + LocationSet: + type: object + description: |- + Location set information serializer with automatic version numbering. + + Provides location set details including: + - Automatically assigned version number (integer) + - Version metadata and creation date + - Custom field definitions + - Location count and aggregations + + Version Numbering: + - Version numbers are automatically assigned as sequential integers (1, 2, 3, etc.) + - Version field is read-only and cannot be manually set + - Versions are unique within each location tool + - Proper numerical sorting is supported (1, 2, 10 not 1, 10, 2) + properties: + id: + type: integer + readOnly: true + version: + type: integer + readOnly: true + description: Automatically assigned sequential version number (1, 2, 3, + etc.) + name: + type: string + description: Display name for this version (automatically generated) + maxLength: 255 + description: + type: string + description: Optional description of this version + is_active: + type: boolean + description: Whether this version is active + location_count: + type: string + readOnly: true + custom_field_definitions: + type: array + items: + $ref: '#/components/schemas/CustomFieldDefinition' + readOnly: true + count_field_aggregations: + type: string + readOnly: true + created_date: + type: string + format: date-time + readOnly: true + description: When this version was created + required: + - count_field_aggregations + - created_date + - custom_field_definitions + - id + - location_count + - name + - version + LocationSetRequest: + type: object + description: |- + Location set information serializer with automatic version numbering. + + Provides location set details including: + - Automatically assigned version number (integer) + - Version metadata and creation date + - Custom field definitions + - Location count and aggregations + + Version Numbering: + - Version numbers are automatically assigned as sequential integers (1, 2, 3, etc.) + - Version field is read-only and cannot be manually set + - Versions are unique within each location tool + - Proper numerical sorting is supported (1, 2, 10 not 1, 10, 2) + properties: + name: + type: string + minLength: 1 + description: Display name for this version (automatically generated) + maxLength: 255 + description: + type: string + description: Optional description of this version + is_active: + type: boolean + description: Whether this version is active + required: + - name + LocationTableSource: + type: object + description: |- + Location table source configuration serializer. + + Provides table source configuration including: + - Field selection settings + - Schema generation + - Data access information + properties: + id: + type: integer + readOnly: true + name: + type: string + maxLength: 255 + description: + type: string + include_client: + type: boolean + include_division: + type: boolean + include_site_name: + type: boolean + include_address: + type: boolean + include_state: + type: boolean + include_country: + type: boolean + selected_custom_fields: + type: array + items: + type: integer + schema: + type: string + readOnly: true + data_preview: + type: string + readOnly: true + created_date: + type: string + format: date-time + readOnly: true + modified_date: + type: string + format: date-time + readOnly: true + required: + - created_date + - data_preview + - id + - modified_date + - name + - schema + LocationTableSourceRequest: + type: object + description: |- + Location table source configuration serializer. + + Provides table source configuration including: + - Field selection settings + - Schema generation + - Data access information + properties: + name: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + include_client: + type: boolean + include_division: + type: boolean + include_site_name: + type: boolean + include_address: + type: boolean + include_state: + type: boolean + include_country: + type: boolean + selected_custom_fields: + type: array + items: + type: integer + required: + - name + MediaCollection: + type: object + properties: + id: + type: integer + readOnly: true + name: + type: string + maxLength: 255 + description: + type: string + items: + type: array + items: + $ref: '#/components/schemas/MediaItem' + readOnly: true + item_count: + type: string + readOnly: true + required: + - id + - item_count + - items + - name + MediaCollectionRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + required: + - name + MediaItem: + type: object + properties: + id: + type: integer + readOnly: true + name: + type: string + maxLength: 255 + file: + type: string + format: uri + pattern: (?:jpg|jpeg|png|gif|svg|emf|pdf|docx|xlsx|pptx)$ + file_url: + type: string + readOnly: true + preview_url: + type: string + readOnly: true + file_size: + type: integer + readOnly: true + nullable: true + description: File size in bytes + size_mb: + type: string + readOnly: true + mime_type: + type: string + readOnly: true + type: + $ref: '#/components/schemas/MediaItemTypeEnum' + description: + type: string + tags: + type: string + description: Comma-separated tags for searching + maxLength: 500 + tag_list: + type: string + readOnly: true + rating: + type: integer + maximum: 5 + minimum: 0 + description: Star rating 0-5 + is_logo: + type: boolean + created_date: + type: string + format: date-time + readOnly: true + modified_date: + type: string + format: date-time + readOnly: true + required: + - created_date + - file + - file_size + - file_url + - id + - mime_type + - modified_date + - name + - preview_url + - size_mb + - tag_list + MediaItemRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 255 + file: + type: string + format: binary + pattern: (?:jpg|jpeg|png|gif|svg|emf|pdf|docx|xlsx|pptx)$ + type: + $ref: '#/components/schemas/MediaItemTypeEnum' + description: + type: string + tags: + type: string + description: Comma-separated tags for searching + maxLength: 500 + rating: + type: integer + maximum: 5 + minimum: 0 + description: Star rating 0-5 + is_logo: + type: boolean + required: + - file + - name + MediaItemTypeEnum: + enum: + - image + - document + - presentation + - spreadsheet + - logo + - contact + type: string + description: |- + * `image` - Image + * `document` - Document + * `presentation` - Presentation + * `spreadsheet` - Spreadsheet + * `logo` - Logo + * `contact` - Contact + Milestone: + type: object + properties: + id: + type: integer + readOnly: true + name: + type: string + maxLength: 255 + date: + type: string + format: date + status: + $ref: '#/components/schemas/MilestoneStatusEnum' + milestone_type: + $ref: '#/components/schemas/MilestoneTypeEnum' + priority: + $ref: '#/components/schemas/MilestonePriorityEnum' + progress: + type: integer + maximum: 2147483647 + minimum: -2147483648 + description: Completion percentage (0-100) + assignee: + type: integer + nullable: true + assignee_name: + type: string + readOnly: true + acceptance_criteria: + type: string + description: Define what needs to be completed for this milestone + weeks_from_reference: + type: integer + maximum: 2147483647 + minimum: -2147483648 + nullable: true + reference_milestone: + type: integer + nullable: true + order: + type: integer + maximum: 2147483647 + minimum: -2147483648 + is_completed: + type: string + readOnly: true + is_today: + type: string + readOnly: true + status_color: + type: string + readOnly: true + required: + - assignee_name + - date + - id + - is_completed + - is_today + - name + - order + - status_color + MilestonePriorityEnum: + enum: + - high + - medium + - low + type: string + description: |- + * `high` - High + * `medium` - Medium + * `low` - Low + MilestoneRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 255 + date: + type: string + format: date + status: + $ref: '#/components/schemas/MilestoneStatusEnum' + milestone_type: + $ref: '#/components/schemas/MilestoneTypeEnum' + priority: + $ref: '#/components/schemas/MilestonePriorityEnum' + progress: + type: integer + maximum: 2147483647 + minimum: -2147483648 + description: Completion percentage (0-100) + assignee: + type: integer + nullable: true + acceptance_criteria: + type: string + description: Define what needs to be completed for this milestone + weeks_from_reference: + type: integer + maximum: 2147483647 + minimum: -2147483648 + nullable: true + reference_milestone: + type: integer + nullable: true + order: + type: integer + maximum: 2147483647 + minimum: -2147483648 + required: + - date + - name + - order + MilestoneStatusEnum: + enum: + - not_started + - in_progress + - completed + - overdue + - on_hold + type: string + description: |- + * `not_started` - Not Started + * `in_progress` - In Progress + * `completed` - Completed + * `overdue` - Overdue + * `on_hold` - On Hold + MilestoneTypeEnum: + enum: + - deliverable + - review + - decision + - checkpoint + - other + type: string + description: |- + * `deliverable` - Deliverable + * `review` - Review + * `decision` - Decision Point + * `checkpoint` - Checkpoint + * `other` - Other + NullEnum: + enum: + - null + OperationEnum: + enum: + - delete + - update_state + - update_country + - export + type: string + description: |- + * `delete` - Delete + * `update_state` - Update State + * `update_country` - Update Country + * `export` - Export + Opportunity: + type: object + properties: + id: + type: integer + readOnly: true + name: + type: string + maxLength: 255 + description: + type: string + notes: + type: string + description: Opportunity notes and details with URL support + stage: + $ref: '#/components/schemas/StageEnum' + status: + $ref: '#/components/schemas/OpportunityStatusEnum' + is_rfp: + type: boolean + title: Is this an RFP? + description: Check if this opportunity involves an RFP process + client: + allOf: + - $ref: '#/components/schemas/Client' + readOnly: true + competitors: + type: array + items: + $ref: '#/components/schemas/Vendor' + readOnly: true + incumbent_vendor: + allOf: + - $ref: '#/components/schemas/Vendor' + readOnly: true + one_time_revenue: + type: string + format: decimal + pattern: ^-?\d{0,13}(?:\.\d{0,2})?$ + annual_revenue: + type: string + format: decimal + pattern: ^-?\d{0,13}(?:\.\d{0,2})?$ + one_time_margin: + type: string + format: decimal + pattern: ^-?\d{0,3}(?:\.\d{0,2})?$ + annual_margin: + type: string + format: decimal + pattern: ^-?\d{0,3}(?:\.\d{0,2})?$ + term_years: + allOf: + - $ref: '#/components/schemas/TermYearsEnum' + description: |- + Contract term in years + + * `1` - 1 Year + * `2` - 2 Years + * `3` - 3 Years + * `4` - 4 Years + * `5` - 5 Years + minimum: -2147483648 + maximum: 2147483647 + currency: + type: string + readOnly: true + value: + type: string + format: decimal + pattern: ^-?\d{0,13}(?:\.\d{0,2})?$ + expected_close_date: + type: string + format: date + nullable: true + close_date: + type: string + format: date + nullable: true + deal_registered: + type: boolean + description: Has this deal been registered with the vendor? + registered_vendor: + allOf: + - $ref: '#/components/schemas/Vendor' + readOnly: true + deal_registration_name: + type: string + description: Deal registration identifier or name + maxLength: 255 + deal_registration_url: + type: string + format: uri + description: Link to deal registration portal or details + maxLength: 200 + deal_registration_date: + type: string + format: date + nullable: true + description: Date the deal was registered with vendor + deal_registration_expiry: + type: string + format: date + nullable: true + description: Expiration date for deal registration + deal_registration_amount: + type: string + format: decimal + pattern: ^-?\d{0,13}(?:\.\d{0,2})?$ + description: Monetary value of the deal registration + opportunity_identified_date: + type: string + format: date + nullable: true + description: Date when opportunity was first identified + rfp_release_date: + type: string + format: date + nullable: true + description: Date RFP was officially released + rfp_vendor_info_session_datetime: + type: string + format: date-time + nullable: true + description: RFP vendor information session date and time + rfp_vendor_questions_due_datetime: + type: string + format: date-time + nullable: true + description: Deadline for vendors to submit questions + rfp_due_datetime: + type: string + format: date-time + nullable: true + description: RFP submission deadline + rfp_presentation_datetime: + type: string + format: date-time + nullable: true + description: RFP presentation date and time + rfp_award_announcement_date: + type: string + format: date + nullable: true + description: Date RFP award will be announced + required: + - client + - competitors + - currency + - id + - incumbent_vendor + - name + - registered_vendor + - stage + - status + OpportunityRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + notes: + type: string + minLength: 1 + description: Opportunity notes and details with URL support + stage: + $ref: '#/components/schemas/StageEnum' + status: + $ref: '#/components/schemas/OpportunityStatusEnum' + is_rfp: + type: boolean + title: Is this an RFP? + description: Check if this opportunity involves an RFP process + client_id: + type: integer + writeOnly: true + primary_vendor_solution_id: + type: integer + writeOnly: true + competitor_ids: + type: array + items: + type: integer + writeOnly: true + description: List of competitor vendor IDs + incumbent_vendor_id: + type: integer + writeOnly: true + description: Incumbent vendor ID + one_time_revenue: + type: string + format: decimal + pattern: ^-?\d{0,13}(?:\.\d{0,2})?$ + annual_revenue: + type: string + format: decimal + pattern: ^-?\d{0,13}(?:\.\d{0,2})?$ + one_time_margin: + type: string + format: decimal + pattern: ^-?\d{0,3}(?:\.\d{0,2})?$ + annual_margin: + type: string + format: decimal + pattern: ^-?\d{0,3}(?:\.\d{0,2})?$ + term_years: + allOf: + - $ref: '#/components/schemas/TermYearsEnum' + description: |- + Contract term in years + + * `1` - 1 Year + * `2` - 2 Years + * `3` - 3 Years + * `4` - 4 Years + * `5` - 5 Years + minimum: -2147483648 + maximum: 2147483647 + currency_id: + type: string + writeOnly: true + minLength: 1 + description: Currency code (e.g., USD, EUR) + value: + type: string + format: decimal + pattern: ^-?\d{0,13}(?:\.\d{0,2})?$ + expected_close_date: + type: string + format: date + nullable: true + close_date: + type: string + format: date + nullable: true + deal_registered: + type: boolean + description: Has this deal been registered with the vendor? + registered_vendor_id: + type: integer + writeOnly: true + description: Registered vendor ID + deal_registration_name: + type: string + description: Deal registration identifier or name + maxLength: 255 + deal_registration_url: + type: string + format: uri + description: Link to deal registration portal or details + maxLength: 200 + deal_registration_date: + type: string + format: date + nullable: true + description: Date the deal was registered with vendor + deal_registration_expiry: + type: string + format: date + nullable: true + description: Expiration date for deal registration + deal_registration_amount: + type: string + format: decimal + pattern: ^-?\d{0,13}(?:\.\d{0,2})?$ + description: Monetary value of the deal registration + opportunity_identified_date: + type: string + format: date + nullable: true + description: Date when opportunity was first identified + rfp_release_date: + type: string + format: date + nullable: true + description: Date RFP was officially released + rfp_vendor_info_session_datetime: + type: string + format: date-time + nullable: true + description: RFP vendor information session date and time + rfp_vendor_questions_due_datetime: + type: string + format: date-time + nullable: true + description: Deadline for vendors to submit questions + rfp_due_datetime: + type: string + format: date-time + nullable: true + description: RFP submission deadline + rfp_presentation_datetime: + type: string + format: date-time + nullable: true + description: RFP presentation date and time + rfp_award_announcement_date: + type: string + format: date + nullable: true + description: Date RFP award will be announced + required: + - client_id + - name + - stage + - status + OpportunityStatusEnum: + enum: + - Active + - Won + - Lost + - Dropped + type: string + description: |- + * `Active` - Active + * `Won` - Won + * `Lost` - Lost + * `Dropped` - Dropped + OrgChart: + type: object + properties: + id: + type: integer + readOnly: true + root_contact: + type: integer + root_contact_name: + type: string + readOnly: true + depth: + type: integer + maximum: 2147483647 + minimum: -2147483648 + include_fields: {} + title: + type: string + maxLength: 255 + description: + type: string + chart_data: + type: string + readOnly: true + required: + - chart_data + - id + - root_contact + - root_contact_name + - title + OrgChartRequest: + type: object + properties: + root_contact: + type: integer + depth: + type: integer + maximum: 2147483647 + minimum: -2147483648 + include_fields: {} + title: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + required: + - root_contact + - title + Organization: + type: object + description: |- + Organization information serializer. + + Provides organization details including: + - Basic information (name, type, sector) + - Contact and location details + - Financial information + - Hierarchy relationships + properties: + id: + type: integer + readOnly: true + name: + type: string + maxLength: 255 + legal_name: + type: string + maxLength: 255 + abbreviated_name: + type: string + maxLength: 50 + type: + $ref: '#/components/schemas/OrganizationTypeEnum' + sector: + nullable: true + oneOf: + - $ref: '#/components/schemas/SectorEnum' + - $ref: '#/components/schemas/BlankEnum' + - $ref: '#/components/schemas/NullEnum' + country: + type: string + maxLength: 100 + size: + $ref: '#/components/schemas/SizeEnum' + employee_count: + type: integer + maximum: 2147483647 + minimum: -2147483648 + nullable: true + revenue: + type: string + format: decimal + pattern: ^-?\d{0,13}(?:\.\d{0,2})?$ + nullable: true + assets: + type: string + format: decimal + pattern: ^-?\d{0,13}(?:\.\d{0,2})?$ + nullable: true + stock_exchange: + type: string + nullable: true + stock_symbol: + type: string + nullable: true + maxLength: 10 + parent_organization: + type: integer + nullable: true + parent_relationship_type: + nullable: true + description: |- + Type of relationship with parent organization + + * `subsidiary` - True Subsidiary + * `controlled_affiliate` - Controlled Affiliate + * `wholly_owned` - Wholly-Owned Subsidiary + * `branch` - Branch + * `agency` - Agency + * `ministry` - Ministry/Department + * `executive_agency` - Executive Agency + * `regulatory_agency` - Regulatory Agency + * `soc` - State-Owned Company + * `gbe` - Government Business Enterprise + oneOf: + - $ref: '#/components/schemas/ParentRelationshipTypeEnum' + - $ref: '#/components/schemas/BlankEnum' + - $ref: '#/components/schemas/NullEnum' + has_client_profile: + type: string + readOnly: true + has_vendor_profile: + type: string + readOnly: true + legal_street_address: + type: string + maxLength: 255 + legal_city: + type: string + maxLength: 100 + legal_state: + type: string + maxLength: 100 + legal_country: + type: string + maxLength: 100 + legal_postal_code: + type: string + maxLength: 20 + created_date: + type: string + format: date-time + readOnly: true + modified_date: + type: string + format: date-time + readOnly: true + required: + - country + - created_date + - has_client_profile + - has_vendor_profile + - id + - modified_date + - name + - size + - type + OrganizationDivision: + type: object + description: |- + Organization division information serializer. + + Provides division details including: + - Division name, abbreviation, and description + - Organizational hierarchy (parent division) + - Full hierarchical path + - Contact and opportunity counts + properties: + id: + type: integer + readOnly: true + name: + type: string + description: Division name (e.g., "IT Department", "Western Region Sales") + maxLength: 255 + abbreviation: + type: string + description: Short abbreviation for the division (e.g., "IT", "West") + maxLength: 50 + description: + type: string + description: Description of the division's purpose and scope + organization: + type: integer + description: Parent organization this division belongs to + organization_name: + type: string + readOnly: true + parent_division: + type: integer + nullable: true + description: Parent division for nested organizational structures + parent_division_name: + type: string + readOnly: true + nullable: true + full_path: + type: string + readOnly: true + hierarchy_level: + type: integer + readOnly: true + contact_count: + type: string + readOnly: true + opportunity_count: + type: string + readOnly: true + created_date: + type: string + format: date-time + readOnly: true + modified_date: + type: string + format: date-time + readOnly: true + required: + - contact_count + - created_date + - full_path + - hierarchy_level + - id + - modified_date + - name + - opportunity_count + - organization + - organization_name + - parent_division_name + OrganizationDivisionRequest: + type: object + description: |- + Organization division information serializer. + + Provides division details including: + - Division name, abbreviation, and description + - Organizational hierarchy (parent division) + - Full hierarchical path + - Contact and opportunity counts + properties: + name: + type: string + minLength: 1 + description: Division name (e.g., "IT Department", "Western Region Sales") + maxLength: 255 + abbreviation: + type: string + description: Short abbreviation for the division (e.g., "IT", "West") + maxLength: 50 + description: + type: string + description: Description of the division's purpose and scope + organization: + type: integer + description: Parent organization this division belongs to + parent_division: + type: integer + nullable: true + description: Parent division for nested organizational structures + required: + - name + - organization + OrganizationTypeEnum: + enum: + - For-Profit + - Non-Profit + - Government + - NGO + - Educational + - Healthcare + - Cooperative + type: string + description: |- + * `For-Profit` - For-Profit + * `Non-Profit` - Non-Profit + * `Government` - Government + * `NGO` - NGO + * `Educational` - Educational + * `Healthcare` - Healthcare + * `Cooperative` - Cooperative + PaginatedAPIKeyList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/APIKey' + PaginatedAdvisorToolList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/AdvisorTool' + PaginatedAssumptionList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/Assumption' + PaginatedBMCList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/BMC' + PaginatedBMCTemplateList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/BMCTemplate' + PaginatedBMCToolList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/BMCTool' + PaginatedCalendarEventList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/CalendarEvent' + PaginatedClientList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/Client' + PaginatedClientLocationToolList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/ClientLocationTool' + PaginatedContactList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/Contact' + PaginatedCurrencyList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/Currency' + PaginatedCustomFieldDefinitionList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/CustomFieldDefinition' + PaginatedDeckList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/Deck' + PaginatedDeckSlideList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/DeckSlide' + PaginatedDependencyList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/Dependency' + PaginatedDigitalContactList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/DigitalContact' + PaginatedDigitalContactTypeList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/DigitalContactType' + PaginatedEngagementSummaryList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/EngagementSummary' + PaginatedFindingList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/Finding' + PaginatedFiscalYearList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/FiscalYear' + PaginatedInterviewList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/Interview' + PaginatedIssueList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/Issue' + PaginatedLocationList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/Location' + PaginatedLocationSetList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/LocationSet' + PaginatedLocationTableSourceList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/LocationTableSource' + PaginatedMediaCollectionList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/MediaCollection' + PaginatedMediaItemList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/MediaItem' + PaginatedMilestoneList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/Milestone' + PaginatedOpportunityList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/Opportunity' + PaginatedOrgChartList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/OrgChart' + PaginatedOrganizationDivisionList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/OrganizationDivision' + PaginatedOrganizationList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/Organization' + PaginatedPowerPointTemplateList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/PowerPointTemplate' + PaginatedPresentationList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/Presentation' + PaginatedPriceLineItemList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/PriceLineItem' + PaginatedPriceVersionList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/PriceVersion' + PaginatedPricingToolList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/PricingTool' + PaginatedPrincipleList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/Principle' + PaginatedProposalList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/Proposal' + PaginatedRAIDToolList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/RAIDTool' + PaginatedRecommendationList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/Recommendation' + PaginatedRequirementList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/Requirement' + PaginatedRequirementsToolList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/RequirementsTool' + PaginatedRiskList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/Risk' + PaginatedStageList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/Stage' + PaginatedStakeholderList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/Stakeholder' + PaginatedStakeholderToolList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/StakeholderTool' + PaginatedTEIReportFieldList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/TEIReportField' + PaginatedTEIReportList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/TEIReport' + PaginatedTEIToolList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/TEITool' + PaginatedTableSourceList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/TableSource' + PaginatedTagList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/Tag' + PaginatedTimelineList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/Timeline' + PaginatedTimelineToolList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/TimelineTool' + PaginatedVendorList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/Vendor' + PaginatedVendorSolutionList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/VendorSolution' + PaginatedWorkshopList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/Workshop' + ParentRelationshipTypeEnum: + enum: + - subsidiary + - controlled_affiliate + - wholly_owned + - branch + - agency + - ministry + - executive_agency + - regulatory_agency + - soc + - gbe + type: string + description: |- + * `subsidiary` - True Subsidiary + * `controlled_affiliate` - Controlled Affiliate + * `wholly_owned` - Wholly-Owned Subsidiary + * `branch` - Branch + * `agency` - Agency + * `ministry` - Ministry/Department + * `executive_agency` - Executive Agency + * `regulatory_agency` - Regulatory Agency + * `soc` - State-Owned Company + * `gbe` - Government Business Enterprise + PatchedAdvisorToolRequest: + type: object + description: Serialize an AdvisorTool with computed counts and client/association + display fields. + properties: + name: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + minLength: 1 + proposal: + type: integer + nullable: true + engagement: + type: integer + nullable: true + categories: + description: Custom categories for findings and requirements + timeframe_short: + type: integer + maximum: 2147483647 + minimum: -2147483648 + description: Short timeframe in days + timeframe_medium: + type: integer + maximum: 2147483647 + minimum: -2147483648 + description: Medium timeframe in days + timeframe_long: + type: integer + maximum: 2147483647 + minimum: -2147483648 + description: Long timeframe in days + PatchedBMCRequest: + type: object + properties: + version: + type: integer + maximum: 2147483647 + minimum: -2147483648 + customer_segments: + type: string + value_propositions: + type: string + channels: + type: string + customer_relationships: + type: string + revenue_streams: + type: string + key_resources: + type: string + key_activities: + type: string + key_partnerships: + type: string + cost_structure: + type: string + PatchedBMCTemplateRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + customer_segments: + type: string + value_propositions: + type: string + channels: + type: string + customer_relationships: + type: string + revenue_streams: + type: string + key_resources: + type: string + key_activities: + type: string + key_partnerships: + type: string + cost_structure: + type: string + is_active: + type: boolean + PatchedBMCToolRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + minLength: 1 + proposal: + type: integer + nullable: true + engagement: + type: integer + nullable: true + PatchedCalendarEventRequest: + type: object + properties: + title: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + start_time: + type: string + format: date-time + end_time: + type: string + format: date-time + location: + type: string + maxLength: 255 + event_type: + $ref: '#/components/schemas/EventTypeEnum' + PatchedClientLocationToolRequest: + type: object + description: |- + Client Location Tool serializer with automatic version management. + + Provides tool information including: + - Basic tool metadata + - Current version number (automatically managed) + - All location sets with automatic version numbering + - Table source configuration + + Automatic Version Management: + - current_version field shows the active version number (integer) + - location_sets array shows all versions ordered by version number + - Version creation is handled by the create_version API endpoint + - Version switching is handled by the switch_version API endpoint + - All version numbers are automatically assigned and read-only + properties: + name: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + minLength: 1 + proposal: + type: integer + nullable: true + engagement: + type: integer + nullable: true + PatchedClientRequest: + type: object + properties: + name: + type: string + legal_name: + type: string + abbreviated_name: + type: string + overview: + type: string + history: + type: string + services_provided: + type: string + client_type: + type: string + vertical: + type: string + nullable: true + stock_exchange: + type: string + nullable: true + stock_symbol: + type: string + nullable: true + employee_count: + type: integer + nullable: true + revenue: + type: string + format: decimal + pattern: ^-?\d{0,13}(?:\.\d{0,2})?$ + nullable: true + assets: + type: string + format: decimal + pattern: ^-?\d{0,13}(?:\.\d{0,2})?$ + nullable: true + fiscal_year_end: + type: string + format: date + nullable: true + contact_center_agent_count: + type: integer + nullable: true + service_desk_agent_count: + type: integer + nullable: true + supervisor_count: + type: integer + nullable: true + location_count: + type: integer + nullable: true + repository_url: + type: string + PatchedContactRequest: + type: object + properties: + first_name: + type: string + minLength: 1 + maxLength: 100 + last_name: + type: string + minLength: 1 + maxLength: 100 + job_title: + type: string + maxLength: 100 + email: + type: string + format: email + maxLength: 254 + organization_id: + type: integer + writeOnly: true + reports_to: + type: integer + nullable: true + PatchedCustomFieldDefinitionRequest: + type: object + description: |- + Custom field definition serializer. + + Provides custom field schema information including: + - Field name, type, and description + - Validation rules and default values + - Display order + properties: + name: + type: string + minLength: 1 + maxLength: 100 + field_type: + $ref: '#/components/schemas/CustomFieldDefinitionFieldTypeEnum' + description: + type: string + is_required: + type: boolean + default_value: + type: string + order: + type: integer + maximum: 2147483647 + minimum: 0 + PatchedDeckRequest: + type: object + properties: + presentation: + type: integer + template: + type: integer + title: + type: string + maxLength: 255 + subtitle: + type: string + maxLength: 255 + presenter: + type: integer + nullable: true + status: + $ref: '#/components/schemas/DeckStatusEnum' + date: + type: string + format: date + PatchedDeckSlideRequest: + type: object + properties: + deck: + type: integer + slide_type: + type: string + minLength: 1 + maxLength: 50 + order: + type: integer + maximum: 2147483647 + minimum: -2147483648 + content_mapping: {} + content1_type: + $ref: '#/components/schemas/Content3TypeEnum' + content1_image: + type: integer + nullable: true + content1_table: + type: integer + nullable: true + content2_type: + $ref: '#/components/schemas/Content3TypeEnum' + content2_image: + type: integer + nullable: true + content2_table: + type: integer + nullable: true + content3_type: + $ref: '#/components/schemas/Content3TypeEnum' + content3_image: + type: integer + nullable: true + content3_table: + type: integer + nullable: true + PatchedDigitalContactRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 100 + contact_type_id: + type: integer + writeOnly: true + url: + type: string + minLength: 1 + maxLength: 500 + notes: + type: string + client: + type: integer + nullable: true + vendor: + type: integer + nullable: true + PatchedEngagementRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 255 + client_id: + type: integer + writeOnly: true + opportunity_id: + type: integer + writeOnly: true + start_date: + type: string + format: date + end_date: + type: string + format: date + status: + $ref: '#/components/schemas/Status557Enum' + description: + type: string + PatchedFindingRequest: + type: object + description: Serialize all Finding fields for read and write. + properties: + name: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + minLength: 1 + source: + $ref: '#/components/schemas/SourceEnum' + source_detail: + type: string + maxLength: 255 + category: + type: string + maxLength: 100 + impact_level: + $ref: '#/components/schemas/ImpactLevelEnum' + impact_description: + type: string + minLength: 1 + PatchedInterviewRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 255 + contact_id: + type: integer + writeOnly: true + date: + type: string + format: date + time: + type: string + format: time + notes: + type: string + PatchedLocationRequest: + type: object + description: |- + Location information serializer. + + Provides location details including: + - Basic location information + - Client and division relationships + - Custom field values + properties: + client: + type: integer + division: + type: integer + site_name: + type: string + minLength: 1 + maxLength: 255 + address: + type: string + minLength: 1 + state: + type: string + minLength: 1 + maxLength: 100 + country: + type: string + minLength: 1 + maxLength: 100 + PatchedLocationSetRequest: + type: object + description: |- + Location set information serializer with automatic version numbering. + + Provides location set details including: + - Automatically assigned version number (integer) + - Version metadata and creation date + - Custom field definitions + - Location count and aggregations + + Version Numbering: + - Version numbers are automatically assigned as sequential integers (1, 2, 3, etc.) + - Version field is read-only and cannot be manually set + - Versions are unique within each location tool + - Proper numerical sorting is supported (1, 2, 10 not 1, 10, 2) + properties: + name: + type: string + minLength: 1 + description: Display name for this version (automatically generated) + maxLength: 255 + description: + type: string + description: Optional description of this version + is_active: + type: boolean + description: Whether this version is active + PatchedLocationTableSourceRequest: + type: object + description: |- + Location table source configuration serializer. + + Provides table source configuration including: + - Field selection settings + - Schema generation + - Data access information + properties: + name: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + include_client: + type: boolean + include_division: + type: boolean + include_site_name: + type: boolean + include_address: + type: boolean + include_state: + type: boolean + include_country: + type: boolean + selected_custom_fields: + type: array + items: + type: integer + PatchedMediaCollectionRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + PatchedMediaItemRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 255 + file: + type: string + format: binary + pattern: (?:jpg|jpeg|png|gif|svg|emf|pdf|docx|xlsx|pptx)$ + type: + $ref: '#/components/schemas/MediaItemTypeEnum' + description: + type: string + tags: + type: string + description: Comma-separated tags for searching + maxLength: 500 + rating: + type: integer + maximum: 5 + minimum: 0 + description: Star rating 0-5 + is_logo: + type: boolean + PatchedMilestoneRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 255 + date: + type: string + format: date + status: + $ref: '#/components/schemas/MilestoneStatusEnum' + milestone_type: + $ref: '#/components/schemas/MilestoneTypeEnum' + priority: + $ref: '#/components/schemas/MilestonePriorityEnum' + progress: + type: integer + maximum: 2147483647 + minimum: -2147483648 + description: Completion percentage (0-100) + assignee: + type: integer + nullable: true + acceptance_criteria: + type: string + description: Define what needs to be completed for this milestone + weeks_from_reference: + type: integer + maximum: 2147483647 + minimum: -2147483648 + nullable: true + reference_milestone: + type: integer + nullable: true + order: + type: integer + maximum: 2147483647 + minimum: -2147483648 + PatchedOpportunityRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + notes: + type: string + minLength: 1 + description: Opportunity notes and details with URL support + stage: + $ref: '#/components/schemas/StageEnum' + status: + $ref: '#/components/schemas/OpportunityStatusEnum' + is_rfp: + type: boolean + title: Is this an RFP? + description: Check if this opportunity involves an RFP process + client_id: + type: integer + writeOnly: true + primary_vendor_solution_id: + type: integer + writeOnly: true + competitor_ids: + type: array + items: + type: integer + writeOnly: true + description: List of competitor vendor IDs + incumbent_vendor_id: + type: integer + writeOnly: true + description: Incumbent vendor ID + one_time_revenue: + type: string + format: decimal + pattern: ^-?\d{0,13}(?:\.\d{0,2})?$ + annual_revenue: + type: string + format: decimal + pattern: ^-?\d{0,13}(?:\.\d{0,2})?$ + one_time_margin: + type: string + format: decimal + pattern: ^-?\d{0,3}(?:\.\d{0,2})?$ + annual_margin: + type: string + format: decimal + pattern: ^-?\d{0,3}(?:\.\d{0,2})?$ + term_years: + allOf: + - $ref: '#/components/schemas/TermYearsEnum' + description: |- + Contract term in years + + * `1` - 1 Year + * `2` - 2 Years + * `3` - 3 Years + * `4` - 4 Years + * `5` - 5 Years + minimum: -2147483648 + maximum: 2147483647 + currency_id: + type: string + writeOnly: true + minLength: 1 + description: Currency code (e.g., USD, EUR) + value: + type: string + format: decimal + pattern: ^-?\d{0,13}(?:\.\d{0,2})?$ + expected_close_date: + type: string + format: date + nullable: true + close_date: + type: string + format: date + nullable: true + deal_registered: + type: boolean + description: Has this deal been registered with the vendor? + registered_vendor_id: + type: integer + writeOnly: true + description: Registered vendor ID + deal_registration_name: + type: string + description: Deal registration identifier or name + maxLength: 255 + deal_registration_url: + type: string + format: uri + description: Link to deal registration portal or details + maxLength: 200 + deal_registration_date: + type: string + format: date + nullable: true + description: Date the deal was registered with vendor + deal_registration_expiry: + type: string + format: date + nullable: true + description: Expiration date for deal registration + deal_registration_amount: + type: string + format: decimal + pattern: ^-?\d{0,13}(?:\.\d{0,2})?$ + description: Monetary value of the deal registration + opportunity_identified_date: + type: string + format: date + nullable: true + description: Date when opportunity was first identified + rfp_release_date: + type: string + format: date + nullable: true + description: Date RFP was officially released + rfp_vendor_info_session_datetime: + type: string + format: date-time + nullable: true + description: RFP vendor information session date and time + rfp_vendor_questions_due_datetime: + type: string + format: date-time + nullable: true + description: Deadline for vendors to submit questions + rfp_due_datetime: + type: string + format: date-time + nullable: true + description: RFP submission deadline + rfp_presentation_datetime: + type: string + format: date-time + nullable: true + description: RFP presentation date and time + rfp_award_announcement_date: + type: string + format: date + nullable: true + description: Date RFP award will be announced + PatchedOrgChartRequest: + type: object + properties: + root_contact: + type: integer + depth: + type: integer + maximum: 2147483647 + minimum: -2147483648 + include_fields: {} + title: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + PatchedOrganizationDivisionRequest: + type: object + description: |- + Organization division information serializer. + + Provides division details including: + - Division name, abbreviation, and description + - Organizational hierarchy (parent division) + - Full hierarchical path + - Contact and opportunity counts + properties: + name: + type: string + minLength: 1 + description: Division name (e.g., "IT Department", "Western Region Sales") + maxLength: 255 + abbreviation: + type: string + description: Short abbreviation for the division (e.g., "IT", "West") + maxLength: 50 + description: + type: string + description: Description of the division's purpose and scope + organization: + type: integer + description: Parent organization this division belongs to + parent_division: + type: integer + nullable: true + description: Parent division for nested organizational structures + PatchedPowerPointTemplateRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + is_active: + type: boolean + PatchedPresentationRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 255 + source_type: + $ref: '#/components/schemas/SourceTypeEnum' + source_id: + type: integer + maximum: 2147483647 + minimum: -2147483648 + date: + type: string + format: date + presenter: + type: integer + nullable: true + PatchedPriceLineItemRequest: + type: object + properties: + vendor_solution: + type: integer + type: + $ref: '#/components/schemas/PriceLineItemTypeEnum' + billing_cycle: + $ref: '#/components/schemas/BillingCycleEnum' + quantity: + type: string + format: decimal + pattern: ^-?\d{0,8}(?:\.\d{0,2})?$ + cost: + type: string + format: decimal + pattern: ^-?\d{0,13}(?:\.\d{0,2})?$ + margin: + type: string + format: decimal + pattern: ^-?\d{0,3}(?:\.\d{0,2})?$ + order: + type: integer + maximum: 2147483647 + minimum: -2147483648 + PatchedPriceVersionRequest: + type: object + properties: + title: + type: string + minLength: 1 + maxLength: 255 + currency: + type: string + minLength: 1 + version_number: + type: integer + maximum: 2147483647 + minimum: -2147483648 + notes: + type: string + PatchedPricingToolRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + minLength: 1 + proposal: + type: integer + nullable: true + engagement: + type: integer + nullable: true + PatchedPrincipleRequest: + type: object + description: Serialize all Principle fields. + properties: + name: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + minLength: 1 + weight: + type: integer + maximum: 2147483647 + minimum: -2147483648 + description: Importance (1-10) + order: + type: integer + maximum: 2147483647 + minimum: -2147483648 + PatchedProposalRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 255 + opportunity_id: + type: integer + writeOnly: true + primary_contact_id: + type: integer + writeOnly: true + nullable: true + status: + $ref: '#/components/schemas/ProposalStatusEnum' + due_date: + type: string + format: date + nullable: true + submitted_date: + type: string + format: date + nullable: true + repository_url: + type: string + format: uri + maxLength: 200 + PatchedRecommendationRequest: + type: object + description: |- + Serialize a Recommendation with nested impacts, alignments, findings, and requirements. + + ``finding_ids`` and ``requirement_ids`` are write-only; on read the full nested + objects are returned under ``associated_findings`` / ``associated_requirements``. + ``timeframe`` is a computed read-only field (Short / Medium / Long) derived from + ``implementation_days`` against fixed 30- and 180-day thresholds. + properties: + title: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + minLength: 1 + implementation_days: + type: integer + maximum: 2147483647 + minimum: -2147483648 + finding_ids: + type: array + items: + type: integer + writeOnly: true + requirement_ids: + type: array + items: + type: integer + writeOnly: true + cost: + type: string + format: decimal + pattern: ^-?\d{0,10}(?:\.\d{0,2})?$ + currency: + type: string + minLength: 1 + complexity: + $ref: '#/components/schemas/ComplexityEnum' + PatchedRequirementRequest: + type: object + description: Serialize a Requirement with nested findings on read and a writable + ``related_finding_ids`` list on write. + properties: + name: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + minLength: 1 + type: + $ref: '#/components/schemas/RequirementTypeEnum' + category: + type: string + maxLength: 100 + success_measure: + type: string + minLength: 1 + notes: + type: string + related_finding_ids: + type: array + items: + type: integer + writeOnly: true + PatchedStageRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + start_date: + type: string + format: date + end_date: + type: string + format: date + color: + type: integer + nullable: true + description: Stage color (editable in admin) + status: + $ref: '#/components/schemas/StageStatusEnum' + progress: + type: integer + maximum: 2147483647 + minimum: -2147483648 + description: Completion percentage (0-100) + order: + type: integer + maximum: 2147483647 + minimum: -2147483648 + milestone: + type: integer + nullable: true + description: Optional milestone that marks the completion of this stage + PatchedStakeholderRequest: + type: object + properties: + contact_id: + type: integer + writeOnly: true + tenure: + type: integer + maximum: 2147483647 + minimum: -2147483648 + description: Years in current role + solution_seek: + type: string + solution_avoid: + type: string + notes: + type: string + power: + $ref: '#/components/schemas/PowerEnum' + interest: + $ref: '#/components/schemas/InterestEnum' + PatchedStakeholderToolRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + minLength: 1 + proposal: + type: integer + nullable: true + engagement: + type: integer + nullable: true + PatchedTEIReportFieldRequest: + type: object + description: Field-definition serializer. + properties: + table: + $ref: '#/components/schemas/TableEnum' + field_key: + type: string + minLength: 1 + description: Machine identifier, unique within report. + maxLength: 64 + pattern: ^[-a-zA-Z0-9_]+$ + label: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + field_type: + $ref: '#/components/schemas/TEIReportFieldFieldTypeEnum' + category: + type: string + maxLength: 100 + default_value: + type: string + description: Pre-populated default (stored as string, cast by calculator). + maxLength: 255 + is_annual: + type: boolean + description: If True, value is collected per year (Y1..YN); if False, a + single value. + risk_adjustment: + type: string + format: decimal + pattern: ^-?\d{0,1}(?:\.\d{0,4})?$ + nullable: true + description: Default risk adjustment (0.0-1.0); only applied to benefit + fields. + sort_order: + type: integer + maximum: 2147483647 + minimum: 0 + is_required: + type: boolean + source_notes: + type: string + PatchedTEIReportRequest: + type: object + description: Report template serializer with counts and the spec-style ``id`` + alias. + properties: + name: + type: string + minLength: 1 + description: Display name (e.g., 'Amazon Connect 2026'). + maxLength: 255 + vendor: + type: string + minLength: 1 + description: Technology vendor (e.g., 'AWS', 'Genesys'). + maxLength: 255 + version: + type: string + minLength: 1 + description: Model version (e.g., '1.0', '2.1'). + maxLength: 50 + description: + type: string + analysis_period_years: + type: integer + maximum: 2147483647 + minimum: 0 + description: Number of years included in NPV/ROI (1-10). + discount_rate: + type: string + format: decimal + pattern: ^-?\d{0,2}(?:\.\d{0,4})?$ + description: Annual discount rate (e.g., 0.10 = 10%). + status: + $ref: '#/components/schemas/TEIReportStatusEnum' + PatchedTEIToolUpdateRequest: + type: object + description: Write serializer for ``PUT /tools/{id}/`` — only name/status are + mutable. + properties: + name: + type: string + minLength: 1 + maxLength: 255 + status: + $ref: '#/components/schemas/Status49eEnum' + PatchedTagRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 50 + PatchedTimelineRequest: + type: object + properties: + title: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + start_date: + type: string + format: date + PatchedTimelineToolRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + minLength: 1 + proposal: + type: integer + nullable: true + engagement: + type: integer + nullable: true + PatchedUserProfileRequest: + type: object + description: |- + User profile serializer with preference settings. + + Provides access to user-specific configuration including: + - Date and time format preferences + - Timezone settings (home and local) + - UI theme customization + properties: + date_format: + allOf: + - $ref: '#/components/schemas/DateFormatEnum' + description: |- + Preferred date display format + + * `YYYY-MM-DD` - 2024-12-25 + * `DD/MM/YYYY` - 25/12/2024 + * `MM/DD/YYYY` - 12/25/2024 + * `DD.MM.YYYY` - 25.12.2024 + * `DD-MM-YYYY` - 25-12-2024 + time_format: + allOf: + - $ref: '#/components/schemas/TimeFormatEnum' + description: |- + 12-hour or 24-hour time format + + * `12-hour` - 12-hour (3:30 PM) + * `24-hour` - 24-hour (15:30) + home_timezone: + allOf: + - $ref: '#/components/schemas/HomeTimezoneEnum' + description: |- + User's home/permanent timezone + + * `Africa/Abidjan` - Africa/Abidjan + * `Africa/Accra` - Africa/Accra + * `Africa/Addis_Ababa` - Africa/Addis_Ababa + * `Africa/Algiers` - Africa/Algiers + * `Africa/Asmara` - Africa/Asmara + * `Africa/Bamako` - Africa/Bamako + * `Africa/Bangui` - Africa/Bangui + * `Africa/Banjul` - Africa/Banjul + * `Africa/Bissau` - Africa/Bissau + * `Africa/Blantyre` - Africa/Blantyre + * `Africa/Brazzaville` - Africa/Brazzaville + * `Africa/Bujumbura` - Africa/Bujumbura + * `Africa/Cairo` - Africa/Cairo + * `Africa/Casablanca` - Africa/Casablanca + * `Africa/Ceuta` - Africa/Ceuta + * `Africa/Conakry` - Africa/Conakry + * `Africa/Dakar` - Africa/Dakar + * `Africa/Dar_es_Salaam` - Africa/Dar_es_Salaam + * `Africa/Djibouti` - Africa/Djibouti + * `Africa/Douala` - Africa/Douala + * `Africa/El_Aaiun` - Africa/El_Aaiun + * `Africa/Freetown` - Africa/Freetown + * `Africa/Gaborone` - Africa/Gaborone + * `Africa/Harare` - Africa/Harare + * `Africa/Johannesburg` - Africa/Johannesburg + * `Africa/Juba` - Africa/Juba + * `Africa/Kampala` - Africa/Kampala + * `Africa/Khartoum` - Africa/Khartoum + * `Africa/Kigali` - Africa/Kigali + * `Africa/Kinshasa` - Africa/Kinshasa + * `Africa/Lagos` - Africa/Lagos + * `Africa/Libreville` - Africa/Libreville + * `Africa/Lome` - Africa/Lome + * `Africa/Luanda` - Africa/Luanda + * `Africa/Lubumbashi` - Africa/Lubumbashi + * `Africa/Lusaka` - Africa/Lusaka + * `Africa/Malabo` - Africa/Malabo + * `Africa/Maputo` - Africa/Maputo + * `Africa/Maseru` - Africa/Maseru + * `Africa/Mbabane` - Africa/Mbabane + * `Africa/Mogadishu` - Africa/Mogadishu + * `Africa/Monrovia` - Africa/Monrovia + * `Africa/Nairobi` - Africa/Nairobi + * `Africa/Ndjamena` - Africa/Ndjamena + * `Africa/Niamey` - Africa/Niamey + * `Africa/Nouakchott` - Africa/Nouakchott + * `Africa/Ouagadougou` - Africa/Ouagadougou + * `Africa/Porto-Novo` - Africa/Porto-Novo + * `Africa/Sao_Tome` - Africa/Sao_Tome + * `Africa/Tripoli` - Africa/Tripoli + * `Africa/Tunis` - Africa/Tunis + * `Africa/Windhoek` - Africa/Windhoek + * `America/Adak` - America/Adak + * `America/Anchorage` - America/Anchorage + * `America/Anguilla` - America/Anguilla + * `America/Antigua` - America/Antigua + * `America/Araguaina` - America/Araguaina + * `America/Argentina/Buenos_Aires` - America/Argentina/Buenos_Aires + * `America/Argentina/Catamarca` - America/Argentina/Catamarca + * `America/Argentina/Cordoba` - America/Argentina/Cordoba + * `America/Argentina/Jujuy` - America/Argentina/Jujuy + * `America/Argentina/La_Rioja` - America/Argentina/La_Rioja + * `America/Argentina/Mendoza` - America/Argentina/Mendoza + * `America/Argentina/Rio_Gallegos` - America/Argentina/Rio_Gallegos + * `America/Argentina/Salta` - America/Argentina/Salta + * `America/Argentina/San_Juan` - America/Argentina/San_Juan + * `America/Argentina/San_Luis` - America/Argentina/San_Luis + * `America/Argentina/Tucuman` - America/Argentina/Tucuman + * `America/Argentina/Ushuaia` - America/Argentina/Ushuaia + * `America/Aruba` - America/Aruba + * `America/Asuncion` - America/Asuncion + * `America/Atikokan` - America/Atikokan + * `America/Bahia` - America/Bahia + * `America/Bahia_Banderas` - America/Bahia_Banderas + * `America/Barbados` - America/Barbados + * `America/Belem` - America/Belem + * `America/Belize` - America/Belize + * `America/Blanc-Sablon` - America/Blanc-Sablon + * `America/Boa_Vista` - America/Boa_Vista + * `America/Bogota` - America/Bogota + * `America/Boise` - America/Boise + * `America/Cambridge_Bay` - America/Cambridge_Bay + * `America/Campo_Grande` - America/Campo_Grande + * `America/Cancun` - America/Cancun + * `America/Caracas` - America/Caracas + * `America/Cayenne` - America/Cayenne + * `America/Cayman` - America/Cayman + * `America/Chicago` - America/Chicago + * `America/Chihuahua` - America/Chihuahua + * `America/Ciudad_Juarez` - America/Ciudad_Juarez + * `America/Costa_Rica` - America/Costa_Rica + * `America/Coyhaique` - America/Coyhaique + * `America/Creston` - America/Creston + * `America/Cuiaba` - America/Cuiaba + * `America/Curacao` - America/Curacao + * `America/Danmarkshavn` - America/Danmarkshavn + * `America/Dawson` - America/Dawson + * `America/Dawson_Creek` - America/Dawson_Creek + * `America/Denver` - America/Denver + * `America/Detroit` - America/Detroit + * `America/Dominica` - America/Dominica + * `America/Edmonton` - America/Edmonton + * `America/Eirunepe` - America/Eirunepe + * `America/El_Salvador` - America/El_Salvador + * `America/Fort_Nelson` - America/Fort_Nelson + * `America/Fortaleza` - America/Fortaleza + * `America/Glace_Bay` - America/Glace_Bay + * `America/Goose_Bay` - America/Goose_Bay + * `America/Grand_Turk` - America/Grand_Turk + * `America/Grenada` - America/Grenada + * `America/Guadeloupe` - America/Guadeloupe + * `America/Guatemala` - America/Guatemala + * `America/Guayaquil` - America/Guayaquil + * `America/Guyana` - America/Guyana + * `America/Halifax` - America/Halifax + * `America/Havana` - America/Havana + * `America/Hermosillo` - America/Hermosillo + * `America/Indiana/Indianapolis` - America/Indiana/Indianapolis + * `America/Indiana/Knox` - America/Indiana/Knox + * `America/Indiana/Marengo` - America/Indiana/Marengo + * `America/Indiana/Petersburg` - America/Indiana/Petersburg + * `America/Indiana/Tell_City` - America/Indiana/Tell_City + * `America/Indiana/Vevay` - America/Indiana/Vevay + * `America/Indiana/Vincennes` - America/Indiana/Vincennes + * `America/Indiana/Winamac` - America/Indiana/Winamac + * `America/Inuvik` - America/Inuvik + * `America/Iqaluit` - America/Iqaluit + * `America/Jamaica` - America/Jamaica + * `America/Juneau` - America/Juneau + * `America/Kentucky/Louisville` - America/Kentucky/Louisville + * `America/Kentucky/Monticello` - America/Kentucky/Monticello + * `America/Kralendijk` - America/Kralendijk + * `America/La_Paz` - America/La_Paz + * `America/Lima` - America/Lima + * `America/Los_Angeles` - America/Los_Angeles + * `America/Lower_Princes` - America/Lower_Princes + * `America/Maceio` - America/Maceio + * `America/Managua` - America/Managua + * `America/Manaus` - America/Manaus + * `America/Marigot` - America/Marigot + * `America/Martinique` - America/Martinique + * `America/Matamoros` - America/Matamoros + * `America/Mazatlan` - America/Mazatlan + * `America/Menominee` - America/Menominee + * `America/Merida` - America/Merida + * `America/Metlakatla` - America/Metlakatla + * `America/Mexico_City` - America/Mexico_City + * `America/Miquelon` - America/Miquelon + * `America/Moncton` - America/Moncton + * `America/Monterrey` - America/Monterrey + * `America/Montevideo` - America/Montevideo + * `America/Montserrat` - America/Montserrat + * `America/Nassau` - America/Nassau + * `America/New_York` - America/New_York + * `America/Nome` - America/Nome + * `America/Noronha` - America/Noronha + * `America/North_Dakota/Beulah` - America/North_Dakota/Beulah + * `America/North_Dakota/Center` - America/North_Dakota/Center + * `America/North_Dakota/New_Salem` - America/North_Dakota/New_Salem + * `America/Nuuk` - America/Nuuk + * `America/Ojinaga` - America/Ojinaga + * `America/Panama` - America/Panama + * `America/Paramaribo` - America/Paramaribo + * `America/Phoenix` - America/Phoenix + * `America/Port-au-Prince` - America/Port-au-Prince + * `America/Port_of_Spain` - America/Port_of_Spain + * `America/Porto_Velho` - America/Porto_Velho + * `America/Puerto_Rico` - America/Puerto_Rico + * `America/Punta_Arenas` - America/Punta_Arenas + * `America/Rankin_Inlet` - America/Rankin_Inlet + * `America/Recife` - America/Recife + * `America/Regina` - America/Regina + * `America/Resolute` - America/Resolute + * `America/Rio_Branco` - America/Rio_Branco + * `America/Santarem` - America/Santarem + * `America/Santiago` - America/Santiago + * `America/Santo_Domingo` - America/Santo_Domingo + * `America/Sao_Paulo` - America/Sao_Paulo + * `America/Scoresbysund` - America/Scoresbysund + * `America/Sitka` - America/Sitka + * `America/St_Barthelemy` - America/St_Barthelemy + * `America/St_Johns` - America/St_Johns + * `America/St_Kitts` - America/St_Kitts + * `America/St_Lucia` - America/St_Lucia + * `America/St_Thomas` - America/St_Thomas + * `America/St_Vincent` - America/St_Vincent + * `America/Swift_Current` - America/Swift_Current + * `America/Tegucigalpa` - America/Tegucigalpa + * `America/Thule` - America/Thule + * `America/Tijuana` - America/Tijuana + * `America/Toronto` - America/Toronto + * `America/Tortola` - America/Tortola + * `America/Vancouver` - America/Vancouver + * `America/Whitehorse` - America/Whitehorse + * `America/Winnipeg` - America/Winnipeg + * `America/Yakutat` - America/Yakutat + * `Antarctica/Casey` - Antarctica/Casey + * `Antarctica/Davis` - Antarctica/Davis + * `Antarctica/DumontDUrville` - Antarctica/DumontDUrville + * `Antarctica/Macquarie` - Antarctica/Macquarie + * `Antarctica/Mawson` - Antarctica/Mawson + * `Antarctica/McMurdo` - Antarctica/McMurdo + * `Antarctica/Palmer` - Antarctica/Palmer + * `Antarctica/Rothera` - Antarctica/Rothera + * `Antarctica/Syowa` - Antarctica/Syowa + * `Antarctica/Troll` - Antarctica/Troll + * `Antarctica/Vostok` - Antarctica/Vostok + * `Arctic/Longyearbyen` - Arctic/Longyearbyen + * `Asia/Aden` - Asia/Aden + * `Asia/Almaty` - Asia/Almaty + * `Asia/Amman` - Asia/Amman + * `Asia/Anadyr` - Asia/Anadyr + * `Asia/Aqtau` - Asia/Aqtau + * `Asia/Aqtobe` - Asia/Aqtobe + * `Asia/Ashgabat` - Asia/Ashgabat + * `Asia/Atyrau` - Asia/Atyrau + * `Asia/Baghdad` - Asia/Baghdad + * `Asia/Bahrain` - Asia/Bahrain + * `Asia/Baku` - Asia/Baku + * `Asia/Bangkok` - Asia/Bangkok + * `Asia/Barnaul` - Asia/Barnaul + * `Asia/Beirut` - Asia/Beirut + * `Asia/Bishkek` - Asia/Bishkek + * `Asia/Brunei` - Asia/Brunei + * `Asia/Chita` - Asia/Chita + * `Asia/Colombo` - Asia/Colombo + * `Asia/Damascus` - Asia/Damascus + * `Asia/Dhaka` - Asia/Dhaka + * `Asia/Dili` - Asia/Dili + * `Asia/Dubai` - Asia/Dubai + * `Asia/Dushanbe` - Asia/Dushanbe + * `Asia/Famagusta` - Asia/Famagusta + * `Asia/Gaza` - Asia/Gaza + * `Asia/Hebron` - Asia/Hebron + * `Asia/Ho_Chi_Minh` - Asia/Ho_Chi_Minh + * `Asia/Hong_Kong` - Asia/Hong_Kong + * `Asia/Hovd` - Asia/Hovd + * `Asia/Irkutsk` - Asia/Irkutsk + * `Asia/Jakarta` - Asia/Jakarta + * `Asia/Jayapura` - Asia/Jayapura + * `Asia/Jerusalem` - Asia/Jerusalem + * `Asia/Kabul` - Asia/Kabul + * `Asia/Kamchatka` - Asia/Kamchatka + * `Asia/Karachi` - Asia/Karachi + * `Asia/Kathmandu` - Asia/Kathmandu + * `Asia/Khandyga` - Asia/Khandyga + * `Asia/Kolkata` - Asia/Kolkata + * `Asia/Krasnoyarsk` - Asia/Krasnoyarsk + * `Asia/Kuala_Lumpur` - Asia/Kuala_Lumpur + * `Asia/Kuching` - Asia/Kuching + * `Asia/Kuwait` - Asia/Kuwait + * `Asia/Macau` - Asia/Macau + * `Asia/Magadan` - Asia/Magadan + * `Asia/Makassar` - Asia/Makassar + * `Asia/Manila` - Asia/Manila + * `Asia/Muscat` - Asia/Muscat + * `Asia/Nicosia` - Asia/Nicosia + * `Asia/Novokuznetsk` - Asia/Novokuznetsk + * `Asia/Novosibirsk` - Asia/Novosibirsk + * `Asia/Omsk` - Asia/Omsk + * `Asia/Oral` - Asia/Oral + * `Asia/Phnom_Penh` - Asia/Phnom_Penh + * `Asia/Pontianak` - Asia/Pontianak + * `Asia/Pyongyang` - Asia/Pyongyang + * `Asia/Qatar` - Asia/Qatar + * `Asia/Qostanay` - Asia/Qostanay + * `Asia/Qyzylorda` - Asia/Qyzylorda + * `Asia/Riyadh` - Asia/Riyadh + * `Asia/Sakhalin` - Asia/Sakhalin + * `Asia/Samarkand` - Asia/Samarkand + * `Asia/Seoul` - Asia/Seoul + * `Asia/Shanghai` - Asia/Shanghai + * `Asia/Singapore` - Asia/Singapore + * `Asia/Srednekolymsk` - Asia/Srednekolymsk + * `Asia/Taipei` - Asia/Taipei + * `Asia/Tashkent` - Asia/Tashkent + * `Asia/Tbilisi` - Asia/Tbilisi + * `Asia/Tehran` - Asia/Tehran + * `Asia/Thimphu` - Asia/Thimphu + * `Asia/Tokyo` - Asia/Tokyo + * `Asia/Tomsk` - Asia/Tomsk + * `Asia/Ulaanbaatar` - Asia/Ulaanbaatar + * `Asia/Urumqi` - Asia/Urumqi + * `Asia/Ust-Nera` - Asia/Ust-Nera + * `Asia/Vientiane` - Asia/Vientiane + * `Asia/Vladivostok` - Asia/Vladivostok + * `Asia/Yakutsk` - Asia/Yakutsk + * `Asia/Yangon` - Asia/Yangon + * `Asia/Yekaterinburg` - Asia/Yekaterinburg + * `Asia/Yerevan` - Asia/Yerevan + * `Atlantic/Azores` - Atlantic/Azores + * `Atlantic/Bermuda` - Atlantic/Bermuda + * `Atlantic/Canary` - Atlantic/Canary + * `Atlantic/Cape_Verde` - Atlantic/Cape_Verde + * `Atlantic/Faroe` - Atlantic/Faroe + * `Atlantic/Madeira` - Atlantic/Madeira + * `Atlantic/Reykjavik` - Atlantic/Reykjavik + * `Atlantic/South_Georgia` - Atlantic/South_Georgia + * `Atlantic/St_Helena` - Atlantic/St_Helena + * `Atlantic/Stanley` - Atlantic/Stanley + * `Australia/Adelaide` - Australia/Adelaide + * `Australia/Brisbane` - Australia/Brisbane + * `Australia/Broken_Hill` - Australia/Broken_Hill + * `Australia/Darwin` - Australia/Darwin + * `Australia/Eucla` - Australia/Eucla + * `Australia/Hobart` - Australia/Hobart + * `Australia/Lindeman` - Australia/Lindeman + * `Australia/Lord_Howe` - Australia/Lord_Howe + * `Australia/Melbourne` - Australia/Melbourne + * `Australia/Perth` - Australia/Perth + * `Australia/Sydney` - Australia/Sydney + * `Europe/Amsterdam` - Europe/Amsterdam + * `Europe/Andorra` - Europe/Andorra + * `Europe/Astrakhan` - Europe/Astrakhan + * `Europe/Athens` - Europe/Athens + * `Europe/Belgrade` - Europe/Belgrade + * `Europe/Berlin` - Europe/Berlin + * `Europe/Bratislava` - Europe/Bratislava + * `Europe/Brussels` - Europe/Brussels + * `Europe/Bucharest` - Europe/Bucharest + * `Europe/Budapest` - Europe/Budapest + * `Europe/Busingen` - Europe/Busingen + * `Europe/Chisinau` - Europe/Chisinau + * `Europe/Copenhagen` - Europe/Copenhagen + * `Europe/Dublin` - Europe/Dublin + * `Europe/Gibraltar` - Europe/Gibraltar + * `Europe/Guernsey` - Europe/Guernsey + * `Europe/Helsinki` - Europe/Helsinki + * `Europe/Isle_of_Man` - Europe/Isle_of_Man + * `Europe/Istanbul` - Europe/Istanbul + * `Europe/Jersey` - Europe/Jersey + * `Europe/Kaliningrad` - Europe/Kaliningrad + * `Europe/Kirov` - Europe/Kirov + * `Europe/Kyiv` - Europe/Kyiv + * `Europe/Lisbon` - Europe/Lisbon + * `Europe/Ljubljana` - Europe/Ljubljana + * `Europe/London` - Europe/London + * `Europe/Luxembourg` - Europe/Luxembourg + * `Europe/Madrid` - Europe/Madrid + * `Europe/Malta` - Europe/Malta + * `Europe/Mariehamn` - Europe/Mariehamn + * `Europe/Minsk` - Europe/Minsk + * `Europe/Monaco` - Europe/Monaco + * `Europe/Moscow` - Europe/Moscow + * `Europe/Oslo` - Europe/Oslo + * `Europe/Paris` - Europe/Paris + * `Europe/Podgorica` - Europe/Podgorica + * `Europe/Prague` - Europe/Prague + * `Europe/Riga` - Europe/Riga + * `Europe/Rome` - Europe/Rome + * `Europe/Samara` - Europe/Samara + * `Europe/San_Marino` - Europe/San_Marino + * `Europe/Sarajevo` - Europe/Sarajevo + * `Europe/Saratov` - Europe/Saratov + * `Europe/Simferopol` - Europe/Simferopol + * `Europe/Skopje` - Europe/Skopje + * `Europe/Sofia` - Europe/Sofia + * `Europe/Stockholm` - Europe/Stockholm + * `Europe/Tallinn` - Europe/Tallinn + * `Europe/Tirane` - Europe/Tirane + * `Europe/Ulyanovsk` - Europe/Ulyanovsk + * `Europe/Vaduz` - Europe/Vaduz + * `Europe/Vatican` - Europe/Vatican + * `Europe/Vienna` - Europe/Vienna + * `Europe/Vilnius` - Europe/Vilnius + * `Europe/Volgograd` - Europe/Volgograd + * `Europe/Warsaw` - Europe/Warsaw + * `Europe/Zagreb` - Europe/Zagreb + * `Europe/Zurich` - Europe/Zurich + * `GMT` - GMT + * `Indian/Antananarivo` - Indian/Antananarivo + * `Indian/Chagos` - Indian/Chagos + * `Indian/Christmas` - Indian/Christmas + * `Indian/Cocos` - Indian/Cocos + * `Indian/Comoro` - Indian/Comoro + * `Indian/Kerguelen` - Indian/Kerguelen + * `Indian/Mahe` - Indian/Mahe + * `Indian/Maldives` - Indian/Maldives + * `Indian/Mauritius` - Indian/Mauritius + * `Indian/Mayotte` - Indian/Mayotte + * `Indian/Reunion` - Indian/Reunion + * `Pacific/Apia` - Pacific/Apia + * `Pacific/Auckland` - Pacific/Auckland + * `Pacific/Bougainville` - Pacific/Bougainville + * `Pacific/Chatham` - Pacific/Chatham + * `Pacific/Chuuk` - Pacific/Chuuk + * `Pacific/Easter` - Pacific/Easter + * `Pacific/Efate` - Pacific/Efate + * `Pacific/Fakaofo` - Pacific/Fakaofo + * `Pacific/Fiji` - Pacific/Fiji + * `Pacific/Funafuti` - Pacific/Funafuti + * `Pacific/Galapagos` - Pacific/Galapagos + * `Pacific/Gambier` - Pacific/Gambier + * `Pacific/Guadalcanal` - Pacific/Guadalcanal + * `Pacific/Guam` - Pacific/Guam + * `Pacific/Honolulu` - Pacific/Honolulu + * `Pacific/Kanton` - Pacific/Kanton + * `Pacific/Kiritimati` - Pacific/Kiritimati + * `Pacific/Kosrae` - Pacific/Kosrae + * `Pacific/Kwajalein` - Pacific/Kwajalein + * `Pacific/Majuro` - Pacific/Majuro + * `Pacific/Marquesas` - Pacific/Marquesas + * `Pacific/Midway` - Pacific/Midway + * `Pacific/Nauru` - Pacific/Nauru + * `Pacific/Niue` - Pacific/Niue + * `Pacific/Norfolk` - Pacific/Norfolk + * `Pacific/Noumea` - Pacific/Noumea + * `Pacific/Pago_Pago` - Pacific/Pago_Pago + * `Pacific/Palau` - Pacific/Palau + * `Pacific/Pitcairn` - Pacific/Pitcairn + * `Pacific/Pohnpei` - Pacific/Pohnpei + * `Pacific/Port_Moresby` - Pacific/Port_Moresby + * `Pacific/Rarotonga` - Pacific/Rarotonga + * `Pacific/Saipan` - Pacific/Saipan + * `Pacific/Tahiti` - Pacific/Tahiti + * `Pacific/Tarawa` - Pacific/Tarawa + * `Pacific/Tongatapu` - Pacific/Tongatapu + * `Pacific/Wake` - Pacific/Wake + * `Pacific/Wallis` - Pacific/Wallis + * `UTC` - UTC + local_timezone: + nullable: true + description: |- + User's current local timezone (if traveling) + + * `Africa/Abidjan` - Africa/Abidjan + * `Africa/Accra` - Africa/Accra + * `Africa/Addis_Ababa` - Africa/Addis_Ababa + * `Africa/Algiers` - Africa/Algiers + * `Africa/Asmara` - Africa/Asmara + * `Africa/Bamako` - Africa/Bamako + * `Africa/Bangui` - Africa/Bangui + * `Africa/Banjul` - Africa/Banjul + * `Africa/Bissau` - Africa/Bissau + * `Africa/Blantyre` - Africa/Blantyre + * `Africa/Brazzaville` - Africa/Brazzaville + * `Africa/Bujumbura` - Africa/Bujumbura + * `Africa/Cairo` - Africa/Cairo + * `Africa/Casablanca` - Africa/Casablanca + * `Africa/Ceuta` - Africa/Ceuta + * `Africa/Conakry` - Africa/Conakry + * `Africa/Dakar` - Africa/Dakar + * `Africa/Dar_es_Salaam` - Africa/Dar_es_Salaam + * `Africa/Djibouti` - Africa/Djibouti + * `Africa/Douala` - Africa/Douala + * `Africa/El_Aaiun` - Africa/El_Aaiun + * `Africa/Freetown` - Africa/Freetown + * `Africa/Gaborone` - Africa/Gaborone + * `Africa/Harare` - Africa/Harare + * `Africa/Johannesburg` - Africa/Johannesburg + * `Africa/Juba` - Africa/Juba + * `Africa/Kampala` - Africa/Kampala + * `Africa/Khartoum` - Africa/Khartoum + * `Africa/Kigali` - Africa/Kigali + * `Africa/Kinshasa` - Africa/Kinshasa + * `Africa/Lagos` - Africa/Lagos + * `Africa/Libreville` - Africa/Libreville + * `Africa/Lome` - Africa/Lome + * `Africa/Luanda` - Africa/Luanda + * `Africa/Lubumbashi` - Africa/Lubumbashi + * `Africa/Lusaka` - Africa/Lusaka + * `Africa/Malabo` - Africa/Malabo + * `Africa/Maputo` - Africa/Maputo + * `Africa/Maseru` - Africa/Maseru + * `Africa/Mbabane` - Africa/Mbabane + * `Africa/Mogadishu` - Africa/Mogadishu + * `Africa/Monrovia` - Africa/Monrovia + * `Africa/Nairobi` - Africa/Nairobi + * `Africa/Ndjamena` - Africa/Ndjamena + * `Africa/Niamey` - Africa/Niamey + * `Africa/Nouakchott` - Africa/Nouakchott + * `Africa/Ouagadougou` - Africa/Ouagadougou + * `Africa/Porto-Novo` - Africa/Porto-Novo + * `Africa/Sao_Tome` - Africa/Sao_Tome + * `Africa/Tripoli` - Africa/Tripoli + * `Africa/Tunis` - Africa/Tunis + * `Africa/Windhoek` - Africa/Windhoek + * `America/Adak` - America/Adak + * `America/Anchorage` - America/Anchorage + * `America/Anguilla` - America/Anguilla + * `America/Antigua` - America/Antigua + * `America/Araguaina` - America/Araguaina + * `America/Argentina/Buenos_Aires` - America/Argentina/Buenos_Aires + * `America/Argentina/Catamarca` - America/Argentina/Catamarca + * `America/Argentina/Cordoba` - America/Argentina/Cordoba + * `America/Argentina/Jujuy` - America/Argentina/Jujuy + * `America/Argentina/La_Rioja` - America/Argentina/La_Rioja + * `America/Argentina/Mendoza` - America/Argentina/Mendoza + * `America/Argentina/Rio_Gallegos` - America/Argentina/Rio_Gallegos + * `America/Argentina/Salta` - America/Argentina/Salta + * `America/Argentina/San_Juan` - America/Argentina/San_Juan + * `America/Argentina/San_Luis` - America/Argentina/San_Luis + * `America/Argentina/Tucuman` - America/Argentina/Tucuman + * `America/Argentina/Ushuaia` - America/Argentina/Ushuaia + * `America/Aruba` - America/Aruba + * `America/Asuncion` - America/Asuncion + * `America/Atikokan` - America/Atikokan + * `America/Bahia` - America/Bahia + * `America/Bahia_Banderas` - America/Bahia_Banderas + * `America/Barbados` - America/Barbados + * `America/Belem` - America/Belem + * `America/Belize` - America/Belize + * `America/Blanc-Sablon` - America/Blanc-Sablon + * `America/Boa_Vista` - America/Boa_Vista + * `America/Bogota` - America/Bogota + * `America/Boise` - America/Boise + * `America/Cambridge_Bay` - America/Cambridge_Bay + * `America/Campo_Grande` - America/Campo_Grande + * `America/Cancun` - America/Cancun + * `America/Caracas` - America/Caracas + * `America/Cayenne` - America/Cayenne + * `America/Cayman` - America/Cayman + * `America/Chicago` - America/Chicago + * `America/Chihuahua` - America/Chihuahua + * `America/Ciudad_Juarez` - America/Ciudad_Juarez + * `America/Costa_Rica` - America/Costa_Rica + * `America/Coyhaique` - America/Coyhaique + * `America/Creston` - America/Creston + * `America/Cuiaba` - America/Cuiaba + * `America/Curacao` - America/Curacao + * `America/Danmarkshavn` - America/Danmarkshavn + * `America/Dawson` - America/Dawson + * `America/Dawson_Creek` - America/Dawson_Creek + * `America/Denver` - America/Denver + * `America/Detroit` - America/Detroit + * `America/Dominica` - America/Dominica + * `America/Edmonton` - America/Edmonton + * `America/Eirunepe` - America/Eirunepe + * `America/El_Salvador` - America/El_Salvador + * `America/Fort_Nelson` - America/Fort_Nelson + * `America/Fortaleza` - America/Fortaleza + * `America/Glace_Bay` - America/Glace_Bay + * `America/Goose_Bay` - America/Goose_Bay + * `America/Grand_Turk` - America/Grand_Turk + * `America/Grenada` - America/Grenada + * `America/Guadeloupe` - America/Guadeloupe + * `America/Guatemala` - America/Guatemala + * `America/Guayaquil` - America/Guayaquil + * `America/Guyana` - America/Guyana + * `America/Halifax` - America/Halifax + * `America/Havana` - America/Havana + * `America/Hermosillo` - America/Hermosillo + * `America/Indiana/Indianapolis` - America/Indiana/Indianapolis + * `America/Indiana/Knox` - America/Indiana/Knox + * `America/Indiana/Marengo` - America/Indiana/Marengo + * `America/Indiana/Petersburg` - America/Indiana/Petersburg + * `America/Indiana/Tell_City` - America/Indiana/Tell_City + * `America/Indiana/Vevay` - America/Indiana/Vevay + * `America/Indiana/Vincennes` - America/Indiana/Vincennes + * `America/Indiana/Winamac` - America/Indiana/Winamac + * `America/Inuvik` - America/Inuvik + * `America/Iqaluit` - America/Iqaluit + * `America/Jamaica` - America/Jamaica + * `America/Juneau` - America/Juneau + * `America/Kentucky/Louisville` - America/Kentucky/Louisville + * `America/Kentucky/Monticello` - America/Kentucky/Monticello + * `America/Kralendijk` - America/Kralendijk + * `America/La_Paz` - America/La_Paz + * `America/Lima` - America/Lima + * `America/Los_Angeles` - America/Los_Angeles + * `America/Lower_Princes` - America/Lower_Princes + * `America/Maceio` - America/Maceio + * `America/Managua` - America/Managua + * `America/Manaus` - America/Manaus + * `America/Marigot` - America/Marigot + * `America/Martinique` - America/Martinique + * `America/Matamoros` - America/Matamoros + * `America/Mazatlan` - America/Mazatlan + * `America/Menominee` - America/Menominee + * `America/Merida` - America/Merida + * `America/Metlakatla` - America/Metlakatla + * `America/Mexico_City` - America/Mexico_City + * `America/Miquelon` - America/Miquelon + * `America/Moncton` - America/Moncton + * `America/Monterrey` - America/Monterrey + * `America/Montevideo` - America/Montevideo + * `America/Montserrat` - America/Montserrat + * `America/Nassau` - America/Nassau + * `America/New_York` - America/New_York + * `America/Nome` - America/Nome + * `America/Noronha` - America/Noronha + * `America/North_Dakota/Beulah` - America/North_Dakota/Beulah + * `America/North_Dakota/Center` - America/North_Dakota/Center + * `America/North_Dakota/New_Salem` - America/North_Dakota/New_Salem + * `America/Nuuk` - America/Nuuk + * `America/Ojinaga` - America/Ojinaga + * `America/Panama` - America/Panama + * `America/Paramaribo` - America/Paramaribo + * `America/Phoenix` - America/Phoenix + * `America/Port-au-Prince` - America/Port-au-Prince + * `America/Port_of_Spain` - America/Port_of_Spain + * `America/Porto_Velho` - America/Porto_Velho + * `America/Puerto_Rico` - America/Puerto_Rico + * `America/Punta_Arenas` - America/Punta_Arenas + * `America/Rankin_Inlet` - America/Rankin_Inlet + * `America/Recife` - America/Recife + * `America/Regina` - America/Regina + * `America/Resolute` - America/Resolute + * `America/Rio_Branco` - America/Rio_Branco + * `America/Santarem` - America/Santarem + * `America/Santiago` - America/Santiago + * `America/Santo_Domingo` - America/Santo_Domingo + * `America/Sao_Paulo` - America/Sao_Paulo + * `America/Scoresbysund` - America/Scoresbysund + * `America/Sitka` - America/Sitka + * `America/St_Barthelemy` - America/St_Barthelemy + * `America/St_Johns` - America/St_Johns + * `America/St_Kitts` - America/St_Kitts + * `America/St_Lucia` - America/St_Lucia + * `America/St_Thomas` - America/St_Thomas + * `America/St_Vincent` - America/St_Vincent + * `America/Swift_Current` - America/Swift_Current + * `America/Tegucigalpa` - America/Tegucigalpa + * `America/Thule` - America/Thule + * `America/Tijuana` - America/Tijuana + * `America/Toronto` - America/Toronto + * `America/Tortola` - America/Tortola + * `America/Vancouver` - America/Vancouver + * `America/Whitehorse` - America/Whitehorse + * `America/Winnipeg` - America/Winnipeg + * `America/Yakutat` - America/Yakutat + * `Antarctica/Casey` - Antarctica/Casey + * `Antarctica/Davis` - Antarctica/Davis + * `Antarctica/DumontDUrville` - Antarctica/DumontDUrville + * `Antarctica/Macquarie` - Antarctica/Macquarie + * `Antarctica/Mawson` - Antarctica/Mawson + * `Antarctica/McMurdo` - Antarctica/McMurdo + * `Antarctica/Palmer` - Antarctica/Palmer + * `Antarctica/Rothera` - Antarctica/Rothera + * `Antarctica/Syowa` - Antarctica/Syowa + * `Antarctica/Troll` - Antarctica/Troll + * `Antarctica/Vostok` - Antarctica/Vostok + * `Arctic/Longyearbyen` - Arctic/Longyearbyen + * `Asia/Aden` - Asia/Aden + * `Asia/Almaty` - Asia/Almaty + * `Asia/Amman` - Asia/Amman + * `Asia/Anadyr` - Asia/Anadyr + * `Asia/Aqtau` - Asia/Aqtau + * `Asia/Aqtobe` - Asia/Aqtobe + * `Asia/Ashgabat` - Asia/Ashgabat + * `Asia/Atyrau` - Asia/Atyrau + * `Asia/Baghdad` - Asia/Baghdad + * `Asia/Bahrain` - Asia/Bahrain + * `Asia/Baku` - Asia/Baku + * `Asia/Bangkok` - Asia/Bangkok + * `Asia/Barnaul` - Asia/Barnaul + * `Asia/Beirut` - Asia/Beirut + * `Asia/Bishkek` - Asia/Bishkek + * `Asia/Brunei` - Asia/Brunei + * `Asia/Chita` - Asia/Chita + * `Asia/Colombo` - Asia/Colombo + * `Asia/Damascus` - Asia/Damascus + * `Asia/Dhaka` - Asia/Dhaka + * `Asia/Dili` - Asia/Dili + * `Asia/Dubai` - Asia/Dubai + * `Asia/Dushanbe` - Asia/Dushanbe + * `Asia/Famagusta` - Asia/Famagusta + * `Asia/Gaza` - Asia/Gaza + * `Asia/Hebron` - Asia/Hebron + * `Asia/Ho_Chi_Minh` - Asia/Ho_Chi_Minh + * `Asia/Hong_Kong` - Asia/Hong_Kong + * `Asia/Hovd` - Asia/Hovd + * `Asia/Irkutsk` - Asia/Irkutsk + * `Asia/Jakarta` - Asia/Jakarta + * `Asia/Jayapura` - Asia/Jayapura + * `Asia/Jerusalem` - Asia/Jerusalem + * `Asia/Kabul` - Asia/Kabul + * `Asia/Kamchatka` - Asia/Kamchatka + * `Asia/Karachi` - Asia/Karachi + * `Asia/Kathmandu` - Asia/Kathmandu + * `Asia/Khandyga` - Asia/Khandyga + * `Asia/Kolkata` - Asia/Kolkata + * `Asia/Krasnoyarsk` - Asia/Krasnoyarsk + * `Asia/Kuala_Lumpur` - Asia/Kuala_Lumpur + * `Asia/Kuching` - Asia/Kuching + * `Asia/Kuwait` - Asia/Kuwait + * `Asia/Macau` - Asia/Macau + * `Asia/Magadan` - Asia/Magadan + * `Asia/Makassar` - Asia/Makassar + * `Asia/Manila` - Asia/Manila + * `Asia/Muscat` - Asia/Muscat + * `Asia/Nicosia` - Asia/Nicosia + * `Asia/Novokuznetsk` - Asia/Novokuznetsk + * `Asia/Novosibirsk` - Asia/Novosibirsk + * `Asia/Omsk` - Asia/Omsk + * `Asia/Oral` - Asia/Oral + * `Asia/Phnom_Penh` - Asia/Phnom_Penh + * `Asia/Pontianak` - Asia/Pontianak + * `Asia/Pyongyang` - Asia/Pyongyang + * `Asia/Qatar` - Asia/Qatar + * `Asia/Qostanay` - Asia/Qostanay + * `Asia/Qyzylorda` - Asia/Qyzylorda + * `Asia/Riyadh` - Asia/Riyadh + * `Asia/Sakhalin` - Asia/Sakhalin + * `Asia/Samarkand` - Asia/Samarkand + * `Asia/Seoul` - Asia/Seoul + * `Asia/Shanghai` - Asia/Shanghai + * `Asia/Singapore` - Asia/Singapore + * `Asia/Srednekolymsk` - Asia/Srednekolymsk + * `Asia/Taipei` - Asia/Taipei + * `Asia/Tashkent` - Asia/Tashkent + * `Asia/Tbilisi` - Asia/Tbilisi + * `Asia/Tehran` - Asia/Tehran + * `Asia/Thimphu` - Asia/Thimphu + * `Asia/Tokyo` - Asia/Tokyo + * `Asia/Tomsk` - Asia/Tomsk + * `Asia/Ulaanbaatar` - Asia/Ulaanbaatar + * `Asia/Urumqi` - Asia/Urumqi + * `Asia/Ust-Nera` - Asia/Ust-Nera + * `Asia/Vientiane` - Asia/Vientiane + * `Asia/Vladivostok` - Asia/Vladivostok + * `Asia/Yakutsk` - Asia/Yakutsk + * `Asia/Yangon` - Asia/Yangon + * `Asia/Yekaterinburg` - Asia/Yekaterinburg + * `Asia/Yerevan` - Asia/Yerevan + * `Atlantic/Azores` - Atlantic/Azores + * `Atlantic/Bermuda` - Atlantic/Bermuda + * `Atlantic/Canary` - Atlantic/Canary + * `Atlantic/Cape_Verde` - Atlantic/Cape_Verde + * `Atlantic/Faroe` - Atlantic/Faroe + * `Atlantic/Madeira` - Atlantic/Madeira + * `Atlantic/Reykjavik` - Atlantic/Reykjavik + * `Atlantic/South_Georgia` - Atlantic/South_Georgia + * `Atlantic/St_Helena` - Atlantic/St_Helena + * `Atlantic/Stanley` - Atlantic/Stanley + * `Australia/Adelaide` - Australia/Adelaide + * `Australia/Brisbane` - Australia/Brisbane + * `Australia/Broken_Hill` - Australia/Broken_Hill + * `Australia/Darwin` - Australia/Darwin + * `Australia/Eucla` - Australia/Eucla + * `Australia/Hobart` - Australia/Hobart + * `Australia/Lindeman` - Australia/Lindeman + * `Australia/Lord_Howe` - Australia/Lord_Howe + * `Australia/Melbourne` - Australia/Melbourne + * `Australia/Perth` - Australia/Perth + * `Australia/Sydney` - Australia/Sydney + * `Europe/Amsterdam` - Europe/Amsterdam + * `Europe/Andorra` - Europe/Andorra + * `Europe/Astrakhan` - Europe/Astrakhan + * `Europe/Athens` - Europe/Athens + * `Europe/Belgrade` - Europe/Belgrade + * `Europe/Berlin` - Europe/Berlin + * `Europe/Bratislava` - Europe/Bratislava + * `Europe/Brussels` - Europe/Brussels + * `Europe/Bucharest` - Europe/Bucharest + * `Europe/Budapest` - Europe/Budapest + * `Europe/Busingen` - Europe/Busingen + * `Europe/Chisinau` - Europe/Chisinau + * `Europe/Copenhagen` - Europe/Copenhagen + * `Europe/Dublin` - Europe/Dublin + * `Europe/Gibraltar` - Europe/Gibraltar + * `Europe/Guernsey` - Europe/Guernsey + * `Europe/Helsinki` - Europe/Helsinki + * `Europe/Isle_of_Man` - Europe/Isle_of_Man + * `Europe/Istanbul` - Europe/Istanbul + * `Europe/Jersey` - Europe/Jersey + * `Europe/Kaliningrad` - Europe/Kaliningrad + * `Europe/Kirov` - Europe/Kirov + * `Europe/Kyiv` - Europe/Kyiv + * `Europe/Lisbon` - Europe/Lisbon + * `Europe/Ljubljana` - Europe/Ljubljana + * `Europe/London` - Europe/London + * `Europe/Luxembourg` - Europe/Luxembourg + * `Europe/Madrid` - Europe/Madrid + * `Europe/Malta` - Europe/Malta + * `Europe/Mariehamn` - Europe/Mariehamn + * `Europe/Minsk` - Europe/Minsk + * `Europe/Monaco` - Europe/Monaco + * `Europe/Moscow` - Europe/Moscow + * `Europe/Oslo` - Europe/Oslo + * `Europe/Paris` - Europe/Paris + * `Europe/Podgorica` - Europe/Podgorica + * `Europe/Prague` - Europe/Prague + * `Europe/Riga` - Europe/Riga + * `Europe/Rome` - Europe/Rome + * `Europe/Samara` - Europe/Samara + * `Europe/San_Marino` - Europe/San_Marino + * `Europe/Sarajevo` - Europe/Sarajevo + * `Europe/Saratov` - Europe/Saratov + * `Europe/Simferopol` - Europe/Simferopol + * `Europe/Skopje` - Europe/Skopje + * `Europe/Sofia` - Europe/Sofia + * `Europe/Stockholm` - Europe/Stockholm + * `Europe/Tallinn` - Europe/Tallinn + * `Europe/Tirane` - Europe/Tirane + * `Europe/Ulyanovsk` - Europe/Ulyanovsk + * `Europe/Vaduz` - Europe/Vaduz + * `Europe/Vatican` - Europe/Vatican + * `Europe/Vienna` - Europe/Vienna + * `Europe/Vilnius` - Europe/Vilnius + * `Europe/Volgograd` - Europe/Volgograd + * `Europe/Warsaw` - Europe/Warsaw + * `Europe/Zagreb` - Europe/Zagreb + * `Europe/Zurich` - Europe/Zurich + * `GMT` - GMT + * `Indian/Antananarivo` - Indian/Antananarivo + * `Indian/Chagos` - Indian/Chagos + * `Indian/Christmas` - Indian/Christmas + * `Indian/Cocos` - Indian/Cocos + * `Indian/Comoro` - Indian/Comoro + * `Indian/Kerguelen` - Indian/Kerguelen + * `Indian/Mahe` - Indian/Mahe + * `Indian/Maldives` - Indian/Maldives + * `Indian/Mauritius` - Indian/Mauritius + * `Indian/Mayotte` - Indian/Mayotte + * `Indian/Reunion` - Indian/Reunion + * `Pacific/Apia` - Pacific/Apia + * `Pacific/Auckland` - Pacific/Auckland + * `Pacific/Bougainville` - Pacific/Bougainville + * `Pacific/Chatham` - Pacific/Chatham + * `Pacific/Chuuk` - Pacific/Chuuk + * `Pacific/Easter` - Pacific/Easter + * `Pacific/Efate` - Pacific/Efate + * `Pacific/Fakaofo` - Pacific/Fakaofo + * `Pacific/Fiji` - Pacific/Fiji + * `Pacific/Funafuti` - Pacific/Funafuti + * `Pacific/Galapagos` - Pacific/Galapagos + * `Pacific/Gambier` - Pacific/Gambier + * `Pacific/Guadalcanal` - Pacific/Guadalcanal + * `Pacific/Guam` - Pacific/Guam + * `Pacific/Honolulu` - Pacific/Honolulu + * `Pacific/Kanton` - Pacific/Kanton + * `Pacific/Kiritimati` - Pacific/Kiritimati + * `Pacific/Kosrae` - Pacific/Kosrae + * `Pacific/Kwajalein` - Pacific/Kwajalein + * `Pacific/Majuro` - Pacific/Majuro + * `Pacific/Marquesas` - Pacific/Marquesas + * `Pacific/Midway` - Pacific/Midway + * `Pacific/Nauru` - Pacific/Nauru + * `Pacific/Niue` - Pacific/Niue + * `Pacific/Norfolk` - Pacific/Norfolk + * `Pacific/Noumea` - Pacific/Noumea + * `Pacific/Pago_Pago` - Pacific/Pago_Pago + * `Pacific/Palau` - Pacific/Palau + * `Pacific/Pitcairn` - Pacific/Pitcairn + * `Pacific/Pohnpei` - Pacific/Pohnpei + * `Pacific/Port_Moresby` - Pacific/Port_Moresby + * `Pacific/Rarotonga` - Pacific/Rarotonga + * `Pacific/Saipan` - Pacific/Saipan + * `Pacific/Tahiti` - Pacific/Tahiti + * `Pacific/Tarawa` - Pacific/Tarawa + * `Pacific/Tongatapu` - Pacific/Tongatapu + * `Pacific/Wake` - Pacific/Wake + * `Pacific/Wallis` - Pacific/Wallis + * `UTC` - UTC + oneOf: + - $ref: '#/components/schemas/LocalTimezoneEnum' + - $ref: '#/components/schemas/BlankEnum' + - $ref: '#/components/schemas/NullEnum' + bootswatch_theme: + type: integer + nullable: true + description: Bootswatch theme for light mode + PatchedVendorRequest: + type: object + properties: + name: + type: string + legal_name: + type: string + abbreviated_name: + type: string + overview: + type: string + history: + type: string + services_provided: + type: string + vendor_type: + type: string + employee_count: + type: integer + nullable: true + revenue: + type: string + format: decimal + pattern: ^-?\d{0,13}(?:\.\d{0,2})?$ + nullable: true + assets: + type: string + format: decimal + pattern: ^-?\d{0,13}(?:\.\d{0,2})?$ + nullable: true + fiscal_year_end: + type: string + format: date + nullable: true + partner_portal_url: + type: string + format: uri + is_competitor: + type: boolean + deal_registration_notes: + type: string + PatchedVendorSolutionRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 255 + vendor_id: + type: integer + writeOnly: true + description: + type: string + minLength: 1 + title: Solution Information + description: Detailed solution description with URL support + url: + type: string + format: uri + maxLength: 200 + notes: + type: string + minLength: 1 + description: Additional notes about the solution + category: + type: string + maxLength: 100 + release_date: + type: string + format: date + nullable: true + end_of_life_date: + type: string + format: date + nullable: true + documentation_url: + type: string + format: uri + maxLength: 200 + PatchedWorkshopRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 255 + date: + type: string + format: date + time: + type: string + format: time + duration: + allOf: + - $ref: '#/components/schemas/DurationEnum' + minimum: -2147483648 + maximum: 2147483647 + workshop_type: + $ref: '#/components/schemas/WorkshopTypeEnum' + participant_ids: + type: array + items: + type: integer + writeOnly: true + notes: + type: string + PotentialImpactEnum: + enum: + - Schedule + - Cost + - Quality + - Multiple + type: string + description: |- + * `Schedule` - Schedule + * `Cost` - Cost + * `Quality` - Quality + * `Multiple` - Multiple + PowerEnum: + enum: + - High + - Medium + - Low + type: string + description: |- + * `High` - High + * `Medium` - Medium + * `Low` - Low + PowerPointTemplate: + type: object + properties: + id: + type: integer + readOnly: true + name: + type: string + maxLength: 255 + description: + type: string + master_slides: + readOnly: true + color_palette: + readOnly: true + is_active: + type: boolean + created_date: + type: string + format: date-time + readOnly: true + modified_date: + type: string + format: date-time + readOnly: true + required: + - color_palette + - created_date + - id + - master_slides + - modified_date + - name + PowerPointTemplateRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + is_active: + type: boolean + required: + - name + Presentation: + type: object + properties: + id: + type: integer + readOnly: true + name: + type: string + maxLength: 255 + source_type: + $ref: '#/components/schemas/SourceTypeEnum' + source_id: + type: integer + maximum: 2147483647 + minimum: -2147483648 + date: + type: string + format: date + presenter: + type: integer + nullable: true + created_date: + type: string + format: date-time + readOnly: true + modified_date: + type: string + format: date-time + readOnly: true + required: + - created_date + - id + - modified_date + - name + - source_id + - source_type + PresentationRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 255 + source_type: + $ref: '#/components/schemas/SourceTypeEnum' + source_id: + type: integer + maximum: 2147483647 + minimum: -2147483648 + date: + type: string + format: date + presenter: + type: integer + nullable: true + required: + - name + - source_id + - source_type + PriceLineItem: + type: object + properties: + id: + type: integer + readOnly: true + vendor_solution: + type: integer + vendor_solution_name: + type: string + readOnly: true + type: + $ref: '#/components/schemas/PriceLineItemTypeEnum' + billing_cycle: + $ref: '#/components/schemas/BillingCycleEnum' + quantity: + type: string + format: decimal + pattern: ^-?\d{0,8}(?:\.\d{0,2})?$ + cost: + type: string + format: decimal + pattern: ^-?\d{0,13}(?:\.\d{0,2})?$ + margin: + type: string + format: decimal + pattern: ^-?\d{0,3}(?:\.\d{0,2})?$ + unit_price: + type: string + readOnly: true + total_price: + type: string + readOnly: true + order: + type: integer + maximum: 2147483647 + minimum: -2147483648 + required: + - cost + - id + - margin + - order + - total_price + - type + - unit_price + - vendor_solution + - vendor_solution_name + PriceLineItemRequest: + type: object + properties: + vendor_solution: + type: integer + type: + $ref: '#/components/schemas/PriceLineItemTypeEnum' + billing_cycle: + $ref: '#/components/schemas/BillingCycleEnum' + quantity: + type: string + format: decimal + pattern: ^-?\d{0,8}(?:\.\d{0,2})?$ + cost: + type: string + format: decimal + pattern: ^-?\d{0,13}(?:\.\d{0,2})?$ + margin: + type: string + format: decimal + pattern: ^-?\d{0,3}(?:\.\d{0,2})?$ + order: + type: integer + maximum: 2147483647 + minimum: -2147483648 + required: + - cost + - margin + - order + - type + - vendor_solution + PriceLineItemTypeEnum: + enum: + - Hardware + - Software + - Subscription + - Advisory + - Implementation + - Support + - Managed Service + type: string + description: |- + * `Hardware` - Hardware + * `Software` - Software + * `Subscription` - Subscription + * `Advisory` - Advisory + * `Implementation` - Implementation + * `Support` - Support + * `Managed Service` - Managed Service + PriceVersion: + type: object + properties: + id: + type: integer + readOnly: true + title: + type: string + maxLength: 255 + currency: + type: string + version_number: + type: integer + maximum: 2147483647 + minimum: -2147483648 + date_created: + type: string + format: date + readOnly: true + notes: + type: string + line_items: + type: string + readOnly: true + one_time_total: + type: string + readOnly: true + monthly_total: + type: string + readOnly: true + annual_total: + type: string + readOnly: true + total: + type: string + readOnly: true + required: + - annual_total + - currency + - date_created + - id + - line_items + - monthly_total + - one_time_total + - title + - total + - version_number + PriceVersionRequest: + type: object + properties: + title: + type: string + minLength: 1 + maxLength: 255 + currency: + type: string + minLength: 1 + version_number: + type: integer + maximum: 2147483647 + minimum: -2147483648 + notes: + type: string + required: + - currency + - title + - version_number + PricingTool: + type: object + properties: + id: + type: integer + readOnly: true + name: + type: string + maxLength: 255 + description: + type: string + proposal: + type: integer + nullable: true + engagement: + type: integer + nullable: true + client_name: + type: string + readOnly: true + association_display: + type: string + readOnly: true + latest_version: + type: string + readOnly: true + version_count: + type: string + readOnly: true + created_date: + type: string + format: date-time + readOnly: true + modified_date: + type: string + format: date-time + readOnly: true + required: + - association_display + - client_name + - created_date + - description + - id + - latest_version + - modified_date + - name + - version_count + PricingToolRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + minLength: 1 + proposal: + type: integer + nullable: true + engagement: + type: integer + nullable: true + required: + - description + - name + Principle: + type: object + description: Serialize all Principle fields. + properties: + id: + type: integer + readOnly: true + name: + type: string + maxLength: 255 + description: + type: string + weight: + type: integer + maximum: 2147483647 + minimum: -2147483648 + description: Importance (1-10) + order: + type: integer + maximum: 2147483647 + minimum: -2147483648 + created_date: + type: string + format: date-time + readOnly: true + required: + - created_date + - description + - id + - name + - order + PrincipleAlignment: + type: object + description: Serialize a PrincipleAlignment with nested principle on read and + writable ``principle_id`` on write. + properties: + principle: + allOf: + - $ref: '#/components/schemas/Principle' + readOnly: true + alignment_score: + type: integer + maximum: 2147483647 + minimum: -2147483648 + description: Alignment score (1-5) + notes: + type: string + required: + - alignment_score + - principle + PrincipleAlignmentRequest: + type: object + description: Serialize a PrincipleAlignment with nested principle on read and + writable ``principle_id`` on write. + properties: + principle_id: + type: integer + writeOnly: true + alignment_score: + type: integer + maximum: 2147483647 + minimum: -2147483648 + description: Alignment score (1-5) + notes: + type: string + required: + - alignment_score + - principle_id + PrincipleRequest: + type: object + description: Serialize all Principle fields. + properties: + name: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + minLength: 1 + weight: + type: integer + maximum: 2147483647 + minimum: -2147483648 + description: Importance (1-10) + order: + type: integer + maximum: 2147483647 + minimum: -2147483648 + required: + - description + - name + - order + Priority758Enum: + enum: + - High + - Medium + - Low + type: string + description: |- + * `High` - High + * `Medium` - Medium + * `Low` - Low + Proposal: + type: object + properties: + id: + type: integer + readOnly: true + name: + type: string + maxLength: 255 + opportunity: + allOf: + - $ref: '#/components/schemas/Opportunity' + readOnly: true + primary_contact: + allOf: + - $ref: '#/components/schemas/Contact' + readOnly: true + status: + $ref: '#/components/schemas/ProposalStatusEnum' + due_date: + type: string + format: date + nullable: true + submitted_date: + type: string + format: date + nullable: true + repository_url: + type: string + format: uri + maxLength: 200 + created_date: + type: string + format: date-time + readOnly: true + required: + - created_date + - id + - name + - opportunity + - primary_contact + - status + ProposalRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 255 + opportunity_id: + type: integer + writeOnly: true + primary_contact_id: + type: integer + writeOnly: true + nullable: true + status: + $ref: '#/components/schemas/ProposalStatusEnum' + due_date: + type: string + format: date + nullable: true + submitted_date: + type: string + format: date + nullable: true + repository_url: + type: string + format: uri + maxLength: 200 + required: + - name + - opportunity_id + - status + ProposalStatusEnum: + enum: + - Draft + - In Review + - Final + type: string + description: |- + * `Draft` - Draft + * `In Review` - In Review + * `Final` - Final + RAIDTool: + type: object + properties: + id: + type: integer + readOnly: true + name: + type: string + maxLength: 255 + description: + type: string + proposal: + type: integer + nullable: true + engagement: + type: integer + nullable: true + client_name: + type: string + readOnly: true + association_display: + type: string + readOnly: true + created_date: + type: string + format: date-time + readOnly: true + modified_date: + type: string + format: date-time + readOnly: true + risks: + type: array + items: + $ref: '#/components/schemas/Risk' + readOnly: true + assumptions: + type: array + items: + $ref: '#/components/schemas/Assumption' + readOnly: true + issues: + type: array + items: + $ref: '#/components/schemas/Issue' + readOnly: true + dependencies: + type: array + items: + $ref: '#/components/schemas/Dependency' + readOnly: true + required: + - association_display + - assumptions + - client_name + - created_date + - dependencies + - description + - id + - issues + - modified_date + - name + - risks + Recommendation: + type: object + description: |- + Serialize a Recommendation with nested impacts, alignments, findings, and requirements. + + ``finding_ids`` and ``requirement_ids`` are write-only; on read the full nested + objects are returned under ``associated_findings`` / ``associated_requirements``. + ``timeframe`` is a computed read-only field (Short / Medium / Long) derived from + ``implementation_days`` against fixed 30- and 180-day thresholds. + properties: + id: + type: integer + readOnly: true + title: + type: string + maxLength: 255 + description: + type: string + implementation_days: + type: integer + maximum: 2147483647 + minimum: -2147483648 + timeframe: + type: string + readOnly: true + associated_findings: + type: array + items: + $ref: '#/components/schemas/Finding' + readOnly: true + associated_requirements: + type: array + items: + $ref: '#/components/schemas/Requirement' + readOnly: true + cost: + type: string + format: decimal + pattern: ^-?\d{0,10}(?:\.\d{0,2})?$ + currency: + type: string + complexity: + $ref: '#/components/schemas/ComplexityEnum' + impacts: + type: array + items: + $ref: '#/components/schemas/RecommendationImpact' + readOnly: true + alignments: + type: array + items: + $ref: '#/components/schemas/PrincipleAlignment' + readOnly: true + created_date: + type: string + format: date-time + readOnly: true + required: + - alignments + - associated_findings + - associated_requirements + - complexity + - cost + - created_date + - currency + - description + - id + - impacts + - implementation_days + - timeframe + - title + RecommendationImpact: + type: object + description: Serialize a RecommendationImpact record (impact type, score 1–5, + and notes). + properties: + impact_type: + $ref: '#/components/schemas/ImpactTypeEnum' + impact_score: + type: integer + maximum: 2147483647 + minimum: -2147483648 + description: Impact level (1-5) + notes: + type: string + required: + - impact_score + - impact_type + RecommendationImpactRequest: + type: object + description: Serialize a RecommendationImpact record (impact type, score 1–5, + and notes). + properties: + impact_type: + $ref: '#/components/schemas/ImpactTypeEnum' + impact_score: + type: integer + maximum: 2147483647 + minimum: -2147483648 + description: Impact level (1-5) + notes: + type: string + required: + - impact_score + - impact_type + RecommendationRequest: + type: object + description: |- + Serialize a Recommendation with nested impacts, alignments, findings, and requirements. + + ``finding_ids`` and ``requirement_ids`` are write-only; on read the full nested + objects are returned under ``associated_findings`` / ``associated_requirements``. + ``timeframe`` is a computed read-only field (Short / Medium / Long) derived from + ``implementation_days`` against fixed 30- and 180-day thresholds. + properties: + title: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + minLength: 1 + implementation_days: + type: integer + maximum: 2147483647 + minimum: -2147483648 + finding_ids: + type: array + items: + type: integer + writeOnly: true + requirement_ids: + type: array + items: + type: integer + writeOnly: true + cost: + type: string + format: decimal + pattern: ^-?\d{0,10}(?:\.\d{0,2})?$ + currency: + type: string + minLength: 1 + complexity: + $ref: '#/components/schemas/ComplexityEnum' + required: + - complexity + - cost + - currency + - description + - implementation_days + - title + Requirement: + type: object + description: Serialize a Requirement with nested findings on read and a writable + ``related_finding_ids`` list on write. + properties: + id: + type: integer + readOnly: true + name: + type: string + maxLength: 255 + description: + type: string + type: + $ref: '#/components/schemas/RequirementTypeEnum' + category: + type: string + maxLength: 100 + success_measure: + type: string + notes: + type: string + related_findings: + type: array + items: + $ref: '#/components/schemas/Finding' + readOnly: true + created_date: + type: string + format: date-time + readOnly: true + required: + - created_date + - description + - id + - name + - related_findings + - success_measure + - type + RequirementRequest: + type: object + description: Serialize a Requirement with nested findings on read and a writable + ``related_finding_ids`` list on write. + properties: + name: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + minLength: 1 + type: + $ref: '#/components/schemas/RequirementTypeEnum' + category: + type: string + maxLength: 100 + success_measure: + type: string + minLength: 1 + notes: + type: string + related_finding_ids: + type: array + items: + type: integer + writeOnly: true + required: + - description + - name + - success_measure + - type + RequirementTypeEnum: + enum: + - Functional + - NonFunctional + type: string + description: |- + * `Functional` - Functional + * `NonFunctional` - Non-Functional + RequirementsTool: + type: object + properties: + id: + type: integer + readOnly: true + name: + type: string + maxLength: 255 + description: + type: string + proposal: + type: integer + nullable: true + engagement: + type: integer + nullable: true + client_name: + type: string + readOnly: true + association_display: + type: string + readOnly: true + created_date: + type: string + format: date-time + readOnly: true + modified_date: + type: string + format: date-time + readOnly: true + requirements: + type: array + items: + $ref: '#/components/schemas/Requirement' + readOnly: true + required: + - association_display + - client_name + - created_date + - description + - id + - modified_date + - name + - requirements + Risk: + type: object + properties: + id: + type: integer + readOnly: true + title: + type: string + maxLength: 255 + description: + type: string + probability: + type: integer + maximum: 2147483647 + minimum: -2147483648 + description: Percentage (0-100) + impact: + $ref: '#/components/schemas/ImpactEnum' + mitigation_strategy: + type: string + owner: + type: integer + owner_name: + type: string + readOnly: true + status: + $ref: '#/components/schemas/RiskStatusEnum' + due_date: + type: string + format: date + required: + - description + - due_date + - id + - impact + - mitigation_strategy + - owner + - owner_name + - probability + - status + - title + RiskStatusEnum: + enum: + - Open + - Mitigating + - Closed + - Accepted + type: string + description: |- + * `Open` - Open + * `Mitigating` - Mitigating + * `Closed` - Closed + * `Accepted` - Accepted + SectorEnum: + enum: + - Aerospace + - Agriculture + - Construction + - Defense + - Education + - Entertainment + - Finance + - Health Care + - Hospitality + - Mining + - Manufacturing + - Technology + - Transport + - Utilities + type: string + description: |- + * `Aerospace` - Aerospace + * `Agriculture` - Agriculture + * `Construction` - Construction + * `Defense` - Defense + * `Education` - Education + * `Entertainment` - Entertainment + * `Finance` - Finance + * `Health Care` - Health Care + * `Hospitality` - Hospitality + * `Mining` - Mining + * `Manufacturing` - Manufacturing + * `Technology` - Technology + * `Transport` - Transport + * `Utilities` - Utilities + SizeEnum: + enum: + - Micro + - Small + - Medium + - Large + - Enterprise + type: string + description: |- + * `Micro` - Micro + * `Small` - Small + * `Medium` - Medium + * `Large` - Large + * `Enterprise` - Enterprise + SourceEnum: + enum: + - Workshop + - Interview + - Document + - Other + type: string + description: |- + * `Workshop` - Workshop + * `Interview` - Interview + * `Document` - Document + * `Other` - Other + SourceTypeEnum: + enum: + - Proposal + - Engagement + type: string + description: |- + * `Proposal` - Proposal + * `Engagement` - Engagement + Stage: + type: object + properties: + id: + type: integer + readOnly: true + name: + type: string + maxLength: 255 + description: + type: string + start_date: + type: string + format: date + end_date: + type: string + format: date + color: + type: integer + nullable: true + description: Stage color (editable in admin) + status: + $ref: '#/components/schemas/StageStatusEnum' + progress: + type: integer + maximum: 2147483647 + minimum: -2147483648 + description: Completion percentage (0-100) + order: + type: integer + maximum: 2147483647 + minimum: -2147483648 + milestone: + type: integer + nullable: true + description: Optional milestone that marks the completion of this stage + duration_days: + type: string + readOnly: true + required: + - duration_days + - end_date + - id + - name + - start_date + StageEnum: + enum: + - Prospecting + - Qualification + - Workshops + - Proposal + - Negotiation + - Closed + type: string + description: |- + * `Prospecting` - Prospecting + * `Qualification` - Qualification + * `Workshops` - Workshops + * `Proposal` - Proposal + * `Negotiation` - Negotiation + * `Closed` - Closed + StageRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + start_date: + type: string + format: date + end_date: + type: string + format: date + color: + type: integer + nullable: true + description: Stage color (editable in admin) + status: + $ref: '#/components/schemas/StageStatusEnum' + progress: + type: integer + maximum: 2147483647 + minimum: -2147483648 + description: Completion percentage (0-100) + order: + type: integer + maximum: 2147483647 + minimum: -2147483648 + milestone: + type: integer + nullable: true + description: Optional milestone that marks the completion of this stage + required: + - end_date + - name + - start_date + StageStatusEnum: + enum: + - not_started + - in_progress + - completed + - delayed + - on_hold + type: string + description: |- + * `not_started` - Not Started + * `in_progress` - In Progress + * `completed` - Completed + * `delayed` - Delayed + * `on_hold` - On Hold + Stakeholder: + type: object + properties: + id: + type: integer + readOnly: true + contact: + allOf: + - $ref: '#/components/schemas/Contact' + readOnly: true + contact_name: + type: string + readOnly: true + tenure: + type: integer + maximum: 2147483647 + minimum: -2147483648 + description: Years in current role + solution_seek: + type: string + solution_avoid: + type: string + notes: + type: string + power: + $ref: '#/components/schemas/PowerEnum' + interest: + $ref: '#/components/schemas/InterestEnum' + required: + - contact + - contact_name + - id + StakeholderRequest: + type: object + properties: + contact_id: + type: integer + writeOnly: true + tenure: + type: integer + maximum: 2147483647 + minimum: -2147483648 + description: Years in current role + solution_seek: + type: string + solution_avoid: + type: string + notes: + type: string + power: + $ref: '#/components/schemas/PowerEnum' + interest: + $ref: '#/components/schemas/InterestEnum' + required: + - contact_id + StakeholderTool: + type: object + properties: + id: + type: integer + readOnly: true + name: + type: string + maxLength: 255 + description: + type: string + proposal: + type: integer + nullable: true + engagement: + type: integer + nullable: true + stakeholders: + type: array + items: + $ref: '#/components/schemas/Stakeholder' + readOnly: true + stakeholder_count: + type: string + readOnly: true + client_name: + type: string + readOnly: true + association_display: + type: string + readOnly: true + created_date: + type: string + format: date-time + readOnly: true + modified_date: + type: string + format: date-time + readOnly: true + required: + - association_display + - client_name + - created_date + - description + - id + - modified_date + - name + - stakeholder_count + - stakeholders + StakeholderToolRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + minLength: 1 + proposal: + type: integer + nullable: true + engagement: + type: integer + nullable: true + required: + - description + - name + Status49eEnum: + enum: + - draft + - in_progress + - complete + - delivered + type: string + description: |- + * `draft` - Draft + * `in_progress` - In Progress + * `complete` - Complete + * `delivered` - Delivered + Status557Enum: + enum: + - Planning + - In Progress + - On Hold + - Completed + - Cancelled + type: string + description: |- + * `Planning` - Planning + * `In Progress` - In Progress + * `On Hold` - On Hold + * `Completed` - Completed + * `Cancelled` - Cancelled + TEIReport: + type: object + description: Report template serializer with counts and the spec-style ``id`` + alias. + properties: + id: + type: string + readOnly: true + name: + type: string + description: Display name (e.g., 'Amazon Connect 2026'). + maxLength: 255 + vendor: + type: string + description: Technology vendor (e.g., 'AWS', 'Genesys'). + maxLength: 255 + version: + type: string + description: Model version (e.g., '1.0', '2.1'). + maxLength: 50 + description: + type: string + analysis_period_years: + type: integer + maximum: 2147483647 + minimum: 0 + description: Number of years included in NPV/ROI (1-10). + discount_rate: + type: string + format: decimal + pattern: ^-?\d{0,2}(?:\.\d{0,4})?$ + description: Annual discount rate (e.g., 0.10 = 10%). + status: + $ref: '#/components/schemas/TEIReportStatusEnum' + field_count: + type: integer + readOnly: true + instance_count: + type: integer + readOnly: true + created_at: + type: string + format: date-time + readOnly: true + updated_at: + type: string + format: date-time + readOnly: true + required: + - created_at + - field_count + - id + - instance_count + - name + - updated_at + - vendor + - version + TEIReportField: + type: object + description: Field-definition serializer. + properties: + id: + type: integer + readOnly: true + table: + $ref: '#/components/schemas/TableEnum' + field_key: + type: string + description: Machine identifier, unique within report. + maxLength: 64 + pattern: ^[-a-zA-Z0-9_]+$ + label: + type: string + maxLength: 255 + description: + type: string + field_type: + $ref: '#/components/schemas/TEIReportFieldFieldTypeEnum' + category: + type: string + maxLength: 100 + default_value: + type: string + description: Pre-populated default (stored as string, cast by calculator). + maxLength: 255 + is_annual: + type: boolean + description: If True, value is collected per year (Y1..YN); if False, a + single value. + risk_adjustment: + type: string + format: decimal + pattern: ^-?\d{0,1}(?:\.\d{0,4})?$ + nullable: true + description: Default risk adjustment (0.0-1.0); only applied to benefit + fields. + sort_order: + type: integer + maximum: 2147483647 + minimum: 0 + is_required: + type: boolean + source_notes: + type: string + created_at: + type: string + format: date-time + readOnly: true + updated_at: + type: string + format: date-time + readOnly: true + required: + - created_at + - field_key + - field_type + - id + - label + - table + - updated_at + TEIReportFieldFieldTypeEnum: + enum: + - currency + - percentage + - integer + - decimal + - text + type: string + description: |- + * `currency` - Currency + * `percentage` - Percentage + * `integer` - Integer + * `decimal` - Decimal + * `text` - Text + TEIReportFieldRequest: + type: object + description: Field-definition serializer. + properties: + table: + $ref: '#/components/schemas/TableEnum' + field_key: + type: string + minLength: 1 + description: Machine identifier, unique within report. + maxLength: 64 + pattern: ^[-a-zA-Z0-9_]+$ + label: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + field_type: + $ref: '#/components/schemas/TEIReportFieldFieldTypeEnum' + category: + type: string + maxLength: 100 + default_value: + type: string + description: Pre-populated default (stored as string, cast by calculator). + maxLength: 255 + is_annual: + type: boolean + description: If True, value is collected per year (Y1..YN); if False, a + single value. + risk_adjustment: + type: string + format: decimal + pattern: ^-?\d{0,1}(?:\.\d{0,4})?$ + nullable: true + description: Default risk adjustment (0.0-1.0); only applied to benefit + fields. + sort_order: + type: integer + maximum: 2147483647 + minimum: 0 + is_required: + type: boolean + source_notes: + type: string + required: + - field_key + - field_type + - label + - table + TEIReportRequest: + type: object + description: Report template serializer with counts and the spec-style ``id`` + alias. + properties: + name: + type: string + minLength: 1 + description: Display name (e.g., 'Amazon Connect 2026'). + maxLength: 255 + vendor: + type: string + minLength: 1 + description: Technology vendor (e.g., 'AWS', 'Genesys'). + maxLength: 255 + version: + type: string + minLength: 1 + description: Model version (e.g., '1.0', '2.1'). + maxLength: 50 + description: + type: string + analysis_period_years: + type: integer + maximum: 2147483647 + minimum: 0 + description: Number of years included in NPV/ROI (1-10). + discount_rate: + type: string + format: decimal + pattern: ^-?\d{0,2}(?:\.\d{0,4})?$ + description: Annual discount rate (e.g., 0.10 = 10%). + status: + $ref: '#/components/schemas/TEIReportStatusEnum' + required: + - name + - vendor + - version + TEIReportStatusEnum: + enum: + - draft + - active + - archived + type: string + description: |- + * `draft` - Draft + * `active` - Active + * `archived` - Archived + TEITool: + type: object + description: Read serializer for ``TEITool`` list/detail responses. + properties: + id: + type: string + readOnly: true + report: + type: object + additionalProperties: {} + readOnly: true + opportunity: + type: object + additionalProperties: {} + nullable: true + readOnly: true + engagement: + type: object + additionalProperties: {} + nullable: true + readOnly: true + name: + type: string + readOnly: true + status: + allOf: + - $ref: '#/components/schemas/Status49eEnum' + readOnly: true + current_version: + type: integer + readOnly: true + description: Highest version_number among TEIVersion snapshots; 0 means + none saved yet. + summary: + type: object + additionalProperties: {} + nullable: true + readOnly: true + created_date: + type: string + format: date-time + readOnly: true + modified_date: + type: string + format: date-time + readOnly: true + required: + - created_date + - current_version + - engagement + - id + - modified_date + - name + - opportunity + - report + - status + - summary + TEIToolCreate: + type: object + description: |- + Write serializer for ``POST /tools/``. + + The report is resolved by its ``public_id``; proposal/engagement are + resolved by integer PK for simplicity (matches the rest of the Athena + admin/API surface). Viewset performs the duplicate-instance check. + properties: + report: + type: string + description: Report public_id (short UUID). + proposal: + type: integer + nullable: true + engagement: + type: integer + nullable: true + name: + type: string + nullable: true + status: + allOf: + - $ref: '#/components/schemas/Status49eEnum' + default: draft + required: + - report + TEIToolCreateRequest: + type: object + description: |- + Write serializer for ``POST /tools/``. + + The report is resolved by its ``public_id``; proposal/engagement are + resolved by integer PK for simplicity (matches the rest of the Athena + admin/API surface). Viewset performs the duplicate-instance check. + properties: + report: + type: string + minLength: 1 + description: Report public_id (short UUID). + proposal: + type: integer + nullable: true + engagement: + type: integer + nullable: true + name: + type: string + nullable: true + status: + allOf: + - $ref: '#/components/schemas/Status49eEnum' + default: draft + required: + - report + TEIToolUpdate: + type: object + description: Write serializer for ``PUT /tools/{id}/`` — only name/status are + mutable. + properties: + name: + type: string + maxLength: 255 + status: + $ref: '#/components/schemas/Status49eEnum' + required: + - name + TEIToolUpdateRequest: + type: object + description: Write serializer for ``PUT /tools/{id}/`` — only name/status are + mutable. + properties: + name: + type: string + minLength: 1 + maxLength: 255 + status: + $ref: '#/components/schemas/Status49eEnum' + required: + - name + TableEnum: + enum: + - benefits + - costs + type: string + description: |- + * `benefits` - Benefits + * `costs` - Costs + TableSource: + type: object + properties: + id: + type: string + title: + type: string + type: + type: string + app: + type: string + preview: + type: string + url: + type: string + nullable: true + required: + - app + - id + - preview + - title + - type + Tag: + type: object + properties: + id: + type: integer + readOnly: true + name: + type: string + maxLength: 50 + required: + - id + - name + TagRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 50 + required: + - name + TermYearsEnum: + enum: + - 1 + - 2 + - 3 + - 4 + - 5 + type: integer + description: |- + * `1` - 1 Year + * `2` - 2 Years + * `3` - 3 Years + * `4` - 4 Years + * `5` - 5 Years + TimeFormatEnum: + enum: + - 12-hour + - 24-hour + type: string + description: |- + * `12-hour` - 12-hour (3:30 PM) + * `24-hour` - 24-hour (15:30) + Timeline: + type: object + properties: + id: + type: integer + readOnly: true + title: + type: string + maxLength: 255 + description: + type: string + start_date: + type: string + format: date + end_date: + type: string + readOnly: true + progress_percentage: + type: string + readOnly: true + milestones: + type: array + items: + $ref: '#/components/schemas/Milestone' + readOnly: true + stages: + type: array + items: + $ref: '#/components/schemas/Stage' + readOnly: true + required: + - end_date + - id + - milestones + - progress_percentage + - stages + - start_date + - title + TimelineRequest: + type: object + properties: + title: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + start_date: + type: string + format: date + required: + - start_date + - title + TimelineTool: + type: object + properties: + id: + type: integer + readOnly: true + name: + type: string + maxLength: 255 + description: + type: string + proposal: + type: integer + nullable: true + engagement: + type: integer + nullable: true + client_name: + type: string + readOnly: true + association_display: + type: string + readOnly: true + timelines: + type: array + items: + $ref: '#/components/schemas/Timeline' + readOnly: true + timeline_count: + type: string + readOnly: true + created_date: + type: string + format: date-time + readOnly: true + modified_date: + type: string + format: date-time + readOnly: true + required: + - association_display + - client_name + - created_date + - description + - id + - modified_date + - name + - timeline_count + - timelines + TimelineToolRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 255 + description: + type: string + minLength: 1 + proposal: + type: integer + nullable: true + engagement: + type: integer + nullable: true + required: + - description + - name + UserProfile: + type: object + description: |- + User profile serializer with preference settings. + + Provides access to user-specific configuration including: + - Date and time format preferences + - Timezone settings (home and local) + - UI theme customization + properties: + date_format: + allOf: + - $ref: '#/components/schemas/DateFormatEnum' + description: |- + Preferred date display format + + * `YYYY-MM-DD` - 2024-12-25 + * `DD/MM/YYYY` - 25/12/2024 + * `MM/DD/YYYY` - 12/25/2024 + * `DD.MM.YYYY` - 25.12.2024 + * `DD-MM-YYYY` - 25-12-2024 + time_format: + allOf: + - $ref: '#/components/schemas/TimeFormatEnum' + description: |- + 12-hour or 24-hour time format + + * `12-hour` - 12-hour (3:30 PM) + * `24-hour` - 24-hour (15:30) + home_timezone: + allOf: + - $ref: '#/components/schemas/HomeTimezoneEnum' + description: |- + User's home/permanent timezone + + * `Africa/Abidjan` - Africa/Abidjan + * `Africa/Accra` - Africa/Accra + * `Africa/Addis_Ababa` - Africa/Addis_Ababa + * `Africa/Algiers` - Africa/Algiers + * `Africa/Asmara` - Africa/Asmara + * `Africa/Bamako` - Africa/Bamako + * `Africa/Bangui` - Africa/Bangui + * `Africa/Banjul` - Africa/Banjul + * `Africa/Bissau` - Africa/Bissau + * `Africa/Blantyre` - Africa/Blantyre + * `Africa/Brazzaville` - Africa/Brazzaville + * `Africa/Bujumbura` - Africa/Bujumbura + * `Africa/Cairo` - Africa/Cairo + * `Africa/Casablanca` - Africa/Casablanca + * `Africa/Ceuta` - Africa/Ceuta + * `Africa/Conakry` - Africa/Conakry + * `Africa/Dakar` - Africa/Dakar + * `Africa/Dar_es_Salaam` - Africa/Dar_es_Salaam + * `Africa/Djibouti` - Africa/Djibouti + * `Africa/Douala` - Africa/Douala + * `Africa/El_Aaiun` - Africa/El_Aaiun + * `Africa/Freetown` - Africa/Freetown + * `Africa/Gaborone` - Africa/Gaborone + * `Africa/Harare` - Africa/Harare + * `Africa/Johannesburg` - Africa/Johannesburg + * `Africa/Juba` - Africa/Juba + * `Africa/Kampala` - Africa/Kampala + * `Africa/Khartoum` - Africa/Khartoum + * `Africa/Kigali` - Africa/Kigali + * `Africa/Kinshasa` - Africa/Kinshasa + * `Africa/Lagos` - Africa/Lagos + * `Africa/Libreville` - Africa/Libreville + * `Africa/Lome` - Africa/Lome + * `Africa/Luanda` - Africa/Luanda + * `Africa/Lubumbashi` - Africa/Lubumbashi + * `Africa/Lusaka` - Africa/Lusaka + * `Africa/Malabo` - Africa/Malabo + * `Africa/Maputo` - Africa/Maputo + * `Africa/Maseru` - Africa/Maseru + * `Africa/Mbabane` - Africa/Mbabane + * `Africa/Mogadishu` - Africa/Mogadishu + * `Africa/Monrovia` - Africa/Monrovia + * `Africa/Nairobi` - Africa/Nairobi + * `Africa/Ndjamena` - Africa/Ndjamena + * `Africa/Niamey` - Africa/Niamey + * `Africa/Nouakchott` - Africa/Nouakchott + * `Africa/Ouagadougou` - Africa/Ouagadougou + * `Africa/Porto-Novo` - Africa/Porto-Novo + * `Africa/Sao_Tome` - Africa/Sao_Tome + * `Africa/Tripoli` - Africa/Tripoli + * `Africa/Tunis` - Africa/Tunis + * `Africa/Windhoek` - Africa/Windhoek + * `America/Adak` - America/Adak + * `America/Anchorage` - America/Anchorage + * `America/Anguilla` - America/Anguilla + * `America/Antigua` - America/Antigua + * `America/Araguaina` - America/Araguaina + * `America/Argentina/Buenos_Aires` - America/Argentina/Buenos_Aires + * `America/Argentina/Catamarca` - America/Argentina/Catamarca + * `America/Argentina/Cordoba` - America/Argentina/Cordoba + * `America/Argentina/Jujuy` - America/Argentina/Jujuy + * `America/Argentina/La_Rioja` - America/Argentina/La_Rioja + * `America/Argentina/Mendoza` - America/Argentina/Mendoza + * `America/Argentina/Rio_Gallegos` - America/Argentina/Rio_Gallegos + * `America/Argentina/Salta` - America/Argentina/Salta + * `America/Argentina/San_Juan` - America/Argentina/San_Juan + * `America/Argentina/San_Luis` - America/Argentina/San_Luis + * `America/Argentina/Tucuman` - America/Argentina/Tucuman + * `America/Argentina/Ushuaia` - America/Argentina/Ushuaia + * `America/Aruba` - America/Aruba + * `America/Asuncion` - America/Asuncion + * `America/Atikokan` - America/Atikokan + * `America/Bahia` - America/Bahia + * `America/Bahia_Banderas` - America/Bahia_Banderas + * `America/Barbados` - America/Barbados + * `America/Belem` - America/Belem + * `America/Belize` - America/Belize + * `America/Blanc-Sablon` - America/Blanc-Sablon + * `America/Boa_Vista` - America/Boa_Vista + * `America/Bogota` - America/Bogota + * `America/Boise` - America/Boise + * `America/Cambridge_Bay` - America/Cambridge_Bay + * `America/Campo_Grande` - America/Campo_Grande + * `America/Cancun` - America/Cancun + * `America/Caracas` - America/Caracas + * `America/Cayenne` - America/Cayenne + * `America/Cayman` - America/Cayman + * `America/Chicago` - America/Chicago + * `America/Chihuahua` - America/Chihuahua + * `America/Ciudad_Juarez` - America/Ciudad_Juarez + * `America/Costa_Rica` - America/Costa_Rica + * `America/Coyhaique` - America/Coyhaique + * `America/Creston` - America/Creston + * `America/Cuiaba` - America/Cuiaba + * `America/Curacao` - America/Curacao + * `America/Danmarkshavn` - America/Danmarkshavn + * `America/Dawson` - America/Dawson + * `America/Dawson_Creek` - America/Dawson_Creek + * `America/Denver` - America/Denver + * `America/Detroit` - America/Detroit + * `America/Dominica` - America/Dominica + * `America/Edmonton` - America/Edmonton + * `America/Eirunepe` - America/Eirunepe + * `America/El_Salvador` - America/El_Salvador + * `America/Fort_Nelson` - America/Fort_Nelson + * `America/Fortaleza` - America/Fortaleza + * `America/Glace_Bay` - America/Glace_Bay + * `America/Goose_Bay` - America/Goose_Bay + * `America/Grand_Turk` - America/Grand_Turk + * `America/Grenada` - America/Grenada + * `America/Guadeloupe` - America/Guadeloupe + * `America/Guatemala` - America/Guatemala + * `America/Guayaquil` - America/Guayaquil + * `America/Guyana` - America/Guyana + * `America/Halifax` - America/Halifax + * `America/Havana` - America/Havana + * `America/Hermosillo` - America/Hermosillo + * `America/Indiana/Indianapolis` - America/Indiana/Indianapolis + * `America/Indiana/Knox` - America/Indiana/Knox + * `America/Indiana/Marengo` - America/Indiana/Marengo + * `America/Indiana/Petersburg` - America/Indiana/Petersburg + * `America/Indiana/Tell_City` - America/Indiana/Tell_City + * `America/Indiana/Vevay` - America/Indiana/Vevay + * `America/Indiana/Vincennes` - America/Indiana/Vincennes + * `America/Indiana/Winamac` - America/Indiana/Winamac + * `America/Inuvik` - America/Inuvik + * `America/Iqaluit` - America/Iqaluit + * `America/Jamaica` - America/Jamaica + * `America/Juneau` - America/Juneau + * `America/Kentucky/Louisville` - America/Kentucky/Louisville + * `America/Kentucky/Monticello` - America/Kentucky/Monticello + * `America/Kralendijk` - America/Kralendijk + * `America/La_Paz` - America/La_Paz + * `America/Lima` - America/Lima + * `America/Los_Angeles` - America/Los_Angeles + * `America/Lower_Princes` - America/Lower_Princes + * `America/Maceio` - America/Maceio + * `America/Managua` - America/Managua + * `America/Manaus` - America/Manaus + * `America/Marigot` - America/Marigot + * `America/Martinique` - America/Martinique + * `America/Matamoros` - America/Matamoros + * `America/Mazatlan` - America/Mazatlan + * `America/Menominee` - America/Menominee + * `America/Merida` - America/Merida + * `America/Metlakatla` - America/Metlakatla + * `America/Mexico_City` - America/Mexico_City + * `America/Miquelon` - America/Miquelon + * `America/Moncton` - America/Moncton + * `America/Monterrey` - America/Monterrey + * `America/Montevideo` - America/Montevideo + * `America/Montserrat` - America/Montserrat + * `America/Nassau` - America/Nassau + * `America/New_York` - America/New_York + * `America/Nome` - America/Nome + * `America/Noronha` - America/Noronha + * `America/North_Dakota/Beulah` - America/North_Dakota/Beulah + * `America/North_Dakota/Center` - America/North_Dakota/Center + * `America/North_Dakota/New_Salem` - America/North_Dakota/New_Salem + * `America/Nuuk` - America/Nuuk + * `America/Ojinaga` - America/Ojinaga + * `America/Panama` - America/Panama + * `America/Paramaribo` - America/Paramaribo + * `America/Phoenix` - America/Phoenix + * `America/Port-au-Prince` - America/Port-au-Prince + * `America/Port_of_Spain` - America/Port_of_Spain + * `America/Porto_Velho` - America/Porto_Velho + * `America/Puerto_Rico` - America/Puerto_Rico + * `America/Punta_Arenas` - America/Punta_Arenas + * `America/Rankin_Inlet` - America/Rankin_Inlet + * `America/Recife` - America/Recife + * `America/Regina` - America/Regina + * `America/Resolute` - America/Resolute + * `America/Rio_Branco` - America/Rio_Branco + * `America/Santarem` - America/Santarem + * `America/Santiago` - America/Santiago + * `America/Santo_Domingo` - America/Santo_Domingo + * `America/Sao_Paulo` - America/Sao_Paulo + * `America/Scoresbysund` - America/Scoresbysund + * `America/Sitka` - America/Sitka + * `America/St_Barthelemy` - America/St_Barthelemy + * `America/St_Johns` - America/St_Johns + * `America/St_Kitts` - America/St_Kitts + * `America/St_Lucia` - America/St_Lucia + * `America/St_Thomas` - America/St_Thomas + * `America/St_Vincent` - America/St_Vincent + * `America/Swift_Current` - America/Swift_Current + * `America/Tegucigalpa` - America/Tegucigalpa + * `America/Thule` - America/Thule + * `America/Tijuana` - America/Tijuana + * `America/Toronto` - America/Toronto + * `America/Tortola` - America/Tortola + * `America/Vancouver` - America/Vancouver + * `America/Whitehorse` - America/Whitehorse + * `America/Winnipeg` - America/Winnipeg + * `America/Yakutat` - America/Yakutat + * `Antarctica/Casey` - Antarctica/Casey + * `Antarctica/Davis` - Antarctica/Davis + * `Antarctica/DumontDUrville` - Antarctica/DumontDUrville + * `Antarctica/Macquarie` - Antarctica/Macquarie + * `Antarctica/Mawson` - Antarctica/Mawson + * `Antarctica/McMurdo` - Antarctica/McMurdo + * `Antarctica/Palmer` - Antarctica/Palmer + * `Antarctica/Rothera` - Antarctica/Rothera + * `Antarctica/Syowa` - Antarctica/Syowa + * `Antarctica/Troll` - Antarctica/Troll + * `Antarctica/Vostok` - Antarctica/Vostok + * `Arctic/Longyearbyen` - Arctic/Longyearbyen + * `Asia/Aden` - Asia/Aden + * `Asia/Almaty` - Asia/Almaty + * `Asia/Amman` - Asia/Amman + * `Asia/Anadyr` - Asia/Anadyr + * `Asia/Aqtau` - Asia/Aqtau + * `Asia/Aqtobe` - Asia/Aqtobe + * `Asia/Ashgabat` - Asia/Ashgabat + * `Asia/Atyrau` - Asia/Atyrau + * `Asia/Baghdad` - Asia/Baghdad + * `Asia/Bahrain` - Asia/Bahrain + * `Asia/Baku` - Asia/Baku + * `Asia/Bangkok` - Asia/Bangkok + * `Asia/Barnaul` - Asia/Barnaul + * `Asia/Beirut` - Asia/Beirut + * `Asia/Bishkek` - Asia/Bishkek + * `Asia/Brunei` - Asia/Brunei + * `Asia/Chita` - Asia/Chita + * `Asia/Colombo` - Asia/Colombo + * `Asia/Damascus` - Asia/Damascus + * `Asia/Dhaka` - Asia/Dhaka + * `Asia/Dili` - Asia/Dili + * `Asia/Dubai` - Asia/Dubai + * `Asia/Dushanbe` - Asia/Dushanbe + * `Asia/Famagusta` - Asia/Famagusta + * `Asia/Gaza` - Asia/Gaza + * `Asia/Hebron` - Asia/Hebron + * `Asia/Ho_Chi_Minh` - Asia/Ho_Chi_Minh + * `Asia/Hong_Kong` - Asia/Hong_Kong + * `Asia/Hovd` - Asia/Hovd + * `Asia/Irkutsk` - Asia/Irkutsk + * `Asia/Jakarta` - Asia/Jakarta + * `Asia/Jayapura` - Asia/Jayapura + * `Asia/Jerusalem` - Asia/Jerusalem + * `Asia/Kabul` - Asia/Kabul + * `Asia/Kamchatka` - Asia/Kamchatka + * `Asia/Karachi` - Asia/Karachi + * `Asia/Kathmandu` - Asia/Kathmandu + * `Asia/Khandyga` - Asia/Khandyga + * `Asia/Kolkata` - Asia/Kolkata + * `Asia/Krasnoyarsk` - Asia/Krasnoyarsk + * `Asia/Kuala_Lumpur` - Asia/Kuala_Lumpur + * `Asia/Kuching` - Asia/Kuching + * `Asia/Kuwait` - Asia/Kuwait + * `Asia/Macau` - Asia/Macau + * `Asia/Magadan` - Asia/Magadan + * `Asia/Makassar` - Asia/Makassar + * `Asia/Manila` - Asia/Manila + * `Asia/Muscat` - Asia/Muscat + * `Asia/Nicosia` - Asia/Nicosia + * `Asia/Novokuznetsk` - Asia/Novokuznetsk + * `Asia/Novosibirsk` - Asia/Novosibirsk + * `Asia/Omsk` - Asia/Omsk + * `Asia/Oral` - Asia/Oral + * `Asia/Phnom_Penh` - Asia/Phnom_Penh + * `Asia/Pontianak` - Asia/Pontianak + * `Asia/Pyongyang` - Asia/Pyongyang + * `Asia/Qatar` - Asia/Qatar + * `Asia/Qostanay` - Asia/Qostanay + * `Asia/Qyzylorda` - Asia/Qyzylorda + * `Asia/Riyadh` - Asia/Riyadh + * `Asia/Sakhalin` - Asia/Sakhalin + * `Asia/Samarkand` - Asia/Samarkand + * `Asia/Seoul` - Asia/Seoul + * `Asia/Shanghai` - Asia/Shanghai + * `Asia/Singapore` - Asia/Singapore + * `Asia/Srednekolymsk` - Asia/Srednekolymsk + * `Asia/Taipei` - Asia/Taipei + * `Asia/Tashkent` - Asia/Tashkent + * `Asia/Tbilisi` - Asia/Tbilisi + * `Asia/Tehran` - Asia/Tehran + * `Asia/Thimphu` - Asia/Thimphu + * `Asia/Tokyo` - Asia/Tokyo + * `Asia/Tomsk` - Asia/Tomsk + * `Asia/Ulaanbaatar` - Asia/Ulaanbaatar + * `Asia/Urumqi` - Asia/Urumqi + * `Asia/Ust-Nera` - Asia/Ust-Nera + * `Asia/Vientiane` - Asia/Vientiane + * `Asia/Vladivostok` - Asia/Vladivostok + * `Asia/Yakutsk` - Asia/Yakutsk + * `Asia/Yangon` - Asia/Yangon + * `Asia/Yekaterinburg` - Asia/Yekaterinburg + * `Asia/Yerevan` - Asia/Yerevan + * `Atlantic/Azores` - Atlantic/Azores + * `Atlantic/Bermuda` - Atlantic/Bermuda + * `Atlantic/Canary` - Atlantic/Canary + * `Atlantic/Cape_Verde` - Atlantic/Cape_Verde + * `Atlantic/Faroe` - Atlantic/Faroe + * `Atlantic/Madeira` - Atlantic/Madeira + * `Atlantic/Reykjavik` - Atlantic/Reykjavik + * `Atlantic/South_Georgia` - Atlantic/South_Georgia + * `Atlantic/St_Helena` - Atlantic/St_Helena + * `Atlantic/Stanley` - Atlantic/Stanley + * `Australia/Adelaide` - Australia/Adelaide + * `Australia/Brisbane` - Australia/Brisbane + * `Australia/Broken_Hill` - Australia/Broken_Hill + * `Australia/Darwin` - Australia/Darwin + * `Australia/Eucla` - Australia/Eucla + * `Australia/Hobart` - Australia/Hobart + * `Australia/Lindeman` - Australia/Lindeman + * `Australia/Lord_Howe` - Australia/Lord_Howe + * `Australia/Melbourne` - Australia/Melbourne + * `Australia/Perth` - Australia/Perth + * `Australia/Sydney` - Australia/Sydney + * `Europe/Amsterdam` - Europe/Amsterdam + * `Europe/Andorra` - Europe/Andorra + * `Europe/Astrakhan` - Europe/Astrakhan + * `Europe/Athens` - Europe/Athens + * `Europe/Belgrade` - Europe/Belgrade + * `Europe/Berlin` - Europe/Berlin + * `Europe/Bratislava` - Europe/Bratislava + * `Europe/Brussels` - Europe/Brussels + * `Europe/Bucharest` - Europe/Bucharest + * `Europe/Budapest` - Europe/Budapest + * `Europe/Busingen` - Europe/Busingen + * `Europe/Chisinau` - Europe/Chisinau + * `Europe/Copenhagen` - Europe/Copenhagen + * `Europe/Dublin` - Europe/Dublin + * `Europe/Gibraltar` - Europe/Gibraltar + * `Europe/Guernsey` - Europe/Guernsey + * `Europe/Helsinki` - Europe/Helsinki + * `Europe/Isle_of_Man` - Europe/Isle_of_Man + * `Europe/Istanbul` - Europe/Istanbul + * `Europe/Jersey` - Europe/Jersey + * `Europe/Kaliningrad` - Europe/Kaliningrad + * `Europe/Kirov` - Europe/Kirov + * `Europe/Kyiv` - Europe/Kyiv + * `Europe/Lisbon` - Europe/Lisbon + * `Europe/Ljubljana` - Europe/Ljubljana + * `Europe/London` - Europe/London + * `Europe/Luxembourg` - Europe/Luxembourg + * `Europe/Madrid` - Europe/Madrid + * `Europe/Malta` - Europe/Malta + * `Europe/Mariehamn` - Europe/Mariehamn + * `Europe/Minsk` - Europe/Minsk + * `Europe/Monaco` - Europe/Monaco + * `Europe/Moscow` - Europe/Moscow + * `Europe/Oslo` - Europe/Oslo + * `Europe/Paris` - Europe/Paris + * `Europe/Podgorica` - Europe/Podgorica + * `Europe/Prague` - Europe/Prague + * `Europe/Riga` - Europe/Riga + * `Europe/Rome` - Europe/Rome + * `Europe/Samara` - Europe/Samara + * `Europe/San_Marino` - Europe/San_Marino + * `Europe/Sarajevo` - Europe/Sarajevo + * `Europe/Saratov` - Europe/Saratov + * `Europe/Simferopol` - Europe/Simferopol + * `Europe/Skopje` - Europe/Skopje + * `Europe/Sofia` - Europe/Sofia + * `Europe/Stockholm` - Europe/Stockholm + * `Europe/Tallinn` - Europe/Tallinn + * `Europe/Tirane` - Europe/Tirane + * `Europe/Ulyanovsk` - Europe/Ulyanovsk + * `Europe/Vaduz` - Europe/Vaduz + * `Europe/Vatican` - Europe/Vatican + * `Europe/Vienna` - Europe/Vienna + * `Europe/Vilnius` - Europe/Vilnius + * `Europe/Volgograd` - Europe/Volgograd + * `Europe/Warsaw` - Europe/Warsaw + * `Europe/Zagreb` - Europe/Zagreb + * `Europe/Zurich` - Europe/Zurich + * `GMT` - GMT + * `Indian/Antananarivo` - Indian/Antananarivo + * `Indian/Chagos` - Indian/Chagos + * `Indian/Christmas` - Indian/Christmas + * `Indian/Cocos` - Indian/Cocos + * `Indian/Comoro` - Indian/Comoro + * `Indian/Kerguelen` - Indian/Kerguelen + * `Indian/Mahe` - Indian/Mahe + * `Indian/Maldives` - Indian/Maldives + * `Indian/Mauritius` - Indian/Mauritius + * `Indian/Mayotte` - Indian/Mayotte + * `Indian/Reunion` - Indian/Reunion + * `Pacific/Apia` - Pacific/Apia + * `Pacific/Auckland` - Pacific/Auckland + * `Pacific/Bougainville` - Pacific/Bougainville + * `Pacific/Chatham` - Pacific/Chatham + * `Pacific/Chuuk` - Pacific/Chuuk + * `Pacific/Easter` - Pacific/Easter + * `Pacific/Efate` - Pacific/Efate + * `Pacific/Fakaofo` - Pacific/Fakaofo + * `Pacific/Fiji` - Pacific/Fiji + * `Pacific/Funafuti` - Pacific/Funafuti + * `Pacific/Galapagos` - Pacific/Galapagos + * `Pacific/Gambier` - Pacific/Gambier + * `Pacific/Guadalcanal` - Pacific/Guadalcanal + * `Pacific/Guam` - Pacific/Guam + * `Pacific/Honolulu` - Pacific/Honolulu + * `Pacific/Kanton` - Pacific/Kanton + * `Pacific/Kiritimati` - Pacific/Kiritimati + * `Pacific/Kosrae` - Pacific/Kosrae + * `Pacific/Kwajalein` - Pacific/Kwajalein + * `Pacific/Majuro` - Pacific/Majuro + * `Pacific/Marquesas` - Pacific/Marquesas + * `Pacific/Midway` - Pacific/Midway + * `Pacific/Nauru` - Pacific/Nauru + * `Pacific/Niue` - Pacific/Niue + * `Pacific/Norfolk` - Pacific/Norfolk + * `Pacific/Noumea` - Pacific/Noumea + * `Pacific/Pago_Pago` - Pacific/Pago_Pago + * `Pacific/Palau` - Pacific/Palau + * `Pacific/Pitcairn` - Pacific/Pitcairn + * `Pacific/Pohnpei` - Pacific/Pohnpei + * `Pacific/Port_Moresby` - Pacific/Port_Moresby + * `Pacific/Rarotonga` - Pacific/Rarotonga + * `Pacific/Saipan` - Pacific/Saipan + * `Pacific/Tahiti` - Pacific/Tahiti + * `Pacific/Tarawa` - Pacific/Tarawa + * `Pacific/Tongatapu` - Pacific/Tongatapu + * `Pacific/Wake` - Pacific/Wake + * `Pacific/Wallis` - Pacific/Wallis + * `UTC` - UTC + local_timezone: + nullable: true + description: |- + User's current local timezone (if traveling) + + * `Africa/Abidjan` - Africa/Abidjan + * `Africa/Accra` - Africa/Accra + * `Africa/Addis_Ababa` - Africa/Addis_Ababa + * `Africa/Algiers` - Africa/Algiers + * `Africa/Asmara` - Africa/Asmara + * `Africa/Bamako` - Africa/Bamako + * `Africa/Bangui` - Africa/Bangui + * `Africa/Banjul` - Africa/Banjul + * `Africa/Bissau` - Africa/Bissau + * `Africa/Blantyre` - Africa/Blantyre + * `Africa/Brazzaville` - Africa/Brazzaville + * `Africa/Bujumbura` - Africa/Bujumbura + * `Africa/Cairo` - Africa/Cairo + * `Africa/Casablanca` - Africa/Casablanca + * `Africa/Ceuta` - Africa/Ceuta + * `Africa/Conakry` - Africa/Conakry + * `Africa/Dakar` - Africa/Dakar + * `Africa/Dar_es_Salaam` - Africa/Dar_es_Salaam + * `Africa/Djibouti` - Africa/Djibouti + * `Africa/Douala` - Africa/Douala + * `Africa/El_Aaiun` - Africa/El_Aaiun + * `Africa/Freetown` - Africa/Freetown + * `Africa/Gaborone` - Africa/Gaborone + * `Africa/Harare` - Africa/Harare + * `Africa/Johannesburg` - Africa/Johannesburg + * `Africa/Juba` - Africa/Juba + * `Africa/Kampala` - Africa/Kampala + * `Africa/Khartoum` - Africa/Khartoum + * `Africa/Kigali` - Africa/Kigali + * `Africa/Kinshasa` - Africa/Kinshasa + * `Africa/Lagos` - Africa/Lagos + * `Africa/Libreville` - Africa/Libreville + * `Africa/Lome` - Africa/Lome + * `Africa/Luanda` - Africa/Luanda + * `Africa/Lubumbashi` - Africa/Lubumbashi + * `Africa/Lusaka` - Africa/Lusaka + * `Africa/Malabo` - Africa/Malabo + * `Africa/Maputo` - Africa/Maputo + * `Africa/Maseru` - Africa/Maseru + * `Africa/Mbabane` - Africa/Mbabane + * `Africa/Mogadishu` - Africa/Mogadishu + * `Africa/Monrovia` - Africa/Monrovia + * `Africa/Nairobi` - Africa/Nairobi + * `Africa/Ndjamena` - Africa/Ndjamena + * `Africa/Niamey` - Africa/Niamey + * `Africa/Nouakchott` - Africa/Nouakchott + * `Africa/Ouagadougou` - Africa/Ouagadougou + * `Africa/Porto-Novo` - Africa/Porto-Novo + * `Africa/Sao_Tome` - Africa/Sao_Tome + * `Africa/Tripoli` - Africa/Tripoli + * `Africa/Tunis` - Africa/Tunis + * `Africa/Windhoek` - Africa/Windhoek + * `America/Adak` - America/Adak + * `America/Anchorage` - America/Anchorage + * `America/Anguilla` - America/Anguilla + * `America/Antigua` - America/Antigua + * `America/Araguaina` - America/Araguaina + * `America/Argentina/Buenos_Aires` - America/Argentina/Buenos_Aires + * `America/Argentina/Catamarca` - America/Argentina/Catamarca + * `America/Argentina/Cordoba` - America/Argentina/Cordoba + * `America/Argentina/Jujuy` - America/Argentina/Jujuy + * `America/Argentina/La_Rioja` - America/Argentina/La_Rioja + * `America/Argentina/Mendoza` - America/Argentina/Mendoza + * `America/Argentina/Rio_Gallegos` - America/Argentina/Rio_Gallegos + * `America/Argentina/Salta` - America/Argentina/Salta + * `America/Argentina/San_Juan` - America/Argentina/San_Juan + * `America/Argentina/San_Luis` - America/Argentina/San_Luis + * `America/Argentina/Tucuman` - America/Argentina/Tucuman + * `America/Argentina/Ushuaia` - America/Argentina/Ushuaia + * `America/Aruba` - America/Aruba + * `America/Asuncion` - America/Asuncion + * `America/Atikokan` - America/Atikokan + * `America/Bahia` - America/Bahia + * `America/Bahia_Banderas` - America/Bahia_Banderas + * `America/Barbados` - America/Barbados + * `America/Belem` - America/Belem + * `America/Belize` - America/Belize + * `America/Blanc-Sablon` - America/Blanc-Sablon + * `America/Boa_Vista` - America/Boa_Vista + * `America/Bogota` - America/Bogota + * `America/Boise` - America/Boise + * `America/Cambridge_Bay` - America/Cambridge_Bay + * `America/Campo_Grande` - America/Campo_Grande + * `America/Cancun` - America/Cancun + * `America/Caracas` - America/Caracas + * `America/Cayenne` - America/Cayenne + * `America/Cayman` - America/Cayman + * `America/Chicago` - America/Chicago + * `America/Chihuahua` - America/Chihuahua + * `America/Ciudad_Juarez` - America/Ciudad_Juarez + * `America/Costa_Rica` - America/Costa_Rica + * `America/Coyhaique` - America/Coyhaique + * `America/Creston` - America/Creston + * `America/Cuiaba` - America/Cuiaba + * `America/Curacao` - America/Curacao + * `America/Danmarkshavn` - America/Danmarkshavn + * `America/Dawson` - America/Dawson + * `America/Dawson_Creek` - America/Dawson_Creek + * `America/Denver` - America/Denver + * `America/Detroit` - America/Detroit + * `America/Dominica` - America/Dominica + * `America/Edmonton` - America/Edmonton + * `America/Eirunepe` - America/Eirunepe + * `America/El_Salvador` - America/El_Salvador + * `America/Fort_Nelson` - America/Fort_Nelson + * `America/Fortaleza` - America/Fortaleza + * `America/Glace_Bay` - America/Glace_Bay + * `America/Goose_Bay` - America/Goose_Bay + * `America/Grand_Turk` - America/Grand_Turk + * `America/Grenada` - America/Grenada + * `America/Guadeloupe` - America/Guadeloupe + * `America/Guatemala` - America/Guatemala + * `America/Guayaquil` - America/Guayaquil + * `America/Guyana` - America/Guyana + * `America/Halifax` - America/Halifax + * `America/Havana` - America/Havana + * `America/Hermosillo` - America/Hermosillo + * `America/Indiana/Indianapolis` - America/Indiana/Indianapolis + * `America/Indiana/Knox` - America/Indiana/Knox + * `America/Indiana/Marengo` - America/Indiana/Marengo + * `America/Indiana/Petersburg` - America/Indiana/Petersburg + * `America/Indiana/Tell_City` - America/Indiana/Tell_City + * `America/Indiana/Vevay` - America/Indiana/Vevay + * `America/Indiana/Vincennes` - America/Indiana/Vincennes + * `America/Indiana/Winamac` - America/Indiana/Winamac + * `America/Inuvik` - America/Inuvik + * `America/Iqaluit` - America/Iqaluit + * `America/Jamaica` - America/Jamaica + * `America/Juneau` - America/Juneau + * `America/Kentucky/Louisville` - America/Kentucky/Louisville + * `America/Kentucky/Monticello` - America/Kentucky/Monticello + * `America/Kralendijk` - America/Kralendijk + * `America/La_Paz` - America/La_Paz + * `America/Lima` - America/Lima + * `America/Los_Angeles` - America/Los_Angeles + * `America/Lower_Princes` - America/Lower_Princes + * `America/Maceio` - America/Maceio + * `America/Managua` - America/Managua + * `America/Manaus` - America/Manaus + * `America/Marigot` - America/Marigot + * `America/Martinique` - America/Martinique + * `America/Matamoros` - America/Matamoros + * `America/Mazatlan` - America/Mazatlan + * `America/Menominee` - America/Menominee + * `America/Merida` - America/Merida + * `America/Metlakatla` - America/Metlakatla + * `America/Mexico_City` - America/Mexico_City + * `America/Miquelon` - America/Miquelon + * `America/Moncton` - America/Moncton + * `America/Monterrey` - America/Monterrey + * `America/Montevideo` - America/Montevideo + * `America/Montserrat` - America/Montserrat + * `America/Nassau` - America/Nassau + * `America/New_York` - America/New_York + * `America/Nome` - America/Nome + * `America/Noronha` - America/Noronha + * `America/North_Dakota/Beulah` - America/North_Dakota/Beulah + * `America/North_Dakota/Center` - America/North_Dakota/Center + * `America/North_Dakota/New_Salem` - America/North_Dakota/New_Salem + * `America/Nuuk` - America/Nuuk + * `America/Ojinaga` - America/Ojinaga + * `America/Panama` - America/Panama + * `America/Paramaribo` - America/Paramaribo + * `America/Phoenix` - America/Phoenix + * `America/Port-au-Prince` - America/Port-au-Prince + * `America/Port_of_Spain` - America/Port_of_Spain + * `America/Porto_Velho` - America/Porto_Velho + * `America/Puerto_Rico` - America/Puerto_Rico + * `America/Punta_Arenas` - America/Punta_Arenas + * `America/Rankin_Inlet` - America/Rankin_Inlet + * `America/Recife` - America/Recife + * `America/Regina` - America/Regina + * `America/Resolute` - America/Resolute + * `America/Rio_Branco` - America/Rio_Branco + * `America/Santarem` - America/Santarem + * `America/Santiago` - America/Santiago + * `America/Santo_Domingo` - America/Santo_Domingo + * `America/Sao_Paulo` - America/Sao_Paulo + * `America/Scoresbysund` - America/Scoresbysund + * `America/Sitka` - America/Sitka + * `America/St_Barthelemy` - America/St_Barthelemy + * `America/St_Johns` - America/St_Johns + * `America/St_Kitts` - America/St_Kitts + * `America/St_Lucia` - America/St_Lucia + * `America/St_Thomas` - America/St_Thomas + * `America/St_Vincent` - America/St_Vincent + * `America/Swift_Current` - America/Swift_Current + * `America/Tegucigalpa` - America/Tegucigalpa + * `America/Thule` - America/Thule + * `America/Tijuana` - America/Tijuana + * `America/Toronto` - America/Toronto + * `America/Tortola` - America/Tortola + * `America/Vancouver` - America/Vancouver + * `America/Whitehorse` - America/Whitehorse + * `America/Winnipeg` - America/Winnipeg + * `America/Yakutat` - America/Yakutat + * `Antarctica/Casey` - Antarctica/Casey + * `Antarctica/Davis` - Antarctica/Davis + * `Antarctica/DumontDUrville` - Antarctica/DumontDUrville + * `Antarctica/Macquarie` - Antarctica/Macquarie + * `Antarctica/Mawson` - Antarctica/Mawson + * `Antarctica/McMurdo` - Antarctica/McMurdo + * `Antarctica/Palmer` - Antarctica/Palmer + * `Antarctica/Rothera` - Antarctica/Rothera + * `Antarctica/Syowa` - Antarctica/Syowa + * `Antarctica/Troll` - Antarctica/Troll + * `Antarctica/Vostok` - Antarctica/Vostok + * `Arctic/Longyearbyen` - Arctic/Longyearbyen + * `Asia/Aden` - Asia/Aden + * `Asia/Almaty` - Asia/Almaty + * `Asia/Amman` - Asia/Amman + * `Asia/Anadyr` - Asia/Anadyr + * `Asia/Aqtau` - Asia/Aqtau + * `Asia/Aqtobe` - Asia/Aqtobe + * `Asia/Ashgabat` - Asia/Ashgabat + * `Asia/Atyrau` - Asia/Atyrau + * `Asia/Baghdad` - Asia/Baghdad + * `Asia/Bahrain` - Asia/Bahrain + * `Asia/Baku` - Asia/Baku + * `Asia/Bangkok` - Asia/Bangkok + * `Asia/Barnaul` - Asia/Barnaul + * `Asia/Beirut` - Asia/Beirut + * `Asia/Bishkek` - Asia/Bishkek + * `Asia/Brunei` - Asia/Brunei + * `Asia/Chita` - Asia/Chita + * `Asia/Colombo` - Asia/Colombo + * `Asia/Damascus` - Asia/Damascus + * `Asia/Dhaka` - Asia/Dhaka + * `Asia/Dili` - Asia/Dili + * `Asia/Dubai` - Asia/Dubai + * `Asia/Dushanbe` - Asia/Dushanbe + * `Asia/Famagusta` - Asia/Famagusta + * `Asia/Gaza` - Asia/Gaza + * `Asia/Hebron` - Asia/Hebron + * `Asia/Ho_Chi_Minh` - Asia/Ho_Chi_Minh + * `Asia/Hong_Kong` - Asia/Hong_Kong + * `Asia/Hovd` - Asia/Hovd + * `Asia/Irkutsk` - Asia/Irkutsk + * `Asia/Jakarta` - Asia/Jakarta + * `Asia/Jayapura` - Asia/Jayapura + * `Asia/Jerusalem` - Asia/Jerusalem + * `Asia/Kabul` - Asia/Kabul + * `Asia/Kamchatka` - Asia/Kamchatka + * `Asia/Karachi` - Asia/Karachi + * `Asia/Kathmandu` - Asia/Kathmandu + * `Asia/Khandyga` - Asia/Khandyga + * `Asia/Kolkata` - Asia/Kolkata + * `Asia/Krasnoyarsk` - Asia/Krasnoyarsk + * `Asia/Kuala_Lumpur` - Asia/Kuala_Lumpur + * `Asia/Kuching` - Asia/Kuching + * `Asia/Kuwait` - Asia/Kuwait + * `Asia/Macau` - Asia/Macau + * `Asia/Magadan` - Asia/Magadan + * `Asia/Makassar` - Asia/Makassar + * `Asia/Manila` - Asia/Manila + * `Asia/Muscat` - Asia/Muscat + * `Asia/Nicosia` - Asia/Nicosia + * `Asia/Novokuznetsk` - Asia/Novokuznetsk + * `Asia/Novosibirsk` - Asia/Novosibirsk + * `Asia/Omsk` - Asia/Omsk + * `Asia/Oral` - Asia/Oral + * `Asia/Phnom_Penh` - Asia/Phnom_Penh + * `Asia/Pontianak` - Asia/Pontianak + * `Asia/Pyongyang` - Asia/Pyongyang + * `Asia/Qatar` - Asia/Qatar + * `Asia/Qostanay` - Asia/Qostanay + * `Asia/Qyzylorda` - Asia/Qyzylorda + * `Asia/Riyadh` - Asia/Riyadh + * `Asia/Sakhalin` - Asia/Sakhalin + * `Asia/Samarkand` - Asia/Samarkand + * `Asia/Seoul` - Asia/Seoul + * `Asia/Shanghai` - Asia/Shanghai + * `Asia/Singapore` - Asia/Singapore + * `Asia/Srednekolymsk` - Asia/Srednekolymsk + * `Asia/Taipei` - Asia/Taipei + * `Asia/Tashkent` - Asia/Tashkent + * `Asia/Tbilisi` - Asia/Tbilisi + * `Asia/Tehran` - Asia/Tehran + * `Asia/Thimphu` - Asia/Thimphu + * `Asia/Tokyo` - Asia/Tokyo + * `Asia/Tomsk` - Asia/Tomsk + * `Asia/Ulaanbaatar` - Asia/Ulaanbaatar + * `Asia/Urumqi` - Asia/Urumqi + * `Asia/Ust-Nera` - Asia/Ust-Nera + * `Asia/Vientiane` - Asia/Vientiane + * `Asia/Vladivostok` - Asia/Vladivostok + * `Asia/Yakutsk` - Asia/Yakutsk + * `Asia/Yangon` - Asia/Yangon + * `Asia/Yekaterinburg` - Asia/Yekaterinburg + * `Asia/Yerevan` - Asia/Yerevan + * `Atlantic/Azores` - Atlantic/Azores + * `Atlantic/Bermuda` - Atlantic/Bermuda + * `Atlantic/Canary` - Atlantic/Canary + * `Atlantic/Cape_Verde` - Atlantic/Cape_Verde + * `Atlantic/Faroe` - Atlantic/Faroe + * `Atlantic/Madeira` - Atlantic/Madeira + * `Atlantic/Reykjavik` - Atlantic/Reykjavik + * `Atlantic/South_Georgia` - Atlantic/South_Georgia + * `Atlantic/St_Helena` - Atlantic/St_Helena + * `Atlantic/Stanley` - Atlantic/Stanley + * `Australia/Adelaide` - Australia/Adelaide + * `Australia/Brisbane` - Australia/Brisbane + * `Australia/Broken_Hill` - Australia/Broken_Hill + * `Australia/Darwin` - Australia/Darwin + * `Australia/Eucla` - Australia/Eucla + * `Australia/Hobart` - Australia/Hobart + * `Australia/Lindeman` - Australia/Lindeman + * `Australia/Lord_Howe` - Australia/Lord_Howe + * `Australia/Melbourne` - Australia/Melbourne + * `Australia/Perth` - Australia/Perth + * `Australia/Sydney` - Australia/Sydney + * `Europe/Amsterdam` - Europe/Amsterdam + * `Europe/Andorra` - Europe/Andorra + * `Europe/Astrakhan` - Europe/Astrakhan + * `Europe/Athens` - Europe/Athens + * `Europe/Belgrade` - Europe/Belgrade + * `Europe/Berlin` - Europe/Berlin + * `Europe/Bratislava` - Europe/Bratislava + * `Europe/Brussels` - Europe/Brussels + * `Europe/Bucharest` - Europe/Bucharest + * `Europe/Budapest` - Europe/Budapest + * `Europe/Busingen` - Europe/Busingen + * `Europe/Chisinau` - Europe/Chisinau + * `Europe/Copenhagen` - Europe/Copenhagen + * `Europe/Dublin` - Europe/Dublin + * `Europe/Gibraltar` - Europe/Gibraltar + * `Europe/Guernsey` - Europe/Guernsey + * `Europe/Helsinki` - Europe/Helsinki + * `Europe/Isle_of_Man` - Europe/Isle_of_Man + * `Europe/Istanbul` - Europe/Istanbul + * `Europe/Jersey` - Europe/Jersey + * `Europe/Kaliningrad` - Europe/Kaliningrad + * `Europe/Kirov` - Europe/Kirov + * `Europe/Kyiv` - Europe/Kyiv + * `Europe/Lisbon` - Europe/Lisbon + * `Europe/Ljubljana` - Europe/Ljubljana + * `Europe/London` - Europe/London + * `Europe/Luxembourg` - Europe/Luxembourg + * `Europe/Madrid` - Europe/Madrid + * `Europe/Malta` - Europe/Malta + * `Europe/Mariehamn` - Europe/Mariehamn + * `Europe/Minsk` - Europe/Minsk + * `Europe/Monaco` - Europe/Monaco + * `Europe/Moscow` - Europe/Moscow + * `Europe/Oslo` - Europe/Oslo + * `Europe/Paris` - Europe/Paris + * `Europe/Podgorica` - Europe/Podgorica + * `Europe/Prague` - Europe/Prague + * `Europe/Riga` - Europe/Riga + * `Europe/Rome` - Europe/Rome + * `Europe/Samara` - Europe/Samara + * `Europe/San_Marino` - Europe/San_Marino + * `Europe/Sarajevo` - Europe/Sarajevo + * `Europe/Saratov` - Europe/Saratov + * `Europe/Simferopol` - Europe/Simferopol + * `Europe/Skopje` - Europe/Skopje + * `Europe/Sofia` - Europe/Sofia + * `Europe/Stockholm` - Europe/Stockholm + * `Europe/Tallinn` - Europe/Tallinn + * `Europe/Tirane` - Europe/Tirane + * `Europe/Ulyanovsk` - Europe/Ulyanovsk + * `Europe/Vaduz` - Europe/Vaduz + * `Europe/Vatican` - Europe/Vatican + * `Europe/Vienna` - Europe/Vienna + * `Europe/Vilnius` - Europe/Vilnius + * `Europe/Volgograd` - Europe/Volgograd + * `Europe/Warsaw` - Europe/Warsaw + * `Europe/Zagreb` - Europe/Zagreb + * `Europe/Zurich` - Europe/Zurich + * `GMT` - GMT + * `Indian/Antananarivo` - Indian/Antananarivo + * `Indian/Chagos` - Indian/Chagos + * `Indian/Christmas` - Indian/Christmas + * `Indian/Cocos` - Indian/Cocos + * `Indian/Comoro` - Indian/Comoro + * `Indian/Kerguelen` - Indian/Kerguelen + * `Indian/Mahe` - Indian/Mahe + * `Indian/Maldives` - Indian/Maldives + * `Indian/Mauritius` - Indian/Mauritius + * `Indian/Mayotte` - Indian/Mayotte + * `Indian/Reunion` - Indian/Reunion + * `Pacific/Apia` - Pacific/Apia + * `Pacific/Auckland` - Pacific/Auckland + * `Pacific/Bougainville` - Pacific/Bougainville + * `Pacific/Chatham` - Pacific/Chatham + * `Pacific/Chuuk` - Pacific/Chuuk + * `Pacific/Easter` - Pacific/Easter + * `Pacific/Efate` - Pacific/Efate + * `Pacific/Fakaofo` - Pacific/Fakaofo + * `Pacific/Fiji` - Pacific/Fiji + * `Pacific/Funafuti` - Pacific/Funafuti + * `Pacific/Galapagos` - Pacific/Galapagos + * `Pacific/Gambier` - Pacific/Gambier + * `Pacific/Guadalcanal` - Pacific/Guadalcanal + * `Pacific/Guam` - Pacific/Guam + * `Pacific/Honolulu` - Pacific/Honolulu + * `Pacific/Kanton` - Pacific/Kanton + * `Pacific/Kiritimati` - Pacific/Kiritimati + * `Pacific/Kosrae` - Pacific/Kosrae + * `Pacific/Kwajalein` - Pacific/Kwajalein + * `Pacific/Majuro` - Pacific/Majuro + * `Pacific/Marquesas` - Pacific/Marquesas + * `Pacific/Midway` - Pacific/Midway + * `Pacific/Nauru` - Pacific/Nauru + * `Pacific/Niue` - Pacific/Niue + * `Pacific/Norfolk` - Pacific/Norfolk + * `Pacific/Noumea` - Pacific/Noumea + * `Pacific/Pago_Pago` - Pacific/Pago_Pago + * `Pacific/Palau` - Pacific/Palau + * `Pacific/Pitcairn` - Pacific/Pitcairn + * `Pacific/Pohnpei` - Pacific/Pohnpei + * `Pacific/Port_Moresby` - Pacific/Port_Moresby + * `Pacific/Rarotonga` - Pacific/Rarotonga + * `Pacific/Saipan` - Pacific/Saipan + * `Pacific/Tahiti` - Pacific/Tahiti + * `Pacific/Tarawa` - Pacific/Tarawa + * `Pacific/Tongatapu` - Pacific/Tongatapu + * `Pacific/Wake` - Pacific/Wake + * `Pacific/Wallis` - Pacific/Wallis + * `UTC` - UTC + oneOf: + - $ref: '#/components/schemas/LocalTimezoneEnum' + - $ref: '#/components/schemas/BlankEnum' + - $ref: '#/components/schemas/NullEnum' + bootswatch_theme: + type: integer + nullable: true + description: Bootswatch theme for light mode + UserProfileRequest: + type: object + description: |- + User profile serializer with preference settings. + + Provides access to user-specific configuration including: + - Date and time format preferences + - Timezone settings (home and local) + - UI theme customization + properties: + date_format: + allOf: + - $ref: '#/components/schemas/DateFormatEnum' + description: |- + Preferred date display format + + * `YYYY-MM-DD` - 2024-12-25 + * `DD/MM/YYYY` - 25/12/2024 + * `MM/DD/YYYY` - 12/25/2024 + * `DD.MM.YYYY` - 25.12.2024 + * `DD-MM-YYYY` - 25-12-2024 + time_format: + allOf: + - $ref: '#/components/schemas/TimeFormatEnum' + description: |- + 12-hour or 24-hour time format + + * `12-hour` - 12-hour (3:30 PM) + * `24-hour` - 24-hour (15:30) + home_timezone: + allOf: + - $ref: '#/components/schemas/HomeTimezoneEnum' + description: |- + User's home/permanent timezone + + * `Africa/Abidjan` - Africa/Abidjan + * `Africa/Accra` - Africa/Accra + * `Africa/Addis_Ababa` - Africa/Addis_Ababa + * `Africa/Algiers` - Africa/Algiers + * `Africa/Asmara` - Africa/Asmara + * `Africa/Bamako` - Africa/Bamako + * `Africa/Bangui` - Africa/Bangui + * `Africa/Banjul` - Africa/Banjul + * `Africa/Bissau` - Africa/Bissau + * `Africa/Blantyre` - Africa/Blantyre + * `Africa/Brazzaville` - Africa/Brazzaville + * `Africa/Bujumbura` - Africa/Bujumbura + * `Africa/Cairo` - Africa/Cairo + * `Africa/Casablanca` - Africa/Casablanca + * `Africa/Ceuta` - Africa/Ceuta + * `Africa/Conakry` - Africa/Conakry + * `Africa/Dakar` - Africa/Dakar + * `Africa/Dar_es_Salaam` - Africa/Dar_es_Salaam + * `Africa/Djibouti` - Africa/Djibouti + * `Africa/Douala` - Africa/Douala + * `Africa/El_Aaiun` - Africa/El_Aaiun + * `Africa/Freetown` - Africa/Freetown + * `Africa/Gaborone` - Africa/Gaborone + * `Africa/Harare` - Africa/Harare + * `Africa/Johannesburg` - Africa/Johannesburg + * `Africa/Juba` - Africa/Juba + * `Africa/Kampala` - Africa/Kampala + * `Africa/Khartoum` - Africa/Khartoum + * `Africa/Kigali` - Africa/Kigali + * `Africa/Kinshasa` - Africa/Kinshasa + * `Africa/Lagos` - Africa/Lagos + * `Africa/Libreville` - Africa/Libreville + * `Africa/Lome` - Africa/Lome + * `Africa/Luanda` - Africa/Luanda + * `Africa/Lubumbashi` - Africa/Lubumbashi + * `Africa/Lusaka` - Africa/Lusaka + * `Africa/Malabo` - Africa/Malabo + * `Africa/Maputo` - Africa/Maputo + * `Africa/Maseru` - Africa/Maseru + * `Africa/Mbabane` - Africa/Mbabane + * `Africa/Mogadishu` - Africa/Mogadishu + * `Africa/Monrovia` - Africa/Monrovia + * `Africa/Nairobi` - Africa/Nairobi + * `Africa/Ndjamena` - Africa/Ndjamena + * `Africa/Niamey` - Africa/Niamey + * `Africa/Nouakchott` - Africa/Nouakchott + * `Africa/Ouagadougou` - Africa/Ouagadougou + * `Africa/Porto-Novo` - Africa/Porto-Novo + * `Africa/Sao_Tome` - Africa/Sao_Tome + * `Africa/Tripoli` - Africa/Tripoli + * `Africa/Tunis` - Africa/Tunis + * `Africa/Windhoek` - Africa/Windhoek + * `America/Adak` - America/Adak + * `America/Anchorage` - America/Anchorage + * `America/Anguilla` - America/Anguilla + * `America/Antigua` - America/Antigua + * `America/Araguaina` - America/Araguaina + * `America/Argentina/Buenos_Aires` - America/Argentina/Buenos_Aires + * `America/Argentina/Catamarca` - America/Argentina/Catamarca + * `America/Argentina/Cordoba` - America/Argentina/Cordoba + * `America/Argentina/Jujuy` - America/Argentina/Jujuy + * `America/Argentina/La_Rioja` - America/Argentina/La_Rioja + * `America/Argentina/Mendoza` - America/Argentina/Mendoza + * `America/Argentina/Rio_Gallegos` - America/Argentina/Rio_Gallegos + * `America/Argentina/Salta` - America/Argentina/Salta + * `America/Argentina/San_Juan` - America/Argentina/San_Juan + * `America/Argentina/San_Luis` - America/Argentina/San_Luis + * `America/Argentina/Tucuman` - America/Argentina/Tucuman + * `America/Argentina/Ushuaia` - America/Argentina/Ushuaia + * `America/Aruba` - America/Aruba + * `America/Asuncion` - America/Asuncion + * `America/Atikokan` - America/Atikokan + * `America/Bahia` - America/Bahia + * `America/Bahia_Banderas` - America/Bahia_Banderas + * `America/Barbados` - America/Barbados + * `America/Belem` - America/Belem + * `America/Belize` - America/Belize + * `America/Blanc-Sablon` - America/Blanc-Sablon + * `America/Boa_Vista` - America/Boa_Vista + * `America/Bogota` - America/Bogota + * `America/Boise` - America/Boise + * `America/Cambridge_Bay` - America/Cambridge_Bay + * `America/Campo_Grande` - America/Campo_Grande + * `America/Cancun` - America/Cancun + * `America/Caracas` - America/Caracas + * `America/Cayenne` - America/Cayenne + * `America/Cayman` - America/Cayman + * `America/Chicago` - America/Chicago + * `America/Chihuahua` - America/Chihuahua + * `America/Ciudad_Juarez` - America/Ciudad_Juarez + * `America/Costa_Rica` - America/Costa_Rica + * `America/Coyhaique` - America/Coyhaique + * `America/Creston` - America/Creston + * `America/Cuiaba` - America/Cuiaba + * `America/Curacao` - America/Curacao + * `America/Danmarkshavn` - America/Danmarkshavn + * `America/Dawson` - America/Dawson + * `America/Dawson_Creek` - America/Dawson_Creek + * `America/Denver` - America/Denver + * `America/Detroit` - America/Detroit + * `America/Dominica` - America/Dominica + * `America/Edmonton` - America/Edmonton + * `America/Eirunepe` - America/Eirunepe + * `America/El_Salvador` - America/El_Salvador + * `America/Fort_Nelson` - America/Fort_Nelson + * `America/Fortaleza` - America/Fortaleza + * `America/Glace_Bay` - America/Glace_Bay + * `America/Goose_Bay` - America/Goose_Bay + * `America/Grand_Turk` - America/Grand_Turk + * `America/Grenada` - America/Grenada + * `America/Guadeloupe` - America/Guadeloupe + * `America/Guatemala` - America/Guatemala + * `America/Guayaquil` - America/Guayaquil + * `America/Guyana` - America/Guyana + * `America/Halifax` - America/Halifax + * `America/Havana` - America/Havana + * `America/Hermosillo` - America/Hermosillo + * `America/Indiana/Indianapolis` - America/Indiana/Indianapolis + * `America/Indiana/Knox` - America/Indiana/Knox + * `America/Indiana/Marengo` - America/Indiana/Marengo + * `America/Indiana/Petersburg` - America/Indiana/Petersburg + * `America/Indiana/Tell_City` - America/Indiana/Tell_City + * `America/Indiana/Vevay` - America/Indiana/Vevay + * `America/Indiana/Vincennes` - America/Indiana/Vincennes + * `America/Indiana/Winamac` - America/Indiana/Winamac + * `America/Inuvik` - America/Inuvik + * `America/Iqaluit` - America/Iqaluit + * `America/Jamaica` - America/Jamaica + * `America/Juneau` - America/Juneau + * `America/Kentucky/Louisville` - America/Kentucky/Louisville + * `America/Kentucky/Monticello` - America/Kentucky/Monticello + * `America/Kralendijk` - America/Kralendijk + * `America/La_Paz` - America/La_Paz + * `America/Lima` - America/Lima + * `America/Los_Angeles` - America/Los_Angeles + * `America/Lower_Princes` - America/Lower_Princes + * `America/Maceio` - America/Maceio + * `America/Managua` - America/Managua + * `America/Manaus` - America/Manaus + * `America/Marigot` - America/Marigot + * `America/Martinique` - America/Martinique + * `America/Matamoros` - America/Matamoros + * `America/Mazatlan` - America/Mazatlan + * `America/Menominee` - America/Menominee + * `America/Merida` - America/Merida + * `America/Metlakatla` - America/Metlakatla + * `America/Mexico_City` - America/Mexico_City + * `America/Miquelon` - America/Miquelon + * `America/Moncton` - America/Moncton + * `America/Monterrey` - America/Monterrey + * `America/Montevideo` - America/Montevideo + * `America/Montserrat` - America/Montserrat + * `America/Nassau` - America/Nassau + * `America/New_York` - America/New_York + * `America/Nome` - America/Nome + * `America/Noronha` - America/Noronha + * `America/North_Dakota/Beulah` - America/North_Dakota/Beulah + * `America/North_Dakota/Center` - America/North_Dakota/Center + * `America/North_Dakota/New_Salem` - America/North_Dakota/New_Salem + * `America/Nuuk` - America/Nuuk + * `America/Ojinaga` - America/Ojinaga + * `America/Panama` - America/Panama + * `America/Paramaribo` - America/Paramaribo + * `America/Phoenix` - America/Phoenix + * `America/Port-au-Prince` - America/Port-au-Prince + * `America/Port_of_Spain` - America/Port_of_Spain + * `America/Porto_Velho` - America/Porto_Velho + * `America/Puerto_Rico` - America/Puerto_Rico + * `America/Punta_Arenas` - America/Punta_Arenas + * `America/Rankin_Inlet` - America/Rankin_Inlet + * `America/Recife` - America/Recife + * `America/Regina` - America/Regina + * `America/Resolute` - America/Resolute + * `America/Rio_Branco` - America/Rio_Branco + * `America/Santarem` - America/Santarem + * `America/Santiago` - America/Santiago + * `America/Santo_Domingo` - America/Santo_Domingo + * `America/Sao_Paulo` - America/Sao_Paulo + * `America/Scoresbysund` - America/Scoresbysund + * `America/Sitka` - America/Sitka + * `America/St_Barthelemy` - America/St_Barthelemy + * `America/St_Johns` - America/St_Johns + * `America/St_Kitts` - America/St_Kitts + * `America/St_Lucia` - America/St_Lucia + * `America/St_Thomas` - America/St_Thomas + * `America/St_Vincent` - America/St_Vincent + * `America/Swift_Current` - America/Swift_Current + * `America/Tegucigalpa` - America/Tegucigalpa + * `America/Thule` - America/Thule + * `America/Tijuana` - America/Tijuana + * `America/Toronto` - America/Toronto + * `America/Tortola` - America/Tortola + * `America/Vancouver` - America/Vancouver + * `America/Whitehorse` - America/Whitehorse + * `America/Winnipeg` - America/Winnipeg + * `America/Yakutat` - America/Yakutat + * `Antarctica/Casey` - Antarctica/Casey + * `Antarctica/Davis` - Antarctica/Davis + * `Antarctica/DumontDUrville` - Antarctica/DumontDUrville + * `Antarctica/Macquarie` - Antarctica/Macquarie + * `Antarctica/Mawson` - Antarctica/Mawson + * `Antarctica/McMurdo` - Antarctica/McMurdo + * `Antarctica/Palmer` - Antarctica/Palmer + * `Antarctica/Rothera` - Antarctica/Rothera + * `Antarctica/Syowa` - Antarctica/Syowa + * `Antarctica/Troll` - Antarctica/Troll + * `Antarctica/Vostok` - Antarctica/Vostok + * `Arctic/Longyearbyen` - Arctic/Longyearbyen + * `Asia/Aden` - Asia/Aden + * `Asia/Almaty` - Asia/Almaty + * `Asia/Amman` - Asia/Amman + * `Asia/Anadyr` - Asia/Anadyr + * `Asia/Aqtau` - Asia/Aqtau + * `Asia/Aqtobe` - Asia/Aqtobe + * `Asia/Ashgabat` - Asia/Ashgabat + * `Asia/Atyrau` - Asia/Atyrau + * `Asia/Baghdad` - Asia/Baghdad + * `Asia/Bahrain` - Asia/Bahrain + * `Asia/Baku` - Asia/Baku + * `Asia/Bangkok` - Asia/Bangkok + * `Asia/Barnaul` - Asia/Barnaul + * `Asia/Beirut` - Asia/Beirut + * `Asia/Bishkek` - Asia/Bishkek + * `Asia/Brunei` - Asia/Brunei + * `Asia/Chita` - Asia/Chita + * `Asia/Colombo` - Asia/Colombo + * `Asia/Damascus` - Asia/Damascus + * `Asia/Dhaka` - Asia/Dhaka + * `Asia/Dili` - Asia/Dili + * `Asia/Dubai` - Asia/Dubai + * `Asia/Dushanbe` - Asia/Dushanbe + * `Asia/Famagusta` - Asia/Famagusta + * `Asia/Gaza` - Asia/Gaza + * `Asia/Hebron` - Asia/Hebron + * `Asia/Ho_Chi_Minh` - Asia/Ho_Chi_Minh + * `Asia/Hong_Kong` - Asia/Hong_Kong + * `Asia/Hovd` - Asia/Hovd + * `Asia/Irkutsk` - Asia/Irkutsk + * `Asia/Jakarta` - Asia/Jakarta + * `Asia/Jayapura` - Asia/Jayapura + * `Asia/Jerusalem` - Asia/Jerusalem + * `Asia/Kabul` - Asia/Kabul + * `Asia/Kamchatka` - Asia/Kamchatka + * `Asia/Karachi` - Asia/Karachi + * `Asia/Kathmandu` - Asia/Kathmandu + * `Asia/Khandyga` - Asia/Khandyga + * `Asia/Kolkata` - Asia/Kolkata + * `Asia/Krasnoyarsk` - Asia/Krasnoyarsk + * `Asia/Kuala_Lumpur` - Asia/Kuala_Lumpur + * `Asia/Kuching` - Asia/Kuching + * `Asia/Kuwait` - Asia/Kuwait + * `Asia/Macau` - Asia/Macau + * `Asia/Magadan` - Asia/Magadan + * `Asia/Makassar` - Asia/Makassar + * `Asia/Manila` - Asia/Manila + * `Asia/Muscat` - Asia/Muscat + * `Asia/Nicosia` - Asia/Nicosia + * `Asia/Novokuznetsk` - Asia/Novokuznetsk + * `Asia/Novosibirsk` - Asia/Novosibirsk + * `Asia/Omsk` - Asia/Omsk + * `Asia/Oral` - Asia/Oral + * `Asia/Phnom_Penh` - Asia/Phnom_Penh + * `Asia/Pontianak` - Asia/Pontianak + * `Asia/Pyongyang` - Asia/Pyongyang + * `Asia/Qatar` - Asia/Qatar + * `Asia/Qostanay` - Asia/Qostanay + * `Asia/Qyzylorda` - Asia/Qyzylorda + * `Asia/Riyadh` - Asia/Riyadh + * `Asia/Sakhalin` - Asia/Sakhalin + * `Asia/Samarkand` - Asia/Samarkand + * `Asia/Seoul` - Asia/Seoul + * `Asia/Shanghai` - Asia/Shanghai + * `Asia/Singapore` - Asia/Singapore + * `Asia/Srednekolymsk` - Asia/Srednekolymsk + * `Asia/Taipei` - Asia/Taipei + * `Asia/Tashkent` - Asia/Tashkent + * `Asia/Tbilisi` - Asia/Tbilisi + * `Asia/Tehran` - Asia/Tehran + * `Asia/Thimphu` - Asia/Thimphu + * `Asia/Tokyo` - Asia/Tokyo + * `Asia/Tomsk` - Asia/Tomsk + * `Asia/Ulaanbaatar` - Asia/Ulaanbaatar + * `Asia/Urumqi` - Asia/Urumqi + * `Asia/Ust-Nera` - Asia/Ust-Nera + * `Asia/Vientiane` - Asia/Vientiane + * `Asia/Vladivostok` - Asia/Vladivostok + * `Asia/Yakutsk` - Asia/Yakutsk + * `Asia/Yangon` - Asia/Yangon + * `Asia/Yekaterinburg` - Asia/Yekaterinburg + * `Asia/Yerevan` - Asia/Yerevan + * `Atlantic/Azores` - Atlantic/Azores + * `Atlantic/Bermuda` - Atlantic/Bermuda + * `Atlantic/Canary` - Atlantic/Canary + * `Atlantic/Cape_Verde` - Atlantic/Cape_Verde + * `Atlantic/Faroe` - Atlantic/Faroe + * `Atlantic/Madeira` - Atlantic/Madeira + * `Atlantic/Reykjavik` - Atlantic/Reykjavik + * `Atlantic/South_Georgia` - Atlantic/South_Georgia + * `Atlantic/St_Helena` - Atlantic/St_Helena + * `Atlantic/Stanley` - Atlantic/Stanley + * `Australia/Adelaide` - Australia/Adelaide + * `Australia/Brisbane` - Australia/Brisbane + * `Australia/Broken_Hill` - Australia/Broken_Hill + * `Australia/Darwin` - Australia/Darwin + * `Australia/Eucla` - Australia/Eucla + * `Australia/Hobart` - Australia/Hobart + * `Australia/Lindeman` - Australia/Lindeman + * `Australia/Lord_Howe` - Australia/Lord_Howe + * `Australia/Melbourne` - Australia/Melbourne + * `Australia/Perth` - Australia/Perth + * `Australia/Sydney` - Australia/Sydney + * `Europe/Amsterdam` - Europe/Amsterdam + * `Europe/Andorra` - Europe/Andorra + * `Europe/Astrakhan` - Europe/Astrakhan + * `Europe/Athens` - Europe/Athens + * `Europe/Belgrade` - Europe/Belgrade + * `Europe/Berlin` - Europe/Berlin + * `Europe/Bratislava` - Europe/Bratislava + * `Europe/Brussels` - Europe/Brussels + * `Europe/Bucharest` - Europe/Bucharest + * `Europe/Budapest` - Europe/Budapest + * `Europe/Busingen` - Europe/Busingen + * `Europe/Chisinau` - Europe/Chisinau + * `Europe/Copenhagen` - Europe/Copenhagen + * `Europe/Dublin` - Europe/Dublin + * `Europe/Gibraltar` - Europe/Gibraltar + * `Europe/Guernsey` - Europe/Guernsey + * `Europe/Helsinki` - Europe/Helsinki + * `Europe/Isle_of_Man` - Europe/Isle_of_Man + * `Europe/Istanbul` - Europe/Istanbul + * `Europe/Jersey` - Europe/Jersey + * `Europe/Kaliningrad` - Europe/Kaliningrad + * `Europe/Kirov` - Europe/Kirov + * `Europe/Kyiv` - Europe/Kyiv + * `Europe/Lisbon` - Europe/Lisbon + * `Europe/Ljubljana` - Europe/Ljubljana + * `Europe/London` - Europe/London + * `Europe/Luxembourg` - Europe/Luxembourg + * `Europe/Madrid` - Europe/Madrid + * `Europe/Malta` - Europe/Malta + * `Europe/Mariehamn` - Europe/Mariehamn + * `Europe/Minsk` - Europe/Minsk + * `Europe/Monaco` - Europe/Monaco + * `Europe/Moscow` - Europe/Moscow + * `Europe/Oslo` - Europe/Oslo + * `Europe/Paris` - Europe/Paris + * `Europe/Podgorica` - Europe/Podgorica + * `Europe/Prague` - Europe/Prague + * `Europe/Riga` - Europe/Riga + * `Europe/Rome` - Europe/Rome + * `Europe/Samara` - Europe/Samara + * `Europe/San_Marino` - Europe/San_Marino + * `Europe/Sarajevo` - Europe/Sarajevo + * `Europe/Saratov` - Europe/Saratov + * `Europe/Simferopol` - Europe/Simferopol + * `Europe/Skopje` - Europe/Skopje + * `Europe/Sofia` - Europe/Sofia + * `Europe/Stockholm` - Europe/Stockholm + * `Europe/Tallinn` - Europe/Tallinn + * `Europe/Tirane` - Europe/Tirane + * `Europe/Ulyanovsk` - Europe/Ulyanovsk + * `Europe/Vaduz` - Europe/Vaduz + * `Europe/Vatican` - Europe/Vatican + * `Europe/Vienna` - Europe/Vienna + * `Europe/Vilnius` - Europe/Vilnius + * `Europe/Volgograd` - Europe/Volgograd + * `Europe/Warsaw` - Europe/Warsaw + * `Europe/Zagreb` - Europe/Zagreb + * `Europe/Zurich` - Europe/Zurich + * `GMT` - GMT + * `Indian/Antananarivo` - Indian/Antananarivo + * `Indian/Chagos` - Indian/Chagos + * `Indian/Christmas` - Indian/Christmas + * `Indian/Cocos` - Indian/Cocos + * `Indian/Comoro` - Indian/Comoro + * `Indian/Kerguelen` - Indian/Kerguelen + * `Indian/Mahe` - Indian/Mahe + * `Indian/Maldives` - Indian/Maldives + * `Indian/Mauritius` - Indian/Mauritius + * `Indian/Mayotte` - Indian/Mayotte + * `Indian/Reunion` - Indian/Reunion + * `Pacific/Apia` - Pacific/Apia + * `Pacific/Auckland` - Pacific/Auckland + * `Pacific/Bougainville` - Pacific/Bougainville + * `Pacific/Chatham` - Pacific/Chatham + * `Pacific/Chuuk` - Pacific/Chuuk + * `Pacific/Easter` - Pacific/Easter + * `Pacific/Efate` - Pacific/Efate + * `Pacific/Fakaofo` - Pacific/Fakaofo + * `Pacific/Fiji` - Pacific/Fiji + * `Pacific/Funafuti` - Pacific/Funafuti + * `Pacific/Galapagos` - Pacific/Galapagos + * `Pacific/Gambier` - Pacific/Gambier + * `Pacific/Guadalcanal` - Pacific/Guadalcanal + * `Pacific/Guam` - Pacific/Guam + * `Pacific/Honolulu` - Pacific/Honolulu + * `Pacific/Kanton` - Pacific/Kanton + * `Pacific/Kiritimati` - Pacific/Kiritimati + * `Pacific/Kosrae` - Pacific/Kosrae + * `Pacific/Kwajalein` - Pacific/Kwajalein + * `Pacific/Majuro` - Pacific/Majuro + * `Pacific/Marquesas` - Pacific/Marquesas + * `Pacific/Midway` - Pacific/Midway + * `Pacific/Nauru` - Pacific/Nauru + * `Pacific/Niue` - Pacific/Niue + * `Pacific/Norfolk` - Pacific/Norfolk + * `Pacific/Noumea` - Pacific/Noumea + * `Pacific/Pago_Pago` - Pacific/Pago_Pago + * `Pacific/Palau` - Pacific/Palau + * `Pacific/Pitcairn` - Pacific/Pitcairn + * `Pacific/Pohnpei` - Pacific/Pohnpei + * `Pacific/Port_Moresby` - Pacific/Port_Moresby + * `Pacific/Rarotonga` - Pacific/Rarotonga + * `Pacific/Saipan` - Pacific/Saipan + * `Pacific/Tahiti` - Pacific/Tahiti + * `Pacific/Tarawa` - Pacific/Tarawa + * `Pacific/Tongatapu` - Pacific/Tongatapu + * `Pacific/Wake` - Pacific/Wake + * `Pacific/Wallis` - Pacific/Wallis + * `UTC` - UTC + local_timezone: + nullable: true + description: |- + User's current local timezone (if traveling) + + * `Africa/Abidjan` - Africa/Abidjan + * `Africa/Accra` - Africa/Accra + * `Africa/Addis_Ababa` - Africa/Addis_Ababa + * `Africa/Algiers` - Africa/Algiers + * `Africa/Asmara` - Africa/Asmara + * `Africa/Bamako` - Africa/Bamako + * `Africa/Bangui` - Africa/Bangui + * `Africa/Banjul` - Africa/Banjul + * `Africa/Bissau` - Africa/Bissau + * `Africa/Blantyre` - Africa/Blantyre + * `Africa/Brazzaville` - Africa/Brazzaville + * `Africa/Bujumbura` - Africa/Bujumbura + * `Africa/Cairo` - Africa/Cairo + * `Africa/Casablanca` - Africa/Casablanca + * `Africa/Ceuta` - Africa/Ceuta + * `Africa/Conakry` - Africa/Conakry + * `Africa/Dakar` - Africa/Dakar + * `Africa/Dar_es_Salaam` - Africa/Dar_es_Salaam + * `Africa/Djibouti` - Africa/Djibouti + * `Africa/Douala` - Africa/Douala + * `Africa/El_Aaiun` - Africa/El_Aaiun + * `Africa/Freetown` - Africa/Freetown + * `Africa/Gaborone` - Africa/Gaborone + * `Africa/Harare` - Africa/Harare + * `Africa/Johannesburg` - Africa/Johannesburg + * `Africa/Juba` - Africa/Juba + * `Africa/Kampala` - Africa/Kampala + * `Africa/Khartoum` - Africa/Khartoum + * `Africa/Kigali` - Africa/Kigali + * `Africa/Kinshasa` - Africa/Kinshasa + * `Africa/Lagos` - Africa/Lagos + * `Africa/Libreville` - Africa/Libreville + * `Africa/Lome` - Africa/Lome + * `Africa/Luanda` - Africa/Luanda + * `Africa/Lubumbashi` - Africa/Lubumbashi + * `Africa/Lusaka` - Africa/Lusaka + * `Africa/Malabo` - Africa/Malabo + * `Africa/Maputo` - Africa/Maputo + * `Africa/Maseru` - Africa/Maseru + * `Africa/Mbabane` - Africa/Mbabane + * `Africa/Mogadishu` - Africa/Mogadishu + * `Africa/Monrovia` - Africa/Monrovia + * `Africa/Nairobi` - Africa/Nairobi + * `Africa/Ndjamena` - Africa/Ndjamena + * `Africa/Niamey` - Africa/Niamey + * `Africa/Nouakchott` - Africa/Nouakchott + * `Africa/Ouagadougou` - Africa/Ouagadougou + * `Africa/Porto-Novo` - Africa/Porto-Novo + * `Africa/Sao_Tome` - Africa/Sao_Tome + * `Africa/Tripoli` - Africa/Tripoli + * `Africa/Tunis` - Africa/Tunis + * `Africa/Windhoek` - Africa/Windhoek + * `America/Adak` - America/Adak + * `America/Anchorage` - America/Anchorage + * `America/Anguilla` - America/Anguilla + * `America/Antigua` - America/Antigua + * `America/Araguaina` - America/Araguaina + * `America/Argentina/Buenos_Aires` - America/Argentina/Buenos_Aires + * `America/Argentina/Catamarca` - America/Argentina/Catamarca + * `America/Argentina/Cordoba` - America/Argentina/Cordoba + * `America/Argentina/Jujuy` - America/Argentina/Jujuy + * `America/Argentina/La_Rioja` - America/Argentina/La_Rioja + * `America/Argentina/Mendoza` - America/Argentina/Mendoza + * `America/Argentina/Rio_Gallegos` - America/Argentina/Rio_Gallegos + * `America/Argentina/Salta` - America/Argentina/Salta + * `America/Argentina/San_Juan` - America/Argentina/San_Juan + * `America/Argentina/San_Luis` - America/Argentina/San_Luis + * `America/Argentina/Tucuman` - America/Argentina/Tucuman + * `America/Argentina/Ushuaia` - America/Argentina/Ushuaia + * `America/Aruba` - America/Aruba + * `America/Asuncion` - America/Asuncion + * `America/Atikokan` - America/Atikokan + * `America/Bahia` - America/Bahia + * `America/Bahia_Banderas` - America/Bahia_Banderas + * `America/Barbados` - America/Barbados + * `America/Belem` - America/Belem + * `America/Belize` - America/Belize + * `America/Blanc-Sablon` - America/Blanc-Sablon + * `America/Boa_Vista` - America/Boa_Vista + * `America/Bogota` - America/Bogota + * `America/Boise` - America/Boise + * `America/Cambridge_Bay` - America/Cambridge_Bay + * `America/Campo_Grande` - America/Campo_Grande + * `America/Cancun` - America/Cancun + * `America/Caracas` - America/Caracas + * `America/Cayenne` - America/Cayenne + * `America/Cayman` - America/Cayman + * `America/Chicago` - America/Chicago + * `America/Chihuahua` - America/Chihuahua + * `America/Ciudad_Juarez` - America/Ciudad_Juarez + * `America/Costa_Rica` - America/Costa_Rica + * `America/Coyhaique` - America/Coyhaique + * `America/Creston` - America/Creston + * `America/Cuiaba` - America/Cuiaba + * `America/Curacao` - America/Curacao + * `America/Danmarkshavn` - America/Danmarkshavn + * `America/Dawson` - America/Dawson + * `America/Dawson_Creek` - America/Dawson_Creek + * `America/Denver` - America/Denver + * `America/Detroit` - America/Detroit + * `America/Dominica` - America/Dominica + * `America/Edmonton` - America/Edmonton + * `America/Eirunepe` - America/Eirunepe + * `America/El_Salvador` - America/El_Salvador + * `America/Fort_Nelson` - America/Fort_Nelson + * `America/Fortaleza` - America/Fortaleza + * `America/Glace_Bay` - America/Glace_Bay + * `America/Goose_Bay` - America/Goose_Bay + * `America/Grand_Turk` - America/Grand_Turk + * `America/Grenada` - America/Grenada + * `America/Guadeloupe` - America/Guadeloupe + * `America/Guatemala` - America/Guatemala + * `America/Guayaquil` - America/Guayaquil + * `America/Guyana` - America/Guyana + * `America/Halifax` - America/Halifax + * `America/Havana` - America/Havana + * `America/Hermosillo` - America/Hermosillo + * `America/Indiana/Indianapolis` - America/Indiana/Indianapolis + * `America/Indiana/Knox` - America/Indiana/Knox + * `America/Indiana/Marengo` - America/Indiana/Marengo + * `America/Indiana/Petersburg` - America/Indiana/Petersburg + * `America/Indiana/Tell_City` - America/Indiana/Tell_City + * `America/Indiana/Vevay` - America/Indiana/Vevay + * `America/Indiana/Vincennes` - America/Indiana/Vincennes + * `America/Indiana/Winamac` - America/Indiana/Winamac + * `America/Inuvik` - America/Inuvik + * `America/Iqaluit` - America/Iqaluit + * `America/Jamaica` - America/Jamaica + * `America/Juneau` - America/Juneau + * `America/Kentucky/Louisville` - America/Kentucky/Louisville + * `America/Kentucky/Monticello` - America/Kentucky/Monticello + * `America/Kralendijk` - America/Kralendijk + * `America/La_Paz` - America/La_Paz + * `America/Lima` - America/Lima + * `America/Los_Angeles` - America/Los_Angeles + * `America/Lower_Princes` - America/Lower_Princes + * `America/Maceio` - America/Maceio + * `America/Managua` - America/Managua + * `America/Manaus` - America/Manaus + * `America/Marigot` - America/Marigot + * `America/Martinique` - America/Martinique + * `America/Matamoros` - America/Matamoros + * `America/Mazatlan` - America/Mazatlan + * `America/Menominee` - America/Menominee + * `America/Merida` - America/Merida + * `America/Metlakatla` - America/Metlakatla + * `America/Mexico_City` - America/Mexico_City + * `America/Miquelon` - America/Miquelon + * `America/Moncton` - America/Moncton + * `America/Monterrey` - America/Monterrey + * `America/Montevideo` - America/Montevideo + * `America/Montserrat` - America/Montserrat + * `America/Nassau` - America/Nassau + * `America/New_York` - America/New_York + * `America/Nome` - America/Nome + * `America/Noronha` - America/Noronha + * `America/North_Dakota/Beulah` - America/North_Dakota/Beulah + * `America/North_Dakota/Center` - America/North_Dakota/Center + * `America/North_Dakota/New_Salem` - America/North_Dakota/New_Salem + * `America/Nuuk` - America/Nuuk + * `America/Ojinaga` - America/Ojinaga + * `America/Panama` - America/Panama + * `America/Paramaribo` - America/Paramaribo + * `America/Phoenix` - America/Phoenix + * `America/Port-au-Prince` - America/Port-au-Prince + * `America/Port_of_Spain` - America/Port_of_Spain + * `America/Porto_Velho` - America/Porto_Velho + * `America/Puerto_Rico` - America/Puerto_Rico + * `America/Punta_Arenas` - America/Punta_Arenas + * `America/Rankin_Inlet` - America/Rankin_Inlet + * `America/Recife` - America/Recife + * `America/Regina` - America/Regina + * `America/Resolute` - America/Resolute + * `America/Rio_Branco` - America/Rio_Branco + * `America/Santarem` - America/Santarem + * `America/Santiago` - America/Santiago + * `America/Santo_Domingo` - America/Santo_Domingo + * `America/Sao_Paulo` - America/Sao_Paulo + * `America/Scoresbysund` - America/Scoresbysund + * `America/Sitka` - America/Sitka + * `America/St_Barthelemy` - America/St_Barthelemy + * `America/St_Johns` - America/St_Johns + * `America/St_Kitts` - America/St_Kitts + * `America/St_Lucia` - America/St_Lucia + * `America/St_Thomas` - America/St_Thomas + * `America/St_Vincent` - America/St_Vincent + * `America/Swift_Current` - America/Swift_Current + * `America/Tegucigalpa` - America/Tegucigalpa + * `America/Thule` - America/Thule + * `America/Tijuana` - America/Tijuana + * `America/Toronto` - America/Toronto + * `America/Tortola` - America/Tortola + * `America/Vancouver` - America/Vancouver + * `America/Whitehorse` - America/Whitehorse + * `America/Winnipeg` - America/Winnipeg + * `America/Yakutat` - America/Yakutat + * `Antarctica/Casey` - Antarctica/Casey + * `Antarctica/Davis` - Antarctica/Davis + * `Antarctica/DumontDUrville` - Antarctica/DumontDUrville + * `Antarctica/Macquarie` - Antarctica/Macquarie + * `Antarctica/Mawson` - Antarctica/Mawson + * `Antarctica/McMurdo` - Antarctica/McMurdo + * `Antarctica/Palmer` - Antarctica/Palmer + * `Antarctica/Rothera` - Antarctica/Rothera + * `Antarctica/Syowa` - Antarctica/Syowa + * `Antarctica/Troll` - Antarctica/Troll + * `Antarctica/Vostok` - Antarctica/Vostok + * `Arctic/Longyearbyen` - Arctic/Longyearbyen + * `Asia/Aden` - Asia/Aden + * `Asia/Almaty` - Asia/Almaty + * `Asia/Amman` - Asia/Amman + * `Asia/Anadyr` - Asia/Anadyr + * `Asia/Aqtau` - Asia/Aqtau + * `Asia/Aqtobe` - Asia/Aqtobe + * `Asia/Ashgabat` - Asia/Ashgabat + * `Asia/Atyrau` - Asia/Atyrau + * `Asia/Baghdad` - Asia/Baghdad + * `Asia/Bahrain` - Asia/Bahrain + * `Asia/Baku` - Asia/Baku + * `Asia/Bangkok` - Asia/Bangkok + * `Asia/Barnaul` - Asia/Barnaul + * `Asia/Beirut` - Asia/Beirut + * `Asia/Bishkek` - Asia/Bishkek + * `Asia/Brunei` - Asia/Brunei + * `Asia/Chita` - Asia/Chita + * `Asia/Colombo` - Asia/Colombo + * `Asia/Damascus` - Asia/Damascus + * `Asia/Dhaka` - Asia/Dhaka + * `Asia/Dili` - Asia/Dili + * `Asia/Dubai` - Asia/Dubai + * `Asia/Dushanbe` - Asia/Dushanbe + * `Asia/Famagusta` - Asia/Famagusta + * `Asia/Gaza` - Asia/Gaza + * `Asia/Hebron` - Asia/Hebron + * `Asia/Ho_Chi_Minh` - Asia/Ho_Chi_Minh + * `Asia/Hong_Kong` - Asia/Hong_Kong + * `Asia/Hovd` - Asia/Hovd + * `Asia/Irkutsk` - Asia/Irkutsk + * `Asia/Jakarta` - Asia/Jakarta + * `Asia/Jayapura` - Asia/Jayapura + * `Asia/Jerusalem` - Asia/Jerusalem + * `Asia/Kabul` - Asia/Kabul + * `Asia/Kamchatka` - Asia/Kamchatka + * `Asia/Karachi` - Asia/Karachi + * `Asia/Kathmandu` - Asia/Kathmandu + * `Asia/Khandyga` - Asia/Khandyga + * `Asia/Kolkata` - Asia/Kolkata + * `Asia/Krasnoyarsk` - Asia/Krasnoyarsk + * `Asia/Kuala_Lumpur` - Asia/Kuala_Lumpur + * `Asia/Kuching` - Asia/Kuching + * `Asia/Kuwait` - Asia/Kuwait + * `Asia/Macau` - Asia/Macau + * `Asia/Magadan` - Asia/Magadan + * `Asia/Makassar` - Asia/Makassar + * `Asia/Manila` - Asia/Manila + * `Asia/Muscat` - Asia/Muscat + * `Asia/Nicosia` - Asia/Nicosia + * `Asia/Novokuznetsk` - Asia/Novokuznetsk + * `Asia/Novosibirsk` - Asia/Novosibirsk + * `Asia/Omsk` - Asia/Omsk + * `Asia/Oral` - Asia/Oral + * `Asia/Phnom_Penh` - Asia/Phnom_Penh + * `Asia/Pontianak` - Asia/Pontianak + * `Asia/Pyongyang` - Asia/Pyongyang + * `Asia/Qatar` - Asia/Qatar + * `Asia/Qostanay` - Asia/Qostanay + * `Asia/Qyzylorda` - Asia/Qyzylorda + * `Asia/Riyadh` - Asia/Riyadh + * `Asia/Sakhalin` - Asia/Sakhalin + * `Asia/Samarkand` - Asia/Samarkand + * `Asia/Seoul` - Asia/Seoul + * `Asia/Shanghai` - Asia/Shanghai + * `Asia/Singapore` - Asia/Singapore + * `Asia/Srednekolymsk` - Asia/Srednekolymsk + * `Asia/Taipei` - Asia/Taipei + * `Asia/Tashkent` - Asia/Tashkent + * `Asia/Tbilisi` - Asia/Tbilisi + * `Asia/Tehran` - Asia/Tehran + * `Asia/Thimphu` - Asia/Thimphu + * `Asia/Tokyo` - Asia/Tokyo + * `Asia/Tomsk` - Asia/Tomsk + * `Asia/Ulaanbaatar` - Asia/Ulaanbaatar + * `Asia/Urumqi` - Asia/Urumqi + * `Asia/Ust-Nera` - Asia/Ust-Nera + * `Asia/Vientiane` - Asia/Vientiane + * `Asia/Vladivostok` - Asia/Vladivostok + * `Asia/Yakutsk` - Asia/Yakutsk + * `Asia/Yangon` - Asia/Yangon + * `Asia/Yekaterinburg` - Asia/Yekaterinburg + * `Asia/Yerevan` - Asia/Yerevan + * `Atlantic/Azores` - Atlantic/Azores + * `Atlantic/Bermuda` - Atlantic/Bermuda + * `Atlantic/Canary` - Atlantic/Canary + * `Atlantic/Cape_Verde` - Atlantic/Cape_Verde + * `Atlantic/Faroe` - Atlantic/Faroe + * `Atlantic/Madeira` - Atlantic/Madeira + * `Atlantic/Reykjavik` - Atlantic/Reykjavik + * `Atlantic/South_Georgia` - Atlantic/South_Georgia + * `Atlantic/St_Helena` - Atlantic/St_Helena + * `Atlantic/Stanley` - Atlantic/Stanley + * `Australia/Adelaide` - Australia/Adelaide + * `Australia/Brisbane` - Australia/Brisbane + * `Australia/Broken_Hill` - Australia/Broken_Hill + * `Australia/Darwin` - Australia/Darwin + * `Australia/Eucla` - Australia/Eucla + * `Australia/Hobart` - Australia/Hobart + * `Australia/Lindeman` - Australia/Lindeman + * `Australia/Lord_Howe` - Australia/Lord_Howe + * `Australia/Melbourne` - Australia/Melbourne + * `Australia/Perth` - Australia/Perth + * `Australia/Sydney` - Australia/Sydney + * `Europe/Amsterdam` - Europe/Amsterdam + * `Europe/Andorra` - Europe/Andorra + * `Europe/Astrakhan` - Europe/Astrakhan + * `Europe/Athens` - Europe/Athens + * `Europe/Belgrade` - Europe/Belgrade + * `Europe/Berlin` - Europe/Berlin + * `Europe/Bratislava` - Europe/Bratislava + * `Europe/Brussels` - Europe/Brussels + * `Europe/Bucharest` - Europe/Bucharest + * `Europe/Budapest` - Europe/Budapest + * `Europe/Busingen` - Europe/Busingen + * `Europe/Chisinau` - Europe/Chisinau + * `Europe/Copenhagen` - Europe/Copenhagen + * `Europe/Dublin` - Europe/Dublin + * `Europe/Gibraltar` - Europe/Gibraltar + * `Europe/Guernsey` - Europe/Guernsey + * `Europe/Helsinki` - Europe/Helsinki + * `Europe/Isle_of_Man` - Europe/Isle_of_Man + * `Europe/Istanbul` - Europe/Istanbul + * `Europe/Jersey` - Europe/Jersey + * `Europe/Kaliningrad` - Europe/Kaliningrad + * `Europe/Kirov` - Europe/Kirov + * `Europe/Kyiv` - Europe/Kyiv + * `Europe/Lisbon` - Europe/Lisbon + * `Europe/Ljubljana` - Europe/Ljubljana + * `Europe/London` - Europe/London + * `Europe/Luxembourg` - Europe/Luxembourg + * `Europe/Madrid` - Europe/Madrid + * `Europe/Malta` - Europe/Malta + * `Europe/Mariehamn` - Europe/Mariehamn + * `Europe/Minsk` - Europe/Minsk + * `Europe/Monaco` - Europe/Monaco + * `Europe/Moscow` - Europe/Moscow + * `Europe/Oslo` - Europe/Oslo + * `Europe/Paris` - Europe/Paris + * `Europe/Podgorica` - Europe/Podgorica + * `Europe/Prague` - Europe/Prague + * `Europe/Riga` - Europe/Riga + * `Europe/Rome` - Europe/Rome + * `Europe/Samara` - Europe/Samara + * `Europe/San_Marino` - Europe/San_Marino + * `Europe/Sarajevo` - Europe/Sarajevo + * `Europe/Saratov` - Europe/Saratov + * `Europe/Simferopol` - Europe/Simferopol + * `Europe/Skopje` - Europe/Skopje + * `Europe/Sofia` - Europe/Sofia + * `Europe/Stockholm` - Europe/Stockholm + * `Europe/Tallinn` - Europe/Tallinn + * `Europe/Tirane` - Europe/Tirane + * `Europe/Ulyanovsk` - Europe/Ulyanovsk + * `Europe/Vaduz` - Europe/Vaduz + * `Europe/Vatican` - Europe/Vatican + * `Europe/Vienna` - Europe/Vienna + * `Europe/Vilnius` - Europe/Vilnius + * `Europe/Volgograd` - Europe/Volgograd + * `Europe/Warsaw` - Europe/Warsaw + * `Europe/Zagreb` - Europe/Zagreb + * `Europe/Zurich` - Europe/Zurich + * `GMT` - GMT + * `Indian/Antananarivo` - Indian/Antananarivo + * `Indian/Chagos` - Indian/Chagos + * `Indian/Christmas` - Indian/Christmas + * `Indian/Cocos` - Indian/Cocos + * `Indian/Comoro` - Indian/Comoro + * `Indian/Kerguelen` - Indian/Kerguelen + * `Indian/Mahe` - Indian/Mahe + * `Indian/Maldives` - Indian/Maldives + * `Indian/Mauritius` - Indian/Mauritius + * `Indian/Mayotte` - Indian/Mayotte + * `Indian/Reunion` - Indian/Reunion + * `Pacific/Apia` - Pacific/Apia + * `Pacific/Auckland` - Pacific/Auckland + * `Pacific/Bougainville` - Pacific/Bougainville + * `Pacific/Chatham` - Pacific/Chatham + * `Pacific/Chuuk` - Pacific/Chuuk + * `Pacific/Easter` - Pacific/Easter + * `Pacific/Efate` - Pacific/Efate + * `Pacific/Fakaofo` - Pacific/Fakaofo + * `Pacific/Fiji` - Pacific/Fiji + * `Pacific/Funafuti` - Pacific/Funafuti + * `Pacific/Galapagos` - Pacific/Galapagos + * `Pacific/Gambier` - Pacific/Gambier + * `Pacific/Guadalcanal` - Pacific/Guadalcanal + * `Pacific/Guam` - Pacific/Guam + * `Pacific/Honolulu` - Pacific/Honolulu + * `Pacific/Kanton` - Pacific/Kanton + * `Pacific/Kiritimati` - Pacific/Kiritimati + * `Pacific/Kosrae` - Pacific/Kosrae + * `Pacific/Kwajalein` - Pacific/Kwajalein + * `Pacific/Majuro` - Pacific/Majuro + * `Pacific/Marquesas` - Pacific/Marquesas + * `Pacific/Midway` - Pacific/Midway + * `Pacific/Nauru` - Pacific/Nauru + * `Pacific/Niue` - Pacific/Niue + * `Pacific/Norfolk` - Pacific/Norfolk + * `Pacific/Noumea` - Pacific/Noumea + * `Pacific/Pago_Pago` - Pacific/Pago_Pago + * `Pacific/Palau` - Pacific/Palau + * `Pacific/Pitcairn` - Pacific/Pitcairn + * `Pacific/Pohnpei` - Pacific/Pohnpei + * `Pacific/Port_Moresby` - Pacific/Port_Moresby + * `Pacific/Rarotonga` - Pacific/Rarotonga + * `Pacific/Saipan` - Pacific/Saipan + * `Pacific/Tahiti` - Pacific/Tahiti + * `Pacific/Tarawa` - Pacific/Tarawa + * `Pacific/Tongatapu` - Pacific/Tongatapu + * `Pacific/Wake` - Pacific/Wake + * `Pacific/Wallis` - Pacific/Wallis + * `UTC` - UTC + oneOf: + - $ref: '#/components/schemas/LocalTimezoneEnum' + - $ref: '#/components/schemas/BlankEnum' + - $ref: '#/components/schemas/NullEnum' + bootswatch_theme: + type: integer + nullable: true + description: Bootswatch theme for light mode + Vendor: + type: object + properties: + id: + type: integer + readOnly: true + name: + type: string + legal_name: + type: string + abbreviated_name: + type: string + overview: + type: string + history: + type: string + services_provided: + type: string + vendor_type: + type: string + employee_count: + type: integer + nullable: true + revenue: + type: string + format: decimal + pattern: ^-?\d{0,13}(?:\.\d{0,2})?$ + nullable: true + assets: + type: string + format: decimal + pattern: ^-?\d{0,13}(?:\.\d{0,2})?$ + nullable: true + fiscal_year_end: + type: string + format: date + nullable: true + partner_portal_url: + type: string + format: uri + is_competitor: + type: boolean + deal_registration_notes: + type: string + parent_organization: + type: string + readOnly: true + parent_relationship_type: + type: string + readOnly: true + is_multi_role_organization: + type: string + readOnly: true + child_organizations_count: + type: string + readOnly: true + required: + - child_organizations_count + - id + - is_multi_role_organization + - parent_organization + - parent_relationship_type + VendorRequest: + type: object + properties: + name: + type: string + legal_name: + type: string + abbreviated_name: + type: string + overview: + type: string + history: + type: string + services_provided: + type: string + vendor_type: + type: string + employee_count: + type: integer + nullable: true + revenue: + type: string + format: decimal + pattern: ^-?\d{0,13}(?:\.\d{0,2})?$ + nullable: true + assets: + type: string + format: decimal + pattern: ^-?\d{0,13}(?:\.\d{0,2})?$ + nullable: true + fiscal_year_end: + type: string + format: date + nullable: true + partner_portal_url: + type: string + format: uri + is_competitor: + type: boolean + deal_registration_notes: + type: string + VendorSolution: + type: object + properties: + id: + type: integer + readOnly: true + name: + type: string + maxLength: 255 + vendor: + allOf: + - $ref: '#/components/schemas/Vendor' + readOnly: true + description: + type: string + title: Solution Information + description: Detailed solution description with URL support + url: + type: string + format: uri + maxLength: 200 + notes: + type: string + description: Additional notes about the solution + category: + type: string + maxLength: 100 + release_date: + type: string + format: date + nullable: true + end_of_life_date: + type: string + format: date + nullable: true + documentation_url: + type: string + format: uri + maxLength: 200 + created_at: + type: string + format: date-time + readOnly: true + updated_at: + type: string + format: date-time + readOnly: true + required: + - created_at + - id + - name + - updated_at + - vendor + VendorSolutionRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 255 + vendor_id: + type: integer + writeOnly: true + description: + type: string + minLength: 1 + title: Solution Information + description: Detailed solution description with URL support + url: + type: string + format: uri + maxLength: 200 + notes: + type: string + minLength: 1 + description: Additional notes about the solution + category: + type: string + maxLength: 100 + release_date: + type: string + format: date + nullable: true + end_of_life_date: + type: string + format: date + nullable: true + documentation_url: + type: string + format: uri + maxLength: 200 + required: + - name + - vendor_id + Workshop: + type: object + properties: + id: + type: integer + readOnly: true + name: + type: string + maxLength: 255 + date: + type: string + format: date + time: + type: string + format: time + duration: + allOf: + - $ref: '#/components/schemas/DurationEnum' + minimum: -2147483648 + maximum: 2147483647 + workshop_type: + $ref: '#/components/schemas/WorkshopTypeEnum' + participants: + type: array + items: + $ref: '#/components/schemas/Contact' + readOnly: true + notes: + type: string + required: + - date + - duration + - id + - name + - participants + - time + - workshop_type + WorkshopRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 255 + date: + type: string + format: date + time: + type: string + format: time + duration: + allOf: + - $ref: '#/components/schemas/DurationEnum' + minimum: -2147483648 + maximum: 2147483647 + workshop_type: + $ref: '#/components/schemas/WorkshopTypeEnum' + participant_ids: + type: array + items: + type: integer + writeOnly: true + notes: + type: string + required: + - date + - duration + - name + - time + - workshop_type + WorkshopTypeEnum: + enum: + - Exploration + - Discovery + - Prioritization + - Maturity + type: string + description: |- + * `Exploration` - Exploration + * `Discovery` - Discovery + * `Prioritization` - Prioritization + * `Maturity` - Maturity + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: 'API key authentication. Use format: Api-Key {your-api-key}' +servers: +- url: http://localhost:8000 + description: Development server +- url: https://api.athena.example.com + description: Production server +tags: +- name: Core + description: Core system functionality +- name: Orbit + description: Business relationship management +- name: Location Tools + description: Client location management with automatic version numbering +- name: Location Sets + description: Versioned location collections with automatic numbering +- name: Locations + description: Individual location records with custom fields +- name: Custom Fields + description: Custom field definitions for location data +- name: Table Sources + description: Location data table source configurations +- name: CSV Operations + description: Bulk import/export operations for location data +- name: Engagement + description: Client engagement management +- name: Advisor + description: Advisory tools and recommendations +- name: BMC + description: Business Model Canvas tools +- name: Pricing + description: Pricing and cost management +- name: Gallery + description: Media and document management +- name: RAID + description: Risk, Assumption, Issue, Dependency tracking +- name: Stakeholder + description: Stakeholder management +- name: Timeline + description: Timeline and milestone management diff --git a/palladium/__init__.py b/palladium/__init__.py new file mode 100644 index 0000000..a3d438c --- /dev/null +++ b/palladium/__init__.py @@ -0,0 +1,7 @@ +""" +Palladium CLI shim — exposes ``python -m palladium``. + +The actual logic lives in :mod:`core.cli.main`. This package exists so the +README's command line interface (``python -m palladium test``) works without +clashing with the top-level project name. +""" diff --git a/palladium/__main__.py b/palladium/__main__.py new file mode 100644 index 0000000..567d573 --- /dev/null +++ b/palladium/__main__.py @@ -0,0 +1,6 @@ +"""``python -m palladium`` entrypoint.""" + +from core.cli.main import main + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..2383f0b --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,49 @@ +[build-system] +requires = ["setuptools>=68"] +build-backend = "setuptools.build_meta" + +[project] +name = "palladium" +version = "0.1.0" +description = "TEI (Total Economic Impact) Calculator — Palladium" +readme = "README.md" +requires-python = ">=3.11" +license = {file = "LICENSE"} +authors = [{name = "NTT Data"}] +dependencies = [ + "requests>=2.31", + "python-dotenv>=1.0", + "pandas>=2.0", + "plotly>=5.18", + "numpy>=1.26", +] + +[project.optional-dependencies] +notebooks = ["jupyter>=1.0", "ipython>=8.0"] +app = ["streamlit>=1.30"] +dev = ["pytest>=7.4", "ruff>=0.1"] + +[project.scripts] +palladium = "core.cli.main:main" + +[tool.setuptools.packages.find] +include = ["core*", "palladium*"] +exclude = ["tests*", "studies*", "app*", "docs*"] + +[tool.pytest.ini_options] +testpaths = ["tests"] +python_files = ["test_*.py"] +addopts = "-q" + +[tool.ruff] +line-length = 100 +target-version = "py311" + +[tool.ruff.lint] +select = ["E", "F", "I", "B", "UP", "W"] +ignore = ["E501"] # line length handled by formatter + +[tool.ruff.lint.per-file-ignores] +"studies/*/notebooks/*.ipynb" = ["E402"] +"tests/*" = ["F401"] +"app/main.py" = ["E402"] # sys.path bootstrap before app imports diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..5daa3c0 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,9 @@ +requests>=2.31 +python-dotenv>=1.0 +jupyter>=1.0 +streamlit>=1.30 +pandas>=2.0 +plotly>=5.18 +numpy>=1.26 +pytest>=7.4 +ruff>=0.1 diff --git a/studies/202602_AmazonConnect/README.md b/studies/202602_AmazonConnect/README.md new file mode 100644 index 0000000..49ce756 --- /dev/null +++ b/studies/202602_AmazonConnect/README.md @@ -0,0 +1,71 @@ +# 202602 — Amazon Connect TEI + +Self-contained TEI study folder. All data, notebooks, and exports for the +Forrester *Total Economic Impact™ Of Amazon Connect* (February 2026, +commissioned by AWS) live here. + +## Source + +The full Forrester study is at [`docs/202602_TEI Report Amazon Connect.pdf`](docs/202602_TEI%20Report%20Amazon%20Connect.pdf). + +Key composite numbers reproduced in `seed_data.py`: + +| Metric | Value | +|---|---| +| ROI | **342%** | +| NPV | **$78.7M** | +| Benefits PV | $101.7M | +| Costs PV | $23.0M | +| Payback | <6 months | +| Discount rate | 10% | +| Analysis period | 3 years | + +## Composite organization + +* Global B2C, ~$10B revenue (Y1), 30% YoY growth +* 2,000 contact-center agents, 200 supervisors +* 20M annual contacts (75% calls, 25% chat) +* 10-min average handle time + +## Layout + +``` +202602_AmazonConnect/ +├── README.md ← this file +├── config.py ← TOOL_PUBLIC_ID, REPORT_PUBLIC_ID, study slug +├── seed_data.py ← BENEFITS, COSTS, ASSUMPTIONS as Python dicts +├── notebooks/ +│ ├── 01_benefits.ipynb ← quantify the 5 benefits, push to Athena +│ ├── 02_costs.ipynb ← quantify the 3 costs +│ ├── 03_business_case.ipynb ← /calculate, charts, scenarios +│ └── 04_export.ipynb ← /export → exports/export.json +├── exports/ ← generated; .gitignored +└── docs/ + └── 202602_TEI Report Amazon Connect.pdf +``` + +## Workflow + +1. **Set up credentials** in the project root `.env` (see `.env.example`). +2. **Create / link the TEI tool** in Athena, then put its `public_id` in + [`config.py`](config.py). +3. **Open `notebooks/01_benefits.ipynb`** and run all — pushes the 5 + benefit rows from `seed_data.py` into Athena. +4. **`02_costs.ipynb`** — pushes the 3 cost rows. +5. **`03_business_case.ipynb`** — calls `/calculate`, renders the cash + flow chart, runs scenario analysis. Should reproduce the PDF's + $78.7M NPV / 342% ROI. +6. **`04_export.ipynb`** — writes `exports/export.json` for the report + pipeline. + +## Adding a new study + +Copy this folder, rename to `YYYYMM_`, and: + +1. Replace `seed_data.py` with your benefits/costs. +2. Update `config.py` with the new tool/report public IDs. +3. Tweak the notebooks' narrative; the helper imports are the same. + +The only thing that changes between studies is the **data** and the +**narrative prose** in the notebooks. All math, charts, and API calls +come from `core/`. diff --git a/studies/202602_AmazonConnect/__init__.py b/studies/202602_AmazonConnect/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/studies/202602_AmazonConnect/config.py b/studies/202602_AmazonConnect/config.py new file mode 100644 index 0000000..93e764a --- /dev/null +++ b/studies/202602_AmazonConnect/config.py @@ -0,0 +1,39 @@ +""" +Study configuration for the Amazon Connect TEI (February 2026). + +Set ``TOOL_PUBLIC_ID`` to the public_id of the live TEI tool instance in +Athena once it has been created. ``REPORT_PUBLIC_ID`` is the template +this tool was created from (Athena admin sets up Report templates). + +Until both are filled in, the notebooks fall back to local-only mode: +they compute summaries from ``seed_data.py`` using ``core.calculations`` +and skip the network round-trip. +""" + +from __future__ import annotations + +import os + +#: Human-friendly study identifier — used in export metadata + filenames. +STUDY_SLUG = "202602_AmazonConnect" + +#: TEI Report template public_id (12-char short UUID). Provisioned in +#: Athena admin → TEI → Reports. +REPORT_PUBLIC_ID: str = os.getenv("PALLADIUM_REPORT_PUBLIC_ID", "") + +#: TEI Tool instance public_id. Created via the API +#: (``client.create_tool``) or the Streamlit app sidebar. +TOOL_PUBLIC_ID: str = os.getenv("PALLADIUM_TOOL_PUBLIC_ID", "") + +#: Default discount rate used for local validation of the study numbers. +DISCOUNT_RATE = 0.10 + +#: Analysis horizon (years). +ANALYSIS_YEARS = 3 + +#: Optional Athena Proposal ID this tool is linked to (when known). +PROPOSAL_ID: int | None = ( + int(os.environ["PALLADIUM_PROPOSAL_ID"]) + if os.getenv("PALLADIUM_PROPOSAL_ID") + else None +) diff --git a/studies/202602_AmazonConnect/docs/202602_TEI Report Amazon Connect.pdf b/studies/202602_AmazonConnect/docs/202602_TEI Report Amazon Connect.pdf new file mode 100644 index 0000000..9150966 Binary files /dev/null and b/studies/202602_AmazonConnect/docs/202602_TEI Report Amazon Connect.pdf differ diff --git a/studies/202602_AmazonConnect/exports/.gitkeep b/studies/202602_AmazonConnect/exports/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/studies/202602_AmazonConnect/notebooks/01_benefits.ipynb b/studies/202602_AmazonConnect/notebooks/01_benefits.ipynb new file mode 100644 index 0000000..90208d9 --- /dev/null +++ b/studies/202602_AmazonConnect/notebooks/01_benefits.ipynb @@ -0,0 +1,269 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "231c773a", + "metadata": {}, + "source": [ + "# 01 — Benefits Analysis\n", + "\n", + "**Study:** Forrester *Total Economic Impact™ Of Amazon Connect* (Feb 2026)\n", + "\n", + "Quantify the five benefit categories Forrester identified for the\n", + "composite organization, push them into Athena, and verify the totals\n", + "match the published study (Benefits PV ≈ **$101.7M**)." + ] + }, + { + "cell_type": "markdown", + "id": "110d7e61", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "We add the project root to `sys.path` so the notebook can import `core` and\n", + "the study's local modules without `pip install -e .`." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "c83c2758", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Project root: /home/robert/notebook/git/palladium\n", + "Study root: /home/robert/notebook/git/palladium/studies/202602_AmazonConnect\n" + ] + } + ], + "source": [ + "import sys\n", + "from pathlib import Path\n", + "\n", + "ROOT = Path.cwd().resolve()\n", + "while ROOT != ROOT.parent and not (ROOT / 'core').is_dir():\n", + " ROOT = ROOT.parent\n", + "if str(ROOT) not in sys.path:\n", + " sys.path.insert(0, str(ROOT))\n", + "\n", + "STUDY = ROOT / 'studies' / '202602_AmazonConnect'\n", + "if str(STUDY) not in sys.path:\n", + " sys.path.insert(0, str(STUDY))\n", + "print(f'Project root: {ROOT}')\n", + "print(f'Study root: {STUDY}')" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "c371ef85", + "metadata": {}, + "outputs": [ + { + "ename": "ModuleNotFoundError", + "evalue": "No module named 'pandas'", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mModuleNotFoundError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[2]\u001b[39m\u001b[32m, line 4\u001b[39m\n\u001b[32m 1\u001b[39m \u001b[38;5;28;01mimport\u001b[39;00m config\n\u001b[32m 2\u001b[39m \u001b[38;5;28;01mimport\u001b[39;00m seed_data\n\u001b[32m 3\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m core.calculations \u001b[38;5;28;01mimport\u001b[39;00m npv, risk_adjust_benefit\n\u001b[32m----> \u001b[39m\u001b[32m4\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m core.notebook_helpers \u001b[38;5;28;01mimport\u001b[39;00m charts, display, tables\n\u001b[32m 5\u001b[39m \n\u001b[32m 6\u001b[39m display.alert(\n\u001b[32m 7\u001b[39m f'Study: {config.STUDY_SLUG} • discount rate {config.DISCOUNT_RATE:.0%} '\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/notebook/git/palladium/core/notebook_helpers/__init__.py:3\u001b[39m\n\u001b[32m 1\u001b[39m \u001b[33;03m\"\"\"Notebook helpers — pandas tables, plotly charts, IPython display.\"\"\"\u001b[39;00m\n\u001b[32m----> \u001b[39m\u001b[32m3\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mcore\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mnotebook_helpers\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m charts, display, tables\n\u001b[32m 5\u001b[39m __all__ = [\u001b[33m\"\u001b[39m\u001b[33mcharts\u001b[39m\u001b[33m\"\u001b[39m, \u001b[33m\"\u001b[39m\u001b[33mdisplay\u001b[39m\u001b[33m\"\u001b[39m, \u001b[33m\"\u001b[39m\u001b[33mtables\u001b[39m\u001b[33m\"\u001b[39m]\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/notebook/git/palladium/core/notebook_helpers/tables.py:13\u001b[39m\n\u001b[32m 9\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01m__future__\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m annotations\n\u001b[32m 11\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mtyping\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m Any, Iterable\n\u001b[32m---> \u001b[39m\u001b[32m13\u001b[39m \u001b[38;5;28;01mimport\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mpandas\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mas\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mpd\u001b[39;00m\n\u001b[32m 15\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mcore\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mcalculations\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m risk_adjust_benefit, risk_adjust_cost\n\u001b[32m 18\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34m_years_in_data\u001b[39m(items: Iterable[\u001b[38;5;28mdict\u001b[39m]) -> \u001b[38;5;28mlist\u001b[39m[\u001b[38;5;28mint\u001b[39m]:\n", + "\u001b[31mModuleNotFoundError\u001b[39m: No module named 'pandas'" + ] + } + ], + "source": [ + "import config\n", + "import seed_data\n", + "from core.calculations import npv, risk_adjust_benefit\n", + "from core.notebook_helpers import charts, display, tables\n", + "\n", + "display.alert(\n", + " f'Study: {config.STUDY_SLUG} • discount rate {config.DISCOUNT_RATE:.0%} '\n", + " f'• {config.ANALYSIS_YEARS}-year horizon',\n", + " 'info',\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "fd94503d", + "metadata": {}, + "source": [ + "## Benefits — nominal & risk-adjusted\n", + "\n", + "Forrester quantifies five benefit categories:\n", + "\n", + "| Ref | Benefit | Y1 | Y2 | Y3 | Risk Adj |\n", + "|---|---|---|---|---|---|\n", + "| At | AI-driven contact resolution efficiency | $13.9M | $23.9M | $37.8M | 15% |\n", + "| Bt | AI-powered content & sentiment analysis | $4.6M | $5.4M | $6.3M | 15% |\n", + "| Ct | AI-enabled forecasting & supervision | $6.7M | $9.1M | $12.4M | 15% |\n", + "| Dt | Data-driven profit lift (conversion +20%) | $1.2M | $1.6M | $2.0M | 20% |\n", + "| Et | Legacy solution cost savings | $6.2M | $8.0M | $10.4M | 20% |\n", + "\n", + "All five are seeded in `seed_data.BENEFITS` with full source notes." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6177ea7c", + "metadata": {}, + "outputs": [], + "source": [ + "df = tables.benefits_table(seed_data.BENEFITS)\n", + "df.style.format({col: '${:,.0f}' for col in df.columns if col not in ('field_key','label','category','risk_adjustment')})" + ] + }, + { + "cell_type": "markdown", + "id": "573f12d8", + "metadata": {}, + "source": [ + "## Local validation against the PDF\n", + "\n", + "Re-derive the per-benefit risk-adjusted PV and confirm we land on Forrester's\n", + "**$101,696,791** total within rounding." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8cf32003", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "\n", + "rows = []\n", + "for b in seed_data.BENEFITS:\n", + " rf = b['risk_adjustment']\n", + " yr = [b['year_values'][str(y)] for y in (1, 2, 3)]\n", + " yr_ra = [risk_adjust_benefit(v, rf) for v in yr]\n", + " pv = npv(yr_ra, config.DISCOUNT_RATE)\n", + " rows.append({\n", + " 'Benefit': b['label'],\n", + " 'Y1 (RA)': yr_ra[0],\n", + " 'Y2 (RA)': yr_ra[1],\n", + " 'Y3 (RA)': yr_ra[2],\n", + " 'PV': pv,\n", + " })\n", + "df_check = pd.DataFrame(rows)\n", + "df_check.loc[len(df_check)] = ['TOTAL', df_check['Y1 (RA)'].sum(), df_check['Y2 (RA)'].sum(), df_check['Y3 (RA)'].sum(), df_check['PV'].sum()]\n", + "df_check.style.format({c: '${:,.0f}' for c in df_check.columns if c != 'Benefit'})" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3ded50c8", + "metadata": {}, + "outputs": [], + "source": [ + "expected_pv = 101_696_791\n", + "computed_pv = df_check.iloc[-1]['PV']\n", + "delta = computed_pv - expected_pv\n", + "kind = 'success' if abs(delta) < 1_000 else 'warning'\n", + "display.alert(\n", + " f'Computed Benefits PV: ${computed_pv:,.0f}
'\n", + " f'Forrester target: ${expected_pv:,.0f}
'\n", + " f'Δ = ${delta:,.0f} (rounding)',\n", + " kind,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "a5ad453a", + "metadata": {}, + "source": [ + "## Visualize\n", + "\n", + "Horizontal bar chart of risk-adjusted three-year totals — mirrors the PDF p.6\n", + "*Benefits (Three-Year)* graphic." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "452b8408", + "metadata": {}, + "outputs": [], + "source": [ + "charts.benefits_bar(seed_data.BENEFITS).show()" + ] + }, + { + "cell_type": "markdown", + "id": "1c4591f5", + "metadata": {}, + "source": [ + "## Push to Athena\n", + "\n", + "When `config.TOOL_PUBLIC_ID` is set, persist the seed values to the live\n", + "TEI tool. Otherwise this cell is a no-op so the notebook still runs\n", + "offline." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d10a54b6", + "metadata": {}, + "outputs": [], + "source": [ + "if config.TOOL_PUBLIC_ID:\n", + " from core.tei_client import TEIClient\n", + "\n", + " client = TEIClient()\n", + " result = client.update_values(config.TOOL_PUBLIC_ID, seed_data.BENEFITS)\n", + " display.alert(f'Pushed {len(seed_data.BENEFITS)} benefit rows to '\n", + " f'tool {config.TOOL_PUBLIC_ID}.', 'success')\n", + "else:\n", + " display.alert(\n", + " 'No TOOL_PUBLIC_ID set in config.py — skipped Athena push. '\n", + " 'Set PALLADIUM_TOOL_PUBLIC_ID in your environment '\n", + " 'or edit config.py to enable.',\n", + " 'info',\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "78693c14", + "metadata": {}, + "source": [ + "---\n", + "\n", + "Continue with [`02_costs.ipynb`](02_costs.ipynb) →" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/studies/202602_AmazonConnect/notebooks/02_costs.ipynb b/studies/202602_AmazonConnect/notebooks/02_costs.ipynb new file mode 100644 index 0000000..e4ea7aa --- /dev/null +++ b/studies/202602_AmazonConnect/notebooks/02_costs.ipynb @@ -0,0 +1,213 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "1a76b7ed", + "metadata": {}, + "source": [ + "# 02 — Costs Analysis\n", + "\n", + "**Study:** Forrester TEI™ Of Amazon Connect (Feb 2026)\n", + "\n", + "Three cost categories, three-year horizon, 10% discount rate.\n", + "Target risk-adjusted PV = **$22,983,076**." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "46446223", + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "from pathlib import Path\n", + "\n", + "ROOT = Path.cwd().resolve()\n", + "while ROOT != ROOT.parent and not (ROOT / 'core').is_dir():\n", + " ROOT = ROOT.parent\n", + "if str(ROOT) not in sys.path:\n", + " sys.path.insert(0, str(ROOT))\n", + "STUDY = ROOT / 'studies' / '202602_AmazonConnect'\n", + "if str(STUDY) not in sys.path:\n", + " sys.path.insert(0, str(STUDY))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4ec64198", + "metadata": {}, + "outputs": [], + "source": [ + "import config\n", + "import seed_data\n", + "from core.calculations import npv, risk_adjust_cost\n", + "from core.notebook_helpers import charts, display, tables" + ] + }, + { + "cell_type": "markdown", + "id": "26f1d385", + "metadata": {}, + "source": [ + "## Costs — nominal & risk-adjusted\n", + "\n", + "| Ref | Cost | Initial | Y1 | Y2 | Y3 | Risk Adj |\n", + "|---|---|---|---|---|---|---|\n", + "| Ft | Amazon Connect usage | — | $6.5M | $8.0M | $9.8M | ↑5% |\n", + "| Gt | Implementation & migration | $1.09M | $188K | $188K | — | ↑10% |\n", + "| Ht | Ongoing management | — | $256K | $187K | $187K | ↑15% |\n", + "\n", + "Note **costs are risk-adjusted *upward*** (higher risk → higher modelled cost)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9635f334", + "metadata": {}, + "outputs": [], + "source": [ + "df = tables.costs_table(seed_data.COSTS)\n", + "df.style.format({c: '${:,.0f}' for c in df.columns if c not in ('field_key','label','category','risk_adjustment')})" + ] + }, + { + "cell_type": "markdown", + "id": "0667d1da", + "metadata": {}, + "source": [ + "## Local validation\n", + "\n", + "Reproduce the **$22,983,076** Costs PV from the PDF Cash Flow Analysis." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3e35a794", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "\n", + "rows = []\n", + "for c in seed_data.COSTS:\n", + " rf = c['risk_adjustment']\n", + " init_ra = risk_adjust_cost(c.get('initial') or 0, rf)\n", + " yr = [c['year_values'][str(y)] for y in (1, 2, 3)]\n", + " yr_ra = [risk_adjust_cost(v, rf) for v in yr]\n", + " pv = npv(yr_ra, config.DISCOUNT_RATE, initial=init_ra)\n", + " rows.append({\n", + " 'Cost': c['label'],\n", + " 'Initial (RA)': init_ra,\n", + " 'Y1 (RA)': yr_ra[0],\n", + " 'Y2 (RA)': yr_ra[1],\n", + " 'Y3 (RA)': yr_ra[2],\n", + " 'PV': pv,\n", + " })\n", + "df_check = pd.DataFrame(rows)\n", + "totals = df_check.drop(columns='Cost').sum()\n", + "df_check.loc[len(df_check)] = ['TOTAL'] + totals.tolist()\n", + "df_check.style.format({c: '${:,.0f}' for c in df_check.columns if c != 'Cost'})" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4109784e", + "metadata": {}, + "outputs": [], + "source": [ + "expected_pv = 22_983_076\n", + "computed_pv = df_check.iloc[-1]['PV']\n", + "delta = computed_pv - expected_pv\n", + "kind = 'success' if abs(delta) < 1_000 else 'warning'\n", + "display.alert(\n", + " f'Computed Costs PV: ${computed_pv:,.0f}
'\n", + " f'Forrester target: ${expected_pv:,.0f}
'\n", + " f'Δ = ${delta:,.0f}',\n", + " kind,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "dd1b3c04", + "metadata": {}, + "source": [ + "## Cost mix\n", + "\n", + "Most of the three-year cost (~90%) is Amazon Connect *usage* (Ft) —\n", + "consistent with the PDF's framing that consumption-based pricing dominates,\n", + "with implementation a one-time investment." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "90e9b5e2", + "metadata": {}, + "outputs": [], + "source": [ + "charts.cost_breakdown_pie(seed_data.COSTS).show()" + ] + }, + { + "cell_type": "markdown", + "id": "3d15ae10", + "metadata": {}, + "source": [ + "## Push to Athena" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "03547040", + "metadata": {}, + "outputs": [], + "source": [ + "if config.TOOL_PUBLIC_ID:\n", + " from core.tei_client import TEIClient\n", + "\n", + " client = TEIClient()\n", + " client.update_values(config.TOOL_PUBLIC_ID, seed_data.COSTS)\n", + " display.alert(f'Pushed {len(seed_data.COSTS)} cost rows to '\n", + " f'tool {config.TOOL_PUBLIC_ID}.', 'success')\n", + "else:\n", + " display.alert('No TOOL_PUBLIC_ID set — skipped Athena push.', 'info')" + ] + }, + { + "cell_type": "markdown", + "id": "6f5befbb", + "metadata": {}, + "source": [ + "Continue with [`03_business_case.ipynb`](03_business_case.ipynb) →" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/studies/202602_AmazonConnect/notebooks/03_business_case.ipynb b/studies/202602_AmazonConnect/notebooks/03_business_case.ipynb new file mode 100644 index 0000000..1ed2c87 --- /dev/null +++ b/studies/202602_AmazonConnect/notebooks/03_business_case.ipynb @@ -0,0 +1,221 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 03 — Business Case\n", + "\n", + "Combine the benefits and costs into the consolidated TEI summary,\n", + "render the Cash Flow chart, and run scenario analysis. This notebook\n", + "should reproduce the headline numbers from the PDF Financial Summary:\n", + "\n", + "* **NPV $78.7M • ROI 342% • Payback <6 months**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "from pathlib import Path\n", + "\n", + "ROOT = Path.cwd().resolve()\n", + "while ROOT != ROOT.parent and not (ROOT / 'core').is_dir():\n", + " ROOT = ROOT.parent\n", + "if str(ROOT) not in sys.path:\n", + " sys.path.insert(0, str(ROOT))\n", + "STUDY = ROOT / 'studies' / '202602_AmazonConnect'\n", + "if str(STUDY) not in sys.path:\n", + " sys.path.insert(0, str(STUDY))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import config\n", + "import seed_data\n", + "from core.export import build_report_data\n", + "from core.export.report_data import _compute_summary\n", + "from core.notebook_helpers import charts, display, tables" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Local summary (no Athena round-trip)\n", + "\n", + "Compute the moderate-case TEI summary directly from `seed_data` so the\n", + "notebook produces results even before the Athena tool is provisioned." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "summary = _compute_summary(\n", + " seed_data.BENEFITS,\n", + " seed_data.COSTS,\n", + " config.DISCOUNT_RATE,\n", + " config.ANALYSIS_YEARS,\n", + ")\n", + "# `_compute_summary` returns roi_pct; expose it as `roi` for kpi_cards.\n", + "summary['roi'] = summary.get('roi_pct')\n", + "display.kpi_cards(summary, title='Forrester composite — moderate case')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df_cash = tables.cashflow_table(summary)\n", + "df_cash.style.format({c: '${:,.0f}' for c in df_cash.columns if c != 'Year'})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Cash flow chart\n", + "\n", + "Mirrors the chart on PDF page 25: stacked benefits/costs by year +\n", + "cumulative-net line." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "charts.cashflow_chart(\n", + " summary['yearly_breakdown'],\n", + " initial_cost=summary.get('initial_costs', 0),\n", + ").show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Waterfall: Benefits PV → Costs PV → NPV" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "charts.waterfall([\n", + " ('Benefits PV', summary['total_benefits_pv']),\n", + " ('Costs PV', -summary['total_costs_pv']),\n", + " ('NPV', summary['npv']),\n", + "]).show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Scenario analysis\n", + "\n", + "Apply the default Palladium multipliers (see `core.calculations.SCENARIOS`):\n", + "\n", + "* **Conservative** — 80% adoption, +10pp risk on benefits / -10pp on costs\n", + "* **Moderate** — base case (= the published Forrester study)\n", + "* **Aggressive** — 115% adoption, -5pp risk on benefits / +5pp on costs" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from core.calculations import apply_scenario\n", + "import pandas as pd\n", + "\n", + "scenario_summaries = {}\n", + "for name in ('conservative', 'moderate', 'aggressive'):\n", + " sb = apply_scenario(seed_data.BENEFITS, name, table='benefits')\n", + " sc = apply_scenario(seed_data.COSTS, name, table='costs')\n", + " scenario_summaries[name] = _compute_summary(sb, sc, config.DISCOUNT_RATE, config.ANALYSIS_YEARS)\n", + "\n", + "scen_df = pd.DataFrame([\n", + " {\n", + " 'Scenario': k,\n", + " 'Benefits PV': v['total_benefits_pv'],\n", + " 'Costs PV': v['total_costs_pv'],\n", + " 'NPV': v['npv'],\n", + " 'ROI %': v['roi_pct'],\n", + " 'Payback (mo)': round(v['payback_months'], 1) if v['payback_months'] is not None else None,\n", + " }\n", + " for k, v in scenario_summaries.items()\n", + "])\n", + "scen_df.style.format({\n", + " 'Benefits PV': '${:,.0f}', 'Costs PV': '${:,.0f}', 'NPV': '${:,.0f}', 'ROI %': '{:,.0f}%'\n", + "})" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "charts.scenario_comparison(scenario_summaries).show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Cross-check vs Athena (optional)\n", + "\n", + "When `TOOL_PUBLIC_ID` is set, ask Athena to recalculate the summary on\n", + "the server side and confirm it matches our local computation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "if config.TOOL_PUBLIC_ID:\n", + " from core.tei_client import TEIClient\n", + "\n", + " client = TEIClient()\n", + " client.calculate(config.TOOL_PUBLIC_ID)\n", + " server_summary = client.get_summary(config.TOOL_PUBLIC_ID)\n", + " display.kpi_cards(server_summary, title='Athena server-side summary')\n", + "else:\n", + " display.alert('Set TOOL_PUBLIC_ID to compare Athena vs local.', 'info')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Continue with [`04_export.ipynb`](04_export.ipynb) →" + ] + } + ], + "metadata": { + "kernelspec": {"display_name": "Python 3", "language": "python", "name": "python3"}, + "language_info": {"name": "python", "version": "3.11"} + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/studies/202602_AmazonConnect/notebooks/04_export.ipynb b/studies/202602_AmazonConnect/notebooks/04_export.ipynb new file mode 100644 index 0000000..ce8b15c --- /dev/null +++ b/studies/202602_AmazonConnect/notebooks/04_export.ipynb @@ -0,0 +1,195 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "15a4163e", + "metadata": {}, + "source": [ + "# 04 — Export for the report pipeline\n", + "\n", + "Build the structured JSON envelope consumed by the html2docx report\n", + "generation pipeline (Peitho). Output goes to `exports/export.json`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18f02ef8", + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "from pathlib import Path\n", + "\n", + "ROOT = Path.cwd().resolve()\n", + "while ROOT != ROOT.parent and not (ROOT / 'core').is_dir():\n", + " ROOT = ROOT.parent\n", + "if str(ROOT) not in sys.path:\n", + " sys.path.insert(0, str(ROOT))\n", + "STUDY = ROOT / 'studies' / '202602_AmazonConnect'\n", + "if str(STUDY) not in sys.path:\n", + " sys.path.insert(0, str(STUDY))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7d91c01d", + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "from datetime import datetime, timezone\n", + "\n", + "import config\n", + "import seed_data\n", + "from core import __version__\n", + "from core.calculations import apply_scenario\n", + "from core.export.report_data import _compute_summary\n", + "from core.notebook_helpers import display" + ] + }, + { + "cell_type": "markdown", + "id": "cff0b35b", + "metadata": {}, + "source": [ + "## Build the envelope\n", + "\n", + "Two paths:\n", + "\n", + "* **Live** — `core.export.build_report_data(client, public_id)` pulls\n", + " authoritative values + summary from Athena and stamps it.\n", + "* **Local** — when no `TOOL_PUBLIC_ID` is configured, build the envelope\n", + " directly from `seed_data` so this notebook is always runnable." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "19416ff3", + "metadata": {}, + "outputs": [], + "source": [ + "if config.TOOL_PUBLIC_ID:\n", + " from core.export import build_report_data\n", + " from core.tei_client import TEIClient\n", + "\n", + " client = TEIClient()\n", + " envelope = build_report_data(\n", + " client,\n", + " config.TOOL_PUBLIC_ID,\n", + " include_scenarios=True,\n", + " study_slug=config.STUDY_SLUG,\n", + " )\n", + " source = 'live (Athena)'\n", + "else:\n", + " summary = _compute_summary(\n", + " seed_data.BENEFITS, seed_data.COSTS, config.DISCOUNT_RATE, config.ANALYSIS_YEARS\n", + " )\n", + " summary['roi'] = summary.get('roi_pct')\n", + " scenarios = {}\n", + " for name in ('conservative', 'moderate', 'aggressive'):\n", + " sb = apply_scenario(seed_data.BENEFITS, name, table='benefits')\n", + " sc = apply_scenario(seed_data.COSTS, name, table='costs')\n", + " scenarios[name] = _compute_summary(sb, sc, config.DISCOUNT_RATE, config.ANALYSIS_YEARS)\n", + " envelope = {\n", + " 'metadata': {\n", + " 'study_slug': config.STUDY_SLUG,\n", + " 'tool_public_id': '',\n", + " 'tool_name': 'Amazon Connect TEI (local seed)',\n", + " 'report_name': 'Total Economic Impact™ Of Amazon Connect',\n", + " 'report_vendor': 'AWS',\n", + " 'report_version': '1.0',\n", + " 'generated_at': datetime.now(timezone.utc).isoformat(),\n", + " 'generator': f'palladium core {__version__} (offline)',\n", + " },\n", + " 'report': {\n", + " 'name': 'Total Economic Impact™ Of Amazon Connect',\n", + " 'vendor': 'AWS',\n", + " 'version': '1.0',\n", + " 'discount_rate': config.DISCOUNT_RATE,\n", + " 'analysis_period_years': config.ANALYSIS_YEARS,\n", + " },\n", + " 'values': {'benefits': seed_data.BENEFITS, 'costs': seed_data.COSTS},\n", + " 'summary': summary,\n", + " 'scenarios': scenarios,\n", + " 'assumptions': seed_data.ASSUMPTIONS,\n", + " }\n", + " source = 'offline seed data'\n", + "\n", + "display.alert(f'Envelope built from {source}.', 'info')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "98e94d07", + "metadata": {}, + "outputs": [], + "source": [ + "out_path = STUDY / 'exports' / 'export.json'\n", + "out_path.parent.mkdir(parents=True, exist_ok=True)\n", + "out_path.write_text(json.dumps(envelope, indent=2, default=str))\n", + "size_kb = out_path.stat().st_size / 1024\n", + "display.alert(f'Wrote {out_path.relative_to(ROOT)} ({size_kb:.1f} KB).', 'success')" + ] + }, + { + "cell_type": "markdown", + "id": "d09cad64", + "metadata": {}, + "source": [ + "## Envelope shape\n", + "\n", + "Top-level keys consumed by the report pipeline:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "841f12a1", + "metadata": {}, + "outputs": [], + "source": [ + "for key in envelope:\n", + " sub = envelope[key]\n", + " if isinstance(sub, dict):\n", + " print(f' {key}: dict with keys {list(sub.keys())}')\n", + " elif isinstance(sub, list):\n", + " print(f' {key}: list[{len(sub)}]')\n", + " else:\n", + " print(f' {key}: {type(sub).__name__}')" + ] + }, + { + "cell_type": "markdown", + "id": "17d6d0ce", + "metadata": {}, + "source": [ + "Done. Hand off `exports/export.json` to **Peitho** / **html2docx** to produce the final Word report." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/studies/202602_AmazonConnect/seed_data.py b/studies/202602_AmazonConnect/seed_data.py new file mode 100644 index 0000000..9142cf6 --- /dev/null +++ b/studies/202602_AmazonConnect/seed_data.py @@ -0,0 +1,162 @@ +""" +Seed dataset for the Amazon Connect TEI (Forrester, Feb 2026). + +Each row matches the wire shape produced by +``core.tei_client.TEIClient._normalize_value`` so it can be passed +straight to ``client.update_values(public_id, BENEFITS + COSTS)``. + +Numbers are the *nominal* (pre-risk-adjustment) values from the PDF — +risk adjustment is stored as a factor and applied by Athena's +calculator (or, locally, by ``core.calculations.risk_adjust_*``). + +References for the totals (from the PDF): + + Benefits (3-yr risk-adjusted PV @ 10%): $101,696,791 + Costs (3-yr risk-adjusted PV @ 10%): $ 22,983,076 + NPV $ 78,713,715 + ROI 342% + Payback <6 months +""" + +from __future__ import annotations + +#: 3-year nominal benefit cashflows. Risk adjustment factor is stored +#: separately; calculator applies it. +BENEFITS: list[dict] = [ + { + "field_key": "ai_contact_resolution", + "table": "benefits", + "label": "AI-driven contact resolution efficiency", + "category": "Productivity", + "year_values": {"1": 13_911_040, "2": 23_932_480, "3": 37_797_760}, + "risk_adjustment": 0.15, + "notes": ( + "PDF Section At/Atr. Composite: 20M annual contacts, 30% YoY " + "growth, 75% calls, 10-min AHT with legacy. Connect drops AHT " + "12% Y1 and shifts traffic to chat/self-service. 80% " + "productivity recapture. Risk adj 15% (legacy performance, " + "implementation depth, integration scope, growth)." + ), + }, + { + "field_key": "ai_content_sentiment", + "table": "benefits", + "label": "AI-powered content and sentiment analysis savings", + "category": "Productivity", + "year_values": {"1": 4_586_620, "2": 5_358_412, "3": 6_291_680}, + "risk_adjustment": 0.15, + "notes": ( + "PDF Section Bt/Btr. Auto post-contact summaries reclaim ~60s " + "per call; QA scaled from 1–3% to 100%; supervisors freed from " + "manual review. Risk adj 15%." + ), + }, + { + "field_key": "ai_forecasting_supervision", + "table": "benefits", + "label": "AI-enabled forecasting, agent scheduling, and supervision", + "category": "Productivity", + "year_values": {"1": 6_651_680, "2": 9_133_760, "3": 12_391_712}, + "risk_adjustment": 0.15, + "notes": ( + "PDF Section Ct/Ctr. ML-WFM yields 5% agent FTE optimization " + "and supervisors managing 20% more agents (10→12). 80% " + "productivity recapture. Risk adj 15%." + ), + }, + { + "field_key": "data_driven_profit_lift", + "table": "benefits", + "label": "Data-driven profit lift with increased conversion", + "category": "Revenue", + "year_values": {"1": 1_200_000, "2": 1_560_000, "3": 2_028_000}, + "risk_adjustment": 0.20, + "notes": ( + "PDF Section Dt/Dtr. Composite revenue $10B Y1 (+30% YoY); " + "5% from outbound contact-center marketing; conversion lifts " + "from 10% to 12% (+20% relative); 12% operating margin. " + "Risk adj 20%." + ), + }, + { + "field_key": "legacy_solution_savings", + "table": "benefits", + "label": "Legacy solution cost savings", + "category": "Cost Savings", + "year_values": {"1": 6_177_600, "2": 8_030_880, "3": 10_440_144}, + "risk_adjustment": 0.20, + "notes": ( + "PDF Section Et/Etr. Avg legacy license $180/agent-month × " + "(agents+supervisors) × 12, plus 30% overhead for infra & " + "third-party tools. Risk adj 20%." + ), + }, +] + + +#: Costs include an "initial" (year-0, undiscounted) component for +#: implementation. Cost risk adjustments are applied *upward*. +COSTS: list[dict] = [ + { + "field_key": "amazon_connect_usage", + "table": "costs", + "label": "Amazon Connect usage cost", + "category": "Subscription", + "initial": 0, + "year_values": {"1": 6_456_448, "2": 7_951_164, "3": 9_832_961}, + "risk_adjustment": 0.05, + "notes": ( + "PDF Section Ft/Ftr. Telephony $0.0106/min + Unlimited AI " + "$0.0380/min on minutes that reach an agent, plus chat at " + "$0.0100/message (10 messages/chat). Risk adj 5%." + ), + }, + { + "field_key": "implementation_migration", + "table": "costs", + "label": "Implementation and migration cost", + "category": "Implementation", + "initial": 1_087_500, + "year_values": {"1": 188_333, "2": 188_333, "3": 0}, + "risk_adjustment": 0.10, + "notes": ( + "PDF Section Gt/Gtr. 6-month initial migration: 5 internal " + "FTE @ $115k + $800k pro-services. Y1/Y2 M&A integrations: 2 " + "months × 2 FTE + $150k pro-services. Risk adj 10%." + ), + }, + { + "field_key": "ongoing_management", + "table": "costs", + "label": "Ongoing management", + "category": "Operations", + "initial": 0, + "year_values": {"1": 256_200, "2": 187_200, "3": 187_200}, + "risk_adjustment": 0.15, + "notes": ( + "PDF Section Ht/Htr. Y1: 5 IT/PM @ 30% × $115k + 5 business " + "users @ 30% × $55,800. Y2/Y3: 3 IT/PM @ 30% + 5 business " + "users @ 30%. Risk adj 15%." + ), + }, +] + + +#: Top-line composite assumptions — for the 03_business_case narrative. +ASSUMPTIONS: dict = { + "agents_fte": 2_000, + "supervisors_fte": 200, + "annual_contacts_y1": 20_000_000, + "growth_rate": 0.30, + "call_share": 0.75, + "aht_legacy_minutes": 10, + "agent_salary": 45_760, + "supervisor_salary": 55_800, + "discount_rate": 0.10, + "analysis_years": 3, +} + + +def all_values() -> list[dict]: + """Return BENEFITS + COSTS — handy single-call payload for update_values.""" + return BENEFITS + COSTS diff --git a/studies/__init__.py b/studies/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..f127dbf --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,32 @@ +"""Pytest fixtures for Palladium tests.""" + +from __future__ import annotations + +import os +import sys +from pathlib import Path + +import pytest + +ROOT = Path(__file__).resolve().parent.parent +if str(ROOT) not in sys.path: + sys.path.insert(0, str(ROOT)) + + +@pytest.fixture(autouse=True) +def _env(monkeypatch): + """Default test env vars so TEIClient() doesn't need a real .env.""" + monkeypatch.setenv("ATHENA_BASE_URL", "https://athena.test") + monkeypatch.setenv("ATHENA_API_KEY", "test-key") + + +@pytest.fixture +def amazon_connect_seed(): + """Load the Amazon Connect study's seed data.""" + sys.path.insert(0, str(ROOT / "studies" / "202602_AmazonConnect")) + try: + import seed_data # type: ignore[import-not-found] + return seed_data + finally: + # Leave the path alone — many tests will use the seed + pass diff --git a/tests/test_calculations.py b/tests/test_calculations.py new file mode 100644 index 0000000..a96d7a0 --- /dev/null +++ b/tests/test_calculations.py @@ -0,0 +1,206 @@ +""" +Tests for core.calculations — reproduces the Forrester Amazon Connect TEI +totals from the published study within rounding. + +The PDF reports (Cash Flow Analysis, p.25): + Benefits PV (RA) = $101,696,791 + Costs PV (RA) = $ 22,983,076 + NPV = $ 78,713,715 + ROI = 342% + Payback = <6 months +""" + +from __future__ import annotations + +import pytest + +from core.calculations import ( + SCENARIOS, + apply_scenario, + discount_factor, + npv, + payback_months, + payback_years, + present_value, + present_value_series, + risk_adjust_benefit, + risk_adjust_cost, + roi, + roi_percentage, +) + +# ───────────────────────────────────────────── +# Building blocks +# ───────────────────────────────────────────── + + +class TestDiscounting: + def test_discount_factor_year_zero(self): + assert discount_factor(0, 0.10) == pytest.approx(1.0) + + def test_discount_factor_known_value(self): + # 1/(1.10)^3 + assert discount_factor(3, 0.10) == pytest.approx(0.7513148, rel=1e-5) + + def test_negative_year_raises(self): + with pytest.raises(ValueError): + discount_factor(-1, 0.10) + + def test_present_value_year_one(self): + assert present_value(110, 1, 0.10) == pytest.approx(100.0) + + def test_present_value_series_three_years(self): + # 100 each year for 3 years at 10% → ≈ 248.685 + assert present_value_series([100, 100, 100], 0.10) == pytest.approx( + 248.685, rel=1e-3 + ) + + +class TestNPV: + def test_zero_initial(self): + assert npv([100, 100], 0.0) == pytest.approx(200.0) + + def test_with_initial(self): + # 1000 invested up-front, 600 returned each of 2 years at 10% + result = npv([600, 600], 0.10, initial=-1000) + # PV of returns ≈ 545.45 + 495.87 = 1041.32, NPV ≈ 41.32 + assert result == pytest.approx(41.32, abs=0.5) + + +class TestRiskAdjustment: + def test_benefit_zero_risk(self): + assert risk_adjust_benefit(100, 0.0) == 100 + + def test_benefit_15pct(self): + assert risk_adjust_benefit(100, 0.15) == pytest.approx(85.0) + + def test_cost_5pct_upward(self): + assert risk_adjust_cost(100, 0.05) == pytest.approx(105.0) + + def test_clamping(self): + assert risk_adjust_benefit(100, 1.5) == 0 # clamped to 1.0 + assert risk_adjust_benefit(100, -0.5) == 100 # clamped to 0 + + +class TestROI: + def test_zero_costs_returns_zero(self): + assert roi(100, 0) == 0.0 + + def test_known(self): + assert roi_percentage(101_696_791, 22_983_076) == pytest.approx(342, abs=1) + + +class TestPayback: + def test_immediate(self): + assert payback_years(0, [100]) == 0.0 + + def test_amazon_connect_under_six_months(self): + # Initial $1.196M, Y1 net ~$20M → quick crossing + years = payback_years(1_196_250, [19_997_953, 31_562_489, 47_443_905]) + assert years is not None + assert payback_months(1_196_250, [19_997_953, 31_562_489, 47_443_905]) < 6 + + def test_never_recovered(self): + assert payback_years(1000, [100, 100, 100]) is None + + +class TestScenarios: + def test_default_scenarios_present(self): + assert set(SCENARIOS) == {"conservative", "moderate", "aggressive"} + + def test_moderate_is_passthrough(self): + items = [ + { + "table": "benefits", + "field_key": "x", + "year_values": {"1": 1000, "2": 2000}, + "risk_adjustment": 0.15, + } + ] + out = apply_scenario(items, "moderate") + assert out[0]["year_values"] == {"1": 1000.0, "2": 2000.0} + assert out[0]["risk_adjustment"] == pytest.approx(0.15) + + def test_conservative_lowers_benefits(self): + items = [ + { + "table": "benefits", + "field_key": "x", + "year_values": {"1": 1000}, + "risk_adjustment": 0.15, + } + ] + out = apply_scenario(items, "conservative") + assert out[0]["year_values"]["1"] == pytest.approx(800.0) + # 0.15 + 0.10 = 0.25 + assert out[0]["risk_adjustment"] == pytest.approx(0.25) + + def test_aggressive_increases_benefits(self): + items = [ + { + "table": "benefits", + "field_key": "x", + "year_values": {"1": 1000}, + "risk_adjustment": 0.15, + } + ] + out = apply_scenario(items, "aggressive") + assert out[0]["year_values"]["1"] == pytest.approx(1150.0) + assert out[0]["risk_adjustment"] == pytest.approx(0.10) + + def test_unknown_scenario_raises(self): + with pytest.raises(KeyError): + apply_scenario([], "purple") + + +# ───────────────────────────────────────────── +# End-to-end: reproduce the PDF totals +# ───────────────────────────────────────────── + + +class TestAmazonConnectComposite: + """Reproduce the Forrester Amazon Connect TEI numbers within rounding.""" + + DISCOUNT_RATE = 0.10 + EXPECTED_BENEFITS_PV = 101_696_791 + EXPECTED_COSTS_PV = 22_983_076 + EXPECTED_NPV = 78_713_715 + EXPECTED_ROI = 342 # percent + TOLERANCE = 1_500 # dollars; PDF rounding at thousands + + def _benefits_pv(self, seed) -> float: + total = 0.0 + for b in seed.BENEFITS: + rf = b["risk_adjustment"] + yr = [b["year_values"][str(y)] for y in (1, 2, 3)] + yr_ra = [risk_adjust_benefit(v, rf) for v in yr] + total += npv(yr_ra, self.DISCOUNT_RATE) + return total + + def _costs_pv(self, seed) -> float: + total = 0.0 + for c in seed.COSTS: + rf = c["risk_adjustment"] + init = risk_adjust_cost(c.get("initial") or 0, rf) + yr = [c["year_values"][str(y)] for y in (1, 2, 3)] + yr_ra = [risk_adjust_cost(v, rf) for v in yr] + total += npv(yr_ra, self.DISCOUNT_RATE, initial=init) + return total + + def test_benefits_pv(self, amazon_connect_seed): + result = self._benefits_pv(amazon_connect_seed) + assert result == pytest.approx(self.EXPECTED_BENEFITS_PV, abs=self.TOLERANCE) + + def test_costs_pv(self, amazon_connect_seed): + result = self._costs_pv(amazon_connect_seed) + assert result == pytest.approx(self.EXPECTED_COSTS_PV, abs=self.TOLERANCE) + + def test_npv(self, amazon_connect_seed): + b = self._benefits_pv(amazon_connect_seed) + c = self._costs_pv(amazon_connect_seed) + assert (b - c) == pytest.approx(self.EXPECTED_NPV, abs=self.TOLERANCE) + + def test_roi(self, amazon_connect_seed): + b = self._benefits_pv(amazon_connect_seed) + c = self._costs_pv(amazon_connect_seed) + assert roi_percentage(b, c) == pytest.approx(self.EXPECTED_ROI, abs=1) diff --git a/tests/test_client.py b/tests/test_client.py new file mode 100644 index 0000000..f59d048 --- /dev/null +++ b/tests/test_client.py @@ -0,0 +1,174 @@ +""" +TEI client tests with mocked HTTP. + +We mock ``requests.Session.request`` so tests do not require network access +or a live Athena instance. +""" + +from __future__ import annotations + +import json +from unittest.mock import MagicMock + +import pytest + +from core.tei_client import AthenaAPIError, TEIClient + + +def _mock_response(status: int, body=None) -> MagicMock: + resp = MagicMock() + resp.status_code = status + resp.content = b"{}" if body is None else json.dumps(body).encode() + resp.json.return_value = body if body is not None else {} + resp.text = json.dumps(body or {}) + return resp + + +@pytest.fixture +def client(monkeypatch) -> TEIClient: + c = TEIClient() + c.session = MagicMock() + return c + + +class TestConfig: + def test_requires_base_url(self, monkeypatch): + monkeypatch.delenv("ATHENA_BASE_URL", raising=False) + with pytest.raises(ValueError, match="ATHENA_BASE_URL"): + TEIClient(api_key="x") + + def test_requires_api_key(self, monkeypatch): + monkeypatch.delenv("ATHENA_API_KEY", raising=False) + with pytest.raises(ValueError, match="ATHENA_API_KEY"): + TEIClient(base_url="https://example.com") + + def test_authorization_header(self): + c = TEIClient(base_url="https://example.com", api_key="abc123") + assert c.session.headers["Authorization"] == "Api-Key abc123" + + +class TestPaths: + """Verify each endpoint targets the documented URL.""" + + def _last_call_url(self, client: TEIClient) -> str: + return client.session.request.call_args.kwargs["url"] + + def test_list_reports_path(self, client): + client.session.request.return_value = _mock_response( + 200, {"results": [], "next": None} + ) + client.list_reports() + assert self._last_call_url(client) == "https://athena.test/api/v1/tei/reports/" + + def test_get_tool_path(self, client): + client.session.request.return_value = _mock_response(200, {"id": "abc"}) + client.get_tool("abc123") + assert self._last_call_url(client).endswith("/api/v1/tei/tools/abc123/") + + def test_calculate_path(self, client): + client.session.request.return_value = _mock_response(200, {}) + client.calculate("abc") + assert self._last_call_url(client).endswith("/api/v1/tei/tools/abc/calculate/") + assert client.session.request.call_args.kwargs["method"] == "POST" + + def test_export_path(self, client): + client.session.request.return_value = _mock_response(200, {}) + client.export("abc") + assert self._last_call_url(client).endswith("/api/v1/tei/tools/abc/export/") + + def test_aggregate_summary_path(self, client): + client.session.request.return_value = _mock_response(200, {}) + client.aggregate_summary() + assert self._last_call_url(client).endswith("/api/v1/tei/summary/") + + def test_save_version_path(self, client): + client.session.request.return_value = _mock_response(201, {"version_number": 1}) + client.save_version("abc", note="initial") + url = self._last_call_url(client) + assert url.endswith("/api/v1/tei/tools/abc/versions/") + body = client.session.request.call_args.kwargs["json"] + assert body == {"note": "initial"} + + +class TestErrorHandling: + def test_404_raises_athena_error(self, client): + client.session.request.return_value = _mock_response( + 404, {"detail": "Not found"} + ) + with pytest.raises(AthenaAPIError) as ei: + client.get_tool("missing") + assert ei.value.status_code == 404 + assert "Not found" in ei.value.detail + + def test_test_connection_returns_error_dict(self, client): + client.session.request.return_value = _mock_response( + 401, {"detail": "Invalid token"} + ) + result = client.test_connection() + assert result["status"] == "error" + assert result["authenticated"] is False + assert result["error_code"] == 401 + + +class TestPagination: + def test_walks_next_links(self, client): + # First page returns one item with a `next` URL; second page returns + # one more item and no next. + page1 = _mock_response( + 200, + { + "results": [{"id": 1}], + "next": "https://athena.test/api/v1/tei/reports/?page=2", + }, + ) + page2 = _mock_response(200, {"results": [{"id": 2}], "next": None}) + client.session.request.return_value = page1 + client.session.get.return_value = page2 # follow next via session.get + + out = client.list_reports() + assert [r["id"] for r in out] == [1, 2] + + +class TestNormalizeValue: + def test_year_underscore_keys(self): + out = TEIClient._normalize_value( + {"field_key": "x", "year_1": 100, "year_2": 200, "risk_adjustment": 0.1} + ) + assert out["year_values"] == {"1": 100.0, "2": 200.0} + assert out["risk_adjustment"] == 0.1 + + def test_year_values_dict_passthrough(self): + out = TEIClient._normalize_value( + { + "field_key": "x", + "year_values": {"1": 50, "3": 75}, + "notes": " hi ", + } + ) + assert out["year_values"] == {"1": 50.0, "3": 75.0} + assert out["notes"] == " hi " + + def test_initial_carried(self): + out = TEIClient._normalize_value( + {"field_key": "x", "initial": 1000, "year_1": 5} + ) + assert out["initial"] == 1000.0 + + def test_scalar_value(self): + out = TEIClient._normalize_value({"field_key": "rate", "value": 0.10}) + assert out["value"] == 0.10 + assert "year_values" not in out + + +class TestUpdateValuesPayload: + def test_wraps_in_envelope(self, client): + client.session.request.return_value = _mock_response(200, {}) + client.update_values( + "abc", + [{"field_key": "x", "year_1": 100}, {"field_key": "y", "year_1": 200}], + ) + body = client.session.request.call_args.kwargs["json"] + assert "values" in body + assert len(body["values"]) == 2 + assert body["values"][0]["field_key"] == "x" + assert body["values"][0]["year_values"] == {"1": 100.0} diff --git a/tests/test_export.py b/tests/test_export.py new file mode 100644 index 0000000..11ac922 --- /dev/null +++ b/tests/test_export.py @@ -0,0 +1,98 @@ +"""Tests for core.export.report_data — envelope shape and computed totals.""" + +from __future__ import annotations + +from unittest.mock import MagicMock + +import pytest + +from core.export import build_report_data +from core.export.report_data import _compute_summary, _yearly_totals + + +class TestComputeSummary: + def test_amazon_connect_totals(self, amazon_connect_seed): + s = _compute_summary( + amazon_connect_seed.BENEFITS, + amazon_connect_seed.COSTS, + 0.10, + 3, + ) + assert s["total_benefits_pv"] == pytest.approx(101_696_791, abs=1500) + assert s["total_costs_pv"] == pytest.approx(22_983_076, abs=1500) + assert s["npv"] == pytest.approx(78_713_715, abs=2000) + assert s["roi_pct"] == pytest.approx(342, abs=1) + assert s["payback_months"] is not None and s["payback_months"] < 6 + + def test_yearly_breakdown_three_rows(self, amazon_connect_seed): + s = _compute_summary( + amazon_connect_seed.BENEFITS, amazon_connect_seed.COSTS, 0.10, 3 + ) + assert len(s["yearly_breakdown"]) == 3 + assert [r["year"] for r in s["yearly_breakdown"]] == [1, 2, 3] + + +class TestYearlyTotals: + def test_only_within_horizon(self): + items = [ + {"year_values": {"1": 100, "2": 200, "3": 300, "4": 999}}, + ] + assert _yearly_totals(items, 3) == [100.0, 200.0, 300.0] + + def test_skips_invalid_keys(self): + items = [{"year_values": {"1": 50, "abc": 999}}] + assert _yearly_totals(items, 2) == [50.0, 0.0] + + +class TestBuildReportData: + def _stub_client(self, seed): + c = MagicMock() + c.get_tool_with_data.return_value = { + "tool": {"id": "pid", "name": "T", "report": "rid", "proposal": 7}, + "fields": [], + "values": seed.BENEFITS + seed.COSTS, + } + c.get_report.return_value = { + "id": "rid", + "name": "Amazon Connect", + "vendor": "AWS", + "version": "1.0", + "discount_rate": "0.10", + "analysis_period_years": 3, + } + c.export.return_value = {"echoed": True} + return c + + def test_envelope_shape(self, amazon_connect_seed): + client = self._stub_client(amazon_connect_seed) + env = build_report_data(client, "pid", study_slug="202602_AmazonConnect") + assert set(env) >= { + "metadata", + "report", + "fields", + "values", + "summary", + "athena_export", + "scenarios", + } + assert env["metadata"]["study_slug"] == "202602_AmazonConnect" + assert env["metadata"]["proposal"] == 7 + assert env["values"]["benefits"] + assert env["values"]["costs"] + + def test_scenarios_have_three_keys(self, amazon_connect_seed): + client = self._stub_client(amazon_connect_seed) + env = build_report_data(client, "pid") + assert set(env["scenarios"]) == {"conservative", "moderate", "aggressive"} + + def test_no_scenarios_flag(self, amazon_connect_seed): + client = self._stub_client(amazon_connect_seed) + env = build_report_data(client, "pid", include_scenarios=False) + assert "scenarios" not in env + + def test_local_summary_matches_seed(self, amazon_connect_seed): + client = self._stub_client(amazon_connect_seed) + env = build_report_data(client, "pid", include_scenarios=False) + assert env["summary"]["total_benefits_pv"] == pytest.approx( + 101_696_791, abs=1500 + )