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

atlassian-api-client

v1.0.1

Published

Typed Node.js/TypeScript clients and CLI for Atlassian Confluence Cloud REST API v2 and Jira Cloud Platform REST API v3

Readme

atlassian-api-client

Typed Node.js/TypeScript clients and CLI for Atlassian Cloud APIs.

  • Confluence Cloud REST API v2 — Pages, Spaces, Blog Posts, Comments, Attachments, Labels, Content Properties, Custom Content, Whiteboards, Tasks, Versions
  • Jira Cloud Platform REST API v3 — Issues, Projects, Search (JQL), Users, Issue Types, Priorities, Statuses, Issue Comments, Issue Attachments, Labels, Boards, Sprints, Workflows, Dashboards, Filters, Fields, Webhooks, JQL helpers, Bulk operations

Zero runtime dependencies. Uses native fetch (Node.js 24+).

Install

npm install atlassian-api-client

Supported Runtimes

  • Node.js >= 24.0.0

Use with coding agents

A Claude Code skill named atlassian-api-client-cli ships inside this package and teaches coding agents how to drive the atlas CLI safely (env-only auth, first-try gotchas, JQL quoting, pagination, output formats).

# User-wide install, into ~/.claude/skills/atlassian-api-client-cli
npx --package atlassian-api-client -- atlas install-skill

# Project-local install, into <cwd>/.claude/skills/atlassian-api-client-cli
npx --package atlassian-api-client -- atlas install-skill --local

# Print the bundled source path without copying (for symlinks / custom tooling)
npx --package atlassian-api-client -- atlas install-skill --print

# Preview what would be copied
npx --package atlassian-api-client -- atlas install-skill --dry-run

install-skill is a top-level utility command with an options-only shape: run it as atlas install-skill [options].

If atlassian-api-client is already a dependency in your project, the shorter npx atlas install-skill form resolves to node_modules/.bin/atlas and works the same way. The explicit --package form is safer when calling from a clean shell because it pins the source package and won't accidentally resolve an unrelated atlas package from the registry.

The skill source lives at skill/SKILL.md with deeper resource matrices in skill/reference/. It's versioned alongside the npm package: every install stamps the destination SKILL.md with the package version it was copied from.

Quick Start

Confluence

import { ConfluenceClient } from 'atlassian-api-client';

const confluence = new ConfluenceClient({
  baseUrl: 'https://yourcompany.atlassian.net',
  auth: {
    type: 'basic',
    email: '[email protected]',
    apiToken: process.env.ATLASSIAN_API_TOKEN!,
  },
});

// List pages in a space
const pages = await confluence.pages.list({ spaceId: '123456' });
console.log(pages.results);

// Get a specific page
const page = await confluence.pages.get('789');

Jira

import { JiraClient } from 'atlassian-api-client';

const jira = new JiraClient({
  baseUrl: 'https://yourcompany.atlassian.net',
  auth: {
    type: 'basic',
    email: '[email protected]',
    apiToken: process.env.ATLASSIAN_API_TOKEN!,
  },
});

// Get an issue
const issue = await jira.issues.get('PROJ-123');
console.log(issue.fields);

// Search with JQL
const results = await jira.search.search({
  jql: 'project = PROJ AND status = "In Progress"',
});
console.log(results.issues);

Authentication

Basic Auth (Email + API Token)

const client = new ConfluenceClient({
  baseUrl: 'https://yourcompany.atlassian.net',
  auth: {
    type: 'basic',
    email: '[email protected]',
    apiToken: 'your-api-token',
  },
});

Generate an API token at: https://id.atlassian.com/manage-profile/security/api-tokens

Bearer Auth (OAuth 2.0 / PAT)

const client = new JiraClient({
  baseUrl: 'https://yourcompany.atlassian.net',
  auth: {
    type: 'bearer',
    token: 'your-oauth-token',
  },
});

Self-hosted / non-Atlassian baseUrl

By default, only baseUrls whose host ends in .atlassian.{net,com}, .jira-dev.com, or .jira.com are accepted — the transport refuses to send the configured Authorization header to any other host. For self-hosted Jira / Confluence or a reverse proxy in front of Atlassian, pass allowedHosts (bare hostnames, no port) to opt in:

