ArcGIS REST API Scraper - FeatureServer to CSV/JSON avatar

ArcGIS REST API Scraper - FeatureServer to CSV/JSON

Pricing

from $0.35 / 1,000 features

Go to Apify Store
ArcGIS REST API Scraper - FeatureServer to CSV/JSON

ArcGIS REST API Scraper - FeatureServer to CSV/JSON

Export any public ArcGIS FeatureServer/MapServer layer to CSV, JSON or Excel: county parcels, permits, zoning, HUD, USGS, open-data portals. Paste a layer URL, add an optional WHERE filter; auto-paginates. Every attribute + geometry + lon/lat. $0.50 per 1,000 features.

Pricing

from $0.35 / 1,000 features

Rating

0.0

(0)

Developer

Datamule

Datamule

Maintained by Community

Actor stats

0

Bookmarked

4

Total users

0

Monthly active users

16 days ago

Last modified

Share

ArcGIS REST API Scraper: FeatureServer / MapServer to CSV, JSON, Excel

Paste any public Esri ArcGIS layer URL and download every feature as flat rows with coordinates. No code needed.

Price: $0.50 per 1,000 features on the free plan (as low as $0.25 on higher plans). You pay only for rows returned.

What people use it for

  • County parcels & property data. Export assessor or parcel layers to CSV for real-estate and site-selection research.
  • City open data. Pull permits, zoning, code violations, 311 or crime layers from a city's ArcGIS portal.
  • GIS layers to a spreadsheet. Turn any ArcGIS Online / ArcGIS Hub dataset (HUD, USGS, DOTs, utilities) into CSV, JSON or Excel, with lon/lat.
  • Recurring monitoring. Schedule a filtered pull (e.g. STATUS = 'OPEN') and feed it to Sheets, a database or an AI agent.

Ready-made examples (click, then Try for free)

How it works

It accepts a layer /query URL, a layer URL, or a whole FeatureServer/MapServer root (lists all layers). It pages past the server's maxRecordCount automatically (resultOffset + exceededTransferLimit). Each attribute becomes a column, plus _geometry, _bbox, _longitude/_latitude and _raw.


For AI agents and MCP clients

Tool description: Agent tool: given a public ArcGIS FeatureServer/MapServer layer URL and optional WHERE filter, returns one row per feature with all attributes plus lon/lat and geometry.

Output fields (dataset rows): _serviceUrl, _mode, _layerId, _layerName, _objectId, _geometryType, _spatialReferenceWkid, _longitude, _latitude, _bbox, _attributeKeys, _attributeCount, _geometry, _raw, geometryType, type, description, minScale, maxScale, defaultVisibility, subLayerIds, queryUrl

Use cases:

  • Feed county parcel, permit or zoning layers to an AI agent as CSV/JSON rows: 1,000 features cost about $0.50.
  • Monitor an open-data layer (HUD, USGS, city permits) for changed records and push diffs to an MCP client.
  • Geocode-ready exports: pull a layer in a local projection with lon/lat columns for map or BI tools.

Pay-per-event price table (USD; platform compute billed separately by Apify):

EventCharged perFREE planDIAMOND plan
featureFeature$0.0005$0.00025

Set maxTotalChargeUsd on the run to cap spend.

