---
title: "Save Budget"
method: POST
path: "/api/budget/save"
tags: ["copilot_frontend", "copilot_budget"]
---

# Save Budget

`POST /api/budget/save`

Saves a complete budget to database and creates/updates contact in HubSpot.

This endpoint:
1. Gets province name from database
2. Updates commercial phone if provided and different from current
3. Recalculates the budget using BudgetCalculatorService
4. Creates/updates contact in HubSpot with client data
5. Creates note in HubSpot deal with selected option details
6. Saves budget in presup_presupuestos table
7. Generates tokenized URL and updates HubSpot deal property

**Protected Endpoint:** Requires JWT authentication and 'ui_comercial' or 'admin' role

Args:
    request: SaveBudgetRequest with client data, calculation parameters, selected option, and optional commercial phone
    current_user: Current authenticated user
    db: Database session

Returns:
    SaveBudgetResponse with success status, budget ID, and HubSpot contact ID

Raises:
    ErrValidation: If user does not have comercial_id
    ErrNotFound: If province not found
    Exception: If HubSpot or database save fails

## Request body

- SaveBudgetRequest — Request to save a budget calculation to database and HubSpot. This combines: - Client contact data - Budget calculation parameters - Selected budget option - Commercial phone (optional, to update if changed)
  - `client_data` ClientDataForBudget, required — Client contact data to save in HubSpot and database.
    - `nombre` string, required — Client full name (nombre y apellidos)
    - `apellido` string, required — Client last name(s)
    - `numero_documento_identidad` string, nullable — DNI/NIE/CIF del cliente
    - `dni` string, nullable — LEGACY: DNI del cliente (usar numero_documento_identidad)
    - `direccion` string, nullable — Full address
    - `localidad` string, nullable — City/Town
    - `cp` string, nullable — Postal code
    - `provincia_id` integer, required — Province ID from presup_provincias_prod table
    - `email` string, email, required — Client email
    - `telefono` string, nullable — Client phone number
    - `telefono_comercial` string, nullable — Commercial phone number
    - `dia_respuesta` string, date, nullable — Response day for budget (fecha de respuesta del presupuesto) - Optional for drafts, required when sending to HubSpot
  - `calculation_data` BudgetCalculationRequest, required — Request from frontend to calculate budget in real-time. User inputs from the form that trigger recalculation.
    - `solo_bateria` boolean — Battery-only budget (no panels, no consumption required)
    - `consumo_mes_kwh` integer — Monthly consumption in kWh
    - `provincia` integer, required — Province ID for production calculations
    - `presupuesto_id` integer, nullable — Budget ID (optional, used to apply client-specific promotions)
    - `panel_id` integer, nullable — Selected panel ID (None for solo_bateria)
    - `micro_inversor_id` integer, nullable — Selected microinverter ID
    - `baterias` BatterySelection — Battery selection with quantity.
      - `bateria_id` integer, required — Battery ID
      - `cantidad` integer — Battery quantity
    - `extras_ids` integer[] — List of selected extra IDs
    - `metros_cableado` integer — Extra cable meters
    - `estructura_id` integer, nullable — Structure/pergola ID (None = no pergola)
    - `num_paneles_sin_pergola` integer — Number of panels without pergola
    - `ground_type_id` integer, nullable — Ground type ID
    - `forma_pago` integer, nullable — Payment method ID (None = not selected)
    - `opcion_contado_id` integer, nullable — Cash payment option ID from presup_forma_pago_contado (required if forma_pago='contado')
    - `meses_financiacion` integer, nullable — Financing months if forma_pago='financiado'
    - `entidad_financiera` string, nullable — Financing entity if forma_pago='financiado'
    - `contado_financiacion` number, nullable — Down payment amount if forma_pago='contado financiado'
    - `con_backup` boolean — Include backup system cost (adds Backup constant price)
  - `selected_option` integer, nullable — ID/name of the selected budget option (e.g., 'Básica', 'Premium')
  - `enviar_hubspot` boolean — If True, send to HubSpot and set estado=3. If False, only save as draft (estado=2)
  - `presupuesto_id` integer, nullable — ID of existing budget to update directly (optional)
  - `comercial_id` integer, nullable — Override comercial ID. Admin use only: assigns budget to the specified comercial instead of the current user
  - `guarda_opcion_cliente_test` boolean — Test-only flag to control client option saving. In production, this is derived from enviar_hubspot

## Response `200`

Successful Response

- SaveBudgetResponse — Response after saving budget.
  - `success` boolean, required — Whether the save was successful
  - `message` string, required — Success/error message
  - `budget_id` integer, nullable — ID of the created budget in database
  - `hubspot_contact_id` string, nullable — HubSpot contact ID (created or updated)
  - `hubspot_warning` string, nullable — Warning message if HubSpot operation partially failed (e.g., invalid property)

## Other responses

- `422` — Validation Error

---

[API](https://skmtc.dev/hogarsolar/apis/hogarsolar-backend-api.md) · [All operations](https://skmtc.dev/hogarsolar/apis/hogarsolar-backend-api/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/hogarsolar/hogarsolar-backend-api/revisions/d9861e46bef8/schema)