const client = new JiraClient({
  baseUrl: 'https://jira.internal.example',
  auth: { type: 'bearer', token: process.env.PAT! },
  allowedHosts: ['jira.internal.example'],
});

The list must include the baseUrl host itself; resource paths that resolve to a host outside the list throw ValidationError before any HTTP call is made.

Pagination

Async Iteration

// Confluence - cursor-based pagination
for await (const page of confluence.pages.listAll({ spaceId: '123' })) {
  console.log(page.title);
}

// Jira - offset-based pagination
for await (const project of jira.projects.listAll()) {
  console.log(project.name);
}

// Jira search
for await (const issue of jira.search.searchAll({ jql: 'project = PROJ' })) {
  console.log(issue.key);
}

Manual Pagination

// Confluence
const result = await confluence.pages.list({ spaceId: '123', limit: 25 });
console.log(result.results); // current page items
// result._links.next contains cursor for next page

// Jira
const projects = await jira.projects.list({ maxResults: 50 });
console.log(projects.values); // current page items
console.log(projects.total); // total available

Error Handling

import {
  AtlassianError,
  AuthenticationError,
  NotFoundError,
  RateLimitError,
} from 'atlassian-api-client';

try {
  await jira.issues.get('PROJ-999');
} catch (error) {
  if (error instanceof RateLimitError) {
    console.log(`Rate limited. Retry after ${error.retryAfter}s`);
  } else if (error instanceof NotFoundError) {
    console.log('Issue not found');
  } else if (error instanceof AuthenticationError) {
    console.log('Invalid credentials');
  } else if (error instanceof AtlassianError) {
    console.log(`API error: ${error.code} - ${error.message}`);
  }
}

Retry & Timeout

const client = new JiraClient({
  baseUrl: 'https://yourcompany.atlassian.net',
  auth: { type: 'basic', email: '...', apiToken: '...' },
  timeout: 15000, // 15s timeout (default: 30s)
  retries: 5, // max retry attempts (default: 3)
  retryDelay: 2000, // base delay for backoff (default: 1000ms)
  maxRetryDelay: 60000, // max delay cap (default: 30000ms)
});

Retries use exponential backoff with jitter. Retryable: 429, 500, 502, 503, 504, and network errors.

Response Body Size Cap

ClientConfig.maxResponseBytes (default: unset, no cap) bounds the size of any single buffered response body the transport will materialise. When a body exceeds the cap, the request throws ResponseTooLargeError (code: 'RESPONSE_TOO_LARGE_ERROR') instead of loading it into memory.

import { ConfluenceClient, ResponseTooLargeError } from 'atlassian-api-client';

const client = new ConfluenceClient({
  baseUrl: 'https://yourcompany.atlassian.net',
  auth: { type: 'basic', email: '...', apiToken: '...' },
  maxResponseBytes: 50 * 1024 * 1024, // 50 MB cap
});

try {
  await client.pages.getPage('123');
} catch (error) {
  if (error instanceof ResponseTooLargeError) {
    console.error(`Response exceeded ${error.limitBytes} bytes (status: ${error.status ?? 'n/a'})`);
  }
}

Enforcement applies to responseType: 'json' and 'arrayBuffer' AND to the error-response body parsed for error-message extraction — so a misconfigured upstream returning a multi-gigabyte 5xx body cannot exhaust the Node heap on a single request. responseType: 'stream' is exempt by design: the caller owns drain/abort of the ReadableStream. Detection combines a Content-Length fast-fail with a running stream-read tally that cancels the body mid-read on overflow.

Middleware

HttpTransport accepts an optional middleware chain for cross-cutting concerns.

OAuth 2.0 Token Refresh

import { ConfluenceClient, createOAuthRefreshMiddleware } from 'atlassian-api-client';

const oauthMiddleware = createOAuthRefreshMiddleware({
  accessToken: process.env.ACCESS_TOKEN!,
  refreshToken: process.env.REFRESH_TOKEN!,
  clientId: process.env.CLIENT_ID!,
  clientSecret: process.env.CLIENT_SECRET!,
  onTokenRefreshed: (tokens) => {
    // Persist the new tokens
    saveTokens(tokens.accessToken, tokens.refreshToken);
  },
});

