API Guide

The Sencrop API allows you to retrieve our users informations and create value on top of it.

In this guide we discuss some design choices in order to help you grasp our API internals quicker.

First contact

You can simply hit the Sencrop API by simply opening our ping endpoint in your browser: https://api.sencrop.com/v1/ping

You probably noticed we are using the https protocol so that informations transiting between your systems and the Sencrop ones remain confidential.

You can also use the command line to ping our API with the help of curl:

curl https://api.sencrop.com/v1/ping
# Answers: {"pong":"pong"}

Get your token

If you want to use our API, please contact our API team: api@sencrop.com

After that, you'll get a token and you'll be able to use our API :-)

Listing your devices

Before retrieving your data you may want to simply list your own Sencrop Devices:

curl 'https://api.sencrop.com/v1/users/1664/devices'  -H "Authorization: Bearer xxxxx"

The result will look like this:

{
  "items": [
    33,
    114711
  ],
  "devices": {
    33: {
      "id": "33",
      "accessPeriods": [{
        "role": "collaborator",
        "startDate": "2017-03-15T23:00:00.000Z"
      }],
      "modelId": 7,
      "userId": 1,
      "organisationId": 1,
      "identification": "SC999999",
      "contents": {
        "name": "Rain sensor 1"
      }
    },
    "114711": {
      "id": 114711,
      "accessPeriods": [{
        "role": "collaborator"
      }],
      "modelId": 7,
      "userId": 1,
      "organisationId": 1,
      "identification": "SC666999",
      "contents": {
        "name": "Rain sensor 2"
      }
    },
  },
  "devicesStatuses": {
    "33": {
      "id": 33,
      "contents": {
        "latitude": 43.7799,
        "longitude": 1.31287
      }
    },
    "114711": {
      "id": "114711",
      "contents": {
        "latitude": 43.7799,
        "longitude": 1.3128
      }
    },
  },
  "models": {
    "7": {
      "id": 7,
      "contents": {
        "name": "Raincrop",
        "externalDiameter": 0.206,
        "conception": "France - Lille",
        "manufacturing": "Europe-France",
        "calibration": "Ok",
        "weight": 3.5
      }
    }
  }
}

The items property tells you the collection of devices you can access to. While the devices hash allows your to pickup the devices details.

You may ask why using that format. It allows our payload to avoid repating the same informations several times while not requiring you to use a specific JSON loader. It also allows us to significantly reduce our app memory footprint by easing hash merges accross our various states.

⚠️Warning

The API only accept/gives dates in ISODate format (e.g: "2012-07-14T01:00:00+01:00"). It goes for every date delivered by the API in the responses, as well as all the input dates in the queries.

Reading raw device data

Your devices regularly send meteoroligical data to our servers. You can get their direct output by simply requesting the following endpoint:

curl "https://api.sencrop.com/v1/users/1664/devices/33/data/raw?size=100&beforeDate=2017-10-10T00:00:00Z&measures=RELATIVE_HUMIDITY,TEMPERATURE"  -H "Authorization: Bearer xxxxx"

The result will look like this:

[
  {
    "date": "2017-10-09T23:54:07.000Z",
    "type": "RELATIVE_HUMIDITY",
    "value": 65.2,
    "discarded": false
  },
  {
    "date": "2017-10-09T23:54:07.000Z",
    "type": "TEMPERATURE",
    "value": 15.100000000000001,
    "discarded": false
  },
  {
    "date": "2017-10-09T23:39:07.000Z",
    "type": "RELATIVE_HUMIDITY",
    "value": 66.7,
    "discarded": false
  },
  {
    "date": "2017-10-09T23:39:07.000Z",
    "type": "TEMPERATURE",
    "value": 14.8,
    "discarded": false
  },
  {
    "date": "2017-10-09T23:24:06.000Z",
    "type": "TEMPERATURE",
    "value": 14.9,
    "discarded": false
  },
  {
    "date": "2017-10-09T23:24:06.000Z",
    "type": "RELATIVE_HUMIDITY",
    "value": 65.7,
    "discarded": false
  },
  {
    "date": "2017-10-09T23:09:07.000Z",
    "type": "RELATIVE_HUMIDITY",
    "value": 67.4,
    "discarded": false
  },
  {
    "date": "2017-10-09T23:09:07.000Z",
    "type": "TEMPERATURE",
    "value": 14.5,
    "discarded": false
  },
  {
    "date": "2017-10-09T22:54:06.000Z",
    "type": "TEMPERATURE",
    "value": 15,
    "discarded": false
  },
  {
    "date": "2017-10-09T22:54:06.000Z",
    "type": "RELATIVE_HUMIDITY",
    "value": 64.2,
    "discarded": false
  }
]

