georest.esri.portal

ArcGIS / Esri Portal search and service metadata client.

Ported from geeViz’s esriLib.py, with the geeViz-viewer rendering functions (addEsriImageService, addEsriMapService, addEsriFeatureService, addEsriService) removed - this module only fetches data, it does not render into any viewer.

Quick start:

from georest.esri import portal

# Discover data on any ArcGIS Portal
results = portal.searchPortal("naip 2023")                  # IIPP (default)
results = portal.searchPortal("naip 2023", portal="agol")   # ArcGIS Online
results = portal.searchPortal("naip 2023",
                              portal="https://myagency.gov/portal")

# Available portals
portal.PORTALS.keys()   # iipp, agol, usgs, noaa, usfs, nasa

# Inspect any service
meta = portal.getServiceMetadata("https://.../ImageServer")

Secured portals:

# Sign in once (OAuth, works with enterprise SSO); calls without token=
# then use the session wherever it applies. See georest.esri.auth.
from georest.esri import auth
auth.login("https://myorg.maps.arcgis.com", client_id="...")
portal.searchPortal("classified data", portal="https://myorg.maps.arcgis.com")

# An explicit token= still wins; token="" forces an anonymous request.
portal.searchPortal("classified data", token="...")

Copyright 2026 Ryan Rock and Ian Housman

Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at

Module Attributes

PORTALS

Module-level dict mapping short names to portal base URLs.

Functions

getServiceMetadata(url[, token])

Fetch and return the JSON metadata for any ArcGIS REST service.

searchPortal(query[, portal, limit, ...])

Search any ArcGIS Portal for hosted services.

georest.esri.portal.PORTALS: dict[str, str] = {'agol': 'https://www.arcgis.com', 'iipp': 'https://imagery.geoplatform.gov/iipp', 'nasa': 'https://nasa.maps.arcgis.com', 'noaa': 'https://coastalatlas.noaa.gov', 'usfs': 'https://data.fs.usda.gov/geodata', 'usgs': 'https://www.sciencebase.gov/sciencebase'}

Module-level dict mapping short names to portal base URLs.

Add your own at runtime:

from portal import PORTALS
PORTALS["myagency"] = "https://gis.myagency.gov/portal"
georest.esri.portal.searchPortal(query: str, portal: str = 'iipp', limit: int = 20, data_only: bool = True, raw_q: str | None = None, token: str | None = None, org_scoped: bool | None = None, **filters: Any) → list[dict[str, Any]][source]

Search any ArcGIS Portal for hosted services. Defaults to IIPP.

Uses the standard /sharing/rest/search endpoint present on ArcGIS Online, IIPP, and any ArcGIS Enterprise install. This is a raw portal search - it has no theme/keyword-alias matching. To search the USFS EDW catalog specifically (a separate, non-portal REST endpoint), use georest.esri.edw.search_edw_services(query, theme=...) instead.

Parameters:
  • query (str) – Free-text search query (e.g. "naip 2023", "fire perimeter").

  • portal (str, optional) – Either a short name from PORTALS ("iipp", "agol", "usgs", "noaa", "usfs", "nasa") or a full portal base URL. Defaults to "iipp".

  • limit (int, optional) – Maximum results to return (1–100). Defaults to 20.

  • data_only (bool, optional) – When True (default), appends a bundled exclusion list that filters out non-data items (styles, web apps, dashboards, etc.) so results are datasets only. Set to False to search without restrictions.

  • raw_q (str, optional) – If supplied, overrides the assembled query string entirely - ignores query, data_only, and filters. Use for portal query DSL power users.

  • token (str, optional) – ArcGIS token for secured portals. Omit for public services. Obtain via POST <portal>/sharing/rest/generateToken.

  • org_scoped (bool | None, optional) –

    Restrict results to the portal’s own organization by injecting an orgid: clause. - None (default) - auto. Scopes only for a canonical

    <org>.maps.arcgis.com URL, and only when raw_q is not supplied. That includes the built-in portal="nasa" (nasa.maps.arcgis.com), which searches only NASA’s own items; pass False to search all of ArcGIS Online, as portal="nasa" did in 0.1.0. Global AGOL (portal="agol"), Enterprise portals, and custom vanity domains are NOT auto-scoped; pass True for those.

    • True - always scope, wrapping raw_q if present.

    • False - never scope (pre-fix behaviour), and the escape hatch if the organization id cannot be resolved.

    Scoping costs one extra /sharing/rest/portals/self request the first time a portal is seen; successful lookups are cached for the process. If the id cannot be resolved this raises RuntimeError rather than silently returning other organizations’ data. When scoping, a query with unbalanced parentheses or quotes has those characters dropped (it is searched as plain terms), while a raw_q that could escape the orgid: wrapper raises ValueError.

  • **filters – Extra ArcGIS search filters forwarded as query params (e.g. sortField="title", sortOrder="asc", bbox="-120,35,-110,42"). q, num and f are set by this function and raise TypeError if passed here; use raw_q and limit instead.

