RoofLine360 API Get an API key

Developer API · v1

Roof measurements, by API.

Send an address, get back a measured roof: slope areas, pitches, edge lengths, a material takeoff, 3D geometry, the LiDAR point cloud and four solar layouts. The same data behind the RoofLine360 workspace and PDF report, for your CRM, quoting tool or AI assistant.

  • REST + JSONOne key, plain HTTPS
  • MCP serverFor Claude and other AI assistants
  • Across the USAny address with LiDAR coverage
Request
curl https://roofline360.com/api/v1/roofs \
  -H "Authorization: Bearer $RL360_KEY" \
  -H "Content-Type: application/json" \
  -d '{"address": "10360 Wylde Point Ln, Houston, TX"}'
Response · 200
{
  "id": "h271360-3290360",
  "status": "ready",
  "summary": {
    "slopes": 14,
    "roof_area_ft2": 5605.7,
    "squares": 56.06,
    "ridge_height_m": 10.3
  },
  "links": {
    "measurements": ".../roofs/h271360-3290360/measurements",
    "solar": ".../roofs/h271360-3290360/solar",
    "report": ".../roofs/h271360-3290360/report.pdf"
  }
}

What one address gives you

Every product below comes from the same roof model. Build it once; every later call reads the saved model and answers in milliseconds.

Quickstart

Three calls from an address to a material list.

  1. 1

    Authenticate

    Every request carries your key in the Authorization header. Keys start with rl360_. Keep them on your server, never in a public web page.

    export RL360_KEY="rl360_..."
    export RL360="https://roofline360.com/api/v1"
    curl $RL360/usage -H "Authorization: Bearer $RL360_KEY"
  2. 2

    Measure an address

    A house measured before answers 200 with status ready. A new one answers 202; poll it until it is ready, usually 10 to 60 seconds, up to a few minutes for the first roof in an area.

    curl $RL360/roofs -H "Authorization: Bearer $RL360_KEY" \
      -H "Content-Type: application/json" \
      -d '{"address": "10360 Wylde Point Ln, Houston, TX"}'
    
    # then, until "status" is "ready":
    curl $RL360/roofs/h271360-3290360 -H "Authorization: Bearer $RL360_KEY"
  3. 3

    Read the measurements

    Areas, edges and the material takeoff in one call. Change unit, waste and bundles_per_square to match your quote.

    curl "$RL360/roofs/h271360-3290360/measurements?unit=us&waste=12" \
      -H "Authorization: Bearer $RL360_KEY"

Endpoints

Base URL https://roofline360.com. All responses are JSON unless noted. Open an endpoint for its parameters and an example answer.

Roofs

POST/api/v1/roofsMeasure the roof at an address

Looks the address up and starts the roof model, or returns the saved roof when this house was measured before. Send address, or lat and lon. A new roof counts against your daily limit only when it is built successfully; a failed build and a saved roof do not.

Body fieldTypeDescription
addressstringStreet address, up to 200 characters.
lat, lonnumberThe rooftop point, instead of an address.

200 ready · 202 queued or building · 400 address not found · 429 daily limit reached

GET/api/v1/roofs/{id}Status and summary

status moves queued → building → ready or failed. While building, stage says where: downloading (survey data for a new area) or processing (the roof model). links lists every product once the roof is ready.

{
  "object": "roof",
  "id": "h271360-3290360",
  "status": "ready",
  "address": "10360 Wylde Point Ln, Houston, Texas, 77002",
  "location": { "lat": 29.722357, "lon": -95.363715, "utm_epsg": 6344 },
  "created_at": "2026-11-12 10:06",
  "finished_at": "2026-11-12 10:06",
  "summary": {
    "slopes": 14, "roof_area_m2": 520.79, "roof_area_ft2": 5605.7,
    "squares": 56.06, "footprint_m2": 414.09, "ridge_height_m": 10.3
  },
  "links": {
    "self": "https://roofline360.com/api/v1/roofs/h271360-3290360",
    "workspace": "https://roofline360.com/workspace/#h271360-3290360",
    "measurements": "…/measurements", "geometry": "…/geometry", "scan": "…/scan",
    "solar": "…/solar", "report": "…/report.pdf"
  }
}
GET/api/v1/roofsRoofs your key created

Newest first, as {"object": "list", "data": [roof, …]}. Query limit, 1–100, default 50.

Measurements

