{"info":{"description":"An API for interacting with Spruce.","title":"Spruce External API","version":"1.0.0"},"openapi":"3.1.0","servers":[{"description":"Production server","url":"https://api.spruce.eco"}],"tags":[{"description":"Endpoints for obtaining API access tokens.","name":"Authentication"},{"description":"Job and property management.","name":"Jobs"},{"description":"Heat loss calculations.","name":"Estimates"}],"paths":{"/v1/auth":{"post":{"x-badges":[{"name":"Core"}],"x-tier":"core","description":"Authenticate with a company API key to obtain a JWT token which must be used when accessing other endpoints.","summary":"Authenticate","tags":["Authentication"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"api_key":{"description":"API key","type":"string"}},"required":["api_key"]}}}},"responses":{"200":{"description":"Successfully authenticated","content":{"application/json":{"schema":{"type":"object","properties":{"token":{"description":"JWT token for authenticating subsequent requests","type":"string"}},"required":["token"],"additionalProperties":false}}}},"401":{"description":"Invalid email or password","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"description":"Additional error details"},"error":{"description":"Error code","type":"string","enum":["not_found","internal_server_error","validation_error","postcode_lookup_failed","unauthorized","forbidden","conflict"]},"message":{"description":"Human-readable error message","type":"string"}},"required":["error"],"additionalProperties":false}}}}}}},"/v1/estimates":{"post":{"x-badges":[{"color":"#f59e0b","name":"Extended"}],"x-tier":"extended","description":"Calculate a heat loss estimate for a property.\n          \nThis endpoint is part of the Spruce extended API.","security":[{"bearerAuth":[]}],"summary":"Calculate estimate","tags":["Estimates"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"altitude_m":{"description":"Property altitude in meters. Will be derived from the coordinates if not provided.","example":50,"type":"number"},"built_form":{"description":"Built form of the property","example":"semi_detached","type":"string","enum":["detached","semi_detached","mid_terrace","end_terrace"]},"floor_area_m2":{"description":"Total floor area in square meters","example":120,"type":"number","exclusiveMinimum":0},"fuel_type":{"description":"Current heating fuel type","example":"mains_gas","type":"string","enum":["mains_gas","oil","lpg","electric","coal","wood"]},"loft_insulation":{"description":"Loft insulation thickness","example":"150mm","type":"string","enum":["none","50mm","100mm","150mm","200mm","250+mm"]},"num_bathrooms":{"description":"Number of bathrooms","example":2,"type":"integer","minimum":0,"maximum":9007199254740991},"num_bedrooms":{"description":"Number of bedrooms","example":3,"type":"integer","minimum":0,"maximum":9007199254740991},"num_storeys":{"description":"Number of storeys","example":2,"type":"integer","minimum":1,"maximum":9007199254740991},"postcode":{"description":"UK postcode or Irish Eircode","example":"SW1A 1AA","type":"string","minLength":1},"property_type":{"description":"Property type","example":"house","type":"string","enum":["house","flat","bungalow"]},"room_height_m":{"description":"Room height in metres","example":2.4,"type":"number","exclusiveMinimum":0},"wall_type":{"description":"Wall construction and insulation type","example":"cavity_wall_filled","type":"string","enum":["cavity_wall_filled","cavity_wall_unfilled","solid_brick_uninsulated","solid_brick_internal_insulation","solid_brick_external_insulation","solid_brick_unknown","solid_stone_uninsulated","solid_stone_internal_insulation","solid_stone_external_insulation","solid_stone_unknown","solid_concrete_uninsulated","solid_concrete_internal_insulation","solid_concrete_external_insulation","solid_concrete_unknown","timber_uninsulated","timber_internal_insulation","timber_external_insulation","timber_unknown"]},"window_type":{"description":"Window glazing type","example":"double_glazing","type":"string","enum":["single_glazing","double_glazing","high_performance","triple_glazing"]},"floor_u_value":{"description":"Floor U-value in W/m²K (optional, if directly measured or from EPC). If provided, overrides the floor U-value used in the heat loss calculation.","example":0.18,"type":"number"},"roof_u_value":{"description":"Roof U-value in W/m²K (optional, if directly measured or from EPC). If provided, overrides the roof U-value used in the heat loss calculation (otherwise derived from loft_insulation).","example":0.11,"type":"number"},"wall_u_value":{"description":"External wall U-value in W/m²K (optional, if directly measured or from EPC). If provided, overrides both the external wall and party wall U-values used in the heat loss calculation (party wall capped at 0.5 W/m²K; otherwise derived from wall_type).","example":0.18,"type":"number"},"window_u_value":{"description":"Window U-value in W/m²K (optional, if directly measured or from EPC). If provided, overrides the window U-value used in the heat loss calculation (otherwise derived from window_type).","example":1.4,"type":"number"},"lat_lng":{"description":"Latitude and longitude coordinates of the address [lat, lng]. Will be calculated from the postcode if not provided.","example":[51.5014,-0.1419],"minItems":2,"maxItems":2,"type":"array","items":{"type":"number"}},"ventilation_heat_loss_method":{"description":"The method used to calculate heat loss via ventilation ('air_permeability' is the current, BS EN 12831 method, 'ach' is the legacy CIBSE method).","example":"air_permeability","type":"string","enum":["ach","air_permeability"]}},"required":["built_form","floor_area_m2","fuel_type","loft_insulation","num_bedrooms","postcode","property_type","wall_type","window_type"]}}}},"responses":{"200":{"description":"Successfully calculated estimate","content":{"application/json":{"schema":{"type":"object","properties":{"estimates":{"description":"Array of estimate options. Currently returns one estimate based on best-fit equipment from inventory; will support multiple estimate configurations in future.","type":"array","items":{"type":"object","properties":{"annual_heat_energy_demand_w":{"description":"Annual heat energy demand in watts (space heating + hot water)","example":1850000,"type":"number"},"average_heat_pump_efficiency":{"description":"Average heat pump efficiency including hot water (lower than sCOP)","example":3.32,"type":"number"},"co2_saved_kg":{"description":"Annual CO2 savings in kg compared to current heating system","example":4528,"type":"number"},"commutes_saved_petrol_car":{"description":"CO2 savings expressed as an equivalent number of 12-mile round trip petrol car commutes","example":1258,"type":"number"},"efficiency_baseline":{"description":"Current heating system efficiency (e.g., 0.87 for oil boiler)","example":0.87,"type":"number"},"electricity_tariff_baseline_annual_bill_gbp":{"description":"Estimated annual heating bill with current system in GBP (excluding standing charge)","example":1450,"type":"number"},"electricity_tariff_baseline_pence_per_kwh":{"description":"Current fuel tariff in pence per kWh","example":6.24,"type":"number"},"electricity_tariff_baseline_price_cap_pence_per_kwh":{"description":"Standard electricity price cap in pence per kWh","example":24.5,"type":"number"},"electricity_tariff_heat_pump_annual_bill_gbp":{"description":"Estimated annual heating bill with heat pump on dedicated tariff in GBP","example":1150,"type":"number"},"electricity_tariff_heat_pump_annual_bill_price_cap_gbp":{"description":"Estimated annual heating bill with heat pump on price cap tariff in GBP","example":1370,"type":"number"},"electricity_tariff_heat_pump_based_on":{"description":"Description of electricity tariff used","example":"Octopus Cosy Octopus tariff","type":"string"},"electricity_tariff_heat_pump_pence_per_kwh":{"description":"Electricity tariff in pence per kWh (heat pump tariff)","example":20.6,"type":"number"},"flights_to_spain_saved":{"description":"CO2 savings expressed as an equivalent number of flights from the UK to Spain","example":23,"type":"number"},"flow_temp_c":{"description":"Heat pump flow temperature in °C","example":45,"type":"number"},"fuel_name_baseline":{"description":"Current fuel type name","example":"Mains Gas","type":"string"},"heat_pumps":{"description":"Heat pumps selected for this estimate. Empty if no heat pump large enough is available at these design conditions.","type":"array","items":{"type":"object","properties":{"calculated_capacity_w":{"description":"Capacity in watts at the design specified outdoor and flow temperatures","example":720,"type":"number"},"calculated_scop":{"description":"Seasonal Coefficient of Performance","example":3.6,"type":"number"},"inventory_heat_pump_name":{"description":"Heat pump model name","example":"Vaillant aroTHERM plus 7kW","type":"string"}},"required":["calculated_capacity_w","calculated_scop","inventory_heat_pump_name"],"additionalProperties":false}},"hot_water_cylinders":{"description":"Hot water cylinders selected for this estimate. Empty if no hot water cylinder large enough is available at these design conditions.","type":"array","items":{"type":"object","properties":{"capacity_l":{"description":"Cylinder capacity in litres","example":200,"type":"number"},"inventory_hot_water_cylinder_name":{"description":"Hot water cylinder model name","example":"Joule 200L","type":"string"}},"required":["capacity_l","inventory_hot_water_cylinder_name"],"additionalProperties":false}},"internal_temp_c":{"description":"Design internal temperature in °C","example":20,"type":"number"},"outdoor_temp_c":{"description":"Design outdoor temperature in °C for the location","example":-3.2,"type":"number"},"overall_scop":{"description":"Overall Seasonal Coefficient of Performance for space heating","example":3.6,"type":"number"},"price_cap_description":{"description":"Description of price cap used","example":"Ofgem Price Cap Q4 2024","type":"string"},"total_heat_loss_w":{"description":"Total peak heat loss in watts for all heat pumps","example":720,"type":"number"},"total_price_excluding_grants_pence":{"description":"Total installation quote price in pence before any grant deductions (including VAT where applicable)","example":20000,"type":"number"},"total_price_including_grants_pence":{"description":"Total installation quote price in pence after grant deductions (including VAT where applicable)","example":12500,"type":"number"}},"required":["annual_heat_energy_demand_w","average_heat_pump_efficiency","co2_saved_kg","commutes_saved_petrol_car","efficiency_baseline","electricity_tariff_baseline_annual_bill_gbp","electricity_tariff_baseline_pence_per_kwh","electricity_tariff_baseline_price_cap_pence_per_kwh","electricity_tariff_heat_pump_annual_bill_gbp","electricity_tariff_heat_pump_annual_bill_price_cap_gbp","electricity_tariff_heat_pump_based_on","electricity_tariff_heat_pump_pence_per_kwh","flights_to_spain_saved","fuel_name_baseline","heat_pumps","hot_water_cylinders","internal_temp_c","outdoor_temp_c","price_cap_description","total_heat_loss_w","total_price_excluding_grants_pence","total_price_including_grants_pence"],"additionalProperties":false}},"url":{"description":"Link to the estimate if it exists on Spruce","example":"https://app.spruce.eco/theheatingco/estimate/01234567-89ab-cdef-0123-456789abcdef","type":"string","format":"uri"}},"required":["estimates"],"additionalProperties":false}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"description":"Additional error details"},"error":{"description":"Error code","type":"string","enum":["not_found","internal_server_error","validation_error","postcode_lookup_failed","unauthorized","forbidden","conflict"]},"message":{"description":"Human-readable error message","type":"string"}},"required":["error"],"additionalProperties":false}}}},"401":{"description":"Unauthorized - missing or invalid token","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"description":"Additional error details"},"error":{"description":"Error code","type":"string","enum":["not_found","internal_server_error","validation_error","postcode_lookup_failed","unauthorized","forbidden","conflict"]},"message":{"description":"Human-readable error message","type":"string"}},"required":["error"],"additionalProperties":false}}}},"403":{"description":"Forbidden - insufficient permissions","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"description":"Additional error details"},"error":{"description":"Error code","type":"string","enum":["not_found","internal_server_error","validation_error","postcode_lookup_failed","unauthorized","forbidden","conflict"]},"message":{"description":"Human-readable error message","type":"string"}},"required":["error"],"additionalProperties":false}}}},"422":{"description":"Unprocessable entity","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"description":"Additional error details"},"error":{"description":"Error code","type":"string","enum":["not_found","internal_server_error","validation_error","postcode_lookup_failed","unauthorized","forbidden","conflict"]},"message":{"description":"Human-readable error message","type":"string"}},"required":["error"],"additionalProperties":false}}}}}}},"/v1/jobs":{"post":{"x-badges":[{"name":"Core"}],"x-tier":"core","description":"Create a new job with property and customer details.","security":[{"bearerAuth":[]}],"summary":"Create job","tags":["Jobs"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"altitude_m":{"description":"Property altitude in meters. Will be derived from the coordinates if not provided.","example":50,"type":"number"},"built_form":{"description":"Built form of the property","example":"semi_detached","type":"string","enum":["detached","semi_detached","mid_terrace","end_terrace"]},"floor_area_m2":{"description":"Total floor area in square meters","example":120,"type":"number","exclusiveMinimum":0},"fuel_type":{"description":"Current heating fuel type","example":"mains_gas","type":"string","enum":["mains_gas","oil","lpg","electric","coal","wood"]},"loft_insulation":{"description":"Loft insulation thickness","example":"150mm","type":"string","enum":["none","50mm","100mm","150mm","200mm","250+mm"]},"num_bathrooms":{"description":"Number of bathrooms","example":2,"type":"integer","minimum":0,"maximum":9007199254740991},"num_bedrooms":{"description":"Number of bedrooms","example":3,"type":"integer","minimum":0,"maximum":9007199254740991},"num_storeys":{"description":"Number of storeys","example":2,"type":"integer","minimum":1,"maximum":9007199254740991},"postcode":{"description":"UK postcode or Irish Eircode","example":"SW1A 1AA","type":"string","minLength":1},"property_type":{"description":"Property type","example":"house","type":"string","enum":["house","flat","bungalow"]},"room_height_m":{"description":"Room height in metres","example":2.4,"type":"number","exclusiveMinimum":0},"wall_type":{"description":"Wall construction and insulation type","example":"cavity_wall_filled","type":"string","enum":["cavity_wall_filled","cavity_wall_unfilled","solid_brick_uninsulated","solid_brick_internal_insulation","solid_brick_external_insulation","solid_brick_unknown","solid_stone_uninsulated","solid_stone_internal_insulation","solid_stone_external_insulation","solid_stone_unknown","solid_concrete_uninsulated","solid_concrete_internal_insulation","solid_concrete_external_insulation","solid_concrete_unknown","timber_uninsulated","timber_internal_insulation","timber_external_insulation","timber_unknown"]},"window_type":{"description":"Window glazing type","example":"double_glazing","type":"string","enum":["single_glazing","double_glazing","high_performance","triple_glazing"]},"floor_u_value":{"description":"Floor U-value in W/m²K (optional, if directly measured or from EPC). If provided, overrides the floor U-value used in the heat loss calculation.","example":0.18,"type":"number"},"roof_u_value":{"description":"Roof U-value in W/m²K (optional, if directly measured or from EPC). If provided, overrides the roof U-value used in the heat loss calculation (otherwise derived from loft_insulation).","example":0.11,"type":"number"},"wall_u_value":{"description":"External wall U-value in W/m²K (optional, if directly measured or from EPC). If provided, overrides both the external wall and party wall U-values used in the heat loss calculation (party wall capped at 0.5 W/m²K; otherwise derived from wall_type).","example":0.18,"type":"number"},"window_u_value":{"description":"Window U-value in W/m²K (optional, if directly measured or from EPC). If provided, overrides the window U-value used in the heat loss calculation (otherwise derived from window_type).","example":1.4,"type":"number"},"address":{"description":"Full property address (consisting of optional apartment number, street and post town) without postcode","example":"10 Downing Street, Westminster, London","type":"string"},"cibse_design_temperature_version":{"description":"Optional CIBSE weather dataset version to use for this job (2 = 2015 dataset, 3 = 2026 dataset). If omitted, the job inherits the company's configured default at read time.","example":2,"type":"number"},"country":{"description":"The country the property is in. Used to determine grant scheme and paperwork. If omitted, it's derived from the postcode / any linked EPC.","example":"England","type":"string","enum":["England","Scotland","Wales","Northern Ireland","Channel Islands","Isle of Man","Ireland"]},"customer_email":{"description":"Customer's email address","example":"james.bond@example.com","type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"customer_first_name":{"description":"Customer's first name","example":"James","type":"string"},"customer_last_name":{"description":"Customer's last name","example":"Bond","type":"string"},"customer_phone":{"description":"Customer's phone number","example":"07700 900123","type":"string"},"epc_certificate_number":{"description":"EPC Certificate number for an English/Welsh EPC. If this is present, we will make a best-effort attempt to retrieve the EPC data and link it to the job.","example":"0123-4567-8901-2345-6789","type":"string"},"job_name":{"description":"Optional custom name for the job","example":"Bond Residence Heat Pump Installation","type":"string"},"job_reference":{"description":"Optional job reference, which must be unique within the company. If not provided, will be generated from the customer surname. Must be at least 3 characters long and not contain the # character. The API will return an error if the reference is invalid or already exists.","example":"BON-007","type":"string","minLength":3,"pattern":"^[^#]*$"},"lat_lng":{"description":"Latitude and longitude coordinates of the address [lat, lng]","example":[51.5033,-0.1276],"minItems":2,"maxItems":2,"type":"array","items":{"type":"number"}},"outdoor_space":{"description":"Outdoor space availability. Only relevant for flats - indicates where the heat pump could be installed.","example":"ground_floor","type":"string","enum":["ground_floor","upper_floor","none"]},"payaca_project_id":{"description":"The ID of an associated Payaca project. If included and the authed company has a Payaca integration set up, the job in Spruce will be linked to this Payaca project. This ID cannot be changed after the job is created.","example":"deea7132-665b-4531-86fb-072439bd68ba","type":"string"},"source":{"description":"Source of the lead (e.g., 'API', 'Social media', 'Search engine', 'Word of mouth', 'Trade show')","example":"API","type":"string"},"tags":{"description":"Tags to apply to this job. If a tag name does not exist for your company, it will be created automatically.","example":["From API","Referral"],"type":"array","items":{"type":"string","minLength":1}},"udprn":{"description":"Unique Delivery Point Reference Number","example":"23747771","type":"string"},"uprn":{"description":"Unique Property Reference Number. If this is included but the EPC certificate number key is omitted, we will attempt to retrieve the EPC from this. Passing the EPC certificate number is a more reliable method to retrieve an EPC.","example":"100023336956","type":"string"},"year_built":{"description":"Year the property was built.","example":1949,"type":"number"}},"required":["built_form","floor_area_m2","fuel_type","loft_insulation","num_bedrooms","postcode","property_type","wall_type","window_type","address","customer_email","customer_first_name","customer_last_name","customer_phone","lat_lng"]}}}},"responses":{"201":{"description":"Job created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"job_reference":{"description":"The unique job reference, which can be used to search for the job in the Spruce admin panel. It is never used by the API.","example":"custom_ref_001","type":"string"},"url":{"description":"The full admin URL of the job. This URL requires a logged in Spruce account to view.","example":"https://app.spruce.eco/admin/quotes/d87fd67e-f2dd-4363-80ef-b5e9d60048de","type":"string","format":"uri"},"uuid":{"description":"The UUID of the new job, which must be used for interacting with the job via the API in future. We strongly recommend that you store this UUID in your internal systems.","example":"d87fd67e-f2dd-4363-80ef-b5e9d60048de","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$"}},"required":["job_reference","url","uuid"],"additionalProperties":false}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"description":"Additional error details"},"error":{"description":"Error code","type":"string","enum":["not_found","internal_server_error","validation_error","postcode_lookup_failed","unauthorized","forbidden","conflict"]},"message":{"description":"Human-readable error message","type":"string"}},"required":["error"],"additionalProperties":false}}}},"401":{"description":"Unauthorized - missing or invalid token","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"description":"Additional error details"},"error":{"description":"Error code","type":"string","enum":["not_found","internal_server_error","validation_error","postcode_lookup_failed","unauthorized","forbidden","conflict"]},"message":{"description":"Human-readable error message","type":"string"}},"required":["error"],"additionalProperties":false}}}},"403":{"description":"Forbidden - insufficient permissions","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"description":"Additional error details"},"error":{"description":"Error code","type":"string","enum":["not_found","internal_server_error","validation_error","postcode_lookup_failed","unauthorized","forbidden","conflict"]},"message":{"description":"Human-readable error message","type":"string"}},"required":["error"],"additionalProperties":false}}}},"409":{"description":"Conflict - resource already exists","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"description":"Additional error details"},"error":{"description":"Error code","type":"string","enum":["not_found","internal_server_error","validation_error","postcode_lookup_failed","unauthorized","forbidden","conflict"]},"message":{"description":"Human-readable error message","type":"string"}},"required":["error"],"additionalProperties":false}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"JWT","description":"JWT token obtained from /v1/auth endpoint","scheme":"bearer","type":"http"}}}}