Postcodeheatmap API Usage Guide

by

in

Introduction

The Postcodeheatmap API is a RESTful API designed to allow for automating the synchronisation of map visualisations created using Postcodeheatmap with external data pipelines. In short, this API provides the ability for developers to bulk import data into Postcodeheatmap and update existing maps.

Authentication

The API uses API key based authentication. API keys can be created from the user account page in Postcodeheatmap. The API key must be included in all API request headers. Additionally a custom request header containing a Profile ID must also be included. The Profile ID can also be found in the user account page.

To keep your API key safe you must ensure that it is never exposed in the browser. Always keep your API key on the server side.

API Reference

There are a small handful of API endpoints used to facilitate the data synchronisation use case. They can all be found and experimented with using the Postcodeheatmap Swagger definition.

View Swagger definition

Usage Example

The following example uses NodeJS. The script does three main things:

  • Uploads a csv file containing postcode data
  • Imports the data into Postcodeheatmap
  • Updates an existing map so that it now consumes the newly imported data

Step One – Prerequisites

This example has a few prerequisites:

  • Install NodeJS
  • Install the node-fetch JavaScript package
  • Make sure you have an existing map created in Postcodeheatmap (you can find the map ID in the browser address bar when viewing your map)
  • Make sure you have a csv file on the file system that contains postcodes in one of the columns
  • Make sure you have a valid API key and your Profile ID
import fetch, { fileFromSync } from "node-fetch";

const API_KEY = "YOUR_API_KEY";
const PROFILE_ID = "YOUR_PROFILE_ID";

const API_BASE_URL = "https://api.postcodeheatmap.com/v1";

// The name of the csv file stored on the file system
const csvFileName = "example.csv";

// The ID of the saved map that we want to update using the data stored in example.csv
const mapId = "YOUR_MAP_ID";

// The request headers used to authenticate all API calls
const headers = {
  "Content-Type": "application/json",
  "X-Api-Key": API_KEY,
  "Postcodeheatmap-User-Id": PROFILE_ID,
};

Step Two – File Upload

Define a function that will take care of uploading the csv file stored on the file system. This function will do two things:

  • Use the get-upload-url API endpoint to supply you with a secure upload URL
  • Use the supplied upload URL to upload the csv file to Postcodeheatmap
const uploadFile = async () => {
  // First make an API call to generate a secure URL used to upload the csv file
  const uploadUrlResponse = await fetch(
    `${API_BASE_URL}/get-upload-url?file-name=${csvFileName}`,
    {
      method: "GET",
      headers,
    }
  );

  const uploadUrl = await uploadUrlResponse.text();

  // Now use the generated URL to upload the csv file.
  await fetch(uploadUrl, {
    method: "PUT",
    body: fileFromSync(`./${csvFileName}`, "text/csv"),
  });

  console.log("example.csv has been successfully uploaded");
};

Step Two – Data Import

Define a function that will be responsible for initiating importing the data inside the csv file into Postcodeheatmap by calling the /import API endpoint. This endpoint takes two parameters in the request body:

  • Columns
  • FileName

The columns parameter accepts an array of objects, each object has a “type” key, the value of which should be set to “POSTCODE”. They also have an “index” key, the value of which points to the column in the csv file that contains the postcodes. The first column will always be 0.

You must make sure that the fileName parameter exactly matches that of the file that was uploaded.

const importData = async () => {
  // Start the import process.
  const importResponse = await fetch(`${API_BASE_URL}/import`, {
    method: "POST",
    body: JSON.stringify({
      // This tells Postcodeheatmap that the postcodes stored in the csv file
      // are in the sixth column.
      columns: [{ type: "POSTCODE", index: 6 }],
      fileName: csvFileName,
    }),
    headers,
  });

  // The import ID is used to identify the background process.
  const { importId } = await importResponse.json();

  return importId;
};

Step Three – Import Status Check

Define a function which has the job of checking the status of the in-flight data import that was initiated previously.

The function will poll the /import/status endpoint every three seconds until it returns a “completed” response along with an upload ID. This upload ID is used to associate the imported data with a specific map.

const checkImportStatus = async (importId) => {
  while (true) {
    const statusResponse = await fetch(
      `${API_BASE_URL}/import/status/${importId}`,
      {
        method: "GET",
        headers,
      }
    );

    const { state, returnValue, reason } = await statusResponse.json();

    // When completed return an upload ID used to associate the imported data with the map.
    if (state === "completed") {
      console.log("Postcode data has been successfully imported");
      return {
        uploadId: returnValue.uploadId,
        processedRows: returnValue.processedRows,
      };
    }

    if (state === "failed") {
      throw new Error(reason);
    }

    if (state === "active") {
      // If still active, wait 3 seconds before the next check
      await new Promise((resolve) => setTimeout(resolve, 3000));
    }
  }
};

Step Four – Update and Publish Map

Define a function which associate the imported data with a map created in Postcodeheatmap. This function will do the following:

  • Create a dataset – this is associated to the data imported previously.
  • Update the existing map so that it is associated with the data uploaded previously
  • Publish the map