const client = new ConfluenceClient({
  baseUrl: 'https://yourcompany.atlassian.net',
  auth: { type: 'bearer', token: process.env.ACCESS_TOKEN! },
  middleware: [oauthMiddleware],
});

Automatically injects Authorization: Bearer and silently refreshes on 401 responses.

Token endpoint allowlist (security): tokenEndpoint defaults to https://auth.atlassian.com/oauth/token, and only that host is accepted by default. The validation happens at createOAuthRefreshMiddleware construction time — a misconfigured endpoint (typo, poisoned env var) throws ValidationError before any HTTP traffic, instead of POSTing client_id + client_secret + refresh_token to an attacker host on the first 401. For self-hosted IdPs, proxied auth, or staging endpoints, opt in explicitly:

createOAuthRefreshMiddleware({
  accessToken: '...',
  refreshToken: '...',
  clientId: '...',
  clientSecret: '...',
  tokenEndpoint: 'https://idp.internal.example/oauth/token',
  // REPLACES the default — mirrors ClientConfig.allowedHosts semantics.
  allowedTokenEndpointHosts: ['idp.internal.example'],
});

This is a separate allowlist from ClientConfig.allowedHosts because the OAuth refresh code path calls fetch directly and bypasses the transport-side check by design.

Herd protection (stability): when many concurrent requests hit a 401 at the same time, the middleware already deduplicates the token exchange to a single in-flight refresh. Two additional knobs flatten the surrounding failure modes:

createOAuthRefreshMiddleware({
  accessToken: '...',
  refreshToken: '...',
  clientId: '...',
  clientSecret: '...',
  retryJitterMs: 100, // default — spread post-refresh retries over 0..100ms
  failureCooldownMs: 1000, // default — replay a refresh failure for 1s instead of re-firing
});
  • retryJitterMs (default 100, 0 disables) staggers each waiter's retry after the shared refresh resolves, so N concurrent requests don't dispatch N simultaneous retried API calls and stampede a just-recovered backend or re-trigger upstream rate-limits.
  • failureCooldownMs (default 1000, 0 disables) caches the most recent refresh failure for the configured duration. Subsequent 401s during the window replay the cached error (preserving the original OAuthError for debugging) without firing a new token-endpoint call — so an auth-server outage no longer becomes an unbounded refresh loop.

Both are validated as non-negative finite numbers at construction; the jitter sleep honours RequestOptions.signal so an aborted caller doesn't pay the delay.

Atlassian Connect JWT

import { createConnectJwtMiddleware } from 'atlassian-api-client';

const connectMiddleware = createConnectJwtMiddleware({
  issuer: 'com.example.my-app',
  sharedSecret: process.env.CONNECT_SECRET!,
});

const client = new JiraClient({
  baseUrl: 'https://yourcompany.atlassian.net',
  auth: { type: 'bearer', token: '' },
  middleware: [connectMiddleware],
});

Signs every request with an HS256 JWT per the Atlassian Connect spec (QSH, iss, iat, exp claims).

Response Caching

import { createCacheMiddleware } from 'atlassian-api-client';

const cacheMiddleware = createCacheMiddleware({
  ttl: 30_000, // 30s TTL (default: 60s)
  maxSize: 200, // max entries (default: 100, FIFO eviction)
});

const client = new ConfluenceClient({
  baseUrl: 'https://yourcompany.atlassian.net',
  auth: { type: 'basic', email: '...', apiToken: '...' },
  middleware: [cacheMiddleware],
});

Caches GET responses in memory. Keyed by method + path + query string. Lazily evicts expired entries.

Request Batching / Deduplication

import { createBatchMiddleware } from 'atlassian-api-client';

const client = new JiraClient({
  baseUrl: 'https://yourcompany.atlassian.net',
  auth: { type: 'basic', email: '...', apiToken: '...' },
  middleware: [createBatchMiddleware()],
});

Coalesces concurrent identical in-flight requests so only one HTTP call is made.

OAuth Scope Detection

Map Atlassian operation names to the required Cloud OAuth 2.0 scopes:

import { detectRequiredScopes, listKnownOperations } from 'atlassian-api-client';

