npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

reclaim-sdk

v0.1.0

Published

Unofficial Node.js SDK for the Reclaim.ai API — tasks, habits, hours, webhooks, changelog.

Readme

reclaim-sdk

Unofficial Node.js SDK for the Reclaim.ai API — tasks, habits, hours, webhooks and the changelog feed, all behind a single, fully-typed surface.

npm install reclaim-sdk

Requires Node 20 or newer. Zero runtime dependencies (uses the built-in fetch and node:crypto).

⚠️ This package is not affiliated with Reclaim.ai. It is reverse-engineered from the public web app plus the partial Swagger spec at https://api.app.reclaim.ai/swagger/reclaim-api-0.1.yml. The Reclaim API is not formally versioned for third-party use, so things may break without warning. Pin a version and watch the changelog.


Contents


Quick start

import { ReclaimClient, Task, PriorityLevel } from "reclaim-sdk";

ReclaimClient.configure({ token: process.env.RECLAIM_TOKEN! });

const task = new Task({
  title: "Write Q3 wrap-up",
  priority: PriorityLevel.P2,
  due: new Date("2026-09-30"),
});
task.duration = 2;            // 2h of work
task.minWorkDuration = 0.5;   // 30 min min slot
await task.save();

await task.start();
await task.logWork(45);       // log 45 minutes
await task.stop();
await task.markComplete();

reclaim-sdk ships in both ESM and CommonJS flavours with complete .d.ts types — import and require both work.


Configuration

Grab an API token from https://app.reclaim.ai/settings/developer.

Pick one of:

// Option A — pass it explicitly.
import { ReclaimClient } from "reclaim-sdk";
ReclaimClient.configure({ token: "rec_..." });
# Option B — set an env var; the SDK picks it up on first use.
export RECLAIM_TOKEN=rec_...

ReclaimClient is a singleton — call configure once at boot and every resource picks it up automatically. To override per-call (e.g. multi-tenant workers), pass a { client } option to any static method.

Custom base URL or fetch

ReclaimClient.configure({
  token: "rec_...",
  baseUrl: "https://api.app.reclaim.ai", // default
  fetch: customFetch,                     // optional — for tests / proxies
});

Tasks

Full CRUD against /api/tasks plus every planner action.

import { Task, PriorityLevel, EventColor } from "reclaim-sdk";

// List
const tasks = await Task.list();

// Fetch one
const t = await Task.get(12345);

// Create
const draft = new Task({
  title: "Migrate auth middleware",
  priority: PriorityLevel.P1,
  notes: "Per legal/compliance review",
});
draft.duration = 4;             // 4 hours, rounded to 15-min chunks
draft.eventColor = EventColor.GRAPE;
await draft.save();

// Update — patch only the touched fields
draft.notes = "Updated scope after standup";
await draft.save();

// Planner actions
await draft.start();
await draft.logWork(60);
await draft.stop();
await draft.markComplete();
await draft.markIncomplete();   // un-archive

await draft.snooze({ option: "TOMORROW" });
await draft.clearSnooze();

await draft.planWork({
  dateTime: new Date("2026-06-01T09:00:00Z"),
  durationMinutes: 90,
});
await draft.addTime(0.5);       // add 30 min

await draft.delete();

Batch operations

import { Task, type TaskPatch } from "reclaim-sdk";

const patches: TaskPatch[] = [
  { taskId: 11, patch: { priority: "P1" } },
  { taskId: 12, patch: { priority: "P2" } },
];
await Task.batchPatch(patches);
await Task.batchArchive(patches);
await Task.batchDelete(patches);

// One-shot reorder by due date:
const reordered = await Task.prioritizeByDue();

Pin to a specific start time

import { Task } from "reclaim-sdk";

const pinned = new Task({ title: "Pair-programming with @b", priority: "P2" });
pinned.duration = 1;
const result = await Task.createAtTime(pinned, new Date("2026-06-02T15:00:00Z"));

Time chunks

Reclaim's planner thinks in 15-minute increments. Use the human-friendly accessors:

| Hours accessor | Underlying chunk field | | ----------------- | ---------------------- | | duration | timeChunksRequired | | minWorkDuration | minChunkSize | | maxWorkDuration | maxChunkSize | | upNext | onDeck |

Setting an hours value rewrites the chunk count; reading it returns chunks / 4.


Daily habits

import { DailyHabit } from "reclaim-sdk";

const habits = await DailyHabit.list();

const focus = habits.find(h => h.title === "Deep work");
if (focus) {
  await focus.toggle(false);         // pause
  await focus.toggle(true);          // resume
}

// Habit templates (read-only catalogue)
const tpl = await DailyHabit.getTemplate();
const examples = await DailyHabit.listTemplates({ role: "engineer" });

Creating habits directly via API frequently returns 409 (calendar collisions, plan-tier limits). In practice, create habits in the web app and manage them — toggle / reschedule / skip — through the SDK.


Hours / time schemes