Quick start (about 1 minute)

  1. Open the ready-made example HUD assisted multifamily properties and click Try for free, or paste this input into the Input tab (it is the Actor's default example):

    {
    "where": "1=1",
    "outFields": "*",
    "returnGeometry": true,
    "listLayersOnly": false,
    "maxRecords": 5,
    "pageSize": 5,
    "serviceUrls": [
    "https://services.arcgis.com/VTyQ9soqVukalItT/arcgis/rest/services/Multifamily_Properties_Assisted/FeatureServer/0/query"
    ]
    }
  2. Click Start. A cloud run with exactly this input returned 5 rows in a few seconds and charged 5 feature events.

  3. Open the Output tab and export CSV, JSON or Excel. Each row carries all 284 source attributes of that layer as columns, plus _latitude/_longitude in WGS84.

  4. Swap in your own layer URL. To find the layers behind a service, first run it with List layers only on a …/FeatureServer or …/MapServer root, then copy a queryUrl from the result. Raise maxRecords when the output looks right.

Map-ready CSV from a layer in a local projection (no code)

Some layers store points in a local projection (for example UTM, wkid 26917), so _longitude/_latitude come back empty. Ask the server for WGS84 instead:

  1. On the Input tab, Form view, put the layer URL in ArcGIS REST URL(s). For example, this public points-of-interest layer is stored in wkid 26917:

    https://services6.arcgis.com/hM5ymMLbxIyWTjn2/arcgis/rest/services/CITY_POI/FeatureServer/0
  2. Output coordinate system (outSR): 4326.

  3. Max records (total): 3 and Page size: 3 for a cheap first check.

  4. Under Run options, set Maximum cost per run to $0.02.

  5. Click Start (it reads Save & start after you edit the form).

  6. On the run page, click Export, choose CSV, then Download. The ArcGIS features view gives layer name, object id, _longitude, _latitude. Choose All fields to get every attribute too.

A cloud run of exactly these steps returned 3 rows for 3 feature events (about $0.0015 on the free tier). Its first row was _longitude -79.693678, _latitude 43.529706, the same point the server returns for outSR=4326. When the rows look right, raise Max records.

What the quick start returned

Selected columns from that run (the full rows have every attribute):

OBJECTIDPROPERTY_NAME_TEXTPLACED_BASE_CITY_NAME_TEXTSTD_ST_latitude_longitude
1Capitol Oaks Senior ResidencesAtlantaGA33.7182-84.4241
2Burnt Hill Road Group Home(empty in source)NJ40.4142-74.6787
3Villa MariaWilmingtonDE39.7517-75.5416
4Eastern West Virginia Community Action Agency, IncMoorefieldWV39.0494-78.9379
5Christopher Homes of BeebeJacksonvilleAR34.8866-92.1366

Cost: one feature event per row, $0.0005 on the free tier (down to $0.00025 on higher Apify plans), so the quick start is about $0.0025 plus normal platform usage.

Limits: it reads only what the ArcGIS server exposes publicly (or with a token you supply); some servers cap where/paging or return fewer fields; the actor never fills in missing values.

What it does

  1. Accepts a layer query URL, a layer URL, or a service root. Give it …/FeatureServer/0/query, …/FeatureServer/0, or a bare …/FeatureServer / …/MapServer root — a root is enumerated into all of its layers automatically.
  2. Pages the layer via the standard resultOffset + resultRecordCount, honoring the layer's maxRecordCount and following exceededTransferLimit until the result set is exhausted or your cap is hit. f=json is always sent explicitly.
  3. Flattens every feature into one row: every attribute becomes its own column, plus the Esri geometry (_geometry), a computed bounding box (_bbox), _longitude/_latitude for point layers (reprojected from Web Mercator to WGS84 when needed), provenance (_serviceUrl, _layerId, _layerName, _objectId), and a lossless _raw copy of the original Esri feature.

Modes

  • Layer catalog — turn on List layers only and give a service root: the actor reads the service descriptor and emits one row per layer (id, name, geometry type, min/max scale, query URL). A cheap map of what a service exposes — then point at a specific layer's /query.
  • Features — give one or more layer query URLs (or a root, which expands to all layers), optionally a where clause and an outFields list, and it pages every matching feature.

Use it from your own code (Python, standard library only)

Export a filtered slice of any public layer to a local CSV from a script: start a run with an explicit where, outFields and returnGeometry, a small maxRecords row cap and a separate per-run money cap (maxTotalChargeUsd), wait a bounded time for it to finish, then download only the columns you want. Set APIFY_TOKEN in your environment; the script never prints it. The example pulls HUD assisted multifamily properties in Vermont.

import csv, io, json, os, time, urllib.request
TOKEN = os.environ["APIFY_TOKEN"]
API = "https://api.apify.com/v2"
LAYER = "https://services.arcgis.com/VTyQ9soqVukalItT/arcgis/rest/services/Multifamily_Properties_Assisted/FeatureServer/0/query"
FIELDS = "OBJECTID,PROPERTY_NAME_TEXT,PLACED_BASE_CITY_NAME_TEXT,STD_ST,TOTAL_ASSISTED_UNIT_COUNT"
def call(method, path, body=None, raw=False):
req = urllib.request.Request(
API + path, method=method,
data=None if body is None else json.dumps(body).encode(),
headers={"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"})
with urllib.request.urlopen(req) as r:
return r.read() if raw else json.load(r)
run_input = {"serviceUrls": [LAYER], "where": "STD_ST = 'VT'", "outFields": FIELDS,
"returnGeometry": False, "maxRecords": 10}
# maxRecords caps rows; maxTotalChargeUsd separately caps what this run can cost you.
run = call("POST", "/acts/datamule~arcgis-featureserver-extractor/runs?maxTotalChargeUsd=0.01", run_input)["data"]
deadline = time.time() + 300 # give up waiting after 5 minutes
while run["status"] in ("READY", "RUNNING") and time.time() < deadline:
run = call("GET", f"/actor-runs/{run['id']}?waitForFinish=60")["data"]
if run["status"] != "SUCCEEDED":
# A run still RUNNING at the deadline keeps going in the cloud (still bound by the money cap).
raise SystemExit(f"run {run['id']} ended {run['status']} (check its log in Apify Console)")
csv_bytes = call("GET", f"/datasets/{run['defaultDatasetId']}/items?format=csv&clean=true&fields={FIELDS}", raw=True)
with open("arcgis_export.csv", "wb") as f:
f.write(csv_bytes)
rows = len(list(csv.DictReader(io.StringIO(csv_bytes.decode("utf-8-sig")))))
print(f"run {run['id']} SUCCEEDED; {rows} rows -> arcgis_export.csv")
# 0 rows on a SUCCEEDED run means the filter matched nothing, not an error.

Real result of this exact script on 2026-09-27: SUCCEEDED; 10 rows -> arcgis_export.csv (the layer has 148 Vermont rows; the 10-row cap stopped it). First 3 lines of the file:

"OBJECTID","PROPERTY_NAME_TEXT","PLACED_BASE_CITY_NAME_TEXT","STD_ST","TOTAL_ASSISTED_UNIT_COUNT"
"13511","Graystone Village",,"VT","34"
"13512","Green Hill Apartments","Beecher Falls","VT","12"

10 rows = 10 feature events = $0.005 at the free-tier price (less on volume tiers) plus normal platform usage. If you raise maxRecords, raise maxTotalChargeUsd too, or the run stops at the money cap. Use format=json instead of format=csv for JSON.

Input

FieldDescription
ArcGIS REST URL(s) (required)One or more layer query URLs, layer URLs, or service roots. A root is enumerated into its layers. An ArcGIS Online item page (…/home/item.html?id=…) or ArcGIS Hub dataset page (…/datasets/<id>_0) also works: the Actor reads the item's REST URL (one extra request) and uses that. A web map, CSV or other non-layer item fails with a message saying so. Pre-existing query params (e.g. a token) are preserved. If you paste a query URL that already has where, outFields or returnGeometry, those values are used for that URL unless you changed the matching field below. To get all rows or all fields instead, remove that part from the URL.
WHERE clauseSQL-style filter, e.g. STATE = 'CA' or POP > 100000. Default 1=1 (all rows).
Output fieldsComma-separated field names, e.g. OBJECTID,NAME,POP. Default * (all fields).
Return geometryInclude the Esri geometry, _bbox and derived lon/lat. Default on.
Output coordinate system (outSR)Optional. Empty keeps the layer's own coordinates. Enter 4326 to get WGS84 longitude/latitude from any layer: the ArcGIS server converts the geometry, and point rows then get _longitude/_latitude even when the layer is published in a local projection (US State Plane, UTM, British National Grid…). When filled in, it replaces any outSR in a pasted URL.
List layers onlyDiscovery: enumerate a service root's layers, fetch no features.
Max records (total)Cap across all layers/pages. One billable event per feature.
Page sizeServer-side records per /query call (resultRecordCount).
ArcGIS tokenOptional ?token= for secured services. Sent only to the host(s) you pasted. Never logged.
Bearer token / Extra headersOptional auth for proxied deployments. Never logged.

Credentials stay with the address you pasted. If an item or Hub page points to a layer on a different host, or a server redirects elsewhere, the token and headers are not sent there. A public layer still exports; a protected one fails with the host named, and you can paste that layer URL directly to use your token.

Output

One row per feature. Every source attribute is promoted to a top-level column; derived and provenance fields are _-prefixed so a source column can never collide with them:

{
"OBJECTID": 1,
"PROPERTY_NAME_TEXT": "Capitol Oaks Senior Residences",
"HUB_NAME_TEXT": "Atlanta",
"PLACED_BASE_CITY_NAME_TEXT": "Atlanta",
"_serviceUrl": "https://services.arcgis.com/VTyQ9soqVukalItT/arcgis/rest/services/Multifamily_Properties_Assisted/FeatureServer/0/query",
"_layerId": "0",
"_objectId": 1,
"_geometryType": "esriGeometryPoint",
"_spatialReferenceWkid": 3857,
"_longitude": -84.4241,
"_latitude": 33.7182,
"_bbox": [-9398049.15, 3991019.62, -9398049.15, 3991019.62],
"_attributeKeys": ["HUB_NAME_TEXT", "OBJECTID", "PLACED_BASE_CITY_NAME_TEXT", "PROPERTY_NAME_TEXT"],
"_attributeCount": 4,
"_geometry": { "x": -9398049.1585, "y": 3991019.6226 },
"_raw": { }
}

Point geometries in WGS84 or Web Mercator become _longitude/_latitude. A point layer in any other projection keeps them empty (the run log says so); set Output coordinate system to 4326 to fill them. Polygon/polyline features get a computed _bbox (in the layer's native SR) and _longitude/_latitude of null. Catalog rows (_mode: "layer") carry _layerName, geometryType, type, minScale/maxScale and queryUrl instead.

Resilience

Per-layer, like a batch harvester. ArcGIS returns HTTP 200 with an {"error": …} body on a bad query — that is detected (not mistaken for success) and the offending layer is a skip-with-warning while the run continues. A dead / 403 / 404 service is skipped too; if every target fails the run exits non-zero (91) so nothing broken ships. A reachable layer whose where matches nothing yields 0 rows and a clean exit — the actor never fabricates rows. A server that ignores resultOffset (repeats the first record) is detected and paging stops to avoid a duplicate loop.

Many government ArcGIS hosts sit behind a WAF that blocks obvious bot User-Agents. The actor sends a standard browser User-Agent so it works across the widest range of public deployments.

Pricing

Pay-per-event: one feature charge per emitted row (feature, or layer in catalog mode). Aggressive subscription-tier volume discounts apply.

Examples

US HUD Multifamily Assisted Properties (ArcGIS Online, 23,781 point features):

{
"serviceUrls": ["https://services.arcgis.com/VTyQ9soqVukalItT/arcgis/rest/services/Multifamily_Properties_Assisted/FeatureServer/0/query"],
"where": "1=1",
"maxRecords": 500
}

Enumerate a service root, then extract (City of Chicago ArcGIS Server):

{
"serviceUrls": ["https://gisapps.chicago.gov/arcgis/rest/services/SampleWorldCities/MapServer"],
"listLayersOnly": true
}