{"openapi":"3.1.0","info":{"title":"Multispecies API","description":"# Overview\n\nThis API allows developers to access the [Scion/BSI](https://www.scionresearch.com) tree growth models that power the [Multispecies Dashboard](https://multispecies.nz) and the [Multispecies Carbon Calculator spreadsheet](https://fgr.nz/tools/multi-species-carbon-calculator). It is designed for data scientists and developers who want to integrate the Scion models directly into their own processes, tools, and products.\n\nThe API replicates the functionality found in the [MSCC spreadsheet](https://fgr.nz/tools/multi-species-carbon-calculator). It allows you to specify stand information, thinning/pruning schedules, a 'starting tree list' and other growth related parameters, and generate projections of stem volume, diameter, height, carbon and other metrics over time, and predicted log volumes at harvest.\n# AI Assistance\n\nYou do not need to be a programmer to use this API. An AI agent can call it for you, generate results, or write a script you can run later.\n\nPoint the agent at the AI usage guide:\n\n**[https://api.multispecies.nz/ai](https://api.multispecies.nz/ai)**\n\nThen ask for the analysis you want. For example:\n\n> Read the instructions at https://api.multispecies.nz/ai and then use the Multispecies API to create a graph showing how radiata harvest yield varies with 300-index and site-index.\n\nUse a coding-capable agent such as Claude Code. Free or general-purpose chat tools (for example ChatGPT) often struggle to follow the AI instructions properly.\n\nProgrammers may also find it helpful to ask an AI agent to assist with using the API in their preferred programming language. Or if you want to write code from scratch, the rest of this document provides information on the API schema and requirements.\n# Getting started\n\nHere is a quick guide to help you get up and running with writing code using the Multispecies API.\n\n### Python sample scripts\n\nThe API uses **HTTP and JSON** to handle processing requests. You can call it from any language or tool that can send `POST` requests (for example Python, R, JavaScript, or `curl`).\n\nThe examples in this documentation are written in Python. If you want to run them, install a supported Python 3 release (we recommend **3.9 or later**, available from [Python Downloads](https://www.python.org/downloads)) and then install the `requests` library:\n\n```bash\npython -m pip install requests\n```\n\nIf you already use another language, use the request/response shapes described in the **API technical specification** below and implement the same HTTP calls there.\n\nNo API key is required to call the model.\n\n### Minimal Python example\n\nThe script below requests a growth schedule for radiata pine over 25 years and prints standing volume (`vol_m3_per_ha`) for each year.\n\n```python\nimport requests\n\nAPI_URL = \"https://api.multispecies.nz/run\"\n\npayload = {\n    \"input_criteria\": {\n        \"species\": \"radiata pine\",\n        \"initialStocking\": 1000,\n        \"siteIndex\": 30,\n        \"index300\": 50,\n        \"rotationLength\": 25,\n    },\n}\n\nresponse = requests.post(API_URL, json=payload)\n\nprint(\"Status code:\", response.status_code)\n\nif response.status_code == 200 and response.json().get(\"status\") == \"OK\":\n    for year in response.json().get(\"results\", []):\n        vol = year.get(\"vol_m3_per_ha\")\n        print(\n            \"Year:\", year.get(\"age_years\"),\n            \"| Vol m³/ha:\", f\"{vol:.2f}\" if vol is not None else None,\n        )\nelse:\n    print(response.json())\n```\n\nSave the script (for example as `sample_basic.py`) and run:\n\n```bash\npython sample_basic.py\n```\n\nYou should see output similar to the following:\n\n![Example output](/docs/ScriptResult.jpg)\n\nAdditional examples are shown in the **API technical specification** section below.\n\n### Higher rate limits\n\nThe example above is limited to **10 requests per minute**. To raise that limit, email [support@carboncritical.org](mailto:support@carboncritical.org) and ask for an API key. Send the key in the `x-api-key` HTTP header:\n\n```python\nheaders = {\"x-api-key\": \"paste-your-key-here\"}\nresponse = requests.post(API_URL, json=payload, headers=headers)\n```\n\nWith a key, the limit is **100 requests per minute**. Batches may be larger than 10 runs, and scenarios may use up to **5,000** combinations instead of 100. An invalid key is rejected. Do not commit the key or paste it into a chat.\n\nA response without a key includes `meta.anonymous: true` and a short `meta.notice`.\n\n### Calling the API\n\nWhen you call the API you must provide a **payload** (a JSON object, or a Python `dict` if you use `requests`) describing the combination of input parameters you want to use—such as tree species, productivity indices, initial stocking, and so on. Some parameters are required; others are optional and default values will be used if omitted.\n\nSend the payload as the JSON body of a `POST` request. See the **API technical specification** section below for endpoint details and schemas.\n\n### Available functionality\n\nThere are three ways to run the growth model and retrieve results from the API.\n\n#### 1. Single request (`/run`)\n\nSend one configuration and receive growth projections for that configuration. Results are returned as a JSON object.\n\nIf you need to evaluate many configurations, use one of the batch methods below. They are more efficient than many separate `/run` calls and reduce the chance of hitting usage limits.\n\n#### 2. Batch requests (`/run/batch`)\n\nSend multiple independent configurations in a single API call. Each configuration is a JSON object; the request body is a JSON array of those objects. The response contains a corresponding list of results.\n\n#### 3. Scenario requests (`/run/scenario`)\n\nUse this mode when most parameters stay the same and one or more numeric parameters are varied over a range. For example, you might vary Site Index to see how it affects projected growth for a given site.\n\nFor each parameter to be varied, specify `start`, `stop`, and `step` values to define a discrete set of inputs. If more than one parameter is varied, **all combinations** are evaluated (subject to the scenario limit described under **Usage limits** below).\n\n### Usage limits\n\nUsage limits protect the service from accidental overload and keep response times predictable for all users.\n\n#### Rate limit\n\nCalls without an API key are limited to **10 requests per minute** per client, and **120 requests per minute** across all such clients. Batches are limited to 10 runs, and scenarios to 100 combinations.\n\nWith an API key, the limit is **100 requests per minute** for that key, measured over a rolling 60-second window. The batch-size cap above does not apply.\n\nIf you exceed this limit, the API responds with **HTTP 429 Too Many Requests** and a JSON body similar to:\n\n```json\n{\"detail\": \"Rate limit exceeded\"}\n```\n\nWhen this happens, wait until the current window has passed (typically up to one minute) before retrying. Prefer `/run/batch` or `/run/scenario` when you need many model runs, rather than sending hundreds of separate `/run` requests.\n\n`GET /health` only checks that the process is running. It is not rate-limited and does not run the growth model.\n\n#### Scenario combination limit\n\nScenario requests expand each swept parameter into a set of values and run the model for every combination. Without an API key, the limit is **100** combinations. With an API key, the limit is **5,000**.\n\nIf your scenario exceeds this limit, the API responds with **HTTP 400 Bad Request** and a message explaining how many combinations were requested. Reduce the number of steps or the number of swept parameters and try again.\n# Licence\n\nThis API is provided as a free service so that anyone with an interest in modelling long-term forest growth outcomes in Aotearoa New Zealand can programmatically access the Scion/BSI tree growth models and generate projections.\n\nYou may use the API for personal, research, and commercial purposes, subject to the usage limits described above and reasonable fair-use expectations. We may change limits or availability as the service evolves.\n\nThe underlying models remain the intellectual property of Scion/BSI and its licensors. This licence grants access via the API only; it does not transfer ownership of the models or permit redistribution of model code or data except as permitted by Scion/BSI.\n\n# Disclaimer\n\nThe models exposed by this API are statistical in nature. They represent **typical** outcomes based on national and regional datasets; they are not a substitute for site-specific knowledge, local experience, or professional forestry advice.\n\nTree growth and future environmental conditions involve inherent uncertainty. While reasonable endeavours have been made to provide a robust service, **we do not warrant** that the API will be accurate, complete, available without interruption, or fit for any particular purpose.\n\nThe API must not be used as the sole basis for carbon accounting, investment decisions, or planting decisions. Any projections should be interpreted alongside appropriate professional judgement and, where relevant, field measurements.\n\nTo the fullest extent permitted by law, Scion/BSI, Carbon Critical, and their respective officers, employees, and agents **exclude liability** for any loss, damage, or expense arising from use of or reliance on the API or its outputs, whether in contract, tort, or otherwise.\n\n---\n\n\nThe sections below describe each HTTP endpoint, including request payloads, response schemas, and example code.\n","version":"1.0.3","x-logo":{"url":"/assets/BSI_CC_logos_stacked.png?v=1793080796","altText":"Scion Forestry Growth API"}},"servers":[{"url":"/"}],"paths":{"/health":{"get":{"tags":["Monitoring"],"summary":"Health check","description":"Returns the current health status of the API.\n\nA response with `\"status\": \"ok\"` indicates that the service process is running.\nThis does not run the growth model. It is not rate-limited.\nUse it for uptime monitoring and load-balancer health checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/run":{"post":{"tags":["Single request"],"summary":"Run growth model","description":"Execute a single forestry growth simulation for one set of input parameters.\n\nReturns yearly projection results. `vol_m3_per_ha` is standing volume for that year.\n`log_volume` is a separate log-volume series. No API key is required.\nWithout a key, calls are limited to 10 requests per minute per client,\nand the response sets `meta.anonymous` to true.\nSend an `x-api-key` header for higher limits. An invalid key is rejected.","operationId":"run_single","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional. Omit this header to call the API: 10 requests per minute per client, batches of at most 10 runs, and scenarios of at most 100 combinations. Send a key for higher limits. An invalid or inactive key is rejected.","title":"X-Api-Key"},"description":"Optional. Omit this header to call the API: 10 requests per minute per client, batches of at most 10 runs, and scenarios of at most 100 combinations. Send a key for higher limits. An invalid or inactive key is rejected."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-codeSamples":[{"lang":"Python","label":"Python","source":"import requests\n\n#Requests the growth schedule for Radiata Pine over a 25 year period and prints\n#out the volume for each year. This estimates indices from stand metrics.\n\nAPI_URL = \"{server}/run\"\nheaders = {\n    \"Content-Type\": \"application/json\",\n}\n\npayload = {\n    \"input_criteria\": {\n        \"species\": \"radiata pine\",\n        \"initialStocking\": 1000,\n        \"siteIndex\": 30,\n        \"index300\": 50,\n        \"rotationLength\": 25\n    },\n    \"stand_metrics\": {\n        \"ageYears\": 17.47,\n        \"stockingStemsHA\": 367,\n        \"heightMTH\": 26.59,\n        \"ba\": 11.14,\n    },\n}\n\nresponse = requests.post(API_URL, json=payload, headers=headers)\n\nprint(\"Status code:\", response.status_code)\n\nif response.status_code == 200 and response.json().get(\"status\") == 'OK':\n    years = response.json().get(\"results\", [])\n    for y in years:\n        vol = y.get(\"vol_m3_per_ha\")\n        print(\n            \"Year:\", y.get(\"age_years\"),\n            \"| Vol m³/ha:\", f\"{vol:.2f}\" if vol is not None else None\n        )\n\n"},{"lang":"Python","label":"Python","source":"import requests\n\n#Requests the growth schedule for Radiata Pine over a 25 year period and prints\n#out the volume for each year.\n\nAPI_URL = \"{server}/run\"\n\nheaders = {\n    \"Content-Type\": \"application/json\",\n}\n\npayload = {\n    \"input_criteria\": {\n        \"species\": \"radiata pine\",\n        \"initialStocking\": 1000,\n        \"siteIndex\": 30,\n        \"index300\": 50,\n        \"rotationLength\": 25\n    },\n}\n\nresponse = requests.post(API_URL, json=payload, headers=headers)\n\nprint(\"Status code:\", response.status_code)\n\nif response.status_code == 200 and response.json().get(\"status\") == 'OK':\n    years = response.json().get(\"results\", [])\n    for y in years:\n        vol = y.get(\"vol_m3_per_ha\")\n        print(\n            \"Year:\", y.get(\"age_years\"),\n            \"| Vol m³/ha:\", f\"{vol:.2f}\" if vol is not None else None\n        )\n\n"},{"lang":"Python","label":"Python","source":"import requests\n\n#Requests the growth schedule for Radiata Pine over a 25 year period with\n#a defined thinning/pruning schedule and prints out the volume for each year.\n\nAPI_URL = \"{server}/run\"\nheaders = {\n    \"Content-Type\": \"application/json\",\n}\n\npayload = {\n    \"input_criteria\": {\n        \"species\": \"radiata pine\",\n        \"initialStocking\": 1111,\n        \"siteIndex\": 33.5,\n        \"index300\": 16.6,\n        \"rotationLength\": 25\n    },\n    \"starting_trees\": None,\n    \"stand_metrics\": None,\n    \"thinning_years\": [\n        {\n            \"thinYear\": 5,\n            \"thinStocking\": 800,\n            \"thinType\": \"Waste\",\n        },\n        {\n            \"thinYear\": 15,\n            \"thinStocking\": 350,\n            \"thinType\": \"Waste\",\n        }\n    ],\n    \"pruning_years\": [\n        {\n            \"pruneYear\": 6,\n            \"numStemsPrunedStemsPerHa\": 350,\n            \"prunedHeight\": 2.2\n        },\n        {\n            \"pruneYear\": 8,\n            \"numStemsPrunedStemsPerHa\": 350,\n            \"prunedHeight\": 4.4\n        },\n        {\n            \"pruneYear\": 10,\n            \"numStemsPrunedStemsPerHa\": 350,\n            \"prunedHeight\": 6.5\n        }\n    ],\n}\n\nresponse = requests.post(API_URL, json=payload, headers=headers)\n\nprint(\"Status code:\", response.status_code)\n\nif response.status_code == 200 and response.json().get(\"status\") == 'OK':\n    years = response.json().get(\"results\", [])\n    for y in years:\n        vol = y.get(\"vol_m3_per_ha\")\n        print(\n            \"Year:\", y.get(\"age_years\"),\n            \"| Vol m³/ha:\", f\"{vol:.2f}\" if vol is not None else None\n        )\n"}]}},"/run/batch":{"post":{"tags":["Batch requests"],"summary":"Batch growth simulations","description":"Run multiple independent growth simulations in one request.\n\nThe request body is a JSON array of run configurations (same shape as `/run`).\nEach result includes that request in `inputs`. Completion order is not\nguaranteed, so match results by `inputs` rather than by array position.\nNo API key is required. Without a key, calls are limited to\n10 requests per minute per client and at most 10 runs in one batch,\nand the response sets `meta.anonymous` to true.\nWith a key, that batch-size cap does not apply. An invalid key is rejected.","operationId":"run_batch","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional. Omit this header to call the API: 10 requests per minute per client, batches of at most 10 runs, and scenarios of at most 100 combinations. Send a key for higher limits. An invalid or inactive key is rejected.","title":"X-Api-Key"},"description":"Optional. Omit this header to call the API: 10 requests per minute per client, batches of at most 10 runs, and scenarios of at most 100 combinations. Send a key for higher limits. An invalid or inactive key is rejected."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/RunRequest"},"title":"Payloads"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-codeSamples":[{"lang":"Python","label":"Python","source":"import requests\n\n#This script runs two simulations, one with Radiata Pine and another with\n#Coast Redwood and returns growth models for both.\n\nAPI_URL = \"{server}/run/batch\"\nheaders = {\n    \"Content-Type\": \"application/json\",\n}\n\npayloads = [\n    {\n        \"input_criteria\": {\n            \"species\": \"radiata pine\",\n            \"initialStocking\": 1000,\n            \"siteIndex\": 30,\n            \"index300\": 50,\n            \"rotationLength\": 25\n        },\n        \"starting_trees\": None,\n        \"stand_metrics\": None,\n        \"thinning_years\": None,\n        \"pruning_years\": None,\n    },\n    {\n        \"input_criteria\": {\n            \"species\": \"coast redwood\",\n            \"initialStocking\": 1000,\n            \"siteIndex\": 30,\n            \"index300\": 50,\n            \"rotationLength\": 25\n        },\n        \"starting_trees\": None,\n        \"stand_metrics\": None,\n        \"thinning_years\": None,\n        \"pruning_years\": None,\n    }\n]\n\nresponse = requests.post(API_URL, json=payloads, headers=headers)\n\nprint(\"Status code:\", response.status_code)\nprint(\"Response JSON:\")\nprint(response.json())"}]}},"/run/scenario":{"post":{"tags":["Scenario requests"],"summary":"Scenario simulations","description":"Run scenario-based simulations where one or more numeric parameters are swept\nover ranges defined in the scenario specification.\n\nEach result's `inputs` object contains the swept values for that\ncombination (dotted parameter paths). Completion order is not guaranteed.\nNo API key is required. Without a key, the limit is 100 combinations\nand 10 requests per minute per client, and the response sets\n`meta.anonymous` to true.\nWith a key, the limit is 5,000 combinations per request.\nAn invalid key is rejected.","operationId":"run_scenario","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional. Omit this header to call the API: 10 requests per minute per client, batches of at most 10 runs, and scenarios of at most 100 combinations. Send a key for higher limits. An invalid or inactive key is rejected.","title":"X-Api-Key"},"description":"Optional. Omit this header to call the API: 10 requests per minute per client, batches of at most 10 runs, and scenarios of at most 100 combinations. Send a key for higher limits. An invalid or inactive key is rejected."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScenarioRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScenarioResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-codeSamples":[{"lang":"Python","label":"Python","source":"import requests\n\n#Requests multiple growth models for Radiata Pine over a 25 year period.\n#Site index runs from 20 to 30 (step 5) and 300 Index from 40 to 60 (step 10).\n#That is 9 combinations, within the 100-combination limit for calls without an API key.\n\nAPI_URL = \"{server}/run/scenario\"\nheaders = {\n    \"Content-Type\": \"application/json\",\n}\n\npayload = {\n    \"base_request\": {\n        \"input_criteria\": {\n            \"species\": \"radiata pine\",\n            \"initialStocking\": 1000,\n            \"siteIndex\": 30,\n            \"index300\": 50,\n            \"rotationLength\": 25\n        },\n        \"starting_trees\": None,\n        \"stand_metrics\": None,\n        \"thinning_years\": None,\n        \"growth_parameters\" : {\n            #Only need to specify those parameters that change from defaults.\n            \"elevationMetres\" : 400\n        }\n    },\n    \"scenario\": {\n        \"sweeps\": {\n            \"input_criteria.siteIndex\": {\n                \"start\": 20,\n                \"stop\": 30,\n                \"step\": 5\n            },\n            \"input_criteria.index300\": {\n                \"start\": 40,\n                \"stop\": 60,\n                \"step\": 10\n            }\n        }\n    }\n}\n\nresponse = requests.post(API_URL, json=payload, headers=headers)\n\nprint(\"Status code:\", response.status_code)\nprint(\"Response JSON:\")\nprint(response.json())"}]}}},"components":{"schemas":{"BatchMeta":{"properties":{"anonymous":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Anonymous","description":"True when this request was accepted without an API key."},"notice":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notice","description":"Present on anonymous responses. Explains the trial limits and how to request a key."},"batch_size":{"type":"integer","title":"Batch Size","description":"Number of requests in this batch."},"latency_ms":{"type":"integer","title":"Latency Ms","description":"Server time spent on this request, milliseconds."}},"additionalProperties":true,"type":"object","required":["batch_size","latency_ms"],"title":"BatchMeta"},"BatchResponse":{"properties":{"results":{"items":{"$ref":"#/components/schemas/BatchResultItem"},"type":"array","title":"Results","description":"One entry per request. Order is completion order, not request order. Use inputs to match each entry to the request that produced it."},"meta":{"$ref":"#/components/schemas/BatchMeta"}},"additionalProperties":true,"type":"object","required":["results","meta"],"title":"BatchResponse","description":"Response body for POST /run/batch."},"BatchResultItem":{"properties":{"inputs":{"$ref":"#/components/schemas/RunRequest","description":"The request that produced this entry. Identical to the corresponding item in the request array."},"result":{"anyOf":[{"$ref":"#/components/schemas/SimulationResult"},{"type":"null"}],"description":"Present when status is \"ok\"."},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Present when status is \"failed\"."},"status":{"type":"string","enum":["ok","failed"],"title":"Status","description":"\"ok\" if this item finished, \"failed\" if it raised an error. This is not the model status; see result.status for that."}},"additionalProperties":true,"type":"object","required":["inputs","status"],"title":"BatchResultItem","description":"One entry in a batch response. Match it to the request using inputs, not array position."},"CalibrationData":{"properties":{"siteIndex":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Siteindex","description":"Site index in metres used for this projection."},"i300":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"I300","description":"300 Index used for this projection."}},"additionalProperties":true,"type":"object","title":"CalibrationData","description":"Site productivity values used for the projection, after any calibration."},"CarbonBreakdown":{"properties":{"above_ground":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Above Ground","description":"Above-ground carbon pool."},"below_ground":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Below Ground","description":"Below-ground carbon pool."},"dead_wood":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Dead Wood","description":"Dead wood carbon pool."},"litter":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Litter","description":"Litter carbon pool."}},"additionalProperties":true,"type":"object","title":"CarbonBreakdown","description":"Carbon pools that make up carbon_total or carbon_second."},"GrowthModelInput":{"properties":{"species":{"type":"string","title":"Species","description":"Tree species identifier used by the growth model."},"initialStocking":{"type":"number","exclusiveMinimum":0.0,"title":"Initialstocking","description":"Initial stems per hectare at establishment.","examples":[850]},"siteIndex":{"type":"number","exclusiveMinimum":0.0,"title":"Siteindex","description":"Site index in metres at reference age.","examples":[32]},"index300":{"type":"number","exclusiveMinimum":0.0,"title":"Index300","description":"300 Index productivity metric.","examples":[450]},"rotationLength":{"type":"integer","exclusiveMinimum":0.0,"title":"Rotationlength","description":"Harvest rotation length in years.","examples":[28]}},"additionalProperties":false,"type":"object","required":["species","initialStocking","siteIndex","index300","rotationLength"],"title":"GrowthModelInput","description":"Core stand and site information used to initialise the growth model.","example":{"index300":450,"initialStocking":850,"rotationLength":28,"siteIndex":32,"species":"radiata pine"}},"GrowthParameters":{"properties":{"annualAdditionalMortalityPercent":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Annualadditionalmortalitypercent","description":"Additional annual mortality percentage applied to the stand.","examples":[1.5]},"cvOfDBHDistribution":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Cvofdbhdistribution","description":"Coefficient of variation for DBH distribution.","examples":[0.25]},"logLengthAtHarvest":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"null"}],"title":"Loglengthatharvest","description":"Harvest log length in metres.","examples":[5.5]},"minLogSEDInMm":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"null"}],"title":"Minlogsedinmm","description":"Minimum small-end diameter in millimetres.","examples":[150]},"stemBreakHeightPercent":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Stembreakheightpercent","description":"Stem break height as percentage of total tree height.","examples":[15]},"volumeLossesAtHarvestPercent":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Volumelossesatharvestpercent","description":"Estimated harvest volume losses percentage.","examples":[8]},"woodDensityAdjustment":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Wooddensityadjustment","description":"Multiplier applied to default wood density values.","examples":[1.05]},"agDeadWoodDecayHalfLifeYears":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"null"}],"title":"Agdeadwooddecayhalflifeyears","description":"Above-ground dead wood decay half-life in years.","examples":[10]},"bgDeadWoodDecayHalfLifeYears":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"null"}],"title":"Bgdeadwooddecayhalflifeyears","description":"Below-ground dead wood decay half-life in years.","examples":[18]},"latitudeDegrees":{"anyOf":[{"type":"number","maximum":90.0,"minimum":-90.0},{"type":"null"}],"title":"Latitudedegrees","description":"Site latitude in decimal degrees.","examples":[-37.8]},"elevationMetres":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Elevationmetres","description":"Site elevation above sea level in metres.","examples":[420]},"soilCPercent":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Soilcpercent","description":"Soil carbon percentage.","examples":[3.5]},"soilNPercent":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Soilnpercent","description":"Soil nitrogen percentage.","examples":[0.25]},"meanAnnualTemperature":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Meanannualtemperature","description":"Mean annual temperature in degrees Celsius.","examples":[12.5]},"driftFactor":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Driftfactor","description":"Calibration drift adjustment factor.","examples":[0.98]}},"additionalProperties":false,"type":"object","title":"GrowthParameters","description":"Optional advanced calibration parameters for the growth model."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"PruningYear":{"properties":{"pruneYear":{"type":"integer","exclusiveMinimum":0.0,"title":"Pruneyear","description":"Year pruning occurs.","examples":[12]},"numStemsPrunedStemsPerHa":{"type":"number","exclusiveMinimum":0.0,"title":"Numstemsprunedstemsperha","description":"Number of stems pruned (stems/ha).","examples":[450]},"prunedHeight":{"type":"number","exclusiveMinimum":0.0,"title":"Prunedheight","description":"Pruned height (m).","examples":[2.2]}},"additionalProperties":false,"type":"object","required":["pruneYear","numStemsPrunedStemsPerHa","prunedHeight"],"title":"PruningYear","description":"Defines a pruning event.","example":{"numStemsPrunedStemsPerHa":450,"pruneYear":12,"prunedHeight":2.2}},"RangeSpec":{"properties":{"start":{"type":"number","title":"Start","description":"Starting value for the sweep range.","examples":[300.0]},"stop":{"type":"number","title":"Stop","description":"Final value for the sweep range.","examples":[600.0]},"step":{"type":"number","exclusiveMinimum":0.0,"title":"Step","description":"Increment between sweep values.","examples":[50.0]}},"additionalProperties":false,"type":"object","required":["start","stop","step"],"title":"RangeSpec","description":"Defines a numeric parameter sweep range for scenario analysis.","example":{"start":300,"step":50,"stop":600}},"RunMeta":{"properties":{"anonymous":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Anonymous","description":"True when this request was accepted without an API key."},"notice":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notice","description":"Present on anonymous responses. Explains the trial limits and how to request a key."},"latency_ms":{"type":"integer","title":"Latency Ms","description":"Server time spent on this request, milliseconds."}},"additionalProperties":true,"type":"object","required":["latency_ms"],"title":"RunMeta"},"RunRequest":{"properties":{"input_criteria":{"$ref":"#/components/schemas/GrowthModelInput","description":"Core stand and site input parameters."},"starting_trees":{"anyOf":[{"$ref":"#/components/schemas/StartingTreeData"},{"type":"null"}],"description":"Optional measured inventory data for stand initialisation."},"stand_metrics":{"anyOf":[{"$ref":"#/components/schemas/StandMetrics"},{"type":"null"}],"description":"Optional stand metrics."},"thinning_years":{"anyOf":[{"items":{"$ref":"#/components/schemas/ThinningYear"},"type":"array"},{"type":"null"}],"title":"Thinning Years","description":"Optional thinning schedule."},"pruning_years":{"anyOf":[{"items":{"$ref":"#/components/schemas/PruningYear"},"type":"array"},{"type":"null"}],"title":"Pruning Years","description":"Optional pruning schedule."},"growth_parameters":{"anyOf":[{"$ref":"#/components/schemas/GrowthParameters"},{"type":"null"}],"description":"Optional advanced growth model calibration parameters."}},"additionalProperties":false,"type":"object","required":["input_criteria"],"title":"RunRequest","description":"Main simulation request for the forest growth model.","example":{"input_criteria":{"index300":450,"initialStocking":850,"rotationLength":28,"siteIndex":32,"species":"radiata pine"}}},"RunResponse":{"properties":{"status":{"type":"string","title":"Status","description":"Model status for this projection. \"OK\" means it succeeded. Any other value is a model error code and results should not be treated as valid."},"calib_data":{"$ref":"#/components/schemas/CalibrationData","description":"Calibrated site index and 300 Index for this projection."},"results":{"items":{"$ref":"#/components/schemas/YearResult"},"type":"array","title":"Results","description":"Yearly projection rows, including age 0 through the rotation length."},"meta":{"$ref":"#/components/schemas/RunMeta"}},"additionalProperties":true,"type":"object","required":["status","calib_data","results","meta"],"title":"RunResponse","description":"Response body for POST /run."},"ScenarioMeta":{"properties":{"anonymous":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Anonymous","description":"True when this request was accepted without an API key."},"notice":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notice","description":"Present on anonymous responses. Explains the trial limits and how to request a key."},"num_scenarios":{"type":"integer","title":"Num Scenarios","description":"Number of combinations evaluated."},"latency_ms":{"type":"integer","title":"Latency Ms","description":"Server time spent on this request, milliseconds."}},"additionalProperties":true,"type":"object","required":["num_scenarios","latency_ms"],"title":"ScenarioMeta"},"ScenarioRequest":{"properties":{"base_request":{"$ref":"#/components/schemas/RunRequest","description":"Base simulation request."},"scenario":{"$ref":"#/components/schemas/ScenarioSpec","description":"Scenario sweep definition."}},"additionalProperties":false,"type":"object","required":["base_request","scenario"],"title":"ScenarioRequest","description":"Scenario analysis request consisting of a base simulation\nand one or more parameter sweeps.","example":{"base_request":{"input_criteria":{"index300":450,"initialStocking":850,"rotationLength":28,"siteIndex":32,"species":"radiata pine"}},"scenario":{"sweeps":{"initialStocking":{"start":300,"step":50,"stop":600}}}}},"ScenarioResponse":{"properties":{"results":{"items":{"$ref":"#/components/schemas/ScenarioResultItem"},"type":"array","title":"Results","description":"One entry per combination. Order is completion order, not sweep order. Use inputs to identify which swept values produced each entry."},"meta":{"$ref":"#/components/schemas/ScenarioMeta"}},"additionalProperties":true,"type":"object","required":["results","meta"],"title":"ScenarioResponse","description":"Response body for POST /run/scenario."},"ScenarioResultItem":{"properties":{"inputs":{"additionalProperties":{"type":"number"},"type":"object","title":"Inputs","description":"Swept parameter values for this combination. Keys are dotted paths such as \"input_criteria.siteIndex\"."},"result":{"anyOf":[{"$ref":"#/components/schemas/SimulationResult"},{"type":"null"}],"description":"Present when status is \"ok\"."},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Present when status is \"failed\"."},"status":{"type":"string","enum":["ok","failed"],"title":"Status","description":"\"ok\" if this combination finished, \"failed\" if it raised an error. This is not the model status; see result.status for that."}},"additionalProperties":true,"type":"object","required":["inputs","status"],"title":"ScenarioResultItem","description":"One combination from a scenario sweep."},"ScenarioSpec":{"properties":{"sweeps":{"additionalProperties":{"anyOf":[{"type":"number"},{"$ref":"#/components/schemas/RangeSpec"}]},"type":"object","title":"Sweeps","description":"Dictionary of parameter sweeps. Keys are parameter names and values are either fixed numeric values or sweep ranges."}},"additionalProperties":false,"type":"object","required":["sweeps"],"title":"ScenarioSpec","description":"Defines scenario sweep parameters.\n\nEach key corresponds to a model parameter name.\nValues may either be:\n- a fixed numeric override\n- a range specification for sensitivity analysis","example":{"sweeps":{"initialStocking":{"start":300,"step":50,"stop":600},"woodDensityAdjustment":1.05}}},"SimulationResult":{"properties":{"status":{"type":"string","title":"Status","description":"Model status for this projection. \"OK\" means it succeeded. Any other value is a model error code and results should not be treated as valid."},"calib_data":{"$ref":"#/components/schemas/CalibrationData","description":"Calibrated site index and 300 Index for this projection."},"results":{"items":{"$ref":"#/components/schemas/YearResult"},"type":"array","title":"Results","description":"Yearly projection rows, including age 0 through the rotation length."}},"additionalProperties":true,"type":"object","required":["status","calib_data","results"],"title":"SimulationResult","description":"Growth-model result for one set of inputs. Does not include request latency."},"StandMetrics":{"properties":{"ageYears":{"type":"number","exclusiveMinimum":0.0,"title":"Ageyears","description":"Age in years.","examples":[17]},"stockingStemsHA":{"type":"number","exclusiveMinimum":0.0,"title":"Stockingstemsha","description":"Stocking (measured in stems/ha)","examples":[367]},"heightMTH":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"null"}],"title":"Heightmth","description":"Height in metres (MTH) - either MTH or meanHeight must be specified but not both.","examples":[26.59]},"meanHeight":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"null"}],"title":"Meanheight","description":"Height in metres (meanHeight) - either MTH or meanHeight must be specified but not both.","examples":[26.59]},"ba":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"null"}],"title":"Ba","description":"BA (measured in metres squared per ha) - either BA OR DBH must be specified but not both.","examples":[11.14]},"dbh":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"null"}],"title":"Dbh","description":"DBH (measured in cm) - either BA OR DBH must be specified but not both.","examples":[11.14]}},"additionalProperties":false,"type":"object","required":["ageYears","stockingStemsHA"],"title":"StandMetrics","description":"Stand metrics used for initial calibration of SiteIndex and Index300.","example":{"ageYears":17,"ba":11.14,"heightMTH":28.1,"stockingStemsHA":367}},"StartingTree":{"properties":{"stemNumber":{"type":"integer","exclusiveMinimum":0.0,"title":"Stemnumber","description":"Unique tree stem identifier."},"dbhCm":{"type":"number","exclusiveMinimum":0.0,"title":"Dbhcm","description":"Diameter at breast height in centimetres.","examples":[42.5]},"heightMetres":{"type":"number","exclusiveMinimum":0.0,"title":"Heightmetres","description":"Tree height in metres.","examples":[28.1]}},"additionalProperties":false,"type":"object","required":["stemNumber","dbhCm","heightMetres"],"title":"StartingTree","description":"Individual tree measurement used for initial stand reconstruction.","example":{"dbhCm":42.5,"heightMetres":28.1,"stemNumber":1}},"StartingTreeData":{"properties":{"plotAreaHa":{"type":"number","exclusiveMinimum":0.0,"title":"Plotareaha","description":"Plot area in hectares.","examples":[0.04]},"ageYears":{"type":"integer","exclusiveMinimum":0.0,"title":"Ageyears","description":"Stand age in years.","examples":[12]},"trees":{"items":{"$ref":"#/components/schemas/StartingTree"},"type":"array","title":"Trees","description":"Measured trees within the inventory plot."}},"additionalProperties":false,"type":"object","required":["plotAreaHa","ageYears","trees"],"title":"StartingTreeData","description":"Initial measured tree inventory data.","example":{"ageYears":12,"plotAreaHa":0.04,"trees":[{"dbhCm":42.5,"heightMetres":28.1,"stemNumber":1}]}},"ThinType":{"type":"string","enum":["Production","Waste"],"title":"ThinType"},"ThinningYear":{"properties":{"thinYear":{"type":"integer","exclusiveMinimum":0.0,"title":"Thinyear","description":"Year thinning occurs.","examples":[12]},"thinStocking":{"type":"number","exclusiveMinimum":0.0,"title":"Thinstocking","description":"Residual stocking after thinning (stems/ha).","examples":[450]},"thinType":{"$ref":"#/components/schemas/ThinType","description":"Type of thinning operation.","default":"Waste"},"thinCoefficient":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"null"}],"title":"Thincoefficient","description":"Optional thinning coefficient."}},"additionalProperties":false,"type":"object","required":["thinYear","thinStocking"],"title":"ThinningYear","description":"Defines a thinning event.","example":{"thinCoefficient":0.75,"thinStocking":450,"thinType":"Waste","thinYear":12}},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"YearResult":{"properties":{"age_years":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Age Years","description":"Stand age in years for this row."},"stocking_stems_per_ha":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Stocking Stems Per Ha","description":"Predicted stocking, stems per hectare."},"ba_m2_per_ha":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ba M2 Per Ha","description":"Predicted basal area, square metres per hectare."},"dbh_cm":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Dbh Cm","description":"Predicted mean diameter at breast height, centimetres."},"vol_m3_per_ha":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Vol M3 Per Ha","description":"Predicted standing stem volume for this year, cubic metres per hectare. This is not log volume."},"log_volume":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Log Volume","description":"Predicted log volume for this year. A separate series from vol_m3_per_ha."},"mean_top_height_m":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Mean Top Height M","description":"Predicted mean top height, metres."},"mean_height_m":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Mean Height M","description":"Predicted mean height, metres."},"wood_density_kg_per_m3":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Wood Density Kg Per M3","description":"Predicted wood density, kilograms per cubic metre."},"carbon_total":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Carbon Total","description":"Predicted total carbon stock for this year. See carbon_breakdown."},"carbon_second":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Carbon Second","description":"Second carbon stock estimate returned by the model for this year. See carbon_second_breakdown."},"carbon_breakdown":{"anyOf":[{"$ref":"#/components/schemas/CarbonBreakdown"},{"type":"null"}],"description":"Pools that make up carbon_total."},"carbon_second_breakdown":{"anyOf":[{"$ref":"#/components/schemas/CarbonBreakdown"},{"type":"null"}],"description":"Pools that make up carbon_second."}},"additionalProperties":true,"type":"object","title":"YearResult","description":"One year of a growth projection. Values are model predictions, not measurements."}}},"tags":[{"name":"Monitoring","description":"Endpoints for checking whether the API process is running. These do not run the growth model."},{"name":"Single request","description":"Run the growth model once per request. Use when you need a single set of projections."},{"name":"Batch requests","description":"Run multiple independent configurations in one request. Prefer this over many `/run` calls when inputs differ."},{"name":"Scenario requests","description":"Vary one or more numeric parameters over ranges and evaluate all combinations (up to 5,000 per request)."}],"x-tagGroups":[{"name":"API technical specification","tags":["Monitoring","Single request","Batch requests","Scenario requests"]}]}