A time scheme is a named working-hours profile (/api/timeschemes). You can create custom schemes with per-weekday intervals and bind them to specific tasks or habits.

import { EventCategory, Hours, PolicyType, TimeSchemeFeature, Weekday } from "reclaim-sdk";

const workday = { intervals: [{ start: "09:00:00", end: "17:00:00" }] };

const scheme = new Hours({
  title: "Demo Hours",
  description: "Custom 9-5 Mon-Fri",
  taskCategory: EventCategory.WORK,
  policyType: PolicyType.CUSTOM,
  policy: {
    dayHours: {
      [Weekday.MONDAY]: workday,
      [Weekday.TUESDAY]: workday,
      [Weekday.WEDNESDAY]: workday,
      [Weekday.THURSDAY]: workday,
      [Weekday.FRIDAY]: workday,
    },
  },
  features: [
    TimeSchemeFeature.TASK_ASSIGNMENT,
    TimeSchemeFeature.HABIT_ASSIGNMENT,
  ],
});
await scheme.save();

// Bind a task to this scheme
const t = await Task.get(123);
t.timeSchemeId = scheme.id;
await t.save();

// Update
scheme.description = "Updated";
await scheme.save();

// Delete
await scheme.delete();

policyType: "CUSTOM" is required when you want to send your own policy.dayHours. The other policy values (WORK, PERSONAL, MEETING) bind the scheme to a user's main profile.


Changelog feed

A read-only stream of change events across Reclaim resources. Useful for polling sync workers.

import { Changelog, Task } from "reclaim-sdk";

// Everything
const all = await Changelog.all();

// Scoped to specific ids
const tasks = await Task.list();
const ids = tasks.slice(0, 5).map(t => t.id!).filter((v): v is number => !!v);
const taskEvents = await Changelog.tasks(ids);

// Other feeds
await Changelog.events(["abc", "def"]);
await Changelog.smartHabits([1, 2]);
await Changelog.smartMeetings([3, 4]);
await Changelog.schedulingLinks(["link-1", "link-2"]);

Webhooks

Subscribing

import { Webhook } from "reclaim-sdk";

// List existing subscriptions
for (const wh of await Webhook.list()) {
  console.log(wh.id, wh.url, wh.status, wh.events);
}

// Create a new one
const wh = new Webhook({
  url: "https://yourapp.example.com/reclaim-hook",
  events: ["task.created", "task.updated", "task.completed"],
  status: "ACTIVE",
  apiVersion: "v2026-04-13",
  name: "my-integration",
});
await wh.save();

Subscribing requires an apiVersion value. The webhook feature may need to be provisioned by Reclaim before POST succeeds — contact support if you see 500 errors on create.

Receiving

import express from "express";
import {
  parseWebhookPayload,
  verifySignature,
  isTaskWebhookEvent,
  isHabitWebhookEvent,
  SignatureVerificationError,
} from "reclaim-sdk";

const app = express();

app.post(
  "/reclaim-hook",
  express.raw({ type: "application/json" }),    // raw body, not parsed JSON
  (req, res) => {
    try {
      verifySignature(
        req.body,
        req.header("X-Reclaim-Signature"),
        process.env.RECLAIM_WEBHOOK_SECRET!,
      );
    } catch (err) {
      if (err instanceof SignatureVerificationError) return res.sendStatus(401);
      throw err;
    }

    const event = parseWebhookPayload(req.body);
    if (isTaskWebhookEvent(event)) {
      console.log("task", event.type, event.task.id, event.task.title);
    } else if (isHabitWebhookEvent(event)) {
      console.log("habit", event.type, event.habit.id, event.habit.title);
    }
    res.sendStatus(200);
  },
);

parseWebhookPayload rehydrates the inner task / habit field into a full Task / DailyHabit instance, so you can immediately call methods on it (e.g. event.task.markComplete()).

Always pass the raw body to verifySignature. Parsing and re-stringifying JSON changes whitespace and breaks the HMAC digest.


Errors

Every API failure throws a typed error. Catch the base class to handle them generically, or the subclass for fine-grained recovery:

import {
  ReclaimAPIError,
  RecordNotFound,
  InvalidRecord,
  AuthenticationError,
  SignatureVerificationError,
} from "reclaim-sdk";

try {
  await Task.get(99999999);
} catch (err) {
  if (err instanceof RecordNotFound)        // 404
    return null;
  if (err instanceof InvalidRecord)         // 400 / 422
    console.warn("bad payload", err.body);
  if (err instanceof AuthenticationError)   // 401
    process.exit(1);
  if (err instanceof ReclaimAPIError)       // anything else
    throw err;
}

Every ReclaimAPIError carries .status (the HTTP status code) and .body (the parsed error body, if any).


Coverage matrix

