Authentication

There are two ways you can authenticate on the mediarithmics API:

  • Login / Password authentication generates a temporary access token. Best used for web applications

  • Long-term access tokens for any server to server integration

A third way has been created specifically for mobile apps that are collecting events: Signature authentication. It is only available for a few specific endpoints.

Temporary access tokens

Temporary access tokens are the way to go if you plan to make client-to-server calls, as using a long-term access token in your client application would not be safe.. A temporary token with an expiration delay of 1 hour is generated server-side, then passed on to the client application from which the calls will be made.

To generate your temporary access token, you need to call the /v1/access_tokens endpoint, using your mediarithmics username and password (same as the ones you use to log in).

curl --location --request POST 'https://api.mediarithmics.com/v1/authentication/access_tokens' \
--header 'Content-Type: application/json' \
--data-raw '{
    "email": "<USERNAME>",
    "password": "<PASSWORD>"
}'

The response will give you an access token:

{
    "status": "ok",
    "data": {
        "access_token": "<ACCESS_TOKEN>",
        "expires_in": 3600,
        "refresh_token": null
    }
}

You then need to use the provided access token in the Authorization header of all your requests.

Long-term access tokens

Long-term access tokens are the way to go if you plan to make server-to-server calls.

We recommend creating "technical" users to bear long-term access tokens, in order to avoid cancelling your token along with deleting a regular user.

A long-term access token works as an access token but it has a longer expiration delay (usually from 12 to 24 months).

A user can have multiple long-term tokens, which can be useful to manage tokens renewal: just create a new access token before the old one expires and update your app.

There are 2 ways you can create a long-term access token:

  • In the Navigator application

  • Using the API

Once generated you can use the access token in the Authorization header of all your requests.

List and create in the UI

  1. Navigate to settings

  2. Then My Account > API Tokens

  3. Click on the "New API Token" to generate a long-term access token

Generating using the API

POST https://api.mediarithmics.com/v1/users/:userId/api_tokens

Generates an API token for a mediarithmics user

Path Parameters

Headers

Request Body

{
    "status":"ok",
    "data":
    {
        "id":"1438281",
        "name":"Postman",
        "creation_date":1602515387862,
        "expiration_date":1665587387862,
        "value":"token value"
    }
}

Please note that you need an access token to generate long-term access tokens using the API. You can generate a temporary access token to authenticate if you don't already have a long-term token

List API tokens for a user

GET https://api.mediarithmics.com/v1/users/:userId/api_tokens

Path Parameters

Headers

{
    "status":"ok",
    "data":[
        {
            "id":"1483281",
            "name":"Postman",
            "creation_date":1602515387862,
            "expiration_date":1665587387862,
            "value":"api:gkqKkuXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXGcdKn7Vv9G"
        }],
    "count":1,
    "total":1,
    "first_result":0,
    "max_result":50,
    "max_results":50
}

Please note that you need an access token to list long-term access tokens using the API. You can generate a temporary access token to authenticate if you don't already have a long-term token. You won't be able to see the complete value of the tokens using this API.

Signature authentication

The Signature authentication scheme is used in an environment where the calling part operates in a public network and the communication with the mediarithmics platform could be hacked by a man in the middle. It is the preferred choice for mobile app tracking.

Signature authentication is limited to 2 actions only:

  • Posting activities using the POST /v1/datamarts/${datamart_id}/user_activities endpoint

  • Getting user segments by user account ID, email hash or user agent ID (not user point ID) using GET /v1/datamarts/:datamartId/user_points/compartmentId=:compartmentId,user_account_id=:userAccountId/user_segments

    , or GET /v1/datamarts/:datamartId/user_points/email_hash=:emailHash/user_segments or GET /v1/datamarts/:datamartId/user_points/user_agent_id=:userAgentId/user_segments endpoints

The authentication of the calling party (the mobile app) is done through a signature (Message Authentication Code) using a hashing algorithm (HMAC-SHA256).

Signature authentication is linked to a mediarithmics user. As for the API token approach, we recommend you create a technical user on mediarithmics to bear this authentication scheme.

Generate a secret key

POST https://api.mediarithmics.com/v1/users/:userId/message_authentication_keys

Request Body

Response body format:

Usage

For each API call, a digest of the message should be calculated. Then, the 3 following headers should be contained in the API call:

  • X-Mics-Mac: the digest of the input data (Message Authentication Code)

  • X-Mics-Key-Id: the key_id of the secret key (provided by you when creating a new secret key)

  • X-Mics-Ts: the timestamp used in the digest (in ms)

Digest calculation

We recommend you find a library that generates digests for your platform. This library will need 2 input in order to work:

  • A key. You should use the secret_key provided by the platform.

  • A message to sign. you should generate the message from the info contained in the API call payload (see below).