const scopes = detectRequiredScopes(['jira.issues.create', 'confluence.pages.get']);
// → ['write:jira-work', 'read:confluence-content.all']

const allOps = listKnownOperations();
// → ['confluence.pages.create', 'confluence.pages.delete', ...]

OpenAPI Type Generation

Generate TypeScript interfaces from an OpenAPI 3.x schema:

import { generateTypes } from 'atlassian-api-client';

const spec = {
  components: {
    schemas: {
      Issue: {
        type: 'object',
        properties: {
          id: { type: 'string' },
          summary: { type: 'string', nullable: true },
        },
      },
    },
  },
};

const { source } = generateTypes(spec);
// → 'export interface Issue { id?: string; summary?: string | null; }'

Supports $ref, allOf, oneOf, anyOf, enum, nullable, and additionalProperties.

CLI

The atlas CLI provides command-line access to both APIs.

# Install globally
npm install -g atlassian-api-client

# Or use via npx
npx -p atlassian-api-client atlas --help

Syntax

atlas <api> <resource> <action> [args] [options]

Auth

Via flags or environment variables:

export ATLASSIAN_BASE_URL=https://yourcompany.atlassian.net
export [email protected]
export ATLASSIAN_API_TOKEN=your-token

# Or pass inline
atlas --base-url https://... --email user@... --token ...

Self-hosted / non-Atlassian baseUrl

For security, the CLI's default host allowlist only accepts *.atlassian.{net,com}, *.jira-dev.com, and *.jira.com — calls outside that suffix list fail with ValidationError. Self-hosted or proxied deployments must opt in with --allowed-hosts (or the ATLASSIAN_ALLOWED_HOSTS env var). Entries are bare hostnames (no scheme, no port) and must include the baseUrl host itself:

atlas confluence spaces list \
  --base-url https://jira.internal.example \
  --allowed-hosts jira.internal.example

Examples

# Confluence
atlas confluence pages list --space-id 123
atlas confluence pages get 456
atlas confluence spaces list

# Jira
atlas jira issues get PROJ-123
atlas jira projects list
atlas jira search --jql "project = PROJ AND status = Open"
atlas jira users me

# Output formats
atlas jira issues get PROJ-123 --format table
atlas jira projects list --format minimal

API Reference

ConfluenceClient

| Resource | Methods | | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | pages | list, get, create, update, delete, listAll | | spaces | list, get, listAll | | blogPosts | list, get, create, update, delete, listAll | | comments | listFooter, getFooter, createFooter, updateFooter, deleteFooter, listInline, getInline, createInline, updateInline, deleteInline | | attachments | listForPage, get, upload, delete, listAllForPage | | labels | listForPage, listForSpace, listForBlogPost, listAllForPage | | contentProperties | list, get, create, update, delete | | customContent | list, get, create, update, delete | | whiteboards | get, create, delete | | tasks | list, get, update | | versions | listForPage, getForPage, listForBlogPost, getForBlogPost |

JiraClient

| Resource | Methods | | ------------------ | -------------------------------------------------------------------------- | | issues | get, create, update, delete, getTransitions, transition | | projects | list, get, listAll | | search | search, searchGet, searchAll | | users | get, getCurrentUser, search | | issueTypes | list, get | | priorities | list, get | | statuses | list | | issueComments | list, get, create, update, delete | | issueAttachments | list, get, upload | | labels | list | | boards | list, get, getIssues | | sprints | get, create, update, delete, getIssues | | workflows | list, get | | dashboards | list, get, create, update, delete | | filters | list, get, create, update, delete | | fields | list, listAll, create, update, delete | | webhooks | list, register, delete | | jql | getAutocompleteData, parse, sanitize, getFieldReferenceSuggestions | | bulk | createBulk, setPropertyBulk, deletePropertyBulk |

Recipes

Copy-paste snippets for common setups. Each recipe is self-contained.

Custom logger

Warnings the client emits through its configured logger, such as rate-limit proximity and deprecated constructor usage, are routed through the logger you provide.

import { ConfluenceClient, type Logger } from 'atlassian-api-client';
import pino from 'pino';

