Skip to content

Parameter Management

This document explains the comprehensive parameter management system in openEO by TiTiler, which provides dynamic parameter substitution for both synchronous result processing and XYZ tile services.

Overview

The parameter management system allows you to create flexible, reusable process graphs that can accept dynamic parameters at runtime. This enables:

  • Dynamic Query Parameters: Pass parameters via query strings in API requests
  • Default Parameter Values: Define fallback values in process graph definitions
  • Automatic Parameter Injection: Built-in injection of system parameters like user information
  • Parameter Precedence: Clear hierarchy for parameter resolution

Parameter Types

1. Process Graph Parameters

Parameters defined in the process graph definition with optional default values:

{
  "process_graph": {
    "load1": {
      "process_id": "load_collection",
      "arguments": {
        "id": "S2",
        "spatial_extent": { "from_parameter": "bbox" },
        "temporal_extent": { "from_parameter": "time_range" }
      },
      "result": true
    }
  },
  "parameters": [
    {
      "name": "bbox",
      "description": "Spatial bounding box",
      "schema": { "type": "object" },
      "default": {
        "west": 16.1,
        "east": 16.6,
        "north": 48.6,
        "south": 47.2
      }
    },
    {
      "name": "time_range",
      "description": "Temporal extent",
      "schema": { "type": "array" },
      "default": ["2023-01-01", "2023-12-31"]
    }
  ]
}

2. Query Parameters

Parameters passed in the URL query string that override defaults:

# Override bbox parameter
GET /result?bbox={"west":10.0,"east":20.0,"north":50.0,"south":40.0}

# Override multiple parameters
GET /services/xyz/{service_id}/tiles/{z}/{x}/{y}?time_range=["2024-01-01","2024-06-30"]&bands=["red","green","blue"]

3. Reserved System Parameters

Automatically injected parameters that provide system context:

Parameter Description Available In Comment
_openeo_user Authenticated user object Both endpoints
_openeo_tile_store Tile storage backend XYZ services only
spatial_extent_* Tile boundary coordinates XYZ services only Deprecated. Will be removed in a future release. Use bounding_box instead.
tile_x, tile_y, tile_z Tile coordinates XYZ services only
bounding_box Tile bounding box object XYZ services only

Parameter Resolution Priority

Parameters are resolved in the following order (highest to lowest priority):

  1. Query Parameters: Values passed in the URL query string
  2. System Parameters: Automatically injected reserved parameters
  3. Default Values: Default values defined in the process graph parameters
  4. Process Defaults: Built-in default values from process implementations

Endpoint Support

POST /result (Synchronous Processing)

The /result endpoint supports full parameter management:

POST /result?temporal_extent=["2024-01-01","2024-12-31"]&bands=["B04","B08"]
Content-Type: application/json

{
  "process": {
    "process_graph": {
      "load1": {
        "process_id": "load_collection",
        "arguments": {
          "id": "S2",
          "temporal_extent": {"from_parameter": "temporal_extent"},
          "bands": {"from_parameter": "bands"}
        },
        "result": true
      }
    },
    "parameters": [
      {
        "name": "temporal_extent",
        "schema": {"type": "array"},
        "default": ["2023-01-01", "2023-12-31"]
      },
      {
        "name": "bands",
        "schema": {"type": "array"},
        "default": ["B04", "B03", "B02"]
      }
    ]
  }
}

Features:

  • Query parameter parsing and JSON deserialization
  • User injection via _openeo_user parameter
  • Default parameter value application
  • Built-in parameter substitution via OpenEO process graph parser

GET /services/xyz/{service_id}/tiles/{z}/{x}/{y} (XYZ Tile Service)

XYZ tile services support the same parameter management with additional spatial context:

GET /services/xyz/abc123/tiles/10/512/341?temporal_extent=["2024-06-01","2024-06-30"]

Additional Features:

  • Automatic spatial parameter injection (tile bounds, coordinates)
  • Tile-specific context parameters
  • Same query parameter and default value support as /result

Parameter Validation

JSON Parameter Validation

Complex parameters passed as query strings are automatically parsed as JSON:

# Array parameter
?bands=["red","green","blue"]

# Object parameter
?bounding_box={"west":10,"east":20,"north":50,"south":40}

# Nested object parameter
?filter_options={"cloud_cover":{"max":20},"processing_level":"L2A"}

Schema Validation

Parameters are validated against their schema definitions:

{
  "name": "cloud_cover_max",
  "description": "Maximum cloud cover percentage",
  "schema": {
    "type": "number",
    "minimum": 0,
    "maximum": 100
  },
  "default": 20
}

Best Practices

1. Parameter Naming

  • Use descriptive, lowercase parameter names
  • Use underscores for multi-word parameters: temporal_extent, cloud_cover_max
  • Avoid conflicts with reserved parameter names (_openeo_*)

2. Default Values

  • Always provide sensible default values for optional parameters
  • Ensure defaults work across your expected data collections and time ranges
  • Document the reasoning behind default choices

3. Parameter Documentation

{
  "name": "temporal_extent",
  "description": "Temporal extent as [start_date, end_date] in ISO 8601 format",
  "schema": {
    "type": "array",
    "minItems": 2,
    "maxItems": 2,
    "items": { "type": "string", "format": "date" }
  },
  "default": ["2023-01-01", "2023-12-31"],
  "examples": [
    ["2024-01-01", "2024-06-30"],
    ["2023-07-15", "2023-08-15"]
  ]
}

4. Complex Parameters

For complex nested parameters, use clear structure and validation:

{
  "name": "processing_options",
  "description": "Processing configuration options",
  "schema": {
    "type": "object",
    "properties": {
      "cloud_mask": { "type": "boolean", "default": true },
      "atmospheric_correction": { "type": "boolean", "default": false },
      "resampling": {
        "type": "string",
        "enum": ["nearest", "bilinear", "cubic"],
        "default": "bilinear"
      }
    }
  },
  "default": {
    "cloud_mask": true,
    "atmospheric_correction": false,
    "resampling": "bilinear"
  }
}

Examples

Basic Parameter Usage

Simple parameter substitution with defaults:

{
  "process_graph": {
    "load1": {
      "process_id": "load_collection",
      "arguments": {
        "id": { "from_parameter": "collection" },
        "temporal_extent": { "from_parameter": "time_range" }
      },
      "result": true
    }
  },
  "parameters": [
    {
      "name": "collection",
      "schema": { "type": "string" },
      "default": "S2"
    },
    {
      "name": "time_range",
      "schema": { "type": "array" },
      "default": ["2023-01-01", "2023-12-31"]
    }
  ]
}

Usage: POST /result?collection=L8&time_range=["2024-01-01","2024-06-30"]

Advanced Parameter Usage

Complex parameters with validation and user context:

{
  "process_graph": {
    "load1": {
      "process_id": "load_collection",
      "arguments": {
        "id": "S2",
        "spatial_extent": { "from_parameter": "bbox" },
        "temporal_extent": { "from_parameter": "time_range" }
      }
    },
    "filter1": {
      "process_id": "filter_bands",
      "arguments": {
        "data": { "from_node": "load1" },
        "bands": { "from_parameter": "bands" }
      }
    },
    "user_process": {
      "process_id": "custom_user_process",
      "arguments": {
        "data": { "from_node": "filter1" },
        "user_id": { "from_parameter": "_openeo_user" }
      },
      "result": true
    }
  },
  "parameters": [
    {
      "name": "bbox",
      "description": "Spatial bounding box",
      "schema": {
        "type": "object",
        "required": ["west", "east", "north", "south"],
        "properties": {
          "west": { "type": "number" },
          "east": { "type": "number" },
          "north": { "type": "number" },
          "south": { "type": "number" }
        }
      }
    },
    {
      "name": "time_range",
      "schema": { "type": "array" },
      "default": ["2023-01-01", "2023-12-31"]
    },
    {
      "name": "bands",
      "schema": { "type": "array" },
      "default": ["B04", "B03", "B02"]
    }
  ]
}

Parameter Scope in Callbacks

Callback processes (reducer in reduce_dimension, process in apply / apply_dimension, etc.) have their own parameter scope. Per the openEO spec, a callback receives only the parameters it declares (data, context, etc.) — top-level UDP parameters are not automatically visible inside a callback.

The correct way to pass UDP-level parameters into a callback is through the context argument, which is designed exactly for this purpose.

Option A — single value (when the callback only needs one parameter):

{
  "process_id": "apply_dimension",
  "arguments": {
    "data": { "from_node": "load1" },
    "dimension": "bands",
    "context": { "from_parameter": "indicator" },
    "process": {
      "process_graph": {
        "eq1": {
          "process_id": "eq",
          "arguments": {
            "x": { "from_parameter": "data" },
            "y": { "from_parameter": "context" }
          },
          "result": true
        }
      }
    }
  }
}