The digest should be presented as a Base64 string.

Message generation

The message to sign should be generated from the following values, in this order, separated by a \n (e.g. ${uri}\n${key_id}\n${timestamp}\n${request_body}):

  • The uri of the API method used

  • The key_id value (provided by you when creating the secret key)

  • A timestamp (ms) generated at the time of the call

  • The request body as a string

Examples

Signing the message

These values:

  • uri: /v1/datamarts/854/user_activities

  • key id: my_key_identifier

  • timestamp: 1499103950000

  • request body: {\"hello\":\"world\"}

Will translate to the following message:

/v1/datamarts/854/user_activities
my_key_identifier
1499103950000
{"hello":"world"}

Therefore, the calculation of the HMAC-SHA256 of this message, with the secret key 846cee8e-5558-4ca0-b723-095aa043c6ee, base64 encoded, will be rwhKdaWtw5Hx3zjcrZDv7eO4fyNbBkIfsh2PjI+BiRE=.

Posting activities for iOS using Swift

import Foundation
import CryptoKit

func signPayload(uri:String, secretKeyId:String, timestamp:Int64, payload:String, secretKeyValue:String) -> String {
    let message = Data("\(uri)\n\(secretKeyId)\n\(String(timestamp))\n\(payload)".utf8)
    let symmetricKey = SymmetricKey(data: Data(secretKeyValue.utf8))
    let hmac = HMAC<SHA256>.authenticationCode(for: message, using: symmetricKey)
    let base64hmac = Data(hmac).base64EncodedString()
    return base64hmac
}

func sendUserEvent(
    userAccountId:String?,
    mobileVendorId:String?,
    mobileAdId:String?,
    eventName:String,
    category:String?,
    subCategory:String?) {
        
        // Constant parameters that could be defined in a configuration file
        let DATAMART_ID="XXX"           // the id of the database
        let APPLICATION_ID="XXX"        // the id of the mobile application
        let COMPARTMENT_ID="XXX"        // the id used to define the nature of the user account id
        let VENDOR_ID_REGISTRY_ID="XXX" // the id used to define the nature of the mobile vendor id
        let SECRET_KEY_ID="XXX" // the id of the secret key used to sign the message
        let SECRET_KEY_VALUE="ZZZZZZZZZZZZZZZZZZZZZZZZZZ" // the secret key value
        
        // The URL entry is static
        let uri = "/v1/datamarts/"+DATAMART_ID+"/user_activities"
        let url = "https://api.mediarithmics.com" + uri
        let timestamp = Int64(Date().timeIntervalSince1970 * 1000)
        
        // Prepare the json payload
        var identifiers:[[String:Any]]  = []
        if let getUserAccountId = userAccountId  { identifiers.append(["$type":"USER_ACCOUNT", "$compartment_id":COMPARTMENT_ID, "$user_account_id": getUserAccountId ]) }
        if let getMobileVendorId = mobileVendorId  { identifiers.append(["$type":"USER_AGENT", "$user_agent_id": ("mov:ios:" + VENDOR_ID_REGISTRY_ID + ":" + getMobileVendorId) ]) }
        if let getMobileAdId = mobileAdId  { identifiers.append(["$type":"USER_AGENT", "$user_agent_id": ("mob:ios:raw:" + getMobileAdId) ]) }
        
        let jsonPayload: [String: Any] = [
            "$type": "APP_VISIT",
            "$app_id": APPLICATION_ID,
            "$session_status": "IN_SESSION",
            "$ts" : timestamp,
            "$user_identifiers" : identifiers,
            "$events":[[
                "$event_name" : eventName,
                "$ts" : timestamp,
                "$properties" : [
                    "category" : category ?? nil,
                    "sub_category" : subCategory ?? nil ]]]]
        
        // Create a URLRequest with the specified URL
        var request = URLRequest(url: URL(string:url)!)
        
        do {
            // Set the HTTP method
            request.httpMethod = "POST"
            
            // Convert the dictionary to JSON data
            let jsonData = try JSONSerialization.data(withJSONObject: jsonPayload, options: [])
            request.httpBody = jsonData
            request.setValue("application/json", forHTTPHeaderField: "Content-Type")
            
            // Build the HMAC signature
            let base64hmac = signPayload(uri:uri, secretKeyId: SECRET_KEY_ID, timestamp: timestamp, payload: String(data:jsonData, encoding: .utf8)!, secretKeyValue: SECRET_KEY_VALUE)
            
            // Set the specific headers
            request.setValue(SECRET_KEY_ID, forHTTPHeaderField: "X-Mics-Key-Id")
            request.setValue(String(timestamp), forHTTPHeaderField: "X-Mics-Ts")
            request.setValue(base64hmac, forHTTPHeaderField: "X-Mics-Mac")
            
        } catch {
            print("Error while building the request data: \(error)")
            return
        }
        
        // Create a URLSession task to perform the API call
        let task = URLSession.shared.dataTask(with: request) { data, response, error in
            // Check for errors
            if let error = error {
                print("Error: \(error)")
                return
            }
            
            // Check for a valid HTTP response
            guard let httpResponse = response as? HTTPURLResponse else {
                print("Invalid HTTP response")
                return
            }
            
            // Check for a successful status code (e.g., 200 OK)
            if httpResponse.statusCode == 200 {
                let responseDataString = data.flatMap { String(data: $0, encoding: .utf8) } ?? "No response data"
                print("HTTP Response OK!, body:\(responseDataString) ")
            } else {
                let responseDataString = data.flatMap { String(data: $0, encoding: .utf8) } ?? "No response data"
                print("HTTP Response Error: \(httpResponse.statusCode)\n\(responseDataString)")
                return
            }
        }
        
        // Start the URLSession task
        task.resume()
}

Posting activities for Android using Kotlin

import java.io.DataOutputStream
import java.net.HttpURLConnection
import java.net.URL
import java.util.*
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec


fun signPayload(uri: String, secretKeyId: String, timestamp: Long, payload: String, secretKeyValue: String): String {
    val message = "$uri\n$secretKeyId\n$timestamp\n$payload".toByteArray(Charsets.UTF_8)
    val secretKey = SecretKeySpec(secretKeyValue.toByteArray(Charsets.UTF_8), "HmacSHA256")
    val hmac = Mac.getInstance("HmacSHA256").apply { init(secretKey) }.doFinal(message)
    return Base64.getEncoder().encodeToString(hmac)
}

fun sendUserEvent(
    userAccountId: String?,
    userVendorId: String?,
    userMobileAdId: String?,
    eventName: String,
    category: String?,
    subCategory: String?
) {

    // Constant parameters that could be defined in a configuration file
    val DATAMART_ID = "XXX"          // the id of the database
    val APPLICATION_ID = "XXX"       // the id of the mobile application
    val COMPARTMENT_ID = "XXX"       // the id used to define the nature of the user account id
    val SECRET_KEY_ID = "XXXX" // the id of the secret key used to sign the message
    val SECRET_KEY_VALUE = "ZZZZZZZZ" // the secret key value

    // The URL entry is static
    val uri = "/v1/datamarts/$DATAMART_ID/user_activities"
    val url = "https://api.mediarithmics.com$uri"
    val timestamp = System.currentTimeMillis()

    // Prepare the JSON payload
    val identifiers = mutableListOf<String>()
    userAccountId?.let { it -> identifiers.add(""" { "${'$'}type" : "USER_ACCOUNT" , "${'$'}compartment_id" : "$COMPARTMENT_ID", "${'$'}user_account_id" : "$it" }""") }

    val jsonPayload = """{
            "${'$'}type" : "APP_VISIT",
            "${'$'}app_id" : "$APPLICATION_ID",
            "${'$'}session_status" : "IN_SESSION",
            "${'$'}ts" : $timestamp,
            "${'$'}user_identifiers" : ${identifiers.toList().joinToString(",", "[", "]")},
            "${'$'}events" : [
                    {
                            "${'$'}event_name" : "$eventName",
                            "${'$'}ts" : $timestamp,
                            "${'$'}properties" : {
                                    "category" : ${if (category == null) "null" else "\"${category}\""},
                                    "sub_category" : ${if (subCategory == null) "null" else "\"${subCategory}\""}
                            }
                    }
            ]
    }"""

    // Create a HttpURLConnection with the specified URL
    val urlConnection = URL(url).openConnection() as HttpURLConnection

    try {
        // Set the HTTP method
        urlConnection.requestMethod = "POST"

        // Enable output streams
        urlConnection.doOutput = true
        urlConnection.doInput = true

        // Convert the map to JSON data
        val jsonData = jsonPayload.toString().toByteArray(Charsets.UTF_8)

        urlConnection.setRequestProperty("charset", "utf-8")
        urlConnection.setRequestProperty("Content-Type", "application/json")
        urlConnection.setRequestProperty("Content-length", jsonData.size.toString())

        println(jsonData.toString(Charsets.UTF_8))
        // Build the HMAC signature
        val base64hmac = signPayload(uri, SECRET_KEY_ID, timestamp, String(jsonData, Charsets.UTF_8), SECRET_KEY_VALUE)

        // Set the specific headers
        urlConnection.setRequestProperty("X-Mics-Key-Id", SECRET_KEY_ID)
        urlConnection.setRequestProperty("X-Mics-Ts", timestamp.toString())
        urlConnection.setRequestProperty("X-Mics-Mac", base64hmac)

        //urlConnection.disconnect()

        val outputStream: DataOutputStream = DataOutputStream(urlConnection.outputStream)
        outputStream.write(jsonData)
        outputStream.flush()
        
    } catch (e: Exception) {
        println("Error while building the request data: $e")
        return
    }

    // Perform the API call
    try {
        val responseCode = urlConnection.responseCode
        // Check for a successful status code (e.g., 200 OK)
        if (responseCode == 200) {
            val responseDataString = urlConnection.inputStream.bufferedReader().use { it.readText() }
            println("HTTP Response OK!, body: $responseDataString")
        } else {
            val responseDataString = urlConnection.errorStream.bufferedReader().use { it.readText() }
            println("HTTP Response Error: $responseCode\n$responseDataString")
        }
    } catch (e: Exception) {
        println("Error: $e")
    } finally {
        // Disconnect after the request is complete
        urlConnection.disconnect()
    }
}

Posting activities in Node.js

var rp = require('request-promise');
var crypto = require('crypto');

var datamart_id = 854;
var app_id = "154"; // string only
var domain = `https://api.mediarithmics.com`
var uri = `/v1/datamarts/${datamart_id}/user_activities`;
var secret_key = "846cee8e-5558-4ca0-b723-095aa043c6ee";				 
var key_id = "my_key_identifier";
var ts = 1499103950000; // in milliseconds
var test_device = {
    "os": "and",
    "aaid": "b9f47db8-c0e9-40f4-8aed-fb063d7e54f7"
};
var body = {
    "$ts": ts,
    "$type": "APP_VISIT",
    "$session_status": "IN_SESSION",
    "$user_agent_id": `mob:${test_device.os}:raw:${test_device.aaid}`,
    "$app_id": app_id,
    "$events": [{
        "$ts": ts,
        "$event_name": "my_super_event",
        "$properties": {
            "custom_property1": "value1",
            "custom_property2": "value2",
            "custom_property3": "value3",
            "custom_property4": "value4",
            "custom_property5": "value5"
        }
    }]
};
var headers = {
    "X-Mics-Key-Id": key_id,
    "X-Mics-Ts": ts
};

var message = uri + "\n" + key_id + "\n" + JSON.stringify(ts) + "\n" + JSON.stringify(body);
var signature = signKey(secret_key, message);

headers["X-Mics-Mac"] = signature;

console.log(`uri: ${uri}\nsecret_key: ${secret_key}\nkey_id: ${key_id}\nts: ${JSON.stringify(ts)}\nheaders: ${JSON.stringify(headers)}\nbody: ${JSON.stringify(body)}`)
console.log(`message: ${message}`);
console.log(`signature: ${signature}`);

postEvent(domain + uri,'POST',headers,body);

function signKey(clientKey, message) {
    var hash = crypto.createHmac('sha256', clientKey).update(message).digest('base64');
    return hash;
};

async function postEvent(uri, method, headers, body) {    
    var request = await rp({
        uri: uri,
        method: method,
        headers: headers,
        body: body,
        json: true
    });
    console.log(request)
}

Getting user segments in Node.js

var rp = require('request-promise');
var crypto = require('crypto');

var datamart_id = 854;
var user_agent_id = 'vec:xxx';
var domain = `https://api.mediarithmics.com`;
var uri = `/v1/datamarts/${datamart_id}/user_points/user_agent_id=${user_agent_id}/user_segments`;
var secret_key = "846cee8e-5558-4ca0-b723-095aa043c6ee";				 
var key_id = "my_key_identifier";
var ts = 1499103950000; // in milliseconds
var headers = {
    "X-Mics-Key-Id": key_id,
    "X-Mics-Ts": ts
};

var message = uri + "\n" + key_id + "\n" + JSON.stringify(ts);
var signature = signKey(secret_key, message);

headers["X-Mics-Mac"] = signature;

console.log(`uri: ${uri}\nsecret_key: ${secret_key}\nkey_id: ${key_id}\nts: ${JSON.stringify(ts)}\nheaders: ${JSON.stringify(headers)}`)
console.log(`message: ${message}`);
console.log(`signature: ${signature}`);

getEvent(domain + uri,'GET',headers);

function signKey(clientKey, message) {
    var hash = crypto.createHmac('sha256', clientKey).update(message).digest('base64');
    return hash;
};

async function getEvent(uri, method, headers) {    
    var request = await rp({
        uri: uri,
        method: method,
        headers: headers,
        json: true
    });
    console.log(request)
}

Last updated