Language

An Offer is one side of a data exchange agreement between the individual HAT owner and any other entity requesting data, the other side being the Data Debit. For the DEX, an Offer simply describes what data is requested and for how long. Applications do not have rights to directly create a Data Debit on a HAT, but instead ask DEX to do so on their behalf when the user claims the Offer. It is important to note, however, that even DEX is not able to grant itself access to the data, but only request for data from a HAT by registering a Data Debit: the Data Debit is not active until the owner of the HAT explicitly enables the Data Debit.

Offer details

ParameterDescription
offerIdAlphanumeric Offer ID - must be unique
titleTitle of the Offer
descriptionTextual description of the Offer
startsThe (UNIX timestamp) Date and Time of when the Offer becomes available for claiming
expiresThe Date and Time of when the Offer expires
collectionDurationDuration of data collection (in milliseconds) from the moment a HAT claims the Offer
dataConditions(Optional) conditions that need to be satisfied, i.e. contain data in the Data Bundle format
requiredDataThe technical definition of the data required in the Data Bundle format
requiredMinUserThe minimum number of users required to release access to all claiming HATs, confirming exchange
requiredMaxUserThe maximum number of users allowed to claim the offer, e.g. to control the maximum campaign cost
statusValid values include: “approved” and “rejected”

The format of the Data Bundle is covered in the guide on the power of HAT data bundling

Register an Offer

To be able to register an Offer directly with DEX, your account will need to have MarketPlace role granted. Normally you would use the graphical interface provided by e.g. the DataBuyer service to create your Offer.

Offer is created by sending all the details as described above to DEX, for example:

curl --request POST \
  --url https://dex.hubofallthings.com/api/v2/offer \
  --header 'content-type: application/json' \
  --header 'x-auth-token: ACCESS_TOKEN' \
  --data '{"offerId":"789d1dff-ce8d-4c6e-a201-e89ee9d7f5cf","title":"Location fetching offer","description":"Location fetch test","starts":1507503600000,"expires":1510099200000,"collectionDuration":86400000,"requiredData":{"name":"789d1dff-ce8d-4c6e-a201-e89ee9d7f5cf","bundle":{"iphone/locations":{"endpoints":[{"endpoint":"iphone/locations","mapping":{"accuracy":"accuracy","latitude":"latitude","longitude":"longitude","timestamp":"timestamp","lastUpdated":"lastUpdated"},"filters":[]}]}}},"requiredMinUser":1,"requiredMaxUser":10,"status":"approved"}'
var data = JSON.stringify({
  "offerId": "789d1dff-ce8d-4c6e-a201-e89ee9d7f5cf",
  "title": "Location fetching offer",
  "description": "Location fetch test",
  "starts": 1507503600000,
  "expires": 1510099200000,
  "collectionDuration": 86400000,
  "requiredData": {
    "name": "789d1dff-ce8d-4c6e-a201-e89ee9d7f5cf",
    "bundle": {
      "iphone/locations": {
        "endpoints": [
          {
            "endpoint": "iphone/locations",
            "mapping": {
              "accuracy": "accuracy",
              "latitude": "latitude",
              "longitude": "longitude",
              "timestamp": "timestamp",
              "lastUpdated": "lastUpdated"
            },
            "filters": []
          }
        ]
      }
    }
  },
  "requiredMinUser": 1,
  "requiredMaxUser": 10,
  "status": "approved"
});

var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

xhr.addEventListener("readystatechange", function () {
  if (this.readyState === this.DONE) {
    console.log(this.responseText);
  }
});

xhr.open("POST", "https://dex.hubofallthings.com/api/v2/offer");
xhr.setRequestHeader("x-auth-token", "ACCESS_TOKEN");
xhr.setRequestHeader("content-type", "application/json");

xhr.send(data);
<?php

$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => "https://dex.hubofallthings.com/api/v2/offer",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => "",
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 30,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_POSTFIELDS => "{\"offerId\":\"789d1dff-ce8d-4c6e-a201-e89ee9d7f5cf\",\"title\":\"Location fetching offer\",\"description\":\"Location fetch test\",\"starts\":1507503600000,\"expires\":1510099200000,\"collectionDuration\":86400000,\"requiredData\":{\"name\":\"789d1dff-ce8d-4c6e-a201-e89ee9d7f5cf\",\"bundle\":{\"iphone/locations\":{\"endpoints\":[{\"endpoint\":\"iphone/locations\",\"mapping\":{\"accuracy\":\"accuracy\",\"latitude\":\"latitude\",\"longitude\":\"longitude\",\"timestamp\":\"timestamp\",\"lastUpdated\":\"lastUpdated\"},\"filters\":[]}]}}},\"requiredMinUser\":1,\"requiredMaxUser\":10,\"status\":\"approved\"}",
  CURLOPT_HTTPHEADER => array(
    "content-type: application/json",
    "x-auth-token: ACCESS_TOKEN"
  ),
));

