{
  "openapi": "3.0.1",
  "info": {
    "title": "Angi Scraper",
    "description": "[💰 $0.8 / 1K] Extract contractor and home-service pro listings from Angi.com - business names, phone numbers, addresses, ratings, reviews, hours, and photos. Search by keyword and location, browse by category, paste Angi URLs directly. Optional per-pro reviews extraction.",
    "version": "1.1",
    "x-build-id": "ff3K3lvVY0XwaEtaJ"
  },
  "servers": [
    {
      "url": "https://api.apify.com/v2"
    }
  ],
  "paths": {
    "/acts/solidcode~angi-scraper/run-sync-get-dataset-items": {
      "post": {
        "operationId": "run-sync-get-dataset-items-solidcode-angi-scraper",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/acts/solidcode~angi-scraper/runs": {
      "post": {
        "operationId": "runs-sync-solidcode-angi-scraper",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor and returns information about the initiated run in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/runsResponseSchema"
                }
              }
            }
          }
        }
      }
    },
    "/acts/solidcode~angi-scraper/run-sync": {
      "post": {
        "operationId": "run-sync-solidcode-angi-scraper",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "inputSchema": {
        "type": "object",
        "properties": {
          "searchTerms": {
            "title": "Search Terms",
            "type": "array",
            "description": "Keywords or service names to search for on Angi (e.g. \"plumber\", \"roof repair\", \"HVAC contractor\"). Each term runs as its own search and is combined with the Location below. If you also pick a Service Category, that category is searched as well, in addition to your terms - each distinct trade is searched exactly once, so nothing is collected or charged twice. Terms are auto-mapped to Angi's nearest matching trade category (close-but-not-exact terms like \"plumber repair\" may be skipped if no category matches well - pick the Service Category dropdown for exact control). Leave empty if you only want to use a category browse or Start URLs.",
            "items": {
              "type": "string"
            }
          },
          "location": {
            "title": "Location",
            "type": "string",
            "description": "City and state (e.g. \"Austin, TX\") or 5-digit US ZIP code (e.g. \"78701\"). Combined with each search term and with the category below. Leave empty if you only want to use Start URLs."
          },
          "category": {
            "title": "Service Category",
            "enum": [
              "",
              "plumbing",
              "electrical",
              "hvac",
              "roofing",
              "painting",
              "landscaping",
              "lawn_care",
              "tree_service",
              "handyman",
              "cleaning",
              "pest_control",
              "remodeling",
              "kitchen_remodeling",
              "bathroom_remodeling",
              "flooring",
              "carpet_cleaning",
              "appliance_repair",
              "garage_door",
              "fencing",
              "deck_patio",
              "pool_spa",
              "moving",
              "junk_removal",
              "windows",
              "siding",
              "gutter",
              "concrete",
              "masonry",
              "drywall",
              "general_contracting"
            ],
            "type": "string",
            "description": "Optional Angi service category to browse. Use this when you want every pro in a single trade for your location, instead of a specific keyword. Combine with Location for a city-wide trade sweep. If you also enter Search Terms, this category is searched in addition to them (never instead of them), and any duplicate trade is searched only once."
          },
          "startUrls": {
            "title": "Start URLs",
            "type": "array",
            "description": "Paste any Angi URLs directly: pro profile pages, category hubs, city or state hubs, or ZIP search pages (e.g. https://www.angi.com/companylist/us/tx/austin/plumbing.htm). When provided, these are scraped in addition to the keyword and category search above.",
            "items": {
              "type": "string"
            }
          },
          "maxResults": {
            "title": "Maximum Pros (whole run)",
            "minimum": 0,
            "type": "integer",
            "description": "The total number of pros to save for the entire run - across every search term, every category and every start URL combined, and across every city a location expands into. This is a hard limit: the actor never saves, and you are never charged for, more pro records than this. When you enter several search terms or a location that covers several cities (New York, for example, is searched borough by borough), the total is shared out evenly between them, and whatever one search does not use is passed on to the next. Set to 0 for unlimited (every result Angi returns). This counts PROS ONLY - if you turn \"Include Reviews\" on, each pro also brings its own review rows on top, so your worst case becomes Maximum Pros x (1 + Max Reviews per Pro). Use \"Maximum Rows in Total\" below if you want a single hard number for the whole run. Note: small-to-medium markets may have fewer unique pros than the limit (Angi often serves only 30-60 unique pros for a single trade in a single city).",
            "default": 100
          },
          "detailLevel": {
            "title": "Detail Level",
            "enum": [
              "listing",
              "full"
            ],
            "type": "string",
            "description": "\"Listing only\" returns just what's shown on each search result card (cheap, fast). \"Full profile\" also visits each pro's profile page to get hours, photo gallery, full address, description, and the most complete contact info.",
            "default": "full"
          },
          "keepPartialProfiles": {
            "title": "Keep incomplete profiles",
            "type": "boolean",
            "description": "Now and then Angi refuses to open a pro's profile page, so that record comes back with no phone, rating or hours. By default those records are dropped, so you are never charged for a result that is missing the details you asked for. Turn this on if you would rather keep them anyway - they arrive with \"detailFetchFailed\" set to true so you can filter them out yourself. Only applies when Detail Level is \"Full profile\".",
            "default": false
          },
          "includeReviews": {
            "title": "Include Reviews",
            "type": "boolean",
            "description": "When on, fetches each pro's reviews and saves them as separate rows in the dataset (one row per review). Review rows are extra: they are NOT counted against \"Maximum Pros\", so a pro with 50 reviews adds 50 rows on top of that pro's own row. With reviews on, the most rows a run can save is Maximum Pros x (1 + Max Reviews per Pro). Set \"Max Reviews per Pro\" to keep that number where you want it, or set \"Maximum Rows in Total\" for a single hard stop.",
            "default": false
          },
          "maxReviewsPerPro": {
            "title": "Max Reviews per Pro",
            "minimum": 0,
            "type": "integer",
            "description": "Cap on how many reviews to collect for each pro. This is a hard limit: the actor never saves, and you are never charged for, more reviews than this for any one pro. It multiplies: every pro can add this many rows, so 100 pros at 25 reviews each is 2,500 review rows plus the 100 pro rows. Set to 0 to fetch every review a pro has (the total then depends on how many reviews Angi holds, so pair it with \"Maximum Rows in Total\" if you want a guaranteed ceiling). Ignored when \"Include Reviews\" is off.",
            "default": 20
          },
          "maxTotalRows": {
            "title": "Maximum Rows in Total (safety cap)",
            "minimum": 0,
            "type": "integer",
            "description": "Optional hard stop on the TOTAL number of rows the run saves - pros and reviews added together. Leave at 0 for no total cap, which is the normal setting: the run then saves up to Maximum Pros x (1 + Max Reviews per Pro) rows. Set a number here when you want one figure you can rely on no matter how the pros and reviews divide up. The run stops saving the moment it reaches it, and tells you in the final message that it did, so a short result is never a mystery. Pros are always saved before that pro's reviews, so a capped run gives you as many complete businesses as it can fit."
          }
        }
      },
      "runsResponseSchema": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "actId": {
                "type": "string"
              },
              "userId": {
                "type": "string"
              },
              "startedAt": {
                "type": "string",
                "format": "date-time",
                "example": "2025-01-08T00:00:00.000Z"
              },
              "finishedAt": {
                "type": "string",
                "format": "date-time",
                "example": "2025-01-08T00:00:00.000Z"
              },
              "status": {
                "type": "string",
                "example": "READY"
              },
              "meta": {
                "type": "object",
                "properties": {
                  "origin": {
                    "type": "string",
                    "example": "API"
                  },
                  "userAgent": {
                    "type": "string"
                  }
                }
              },
              "stats": {
                "type": "object",
                "properties": {
                  "inputBodyLen": {
                    "type": "integer",
                    "example": 2000
                  },
                  "rebootCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "restartCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "resurrectCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "computeUnits": {
                    "type": "integer",
                    "example": 0
                  }
                }
              },
              "options": {
                "type": "object",
                "properties": {
                  "build": {
                    "type": "string",
                    "example": "latest"
                  },
                  "timeoutSecs": {
                    "type": "integer",
                    "example": 300
                  },
                  "memoryMbytes": {
                    "type": "integer",
                    "example": 1024
                  },
                  "diskMbytes": {
                    "type": "integer",
                    "example": 2048
                  }
                }
              },
              "buildId": {
                "type": "string"
              },
              "defaultKeyValueStoreId": {
                "type": "string"
              },
              "defaultDatasetId": {
                "type": "string"
              },
              "defaultRequestQueueId": {
                "type": "string"
              },
              "buildNumber": {
                "type": "string",
                "example": "1.0.0"
              },
              "containerUrl": {
                "type": "string"
              },
              "usage": {
                "type": "object",
                "properties": {
                  "ACTOR_COMPUTE_UNITS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_WRITES": {
                    "type": "integer",
                    "example": 1
                  },
                  "KEY_VALUE_STORE_LISTS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_INTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_EXTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_SERPS": {
                    "type": "integer",
                    "example": 0
                  }
                }
              },
              "usageTotalUsd": {
                "type": "number",
                "example": 0.00005
              },
              "usageUsd": {
                "type": "object",
                "properties": {
                  "ACTOR_COMPUTE_UNITS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_WRITES": {
                    "type": "number",
                    "example": 0.00005
                  },
                  "KEY_VALUE_STORE_LISTS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_INTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_EXTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_SERPS": {
                    "type": "integer",
                    "example": 0
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}