# AI Drone Roof Inspection Report — Findings & Repair Scope (`owlabee/drone-roof-inspection-report`) Actor

Turn drone and ground roof photos into a per-building inspection report: defect findings per photo, condition grade, repair scope, materials and labor from your price list, a Markdown report and contact sheet. Rules mode or Claude vision. Pay per photo and per building.

- **URL**: https://apify.com/owlabee/drone-roof-inspection-report.md
- **Developed by:** [Owlabee](https://apify.com/owlabee) (community)
- **Categories:** AI, Real estate
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $20.00 / 1,000 photo analyzed (hosted, fast tier)s

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## AI Drone Roof Inspection Report — Findings & Repair Scope

### What it does

AI Drone Roof Inspection Report turns a set of roof photos into a **preliminary, per-building inspection report** that an estimator can price and a field crew can verify. Give it drone or ground photos (image URLs, `kvs://<storeId>/<key>` references to an Apify key-value store, or `data:` URLs) and it:

1. reads each photo's EXIF (capture time, GPS position, camera make and model) and numbers the photos in input order (Photo 1, Photo 2…);
2. groups the photos into **buildings**: by your own labels when you give them (`"Building 12": "1-7, 158"`), otherwise by GPS distance, otherwise by gaps in capture time;
3. in model modes, sends each photo (downscaled to at most 1120x840 pixels) to **Claude vision**, which returns per-photo **defect findings**: defect type (missing, lifted, creased or torn shingles, granule loss, exposed mat or decking, flashing, chimney, pipe boot or vent, ridge and hip cap, ponding, membrane, debris, gutters, prior repairs), severity, location on the roof, quantity, confidence and the photo number;
4. writes one **building rollup** per roof: roof type, condition grade (good, fair, poor, end-of-life), replacement priority (1 = within 12 months, 2 = within 24, 3 = within 36, 4 = not indicated), observations that cite photo numbers, and a **repair scope** in zones, linear feet and each;
5. turns the scope into **materials in whole packs, a labor bundle and a height charge** with deterministic code (no model) using your price list, or a built-in EXAMPLE price list that you must replace with your own;
6. saves a Markdown **report** (`REPORT`), a JPEG **contact sheet** of the first 100 photos labeled with their numbers (`CONTACT_SHEET`) and a run **summary** (`SUMMARY`) to the key-value store.

Every quantity carries `verificationRequired: true`, and the report states that counts are taken from photographs and will be verified on the roof.

Three analyzers, chosen with the `analyzer` input and recorded on every item as `analysisMethod`:

- **`hosted`** (default when the Actor's model key is configured): Claude on the Actor's own key, tokens included, from $0.02 per photo plus $0.10 per building. Pick a **model tier**: `fast` (Claude Haiku 5.5) or `balanced` (Claude Sonnet 5.5); the `best` tier (Claude Opus 5.5) is available with your own key (`byok`).
- **`byok`** (bring your own key): the same analysis with the `anthropicApiKey` you supply; $0.01 per photo and $0.05 per building here, tokens billed by Anthropic to you.
- **`rules`**: no model. EXIF index, building grouping and contact sheet, with empty findings, for $0.002 per photo; the building items are free in this mode because they are an index without findings. This is what Apify's daily test runs.

Built for roofing contractors, property managers and multifamily owners, insurance-restoration estimators and drone service providers who need to go from a few hundred photos to a building-by-building scope quickly. Results are available as JSON, CSV, Excel, through the API, webhooks and the Apify MCP server.

### How to use

1. Paste your **Roof photo URLs**, one per line, in the order you want them numbered. Optionally add **Building labels** (`[{"label": "Building 1", "photos": "1-12", "stories": 2}]`) and your **Price list** and **Company and property** details.
2. Leave **Analyzer** empty for hosted Claude vision and pick a **Model tier** (`fast` is the default); or choose `byok` and paste your **Anthropic API key**; or choose `rules` for a model-free photo index.
3. Click **Start**. Photo items and building items appear in the dataset as they are ready; open `REPORT` and `CONTACT_SHEET` in the key-value store (Output tab) when the run finishes.

#### Quick start input

The same input the Store runs in its daily automated test, so it is cheap and always available; paste it into the JSON tab of the input form or send it as the body of an API or MCP call:

```json
{
  "imageUrls": [
    "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c8/US_Navy_070706-N-3832S-057_Cyptologic_Technician_Technical_1st_Class_Jason_Guidry_nails_shingles_onto_the_roof_of_a_house_during_a_community_relations_project_with_Habitat_for_Humanity.jpg/960px-US_Navy_070706-N-3832S-057_Cyptologic_Technician_Technical_1st_Class_Jason_Guidry_nails_shingles_onto_the_roof_of_a_house_during_a_community_relations_project_with_Habitat_for_Humanity.jpg",
    "https://upload.wikimedia.org/wikipedia/commons/thumb/a/a2/US_Navy_090611-N-9712C-001_Aviation_Electronics_Technician_2nd_Class_Sonny_Abernathy_nails_shingles_to_the_roof_of_a_Habitat_for_Humanity_house.jpg/960px-US_Navy_090611-N-9712C-001_Aviation_Electronics_Technician_2nd_Class_Sonny_Abernathy_nails_shingles_to_the_roof_of_a_Habitat_for_Humanity_house.jpg",
    "https://upload.wikimedia.org/wikipedia/commons/thumb/f/ff/Staggered_planks_on_the_roof_referred_to_as_%22skip_sheathing%22_forms_the_surface_for_new_cedar_shingles_to_be_attached_to_the_roof._%283d6d81ed-74ae-4577-b77a-8f05e2b5c48b%29.jpg/960px-Staggered_planks_on_the_roof_referred_to_as_%22skip_sheathing%22_forms_the_surface_for_new_cedar_shingles_to_be_attached_to_the_roof._%283d6d81ed-74ae-4577-b77a-8f05e2b5c48b%29.jpg"
  ],
  "analyzer": "rules"
}
```

### Input

| Field | Type | Required | Description |
|---|---|---|---|
| `imageUrls` | string\[] | yes | Photo URLs, `kvs://<storeId>/<key>` references or `data:` URLs, one per line; the first is Photo 1 |
| `buildings` | object\[] | no | Building labels: `{ label, photos ("1-7, 158"), stories, units }`. Unlabeled photos are grouped by GPS, then by time |
| `analyzer` | string | no | `hosted`, `byok` or `rules`. Empty = `hosted` when the Actor's model key is configured, else `rules` |
| `anthropicApiKey` | string (secret) | for `byok` | Your Anthropic API key; used only in `byok` mode |
| `modelTier` | string | no | `fast` (default, Claude Haiku 5.5) or `balanced` (Claude Sonnet 5.5) in `hosted` mode; `byok` also takes `best` (Claude Opus 5.5) |
| `instructions` | string | no | Extra guidance for the model: roof system, what to ignore, local terminology. Ignored by `rules` |
| `priceList` | object | no | `materials` \[{name, packSize, perUnit, appliesTo, priceUsd}], `laborBundles` \[{hours, priceUsd}], `heightCharges` \[{stories, priceUsd}], `laborRounding` ('down' default or 'up'), `hoursPerZone`, `zoneSf`, `currency`. Empty = the EXAMPLE list (placeholder prices) |
| `company` | object | no | Report header: `name`, `email`, `phone`, `website`, `inspector`, `propertyName`, `propertyAddress`, `inspectionDate` |
| `maxPhotos` | integer | no | Photos processed per run, 1–500 (default 200) |
| `photosPerModelCall` | integer | no | Photos of one building per model call, 1–8 (default 4) |
| `groupingDistanceMeters` | number | no | GPS grouping radius, 5–500 m (default 25) |
| `groupingGapMinutes` | number | no | Time gap that starts a new building when there is no GPS, 1–240 min (default 10) |
| `maxConcurrency` | integer | no | Photos processed in parallel, 1–4 (default 2) |
| `maxFileSizeMb` | integer | no | Photos larger than this become a `TOO_LARGE` error item (default 25, max 50) |
| `downloadTimeoutSecs` | integer | no | Per-photo download timeout (default 20, max 120) |
| `includeContactSheet` | boolean | no | Write the `CONTACT_SHEET` JPEG montage (default true) |
| `webhookUrl` | string | no | POST target for the run summary |

### Output example

A photo item from a hosted `fast` run (illustrative values):

```json
{
  "recordType": "photo",
  "photoNumber": 3,
  "url": "https://example.com/inspections/oak-ridge/DJI_0003.JPG",
  "filename": "DJI_0003.JPG",
  "mimeType": "image/jpeg",
  "bytes": 6815244,
  "width": 5472,
  "height": 3648,
  "takenAt": "2026-09-14T10:21:07.000Z",
  "gps": { "lat": 35.22714, "lon": -80.84312, "altitudeM": 241.3 },
  "camera": { "make": "DJI", "model": "FC6310" },
  "orientation": 1,
  "buildingId": "building-1",
  "buildingLabel": "Building 1",
  "analysisMethod": "hosted",
  "modelTier": "fast",
  "model": "claude-haiku-5-5",
  "view": "close-up",
  "roofType": "3-tab asphalt shingle",
  "summary": "South slope near the eave; several missing tabs and a lifted row next to the plumbing vent.",
  "findings": [
    {
      "photoNumber": 3,
      "defectType": "missing-shingles",
      "severity": "moderate",
      "location": "south slope, lower third near eave",
      "description": "About four missing tabs with exposed underlayment.",
      "quantity": 1,
      "unit": "zone",
      "confidence": 0.82,
      "verificationRequired": true
    },
    {
      "photoNumber": 3,
      "defectType": "pipe-boot-or-vent-defect",
      "severity": "minor",
      "location": "plumbing vent, south slope",
      "description": "Cracked rubber collar on the pipe boot.",
      "quantity": 1,
      "unit": "ea",
      "confidence": 0.7,
      "verificationRequired": true
    }
  ],
  "findingCount": 2,
  "confidence": 0.78,
  "tokensIn": 2140,
  "tokensOut": 310,
  "modelCostUsdEstimate": 0.0004,
  "sentWidth": 1120,
  "sentHeight": 747,
  "processingMs": 4210,
  "fetchedAt": "2026-10-19T09:12:44.118Z"
}
```

A building item (estimate computed from the EXAMPLE price list):

```json
{
  "recordType": "building",
  "id": "building-1",
  "label": "Building 1",
  "photoNumbers": [1, 2, 3, 4, 5, 6, 7],
  "groupedBy": "gps",
  "centroid": { "lat": 35.22711, "lon": -80.84309 },
  "stories": 2,
  "units": null,
  "photoRange": "Photos 1–7",
  "photoCount": 7,
  "analysisMethod": "hosted",
  "modelTier": "fast",
  "model": "claude-haiku-5-5",
  "assessment": {
    "roofType": "3-tab asphalt shingle",
    "storiesEstimate": 2,
    "condition": "fair",
    "replacementPriority": 3,
    "observations": [
      "Missing tabs on the south slope near the eave (Photos 3–4).",
      "Cracked pipe boot collar at the plumbing vent (Photo 3).",
      "Ridge cap intact; light granule loss on the west slope (Photo 6)."
    ],
    "notes": null,
    "confidence": 0.74
  },
  "findingCount": 4,
  "findingsBySeverity": { "minor": 2, "moderate": 2, "severe": 0 },
  "findingsByType": { "missing-shingles": 2, "pipe-boot-or-vent-defect": 1, "granule-loss": 1 },
  "estimate": {
    "scope": [
      { "action": "shingle-zone-repair", "description": "Shingle zone repair — remove and replace missing, torn or creased shingles and hand-seal lifted tabs, underlayment as needed (up to 25 sf per zone)", "quantity": 2, "unit": "zone", "photoNumbers": [3, 4], "verificationRequired": true },
      { "action": "boot-or-vent-replacement", "description": "Pipe boot or vent replacement", "quantity": 1, "unit": "ea", "photoNumbers": [3], "verificationRequired": true }
    ],
    "materials": [
      { "name": "3-tab asphalt shingles", "packSize": "bundle (about 33 sf)", "packs": 2, "unitPriceUsd": 50, "amountUsd": 100, "verificationRequired": true },
      { "name": "Roofing nails", "packSize": "5 lb box", "packs": 1, "unitPriceUsd": 30, "amountUsd": 30, "verificationRequired": true },
      { "name": "Roofing cement / sealant", "packSize": "10 oz tube", "packs": 2, "unitPriceUsd": 15, "amountUsd": 30, "verificationRequired": true },
      { "name": "Pipe boot", "packSize": "each", "packs": 1, "unitPriceUsd": 25, "amountUsd": 25, "verificationRequired": true }
    ],
    "materialsSubtotalUsd": 185,
    "labor": { "estimatedHours": 0.8, "bundleHours": 2, "priceUsd": 600, "description": "Repair labor — 2 hours incl. setup, cleanup and haul-off", "verificationRequired": true },
    "height": { "stories": 2, "priceUsd": 150, "description": "Ladders, safety and staging (2-story building)", "verificationRequired": true },
    "totalUsd": 935,
    "priced": true,
    "verificationRequired": true
  },
  "verificationRequired": true,
  "tokensIn": 3920,
  "tokensOut": 540,
  "modelCostUsdEstimate": 0.0007,
  "processingMs": 6120,
  "fetchedAt": "2026-10-19T09:13:02.551Z"
}
```

In `rules` mode photo items carry the EXIF fields and building assignment with `view`, `roofType`, `summary` and `confidence` set to `null` and empty `findings`; building items carry the grouping with `assessment: null` and an empty estimate. If a model call fails or the hosted model is not configured, the item falls back to the rules output with `analysisMethod: "rules-fallback"`, the reason in `analysisWarning`, and is charged the rules price.

Error items look like `{ "recordType": "photo", "photoNumber": 5, "url": "…", "error": "NOT_FOUND", "errorMessage": "HTTP 404 for …" }` with codes `INVALID_URL`, `DOWNLOAD_FAILED`, `NOT_FOUND`, `TIMEOUT`, `SOURCE_UNAVAILABLE`, `TOO_LARGE`, `UNSUPPORTED_TYPE`, `UNREADABLE_IMAGE` and `PROCESSING_FAILED`. They are charged at the rules price, because the work to discover the problem was done.

If the output saves you time, a review on the Store page helps other buyers find this Actor; the Store invites you to leave one after you have run it a few times.

### Pricing

Pay per event. You only pay for what the Actor produces; set a **maximum cost per run** in the run options to cap spend.

| Event | Price | Notes |
|---|---|---|
| Photo indexed (rules, no model) | $0.002 | Per photo item in `rules` mode or on a `rules-fallback`; error items are charged at this price too |
| Photo analyzed (your Anthropic key) | $0.01 | Per photo analyzed by Claude in `byok` mode, any tier; your tokens are billed by Anthropic, not here |
| Building report (your Anthropic key) | $0.05 | Per building rollup in `byok` mode; tokens billed by Anthropic to your key |
| Photo analyzed (hosted, fast tier) | $0.02 | Claude Haiku 5.5 on the Actor's key, tokens included; photo downscaled to at most 1120x840 |
| Building report (hosted, fast tier) | $0.10 | Claude Haiku 5.5, up to 8,000 input tokens of findings per building |
| Photo analyzed (hosted, balanced tier) | $0.08 | Claude Sonnet 5.5, same caps |
| Building report (hosted, balanced tier) | $0.30 | Claude Sonnet 5.5, same caps |
| Actor start | $0.00005 | Apify platform event |

Building items are not charged in `rules` mode: there they are an index without findings.

**Worked example.** A 167-photo inspection of 18 buildings on hosted `fast` ≈ 167 × $0.02 + 18 × $0.10 = **$5.14**, about $5, tokens included. The same job on `balanced` ≈ **$18.76** (167 × $0.08 + 18 × $0.30). Bring your own key: ≈ **$2.57** here (167 × $0.01 + 18 × $0.05) plus your tokens on your Anthropic bill. Rules only: 167 photos ≈ **$0.33**.

**Spending limit and model tiers.** The model is called before its events are charged, so before every model call the Actor checks that your run's maximum total charge still covers the events that call will produce (the photo events for the batch, or the building report). When it does not, the model is not called: the photos get the rules output instead, charged at the rules price, with an `analysisWarning` that names the tier, the amount and the limit to set (the run's `SUMMARY` counts these). When not even a rules item fits, the run stops cleanly with a log line and nothing more is charged. A run with no limit set behaves as before. In practice: set the maximum total charge to at least photos × the photo price plus buildings × the building price for the tier you chose. A limit of $1.00 always covers one batch of photos plus one building report on either hosted tier, even at 8 photos per call on `balanced` (8 × $0.08 + $0.30 = $0.94).

**How the hosted prices are set.** Against the worst case, not the average. Every photo is downscaled to fit 1120x840 pixels before the call, which is at most 1,200 visual tokens (one per 28x28-pixel patch); each call also carries about 870 tokens of instructions, JSON schema and photo headers, shared by the photos in the batch (photos of one building are packed into near-equal batches of up to `photosPerModelCall`), and output is capped at 500 tokens per photo. So the most one photo costs in tokens is, on a 1-photo call: `fast` 2,090 × $0.10/M + 500 × $0.50/M ≈ **$0.00046**; `balanced` 2,090 × $2/M + 500 × $10/M ≈ **$0.0092**; on the default 4-photo batch about 15% less. A building report is text only, capped at 8,000 input tokens of findings and 1,200 output tokens: ≈ **$0.0014** and **$0.028**. Apify pays developers 80% of the price, so every hosted event clears its worst-case token cost by at least 7× (a margin above 85% at the 1-photo worst case: fast 97%, balanced 85.7%, building reports 88% and 98%). Reasoning tokens count inside the output cap (thinking is off on Haiku 5.5 and limited to between tool calls on Sonnet 5.5). The `best` tier (Claude Opus 5.5, which cannot turn thinking off) is offered only with your own key, where you pay Anthropic for its tokens. A model failure is never charged at the hosted price: the item falls back to the rules index at $0.002.

### Limits and fair use

- Photos are fetched with plain HTTP from Apify datacenter IPs, up to 25 MB each by default (50 MB maximum), with a 20 s download timeout by default. JPEG, PNG, WebP, GIF and TIFF are supported; HEIC is detected but usually cannot be decoded on the platform (patent-encumbered codec), so convert iPhone photos to JPEG first; anything else becomes an `UNSUPPORTED_TYPE` or `UNREADABLE_IMAGE` error item.
- Each photo is downscaled to at most 1120x840 pixels before it is sent to the model, so fine detail visible only at full resolution may be missed; shoot close-ups of the areas that matter.
- At most 500 photos per run (`maxPhotos`, default 200). Split larger inspections into several runs.
- The Actor runs with 1–2 GB of memory because full-resolution drone photos (20 MP and up) are decoded in memory for EXIF, downscaling and the contact sheet; keep `maxConcurrency` at 2 on 1 GB. Default run memory is 1024 MB.
- **Time budget.** Each photo gets one download deadline (`downloadTimeoutSecs`, default 20 s) that covers redirects and the retries of a 429 or 5xx response; a header or connection stall is not retried, and a 403 or 404 is never retried. A photo that cannot be downloaded in time becomes a `TIMEOUT` error item, charged at the rules rate like every error item. After three downloads in a row time out on the same host, that host's remaining photos are not requested: each gets one charged `SOURCE_UNAVAILABLE` error item. A model call that takes longer than 60 seconds is cancelled without a retry; the photos in that call get the rules index at the rules rate, and the building rollup is written without the model's assessment (and without a charge).
- Only the URLs you give, your own key-value stores and, in model modes, the Anthropic API are contacted. No proxies, no browser. Your `anthropicApiKey` is stored encrypted by Apify and is never logged or written to the dataset.
- The default price list is an EXAMPLE with placeholder prices, not market pricing; replace it with your own before quoting.
- **Sample photos** (prefilled for the daily test, 960 px Wikimedia Commons thumbnails, all public domain as works of the U.S. federal government): Photo 1, U.S. Navy photo by Mass Communication Specialist 3rd Class Steven Scott Smith; Photo 2, U.S. Navy photo by Mass Communications Specialist 2nd Class John P. Curtis; Photo 3, NPS Photo (U.S. National Park Service, via NPGallery). All via Wikimedia Commons.

### Related Actors

- *AI Document to JSON Extractor (same account): turn supplier quotes and material price sheets (PDF, DOCX) into JSON, then feed the prices into this Actor's `priceList`.*

### Use from AI agents (MCP)

Pin this Actor in the Apify MCP server with one link: `https://mcp.apify.com?tools=owlabee/drone-roof-inspection-report`, or let the agent find it with the `search-actors` tool. It is pay-per-event with limited permissions, so agents can discover it, inspect the input schema with `fetch-actor-details` and pay per run through agentic payments. Prompts that map directly onto the input:

1. "Analyze these 120 drone photos of the Oak Ridge apartments, group them by building and give me a repair scope per roof."
2. "Index these roof photos by building using GPS and make a contact sheet, no AI analysis."
3. "Inspect these roof photos with my Anthropic key on the balanced tier and price the repairs with my price list."

The agent receives the same dataset records shown in the output example above.

### FAQ

**Does this need proxies?** No. Photos are downloaded over plain HTTP from the URLs you supply, or read from your own key-value stores; there is no third-party site to scrape. An API key is needed only for `byok` mode.

**How is cost controlled?** Cost = photos × the photo price of your mode and tier, plus buildings × the building price in model modes. Use `maxPhotos`, drop photos that do not show a roof, and set the run's maximum total charge; the Actor checks it before every model call and stops cleanly when it is reached. Every model-mode item records `tokensIn`, `tokensOut` and `modelCostUsdEstimate`, and `SUMMARY` totals them.

**How accurate are the counts?** They are estimates from photographs, not measurements. The model counts what it can see in a downscaled photo, so counting is approximate: hidden damage, overlapping photos and shadows can push numbers up or down. Every quantity is marked `verificationRequired: true` and must be verified on the roof before any material is ordered.

**Can I supply my own price list?** Yes. Pass `priceList` with your materials (pack size, packs per unit of repair, price), labor bundles, height charges, hours per zone and zone size. The math is deterministic code: packs are rounded up to whole packs per building, the labor bundle is, by default (`laborRounding` 'down', as in the reference report), the largest one whose hours do not exceed the estimated hours, or with `laborRounding` 'up' the smallest one that covers them (never below the smallest bundle, never above the largest, which caps the labor charge), and the height charge follows the story count. Without it the Actor uses an EXAMPLE list whose prices are placeholders.

**How are photos grouped into buildings?** Your `buildings` labels come first (photo numbers and ranges like "1-7, 158"). When at least 60% of the remaining photos carry GPS, they are clustered by distance (`groupingDistanceMeters`, default 25 m) and a photo without GPS joins its neighbor in the sequence; otherwise, when most photos carry a capture time, a new building starts where the time jumps by more than `groupingGapMinutes` (default 10); with neither, every photo goes into one building. Each building item says how it was formed in `groupedBy`.

**Can an AI agent use this?** Yes; see "Use from AI agents (MCP)" above for the pin link and example prompts. Schedules and webhooks work the same way from the Console or the API.

**Can I try it for free?** Yes. The Apify free plan comes with monthly platform credit, and the quick-start input above costs a few cents per run, so you can test it on your own inputs before paying anything.

**How do I find this Actor again?** Bookmark it on the Store page (the bookmark icon next to the title); bookmarked Actors are listed in your Console, and the Store page shows new versions as they ship.

### Disclaimer

Counts and quantities are taken from photographs and will be verified on the roof before any material is ordered or work is scheduled. The report is preliminary, prepared for estimating and field verification, and makes no engineering, structural, code-compliance or insurance determination. The Actor is not a licensed inspector, and its output is machine-generated and may contain errors. You are responsible for having the right to process the images you supply. In model modes the photos are sent to the Anthropic API solely to produce the analysis; in `rules` mode nothing leaves Apify. No customer data is stored beyond your own run storages (dataset and key-value store).

# Actor input Schema

## `imageUrls` (type: `array`):

Roof photos to inspect, drone or ground, one per line, in the order you want them numbered (the first line is Photo 1): public http(s) image URLs, Apify key-value-store references in the form kvs://<storeId>/<key> (use kvs://default/<key> for this run's own store), or data: URLs with inline base64 images. JPEG, PNG, WebP, GIF and TIFF are supported; HEIC usually cannot be decoded on the platform, convert it to JPEG first. EXIF time, GPS and camera are read from each file. The three prefilled photos (public-domain U.S. government photos from Wikimedia Commons) are the small, fast set used by Apify's daily automated test.

## `buildings` (type: `array`):

Tell the Actor which photos belong to which building, as a JSON array of objects: `label` (e.g. "Building 12" or "Clubhouse"), `photos` (photo numbers and ranges like "1-7, 158"), `stories` (integer, used for the height charge) and `units` (free text, e.g. "12-17"). Photos not covered by a label are grouped automatically. Without labels, photos are grouped by GPS distance when they carry GPS, otherwise by gaps in the capture time. Example: \[{"label": "Building 1", "photos": "1-12", "stories": 2, "units": "101-108"}].

## `analyzer` (type: `string`):

Leave empty for the default (the Console prefills `rules` so Apify's daily automated test never calls a model; clear it or pick a mode): Claude vision on the Actor's own key (`hosted`) when the Actor has its model key configured, otherwise the free-of-model rules analyzer. Set `rules` for a model-free run (EXIF index, grouping into buildings, contact sheet, empty findings; no key), or `byok` to use your own Anthropic API key (tokens billed by Anthropic to you). In model modes a model problem never fails the run: the photo gets the rules output, `analysisMethod: "rules-fallback"` with an `analysisWarning`, and is charged the rules price.

## `anthropicApiKey` (type: `string`):

Your Anthropic API key, used only when Analyzer is `byok`. Stored encrypted as a secret and sent only to api.anthropic.com. Get one at console.anthropic.com.

## `modelTier` (type: `string`):

`fast` (default): Claude Haiku 5.5, cheapest and quick, fine for clear drone overviews and close-ups. `balanced`: Claude Sonnet 5.5, better on subtle defects (granule loss, lifted tabs, flashing details). `best`: Claude Opus 5.5 for the hardest photo sets, available only with your own key (analyzer `byok`); a hosted run with `best` is rejected before any charge. In hosted mode the tier sets the per-photo and per-building prices ($0.02 + $0.10 for fast, $0.08 + $0.30 for balanced); in byok mode the tier only changes what Anthropic bills you.

## `instructions` (type: `string`):

Extra guidance for the model, in plain language: the roof system ("architectural asphalt shingles over plywood", "TPO membrane on the flat sections"), what to ignore ("skip the solar panels", "photos 40-45 are the parking lot"), or local terminology ("we call the cap shingles 'ridge'"). Ignored by the rules analyzer.

## `priceList` (type: `object`):

Your own prices for the deterministic estimate, as JSON: `materials` \[{name, packSize, perUnit, appliesTo, priceUsd}] where `perUnit` is packs per unit of the repair action named in `appliesTo` (shingle-zone-repair, cap-repair, flashing-repair, boot-or-vent-replacement…), rounded up to whole packs per building; `laborBundles` \[{hours, priceUsd}] (the largest bundle whose hours do not exceed the estimated hours, never below the smallest is used); `heightCharges` \[{stories, priceUsd}]; `laborRounding` 'down' (default: largest bundle at or below the estimated hours, as in the reference report) or 'up' (smallest bundle that covers them); `hoursPerZone` (default 0.4); `zoneSf` (repair zone size in square feet, default 25); `currency`. Leave empty to use the built-in EXAMPLE price list, which is a placeholder and not market pricing: replace it with yours before quoting. All quantities are photo estimates and are field-verified on the roof.

## `company` (type: `object`):

Header details for the report, as JSON: `name`, `email`, `phone`, `website`, `inspector`, `propertyName`, `propertyAddress`, `inspectionDate`. All optional; missing fields are left out of the report.

## `maxPhotos` (type: `integer`):

Upper bound on photos processed in this run; photos beyond it are skipped and not charged. Use it together with the run's maximum total charge to control spend.

## `photosPerModelCall` (type: `integer`):

How many photos of the same building are sent to the model in one call (model modes). Larger batches give the model more context per call; smaller batches cost less per call when the spending limit is tight.

## `groupingDistanceMeters` (type: `number`):

Photos with GPS whose positions are within this distance of a building's photos are grouped into the same building. Used only for photos not covered by Building labels.

## `groupingGapMinutes` (type: `number`):

For photos without GPS, a gap in capture time longer than this starts a new building. Used only for photos not covered by Building labels.

## `maxConcurrency` (type: `integer`):

Photos downloaded and decoded, and model calls made, in parallel. Large photos need memory: keep it at 2 on 1 GB.

## `maxFileSizeMb` (type: `integer`):

Photos larger than this are rejected with a TOO_LARGE error item instead of being downloaded. Raise it up to 50 MB for full-resolution drone photos.

## `downloadTimeoutSecs` (type: `integer`):

Maximum time to download one photo before it is recorded as a TIMEOUT error item.

## `includeContactSheet` (type: `boolean`):

Also write CONTACT_SHEET to the key-value store: a JPEG montage of the first 100 photos labeled with their photo numbers, handy for the field crew.

## `webhookUrl` (type: `string`):

If set, the Actor POSTs a JSON summary of the run (photos, buildings, errors, findings, model token totals, charges) to this URL when it finishes — 10 s timeout, 2 retries. Failures are logged, never fatal.

## Actor input object example

```json
{
  "imageUrls": [
    "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c8/US_Navy_070706-N-3832S-057_Cyptologic_Technician_Technical_1st_Class_Jason_Guidry_nails_shingles_onto_the_roof_of_a_house_during_a_community_relations_project_with_Habitat_for_Humanity.jpg/960px-US_Navy_070706-N-3832S-057_Cyptologic_Technician_Technical_1st_Class_Jason_Guidry_nails_shingles_onto_the_roof_of_a_house_during_a_community_relations_project_with_Habitat_for_Humanity.jpg",
    "https://upload.wikimedia.org/wikipedia/commons/thumb/a/a2/US_Navy_090611-N-9712C-001_Aviation_Electronics_Technician_2nd_Class_Sonny_Abernathy_nails_shingles_to_the_roof_of_a_Habitat_for_Humanity_house.jpg/960px-US_Navy_090611-N-9712C-001_Aviation_Electronics_Technician_2nd_Class_Sonny_Abernathy_nails_shingles_to_the_roof_of_a_Habitat_for_Humanity_house.jpg",
    "https://upload.wikimedia.org/wikipedia/commons/thumb/f/ff/Staggered_planks_on_the_roof_referred_to_as_%22skip_sheathing%22_forms_the_surface_for_new_cedar_shingles_to_be_attached_to_the_roof._%283d6d81ed-74ae-4577-b77a-8f05e2b5c48b%29.jpg/960px-Staggered_planks_on_the_roof_referred_to_as_%22skip_sheathing%22_forms_the_surface_for_new_cedar_shingles_to_be_attached_to_the_roof._%283d6d81ed-74ae-4577-b77a-8f05e2b5c48b%29.jpg"
  ],
  "analyzer": "rules",
  "modelTier": "fast",
  "maxPhotos": 200,
  "photosPerModelCall": 4,
  "groupingDistanceMeters": 25,
  "groupingGapMinutes": 10,
  "maxConcurrency": 2,
  "maxFileSizeMb": 25,
  "downloadTimeoutSecs": 20,
  "includeContactSheet": true
}
```

# Actor output Schema

## `results` (type: `string`):

Photo items (recordType "photo": photoNumber, url, EXIF time, GPS and camera, buildingLabel, view, roofType, summary, findings, findingCount, confidence, analysisMethod) and building items (recordType "building": label, photoRange, photoCount, assessment, findingsBySeverity, findingsByType, estimate) in the default dataset; error items carry error and errorMessage. Fields are described in dataset_schema.json.

## `report` (type: `string`):

REPORT record in the run's default key-value store: the preliminary roof inspection report in Markdown, one section per building with condition, observations citing photo numbers, repair scope, materials, labor and height charge, and the field-verification notice.

## `contactSheet` (type: `string`):

CONTACT_SHEET record in the run's default key-value store: a JPEG montage of the photos with their photo numbers, written when includeContactSheet is on.

## `summary` (type: `string`):

SUMMARY record in the run's default key-value store: photos, buildings, errors, findings, model token totals and cost estimate, charges and webhook delivery status, written when the run finishes.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "imageUrls": [
        "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c8/US_Navy_070706-N-3832S-057_Cyptologic_Technician_Technical_1st_Class_Jason_Guidry_nails_shingles_onto_the_roof_of_a_house_during_a_community_relations_project_with_Habitat_for_Humanity.jpg/960px-US_Navy_070706-N-3832S-057_Cyptologic_Technician_Technical_1st_Class_Jason_Guidry_nails_shingles_onto_the_roof_of_a_house_during_a_community_relations_project_with_Habitat_for_Humanity.jpg",
        "https://upload.wikimedia.org/wikipedia/commons/thumb/a/a2/US_Navy_090611-N-9712C-001_Aviation_Electronics_Technician_2nd_Class_Sonny_Abernathy_nails_shingles_to_the_roof_of_a_Habitat_for_Humanity_house.jpg/960px-US_Navy_090611-N-9712C-001_Aviation_Electronics_Technician_2nd_Class_Sonny_Abernathy_nails_shingles_to_the_roof_of_a_Habitat_for_Humanity_house.jpg",
        "https://upload.wikimedia.org/wikipedia/commons/thumb/f/ff/Staggered_planks_on_the_roof_referred_to_as_%22skip_sheathing%22_forms_the_surface_for_new_cedar_shingles_to_be_attached_to_the_roof._%283d6d81ed-74ae-4577-b77a-8f05e2b5c48b%29.jpg/960px-Staggered_planks_on_the_roof_referred_to_as_%22skip_sheathing%22_forms_the_surface_for_new_cedar_shingles_to_be_attached_to_the_roof._%283d6d81ed-74ae-4577-b77a-8f05e2b5c48b%29.jpg"
    ],
    "analyzer": "rules"
};

