diff --git a/services/wakatime/wakatime-time.service.js b/services/wakatime/wakatime-time.service.js
new file mode 100644
index 0000000000000..501498e106b91
--- /dev/null
+++ b/services/wakatime/wakatime-time.service.js
@@ -0,0 +1,54 @@
+import { BaseSvgScrapingService, pathParams } from '../index.js'
+
+export default class WakaTimeBadge extends BaseSvgScrapingService {
+ static category = 'activity'
+ static route = {
+ base: 'wakatime',
+ pattern: ':type(user|project)/:id',
+ }
+
+ static openApi = {
+ '/wakatime/{type}/{id}': {
+ get: {
+ summary: 'WakaTime coding time badge',
+ parameters: pathParams(
+ {
+ name: 'type',
+ example: 'user',
+ schema: { type: 'string', enum: this.getEnum('type') },
+ },
+ {
+ name: 'id',
+ example: '73d84531-5bb3-4938-91c9-5ca9e6507df9',
+ },
+ ),
+ },
+ },
+ }
+
+ static _cacheLength = 3600
+
+ static defaultBadgeData = {
+ label: 'wakatime',
+ color: 'blue',
+ }
+
+ static render({ message }) {
+ return { message, color: 'blue' }
+ }
+
+ async fetch({ type, id }) {
+ const url = `https://wakatime.com/badge/${type}/${id}.svg`
+ const { buffer } = await this._request({ url })
+ return { buffer }
+ }
+
+ async handle({ type, id }) {
+ const { buffer } = await this.fetch({ type, id })
+ const match = buffer.match(/>([\d,]+\s+hrs?.*?)
+ nock('https://wakatime.com')
+ .get('/badge/user/test-id.svg')
+ .reply(200, ``),
+ )
+ .expectBadge({
+ label: 'wakatime',
+ message: '1,200 hrs 10 mins',
+ })
+
+t.create('project badge')
+ .get('/project/test-id.json')
+ .intercept(nock =>
+ nock('https://wakatime.com')
+ .get('/badge/project/test-id.svg')
+ .reply(200, ``),
+ )
+ .expectBadge({
+ label: 'wakatime',
+ message: '500 hrs 30 mins',
+ })
+
+t.create('invalid response')
+ .get('/user/test-id.json')
+ .intercept(nock =>
+ nock('https://wakatime.com')
+ .get('/badge/user/test-id.svg')
+ .reply(200, ``),
+ )
+ .expectBadge({
+ label: 'wakatime',
+ message: 'invalid',
+ })