Skip to Content
FarmTree.earth
  • Home
  • Services
    • Service Journey
    • Cocoa Focus
  • Track Record
  • Who we are
    • Team
    • Vacancies
  • Contact us
  • FarmTree Platform
    • FarmTree Platform
    • User videos
    • Acknowledgements
    • Licenses
  • Jobs
  • +1 555-555-5556
  • Contact Us
FarmTree.earth
      • Home
      • Services
        • Service Journey
        • Cocoa Focus
      • Track Record
      • Who we are
        • Team
        • Vacancies
      • Contact us
      • FarmTree Platform
        • FarmTree Platform
        • User videos
        • Acknowledgements
        • Licenses
      • Jobs
    • +1 555-555-5556
      Contact Us

    Technical API Docs 

     

    What you need to know before you start

    The FarmTree Tool is a tool that can be used to predict the outcomes of farming scenarios, by creating projections for a wide range of metrics. With the API you can send scenarios to the Tool (and receive projections in return) without using our FarmTree Tool interface. That way you can integrate the Tool functionality in your own platform. 

    The system follows a secure authentication workflow and provides multiple endpoints for managing agricultural data. Users require a license for the FarmTree platform that includes API access. The license is connected to a team, and a team has access to specific collections of agricultural components and default parameters. 

    The base-URL for the endpoints is: https://tool.farmtree.earth, and the endpoints are well documented using Swagger:  https://tool.farmtree.earth/swagger/index.html. 

    Basic experience with the FarmTree Tool is assumed, it will help you understand what a farming scenario consists of. In the bottom section of this documentation, there are some notes on how to collect the data for a scenario, as well as Python code examples.


    Authentication

    Initial Login 

    Users authenticate to the system using the /Api/Login endpoint (POST method). This endpoint requires sending a username and password as credentials. Upon successful authentication, the system returns two critical tokens:

    • Access Token: A short-lived token valid for 15 minutes used for all subsequent API calls 
    • Refresh Token: A longer-lived token valid for 3 days used to obtain new access tokens without re-entering credentials 
    Token Management 

    To maintain continuous access to the system, users can refresh their authentication using the /Api/RefreshToken endpoint (POST method). This endpoint requires the user's email address and the refresh token obtained during initial login. The system responds with a new set of tokens, extending the user's access.  

    Session Termination 

    Users can explicitly log out using the /Api/Logout endpoint (DELETE method), which invalidates the current session.  

     

    Data Access

    Dataset Management 

    The system organizes agricultural data into datasets accessible through the /Api/Datasets endpoint (GET method). Datasets contain collections of agricultural components including trees, crops, and farming inputs that users can incorporate into their scenarios. As mentioned above, your license is connected to a team (or organization), and a team has access to one or more datasets.

    Team and Region Configuration 

    Users can access team information via /Api/Teams (GET method) and retrieve regional data specific to a team using /Api/Teams/Regions (GET method). This hierarchical structure allows organizations to manage regional pricing data.

    Detailed documentation on the end points and schemas is available on https://tool.farmtree.earth/swagger/index.html.

     

    Scenario Planning

    Available parameters 

    Before creating agricultural scenarios, users can retrieve available planning parameters using /Api/Tool (GET method). This endpoint provides information about available agricultural components (trees, crops, inputs) of a dataset, and default settings for a specific team and pricing region combination.

    Scenario data includes:

    • Selected agricultural components (trees, crops, inputs) 
    • Plot characteristics (location, soil properties, climate settings) 
    • Financial parameters (wages, subsidies, interest rates) 
    • Project timeline and management settings 

    Detailed documentation is available on https://tool.farmtree.earth/swagger/index.html under the 'PlotScenario' schema. 

    Another way to compose a scenario, and this may be easier the first time, is to create a scenario in the FarmTree Tool on the platform (https://tool.farmtree.earth/Tool), and use the export management section to download it as a JSON scenario for the API. Be sure to add at least one coordinate for the plot location of the scenario, though.

    Further notes on how to collect data for a farming scenario can be found in the bottom section of this documentation.


    Scenario Execution 

    The core functionality involves running agricultural simulations through the /Api/Tool (POST method) endpoint. Users submit a scenario in the body of the POST request.

    The system processes this information and returns detailed analysis results including time-series data on performance metrics and economic projections. Detailed documentation on the result is available on https://tool.farmtree.earth/swagger/index.html under the 'ToolModelDto' schema.

     

    Security Requirements 

    All API endpoints except the login endpoints require bearer token authentication using the short-lived access token obtained during login. The token must be included in the Authorization header of each request as "Bearer [token]".  

     

    API Response Handling 

    The system provides standardized error responses for authentication failures (403 Forbidden) and validation errors (400 Bad Request). Successful operations return relevant data structures containing the requested information or analysis results. See also https://tool.farmtree.earth/swagger/index.html under the 'ToolModelDto' schema.


    Notes on collecting data for a FarmTree Scenario


    In the PlotScenario schema on https://tool.farmtree.earth/swagger/index.html, you can see that a scenario is defined by many parameters. Some values you will have collected yourself, but on some others you may not have any information. Therefore, for many of the parameters our database provides default values you can use. Default values can be customized for your organization, and if you wish to do so, please contact our support team.

    Let's consider the three types of parameters:


    Plot settings

    The plot settings are location specific settings, such as the size, slope and altitude, but also data on the climate or soil fertility. They define the "bare" plot.

    Note on the climate data:

    Make sure to add at least one GPS point to the PlotCoordinates, because the location will be used to collect climate data from our database. We have stored one of the CMIP6 climate data models available from the European Copernicus project. Unfortunately no climate model is accurate for all locations in the world, so we added options to correct or add your own precipitation and temperature data in the plot settings.

    Note on the soil and terrain data:

    Apart from the coordinates, for all other plot settings we provide default values. But of course the more specific and accurate the settings are, the better the resulting projections from the Tool. You can use a GPS point to retrieve other data such as the soil or terrain data. 

    In the Python code examples below, API calls to SoilGrids and OpenTopoData are included. This code not only retrieves the soil and terrain data that a scenario needs, it also converts the SoilGrids data into the units that we use in our Tool and calculates the slope. If you have worked with our Tool interface on the FarmTree platform, you may have noticed that we make this API call to SoilGrids and OpenTopoData when you select a location. However, the SoilGrids API is not always available and (like for the climate) the fetched data is not always accurate. That is why we do not try to retrieve the soil or terrain data for you when you call our API. You should fetch it, and if possible assess its accuracy yourself.


    Agricultural components

    By using the GET method on the Tool endpoint, you can collect the data on all available agricultural components, including their default values for planting and application practices. Examples of planting parameters are the 'planting density' or the 'planting month'. You can copy these default planting and application values when you add components to the scenario you wish to add in the POST request to the Tool. Planting and application parameters are required. 

    Apart from those parameters, we also provide many more options for a component (such as harvesting practices) under the so called 'modifications'. Values for modification do not need to be set unless you want to set them. In a scenario any modification parameter can be null, meaning you do not wish to override the default value.


    Other settings

    All other settings are more general project settings, such as the time span of the projections, the interest rate, or the CO2e price. If you do not provide these values, default values are used.


    We hope this information helps you with the complex business of collecting enough data for a PlotScenario. But of course, do not hesitate to ask for support if you need any help. 



    Python code examples

    • FarmTree API Login & Tool
    • SoilGrids
    • OpenTopoData
    import requests
    import json
    import os

    BASE_URL = "https://tool.farmtree.earth"

    def login(email, password):
    login_url = BASE_URL + "/Api/Login"
    credentials = {
    "Email": email,
    "Password": password
    }

    headers = {
    "Content-Type": "application/json",
    "Accept": "text/plain"
    }

    try:
    response = requests.post(login_url, headers=headers, json=credentials)
    response.raise_for_status()

    print(f"Status Code: {response.status_code}")
    return response.json()
    except requests.exceptions.RequestException as e:
    print(f"Error making POST request: {e}")
    if hasattr(e, 'response') and e.response is not None:
    print(f"Response status: {e.response.status_code}")
    print(f"Response body: {e.response.text}")
    return None


    def submit_scenario(bearer_token, scenario_to_submit, team_id, region_id, dataset_id):

    tool_url = BASE_URL + "/Api/Tool"
    headers = {
    "Authorization": f"Bearer {bearer_token}",
    "Content-Type": "application/json",
    "Accept": "text/plain"
    }

    params = {
    "datasetId": dataset_id,
    "regionId": region_id,
    "teamId": team_id
    }

    try:
    response = requests.post(tool_url, headers=headers, json=scenario_to_submit, params=params)
    response.raise_for_status()

    print(f"Status Code: {response.status_code}")
    return response.json()
    except requests.exceptions.RequestException as e:
    print(f"Error making POST request: {e}")
    if hasattr(e, 'response') and e.response is not None:
    print(f"Response status: {e.response.status_code}")
    print(f"Response body: {e.response.text}")
    return None


    def get_json_file(file):
    with open(file, 'r') as f:
    data = json.load(f)
    return data


    if __name__ == "__main__":
    EMAIL = "your_email"
    PASSWORD = "your_password"
    token = None
    # the filepath should point to the JSON file you created, or downloaded by using the 'JSON scenario for API' button in the Tool
    filepath = os.path.expanduser("~/Downloads/your_scenario.json")
    scenario = get_json_file(filepath)

    print("Making POST request to FarmTree Login API...")
    login_result = login(EMAIL, PASSWORD)

    if login_result:
    print("\nLogin successful.")
    print("Response data:")
    print(login_result)

    if 'Token' in login_result:
    token = login_result['Token']
    print(f"\nToken received: {token}")
    if 'RefreshToken' in login_result:
    refresh_token = login_result['RefreshToken']
    print(f"\nRefresh token received: {refresh_token}")
    if 'TokenValidTo' in login_result:
    token_valid_to = login_result['TokenValidTo']
    print(f"\nToken valid to: {token_valid_to}")
    if 'RefreshTokenValidTo' in login_result:
    refresh_token_valid_to = login_result['RefreshTokenValidTo']
    print(f"\nRefresh token valid to: {refresh_token_valid_to}")
    else:
    print("\nLogin failed")

    # NOTE 1:
    # If you perform repeated calls to the Api, insert a check if the token is still valid for another minute
    # and if not, get a new token using the email, refresh_token and the Api/RefreshToken endpoint.
    # NOTE 2:
    # This example takes the default params set for the user. If the user has access to multiple teams,
    # price regions and datasets, their ids can be provided as params. To retrieve the available team_id, region_id
    # and dataset_id, use the appropriate end points.
    #
    # For further documentation see BASE_URL/swagger/index.html.

    print("Making POST request to FarmTree Tool API...")
    scenario_submit_result = submit_scenario(token, scenario, None, None, None)
    if scenario_submit_result:
    print("\nScenario result returned.")
    print("Response data for first time series:")
    # The response data is quite extensive, so only the first time series is printed.
    print(scenario_submit_result['TimeSeriesPerIndicator'][0])
    import requests

    BASE_URL = "https://rest.isric.org/soilgrids/v2.0"
    QUERY_PROPERTIES = [ "cfvo", "sand", "silt", "clay", "phh2o", "cec", "nitrogen", "soc", "bdod" ]
    QUERY_DEPTHS = ["0-5cm", "5-15cm", "15-30cm"]
    QUERY_VALUES = ["mean"]


    def get_top_soil_data_for_gps_point(latitude, longitude):

    query_url = BASE_URL + "/properties/query"
    headers = {
    "Content-Type": "application/json",
    "Accept": "text/plain"
    }

    params = {"lat": latitude, "lon": longitude, "property": QUERY_PROPERTIES, "depth": QUERY_DEPTHS,
    "value": QUERY_VALUES}

    try:
    response = requests.get(query_url, headers=headers, params=params)
    response.raise_for_status()

    print(f"Status Code: {response.status_code}")
    return response.json()
    except requests.exceptions.RequestException as e:
    print(f"Error making GET request: {e}")
    if hasattr(e, 'response') and e.response is not None:
    print(f"Response status: {e.response.status_code}")
    print(f"Response body: {e.response.text}")
    return None

    def get_mean_value_for_property(prop, query_result):
    value_sum = 0.0
    total_weight = 0
    top_soil_value = 0.0
    relative_weight = 0

    for layer in query_result['properties']["layers"]:
    if layer['name'] == prop:
    for depths in layer['depths']:
    if depths['values']['mean'] is not None:
    if depths['label'] == '0-5cm':
    relative_weight = 5
    elif depths['label'] == '5-15cm':
    relative_weight = 10
    elif depths['label'] == '15-30cm':
    relative_weight = 15
    total_weight += relative_weight
    value_sum += depths['values']['mean'] * relative_weight
    top_soil_value = value_sum / total_weight
    return top_soil_value


    def convert_soilgrids_data_to_farmtree_format(raw_result):
    # To reduce storage space in the database, Soilgrids has converted all values to integers.
    # This conversion must be undone for the FarmTree Tool.

    # The cfvo is provided in cm3/dm3 and must be converted to a fraction between 0.0 and 1.0.
    stone_fraction = get_mean_value_for_property('cfvo', raw_result) / 1000.0

    # Sand, silt and clay are provided in g/kg of the fine earth fraction and must be converted to a fraction of the
    # total soil volume. Assuming the difference in density is not significant, we use the mass fractions for this.
    sand_fraction = (get_mean_value_for_property('sand', raw_result)) / 1000.0 * (1.0 - stone_fraction)
    silt_fraction = (get_mean_value_for_property('silt', raw_result)) / 1000.0 * (1.0 - stone_fraction)
    clay_fraction = (get_mean_value_for_property('clay', raw_result)) / 1000.0 * (1.0 - stone_fraction)

    # Nitrogen in provided as total N in cg/kg and must be converted to total N in g/kg.
    nitrogen_content = get_mean_value_for_property('nitrogen', raw_result) / 100.0

    # The cec is provided in mmol/kg and needs no conversion.
    cation_exchange_capacity = get_mean_value_for_property('cec', raw_result)

    # The soc is provided in dg/kg of the fine earth fraction and must be converted to g/kg.
    # The FarmTree SOC model disregards the stone fraction
    soil_organic_carbon = get_mean_value_for_property('soc', raw_result) / 10.0

    # The SoilGrids pH is provided multiplied by 10 and must be converted back.
    soil_water_ph = get_mean_value_for_property('phh2o', raw_result) / 10

    # The bulk density is provided in cg/cm3 of the fine earth fraction and must be converted to kg/dm3.
    bulk_density = get_mean_value_for_property( 'bdod', raw_result) / 100.0

    # The permanent wilting point is not available for all locations, so it is calculated. It is a volume fraction.
    fine_earth_clay_fraction = get_mean_value_for_property('clay', raw_result) / 1000.0
    permanent_wilting_point = min(1.0, max(0.0, -0.03736 + (1.098 * fine_earth_clay_fraction) - (
    0.9219 * pow(fine_earth_clay_fraction, 2)) + (3.323 * soil_organic_carbon / 1000)))

    # The field capacity is not available for all locations, so it is calculated. It is a volume fraction.
    field_capacity = min(1.0, max(0.0, 0.02432 + (1.393 * fine_earth_clay_fraction) -
    (1.264 * pow(fine_earth_clay_fraction, 2)) + (6.036 * soil_organic_carbon / 1000)))

    soil_properties = {
    "SoilStoneFraction": stone_fraction,
    "SoilSiltFraction" : silt_fraction,
    "SoilClayFraction" : clay_fraction,
    "SoilSandFraction" : sand_fraction,
    "SoilNitrogenContent" : nitrogen_content,
    "SoilCationExchangeCapacity" : cation_exchange_capacity,
    "SoilSoilOrganicCarbon" : soil_organic_carbon,
    "SoilAcidity" : soil_water_ph,
    "SoilDensity" : bulk_density,
    "PermanentWiltingPoint" : permanent_wilting_point,
    "FieldCapacity" : field_capacity,
    }

    return soil_properties


    if __name__ == "__main__":
    print("Making GET request to SoilGrids API...")

    lat = -9
    lon = -72
    result = {}

    soilgrids_result = get_top_soil_data_for_gps_point(lat, lon)
    if soilgrids_result is not None:
    soil_props = convert_soilgrids_data_to_farmtree_format(soilgrids_result)
    print(soil_props)

    import math
    import requests

    BASE_URL = "https://api.opentopodata.org/v1"
    DATASET = "mapzen"
    PRECISION_METERS = 30

    def get_terrain_data_for_gps_point(latitude, longitude):
    # Source: https://www.fao.org/soils-portal/data-hub/soil-maps-and-databases/harmonized-world-soil-database-v12/en/#ui-id-2
    # https://www.spatialanalysisonline.com/HTML/gradient__slope_and_aspect.htm
    #
    # The algorithm used to calculate slope gradient and slope aspect operates on sub-grids of 3 by 3 grid cells, here denoted with a to i:
    #
    # a b c
    # d e f
    # g h i

    # Source: https://stackoverflow.com/a/7478827
    lat_offset = PRECISION_METERS / 111320.0
    lon_offset = lat_offset / math.cos(latitude * 0.01745)
    offsets = [
    (-lat_offset, -lon_offset),
    (0, -lon_offset),
    (lat_offset, -lon_offset),
    (-lat_offset, 0),
    (0, 0),
    (lat_offset, 0),
    (-lat_offset, lon_offset),
    (0, lon_offset),
    (lat_offset, lon_offset)
    ]

    locations = '|'.join(
    f"{latitude + lat_off},{longitude + lon_off}"
    for lat_off, lon_off in offsets
    )

    url = f"{BASE_URL}/{DATASET}?locations={locations}"
    response = requests.get(url).json()

    altitudes = []
    if response["results"] is not None:
    for result in response["results"]:
    altitudes.append(result["elevation"])

    # Let px, py and pz denote respectively coordinates of grid point p in x direction (i.e. longitude in our case),
    # y direction (i.e. latitude in our application), and z in vertical direction (i.e., altitude),
    # then calculate partial derivatives (dz/dx) and (dz/dy) from:
    # (dz/dx) = - ((az-cz) + 2 ∙ (dz-fz) + (gz-iz)) / (8∙size_x)
    # (dz/dy) = ((az-gz) + 2 ∙ (bz-hz) + (cz-iz)) / (8∙size_y)

    az = altitudes[0]
    bz = altitudes[1]
    cz = altitudes[2]
    dz = altitudes[3]
    ez = altitudes[4]
    fz = altitudes[5]
    gz = altitudes[6]
    hz = altitudes[7]
    iz = altitudes[8]

    dz_dx = -(az - cz + (2 * (dz - fz)) + (gz - iz)) * (8 * lat_offset)
    dz_dy = (az - gz + (2 * (bz - hz)) + (cz - iz)) * (8 * lon_offset)

    slope_percentage = 100.0 * math.sqrt((dz_dx * dz_dx) + (dz_dy * dz_dy))

    return ez, round(slope_percentage, 0)

    else:
    return "No terrain data found."



    if __name__ == "__main__":
    print("Making GET request to OpenTopoData API...")

    lat = -9
    lon = -72

    terrain_result = get_terrain_data_for_gps_point(lat, lon)
    if terrain_result is not None:
    print(terrain_result)


    How can we help?

    Contact us

    Call us

    +31 (0)630884640  

    Go to:
    FarmTree Platform
     

    Terms & Privacy Policy
    Privacy Policy

    Terms & Conditions

    • ​
    •  
    • ​
    • ​
    •  FarmTree B.V. - Plus Ultra Building I, Bronland 12C-1, Wageningen, The Netherlands 
    • ​
    Copyright © FarmTree® B.V. 
    Powered by Odoo - Create a free website