$response = curl_exec($curl);
$err = curl_error($curl);

curl_close($curl);

if ($err) {
  echo "cURL Error #:" . $err;
} else {
  echo $response;
}
import requests

url = "https://dex.hubofallthings.com/api/v2/offer"

payload = "{\"offerId\":\"789d1dff-ce8d-4c6e-a201-e89ee9d7f5cf\",\"title\":\"Location fetching offer\",\"description\":\"Location fetch test\",\"starts\":1507503600000,\"expires\":1510099200000,\"collectionDuration\":86400000,\"requiredData\":{\"name\":\"789d1dff-ce8d-4c6e-a201-e89ee9d7f5cf\",\"bundle\":{\"iphone/locations\":{\"endpoints\":[{\"endpoint\":\"iphone/locations\",\"mapping\":{\"accuracy\":\"accuracy\",\"latitude\":\"latitude\",\"longitude\":\"longitude\",\"timestamp\":\"timestamp\",\"lastUpdated\":\"lastUpdated\"},\"filters\":[]}]}}},\"requiredMinUser\":1,\"requiredMaxUser\":10,\"status\":\"approved\"}"
headers = {
    'x-auth-token': "ACCESS_TOKEN",
    'content-type': "application/json"
    }

response = requests.request("POST", url, data=payload, headers=headers)

print(response.text)
require 'uri'
require 'net/http'

url = URI("https://dex.hubofallthings.com/api/v2/offer")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE

request = Net::HTTP::Post.new(url)
request["x-auth-token"] = 'ACCESS_TOKEN'
request["content-type"] = 'application/json'
request.body = "{\"offerId\":\"789d1dff-ce8d-4c6e-a201-e89ee9d7f5cf\",\"title\":\"Location fetching offer\",\"description\":\"Location fetch test\",\"starts\":1507503600000,\"expires\":1510099200000,\"collectionDuration\":86400000,\"requiredData\":{\"name\":\"789d1dff-ce8d-4c6e-a201-e89ee9d7f5cf\",\"bundle\":{\"iphone/locations\":{\"endpoints\":[{\"endpoint\":\"iphone/locations\",\"mapping\":{\"accuracy\":\"accuracy\",\"latitude\":\"latitude\",\"longitude\":\"longitude\",\"timestamp\":\"timestamp\",\"lastUpdated\":\"lastUpdated\"},\"filters\":[]}]}}},\"requiredMinUser\":1,\"requiredMaxUser\":10,\"status\":\"approved\"}"

response = http.request(request)
puts response.read_body

Only after the Offer has been registered (DEX responds with status 201 CREATED), it can be claimed by users.

Claim an Offer

Claiming an offer through DEX, or rather registering a claim, can be done by either the HAT, or by the owner of the Offer (i.e. the account that has registered it):

  • Claim by the HAT is done by sending a request with a fresh and appropriate Access Token (one that has been issued for the use with DEX) to GET /api/v2/offer/:id/claim, where :id is the Offer ID that is being claimed.
  • Claim by the Offer owner is done by sending a request with an Access Token issued to the owner account, to the endpoint PUT /api/v2/offer/:id/registerClaim?hat=address, where :id is the Offer ID and address is the HAT address.

An example of registering an offer claim is below:

curl --request PUT \
  --url 'https://dex.hubofallthings.com/api/v2/offer/OFFER_ID/registerClaim?hat=test.hubat.net' \
  --header 'x-auth-token: ACCESS_TOKEN'
var data = null;

var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

xhr.addEventListener("readystatechange", function () {
  if (this.readyState === this.DONE) {
    console.log(this.responseText);
  }
});

xhr.open("PUT", "https://dex.hubofallthings.com/api/v2/offer/OFFER_ID/registerClaim?hat=test.hubat.net");
xhr.setRequestHeader("x-auth-token", "ACCESS_TOKEN");

xhr.send(data);
<?php

$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => "https://dex.hubofallthings.com/api/v2/offer/OFFER_ID/registerClaim?hat=test.hubat.net",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => "",
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 30,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => "PUT",
  CURLOPT_HTTPHEADER => array(
    "x-auth-token: ACCESS_TOKEN"
  ),
));

$response = curl_exec($curl);
$err = curl_error($curl);

curl_close($curl);

if ($err) {
  echo "cURL Error #:" . $err;
} else {
  echo $response;
}
import requests

url = "https://dex.hubofallthings.com/api/v2/offer/OFFER_ID/registerClaim"

headers = {'x-auth-token': 'ACCESS_TOKEN'}

response = requests.request("PUT", url, headers=headers)

print(response.text)
require 'uri'
require 'net/http'

url = URI("https://dex.hubofallthings.com/api/v2/offer/OFFER_ID/registerClaim?hat=test.hubat.net")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE

