@shortcut/client
    Preparing search index...

    @shortcut/client

    Shortcut's logo

    Version Monthly Downloads GitHub License PRs welcome! X

    A library for interacting with the Shortcut REST API.


    npm install @shortcut/client
    
    Important

    Our legacy npm organization namespace is @useshortcut.

    While it will still contain copies of all future released package versions, we strongly recommend switching to our primary @shortcut organization namespace.

    The Shortcut API uses token-based authentication, you will need one to use this library.

    To generate an API token, go to https://app.shortcut.com/settings/account/api-tokens.

    To make it easier to explore our API, we recommend saving this token as an environment variable in your local dev environment:

    export SHORTCUT_API_TOKEN="YOUR API TOKEN HERE"
    

    This will allow you to copy and paste many examples in the documentation to try them out.

    Note

    Requests made with a missing or invalid token will get a 401 Unauthorized response.

    Note

    All requests must be made over HTTPS.

    Caution

    Tokens provide complete access to your Shortcut account, so keep them secure.

    Don’t paste them into your source code, use an environment variable instead.

    For security reasons, we will immediately invalidate any tokens we find have been made public.

    To see all available exports, take a look at the API documentation or check out the .d.ts files in this repository.

    import { ShortcutClient } from '@shortcut/client';
    // const { ShortcutClient } = require('@shortcut/client');

    const shortcut = new ShortcutClient('YOUR_API_TOKEN'); // See https://github.com/useshortcut/shortcut-client-js#how-to-get-an-api-token

    shortcut.getCurrentMemberInfo().then((response) => console.log(response?.data));

    shortcut.listProjects().then((response) => console.log(response?.data));

    Two additional entrypoints cover the REST API v4 and the webhooks that Custom Agents receive. Both use the Fetch API, so they run on Node.js 20+, Cloudflare Workers, Deno, and Bun.

    v3 and v4 ship together in this package because v4 does not yet cover every v3 endpoint, and agents need both from one install. Each entrypoint is independent, so an application that imports only one never bundles the other. Once v4 reaches parity, the next major version will drop the v3 entrypoint.

    v4 is workspace-scoped: every operation takes the workspace slug first, and workspace(slug) binds it once. The generated operations URL-encode their path parameters (the slug, member ids, external ids, ...), so pass raw values. Requests that fail reject with the Response, whose error carries the parsed body, the raw text when the body is not JSON, or null when it is empty, and whose request names the method and URL pathname (never the query) that produced it. summarizeShortcutV4Error(error) reduces a rejection to method, path, status, and the body's identifier-shaped tag and error codes, which is safe to log without echoing user content, cursors, or credentials. Lists page by cursor; paginate() follows next_page_url and only sends the token back to the same API origin. Every request, including refresh waits, retries, and reading its body, is aborted with a TimeoutError after timeoutMs (30 s by default; Infinity disables it), which composes with a per-request signal or cancelToken.

    import {
    ShortcutV4Client,
    isShortcutV4RequestError,
    summarizeShortcutV4Error,
    } from '@shortcut/client/v4';

    const client = new ShortcutV4Client({ token: process.env.SHORTCUT_TOKEN });
    const acme = client.workspace('acme');

    const { entity: story } = await acme.getStory(123, {
    fields: 'name,team,workflow_state',
    });

    for await (const comment of client.paginate(
    acme.listStoryComments(123, { fields: 'id,author,deleted', limit: 100 }),
    )) {
    console.log(comment.id);
    }

    try {
    await acme.createStoryComment(
    123,
    { text: 'Hello from an agent' },
    { fields: 'id' },
    );
    } catch (error) {
    // e.g. { method: 'POST', path: '/api/v4/acme/stories/123/comments', status: 422, tag: 'invalid_params' }
    console.error(summarizeShortcutV4Error(error));
    if (isShortcutV4RequestError(error) && error.status === 404) {
    // error.error is the parsed body, the raw text, or null
    }
    }

    <<<<<<< HEAD Agent apps authenticate with OAuth per workspace. ShortcutOAuth completes the authorization-code exchange and refreshes tokens; the response's permission_id is the agent's own member id, which deliveries report as actor.member_id for changes the agent made. Deliveries also carry actor.mention_name when the actor is a member, so an agent can address the person in a comment without fetching them. A refresh response may omit the workspace fields; pass the previous tokens to refreshAccessToken and it resolves the merged result, keeping them and scope when omitted (applyRefresh does the merge on its own). Token requests share the same timeoutMs option (30 s by default, Infinity disables it, and it covers reading the body).

    The client rotates the token itself when given refresh: it calls run before a request once expiresAt is within beforeMs (five minutes by default) and once more when a request comes back 401, then retries that request. Concurrent requests share one run; a delayed 401 from an older token retries with the token already refreshed by another request. A second 401 rejects as usual. The request deadline and cancellation cover refresh waits and the retry. Cancelling or timing out one request stops its wait without interrupting a shared refresh. run does the persistence and returns the new token, and its time counts against the request's timeoutMs, so raise that on the client when the token store is slow.

    import { ShortcutOAuth, ShortcutV4Client } from '@shortcut/client/v4';

    const oauth = new ShortcutOAuth({ clientId, clientSecret, redirectUri });
    let tokens = await oauth.exchangeAuthorizationCode(code);
    await store.save(tokens);

    const client = new ShortcutV4Client({
    token: tokens.access_token,
    refresh: {
    expiresAt: tokens.access_token_expires_at,
    run: async () => {
    tokens = await oauth.refreshAccessToken(tokens);
    await store.save(tokens);
    return {
    token: tokens.access_token,
    expiresAt: tokens.access_token_expires_at,
    };
    },
    },
    });

    Deliveries are signed with HMAC-SHA256 over the raw request body; the hex digest arrives in the Payload-Signature header. ShortcutWebhookClient verifies the signature in constant time, caps the body (2 MiB by default), rejects payloads that lack the delivery envelope or carry an unrecognized action or trigger shape, and optionally pins deliveries to one workspace or installation. createHandler() works as a Fetch handler and as a Node.js (req, res) handler, and dispatches typed payloads by kind and by interaction trigger. The Node handler is typed structurally (ShortcutNodeRequest, ShortcutNodeResponse), so the entrypoint's declarations never import node:http and other runtimes need no @types/node.

    import { ShortcutWebhookClient } from '@shortcut/client/webhooks';

    const webhooks = new ShortcutWebhookClient(process.env.WEBHOOK_SECRET, {
    workspaceId: process.env.WORKSPACE_ID, // optional: 403 for any other workspace
    });
    const handler = webhooks.createHandler();

    handler.on('mentioned', async (payload) => {
    // payload.trigger is narrowed to the `mentioned` shape
    });
    handler.on('observer', async (payload) => {
    for (const action of payload.actions) {
    // action.changes is present on story updates only; absent means unavailable
    }
    });

    export default { fetch: handler }; // Cloudflare Workers
    // http.createServer(handler); // Node.js

    For frameworks that already read the body, verify(request) and verifyBody(bytes, signature) return the verified payload or throw a ShortcutWebhookError carrying the HTTP status to return. signShortcutWebhookBody() produces a valid signature for testing an agent locally.

    The v4 client is generated from schema/shortcut.v4.openapi.json, which yarn sync:schema:v4 downloads and normalizes (the published document names responses and request bodies numerically).

    You can play with it in your web browser with this live playground:

    Documentation for this client.

    Documentation for the REST API.

    This library uses GitHub Actions for CI/CD. npm releases are staged automatically, then approved manually in npm.

    To publish a new version:

    1. Update the version in package.json
    2. Commit and push to main
    3. Create a GitHub Release with a version tag (e.g., v2.4.0)
    4. The publish workflow will automatically build and stage the package versions on npm
    5. Review and approve both staged packages in npm to publish them live

    Staging is triggered automatically when a GitHub Release is created:

    1. Create a GitHub Release: Go to the repository's Releases page and create a new release with a version tag (e.g., v2.4.0)
    2. The publish.yml workflow will stage @shortcut/client, then stage the same built package under the legacy @useshortcut/client namespace
    3. Review the staged packages in npm using npm stage list, npm stage view <stage-id>, or the npmjs.com Staged Packages tab
    4. Approve both staged packages with 2FA using npm stage approve <stage-id> or the npmjs.com approval flow

    The npm trusted publisher for this repository must allow npm stage publish for .github/workflows/publish.yml. Trusted publishing from GitHub Actions automatically generates provenance for public packages from public repositories. Staged publishing requires npm CLI 11.15.0 or later and Node.js 22.14.0 or later.

    API documentation is automatically deployed to GitHub Pages on every push to main:

    1. The gh-pages.yml workflow builds the documentation using TypeDoc
    2. Documentation is published to https://useshortcut.github.io/shortcut-client-js/