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

@anikghosh256/fortnox-node-sdk

v0.3.0

Published

Modern Node.js SDK for Fortnox API with OAuth 2.0, automatic token refresh, and support for Articles, Customers, Orders, Invoices, Price Lists, and Pricing

Readme

Fortnox Node.js SDK

Modern TypeScript SDK for Fortnox API with OAuth 2.0 and automatic token refresh.

CI npm version License: MIT

Features

  • OAuth 2.0 authentication with CSRF protection
  • Automatic token refresh (proactive before expiry)
  • Config-locked instances (isolated per instance)
  • Native fetch (Node.js 18+, zero dependencies)
  • Full TypeScript support
  • Comprehensive error handling
  • Articles CRUD operations
  • Customers management
  • Orders management with invoicing support
  • Invoices management - create, bookkeep, email, print, credit
  • Price Lists and Pricing management

Installation

npm install @anikghosh256/fortnox-node-sdk

Requirements: Node.js >= 18.0.0

Usage

Initialize SDK

import { FortnoxClient } from '@anikghosh256/fortnox-node-sdk';

const client = new FortnoxClient({
  clientId: 'your-client-id',
  clientSecret: 'your-client-secret',
  redirectUri: 'https://yourapp.com/callback',
  scopes: ['article', 'companyinformation'],
  log: true, // Enable debug logging (optional)
});

OAuth Authentication

// Generate authorization URL
const { url, state } = client.getAuthManager().getAuthorizationUrl();
// Store state in session: req.session.state = state
// Redirect user: res.redirect(url)

// Handle callback
const tokens = await client.getAuthManager().exchangeCodeForToken(code, state);

Articles API

// List
const articles = await client.articles.list({ page: 1, limit: 10 });

// Get
const article = await client.articles.get('ART001');

// Create
const newArticle = await client.articles.create({
  Description: 'Product Name',
  SalesPrice: 99.99,
  VAT: 25,
});

// Update
await client.articles.update('ART001', { SalesPrice: 129.99 });

// Delete
await client.articles.delete('ART001');

Customers API

// List all customers
const customers = await client.customers.list();

// List active customers only
const activeCustomers = await client.customers.list({ 
  filter: 'active',
  sortby: 'name'
});

// Search customers by name, city, email, etc.
const searchResults = await client.customers.list({
  name: 'Acme',
  city: 'Stockholm',
  email: '[email protected]',
});

// Get specific customer
const customer = await client.customers.get('CUST001');

// Create new customer
const newCustomer = await client.customers.create({
  Name: 'Acme Corporation AB',
  Type: 'COMPANY',
  OrganisationNumber: '555555-5555',
  Email: '[email protected]',
  Phone1: '+46 8 123 456',
  Address1: 'Main Street 123',
  City: 'Stockholm',
  ZipCode: '11122',
  CountryCode: 'SE',
  Currency: 'SEK',
  VATType: 'SEVAT',
  Active: true,
  PriceList: 'A',
  TermsOfPayment: '30',
});

// Update customer
const updated = await client.customers.update('CUST001', {
  Email: '[email protected]',
  Phone1: '+46 8 987 654',
  Comments: 'VIP customer - priority handling',
});

// Delete customer
await client.customers.delete('CUST001');

Orders API

// List orders with filters
const orders = await client.orders.list({
  customernumber: 'C001',
  orderdatefrom: '2024-01-01',
  orderdateto: '2024-12-31',
  notcompleted: true,
  page: 1,
  limit: 10,
});

// Get single order
const order = await client.orders.get('1');

// Create order
const newOrder = await client.orders.create({
  CustomerNumber: 'C001',
  OrderDate: '2024-01-15',
  DeliveryDate: '2024-01-20',
  OrderRows: [
    {
      ArticleNumber: 'ART001',
      OrderedQuantity: '5',
      Price: 150,
      VAT: 25,
      Discount: 10,
    },
  ],
  Comments: 'Urgent order',
  Language: 'EN',
  Freight: 50,
});

// Update order
await client.orders.update('1', {
  Comments: 'Updated comment',
  DeliveryDate: '2024-01-18',
});

// Cancel order
await client.orders.cancel('1');

// Create invoice from order
const invoice = await client.orders.createInvoice('1');

// Send order by email
await client.orders.sendEmail('1');

// Print order (returns PDF buffer)
const pdfBuffer = await client.orders.print('1');

// Preview order (returns PDF buffer without marking as printed)
const previewBuffer = await client.orders.preview('1');

// Get external print template
const printData = await client.orders.externalPrint('1');

Invoices API

// List invoices with filters
const invoices = await client.invoices.list({
  filter: 'unpaid',
  customernumber: 'CUST001',
  fromdate: '2024-01-01',
  todate: '2024-12-31',
  sortby: 'invoicedate',
});

// Get specific invoice
const invoice = await client.invoices.get('1');

// Create invoice
const newInvoice = await client.invoices.create({
  CustomerNumber: 'CUST001',
  InvoiceDate: '2024-02-20',
  DueDate: '2024-03-20',
  DeliveryDate: '2024-02-22',
  Comments: 'Thank you for your business',
  Language: 'EN',
  Currency: 'SEK',
  InvoiceRows: [
    {
      ArticleNumber: 'ART001',
      Description: 'Consulting Services',
      Price: 1500,
      VAT: 25,
      Discount: 10,
    },
  ],
  OurReference: 'John Doe',
  YourReference: 'Jane Smith',
  Freight: 100,
  AdministrationFee: 50,
});

