Skip to main content
Leaf’s API outputs GeoJSON, GeoTIFF, and PNG data that maps directly into ArcGIS workflows. This tutorial covers two integration patterns: consuming data in ArcGIS Pro with Python toolboxes, and automating data ingest in ArcGIS Enterprise with geoprocessing services.

Before you start

  • A Leaf account with API credentials and at least one connected provider or sample data.
  • ArcGIS Pro 3.x or ArcGIS Enterprise 11.x (or later).
  • Python 3.9+ with requests and arcpy available (bundled with ArcGIS Pro).
  • The sample toolboxes from Leaf’s ArcGIS GitHub repo.

ArcGIS Pro

Step 1: Set up authentication

Communication with the Leaf API requires a bearer token. The sample toolbox includes an authentication tool that stores the token in a temporary table for use by other tools.
import requests

response = requests.post(
    "https://api.withleaf.io/api/authenticate",
    json={"username": "your-email@example.com", "password": "your-password"}
)
token = response.json()["id_token"]
Run the Leaf Authentication tool in the sample toolbox, or store the token programmatically. Other toolbox tools check for a valid token before executing.

Step 2: Fetch field boundaries

The Get Field Boundaries tool combines data from multiple Leaf endpoints (growers, farms, fields, and boundaries) and converts them into a feature layer in your ArcGIS Pro map. The Leaf API returns boundaries as GeoJSON. The toolbox converts them to features using arcpy.JSONToFeatures_conversion. Fields from different providers appear in a single layer with attributes like provider, farm name, and field name.
import requests, json

headers = {"Authorization": f"Bearer {token}"}
fields = requests.get(
    "https://api.withleaf.io/services/fields/api/fields",
    headers=headers,
    params={"leafUserId": leaf_user_id, "size": 100}
).json()

for field in fields:
    boundary = requests.get(
        f"https://api.withleaf.io/services/fields/api/users/{leaf_user_id}/fields/{field['id']}/boundary",
        headers=headers
    ).json()
    # Convert to feature using arcpy

Step 3: Display satellite imagery

Leaf’s crop monitoring endpoints return GeoTIFF and PNG images for NDVI, NDRE, and RGB. You can download these and add them as raster layers.
images = requests.get(
    f"https://api.withleaf.io/services/satellite/api/fields/{satellite_field_id}/processes",
    headers=headers,
    params={"startDate": "2025-01-01", "endDate": "2025-12-31"}
).json()

for image in images:
    ndvi_image = next(
        (img for img in image.get("images", []) if img.get("type") == "tif_colorized"),
        None
    )
    if ndvi_image:
        ndvi_url = ndvi_image["downloadUrl"]
        response = requests.get(ndvi_url, headers=headers)
        with open(f"/tmp/ndvi_{image['date']}.tif", "wb") as f:
            f.write(response.content)
Add the downloaded GeoTIFFs to your map as raster layers. Leaf images are already georeferenced and clipped to the field boundary.

Step 4: Load field operations

Field operations expose a standard GeoJSON download URL. Fetch the download URL first, then download the actual GeoJSON and convert it to a feature layer:
op_id = "your-operation-id"
geojson_ref = requests.get(
    f"https://api.withleaf.io/services/operations/api/operations/{op_id}/standardGeojson",
    headers=headers
).json()

geojson = requests.get(
    geojson_ref["downloadStandardGeojson"],
    headers=headers
).json()

with open("/tmp/operation.geojson", "w") as f:
    json.dump(geojson, f)

arcpy.conversion.JSONToFeatures("/tmp/operation.geojson", "operation_layer")
The GeoJSON features have standardized properties like yieldVolume, seedRate, and appliedRate that you can use for symbology and analysis.

ArcGIS Enterprise

For automated workflows, publish a geoprocessing service that acts as a webhook for Leaf alerts. When new data arrives, Leaf sends an alert to your geoprocessing service, which downloads and processes the data automatically.

Step 5: Create a geoprocessing webhook

Write a Python toolbox that accepts the parameters for the specific Leaf event you subscribe to. For newSatelliteImage, the payload includes:
ParameterTypeDescription
externalIdStringYour satellite field external ID
processIdStringThe satellite process ID
typeStringAlert type, for example newSatelliteImage
timestampStringWhen the alert fired
X-Leaf-SignatureStringHMAC signature for validation

Step 6: Validate the signature

Every alert from Leaf includes an X-Leaf-Signature header. Validate it against your secret to confirm the request came from Leaf:
import base64
import hmac
import hashlib

def validate_signature(payload: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode(), payload, hashlib.sha256
    ).digest()
    provided = base64.b64decode(signature)
    return hmac.compare_digest(expected, provided)

Step 7: Publish and register

  1. Publish the geoprocessing service following ArcGIS Pro’s publishing guide.
  2. The service must be publicly accessible (Leaf needs to reach it).
  3. Register the service URL as a Leaf alert endpoint:
curl -X POST "https://api.withleaf.io/services/alerts/api/alerts/arcgis" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "events": ["newSatelliteImage"],
    "name": "Satellite images listener",
    "url": "https://your-arcgis-server.com/arcgis/rest/services/LeafWebhook/GPServer/LeafWebhook/submitJob?f=json",
    "secret": "your-random-secret"
  }'
Use /submitJob?f=json for asynchronous geoprocessing or /execute?f=json for synchronous.

What you built

You connected Leaf’s API to ArcGIS for two workflows: interactive data exploration in ArcGIS Pro, and automated data ingest in ArcGIS Enterprise via webhook-driven geoprocessing. The sample toolboxes on GitHub provide working implementations of these patterns. Adapt them to your specific requirements.
Last modified on March 19, 2026