// Run the Actor and wait for it to finish
const run = await client.actor("owlabee/drone-roof-inspection-report").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "imageUrls": [
        "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c8/US_Navy_070706-N-3832S-057_Cyptologic_Technician_Technical_1st_Class_Jason_Guidry_nails_shingles_onto_the_roof_of_a_house_during_a_community_relations_project_with_Habitat_for_Humanity.jpg/960px-US_Navy_070706-N-3832S-057_Cyptologic_Technician_Technical_1st_Class_Jason_Guidry_nails_shingles_onto_the_roof_of_a_house_during_a_community_relations_project_with_Habitat_for_Humanity.jpg",
        "https://upload.wikimedia.org/wikipedia/commons/thumb/a/a2/US_Navy_090611-N-9712C-001_Aviation_Electronics_Technician_2nd_Class_Sonny_Abernathy_nails_shingles_to_the_roof_of_a_Habitat_for_Humanity_house.jpg/960px-US_Navy_090611-N-9712C-001_Aviation_Electronics_Technician_2nd_Class_Sonny_Abernathy_nails_shingles_to_the_roof_of_a_Habitat_for_Humanity_house.jpg",
        "https://upload.wikimedia.org/wikipedia/commons/thumb/f/ff/Staggered_planks_on_the_roof_referred_to_as_%22skip_sheathing%22_forms_the_surface_for_new_cedar_shingles_to_be_attached_to_the_roof._%283d6d81ed-74ae-4577-b77a-8f05e2b5c48b%29.jpg/960px-Staggered_planks_on_the_roof_referred_to_as_%22skip_sheathing%22_forms_the_surface_for_new_cedar_shingles_to_be_attached_to_the_roof._%283d6d81ed-74ae-4577-b77a-8f05e2b5c48b%29.jpg",
    ],
    "analyzer": "rules",
}