GET/api/v1/roofs/{id}/measurementsAreas, pitches, edges, material takeoff
QueryDefaultDescription
unitusus (ft, ft²) or metric (m, m²).
waste10Waste allowance, percent, 0–100.
bundles_per_square3Shingle bundles per 100 ft², 1–20.
{
  "object": "roof_measurements",
  "unit": "us",
  "units": { "length": "ft", "area": "ft²" },
  "roof": {
    "surface_area": 5605.7, "plan_area": 4457.1, "footprint_area": 4457.2,
    "squares": 56.06, "ridge_height": 33.8, "predominant_pitch": "11.9/12",
    "slope_count": 14, "obstacle_count": 7
  },
  "edges": {
    "eave": 219.7, "rake": 106.8, "ridge": 61.0, "hip": 244.8,
    "valley": 146.6, "step": 68.5, "slope_break": 0.0, "total": 847.3
  },
  "materials": {
    "waste_percent": 10, "bundles_per_square": 3,
    "coverage_area": 6166.3, "squares": 61.66, "shingle_bundles": 185,
    "underlayment_area": 5605.7, "starter_drip_edge": 326.5,
    "hip_ridge_cap": 305.7, "valley_lining": 146.6
  },
  "slopes": [
    { "id": 0, "pitch": "11.9/12", "slope_deg": 44.8, "azimuth_deg": 102.8,
      "facing": "E", "plan_area": 850.6, "surface_area": 1198.1 },
    { "id": 2, "pitch": "7.9/12", "slope_deg": 33.5, "azimuth_deg": 192.8,
      "facing": "S", "plan_area": 758.4, "surface_area": 909.6 }
    …
  ],
  "obstacles": [ { "id": "O3", "kind": "vent or pipe", "slope_id": 2, "plan_area": 2.9, "height": 0.2 } … ]
}
GET/api/v1/roofs/{id}/geometry3D roof as GeoJSON

One FeatureCollection. properties.layer is facet (a slope), step_side (a wall between roof levels), edge (with kind), footprint or obstacle. Each position carries an elevation in metres. Properties are metric.

QueryDefaultDescription
crswgs84wgs84: [lon, lat, z]. utm: metres of the house's UTM zone (location.utm_epsg), for CAD.
{
  "type": "FeatureCollection",
  "features": [
    { "type": "Feature",
      "properties": { "layer": "facet", "id": 4, "pitch_in_12": 11.9, "slope_deg": 44.8,
                      "area_true_m2": 28.58, "z_min": 20.4, "z_max": 27.82 },
      "geometry": { "type": "Polygon", "coordinates": [[[-95.36379, 29.72241, 20.399], …]] } },
    { "type": "Feature",
      "properties": { "layer": "edge", "kind": "eave", "length_3d_m": 3.51 },
      "geometry": { "type": "LineString", "coordinates": [[-95.36380, 29.72244, 20.399], …] } }
  ]
}
GET/api/v1/roofs/{id}/scanClassified LiDAR points

Column arrays x, y, z in metres relative to origin (UTM zone of the house) and cat, an index into categories: ground, noise, low returns, elevated surroundings, other roof candidates, selected building. About 30,000 points, 1–2 MB.

GET/api/v1/roofs/{id}/report.pdfCustomer-ready PDF

The full report as application/pdf. Takes unit, waste, bundles_per_square and the solar options with a solar_ prefix: solar_layout (sun, max, portrait, landscape), solar_module, solar_setback, solar_loss, solar_excluded. A few seconds for the first copy; repeats come from storage.

Solar

GET/api/v1/roofs/{id}/solarCompare four panel layouts

Flush-mounted panels on the actual slope polygons, clear of edges and obstacles. sun uses the best slopes only, max fills every slope, portrait and landscape fix the orientation. Production comes from a PVGIS typical weather year at the property.

