diff --git a/services/nodeping/nodeping-status.service.js b/services/nodeping/nodeping-status.service.js
new file mode 100644
index 0000000000000000000000000000000000000000..987e3fb6dc55e92e8c4ba0c50dd4c301fde3a0e0
--- /dev/null
+++ b/services/nodeping/nodeping-status.service.js
@@ -0,0 +1,105 @@
+'use strict'
+
+const { BaseJsonService } = require('..')
+const Joi = require('joi')
+
+const rowSchema = Joi.object().keys({ su: Joi.boolean() })
+
+const schema = Joi.array()
+  .items(rowSchema)
+  .min(1)
+
+/*
+ * this is the checkUuid for the NodePing.com (as used on the [example page](https://nodeping.com/reporting.html#results))
+ */
+const sampleCheckUuid = 'jkiwn052-ntpp-4lbb-8d45-ihew6d9ucoei'
+
+class NodePingStatus extends BaseJsonService {
+  static get category() {
+    return 'monitoring'
+  }
+
+  static get defaultBadgeData() {
+    return {
+      label: 'Status',
+    }
+  }
+
+  static get route() {
+    return {
+      base: 'nodeping/status',
+      pattern: ':checkUuid',
+      queryParams: ['up_message', 'down_message', 'up_color', 'down_color'],
+    }
+  }
+
+  static get examples() {
+    return [
+      {
+        title: 'NodePing status',
+        namedParams: {
+          checkUuid: sampleCheckUuid,
+        },
+        staticPreview: this.render({ status: true }),
+      },
+      {
+        title: 'NodePing status (customized)',
+        namedParams: {
+          checkUuid: sampleCheckUuid,
+        },
+        queryParams: {
+          up_message: 'Online',
+          up_color: 'blue',
+          down_message: 'Offline',
+          down_color: 'lightgrey',
+        },
+        staticPreview: this.render({
+          status: true,
+          upMessage: 'Online',
+          upColor: 'blue',
+        }),
+      },
+    ]
+  }
+
+  async fetch({ checkUuid }) {
+    const rows = await this._requestJson({
+      schema,
+      url: `https://nodeping.com/reports/results/${checkUuid}/1`,
+      options: {
+        qs: { format: 'json' },
+        headers: {
+          'cache-control': 'no-cache',
+        },
+      },
+    })
+    return { status: rows[0].su }
+  }
+
+  async handle(
+    { checkUuid },
+    {
+      up_message: upMessage,
+      down_message: downMessage,
+      up_color: upColor,
+      down_color: downColor,
+    }
+  ) {
+    const { status } = await this.fetch({ checkUuid })
+    return this.constructor.render({
+      status,
+      upMessage,
+      downMessage,
+      upColor,
+      downColor,
+    })
+  }
+
+  static render({ status, upMessage, downMessage, upColor, downColor }) {
+    return status
+      ? { message: upMessage || 'up', color: upColor || 'brightgreen' }
+      : { message: downMessage || 'down', color: downColor || 'red' }
+  }
+}
+
+module.exports = NodePingStatus
diff --git a/services/nodeping/nodeping-status.tester.js b/services/nodeping/nodeping-status.tester.js
new file mode 100644
index 0000000000000000000000000000000000000000..17ee03b0b092f5368ffb4e9a812e24bae1288b9f
--- /dev/null
+++ b/services/nodeping/nodeping-status.tester.js
@@ -0,0 +1,75 @@
+'use strict'
+
+const Joi = require('joi')
+
+const t = (module.exports = require('../tester').createServiceTester())
+
+t.create('NodePing status - live').get(
+  '/jkiwn052-ntpp-4lbb-8d45-ihew6d9ucoei.json'
+)
+Joi.object().keys({
+  name: 'Status',
+  value: Joi.equal('up', 'down').required(),
+})
+
+t.create('NodePing status - up (mock)')
+  .get('/jkiwn052-ntpp-4lbb-8d45-ihew6d9ucoei.json')
+  .intercept(nock =>
+    nock('https://nodeping.com')
+      .get(
+        '/reports/results/jkiwn052-ntpp-4lbb-8d45-ihew6d9ucoei/1?format=json'
+      )
+      .reply(200, [{ su: true }])
+  )
+  .expectJSON({
+    name: 'Status',
+    value: 'up',
+  })
+
+t.create('NodePing status - down (mock)')
+  .get('/jkiwn052-ntpp-4lbb-8d45-ihew6d9ucoei.json')
+  .intercept(nock =>
+    nock('https://nodeping.com')
+      .get(
+        '/reports/results/jkiwn052-ntpp-4lbb-8d45-ihew6d9ucoei/1?format=json'
+      )
+      .reply(200, [{ su: false }])
+  )
+  .expectJSON({
+    name: 'Status',
+    value: 'down',
+  })
+
+t.create('NodePing status - custom up color/message')
+  .get(
+    '/jkiwn052-ntpp-4lbb-8d45-ihew6d9ucoei.json?style=_shields_test&up_color=blue&up_message=happy'
+  )
+  .intercept(nock =>
+    nock('https://nodeping.com')
+      .get(
+        '/reports/results/jkiwn052-ntpp-4lbb-8d45-ihew6d9ucoei/1?format=json'
+      )
+      .reply(200, [{ su: true }])
+  )
+  .expectJSON({
+    name: 'Status',
+    value: 'happy',
+    color: 'blue',
+  })
+
+t.create('NodePing status - custom down color/message')
+  .get(
+    '/jkiwn052-ntpp-4lbb-8d45-ihew6d9ucoei.json?style=_shields_test&down_color=yellow&down_message=sad'
+  )
+  .intercept(nock =>
+    nock('https://nodeping.com')
+      .get(
+        '/reports/results/jkiwn052-ntpp-4lbb-8d45-ihew6d9ucoei/1?format=json'
+      )
+      .reply(200, [{ su: false }])
+  )
+  .expectJSON({
+    name: 'Status',
+    value: 'sad',
+    color: 'yellow',
+  })
diff --git a/services/nodeping/nodeping-uptime.service.js b/services/nodeping/nodeping-uptime.service.js
new file mode 100644
index 0000000000000000000000000000000000000000..3b1c8ff5108c8574175b5b2cd48ef7806ff6202c
--- /dev/null
+++ b/services/nodeping/nodeping-uptime.service.js
@@ -0,0 +1,100 @@
+'use strict'
+
+const { BaseJsonService } = require('..')
+const Joi = require('joi')
+const { colorScale } = require('../../lib/color-formatters')
+const colorFormatter = colorScale([99, 99.5, 100])
+
+const rowSchema = Joi.object().keys({
+  uptime: Joi.number()
+    .precision(3)
+    .min(0)
+    .max(100),
+})
+
+const schema = Joi.array()
+  .items(rowSchema)
+  .min(1)
+
+/*
+ * this is the checkUuid for the NodePing.com (as used on the [example page](https://nodeping.com/reporting.html#results))
+ */
+const sampleCheckUuid = 'jkiwn052-ntpp-4lbb-8d45-ihew6d9ucoei'
+
+// TODO: support for custom # of days
+// TODO: support for custom color thresholds
+// TODO: support for custom colors
+// TODO: support for custom '100%' label
+// TODO: support for custom # of decimal places
+
+class NodePingUptime extends BaseJsonService {
+  static get category() {
+    return 'monitoring'
+  }
+
+  static get defaultBadgeData() {
+    return {
+      label: 'Uptime',
+    }
+  }
+
+  static get route() {
+    return {
+      base: 'nodeping/uptime',
+      pattern: ':checkUuid',
+    }
+  }
+
+  static get examples() {
+    return [
+      {
+        title: 'NodePing uptime',
+        namedParams: {
+          checkUuid: sampleCheckUuid,
+        },
+        staticPreview: this.render({ uptime: 99.999 }),
+      },
+    ]
+  }
+
+  async fetch({ checkUuid }) {
+    const thirtyDaysAgo = new Date(
+      new Date().getTime() - 30 * 24 * 60 * 60 * 1000
+    )
+      .toISOString()
+      .slice(0, 10)
+
+    const rows = await this._requestJson({
+      schema,
+      url: `https://nodeping.com/reports/uptime/${checkUuid}`,
+      options: {
+        qs: { format: 'json', interval: 'days', start: thirtyDaysAgo },
+        headers: {
+          'cache-control': 'no-cache',
+        },
+      },
+    })
+    return { uptime: rows[rows.length - 1].uptime }
+  }
+
+  async handle({ checkUuid }) {
+    const { uptime } = await this.fetch({ checkUuid })
+    return this.constructor.render({ uptime })
+  }
+
+  static formatPercentage(uptime) {
+    if (uptime === 100.0) {
+      return '100%'
+    }
+    return `${uptime.toFixed(3)}%`
+  }
+
+  static render({ uptime }) {
+    return {
+      message: NodePingUptime.formatPercentage(uptime),
+      color: colorFormatter(uptime),
+    }
+  }
+}
+
+module.exports = NodePingUptime
diff --git a/services/nodeping/nodeping-uptime.tester.js b/services/nodeping/nodeping-uptime.tester.js
new file mode 100644
index 0000000000000000000000000000000000000000..7ba3021bd38c407831eec9b0bb7d360727193a8b
--- /dev/null
+++ b/services/nodeping/nodeping-uptime.tester.js
@@ -0,0 +1,87 @@
+'use strict'
+
+const t = (module.exports = require('../tester').createServiceTester())
+const { isPercentage } = require('../test-validators')
+
+t.create('NodePing uptime - live')
+  .get('/jkiwn052-ntpp-4lbb-8d45-ihew6d9ucoei.json')
+  .expectJSONTypes({
+    name: 'Uptime',
+    value: isPercentage,
+  })
+
+t.create('NodePing uptime - 100% (mock)')
+  .get('/jkiwn052-ntpp-4lbb-8d45-ihew6d9ucoei.json?style=_shields_test')
+  .intercept(nock =>
+    nock('https://nodeping.com')
+      .get(
+        `/reports/uptime/jkiwn052-ntpp-4lbb-8d45-ihew6d9ucoei?format=json&interval=days&start=${new Date(
+          new Date().getTime() - 30 * 24 * 60 * 60 * 1000
+        )
+          .toISOString()
+          .slice(0, 10)}`
+      )
+      .reply(200, [{ uptime: 100 }])
+  )
+  .expectJSON({
+    name: 'Uptime',
+    value: '100%',
+    color: 'brightgreen',
+  })
+
+t.create('NodePing uptime - 99.999% (mock)')
+  .get('/jkiwn052-ntpp-4lbb-8d45-ihew6d9ucoei.json?style=_shields_test')
+  .intercept(nock =>
+    nock('https://nodeping.com')
+      .get(
+        `/reports/uptime/jkiwn052-ntpp-4lbb-8d45-ihew6d9ucoei?format=json&interval=days&start=${new Date(
+          new Date().getTime() - 30 * 24 * 60 * 60 * 1000
+        )
+          .toISOString()
+          .slice(0, 10)}`
+      )
+      .reply(200, [{ uptime: 99.999 }])
+  )
+  .expectJSON({
+    name: 'Uptime',
+    value: '99.999%',
+    color: 'green',
+  })
+
+t.create('NodePing uptime - 99.001% (mock)')
+  .get('/jkiwn052-ntpp-4lbb-8d45-ihew6d9ucoei.json?style=_shields_test')
+  .intercept(nock =>
+    nock('https://nodeping.com')
+      .get(
+        `/reports/uptime/jkiwn052-ntpp-4lbb-8d45-ihew6d9ucoei?format=json&interval=days&start=${new Date(
+          new Date().getTime() - 30 * 24 * 60 * 60 * 1000
+        )
+          .toISOString()
+          .slice(0, 10)}`
+      )
+      .reply(200, [{ uptime: 99.001 }])
+  )
+  .expectJSON({
+    name: 'Uptime',
+    value: '99.001%',
+    color: 'yellow',
+  })
+
+t.create('NodePing uptime - 90.001% (mock)')
+  .get('/jkiwn052-ntpp-4lbb-8d45-ihew6d9ucoei.json?style=_shields_test')
+  .intercept(nock =>
+    nock('https://nodeping.com')
+      .get(
+        `/reports/uptime/jkiwn052-ntpp-4lbb-8d45-ihew6d9ucoei?format=json&interval=days&start=${new Date(
+          new Date().getTime() - 30 * 24 * 60 * 60 * 1000
+        )
+          .toISOString()
+          .slice(0, 10)}`
+      )
+      .reply(200, [{ uptime: 90.001 }])
+  )
+  .expectJSON({
+    name: 'Uptime',
+    value: '90.001%',
+    color: 'red',
+  })