// Update invoice
await client.invoices.update('1', {
  Comments: 'Updated payment terms',
  Remarks: 'Payment within 45 days',
});

// Bookkeep invoice (finalize for accounting)
const bookedInvoice = await client.invoices.bookkeep('1');

// Cancel invoice
const cancelledInvoice = await client.invoices.cancel('1');

// Create credit invoice
const creditInvoice = await client.invoices.credit('1');

// Send invoice by email
await client.invoices.sendEmail('1');

// Print invoice (returns PDF buffer)
const pdfBuffer = await client.invoices.print('1');

// Preview invoice (returns PDF without marking as sent)
const previewBuffer = await client.invoices.preview('1');

// Mark invoice as sent (external print)
await client.invoices.externalPrint('1');

// Create invoice with email information
const invoiceWithEmail = await client.invoices.create({
  CustomerNumber: 'CUST001',
  InvoiceDate: '2024-02-20',
  Currency: 'SEK',
  InvoiceRows: [{ ArticleNumber: 'ART001', Price: 999, VAT: 25 }],
  EmailInformation: {
    EmailAddressTo: '[email protected]',
    EmailSubject: 'Your Invoice',
    EmailBody: 'Thank you for your business!',
  },
});

Price Lists API

// List all price lists
const priceLists = await client.priceLists.list();

// Get specific price list
const priceList = await client.priceLists.get('A');

// Create new price list
const newPriceList = await client.priceLists.create({
  Code: 'WHOLESALE',
  Description: 'Wholesale Prices',
  Comments: 'For wholesale customers',
  PreSelected: false,
});

// Update price list
await client.priceLists.update('WHOLESALE', {
  Description: 'Updated Wholesale Prices',
  Comments: 'New comment',
});

// Delete price list
await client.priceLists.delete('WHOLESALE');

Prices API

// List all prices (optionally filter by price list)
const allPrices = await client.prices.list({
  pricelist: 'A',
  articlenumber: 'ART001',
});

// Get specific price
const price = await client.prices.get('A', 'ART001');

// Get price for specific quantity level (volume pricing)
const bulkPrice = await client.prices.get('A', 'ART001', 100);

// Create new price (using fixed price)
await client.prices.create({
  ArticleNumber: 'ART001',
  PriceList: 'WHOLESALE',
  Price: 85.00,
  FromQuantity: 1,
});

// Create price using percentage discount
await client.prices.create({
  ArticleNumber: 'ART001',
  PriceList: 'VIP',
  Percent: -15, // 15% discount
  FromQuantity: 1,
});

// Create volume discount (bulk pricing)
await client.prices.create({
  ArticleNumber: 'ART001',
  PriceList: 'WHOLESALE',
  Price: 75.00, // Lower price
  FromQuantity: 100, // When buying 100+
});

// Update price
await client.prices.update('WHOLESALE', 'ART001', 1, {
  Price: 80.00,
});

// Delete price
await client.prices.delete('WHOLESALE', 'ART001', 1);

Configuration

| Option | Type | Required | Description | |--------|------|----------|-------------| | clientId | string | Yes | Fortnox client ID | | clientSecret | string | Yes | Fortnox client secret | | redirectUri | string | Yes | OAuth redirect URI | | scopes | string[] | No | API scopes (default: ['article']) | | baseUrl | string | No | API base URL | | log | boolean | No | Enable debug logging (default: false) | | onTokenRefresh | function | No | Callback when tokens refresh | | onTokenExpire | function | No | Callback when refresh token expires | | initialAccessToken | string | No | Existing access token | | initialRefreshToken | string | No | Existing refresh token |

Token Management

Access tokens are automatically refreshed 5 minutes before expiry.

Persist Tokens

const client = new FortnoxClient({
  clientId: 'your-client-id',
  clientSecret: 'your-client-secret',
  redirectUri: 'https://yourapp.com/callback',
  initialAccessToken: loadedAccessToken,
  initialRefreshToken: loadedRefreshToken,
  log: false, // Disable logging in production
  onTokenRefresh: async (tokens) => {
    await db.saveTokens(tokens);
  },
});

Token Lifetimes:

  • Access token: 1 hour
  • Refresh token: 45 days
  • Both tokens regenerate on refresh

Error Handling

import { AuthenticationError, ValidationError, NotFoundError } from '@anikghosh256/fortnox-node-sdk';

try {
  const article = await client.articles.get('ART001');
} catch (error) {
  if (error instanceof AuthenticationError) {
    console.error('Auth failed:', error.message);
  } else if (error instanceof NotFoundError) {
    console.error('Not found:', error.message);
  } else if (error instanceof ValidationError) {
    console.error('Invalid input:', error.message);
  }
}

Error Types:

  • AuthenticationError - 401/403 responses
  • ValidationError - 400 responses
  • NotFoundError - 404 responses
  • RateLimitError - 429 responses
  • ApiError - Other API errors

Development

npm install          # Install dependencies
npm test             # Run tests
npm run build        # Build package
npm run lint         # Lint code

License

MIT © Onik G.

Links