The date, type and value fields are self explanatory. The discarded field is a bit more special, he means that our algorithm detected that the measure was wrong. It can be due to many different issues (hardware failure, bad installation, network failures etc...).

Note that the size query parameter is mandatory and limited to a few values. Check out the API reference for more information.

Reading device data

The raw data is cool but you may want a bit more insight on the data you retrieve.

For this purpose we brought to you two kind of endpoints.

# Get hourly aggregated data for two days
curl "https://api.sencrop.com/v1/users/1664/devices/33/data/hourly?beforeDate=2017-10-07T07:34:32.000Z&days=7&measures=WIND_DIRECTION,WIND_SPEED"  -H "Authorization: Bearer xxxxx"
# Get daily aggregated data for two days
curl "https://api.sencrop.com/v1/users/1664/devices/33/data/daily?beforeDate=2017-10-07T07:34:32.000Z&days=90&measures=WIND_DIRECTION,WIND_SPEED"  -H "Authorization: Bearer xxxxx"

The result will look like this:

{
  "item": 33,
  "devices": {
    "33": {
      "id": 33,
      "accessPeriods": [{
        "role": "owner",
        "endDate": "2038-01-19T03:14:07.000Z"
      }],
      "modelId": 8,
      "userId": 1664,
      "identification": "WC666999",
      "contents": {
        "name": "My Windcrop"
      }
    }
  },
  "measures": {
    "interval": "1h",
    "data": [{
      "key": 1507186800000,
      "WIND_SPEED": {
        "value": 15.5
      },
      "WIND_DIRECTION": {
        "value": 262
      },
      "docCount": 4
    },
    {
      "key": 1507190400000,
      "WIND_SPEED": {
        "value": 19.416666666666668
      },
      "WIND_DIRECTION": {
        "value": 281
      },
      "docCount": 12
    },
    { '...': '...' },
    {
      "key": 1507359600000,
      "WIND_SPEED": {
        "value": 15.5
      },
      "WIND_DIRECTION": {
        "value": 222
      },
      "docCount": 6
    }]
  }
}

Each measures are aggregated depending on its nature. The wind, temperature, relative humidity are simple averages while the rain is a sum instead. The wind direction is a vector sum.

Note that the days query parameter is mandatory and limited to a few values. Check out the API reference for more information.

Adaptive scale data

Here, what matters for you is to get data for a given period. In this case, we automatically choose the right scale in order for you to get best insights with no performance hint.

# Get device statistics for two days
curl "https://api.sencrop.com/v1/users/1664/devices/33/statistics?startDate=2017-01-01T00:00:00.000Z&endDate=2017-02-01T00:00:00.000Z&measures=WIND_DIRECTION,WIND_SPEED&patched=false"  -H "Authorization: Bearer xxxxx"
Note the patched parameter that allows your to retrieve only not patched data (ie only real device data). Setting it to true or not specifying it will not return extrapolated leaking data refills.

The result will look like this:

{
  "item": 33,
  "devices": {
    "33": {
      "id": 33,
      "accessPeriods": [{
        "role": "owner",
        "endDate": "2038-01-19T03:14:07.000Z"
      }],
      "modelId": 8,
      "userId": 1664,
      "identification": "WC666999",
      "contents": {
        "name": "My Windcrop"
      }
    }
  },
  "measures": {
    "interval": "1d",
    "data": [{
      "key": 1507186800000,
      "WIND_SPEED": {
        "value": 15.5
      },
      "WIND_DIRECTION": {
        "value": 262
      },
      "docCount": 4
    },
    {
      "key": 1507190400000,
      "WIND_SPEED": {
        "value": 19.416666666666668
      },
      "WIND_DIRECTION": {
        "value": 281
      },
      "docCount": 12
    },
    { '...': '...' },
    {
      "key": 1507359600000,
      "WIND_SPEED": {
        "value": 15.5
      },
      "WIND_DIRECTION": {
        "value": 222
      },
      "docCount": 6
    }]
  }
}

