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

sprint-sync

v0.1.0

Published

Official Node.js SDK for the Sprint Sync API — create and query tasks, sprints, epics and QA checklists, and verify webhook signatures.

Readme

sprint-sync

Official Node.js SDK for the Sprint Sync API.

npm install sprint-sync

Get a key from Sprint Sync → Settings → API Keys. Node 18+.

Quick start

import SprintSync from 'sprint-sync';

const ss = new SprintSync({ apiKey: process.env.SPRINTSYNC_API_KEY });

await ss.tasks.create({
  projectKey: 'EMPL',
  name: 'Add rate limiting to auth endpoints',
  priority: 'Critical',
  components: ['Infra'],
});

Names, not ids

You pass 'High' and 'Frontend', not priority_id: 3. The API resolves them inside your organization.

Those names are configured per organization, so check what yours accepts:

const config = await ss.projects.config('EMPL');
config.priorities;   // ['Critical', 'High', 'Medium', 'Low', 'Enhancement']
config.statuses;     // [{ name: 'Todo', category: 'todo' }, …]
config.components;   // ['Core Platform', 'Infra', …]

Send a name that doesn't exist and you get a 400 listing the ones that do.

Tasks

await ss.tasks.list('EMPL', { limit: 20 });
await ss.tasks.list('EMPL', { sprintId: 12 });

const { task_id } = await ss.tasks.create({ projectKey: 'EMPL', name: 'Fix login' });

await ss.tasks.update(task_id, { status: 'Done' });

update leaves out what you leave out. The one exception is components, which replaces the whole set — send every component the task should end up with, or [] to clear them.

Importing a whole plan

Creating twenty related items one call at a time is slow, and a failure halfway leaves an orphaned epic and half a sprint for someone to clean up by hand. plans.import does it in one transaction: all of it lands or none of it does.

const plan = {
  project_key: 'EMPL',
  epic: { name: 'Checkout redesign' },
  sprints: [{
    name: 'Sprint 12',
    start_date: '2026-09-08',
    end_date: '2026-09-19',
    tasks: [
      {
        name: 'Split CartSummary',
        priority: 'High',
        story_points: 3,
        components: ['Frontend'],
        subtasks: [{ name: 'Extract useCartTotals' }],
      },
      { name: 'Payment intent expires mid-checkout', issue_type: 'bug', priority: 'Critical' },
    ],
  }],
  qa_checklist: {
    testing_type: 'Regression',
    flows: [{
      name: 'Guest checkout',
      steps: [{ name: 'Pay by card', checks: ['3DS challenge appears', 'Receipt email sends'] }],
    }],
  },
};

// See what it would create, without creating it
const { would_create } = await ss.plans.preview(plan);

// Commit
const result = await ss.plans.import(plan, { idempotencyKey: 'checkout-redesign-v1' });

Pass an idempotencyKey if the call might be retried — a request that times out after the server already committed would otherwise file the whole breakdown twice. The same key with the same body replays the original response. The same key with a different body is a 409, because that's a bug rather than a retry.

Receiving webhooks

Sprint Sync signs every delivery. Verify before you trust it:

import express from 'express';
import { constructEvent, EVENTS } from 'sprint-sync';

const app = express();

// express.raw, NOT express.json — see below
app.post('/hooks/sprintsync', express.raw({ type: 'application/json' }), (req, res) => {
  let event;
  try {
    event = constructEvent({
      secret: process.env.SPRINTSYNC_WEBHOOK_SECRET,
      rawBody: req.body,
      signature: req.get('X-SprintSync-Signature'),
    });
  } catch {
    return res.sendStatus(401);   // forged or misconfigured
  }

  if (event.event === EVENTS.TASK_STATUS_CHANGED) {
    console.log(event.data.task_name, '→', event.data.new_status_id);
  }

  res.sendStatus(200);            // ack fast; do the work after
});

The raw body matters. The signature covers the exact bytes sent. Parsing to an object and re-stringifying changes key order and whitespace, and it will never match. This is the single most common reason verification "doesn't work".

verifySignature is available if you want a boolean instead of an exception.

Respond quickly. Sprint Sync retries 5xx three times with backoff and gives up on 4xx, so slow handlers get retried and duplicated.

Errors

Everything throws SprintSyncError:

import { SprintSyncError } from 'sprint-sync';

try {
  await ss.tasks.create({ projectKey: 'EMPL', name: 'x', priority: 'Urgent' });
} catch (err) {
  err.code;       // 'validation'
  err.status;     // 400
  err.message;    // 'Unknown priority "Urgent". Available: Critical, High, …'
  err.details;    // per-item messages, when the endpoint returns them
  err.retryable;  // false
}

Codes: auth, forbidden, not_found, validation, rate_limit, conflict, server, network.

429, 5xx and network failures are retried twice automatically, honouring Retry-After. A 400 is never retried — the same payload would fail again.

What a key cannot do

A key acts as whoever created it and can never exceed their permissions. On top of that, some things are closed to every key at any scope:

  • Creating, changing or deleting organizations and projects
  • Billing, ownership transfer, roles and membership
  • Creating other keys, or re-pointing webhooks

A leaked key can make a mess of your tasks. It cannot take your account.

Keys can also be limited to specific projects, which is worth doing for anything running unattended in CI.

Options

new SprintSync({
  apiKey: '…',        // or SPRINTSYNC_API_KEY
  baseUrl: '…',       // or SPRINTSYNC_API_URL, for self-hosted
  timeout: 30000,
  maxRetries: 2,
});

Also available

@sprintsync/mcp — connects Sprint Sync to Claude Code, Cursor and other AI tools, so an assistant can read your board and file work onto it directly.

Licence

MIT