Returns:

Parsed portal items. Each dict includes:

  • id (str): Item ID.

  • title (str): Item title.

  • type (str): Esri item type (e.g. "Image Service", "Feature Service").

  • snippet (str): Short description.

  • tags (list of str): Associated tags.

  • url (str): Service endpoint URL (may be "" if not set).

  • owner (str): Portal username of the owner.

  • created (int): Unix timestamp (ms) of item creation.

  • modified (int): Unix timestamp (ms) of last modification.

  • thumbnail (str or None): Thumbnail URL, or None if absent.

  • _raw (dict): Full raw portal item dict for advanced access.

An empty list means the search genuinely matched nothing - a rejected search raises rather than returning [] (see below).

Return type:

list of dict

Raises:
  • TypeError – If filters contains q, num or f. Raised before any request is made.

  • ValueError – If the portal answers 200 with an error object - most commonly code 498, an invalid or expired token - or with a body that isn’t JSON. This case used to be swallowed into an empty result list, which read as “no matches”. Also raised when scoping and raw_q has unbalanced parentheses or quotes, or a trailing backslash.

  • RuntimeError – If the portal is unreachable or answers with an HTTP error status. _http converts both urllib.error.HTTPError and urllib.error.URLError into RuntimeError, so a dead host and a 500 surface the same way. Also raised when scoping and the organization id cannot be resolved.

Example:

from georest.esri import portal

# Search IIPP for NAIP imagery (default portal)
results = portal.searchPortal("naip 2023", limit=10)
for r in results:
    print(r["title"], r["type"], r["url"])

# ArcGIS Online
results = portal.searchPortal("wildfire perimeter", portal="agol")

# Any ArcGIS Online organization or Enterprise portal, by its URL
results = portal.searchPortal("active fire",
                              portal="https://nasa.maps.arcgis.com")

# Raw portal query DSL (bypasses data_only and filters)
results = portal.searchPortal("", raw_q='type:"Feature Service" owner:USGS')
georest.esri.portal.getServiceMetadata(url: str, token: str | None = None) → dict[str, Any][source]

Fetch and return the JSON metadata for any ArcGIS REST service.

Appends ?f=json to the URL and returns the parsed response. Works for ImageServer, FeatureServer, MapServer, and any sub-layer URL (e.g. /FeatureServer/0).

Parameters:
  • url (str) –

    ArcGIS service endpoint, e.g.:

    "https://naip.services.arcgis.com/.../ImageServer"
    "https://services.arcgis.com/.../FeatureServer/0"
    "https://server.arcgisonline.com/.../MapServer"
    

  • token (str, optional) – ArcGIS token for secured services.

Returns:

Parsed service metadata. Common keys vary by service type:

  • name (str): Service name.

  • type (str): Layer geometry type (Feature Services).

  • fields (list): Schema fields (Feature Services).

  • extent (dict): Spatial extent.

  • spatialReference (dict): Spatial reference info.

  • minScale, maxScale (int): Scale range.

  • capabilities (str): Comma-separated capabilities string.

Return type:

dict

Raises:
  • ValueError – If the service answers 200 with an Esri error object - which is how ArcGIS reports a service that does not exist, rather than with an HTTP 404 - or if the response is not valid JSON.

  • RuntimeError – If the URL is unreachable or answers with an HTTP error status (see searchPortal()).

Example:

from georest.esri import portal

meta = portal.getServiceMetadata(
    "https://imagery.geoplatform.gov/iipp/rest/services/Vegetation/"
    "USFS_EDW_LCMS_AnnualChange_CONUS/ImageServer")
print(meta["name"])
print(meta["extent"])

# FeatureServer layer 0
meta = portal.getServiceMetadata(
    "https://sampleserver6.arcgisonline.com/arcgis/rest/services/"
    "Earthquakes_Since1970/FeatureServer/0")
print([f["name"] for f in meta.get("fields", [])])