# Run the Actor and wait for it to finish
run = client.actor("owlabee/drone-roof-inspection-report").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "imageUrls": [
    "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c8/US_Navy_070706-N-3832S-057_Cyptologic_Technician_Technical_1st_Class_Jason_Guidry_nails_shingles_onto_the_roof_of_a_house_during_a_community_relations_project_with_Habitat_for_Humanity.jpg/960px-US_Navy_070706-N-3832S-057_Cyptologic_Technician_Technical_1st_Class_Jason_Guidry_nails_shingles_onto_the_roof_of_a_house_during_a_community_relations_project_with_Habitat_for_Humanity.jpg",
    "https://upload.wikimedia.org/wikipedia/commons/thumb/a/a2/US_Navy_090611-N-9712C-001_Aviation_Electronics_Technician_2nd_Class_Sonny_Abernathy_nails_shingles_to_the_roof_of_a_Habitat_for_Humanity_house.jpg/960px-US_Navy_090611-N-9712C-001_Aviation_Electronics_Technician_2nd_Class_Sonny_Abernathy_nails_shingles_to_the_roof_of_a_Habitat_for_Humanity_house.jpg",
    "https://upload.wikimedia.org/wikipedia/commons/thumb/f/ff/Staggered_planks_on_the_roof_referred_to_as_%22skip_sheathing%22_forms_the_surface_for_new_cedar_shingles_to_be_attached_to_the_roof._%283d6d81ed-74ae-4577-b77a-8f05e2b5c48b%29.jpg/960px-Staggered_planks_on_the_roof_referred_to_as_%22skip_sheathing%22_forms_the_surface_for_new_cedar_shingles_to_be_attached_to_the_roof._%283d6d81ed-74ae-4577-b77a-8f05e2b5c48b%29.jpg"
  ],
  "analyzer": "rules"
}' |
apify call owlabee/drone-roof-inspection-report --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,owlabee/drone-roof-inspection-report"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/yxgGyqTGBFKAR5xPY/builds/BbSGdQ8YGn2OyfyhS/openapi.json