QueryDefaultDescription
moduleres430A key from /solar/modules, or custom with length, width (m) and watts.
setback0.6Clearance from every slope edge, metres, 0–3.
loss14System losses, percent, 0–50.
excludeSlope ids to leave empty, e.g. 3,7.
{
  "object": "solar_summary",
  "module": { "key": "res430", "name": "Residential", "watts": 430, "length_m": 1.722, "width_m": 1.134 },
  "setback_m": 0.6, "loss_percent": 14,
  "weather_source": "PVGIS-ERA5 · Houston TMY (2005–2023)",
  "most_production": "max",
  "layouts": {
    "sun": { "name": "Best sun", "panels": 46, "installed_kw_dc": 19.78, "annual_kwh": 27905,
             "specific_yield_kwh_per_kwp": 1411, "shade_loss_percent": 0.5, "monthly_kwh": [1736, 2070, …] },
    "max": { "name": "Max fill", "panels": 76, "installed_kw_dc": 32.68, "annual_kwh": 42334,
             "specific_yield_kwh_per_kwp": 1295, "shade_loss_percent": 3.1, "monthly_kwh": [2454, 3012, …] },
    "portrait":  { "panels": 65, "installed_kw_dc": 27.95, "annual_kwh": 36527, … },
    "landscape": { "panels": 75, "installed_kw_dc": 32.25, "annual_kwh": 41864, … }
  },
  "slopes": [ { "id": 3, "azimuth_deg": 193.1, "tilt_deg": 26.5, "area_m2": 91.4, "usable_area_m2": 66.4,
                "open_sky_yield_kwh_per_kwp": 1458,
                "panels": { "sun": 23, "max": 23, "portrait": 22, "landscape": 23 },
                "annual_kwh": { "sun": 14409, "max": 14409, "portrait": 13783, "landscape": 14409 } } … ]
}
GET/api/v1/solar/modulesPanel formats

Eight example formats from 120 W to 720 W, by key: name, watts, length and width in metres.

Account

GET/api/v1/usageYour key and today's builds

builds_today (successful builds), builds_in_progress, failed_today (not counted), daily_build_limit, remaining_today and the number of roofs your key created.

POST/mcpModel Context Protocol server

JSON-RPC 2.0 over Streamable HTTP for AI assistants. Same key. See MCP.

MCP for AI assistants

Connect Claude, Cursor or any MCP client and ask in plain words: “How many bundles for 10360 Wylde Point Ln at 12% waste?”

  • measure_roofAddress or lat/lon → roof id and measurements. Waits up to 55 s for a new model.
  • get_roofStatus and summary by id.
  • get_roof_measurementsAreas, edges, takeoff and slopes, US or metric, any waste allowance.
  • plan_solarFour layouts with kW DC and kWh per year for a panel format.
  • list_my_roofsRoofs measured with this key.
  • list_solar_modulesPanel formats plan_solar accepts.

Endpoint https://roofline360.com/mcp, Streamable HTTP, stateless. Authenticate with your API key as a Bearer header.

claude mcp add --transport http roofline360 \
  https://roofline360.com/mcp \
  --header "Authorization: Bearer rl360_..."

How many shingle bundles do I need for 10360 Wylde Point Ln, Houston, at 12% waste?

measure_roof · get_roof_measurements(waste_percent=12)

The roof has 14 slopes and 5,606 ft² of surface (56.1 squares, mostly 11.9/12). At 12% waste that is 62.8 squares: 189 bundles at 3 per square, plus 327 ft of starter and drip edge, 306 ft of hip and ridge cap and 147 ft of valley lining.

Try it

Live requests to this server with your key. The key stays in this browser tab only.

Response
Send a request to see the answer.

Errors, limits and units

Errors

Every error is {"error": {"code", "message"}} with an HTTP status.

401unauthorizedMissing or unknown key.
400invalid_parameterBad option, or the address was not found.
404not_foundNo roof with this id.
409not_readyStill building; poll the roof.
409build_failedNo LiDAR or no building at this point.
429rate_limitedDaily limit of successful new roofs reached.
503server_busyThe roof server is starting; retry in a moment.

Limits

A key starts with 25 new roofs per day. Only roofs that build successfully count: a failed build (no LiDAR, no building at the point) is free, and a build still running holds its place until it ends. Roofs measured before, by anyone, do not count, and reading a ready roof is not limited. Ask for more when you request your key.

Units and coordinates

Measurements follow unit. Geometry, scan and solar are metric. Azimuths are degrees clockwise from true north. Elevations are metres in the survey's vertical datum (NAVD88 in the continental US).

Accuracy

Measurements are estimates from aerial LiDAR, typically within a few percent of a tape measure on clean roofs. Verify on site before ordering. Solar figures are a screening estimate, not an engineering design.

On the roadmap

Planned additions to the API. Tell us which you need first when you request a key.

Get an API key

Sign in with your work email and tell us what you are building. Once we approve access, you create your keys right here.