request = Net::HTTP::Put.new(url)
request["x-auth-token"] = 'ACCESS_TOKEN'

response = http.request(request)
puts response.read_body

To which, if the claim is processed successfully, DEX responds with the details of the claim:


HTTP/1.1 200 OK
Content-Type: application/json


{
  "offerId": "OFFER_ID",
  "user": {
    "address": "test.hubat.net",
    "publicKey": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmbpD4OytlMcWRuGHgfy8\nq9I9GT46Sh3FwuZQkicCMqMWaV9ukwo6mW+e4UciNh5acXf2qEnQdbRHCxNVe90G\njTzVCPcFvigE/Tn9icNzISludwA4/uiAbaFwJg9dvXVphQuxZdq5dEIDAvPcqwUk\nJBCX+CLBP1a0CWiB/ACbaVwYm2bZApZe52BLiw7ejvM6UvQoOjOYiRiVGJKdgUgm\nWIruC+bMcbhbNpf/11M0+YCi/d51OSwup/nyEweoa6deTrMdFyMosnlcknEaWx9t\nNPU3Agub9SNZVKkXTYgRXzoQu8k/BC331uKII1pi6atqLjQGkY4rY6fXJ3Db3NYI\n9QIDAQAB\n-----END PUBLIC KEY-----\n"
  },
  "relationship": "claim",
  "confirmed": false,
  "dataDebitId": "OFFER_ID",
  "dateCreated": 1507651701435
}

List offer claims

The final step in the Offer Claim process after the claim and the user accepting the Data Debit on their side, is getting credentials issued by DEX for collecting data from confirmed HATs. The call needs to be authenticated with the Offer owner’s Access Token as well:

curl --request GET \
  --url https://dex.hubofallthings.com/api/v2/offer/OFFER_ID/claims \
  --header 'x-auth-token: ACCESS_TOKEN'
var data = null;

var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

xhr.addEventListener("readystatechange", function () {
  if (this.readyState === this.DONE) {
    console.log(this.responseText);
  }
});

xhr.open("GET", "https://dex.hubofallthings.com/api/v2/offer/OFFER_ID/claims");
xhr.setRequestHeader("x-auth-token", "ACCESS_TOKEN");

xhr.send(data);
<?php

$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => "https://dex.hubofallthings.com/api/v2/offer/OFFER_ID/claims",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => "",
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 30,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_HTTPHEADER => array(
    "x-auth-token: ACCESS_TOKEN"
  ),
));

$response = curl_exec($curl);
$err = curl_error($curl);

curl_close($curl);

if ($err) {
  echo "cURL Error #:" . $err;
} else {
  echo $response;
}
import requests

url = "https://dex.hubofallthings.com/api/v2/offer/OFFER_ID/claims"

headers = {'x-auth-token': 'ACCESS_TOKEN'}

response = requests.request("GET", url, headers=headers)

print(response.text)
require 'uri'
require 'net/http'

url = URI("https://dex.hubofallthings.com/api/v2/offer/OFFER_ID/claims")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE

request = Net::HTTP::Get.new(url)
request["x-auth-token"] = 'ACCESS_TOKEN'

response = http.request(request)
puts response.read_body

The DEX response includes access credentials and the list of all confirmed claims:


HTTP/1.1 200 OK
Content-Type: application/json


{
  "credentials": {
    "offerId": "OFFER_ID",
    "login": "location-test-offer",
    "passwordPlain": "PASSWORD",
    "passwordHash": "$2a$10$nCwNyaQYYZ8wvJN5GCXf/OBVHhFl0fy6VpOL.FKtxXx17FZJhNS2O",
    "dateCreated": 1506608497820
  },
  "claims": [
    {
      "offerId": "OFFER_ID",
      "user": {
        "address": "test.hubat.net",
        "publicKey": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmbpD4OytlMcWRuGHgfy8\nq9I9GT46Sh3FwuZQkicCMqMWaV9ukwo6mW+e4UciNh5acXf2qEnQdbRHCxNVe90G\njTzVCPcFvigE/Tn9icNzISludwA4/uiAbaFwJg9dvXVphQuxZdq5dEIDAvPcqwUk\nJBCX+CLBP1a0CWiB/ACbaVwYm2bZApZe52BLiw7ejvM6UvQoOjOYiRiVGJKdgUgm\nWIruC+bMcbhbNpf/11M0+YCi/d51OSwup/nyEweoa6deTrMdFyMosnlcknEaWx9t\nNPU3Agub9SNZVKkXTYgRXzoQu8k/BC331uKII1pi6atqLjQGkY4rY6fXJ3Db3NYI\n9QIDAQAB\n-----END PUBLIC KEY-----\n"
      },
      "relationship": "claim",
      "confirmed": true,
      "dataDebitId": "OFFER_ID",
      "dateCreated": 1506608497636
    }
  ]
}