ArcGIS REST API Scraper - FeatureServer to CSV/JSON
Pricing
from $0.35 / 1,000 features
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
Maintained by CommunityActor stats
0
Bookmarked
4
Total users
0
Monthly active users
16 days ago
Last modified
Categories
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)
- California counties with population to CSV: WHERE filter plus selected fields
- US parks GIS layer with geometry: polygons, area, bbox
- HUD assisted multifamily properties: points with lon/lat
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):
| Event | Charged per | FREE plan | DIAMOND plan |
|---|---|---|---|
| feature | Feature | $0.0005 | $0.00025 |
Set maxTotalChargeUsd on the run to cap spend.
Quick start (about 1 minute)
-
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"]} -
Click Start. A cloud run with exactly this input returned 5 rows in a few seconds and charged 5
featureevents. -
Open the Output tab and export CSV, JSON or Excel. Each row carries all 284 source attributes of that layer as columns, plus
_latitude/_longitudein WGS84. -
Swap in your own layer URL. To find the layers behind a service, first run it with List layers only on a
…/FeatureServeror…/MapServerroot, then copy aqueryUrlfrom the result. RaisemaxRecordswhen 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:
-
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 -
Output coordinate system (outSR):
4326. -
Max records (total):
3and Page size:3for a cheap first check. -
Under Run options, set Maximum cost per run to
$0.02. -
Click Start (it reads Save & start after you edit the form).
-
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):
| OBJECTID | PROPERTY_NAME_TEXT | PLACED_BASE_CITY_NAME_TEXT | STD_ST | _latitude | _longitude |
|---|---|---|---|---|---|
| 1 | Capitol Oaks Senior Residences | Atlanta | GA | 33.7182 | -84.4241 |
| 2 | Burnt Hill Road Group Home | (empty in source) | NJ | 40.4142 | -74.6787 |
| 3 | Villa Maria | Wilmington | DE | 39.7517 | -75.5416 |
| 4 | Eastern West Virginia Community Action Agency, Inc | Moorefield | WV | 39.0494 | -78.9379 |
| 5 | Christopher Homes of Beebe | Jacksonville | AR | 34.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
- Accepts a layer query URL, a layer URL, or a service root. Give it
…/FeatureServer/0/query,…/FeatureServer/0, or a bare…/FeatureServer/…/MapServerroot — a root is enumerated into all of its layers automatically. - Pages the layer via the standard
resultOffset+resultRecordCount, honoring the layer'smaxRecordCountand followingexceededTransferLimituntil the result set is exhausted or your cap is hit.f=jsonis always sent explicitly. - Flattens every feature into one row: every attribute becomes its own column,
plus the Esri geometry (
_geometry), a computed bounding box (_bbox),_longitude/_latitudefor point layers (reprojected from Web Mercator to WGS84 when needed), provenance (_serviceUrl,_layerId,_layerName,_objectId), and a lossless_rawcopy 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
whereclause and anoutFieldslist, 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.requestTOKEN = 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 minuteswhile 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
| Field | Description |
|---|---|
| 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 clause | SQL-style filter, e.g. STATE = 'CA' or POP > 100000. Default 1=1 (all rows). |
| Output fields | Comma-separated field names, e.g. OBJECTID,NAME,POP. Default * (all fields). |
| Return geometry | Include 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 only | Discovery: enumerate a service root's layers, fetch no features. |
| Max records (total) | Cap across all layers/pages. One billable event per feature. |
| Page size | Server-side records per /query call (resultRecordCount). |
| ArcGIS token | Optional ?token= for secured services. Sent only to the host(s) you pasted. Never logged. |
| Bearer token / Extra headers | Optional 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}