const updateMapData = async (uploadId, rowCount) => {
  // A dataset is created. This represents the data imported into Postcodeheatmap.
  await fetch(`${API_BASE_URL}/dataset`, {
    method: "POST",
    body: JSON.stringify({
      uploadId,
      fileName: csvFileName,
      rowCount,
      hasMeasure: false,
      hasAmount: false,
      hasCategory: false,
    }),
    headers,
  });

  // Update the maps upload ID
  await fetch(`${API_BASE_URL}/map/${mapId}`, {
    method: "PUT",
    body: JSON.stringify({ uploadId }),
    headers,
  });

  // Now finally publish the map
  await fetch(`${API_BASE_URL}/publish`, {
    method: "POST",
    body: JSON.stringify({ mapId }),
    headers,
  });
};

Step Five – Put Everything Together

Now finally chain all of the previously defined function calls in order to execute the data synchronisation.

(async () => {
  try {
    // Upload the csv file
    await uploadFile();

    // Then initiate importing the data to retrieve an import ID
    const importId = await importData();

    // Check the status of the import until an upload ID is returned
    const { uploadId, processedRows } = await checkImportStatus(importId);

    // Update an publish the map
    await updateMapData(uploadId, processedRows);

    console.log("Map data updated successfully!");
  } catch (error) {
    console.error(error.message);
  }
})();

Below you can find the complete NodeJS script that you can use to get started. To sign up and start using Postcodeheatmap and it’s API you can visit postcodeheatmap.com.

import fetch, { fileFromSync } from "node-fetch";

const API_KEY = "YOUR_API_KEY";
const PROFILE_ID = "YOUR_PROFILE_ID";

const API_BASE_URL = "https://api.postcodeheatmap.com/v1";

// The name of the csv file stored on the file system
const csvFileName = "example.csv";

// The ID of the saved map that we want to update using the data stored in example.csv
const mapId = "YOUR_MAP_ID";

// The request headers used to authenticate all API calls
const headers = {
  "Content-Type": "application/json",
  "X-Api-Key": API_KEY,
  "Postcodeheatmap-User-Id": PROFILE_ID,
};

const uploadFile = async () => {
  // First make an API call to generate a secure URL used to upload the csv file
  const uploadUrlResponse = await fetch(
    `${API_BASE_URL}/get-upload-url?file-name=${csvFileName}`,
    {
      method: "GET",
      headers,
    }
  );

  const uploadUrl = await uploadUrlResponse.text();

  // Now use the generated URL to upload the csv file.
  await fetch(uploadUrl, {
    method: "PUT",
    body: fileFromSync(`./${csvFileName}`, "text/csv"),
  });

  console.log("example.csv has been successfully uploaded");
};

const importData = async () => {
  // Start the import process.
  const importResponse = await fetch(`${API_BASE_URL}/import`, {
    method: "POST",
    body: JSON.stringify({
      // This tells Postcodeheatmap that the postcodes stored in the csv file
      // are in the sixth column.
      columns: [{ type: "POSTCODE", index: 6 }],
      fileName: csvFileName,
    }),
    headers,
  });

  // The import ID is used to identify the background process.
  const { importId } = await importResponse.json();

  return importId;
};

const checkImportStatus = async (importId) => {
  while (true) {
    const statusResponse = await fetch(
      `${API_BASE_URL}/import/status/${importId}`,
      {
        method: "GET",
        headers,
      }
    );

    const { state, returnValue, reason } = await statusResponse.json();

    // When completed return an upload ID used to associate the imported data with the map.
    if (state === "completed") {
      console.log("Postcode data has been successfully imported");
      return {
        uploadId: returnValue.uploadId,
        processedRows: returnValue.processedRows,
      };
    }

    if (state === "failed") {
      throw new Error(reason);
    }

    if (state === "active") {
      // If still active, wait 3 seconds before the next check
      await new Promise((resolve) => setTimeout(resolve, 3000));
    }
  }
};

const updateMapData = async (uploadId, rowCount) => {
  // A dataset is created. This represents the data imported into Postcodeheatmap.
  await fetch(`${API_BASE_URL}/dataset`, {
    method: "POST",
    body: JSON.stringify({
      uploadId,
      fileName: csvFileName,
      rowCount,
      hasMeasure: false,
      hasAmount: false,
      hasCategory: false,
    }),
    headers,
  });

  // Update the maps upload ID
  await fetch(`${API_BASE_URL}/map/${mapId}`, {
    method: "PUT",
    body: JSON.stringify({ uploadId }),
    headers,
  });

  // Now finally publish the map
  await fetch(`${API_BASE_URL}/publish`, {
    method: "POST",
    body: JSON.stringify({ mapId }),
    headers,
  });
};

(async () => {
  try {
    // Upload the csv file
    await uploadFile();

    // Then initiate importing the data to retrieve an import ID
    const importId = await importData();

    // Check the status of the import until an upload ID is returned
    const { uploadId, processedRows } = await checkImportStatus(importId);

    // Update an publish the map
    await updateMapData(uploadId, processedRows);

    console.log("Map data updated successfully!");
  } catch (error) {
    console.error(error.message);
  }
})();