Geobased data

The geobased data provides your insights around a given position instead of a single device. Indeed, during its usage, a device may be moved or shutdown. Also, some devices can provide you rain measures while others will instead give you rain data.

For that purpose, you may prefer get available data for a given place in your Sencrop network.

# Get geobased statistics
curl "https://api.sencrop.com/v1/users/1664/statistics?startDate=2017-01-01T00:00:00.000Z&endDate=2017-02-01T00:00:00.000Z&latitude=37.234894&longitude=-115.81082&measures=WIND_SPEED,RAIN_FALL"  -H "Authorization: Bearer xxxxx"
# Get geobased fixed scale data
curl "https://api.sencrop.com/v1/users/1664/data/hourly?beforeDate=2017-01-01T00:00:00.000Z&days=3&latitude=37.234894&longitude=-115.81082&measures=WIND_SPEED,RAIN_FALL"  -H "Authorization: Bearer xxxxx"

The result will look like this:

{
  "measures": {
    "interval": "1d",
    "data": [{
      "key": 1507186800000,
      "WIND_SPEED": {
        "precision": 80,
        "value": 15.5
      },
      "RAIN_FALL": {
        "precision": 99,
        "value": 3
      },
      "docCount": 4
    },
    {
      "key": 1507190400000,
      "WIND_SPEED": {
        "precision": 80,
        "value": 19.416666666666668
      },
      "RAIN_FALL": {
        "precision": 99,
        "value": 0.5
      },
      "docCount": 12
    },
    { '...': '...' },
    {
      "key": 1507359600000,
      "WIND_SPEED": {
        "precision": 80,
        "value": 15.5
      },
      "RAIN_FALL": {
        "precision": 99,
        "value": 0
      },
      "docCount": 6
    }]
  }
}

You can notice a new property called precision that tells you how precise is the result. Indeed, the devices on which are based the data may not be at the exact position you provided but instead in a ten kilometers ring around it.

This precision field goes from 0 to 100 and is based on statistical studies we made upfront. That precision may vary depending on the kind of measure. It may also change since we will probably reshape our statistical algorithms for more sharp results.

Note that the days query parameter is mandatory and limited to a few values. Check out the API reference for more information.

Time buckets

Various measures are aggregated into time buckets. The buckets are computed according to the user's timezone per default. Here are the various intervals you may encounter:

  • 15m: the bucket key will point the start of the buckets with the first one beeing the first 15 minutes part of fours containing the given start date,
  • 30m: the bucket key will point the start of the buckets with the first one beeing the first 30 minutes part of fours containing the given start date,
  • hour: the bucket key will point the start of days hours inside the given time interval,
  • week: the bucket key will point the start of weeks between the provided start and end dates (starting on sunday),
  • month: the bucket key will point the start of months for each month between the provided start and end dates,
  • year: the bucket key will point the start of years for each year between the provided start and end dates.

You have to be aware of the fact that time buckets are aligned on days/months/years boundaries in the user's timezone. So, if you need to get the current year statistics for a french user, you need to set startDate/endDate values in the Europe/Paris timezone.

For instance, with JavaScript and using the MomentJS library you would end up with some code looking like the following:

import API from 'sencrop-js-api-client';
import moment from 'moment-timezone';

API.getUserDeviceStatistics({
  userId: 86,
  deviceId: 33,
  authorization: 'Bearer yolo-token',
  startDate: moment().tz('Europe/Paris').startOf('year').toISOString(),
  endDate: moment().tz('Europe/Paris').endOf('year').toISOString(),
  measures: ['TEMPERATURE'],
}, {
  // Here goes any Axios request configuration override
  // See: https://github.com/mzabriskie/axios#request-config
  timeout: 40000,
})
.then({ data } => {
  console.log(data);
});