const pinoLogger = pino();
const logger: Logger = {
  debug: (msg, ctx) => pinoLogger.debug(ctx, msg),
  info: (msg, ctx) => pinoLogger.info(ctx, msg),
  warn: (msg, ctx) => pinoLogger.warn(ctx, msg),
  error: (msg, ctx) => pinoLogger.error(ctx, msg),
};

const client = new ConfluenceClient({
  baseUrl: 'https://yourcompany.atlassian.net',
  auth: { type: 'basic', email, apiToken },
  logger,
});

Proxy / custom fetch dispatcher

Inject an undici-powered fetch to route every request through a proxy or tune keep-alive. The custom fetch is used by the transport and by OAuth token-refresh calls.

import { ConfluenceClient } from 'atlassian-api-client';
import { fetch as undiciFetch, ProxyAgent } from 'undici';

const dispatcher = new ProxyAgent('http://proxy.internal:8080');
const client = new ConfluenceClient({
  baseUrl: 'https://yourcompany.atlassian.net',
  auth: { type: 'basic', email, apiToken },
  fetch: ((url, init) => undiciFetch(url, { ...init, dispatcher })) as typeof fetch,
});

OAuth 2.0 with token persistence

createOAuthRefreshMiddleware injects the access token on every request and refreshes automatically on a 401. A shared in-flight refresh promise prevents token-endpoint stampedes; the retryJitterMs and failureCooldownMs knobs (see the OAuth 2.0 Token Refresh section) extend that protection to the post-refresh retry burst and the auth-server-outage loop. Use onTokenRefreshed to persist new tokens so worker restarts don't lose them.

import { JiraClient, createOAuthRefreshMiddleware } from 'atlassian-api-client';
import { readFile, writeFile } from 'node:fs/promises';

const tokens = JSON.parse(await readFile('.atlassian-tokens.json', 'utf8'));

const client = new JiraClient({
  baseUrl: 'https://yourcompany.atlassian.net',
  auth: { type: 'bearer', token: tokens.accessToken }, // initial header; middleware keeps it fresh
  middleware: [
    createOAuthRefreshMiddleware({
      accessToken: tokens.accessToken,
      refreshToken: tokens.refreshToken,
      clientId: process.env.ATLASSIAN_CLIENT_ID!,
      clientSecret: process.env.ATLASSIAN_CLIENT_SECRET!,
      // tokenEndpoint defaults to 'https://auth.atlassian.com/oauth/token'
      onTokenRefreshed: async (next) => {
        await writeFile(
          '.atlassian-tokens.json',
          JSON.stringify({ accessToken: next.accessToken, refreshToken: next.refreshToken }),
        );
      },
    }),
  ],
});

Retry tuning

Override the defaults per client. Non-retryable statuses (4xx except 429) are never retried regardless of retries.

const client = new JiraClient({
  baseUrl: 'https://yourcompany.atlassian.net',
  auth: { type: 'bearer', token },
  retries: 5, // default 3
  retryDelay: 500, // default 1000 ms (base for exponential backoff)
  maxRetryDelay: 15_000, // default 30_000 ms (ceiling)
  timeout: 20_000, // default 30_000 ms (per-request AbortController)
});

Caching + batching

For a read-heavy dashboard, layer the cache outermost under auth so every request still carries a fresh token, and batch innermost so concurrent identical requests collapse into one fetch. See docs/ARCHITECTURE.md#middleware-ordering for the full ordering rationale.

import {
  JiraClient,
  createCacheMiddleware,
  createBatchMiddleware,
  createOAuthRefreshMiddleware,
} from 'atlassian-api-client';

const client = new JiraClient({
  baseUrl: 'https://yourcompany.atlassian.net',
  auth: { type: 'bearer', token: accessToken },
  middleware: [
    createOAuthRefreshMiddleware({
      /* … */
    }),
    createCacheMiddleware({ ttl: 30_000, maxSize: 500 }),
    createBatchMiddleware(),
  ],
});

Architecture

See docs/ARCHITECTURE.md for a detailed description of the layered design, core infrastructure, and key design decisions.

Development

# Install
npm install

# Build
npm run build

# Type check
npm run typecheck

# Lint
npm run lint

# Test
npm run test

# Test with coverage
npm run test:coverage

# Full validation
npm run validate

License

MIT