Here context is the resolved value of indicator directly, so {"from_parameter": "context"} inside the callback equals e.g. 3.

Option B — dict (when the callback needs multiple parameters by name):

{
  "process_id": "apply_dimension",
  "arguments": {
    "data": { "from_node": "load1" },
    "dimension": "bands",
    "context": {
      "indicator": { "from_parameter": "indicator" },
      "min_value": { "from_parameter": "min_value" },
      "max_value": { "from_parameter": "max_value" }
    },
    "process": {
      "process_graph": {
        "get_indicator": {
          "process_id": "array_element",
          "arguments": {
            "data": { "from_parameter": "context" },
            "label": "indicator"
          }
        },
        "eq1": {
          "process_id": "eq",
          "arguments": {
            "x": { "from_parameter": "data" },
            "y": { "from_node": "get_indicator" }
          },
          "result": true
        }
      }
    }
  }
}

context becomes a dict; use array_element with label to extract individual values by name inside the callback.

Option C — array by position (less readable, avoid unless needed):

"context": [
  { "from_parameter": "indicator" },
  { "from_parameter": "min_value" },
  { "from_parameter": "max_value" }
]

Inside the callback, extract by index: array_element({"from_parameter": "context"}, 0) gives indicator.

The from_parameter references inside context are resolved against the UDP's named_parameters before the callback is invoked, so the callback receives actual values (integers, strings, etc.) — not unresolved references.

Note: If you used a Python closure to capture Parameter objects from the openeo-python-client and referenced them directly inside a callback without passing through context, those references would not resolve correctly. Always use context to thread outer parameters into callbacks.

Referencing a Stored UDP

A process graph can extend another graph by referencing a stored user-defined process by its process_id, exactly as it would reference a predefined process. Given a UDP saved as true_color through PUT /process_graphs/true_color:

{
  "process_graph": {
    "rendered": {
      "process_id": "true_color",
      "arguments": {
        "bbox": { "west": -74.02, "east": -73.94, "south": 40.70, "north": 40.80 }
      },
      "result": true
    }
  }
}

The reference is inlined — with its arguments bound to the UDP's parameters — before the graph is parsed, so the executed graph is the UDP's own nodes. This happens on POST /validation, POST /result and POST /services.

A few properties worth knowing:

  • UDPs are per-user. A reference resolves only against process graphs stored by the authenticated user; there is no shared or public UDP namespace.
  • References work inside callbacks too. A UDP referenced from a reducer, process or any other callback graph is inlined just like a top-level one, at any nesting depth.
  • Services are resolved once, at creation. POST /services inlines the reference into the stored service definition, because XYZ tiles are rendered later without an authenticated user and cannot look the UDP up themselves. Updating a UDP therefore does not change services created from it — recreate the service to pick up the new definition. This is the one place resolution doesn't stay live: everywhere else (/validation, /result, and a stored UDP that itself references another UDP) there's always an authenticated user available, so the reference is resolved fresh, from whatever it currently looks like, every time.
  • Bind either all of the UDP's parameters, or none of them. Arguments passed to the reference are bound into the inlined graph. If you pass no arguments at all, the UDP's from_parameter references are left in place and resolved later by the usual parameter machinery — query parameters and defaults still apply. Passing some arguments but not others is not currently supported: the unbound parameter has nothing to bind to, and the reference fails to resolve with Process '<id>' not found in registry.
  • Unknown processes still error. A process_id that matches neither a predefined process nor one of your stored UDPs fails with ProcessUnsupported, as before.

Troubleshooting

Common Issues

  1. Parameter Not Found: Ensure parameter names match exactly between from_parameter references and parameter definitions.

  2. Invalid JSON in Query: When passing complex parameters, ensure proper URL encoding:

# Correct
?bbox=%7B%22west%22%3A10%2C%22east%22%3A20%7D

# Also correct (many tools handle this automatically)
?bbox={"west":10,"east":20}
  1. Type Validation Errors: Ensure parameter values match their schema types:
# Wrong - string instead of number
?cloud_cover="20"

# Correct
?cloud_cover=20
  1. Reserved Parameter Conflicts: Don't define parameters that conflict with reserved names (_openeo_*, spatial_extent_*, tile_*).

Debugging

Enable debug logging to see parameter resolution:

import logging
logging.getLogger('titiler.openeo.factory').setLevel(logging.DEBUG)

This will show:

  • Query parameter parsing results
  • Default parameter application
  • Final parameter values passed to process graph