The result will look like this:

{
  "item": 33,
  "devices": {
    "33": {
      "id": 33,
      "accessPeriods": [
        {
          "id": 10279,
          "deviceId": 33,
          "userId": 86,
          "type": "owner",
          "startDate": "2018-02-14T15:15:00.000Z"
        }
      ],
      "modelId": 7,
      "userId": 1217,
      "organisationId": 1061,
      "previousDevicesIds": [
        1308
      ],
      "identification": "RC001664",
      "serial": "ABBACA",
      "contents": {
        "name": ""
      }
    }
  },
  "measures": {
    "interval": "month",
    "data": [
      {
        "key": 1512082800000,
        "TEMPERATURE": {},
        "docCount": 0
      },
      {
        "key": 1514761200000,
        "TEMPERATURE": {},
        "docCount": 0
      },
      {
        "key": 1517439600000,
        "TEMPERATURE": {
          "value": 0.7823753330795595
        },
        "docCount": 7881
      },
      {
        "key": 1519858800000,
        "TEMPERATURE": {
          "value": 5.929323899371062
        },
        "docCount": 8904
      },
      {
        "key": 1522533600000,
        "TEMPERATURE": {
          "value": 12.416400462962963
        },
        "docCount": 8640
      },
      {
        "key": 1525125600000,
        "TEMPERATURE": {
          "value": 14.189615931721207
        },
        "docCount": 4218
      },
      {
        "key": 1527804000000,
        "TEMPERATURE": {},
        "docCount": 0
      },
      {
        "key": 1530396000000,
        "TEMPERATURE": {},
        "docCount": 0
      },
      {
        "key": 1533074400000,
        "TEMPERATURE": {},
        "docCount": 0
      },
      {
        "key": 1535752800000,
        "TEMPERATURE": {},
        "docCount": 0
      },
      {
        "key": 1538344800000,
        "TEMPERATURE": {},
        "docCount": 0
      },
      {
        "key": 1541026800000,
        "TEMPERATURE": {},
        "docCount": 0
      },
      {
        "key": 1543618800000,
        "TEMPERATURE": {},
        "docCount": 0
      }
    ]
  }
}

Beware that not aligning on the user's timezone may lead to incomplete buckets at boundaries of the returned data. Also, if you retrieve a bucket for the current month, you only have a partial bucket for that month since it has not ended yet. In the above result you can see that the may month is not terminated yet so the bucket has a limited number of measures (see the docCount property) and the following buckets are empty since they represent future months.

Units

The API returns values in fixed units. We use the international system of units where applicable so that you can convert it on you side with ease. Here are the various units we use:

  • RAIN_FALL: pluviometry measured in millimeters (mm)
  • TEMPERATURE: temperature measured in degree Celsius (°C)
  • RELATIVE_HUMIDITY: relative humidity measured in percentage (%),
  • WIND_SPEED/WIND_GUST: wind measured in kilometers per hour (km·h−1),
  • WIND_DIRECTION: wind direction angle with the North in angular degrees (°) within a 0 to 360 range (360 excluded), for instance, a value of 0 means a wind coming from the North and directed to the South,
  • WET_TEMPERATURE: wet bulb temperature in degree Celsius (°C),
  • LEAF_WETNESS: amount of wetness time in minutes.
  • LEAF_SENSOR_CONDUCTIVITY: conductivity of the leaf sensor in millivolts (mV).

Please note that the old counter intuitive name (WIND_MAX, WIND_MEAN, RH_AIR_H1, TEMP_AIR_H1, RAIN_TIC) are still supported but deprecated. We will probably remove those measures in a near future.

Limits

Since we are in an early alpha publication of this API, we do not yet provide feedback on rate limitations and how to prevent it. You will not reach the limit until you make a hundred calls per minutes which should be sufficient.

Contact us if you think you need a more intensive access to the data in the meanwhile.

What's next ?

So you read it all? Impressive! You are now in the best conditions to use our API! You can try it interactively with our API reference or directly dive into code with our JavaScript SDK. Check out our open-source tools!