Introduction
Solvi API is organized around REST and allows for programmatic access to resources like users and projects. Below you will find detailed information on how to access the API and which operations are currently supported.
In Solvi, a Project represents a single upload of multiple images. Projects can be grouped under Fields, which in turn are grouped into Farms to make it easier to organize and share data. Current API implementation allows to create and fetch projects for the specific user, as well as upload and stitch images into maps. Once processed, the outputs such as orthomosaics and elevation maps can either be accessed through the API or the user can be redirected to Solvi to view and analyze the processed imagery.
HTTP Response Codes
Like any REST API, Solvi's API indicates the result of a request through the HTTP status code in the response. After sending a request to an endpoint, the returned status must be checked to ensure the request succeeded.
- Successful results return a
2xxstatus:200is used forGETrequests201is returned after successfully creating a new entity from aPOSTrequest
- An invalid request is indictaed by a
4xxstatus:400is used to indicate the parameters were invalid404if the requested resource does not exist402if the account does not have enough quota403if the authenticated user is not allowed to perform this request
302is used if the request was not properly authenticated (this will likely change in the future)5xxmeans an internal error occurred: please contact support since this indicates a
Authentication
Example API key request:
curl "api_endpoint_here"
-H "X-Api-Key: <your-api-key>"
Make sure to replace
<your-api-key>with your API key.
To use Solvi API you first need an API key. You will get an API key from Solvi, contact us to get your key. If you for some reason need to revoke a key, you can also contact us.
Please note that the API key should be kept secret and not be exposed to end-users: for example, never send the API key to a web browser.
With the API key, you can access general API methods, for example, to register new users in Solvi, and create user-specific tokens.
Requests requiring an API key should add the X-Api-Key header:
X-Api-Key: <your-api-key>
Example user-specific request:
curl "api_endpoint_here"
-H "Authorization: Bearer <user-specific-token>"
Make sure to replace
<user-specific-token>with your token.
Instead of using the API key, requests that act on behalf of a specific user, like creating or getting projects, should use a user-specific token to authenticate as the user. When using a user-specific token, do not include the API key in the same request. A user-specific token has a limited lifetime and is less security sensitive compared to the API key. For example, a user token can be sent to and used directly from for example a browser, or for passwordless authentication when the user is redirected from your portal to Solvi. A user-specific token can be used for a limited amount of time (currently 24 hours) before it expires. A user-specific token is created by using the endpoint to create a user-specific token.
User specific requests should use the Authorization header instead of the X-Api-Key header:
Authorization: Bearer <user-specific-token>
Users
Register new user
Example request:
curl -X POST
-H "X-Api-Key: <your-api-key>"
-H "Content-Type: application/json"
-d '{ "user": { "email": "john@example.com", "password": "password", "first_name": "John", "last_name": "Doe"}}'
"https://solvi.ag/api/v1/users"
Example response:
{
"status": "success",
"user_id": 182
}
This endpoint registers a new user. If successful, the response will return user_id that you would use later to generate user-specific tokens.
HTTP Request
POST https://solvi.ag/api/v1/users
Parameters
| Parameter | Description | |
|---|---|---|
| required | ||
| password | required | Password |
| first_name | required | First name |
| last_name | required | Last name |
Generate user-specific token
Example request:
curl -X GET
-H "X-Api-Key: <your-api-key>"
"https://solvi.ag/api/v1/users/<user_id>/token"
Example response:
{
"status": "success",
"user_id": 26,
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoyNiwiZXhwIjo0OCwiZXhwIjoxNTAxMjU2NzAyfQ.cu5zIye7ubBhv7YsFIxXkO0E_W0hG0VrlOTQx6L3b3c"
}
This endpoint gives a token for the specific user that should be used to create and retrieve user projects. Every request will generate na ew token. The token is valid for 24 hours.
HTTP Request
GET https://solvi.ag/api/v1/users/<user_id>/token
Parameters
| Parameter | Description | |
|---|---|---|
| user_id | required | User ID given when user is created |
Attaching custom metadata
When using the API, it is common that the farms, fields and projects in Solvi also exist in your own systems, and you need information to track which entity in your system a Solvi entity maps to. There are many ways to accomplish this, but one way is to attach metadata to the Solvi entities. Farms, fields and projects created through the API all support attaching arbitrary data to them. Solvi does not use this data, so you can put anything here, as long as it can be encoded in JSON.
Endpoints to create or update farms, fields and projects all accept an optional parameter metadata for this purpose.
Endpoints that return farms, fields and projects also return the metadata under the metadata property if set, or null otherwise.
Farms
Create farm
Example request:
curl -X POST
-H "Authorization: Bearer <user-jwt-token>"
-H "Content-Type: application/json"
-d '{"farm": {"name": "My second farm"}}'
"https://solvi.ag/api/v1/farms"
Example response:
{
"status": "success",
"farm_id": 793
}
Creates a new farm.
HTTP Request
POST https://solvi.ag/api/v1/farms
Parameters
| Parameter | Description | |
|---|---|---|
| name | required | Name of the the farm |
| metadata | optional | Optional arbitrary metadata |
Get farms
Example request:
curl -X GET
-H "Authorization: Bearer <user-jwt-token>"
-H "Content-Type: application/json"
"https://solvi.ag/api/v1/farms"
Example response:
[
{
"name": "My farm",
"created_at": "2019-02-08T09:18:37.655Z",
"fields": [
{
"id": 2657,
"name": "Veddige",
"created_at": "2019-03-08T04:05:38.628Z",
"metadata": null
}
],
"metadata": null
}
]
Gets all user farms and a list of fields related to each farm.
HTTP Request
GET https://solvi.ag/api/v1/farms
Fields
Create field
Example request:
curl -X POST
-H "Authorization: Bearer <user-jwt-token>"
-H "Content-Type: application/json"
-d '{"field": { "name": "Winter Wheat", "geom": "{\"type\":\"Polygon\",\"coordinates\":[[[100.0, 0.0],[101.0, 0.0],[101.0, 1.0],[100.0, 1.0],[100.0, 0.0]]]}"}}'
"https://solvi.ag/api/v1/fields"
Example response:
{
"status": "success",
"field_id": 793
}
Creates a new field. Field boundaries can be provided as Polygon or MultiPolygon in GeoJSON format. The farm to organize the field under can also be provided. The response contains field_id which can be later used to relate projects to the specific field.
HTTP Request
POST https://solvi.ag/api/v1/fields
Parameters
| Parameter | Description | |
|---|---|---|
| name | required | Name of the the field |
| geom | optional | Boundaries of the field as a Polygon or Multipolygon in GeoJSON format and EPSG:4326 coordinate system(lonlat) |
| farm_id | optional | The id of the farm to put the field under; if not specified, the user's last created farm is used |
| metadata | optional | Optional arbitrary metadata |
Get fields
Example request:
curl -X GET
-H "Authorization: Bearer <user-jwt-token>"
-H "Content-Type: application/json"
"https://solvi.ag/api/v1/fields"
Example response:
[
{
"id": 199,
"name": "wheat field",
"created_at": "2018-02-27T12:45:05.550Z",
"projects": [
{
"project_id": 1291,
"status": "processed",
"name": "27-FEB-2018",
"field_name": "Wheat field",
"survey_date": "2018-02-26T14:19:36.000Z",
"upload_date": "2018-02-27T12:45:05.556Z",
"url": "https://solvi.ag/projects/1291",
"thumbnail_url": "https://solvi.ag/projects/1291/thumbnail.png",
"metadata": null
}
],
"metadata": null
}
]
Gets all user fields and a list of projects related to each field.
HTTP Request
GET https://solvi.ag/api/v1/fields
Projects
Create project
Example request:
curl -X POST
-H "Authorization: Bearer <user-jwt-token>"
-H "Content-Type: application/json"
-d '{ "type": "overlapping", "field_id": "field_1", "field_name": "Wheat Field", "field_geom": "{\"type\":\"Polygon\",\"coordinates\":[[[100.0, 0.0],[101.0, 0.0],[101.0, 1.0],[100.0, 1.0],[100.0, 0.0]]]}" }'
"https://solvi.ag/api/v1/projects"
Example response:
{
"status": "success",
"project_id": 142,
"project_url": "https://solvi.ag/projects/142/photos/upload",
"imagery_upload_data": {
"url":"https://solvi-projects-dev.s3.eu-west-1.amazonaws.com",
"fields": {
[...]
},
"key_prefix":"uploads/26c1ce11-c9bf-4825-821c-72e9f600a6cf/originals/"
},
}
This endpoint creates a new project which is required before imagery upload. In response, you will receive URL to upload-page for the newly created project where the user can be redirected.
There are multiple types of projects: overlapping (the default), stitched and scouting:
overlappingshould be used if you have a number of photos taken which should be stitched into a map by Solvi; this is the default and what you normally usestitchedcan be used if you already have a single GeoTIFF that have been stitched by another system- In
scouting, images will not be stitched and instead shown as separate, individual images without further processing
Projects can be connected to a Field. When multiple projects are related to the same Field, they appear in the same map view when data is processed. This allows for easier navigation between imagery over the same Field and over the time data comparison.
Fields can be either be:
- created beforehand; for this case, set the
field_idparameter to the previously created field'sid - created on the fly by sending
field_nameandfield_geom(optional), which will create a new field for the project; do not includefield_id
Optionally, a project can be created with a so-called webhook that will be called every time the status of the project changes. This makes it possible for integration to for example react when a project finishes processing, without having to use polling to check the project's status. To add a webhook, specify the URL to be called with the status_webhook parameter. See the section on webhooks for details.
The project's crop, survey date and altitude, data which will be displayed in Solvi's UI, can also be submitted.
HTTP Request
POST https://solvi.ag/api/v1/projects
Parameters
| Parameter | Description | |
|---|---|---|
| type | optional | The type of imagery for this project: overlapping, stitched or scouting; default is overlapping |
| field_id | Solvi field id as returned from Fields API | |
| field_name | Name of the the field | |
| field_geom | optional | Boundaries of the field as a polygon in GeoJSON format and EPSG:4326 coordinate system(lonlat) |
| status_webhook | optional | A URL to be called when the project's status changes |
| webhook_secret | optional | A token to use to sign webhook requests, see webhooks for details |
| survey_date | optional | Date survey was conducted, formatted as YYYY-MM-DD |
| crop | optional | The documented crop; free form string |
| altitude | optional | Altitude the imagery was taken from in meters |
| metadata | optional | Optional arbitrary metadata |
Update project data
Example request:
curl -X PUT
-H "Authorization: Bearer <user-jwt-token>"
-H "Content-Type: application/json"
-d '{
"survey_date": "2022-08-18"
}'
Example response:
{
"status": "ok"
}
HTTP Request
PUT https://solvi.ag/api/v1/projects/<project_id>
Parameters
| Parameter | Description | |
|---|---|---|
| survey_date | optional | Date survey was conducted, formatted as YYYY-MM-DD |
| crop | optional | The documented crop; free form string |
| altitude | optional | Altitude the imagery was taken from in meters |
| metadata | optional | Optional arbitrary metadata |
This endpoint allows updating survey date, crop, altitude and metadata for a project.
Upload local imagery
Example request:
curl -X POST
-H "Authorization: Bearer <user-jwt-token>"
-H "Content-Type: application/json"
"https://solvi.ag/api/v1/projects/<project_id>/begin_upload"
Example response:
{
"status": "ok",
"imagery_upload_data": {
"type": "post_form_data",
"url": "https://solvi-production.s3.eu-west-1.amazonaws.com",
"fields": {
"x-amz-storage-class": "STANDARD_IA",
"policy": "eyJleHBpcmF0aW9uIjoiMjAyNS0wNS0yMVQwNjowMjo1N1oiLCJjb25kaXRpb25zIjpbeyJidWNrZXQiOiJzb2x2aS1wcm9kdWN0aW9uIn0sWyJzdGFydHMtd2l0aCIsIiRrZXkiLCJ1cGxvYWRzLzgxMDQ0N2JkLWQ5MzEtNGJiNS05OTE0LTA2NGZlNDRiODUzZS9vcmlnaW5hbHMvIl0sWyJzdGFydHMtd2l0aCIsIiRDb250ZW50LVR5cGUiLCIiXSx7IngtYW16LXN0b3JhZ2UtY2xhc3MiOiJTVEFOREFSRF9JQSJ9LHsieC1gbXotY3JlZGVudGlhbCI6IkFLSUFVVk1IS0dUUUsyQVVBNExRLzIwMjUwNTE0L2V1LXdlc3QtMS9zMy9hd3M0X3JlcXVlc3QifSx7IngtYW16LWFsZ29yaXRobSI6IkFXUzQtSE1BQy1TSEEyNTYifSx7IngtYW16LWRhdGUiOiIyMDI1MDUxNFQwNjAyNTdaIn1dfQ==",
"x-amz-credential": "AKIAUVMHKGTQK2AUA4LQ/20250514/eu-west-1/s3/aws4_request",
"x-amz-algorithm": "AWS4-HMAC-SHA256",
"x-amz-date": "20250514T060257Z",
"x-amz-signature": "5811e53afc5d1f2e94fc614a29b699d89529d112eb432d7c6d332fafbbf9c987"
},
"key_prefix": "uploads/26c1ce11-c9bf-4825-821c-72e9f600a6cf/originals/",
"number_parallel_hint": 3
}
}
or
{
"status": "ok",
"imagery_upload_data": {
"type": "url_template",
"template": "https://uploads.solvi.ag/uploads/26c1ce11-c9bf-4825-821c-72e9f600a6cf/originals/$filename?token=eyJhbGciOiJIUzI1NiJ9.eyJrZXlQcmVmaXgiOiJ1cGxvYWRzLzVkM2UyZDIwLTIxYTMtNGMyOC1hZWYzLTIxYTU5Y2Y0MzhhZC9vcmlnaW5hbHMvIiwiZXhwIjoxNzQ3ODA3MjgwfQ.UQzDIM020OOrwsFwzEFCtIjLEJJiYYFsPZdwLqLZf58",
"number_parallel_hint": 3
}
}
After a project has been created, imagery can be uploaded to it by sending the begin_upload request. The response includes information required to upload imagery for the project. The response can be of two different types depending on your project type and customer configuration: post_form_data and url_template.
Example JavaScript function to upload an image using
imagery_upload_dataretrieved from the example above:
function uploadFile(imagery_upload_data, f) {
let fetchPromise;
switch (imagery_upload_data.type) {
case "url_template" {
const url = imagery_upload_data.template.replace("$filename", f.name);
fetchPromise = fetch(url, {
method: "PUT",
body: f,
headers: { "Content-Type": f.type },
});
break
}
case "post_form_data" {
const form = new FormData();
for (const key in imagery_upload_data.fields) {
form.append(key, imagery_upload_data.fields[key]);
}
form.append("key", imagery_upload_data.key_prefix + f.name);
form.append("Content-Type", f.type);
form.append("file", f);
fetchPromise = fetch(imagery_upload_data.url, {
method: "POST",
body: form,
});
break
}
default:
throw new Error(`Unexpected upload type ${imagery_upload_data.type}`)
}
return fetchPromise.then((response) => {
if (!response.ok) {
throw new Error(
`Unexpected response HTTP ${response.status} ${response.statusText}`
);
}
});
}
Example JavaScript function to post an array of files using the
uploadFilefunction above, usingimagery_upload_dataretrieved from the example above:
function uploadFiles(imagery_upload_data, files) {
return uploadNext();
function uploadNext() {
return new Promise(function (resolve, reject) {
const file = files.shift();
if (file) {
uploadFile(imagery_upload_data, file).then(uploadNext).catch(reject);
} else {
resolve();
}
});
}
}
Example Python implementation to upload a file using
imagery_upload_datausing the requests library:
import os
import requests
def upload_file(imagery_upload_data, file_path):
with open(file_path, "rb") as f:
if imagery_upload_data["type"] == "post_form_data":
form_data = imagery_upload_data["fields"]
key = imagery_upload_data["key_prefix"] + os.path.basename(file_path)
form_data["key"] = key
form_data["Content-Type"] = "image/jpeg"
response = requests.post(
imagery_upload_data["url"],
data=form_data,
files={"file": (key, f)},
)
elif imagery_upload_data["type"] == "url_template":
url = imagery_upload_data["template"].replace(
"$filename", os.path.basename(file_path)
)
response = requests.put(url, data=f)
else:
raise Exception(f'Unknown type "{imagery_upload_data["type"]}"')
if response.status_code not in [200, 201, 204]:
raise Exception(
f"Error uploading image '{file_path}': HTTP {response.status_code} - {response.text}"
)
post_form_data method
The required parameters are included in the imagery_upload_data object: this object has a url property indicating the URL to POST imagery to, and a fields object, listing the HTTP form data fields required for the POST request. In addition to these fields, the form must also include a key field, this is the destination path for the uploaded file: the key must start with specified key_prefix and its value must be unique for each image, usually the key is set from the key_prefix and the uploaded file's filename. Note that subdirectories are not allowed and the filename can't contain special characters like /, \ and :.
url_template method
The response includes a property called url_template which should be used to create URLs to PUT files to. The template includes that part $filename which should be replaced with the actual name of the file being uploaded. Note that subdirectories are not allowed and the filename can't contain special characters like /, \ and :.
HTTP Request
POST https://solvi.ag/api/v1/projects/<project_id>/begin_upload
Parameters
| Parameter | Description | |
|---|---|---|
| project_id | required | Project ID given when project is created |
Upload remote imagery
Example request:
curl -X POST
-H "Authorization: Bearer <user-jwt-token>"
-H "Content-Type: application/json"
"https://solvi.ag/api/v1/projects/<project_id>/process_external_ortho"
-d '{
"ortho_url": "https://example.com/my_ortho.tiff"
}'
Example response:
{
"status": "ok"
}
This will start processing a prestitched ortho photo located at the URL provided in the ortho_url parameter. Note that the ortho must be possible to download through a simple HTTP GET request without any other authentication than is present in the URL itself. For example a presigned S3 URL is suitable.
You should not use the complete_upload endpoint in combination with this endpoint, processing will start immediately when using this endpoint.
Note that this operation is only valid for projects of prestitched type.
HTTP Request
POST https://solvi.ag/api/v1/projects/<project_id>/process_external_ortho
Parameters
| Parameter | Description | |
|---|---|---|
| project_id | required | Project ID given when project is created |
| ortho_url | required | The URL to download the prestitched ortho from |
Processing uploaded imagery
Example request:
curl -X POST
-H "Authorization: Bearer <user-jwt-token>"
-H "Content-Type: application/json"
"https://solvi.ag/api/v1/projects/<project_id>/complete_upload"
Example response:
{
"status": "ok"
}
When project imagery has been uploaded, the upload must be completed, which will start the processing of the imagery.
If processing was successfully started, the HTTP response code will be 200. In addition, a response of 402 (Payment required) can also be returned, if the upload or credit quota for the user is not sufficient for processing the upload.
HTTP Request
POST https://solvi.ag/api/v1/projects/<project_id>/complete_upload
Parameters
| Parameter | Description | |
|---|---|---|
| project_id | required | Project ID given when project is created |
| created_from | optional | Filter by project creation date, start of interval |
| created_to | optional | Filter by project creation date, end of interval |
| survey_date_from | optional | Filter by project survey date, start of interval |
| survey_date_to | optional | Filter by project survey date, end of interval |
Get projects
Example request:
curl -X GET
-H "Authorization: Bearer <user-jwt-token>"
'https://solvi.ag/api/v1/projects?field_geom={"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[100.0, 0.0],[101.0, 0.0],[101.0, 1.0],[100.0, 1.0],[100.0, 0.0]]]}]}'
Example response:
[
{
"project_id": 9999,
"status": "processed",
"name": "25-JUN-2018",
"plant_counts": "published",
"user": {
"name": "Joe Smith",
"email": "joe@example.com"
},
"plant_counts": "",
"field": {
"id": 9999,
"name": "Winter Wheat",
"identfier": "WW-01",
"farm": {
"id": 656,
"name": "Borgeby Farm"
}
},
"survey_date": "2018-06-25T19:19:27.000Z",
"upload_date": "2018-06-25T20:50:14.870Z",
"url": "https://solvi.ag/projects/9999",
"thumbnail_url": "https://solvi.ag/projects/9999/thumbnail.png",
"metadata": null,
"number_images": 322,
"bands": "rgb",
"crop": "Wheat",
"relative_altitude": 99.9,
"absolute_altitude": 95.443,
"extent": {
"type": "MultiPolygon",
"coordinates": [
[
[
[12.335404361111113, 57.30721566666666],
[12.337469722222222, 57.30721566666666],
[12.337469722222222, 57.30810941666666],
[12.335404361111113, 57.30810941666666],
[12.335404361111113, 57.30721566666666]
]
]
]
}
},
{
"name": "New project",
"url": "https://solvi.ag/projects/new",
"metadata": null
}
]
This endpoint retrieves all projects created by the user or shared with user by others. If field boundaries are provided, only projects whose extent overlaps boundaries are returned.
HTTP Request
GET https://solvi.ag/api/v1/projects
Parameters
| Parameter | Description | |
|---|---|---|
| field_geom | optional | Boundaries of the field as a polygon in GeoJSON format and EPSG:4326 coordinate system(lonlat). |
Project outputs
Example request:
curl -X GET
-H "Authorization: Bearer <user-jwt-token>"
'https://solvi.ag/api/v1/projects/<project-id>
Example response:
{
"project_id": 9999,
"status": "processed",
"name": "25-JUN-2018",
"plant_counts": "published",
"field": {
"id": 9999,
"name": "Winter Wheat",
"identfier": "WW-01",
"farm": {
"id": 656,
"name": "Borgeby Farm"
}
},
"survey_date": "2018-06-25T19:19:27.000Z",
"upload_date": "2018-06-25T20:50:14.870Z",
"url": "https://solvi.ag/projects/9999",
"thumbnail_url": "https://solvi.ag/projects/9999/thumbnail.png",
"resources": {
"thumbnail": "https://solvi.ag/projects/9999/thumbnail.png",
"ortho": "https://solvi-projects.s3.eu-west-1.amazonaws.com/uploads/ed12e5f6-b6c9-4df8-9522-1dbc29be854b/results/ortho.tiff?...",
"dem": "https://solvi-projects-dev.s3.eu-west-1.amazonaws.com/uploads/ed12e5f6-b6c9-4df8-9522-1dbc29be854b/results/dem.tiff?..."
},
"metadata": null
},
This endpoint retrieves projects created by the user or shared with the user by others.
The included resources are URLs that can be used to fetch the project outputs. These resource URLs are temporary, with a lifetime of 15 minutes before they expire.
HTTP Request
GET https://solvi.ag/api/v1/projects/<project_id>
Parameters
| Parameter | Description | |
|---|---|---|
| project_id | required | Project ID given when project is created |
Project tiled imagery
Example request:
curl -X GET
-H "Authorization: Bearer <user-jwt-token>"
'https://solvi.ag/api/v1/projects/<project-id>/tiles/<type>
Example response:
{
"url": "https://ts2.solvi.nu/.../results/ortho.tiff/rgb/rgb/tile/{z}/{x}/{y}.png?token=...",
"type": "tileset/orthomosaic",
"resolution": 0.15961538646165616,
"bounds": {
"value": [
-115.407127627406, 33.13991737241373, -115.39728408213948, 33.144345906954
],
"crs": "EPSG:4326"
}
}
This endpoint retrieves a tile template URL, suitable for use with popular map clients like OpenLayers or Leaflet. The URLs are temporary, with a lifetime of at least 48 hours.
Note that all types are not necessarily available for all projects. Requesting a tile type that is not available will give a HTTP 404 response.
For ortho and dem tiles (when response type is "tileset/orthomosaic), geographic bounds are returned in the bounds property, and the native
resolution (meters/pixel) is given by resolution.
For plant_counts (when response type is "tileset/plant_counts), number_plants and number_missing give totals for the detected plants.
HTTP Request
GET https://solvi.ag/api/v1/projects/<project-id>/tiles/<type>
Parameters
| Parameter | Description | |
|---|---|---|
| project_id | required | Project ID given when project is created |
| type | required | Type of tiles to fetch; currently one of ortho for RGB orthomosaic, dem for colored elevation model or plant_counts for plant counts |
Webhooks
Webhooks allow setting up integrations that subscribe to certain events from Solvi. When one of those events is triggered, an HTTP POST payload is sent to the webhook's configured URL. Currently, project status (event type status_changed) and plant counts published (event type plant_counts_published) are the three available events in Solvi.
Example webhook request payload
{
"project_id": 8993,
"event_type": "status_changed",
"old_status": "not_processed",
"new_status": "processed"
}
The webhook is configured when creating a project or when creating a plant count by specifing the webhook parameter, which should contain the URL of the webhook.
Webhook events
The type of event that occured is determined by the event_type parameter of the request body. Currently, there are two supported event types:
status_changed, indicates that the project's status has changedplant_counts_published, triggered when plant counts have been published for the project
Status changes
The status change messages contains old_status, the status of the project before this change, as well as new_status, the project's updated status.
There are three different project statuses:
not_processed- the project has been created but not yet processed, project outputs will not be availableprocessed- the project has been processed and its outputs are availablefailed- the processing for this project failed, outputs are not available
Plant Counts Published
Notifies that plant counts data has been published for this project. The results can be fetched using the tiles endpoint with plant_counts as type.
Securing webhooks
To ensure that Solvi is the sender of the webhook requests, you can optionally also specify a secret token when registering the webhook, by using the webhook_secret parameter: the secret can be any string of your choosing.
Example signature header
X-Solvi-Signature: sha1=494e5dbdd1afbe4d44091bf86872b5eb4b9133e5
When a secret token has been specified, Solvi will include the HTTP header X-Solvi-Signature, which will contain an HMAC-SHA1 signature of the body.
This follows the same pattern as securing webhooks on GitHub, except for using the header X-Solvi-Signature instead.
Plant Counts
The plant counts API is under development and currently only supports a limited number of use cases:
- Automatic plant counts in scouting projects
- Request plant counts done for you (by Solvi personel) in any type of project
Create Plant Counts
Example request:
curl
-X POST
-H "Authorization: Bearer <user-jwt-token>"
-H 'Content-Type: application/json'
-d '{"model": "base-models/shape"}'
https://solvi.ag/api/v1/projects/<project-id>/plant_counts
Example response:
{
"status": "Created",
"job_id": "88a2af8601cf2b7c4e14979f8df73bca7352ccce"
}
This initiates a plant count for the specified project. A plant count can either be:
- automatic, meaning Plant AI will run unsupervised
- detection done for you by Solvi personel, supervising the detection to ensure high quality.
When using the API, automatic plant counts are currently only supported for scouting projects.
To create a supervised "done for you" plant count, set the request_done_for_you parameter to true.
Creating a plant count is an asynchronous process, after creation the plant count will be processing, and the results can not be accessed until it has completed. This asynchronous process is called a job. To check the job's status, it can either be polled (see below) or a webhook URL can be submitted when starting the plant count; this webhook will be called when the job's status changes. Plant count webhooks only work in automatic mode, "done for you" plant counts can either use polling, or add a project webhook and listen to when the project's plant count is published. See webhooks for more details. Note that "done for you" type plant counts can take up to 24 hours to complete.
The detection is performed using a detection model. By default, two different models are available:
base-models/point- used for basic counts and is applicable to crops like corn or vegetables in early growth-stagesbase-models/shape- includes size estimates and health for individual plants, applicable for pre-harvest crops or trees
In addition, more models might be available on a per-user basis.
HTTP Request
POST https://solvi.ag/api/v1/projects/<project-id>/plant_counts
Parameters
| Parameter | Description | |
|---|---|---|
| model | required | The detection model to be used |
| detect_rows | optional | true if row detection should be performed; only supported in automatic scouting plant counts |
| request_done_for_you | optional | true if detection should be done for you by Solvi personel |
| webhook | optional | URL of the webhook to send status updates to |
Plant Count data
Example request:
curl
-X GET
-H "Authorization: Bearer <user-jwt-token>"
https://solvi.ag/api/v1/projects/<project-id>/plant_counts/<job-id>
Example response:
{
"status": "completed",
"results": [
"https://solvi-staging-production.s3.eu-west-1.amazonaws.com/uploads/..."
]
}
Gets the status of a previously started plant count, or overview information about a processed plant count.
Depending on status of the detection job, different HTTP status codes will be used for the response:
200: completed successfully and the body contains JSON information about the result202: processing is not yet complete404: unknown job id or the job's result has expired and been removed500: an internal error occurred during processing
When processing has completed, the response will contain a results property which lists URLs that can be used to get the detection results. These URLs are valid for at least 24 hours. For scouting projects, a detection results in a JSON file containing a summary of the detection, as well as one GeoJSON file per image in the project.
The results of a detection are kept in storage for one week. After this time, the results are removed from storage and requesting the results will return a 404 code. If a detection is published, the results will be kept in storage.
Note that "done for you" plant counts in legacy projects, processed before 2023-01-01, the returned result may be a single ZIP archive file containing a Shapefile. In newer projects, the returned data will always be uncompressed data in multiple files.
Plant Count Webhook
In addition to polling a plant count for data, you can also register a webhook that will be notified when the plant count has been processed. The webhook URL is specified when you create the plant count. The webhook will sign requests using the project's webhook secret if it is set.
The webhook will currently only receive a single type of event: job_status_changed. This event triggers when the plant detection job's status changes. It contains:
job_idfor the detection that changedproject_idof the project it refers toold_statusthe status of the project before this changenew_statusthe job's updated status.
There are three different statuses:
processing- the plant count is being processedcompleted- the plant count has been processed succesfully and its results are availablefail- the detection failed
Publishing Plant Counts
Example request:
curl
-X POST
-H "Authorization: Bearer <user-jwt-token>"
https://solvi.ag/api/v1/projects/<project-id>/plant_counts/<job-id>/publish
Example response:
{
"status": "Success"
}
After a plant count job has completed succesfully, its results can be published. This will achieve two things:
- The results can be viewed from the Plant Counts tool in Solvi's web app
- Results will not be removed from storage after a week
Listing a Project's Plant Counts
Example request:
curl
-X GET
-H "Authorization: Bearer <user-jwt-token>"
https://solvi.ag/api/v1/projects/<project-id>/plant_counts
Example response:
[
{
"id": "88a2af8601cf2b7c4e14979f8df73bca7352ccce",
"status": "completed",
"created_at": "2019-02-08T09:18:37.655Z"
},
{
"id": "34a2af8601cf2b7c4e14979f8df73bca7352aef43",
"status": "published",
"created_at": "2021-03-12T14:33:31.241Z"
}
]
Lists all created plant counts for a project with their id and status. Status is one of:
processing: the detection has not yet completedcompleted: detection has finished and can be viewedpublished: this detection is the one currently published for the projectfail: detection failed
Deleting a Plant Count
Example request:
curl
-X DELETE
-H "Authorization: Bearer <user-jwt-token>"
https://solvi.ag/api/v1/projects/<project-id>/plant_counts/<job-id>
Example response:
{
"status": "deleted"
}
Removes the results of a plant count. Please note that "done for you" plant counts can't be deleted.
Zonal Statistics
The Zonal Statistics API lets you create zones in a project and calculate various statistics for them: primarily various vegetation indices as well as plant count statistics for projects where plant counts have been published.
Creating and calculating statistics for zones
Example request:
curl -X POST
-H "Authorization: Bearer <user-jwt-token>"
-H "Content-Type: application/json"
-d '{
"indices": ["vari"],
"extra_data": {"plant_counts": true},
"geojson": {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Polygon"
"coordinates": [
[
[
-115.40068410376747,
33.14301501895726
],
[
-115.40068410376747,
33.142561119287876
],
[
-115.4001781509446,
33.142561119287876
],
[
-115.4001781509446,
33.14301501895726
],
[
-115.40068410376747,
33.14301501895726
]
]
],
}
}
]
}
}'
https://solvi.ag/api/v1/projects/<project-id>/zonal_statistics
Example response:
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"vari_p25": -0.12272727489471436,
"vari_p75": -0.051350148394703865,
"vari_mean": -0.08275701161977407,
"vari_median": -0.11009174585342407,
"vari_stdev": 0.05680577057554646,
"vari_min": -0.1606217622756958,
"vari_max": 0.1172839477658
[...]
Sets the zones for a project and calculates the provided statistics, if one of the indices or extra_data parameters are included. The zones should be provided through the geojson parameter: it should contain a GeoJSON FeatureCollection with Polygon features. Other GeoJSON objects or invalid GeoJSON will result in a 400 Bad request response.
In addition to the zone features, the indices parameter lists the vegetation indices that should be calculated for the zones. The indices should be listed as lowercase abbreviations, for example VARI index should be listed as "vari". Note that the available indices vary depending on the project's bands: for example, NDVI is only available if the project's orthophoto has red and near-infrared (NIR) bands. Listing unknown indices or indices that can't be calculated will also result in a 400 Bad request response. If indices are omitted, no indices will be calculated.
For projects with published plant counts, plant count statistics can also be calculated by the optional parameter extra_data, which in this case should be set to {"plant_counts": true}.
The endpoint responds with a GeoJSON FeatureCollection with the same geometries that were provided, but with properties added for the calculated statistics (if any). This response is also saved with the project, and can be viewed on Solvi's web site, and also later fetched with the GET endpoint below.
HTTP Request
POST https://solvi.ag/api/v1/projects/<project-id>/zonal_statistics
Parameters
| Parameter | Description | |
|---|---|---|
| geojson | required | GeoJSON FeatureCollection representing the zone features |
| indices | optional | List in vegetation indices to calculate; index names should be lower case strings |
| extra_data | optional | Extra data layers to calculate statistics for, expressed as an object with string layer names as keys and boolean values to indicate if layer should be used |
Getting Zonal Statistics for a project
Example request:
curl
-X GET
-H "Authorization: Bearer <user-jwt-token>"
https://solvi.ag/api/v1/projects/<project-id>/zonal_statistics
Example response:
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"vari_p25": -0.12272727489471436,
"vari_p75": -0.051350148394703865,
"vari_mean": -0.08275701161977407,
"vari_median": -0.11009174585342407,
"vari_stdev": 0.05680577057554646,
"vari_min": -0.1606217622756958,
"vari_max": 0.1172839477658
[...]
Fetches any saved zonal statistics for a project. The zones are returned as a GeoJSON FeatureCollection with statistics in each feature's properties. If no zones are defined for this project, a 404 Not found is returned.
Transactions
The Transactions API allows you to retrieve the user's credit and upload transaction history, including purchases and usage from project uploads and PlantAI detections.
Get transactions
Example request:
curl
-X GET
-H "Authorization: Bearer <user-jwt-token>"
https://solvi.ag/api/v1/transactions
Example response:
[
{
"datetime": "2023-03-01T09:00:00.000Z",
"unit": "credits",
"amount": 1000,
"type": "purchase"
},
{
"datetime": "2023-03-14T14:20:00.000Z",
"unit": "uploads",
"amount": -1,
"type": "upload",
"project": {
"project_id": 1233,
"name": "Corn Field",
"status": "processed",
...
}
},
{
"datetime": "2023-03-15T10:30:00.000Z",
"unit": "credits",
"amount": -25,
"type": "plantai_detection",
"area_ha": 2.5,
"project": {
"project_id": 1234,
"name": "Wheat Field Survey",
"status": "processed",
...
}
}
]
Retrieves the transaction history for the authenticated user, showing both usage (negative amounts) and purchases (positive amounts). By default, returns transactions from the last year. Transactions can be filtered by date range using the from and to parameters.
Usage transactions include details about the related project and, for PlantAI detections, the analyzed area in hectares.
HTTP Request
GET https://solvi.ag/api/v1/transactions
Parameters
| Parameter | Description | |
|---|---|---|
| from | optional | Start date to filter transactions (ISO 8601 format, e.g., "2023-01-01"); defaults to 1 year ago if neither from nor to is specified |
| to | optional | End date to filter transactions (ISO 8601 format, e.g., "2023-12-31") |
Response
For usage related to a specific project, information about the project is included in the response. See Get Projects for full information about the details included.