| Resource | Supported | | --------------------- | ---------------------------------------------------------------------------------------------------- | | Tasks | CRUD, batch ops, planner actions (start/stop/snooze/plan-work/log-work/complete/add-time/reindex/…) | | Daily habits | CRUD, toggle, reschedule / skip events, templates, smart-series migration | | Hours / time schemes | Full CRUD, custom dayHours policies, features, target calendars | | Webhooks | CRUD subscriptions, typed payloads, HMAC-SHA256 signature verification | | Changelog | All feeds (tasks, events, smart habits, smart meetings, scheduling links, full feed) |

Not yet covered — open a PR if you need them: Events, Calendars, Scheduling Links, One-on-Ones, Analytics, Focus Settings, Integrations, API-key management, Admin / delegated access.


TypeScript

The package is written in TypeScript and ships full .d.ts types. All enums are exported both as value namespaces and as type aliases so you can write priority: PriorityLevel.P1 or priority: "P1" interchangeably:

import { PriorityLevel } from "reclaim-sdk";

let p: PriorityLevel;
p = PriorityLevel.P1;   // ✓
p = "P2";               // ✓
p = "P5";               // ✗ — TS error

Init shapes

Each resource exports a matching *Init interface — useful for typing partial drafts you build up before saving:

import { type TaskInit, type WebhookEventName, Task } from "reclaim-sdk";

function buildTask(overrides: TaskInit): Task {
  return new Task({ title: "Default", priority: "P3", ...overrides });
}

const subscribedEvents: WebhookEventName[] = ["task.created", "habit.deleted"];

Available: TaskInit, DailyHabitInit, HoursInit, WebhookInit, WebhookEventName.


Enum reference

All enums are exported as const objects + matching string-literal type. Use either form interchangeably.

PriorityLevel

Task / habit priority bucket. Lower number = higher priority.

P1 · P2 · P3 · P4

EventCategory

Top-level category. Drives which working-hours profile the planner consults.

WORK · PERSONAL · BOTH

EventColor

Calendar event colour (mirrors Google Calendar's palette).

NONE · LAVENDER · SAGE · GRAPE · FLAMINGO · BANANA · TANGERINE · PEACOCK · GRAPHITE · BLUEBERRY · BASIL · TOMATO

EventSubType

Detailed event sub-category as understood by the scheduling engine.

ONE_ON_ONE · STAFF_MEETING · OP_REVIEW · EXTERNAL · IDEATION · FOCUS · PRODUCTIVITY · TRAVEL · FLIGHT · TRAIN · RECLAIM · VACATION · HEALTH · ERRAND · OTHER_PERSONAL · UNKNOWN

TaskStatus

Lifecycle state of a task. Read-only — set by the server.

NEW · SCHEDULED · IN_PROGRESS · COMPLETE · CANCELLED · ARCHIVED

TaskSource

Where a task originated. Reclaim's own tasks use RECLAIM; integrations carry their own value.

RECLAIM · GOOGLE · ASANA · CLICKUP · JIRA · TODOIST · LINEAR

SnoozeOption

Built-in snooze presets accepted by task.snooze({ option }) / habit.snooze({ option }). Combine with relativeFrom to shift the reference point away from "now".

| Value | Meaning | | --- | --- | | FROM_NOW_15M | +15 minutes | | FROM_NOW_30M | +30 minutes | | FROM_NOW_1H | +1 hour | | FROM_NOW_2H | +2 hours | | FROM_NOW_4H | +4 hours | | TOMORROW | next morning | | IN_TWO_DAYS | the day after tomorrow | | NEXT_WEEK | start of next week |

import { SnoozeOption } from "reclaim-sdk";

await task.snooze({ option: SnoozeOption.TOMORROW });

// Custom reference point — snooze starts 2h after a given timestamp:
await task.snooze({
  option: SnoozeOption.FROM_NOW_2H,
  relativeFrom: new Date("2026-05-13T14:00:00Z"),
});

await task.clearSnooze();

Weekday

Used as the key type for TimeSchemePolicy.dayHours.

MONDAY · TUESDAY · WEDNESDAY · THURSDAY · FRIDAY · SATURDAY · SUNDAY

TimeSchemeFeature

Which Reclaim flows are allowed to consume a given time scheme.

| Value | Used by | | --- | --- | | TASK_ASSIGNMENT | Tasks | | HABIT_ASSIGNMENT | Daily habits | | SMART_HABIT | Smart habits | | ONE_ON_ONE_ASSIGNMENT | 1:1 meetings | | SMART_MEETING | Smart meetings | | SCHEDULING_LINK_MEETING | Scheduling-link bookings |

PolicyType

Time-scheme policy mode. CUSTOM is required to send your own policy.dayHours; the others bind the scheme to the user's main profile.

CUSTOM · WORK · PERSONAL · MEETING


License

MIT — see LICENSE.


Credits

Big thanks to the team behind the original labiso-gmbh/reclaim-sdk (Python) — the work they put into reverse-engineering the Reclaim API and keeping it documented made building alternative clients in other languages possible. If you also work in Python, go check it out.